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)]
158pub struct IntegerExpressionMembership {
159 expression: IntExpr,
161 target: i64,
163}
164
165impl IntegerExpressionMembership {
166 pub fn new(expression: IntExpr, target: i64) -> Self {
172 assert!(target > 0, "target must be a positive integer (got 0)");
173 assert!(
174 expression.all_atoms_positive(),
175 "all Atom values must be positive (> 0)"
176 );
177 Self { expression, target }
178 }
179
180 pub fn expression(&self) -> &IntExpr {
182 &self.expression
183 }
184
185 pub fn target(&self) -> i64 {
187 self.target
188 }
189
190 pub fn expression_size(&self) -> usize {
192 self.expression.size()
193 }
194
195 pub fn num_union_nodes(&self) -> usize {
197 self.expression.count_union_nodes()
198 }
199
200 pub fn num_atoms(&self) -> usize {
202 self.expression.count_atoms()
203 }
204
205 pub fn expression_depth(&self) -> usize {
207 self.expression.depth()
208 }
209
210 pub fn evaluate_config(&self, config: &[bool]) -> Option<i64> {
214 let mut counter = 0;
215 self.expression.evaluate_with_config(config, &mut counter)
216 }
217}
218
219impl Problem for IntegerExpressionMembership {
220 const NAME: &'static str = "IntegerExpressionMembership";
221 type Solution = Vec<bool>;
222 type Value = Or;
223
224 crate::problem_parameters![("num_union_nodes", num_union_nodes),];
225
226 fn evaluate(&self, config: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
227 Ok({
228 Or({
229 if config.len() != self.num_union_nodes() {
230 return Err(crate::traits::EvaluationError::InvalidConfiguration(
231 "union-choice length does not match the expression".into(),
232 ));
233 }
234 match self.evaluate_config(config) {
235 Some(value) => value == self.target,
236 None => false,
237 }
238 })
239 })
240 }
241
242 fn variant() -> Vec<(&'static str, &'static str)> {
243 crate::variant_params![]
244 }
245}
246
247impl crate::solvers::BruteForceProblem for IntegerExpressionMembership {
248 fn dimensions(&self) -> Vec<usize> {
249 vec![2; self.num_union_nodes()]
250 }
251}
252
253crate::declare_variants! {
254 default IntegerExpressionMembership => "2^num_union_nodes",
255}
256
257crate::register_brute_force! {
258 IntegerExpressionMembership decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
259}
260
261#[cfg(feature = "example-db")]
262pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
263 let expr = IntExpr::Sum(
267 Box::new(IntExpr::Sum(
268 Box::new(IntExpr::Union(
269 Box::new(IntExpr::Atom(1)),
270 Box::new(IntExpr::Atom(4)),
271 )),
272 Box::new(IntExpr::Union(
273 Box::new(IntExpr::Atom(3)),
274 Box::new(IntExpr::Atom(6)),
275 )),
276 )),
277 Box::new(IntExpr::Union(
278 Box::new(IntExpr::Atom(2)),
279 Box::new(IntExpr::Atom(5)),
280 )),
281 );
282 vec![crate::example_db::specs::ModelExampleSpec {
283 id: "integer_expression_membership",
284 instance: Box::new(IntegerExpressionMembership::new(expr, 12)),
285 optimal_config: serde_json::json!(vec![true, true, false]),
286 optimal_value: serde_json::json!(true),
287 }]
288}
289
290#[cfg(test)]
291#[path = "../../unit_tests/models/misc/integer_expression_membership.rs"]
292mod tests;