1#[cfg(test)]
2#[path = "../../tests/unit/models/goal_test.rs"]
3mod goal_test;
4
5use crate::construction::enablers::*;
6use crate::construction::heuristics::*;
7use crate::models::common::Cost;
8use crate::models::problem::Job;
9use rand::prelude::SliceRandom;
10use rosomaxa::population::Shuffled;
11use rosomaxa::prelude::*;
12use std::cmp::Ordering;
13use std::collections::HashSet;
14use std::fmt::{Debug, Display, Formatter};
15use std::ops::ControlFlow;
16use std::sync::Arc;
17
18#[derive(Clone)]
32pub struct GoalContext {
33 goal: Goal,
34 alternative_goals: Vec<(Goal, Float)>,
35 constraints: Vec<Arc<dyn FeatureConstraint>>,
36 states: Vec<Arc<dyn FeatureState>>,
37}
38
39impl GoalContext {
40 pub fn with_constraints<Iter>(&self, constraints: Iter) -> Self
42 where
43 Iter: Iterator<Item = Arc<dyn FeatureConstraint>>,
44 {
45 GoalContext { constraints: constraints.collect(), ..self.clone() }
46 }
47
48 pub fn constraints(&self) -> impl Iterator<Item = Arc<dyn FeatureConstraint>> + '_ {
50 self.constraints.iter().cloned()
51 }
52}
53
54impl Debug for GoalContext {
55 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct(short_type_name::<Self>())
57 .field("goal layers", &self.goal.layers.len())
58 .field("alternatives", &self.alternative_goals.len())
59 .field("constraints", &self.constraints.len())
60 .field("states", &self.states.len())
61 .finish()
62 }
63}
64
65pub struct GoalContextBuilder {
67 main_goal: Option<Goal>,
68 alternative_goals: Vec<(Goal, Float)>,
69 features: Vec<Feature>,
70}
71
72impl GoalContextBuilder {
73 pub fn with_features(features: &[Feature]) -> GenericResult<Self> {
75 let features = features.to_vec();
76 let ids_all = features.iter().map(|feature| feature.name.as_str()).collect::<Vec<_>>();
77 let ids_unique = ids_all.iter().collect::<HashSet<_>>();
78
79 if ids_unique.len() != ids_all.len() {
80 return Err(format!(
81 "some of the features are defined more than once, check ids list: {}",
82 ids_all.join(",")
83 )
84 .into());
85 }
86
87 let goal = Goal::simple(&features)?;
88
89 Ok(Self { main_goal: Some(goal), alternative_goals: Vec::default(), features })
90 }
91
92 pub fn set_main_goal(mut self, goal: Goal) -> Self {
94 self.main_goal = Some(goal);
95 self
96 }
97
98 pub fn add_alternative_goal(mut self, goal: Goal, weight: Float) -> Self {
100 self.alternative_goals.push((goal, weight));
101 self
102 }
103
104 pub fn build(self) -> GenericResult<GoalContext> {
106 let goal = self.main_goal.ok_or_else(|| GenericError::from("missing goal of optimization"))?;
107 let alternative_goals = self.alternative_goals;
108 let states = self.features.iter().filter_map(|feature| feature.state.clone()).collect();
109 let constraints = self.features.iter().filter_map(|feature| feature.constraint.clone()).collect();
110
111 Ok(GoalContext { goal, alternative_goals, constraints, states })
112 }
113}
114
115type TotalOrderFn =
116 Arc<dyn Fn(&[Arc<dyn FeatureObjective>], &InsertionContext, &InsertionContext) -> Ordering + Send + Sync>;
117type CostEstimateFn = Arc<dyn Fn(&[Arc<dyn FeatureObjective>], &MoveContext<'_>) -> Cost + Send + Sync>;
118type ObjectiveLayer = (TotalOrderFn, CostEstimateFn, Vec<Arc<dyn FeatureObjective>>);
119
120#[derive(Clone)]
122pub struct Goal {
123 layers: Vec<ObjectiveLayer>,
124}
125
126impl Goal {
127 pub fn simple(features: &[Feature]) -> GenericResult<Self> {
130 let mut builder = GoalBuilder::default();
131 let names = features.iter().filter(|f| f.objective.is_some()).map(|f| &f.name);
132
133 for name in names {
134 builder = Self::add_with_name(builder, features, name)?;
135 }
136
137 builder.build()
138 }
139
140 pub fn subset_of<S: AsRef<str>>(features: &[Feature], names: &[S]) -> GenericResult<Self> {
142 let mut builder = GoalBuilder::default();
143
144 for name in names {
145 builder = Self::add_with_name(builder, features, name.as_ref())?;
146 }
147
148 builder.build()
149 }
150
151 fn add_with_name(builder: GoalBuilder, features: &[Feature], name: &str) -> GenericResult<GoalBuilder> {
152 let feature = features
153 .iter()
154 .find(|f| f.name == name)
155 .ok_or_else(|| GenericError::from(format!("cannot find a feature with given name: '{name}'")))?;
156
157 let objective = feature
158 .objective
159 .clone()
160 .take()
161 .ok_or_else(|| GenericError::from(format!("feature '{name}' has no objective")))?;
162
163 Ok(builder.add_single(objective))
164 }
165}
166
167impl Goal {
168 pub fn total_order(&self, a: &InsertionContext, b: &InsertionContext) -> Ordering {
170 self.layers
171 .iter()
172 .try_fold(Ordering::Equal, |_, (total_order_fn, _, objectives)| {
173 match (total_order_fn)(objectives.as_slice(), a, b) {
174 Ordering::Equal => ControlFlow::Continue(Ordering::Equal),
175 order => ControlFlow::Break(order),
176 }
177 })
178 .unwrap_value()
179 }
180
181 pub fn estimate(&self, move_ctx: &MoveContext<'_>) -> InsertionCost {
183 self.layers.iter().map(|(_, estimate_fn, objectives)| (estimate_fn)(objectives.as_slice(), move_ctx)).collect()
184 }
185
186 pub fn fitness<'a>(&'a self, solution: &'a InsertionContext) -> impl Iterator<Item = Float> + 'a {
188 self.layers.iter().flat_map(|(_, _, objectives)| objectives.iter()).map(|objective| objective.fitness(solution))
189 }
190}
191
192#[derive(Default)]
195pub struct GoalBuilder {
196 layers: Vec<ObjectiveLayer>,
197}
198
199impl GoalBuilder {
200 pub fn add_single(mut self, objective: Arc<dyn FeatureObjective>) -> Self {
202 self.layers.push((
204 Arc::new(|objectives, a, b| {
205 let fitness_a = objectives[0].fitness(a);
206 let fitness_b = objectives[0].fitness(b);
207
208 if fitness_a == 0. && fitness_b == 0. {
211 Ordering::Equal
212 } else {
213 fitness_a.total_cmp(&fitness_b)
214 }
215 }),
216 Arc::new(|objectives, move_ctx| objectives[0].estimate(move_ctx)),
217 vec![objective],
218 ));
219 self
220 }
221
222 pub fn add_multi<TO, CE>(
225 mut self,
226 objectives: &[Arc<dyn FeatureObjective>],
227 total_order_fn: TO,
228 cost_estimate_fn: CE,
229 ) -> Self
230 where
231 TO: Fn(&[Arc<dyn FeatureObjective>], &InsertionContext, &InsertionContext) -> Ordering + Send + Sync + 'static,
232 CE: Fn(&[Arc<dyn FeatureObjective>], &MoveContext<'_>) -> Cost + Send + Sync + 'static,
233 {
234 self.layers.push((Arc::new(total_order_fn), Arc::new(cost_estimate_fn), objectives.to_vec()));
235 self
236 }
237
238 pub fn build(self) -> GenericResult<Goal> {
240 if self.layers.is_empty() {
241 return Err(GenericError::from("no objectives specified in the goal"));
242 }
243
244 Ok(Goal { layers: self.layers })
245 }
246}
247
248#[derive(Clone, Default)]
271pub struct Feature {
272 pub name: String,
274 pub constraint: Option<Arc<dyn FeatureConstraint>>,
276 pub objective: Option<Arc<dyn FeatureObjective>>,
278 pub state: Option<Arc<dyn FeatureState>>,
280}
281
282#[derive(Clone, Debug, Eq, PartialEq)]
284pub struct ConstraintViolation {
285 pub code: ViolationCode,
287 pub stopped: bool,
289}
290
291impl ConstraintViolation {
292 pub fn fail(code: ViolationCode) -> Option<Self> {
294 Some(ConstraintViolation { code, stopped: true })
295 }
296
297 pub fn skip(code: ViolationCode) -> Option<Self> {
299 Some(ConstraintViolation { code, stopped: false })
300 }
301
302 pub fn success() -> Option<Self> {
304 None
305 }
306}
307
308#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)]
310pub struct ViolationCode(pub i32);
311
312impl ViolationCode {
313 pub fn unknown() -> Self {
315 Self(-1)
316 }
317
318 pub fn is_unknown(&self) -> bool {
320 self.0 == -1
321 }
322}
323
324impl From<i32> for ViolationCode {
325 fn from(value: i32) -> Self {
326 Self(value)
327 }
328}
329
330impl Display for ViolationCode {
331 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
332 f.write_fmt(format_args!("{}", self.0))
333 }
334}
335
336#[derive(Default)]
338pub struct FeatureBuilder(Feature);
339
340impl FeatureBuilder {
341 pub fn from_feature(feature: Feature) -> Self {
343 Self(feature)
344 }
345
346 pub fn with_name(mut self, name: &str) -> Self {
348 self.0.name = name.to_string();
349 self
350 }
351
352 pub fn with_constraint<T: FeatureConstraint + 'static>(mut self, constraint: T) -> Self {
354 self.0.constraint = Some(Arc::new(constraint));
355 self
356 }
357
358 pub fn with_objective<T: FeatureObjective + 'static>(mut self, objective: T) -> Self {
360 self.0.objective = Some(Arc::new(objective));
361 self
362 }
363
364 pub fn with_state<T: FeatureState + 'static>(mut self, state: T) -> Self {
366 self.0.state = Some(Arc::new(state));
367 self
368 }
369
370 pub fn build(self) -> Result<Feature, GenericError> {
372 let feature = self.0;
373
374 if feature.name == String::default() {
375 return Err("features with default id are not allowed".into());
376 }
377
378 if feature.constraint.is_none() && feature.objective.is_none() {
379 Err("empty feature is not allowed".into())
380 } else {
381 Ok(feature)
382 }
383 }
384}
385
386pub trait FeatureState: Send + Sync {
388 fn notify_failure(&self, _solution_ctx: &mut SolutionContext, _route_indices: &[usize], _jobs: &[Job]) -> bool {
396 false
397 }
398
399 fn accept_insertion(&self, solution_ctx: &mut SolutionContext, route_index: usize, job: &Job);
406
407 fn accept_route_state(&self, route_ctx: &mut RouteContext);
410
411 fn accept_solution_state(&self, solution_ctx: &mut SolutionContext);
416}
417
418pub trait FeatureConstraint: Send + Sync {
420 fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option<ConstraintViolation>;
422
423 fn merge(&self, _source: Job, _candidate: Job) -> Result<Job, ViolationCode> {
428 Err(ViolationCode::default())
429 }
430}
431
432pub trait FeatureObjective: Send + Sync {
434 fn fitness(&self, solution: &InsertionContext) -> Cost;
436
437 fn estimate(&self, move_ctx: &MoveContext<'_>) -> Cost;
439}
440
441impl HeuristicObjective for GoalContext {
442 type Solution = InsertionContext;
443
444 fn total_order(&self, a: &Self::Solution, b: &Self::Solution) -> Ordering {
445 self.goal.total_order(a, b)
446 }
447}
448
449impl Shuffled for GoalContext {
450 fn get_shuffled(&self, random: &(dyn Random)) -> Self {
451 const RANDOM_ALTERNATIVE_PROBABILITY: Float = 0.05;
452 const RANDOM_SHUFFLE_PROBABILITY: Float = 0.001;
453
454 if !self.alternative_goals.is_empty() && random.is_hit(RANDOM_ALTERNATIVE_PROBABILITY) {
455 let idx = random.uniform_int(0, self.alternative_goals.len() as i32 - 1) as usize;
456 return self.get_alternative(idx);
457 }
458
459 if random.is_hit(RANDOM_SHUFFLE_PROBABILITY) {
460 self.get_shuffled(random)
461 } else {
462 self.clone()
463 }
464 }
465}
466
467impl GoalContext {
468 fn get_alternative(&self, idx: usize) -> Self {
469 let (goal, _) = self.alternative_goals[idx].clone();
470
471 Self { goal, ..self.clone() }
472 }
473
474 fn get_shuffled(&self, random: &(dyn Random)) -> Self {
475 let instance = self.clone();
476
477 let mut layers = self.goal.layers.clone();
478 layers.shuffle(&mut random.get_rng());
479
480 Self { goal: Goal { layers }, ..instance }
481 }
482
483 pub(crate) fn get_alternatives(&self) -> impl Iterator<Item = Self> + '_ {
485 self.alternative_goals.iter().enumerate().map(|(idx, _)| self.get_alternative(idx))
486 }
487
488 pub fn accept_insertion(&self, solution_ctx: &mut SolutionContext, route_index: usize, job: &Job) {
490 accept_insertion_with_states(&self.states, solution_ctx, route_index, job)
491 }
492
493 pub fn accept_route_state(&self, route_ctx: &mut RouteContext) {
495 accept_route_state_with_states(&self.states, route_ctx)
496 }
497
498 pub fn accept_solution_state(&self, solution_ctx: &mut SolutionContext) {
500 accept_solution_state_with_states(&self.states, solution_ctx);
501 }
502
503 pub fn notify_failure(&self, solution_ctx: &mut SolutionContext, route_indices: &[usize], jobs: &[Job]) -> bool {
507 notify_failure_with_states(&self.states, solution_ctx, route_indices, jobs)
508 }
509
510 pub fn merge(&self, source: Job, candidate: Job) -> Result<Job, ViolationCode> {
514 merge_with_constraints(&self.constraints, source, candidate)
515 }
516
517 pub fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option<ConstraintViolation> {
519 evaluate_with_constraints(&self.constraints, move_ctx)
520 }
521
522 pub fn estimate(&self, move_ctx: &MoveContext<'_>) -> InsertionCost {
524 self.goal.estimate(move_ctx)
525 }
526
527 pub fn fitness<'a>(&'a self, solution: &'a InsertionContext) -> impl Iterator<Item = Float> + 'a {
529 self.goal.fitness(solution)
530 }
531}
532
533impl Debug for Feature {
534 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
535 f.debug_struct(short_type_name::<Self>())
536 .field("name", &self.name)
537 .field("constraint", &self.constraint.is_some())
538 .field("objective", &self.objective.is_some())
539 .field("state", &self.state.is_some())
540 .finish()
541 }
542}