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 module_path: module_path!(),
19 description: "Decide whether a target integer belongs to the set represented by an expression tree over union and Minkowski sum",
20 fields: &[
21 FieldInfo { name: "expression", type_name: "IntExpr", description: "Recursive expression tree" },
22 FieldInfo { name: "target", type_name: "u64", description: "Target integer K" },
23 ],
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub enum IntExpr {
35 Atom(u64),
37 Union(Box<IntExpr>, Box<IntExpr>),
39 Sum(Box<IntExpr>, Box<IntExpr>),
41}
42
43impl IntExpr {
44 pub fn all_atoms_positive(&self) -> bool {
46 match self {
47 IntExpr::Atom(n) => *n > 0,
48 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => {
49 l.all_atoms_positive() && r.all_atoms_positive()
50 }
51 }
52 }
53
54 pub fn size(&self) -> usize {
56 match self {
57 IntExpr::Atom(_) => 1,
58 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => 1 + l.size() + r.size(),
59 }
60 }
61
62 pub fn count_union_nodes(&self) -> usize {
64 match self {
65 IntExpr::Atom(_) => 0,
66 IntExpr::Union(l, r) => 1 + l.count_union_nodes() + r.count_union_nodes(),
67 IntExpr::Sum(l, r) => l.count_union_nodes() + r.count_union_nodes(),
68 }
69 }
70
71 pub fn count_atoms(&self) -> usize {
73 match self {
74 IntExpr::Atom(_) => 1,
75 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => l.count_atoms() + r.count_atoms(),
76 }
77 }
78
79 pub fn depth(&self) -> usize {
81 match self {
82 IntExpr::Atom(_) => 0,
83 IntExpr::Union(l, r) | IntExpr::Sum(l, r) => 1 + l.depth().max(r.depth()),
84 }
85 }
86
87 fn evaluate_with_config(&self, config: &[usize], counter: &mut usize) -> Option<u64> {
92 match self {
93 IntExpr::Atom(n) => Some(*n),
94 IntExpr::Union(left, right) => {
95 let idx = *counter;
96 *counter += 1;
97 if idx >= config.len() {
98 return None;
99 }
100 match config[idx] {
101 0 => left.evaluate_with_config(config, counter),
102 1 => right.evaluate_with_config(config, counter),
103 _ => None,
104 }
105 }
106 IntExpr::Sum(left, right) => {
107 let l = left.evaluate_with_config(config, counter)?;
108 let r = right.evaluate_with_config(config, counter)?;
109 l.checked_add(r)
110 }
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct IntegerExpressionMembership {
158 expression: IntExpr,
160 target: u64,
162}
163
164impl IntegerExpressionMembership {
165 pub fn new(expression: IntExpr, target: u64) -> Self {
171 assert!(target > 0, "target must be a positive integer (got 0)");
172 assert!(
173 expression.all_atoms_positive(),
174 "all Atom values must be positive (> 0)"
175 );
176 Self { expression, target }
177 }
178
179 pub fn expression(&self) -> &IntExpr {
181 &self.expression
182 }
183
184 pub fn target(&self) -> u64 {
186 self.target
187 }
188
189 pub fn expression_size(&self) -> usize {
191 self.expression.size()
192 }
193
194 pub fn num_union_nodes(&self) -> usize {
196 self.expression.count_union_nodes()
197 }
198
199 pub fn num_atoms(&self) -> usize {
201 self.expression.count_atoms()
202 }
203
204 pub fn expression_depth(&self) -> usize {
206 self.expression.depth()
207 }
208
209 pub fn evaluate_config(&self, config: &[usize]) -> Option<u64> {
213 let mut counter = 0;
214 self.expression.evaluate_with_config(config, &mut counter)
215 }
216}
217
218impl Problem for IntegerExpressionMembership {
219 const NAME: &'static str = "IntegerExpressionMembership";
220 type Value = Or;
221
222 fn dims(&self) -> Vec<usize> {
223 vec![2; self.num_union_nodes()]
224 }
225
226 fn evaluate(&self, config: &[usize]) -> Or {
227 Or({
228 if config.len() != self.num_union_nodes() {
229 return Or(false);
230 }
231 if config.iter().any(|&v| v >= 2) {
232 return Or(false);
233 }
234 match self.evaluate_config(config) {
235 Some(value) => value == self.target,
236 None => false,
237 }
238 })
239 }
240
241 fn variant() -> Vec<(&'static str, &'static str)> {
242 crate::variant_params![]
243 }
244}
245
246crate::declare_variants! {
247 default IntegerExpressionMembership => "2^num_union_nodes",
248}
249
250#[cfg(feature = "example-db")]
251pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
252 let expr = IntExpr::Sum(
256 Box::new(IntExpr::Sum(
257 Box::new(IntExpr::Union(
258 Box::new(IntExpr::Atom(1)),
259 Box::new(IntExpr::Atom(4)),
260 )),
261 Box::new(IntExpr::Union(
262 Box::new(IntExpr::Atom(3)),
263 Box::new(IntExpr::Atom(6)),
264 )),
265 )),
266 Box::new(IntExpr::Union(
267 Box::new(IntExpr::Atom(2)),
268 Box::new(IntExpr::Atom(5)),
269 )),
270 );
271 vec![crate::example_db::specs::ModelExampleSpec {
272 id: "integer_expression_membership",
273 instance: Box::new(IntegerExpressionMembership::new(expr, 12)),
274 optimal_config: vec![1, 1, 0],
275 optimal_value: serde_json::json!(true),
276 }]
277}
278
279#[cfg(test)]
280#[path = "../../unit_tests/models/misc/integer_expression_membership.rs"]
281mod tests;