problemreductions/models/algebraic/
equilibrium_point.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
9use crate::traits::Problem;
10use crate::types::Or;
11use serde::de::Error as _;
12use serde::{Deserialize, Deserializer, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "EquilibriumPoint",
17 display_name: "Equilibrium Point",
18 aliases: &[],
19 dimensions: &[],
20 module_path: module_path!(),
21 description: "Decide whether a pure-strategy Nash equilibrium exists for a multi-player game with polynomial payoff functions",
22 fields: &[
23 FieldInfo {
24 name: "polynomials",
25 type_name: "Vec<Vec<Vec<i64>>>",
26 description: "polynomials[i] is a list of affine factors for F_i; each factor [a0,a1,...,an] represents a0 + a1*x1 + ... + an*xn",
27 },
28 FieldInfo {
29 name: "range_sets",
30 type_name: "Vec<Vec<i64>>",
31 description: "range_sets[i] is the finite strategy set M_i for player i",
32 },
33 ],
34 }
35}
36
37inventory::submit! {
38 ProblemSizeFieldEntry {
39 name: "EquilibriumPoint",
40 fields: &["num_players"],
41 }
42}
43
44#[derive(Debug, Clone, Serialize)]
80pub struct EquilibriumPoint {
81 polynomials: Vec<Vec<Vec<i64>>>,
84 range_sets: Vec<Vec<i64>>,
86}
87
88impl EquilibriumPoint {
89 fn validate_inputs(
90 polynomials: &[Vec<Vec<i64>>],
91 range_sets: &[Vec<i64>],
92 ) -> Result<(), String> {
93 let n = polynomials.len();
94 if range_sets.len() != n {
95 return Err(format!(
96 "polynomials has {n} entries but range_sets has {} entries; lengths must match",
97 range_sets.len()
98 ));
99 }
100 for (i, m) in range_sets.iter().enumerate() {
101 if m.is_empty() {
102 return Err(format!("range_sets[{i}] must be non-empty"));
103 }
104 }
105 let expected_factor_len = n + 1;
107 for (i, factors) in polynomials.iter().enumerate() {
108 for (j, factor) in factors.iter().enumerate() {
109 if factor.len() != expected_factor_len {
110 return Err(format!(
111 "polynomials[{i}][{j}] has {} coefficients but expected {expected_factor_len} (1 + num_players)",
112 factor.len()
113 ));
114 }
115 }
116 }
117 Ok(())
118 }
119
120 pub fn new(polynomials: Vec<Vec<Vec<i64>>>, range_sets: Vec<Vec<i64>>) -> Result<Self, String> {
122 Self::validate_inputs(&polynomials, &range_sets)?;
123 Ok(Self {
124 polynomials,
125 range_sets,
126 })
127 }
128
129 pub fn num_players(&self) -> usize {
131 self.polynomials.len()
132 }
133
134 pub fn polynomials(&self) -> &[Vec<Vec<i64>>] {
136 &self.polynomials
137 }
138
139 pub fn range_sets(&self) -> &[Vec<i64>] {
141 &self.range_sets
142 }
143
144 fn eval_payoff(&self, player: usize, assignment: &[i64]) -> i64 {
148 let factors = &self.polynomials[player];
149 if factors.is_empty() {
150 return 0;
151 }
152 factors.iter().fold(1i64, |prod, coeffs| {
153 let val: i64 = coeffs[0]
155 + coeffs[1..]
156 .iter()
157 .zip(assignment.iter())
158 .map(|(&c, &y)| c * y)
159 .sum::<i64>();
160 prod * val
161 })
162 }
163}
164
165#[derive(Deserialize)]
166struct EquilibriumPointData {
167 polynomials: Vec<Vec<Vec<i64>>>,
168 range_sets: Vec<Vec<i64>>,
169}
170
171impl<'de> Deserialize<'de> for EquilibriumPoint {
172 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
173 where
174 D: Deserializer<'de>,
175 {
176 let data = EquilibriumPointData::deserialize(deserializer)?;
177 Self::new(data.polynomials, data.range_sets).map_err(D::Error::custom)
178 }
179}
180
181impl Problem for EquilibriumPoint {
182 const NAME: &'static str = "EquilibriumPoint";
183 type Value = Or;
184
185 fn variant() -> Vec<(&'static str, &'static str)> {
186 crate::variant_params![]
187 }
188
189 fn dims(&self) -> Vec<usize> {
190 self.range_sets.iter().map(|m| m.len()).collect()
191 }
192
193 fn evaluate(&self, config: &[usize]) -> Or {
194 let n = self.num_players();
195 if config.len() != n {
196 return Or(false);
197 }
198 for (i, &idx) in config.iter().enumerate() {
200 if idx >= self.range_sets[i].len() {
201 return Or(false);
202 }
203 }
204
205 let assignment: Vec<i64> = config
207 .iter()
208 .enumerate()
209 .map(|(i, &idx)| self.range_sets[i][idx])
210 .collect();
211
212 for i in 0..n {
214 let current_payoff = self.eval_payoff(i, &assignment);
215 let mut best_response_satisfied = true;
217 for &alt in &self.range_sets[i] {
218 if alt == assignment[i] {
219 continue;
220 }
221 let mut alt_assignment = assignment.clone();
223 alt_assignment[i] = alt;
224 let alt_payoff = self.eval_payoff(i, &alt_assignment);
225 if alt_payoff > current_payoff {
226 best_response_satisfied = false;
227 break;
228 }
229 }
230 if !best_response_satisfied {
231 return Or(false);
232 }
233 }
234 Or(true)
235 }
236}
237
238crate::declare_variants! {
239 default EquilibriumPoint => "2^num_players",
240}
241
242#[cfg(feature = "example-db")]
243pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
244 let polynomials = vec![
255 vec![vec![0, 1, 0, 0], vec![0, 0, 1, 0], vec![0, 0, 0, 1]],
256 vec![vec![1, -1, 0, 0], vec![0, 0, 1, 0]],
257 vec![vec![0, 1, 0, 0], vec![1, 0, 0, -1]],
258 ];
259 let range_sets = vec![vec![0, 1], vec![0, 1], vec![0, 1]];
260 vec![crate::example_db::specs::ModelExampleSpec {
261 id: "equilibrium_point",
262 instance: Box::new(EquilibriumPoint::new(polynomials, range_sets).unwrap()),
263 optimal_config: vec![0, 1, 0],
264 optimal_value: serde_json::json!(true),
265 }]
266}
267
268#[cfg(test)]
269#[path = "../../unit_tests/models/algebraic/equilibrium_point.rs"]
270mod tests;