conjure_cp_core/
instantiate.rs1use crate::{
2 Model,
3 ast::{DeclarationKind, DeclarationPtr, Literal, declaration::Declaration, eval_constant},
4};
5use anyhow::anyhow;
6
7pub fn instantiate_model(mut problem_model: Model, param_model: Model) -> anyhow::Result<Model> {
13 let symbol_table = problem_model.symbols_ptr_unchecked().write();
14 let param_table = param_model.symbols_ptr_unchecked().write();
15 let mut pending_givens = symbol_table
16 .iter_local()
17 .filter_map(|(name, decl)| decl.as_given().map(|_| name.clone()))
18 .collect::<Vec<_>>();
19
20 while !pending_givens.is_empty() {
21 let mut next_pending = Vec::new();
22 let mut made_progress = false;
23
24 for name in pending_givens {
25 let mut decl = symbol_table
26 .lookup_local(&name)
27 .ok_or_else(|| anyhow!("Given declaration `{name}` not found in problem model"))?;
28
29 let Some(domain) = decl.as_given() else {
30 continue;
31 };
32
33 let param_decl = param_table.lookup(&name);
34 let expr = param_decl
35 .as_ref()
36 .and_then(DeclarationPtr::as_value_letting)
37 .ok_or_else(|| {
38 anyhow!(
39 "Given declaration `{name}` does not have corresponding letting in parameter file"
40 )
41 })?;
42
43 let expr_value = eval_constant(&expr)
44 .ok_or_else(|| anyhow!("Letting expression `{expr}` cannot be evaluated"))?;
45
46 let Ok(ground_domain) = domain.resolve() else {
47 next_pending.push(name);
48 continue;
49 };
50
51 if !ground_domain.contains(&expr_value)? {
52 return Err(anyhow!(
53 "Domain of given statement `{name}` does not contain letting value"
54 ));
55 }
56
57 let instantiated_domain = expr.domain_of().unwrap_or_else(|| domain.clone());
62 let new_decl = Declaration::new(
63 name.clone(),
64 DeclarationKind::ValueLetting(expr.clone(), Some(instantiated_domain)),
65 );
66 drop(domain);
67 decl.replace(new_decl);
68 made_progress = true;
69
70 tracing::info!("Replaced {name} given with letting.");
71 }
72
73 if next_pending.is_empty() {
74 break;
75 }
76
77 if !made_progress {
78 return Err(anyhow!(
79 "Domain of given statement `{}` cannot be resolved",
80 next_pending[0]
81 ));
82 }
83
84 pending_givens = next_pending;
85 }
86
87 drop(symbol_table);
88 ground_collection_valued_domains(&mut problem_model);
89 validate_instantiation_conditions(&mut problem_model)?;
90 Ok(problem_model)
91}
92
93pub fn validate_instantiation_conditions(model: &mut Model) -> anyhow::Result<()> {
95 for condition in model.take_instantiation_conditions() {
96 match eval_constant(&condition) {
97 Some(Literal::Bool(true)) => {}
98 Some(Literal::Bool(false)) => {
99 return Err(anyhow!(
100 "invalid instance: where condition `{condition}` evaluated to false"
101 ));
102 }
103 Some(value) => {
104 return Err(anyhow!(
105 "where condition `{condition}` evaluated to non-boolean value `{value}`"
106 ));
107 }
108 None => {
109 return Err(anyhow!(
110 "could not evaluate where condition `{condition}` after parameter instantiation"
111 ));
112 }
113 }
114 }
115 Ok(())
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::ast::{
122 Atom, Domain, Expression, GroundDomain, IntVal, Metadata, Name, Objective,
123 OptimiseDirection, Range, Reference,
124 };
125
126 #[test]
127 fn instantiated_given_uses_the_tighter_value_domain() {
128 let name = Name::user("n");
129 let mut problem = Model::default();
130 problem
131 .add_symbol(DeclarationPtr::new_given(
132 name.clone(),
133 Domain::int(vec![Range::UnboundedR(1)]),
134 ))
135 .unwrap();
136
137 let mut parameters = Model::default();
138 parameters
139 .add_symbol(DeclarationPtr::new_value_letting(
140 name.clone(),
141 crate::ast::Expression::Atomic(Metadata::new(), Atom::Literal(Literal::Int(7))),
142 ))
143 .unwrap();
144
145 let instantiated = instantiate_model(problem, parameters).unwrap();
146 let declaration = instantiated
147 .symbols()
148 .lookup(&name)
149 .expect("instantiated parameter should exist");
150
151 assert_eq!(
152 declaration.domain().unwrap().resolve().unwrap().as_ref(),
153 &GroundDomain::Int(vec![Range::Single(7)])
154 );
155 }
156
157 #[test]
158 fn instantiation_invalidates_cached_expression_domains() {
159 let parameter_name = Name::user("n");
160 let parameter = DeclarationPtr::new_given(
161 parameter_name.clone(),
162 Domain::int(vec![Range::UnboundedR(1)]),
163 );
164 let variable = DeclarationPtr::new_find(
165 Name::user("x"),
166 Domain::int(vec![Range::Bounded(
167 IntVal::Const(1),
168 IntVal::Reference(Reference::new(parameter.clone())),
169 )]),
170 );
171 let reference = Expression::Atomic(Metadata::new(), Atom::new_ref(variable.clone()));
172
173 let mut problem = Model::default();
174 problem.add_symbol(parameter).unwrap();
175 problem.add_symbol(variable).unwrap();
176 problem.objective = Some(Objective {
177 direction: OptimiseDirection::Minimising,
178 expression: reference,
179 });
180
181 let cached_domain = problem
182 .objective
183 .as_ref()
184 .unwrap()
185 .expression
186 .domain_of()
187 .unwrap();
188 assert!(cached_domain.resolve().is_err());
189
190 let mut parameters = Model::default();
191 parameters
192 .add_symbol(DeclarationPtr::new_value_letting(
193 parameter_name,
194 Expression::Atomic(Metadata::new(), Atom::Literal(Literal::Int(7))),
195 ))
196 .unwrap();
197
198 let instantiated = instantiate_model(problem, parameters).unwrap();
199 let objective_domain = instantiated
200 .objective
201 .as_ref()
202 .unwrap()
203 .expression
204 .domain_of()
205 .unwrap();
206
207 assert_eq!(
208 objective_domain.resolve().unwrap().as_ref(),
209 &GroundDomain::Int(vec![Range::Bounded(1, 7)])
210 );
211 }
212}
213
214fn ground_collection_valued_domains(model: &mut Model) {
224 for (_, decl) in model.symbols_mut().iter_local_mut() {
225 let Some(mut var) = decl.as_find_mut() else {
229 continue;
230 };
231 if !crate::ast::domain_has_int_from_values(&var.domain) {
232 continue;
233 }
234 let Ok(ground) = var.domain.resolve() else {
235 continue;
236 };
237 var.domain = ground.into();
238 }
239}