problemreductions/models/misc/
integer_expression_membership.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Or;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "IntegerExpressionMembership",
15 display_name: "Integer Expression Membership",
16 aliases: &[],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Misc,
19 module_path: module_path!(),
20 description: "Decide whether a target integer belongs to the set represented by an expression tree over union and Minkowski sum",
21 fields: &[
22 FieldInfo { name: "expression", type_name: "IntExpr", description: "Recursive expression tree" },
23 FieldInfo { name: "target", type_name: "i64", description: "Target integer K" },
24 ],
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35pub enum IntExpr {
36 Atom(i64),
38 Union(Box<IntExpr>, Box<IntExpr>),
40 Sum(Box<IntExpr>, Box<IntExpr>),
42}
43
44impl IntExpr {
45 pub fn all_atoms_positive(&self) -> bool {
47 match self {
48 IntExpr::Atom(n) => *n > 0,
49 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => {
50 l.all_atoms_positive() && r.all_atoms_positive()
51 }
52 }
53 }
54
55 pub fn size(&self) -> usize {
57 match self {
58 IntExpr::Atom(_) => 1,
59 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => 1 + l.size() + r.size(),
60 }
61 }
62
63 pub fn count_union_nodes(&self) -> usize {
65 match self {
66 IntExpr::Atom(_) => 0,
67 IntExpr::Union(l, r) => 1 + l.count_union_nodes() + r.count_union_nodes(),
68 IntExpr::Sum(l, r) => l.count_union_nodes() + r.count_union_nodes(),
69 }
70 }
71
72 pub fn count_atoms(&self) -> usize {
74 match self {
75 IntExpr::Atom(_) => 1,
76 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => l.count_atoms() + r.count_atoms(),
77 }
78 }
79
80 pub fn depth(&self) -> usize {
82 match self {
83 IntExpr::Atom(_) => 0,
84 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => 1 + l.depth().max(r.depth()),
85 }
86 }
87
88 fn evaluate_with_config(&self, config: &[bool], counter: &mut usize) -> Option<i64> {
93 match self {
94 IntExpr::Atom(n) => Some(*n),
95 IntExpr::Union(left, right) => {
96 let idx = *counter;
97 *counter += 1;
98 if idx >= config.len() {
99 return None;
100 }
101 if config[idx] {
102 right.evaluate_with_config(config, counter)
103 } else {
104 left.evaluate_with_config(config, counter)
105 }
106 }
107 IntExpr::Sum(left, right) => {
108 let l = left.evaluate_with_config(config, counter)?;
109 let r = right.evaluate_with_config(config, counter)?;
110 l.checked_add(r)
111 }
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(try_from = "IntegerExpressionMembershipData")]
159pub struct IntegerExpressionMembership {
160 expression: IntExpr,
162 target: i64,
164}
165
166#[derive(Deserialize)]
167struct IntegerExpressionMembershipData {
168 expression: IntExpr,
169 target: i64,
170}
171
172impl TryFrom<IntegerExpressionMembershipData> for IntegerExpressionMembership {
173 type Error = crate::registry::ConstructionError;
174
175 fn try_from(data: IntegerExpressionMembershipData) -> Result<Self, Self::Error> {
176 Self::try_new(data.expression, data.target)
177 }
178}
179
180impl IntegerExpressionMembership {
181 pub fn new(expression: IntExpr, target: i64) -> Self {
187 Self::try_new(expression, target).unwrap_or_else(|error| panic!("{error}"))
188 }
189
190 fn try_new(
191 expression: IntExpr,
192 target: i64,
193 ) -> Result<Self, crate::registry::ConstructionError> {
194 if target <= 0 {
195 return Err("target must be a positive integer (got 0)".into());
196 }
197 if !expression.all_atoms_positive() {
198 return Err("all Atom values must be positive (> 0)".into());
199 }
200 Ok(Self { expression, target })
201 }
202
203 pub fn expression(&self) -> &IntExpr {
205 &self.expression
206 }
207
208 pub fn target(&self) -> i64 {
210 self.target
211 }
212
213 pub fn expression_size(&self) -> usize {
215 self.expression.size()
216 }
217
218 pub fn num_union_nodes(&self) -> usize {
220 self.expression.count_union_nodes()
221 }
222
223 pub fn num_atoms(&self) -> usize {
225 self.expression.count_atoms()
226 }
227
228 pub fn expression_depth(&self) -> usize {
230 self.expression.depth()
231 }
232
233 pub fn evaluate_config(&self, config: &[bool]) -> Option<i64> {
237 let mut counter = 0;
238 self.expression.evaluate_with_config(config, &mut counter)
239 }
240}
241
242impl Problem for IntegerExpressionMembership {
243 const NAME: &'static str = "IntegerExpressionMembership";
244 type Solution = Vec<bool>;
245 type Value = Or;
246
247 crate::problem_parameters![("num_union_nodes", num_union_nodes),];
248
249 fn evaluate(&self, config: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
250 Ok({
251 Or({
252 if config.len() != self.num_union_nodes() {
253 return Err(crate::traits::EvaluationError::InvalidConfiguration(
254 "union-choice length does not match the expression".into(),
255 ));
256 }
257 match self.evaluate_config(config) {
258 Some(value) => value == self.target,
259 None => false,
260 }
261 })
262 })
263 }
264
265 fn variant() -> Vec<(&'static str, &'static str)> {
266 crate::variant_params![]
267 }
268}
269
270impl crate::solvers::BruteForceProblem for IntegerExpressionMembership {
271 fn dimensions(&self) -> Vec<usize> {
272 vec![2; self.num_union_nodes()]
273 }
274}
275
276crate::declare_variants! {
277 default IntegerExpressionMembership => "2^num_union_nodes",
278}
279
280crate::register_brute_force! {
281 IntegerExpressionMembership decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
282}
283
284#[cfg(feature = "example-db")]
285pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
286 let expr = IntExpr::Sum(
290 Box::new(IntExpr::Sum(
291 Box::new(IntExpr::Union(
292 Box::new(IntExpr::Atom(1)),
293 Box::new(IntExpr::Atom(4)),
294 )),
295 Box::new(IntExpr::Union(
296 Box::new(IntExpr::Atom(3)),
297 Box::new(IntExpr::Atom(6)),
298 )),
299 )),
300 Box::new(IntExpr::Union(
301 Box::new(IntExpr::Atom(2)),
302 Box::new(IntExpr::Atom(5)),
303 )),
304 );
305 vec![crate::example_db::specs::ModelExampleSpec {
306 id: "integer_expression_membership",
307 instance: Box::new(IntegerExpressionMembership::new(expr, 12)),
308 optimal_config: serde_json::json!(vec![true, true, false]),
309 optimal_value: serde_json::json!(true),
310 }]
311}
312
313#[cfg(test)]
314#[path = "../../unit_tests/models/misc/integer_expression_membership.rs"]
315mod tests;