1use super::*;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub(crate) enum SchedulingName {
6 Strict,
7 Unary,
8 OptionalUnary,
9 OptionalCumulative,
10 Cumulative,
11 Cumulatives,
12 UnaryFlex,
13 OptionalUnaryFlex,
14 CumulativeFlex,
15 OptionalCumulativeFlex,
16 CumulativesFlex,
17}
18impl SchedulingName {
19 pub fn from_name(name: &str, arity: usize) -> Option<Self> {
20 if name == "fzn_disjunctive_strict" {
21 return (arity == 2).then_some(Self::Strict);
22 }
23 for base in [
24 name,
25 name.strip_prefix("gecode_").unwrap_or(""),
26 name.strip_prefix("fzn_").unwrap_or(""),
27 ] {
28 let kind = match base {
29 "gecode_schedule_unary" => Self::Unary,
30 "gecode_schedule_unary_optional" => Self::OptionalUnary,
31 "gecode_schedule_cumulative_optional" => Self::OptionalCumulative,
32 "gecode_unary_flex" => Self::UnaryFlex,
33 "gecode_unary_flex_optional" => Self::OptionalUnaryFlex,
34 "gecode_cumulative_flex" => Self::CumulativeFlex,
35 "gecode_cumulative_flex_optional" => Self::OptionalCumulativeFlex,
36 "gecode_cumulatives_flex" => Self::CumulativesFlex,
37 "cumulatives" => match arity {
38 4 => Self::Cumulative,
39 6 => Self::Cumulatives,
40 7 => Self::CumulativesFlex,
41 _ => return None,
42 },
43 _ => continue,
44 };
45 return (arity == kind.arity()).then_some(kind);
46 }
47 None
48 }
49 fn arity(self) -> usize {
50 match self {
51 Self::Strict | Self::Unary => 2,
52 Self::OptionalUnary => 3,
53 Self::OptionalCumulative => 5,
54 Self::Cumulative => 4,
55 Self::Cumulatives => 6,
56 Self::UnaryFlex => 3,
57 Self::OptionalUnaryFlex => 4,
58 Self::CumulativeFlex => 5,
59 Self::OptionalCumulativeFlex => 6,
60 Self::CumulativesFlex => 7,
61 }
62 }
63 #[cfg(feature = "prepare")]
64 pub fn uses_explicit_ends(self) -> bool {
65 matches!(
66 self,
67 Self::UnaryFlex
68 | Self::OptionalUnaryFlex
69 | Self::CumulativeFlex
70 | Self::OptionalCumulativeFlex
71 | Self::CumulativesFlex
72 )
73 }
74 #[cfg(feature = "prepare")]
75 pub fn signature(self) -> Vec<prepare::ArgumentType> {
76 let argument = |kind, is_array, is_var| prepare::ArgumentType {
77 kind,
78 is_array,
79 is_var,
80 };
81 let integer_array = |is_var| argument(ValueType::Integer, true, is_var);
82 if self.uses_explicit_ends() {
83 let mut signature = vec![integer_array(true); 3];
84 match self {
85 Self::UnaryFlex => {}
86 Self::OptionalUnaryFlex => signature.push(argument(ValueType::Boolean, true, true)),
87 Self::CumulativeFlex | Self::OptionalCumulativeFlex => {
88 signature.extend([
89 integer_array(false),
90 argument(ValueType::Integer, false, true),
91 ]);
92 if self == Self::OptionalCumulativeFlex {
93 signature.push(argument(ValueType::Boolean, true, true));
94 }
95 }
96 Self::CumulativesFlex => signature.extend([
97 integer_array(true),
98 integer_array(true),
99 integer_array(false),
100 argument(ValueType::Boolean, false, false),
101 ]),
102 _ => unreachable!(),
103 }
104 return signature;
105 }
106 let mut signature = vec![
107 integer_array(true),
108 integer_array(matches!(
109 self,
110 Self::Strict | Self::Cumulative | Self::Cumulatives
111 )),
112 ];
113 match self {
114 Self::Strict | Self::Unary => {}
115 Self::OptionalUnary => signature.push(argument(ValueType::Boolean, true, true)),
116 Self::OptionalCumulative => signature.extend([
117 integer_array(false),
118 argument(ValueType::Boolean, true, true),
119 argument(ValueType::Integer, false, true),
120 ]),
121 Self::Cumulative => signature.extend([
122 integer_array(true),
123 argument(ValueType::Integer, false, true),
124 ]),
125 Self::Cumulatives => signature.extend([
126 integer_array(true),
127 integer_array(true),
128 integer_array(false),
129 argument(ValueType::Boolean, false, false),
130 ]),
131 _ => unreachable!(),
132 }
133 signature
134 }
135}
136
137#[derive(Debug, Serialize, Deserialize)]
138pub(crate) struct ScheduleTasks {
139 pub starts: Vec<Operand>,
140 pub durations: Vec<Operand>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub ends: Option<Vec<Operand>>,
143}
144#[derive(Debug, Serialize, Deserialize)]
145pub(crate) enum Scheduling {
146 Strict(ScheduleTasks),
147 Unary {
148 tasks: ScheduleTasks,
149 present: Option<Vec<Operand>>,
150 },
151 OptionalCumulative {
152 tasks: ScheduleTasks,
153 usages: Vec<Operand>,
154 present: Vec<Operand>,
155 capacity: Operand,
156 },
157 Cumulative {
158 tasks: ScheduleTasks,
159 usages: Vec<Operand>,
160 capacity: Operand,
161 },
162 Cumulatives {
163 tasks: ScheduleTasks,
164 usages: Vec<Operand>,
165 machines: Vec<Operand>,
166 capacities: Vec<Operand>,
167 upper: bool,
168 },
169}
170
171#[derive(Clone, Debug, Serialize)]
172pub struct SchedulingTask {
173 pub position: usize,
174 pub start: Option<ArgumentValue>,
175 pub duration: Option<ArgumentValue>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub end: Option<ArgumentValue>,
178 pub usage: Option<ArgumentValue>,
179 pub present: Option<ArgumentValue>,
180 pub machine: Option<ArgumentValue>,
181}
182#[derive(Clone, Debug, Serialize)]
183#[serde(tag = "kind", rename_all = "snake_case")]
184pub enum SchedulingFault {
185 Duration {
186 position: usize,
187 #[serde(serialize_with = "serialize_integer")]
188 value: i64,
189 },
190 Machine {
191 position: usize,
192 #[serde(serialize_with = "serialize_integer")]
193 value: i64,
194 },
195 Capacity {
196 #[serde(serialize_with = "serialize_integer")]
197 value: i64,
198 },
199 Conflict {
200 first: usize,
201 second: usize,
202 },
203 TaskUsage {
204 position: usize,
205 #[serde(serialize_with = "serialize_integer")]
206 usage: i64,
207 #[serde(serialize_with = "serialize_integer")]
208 bound: i64,
209 },
210 Resource {
211 #[serde(serialize_with = "serialize_optional_integer")]
212 machine: Option<i64>,
213 #[serde(serialize_with = "serialize_integer")]
214 time: i64,
215 #[serde(serialize_with = "serialize_integer")]
216 usage: i64,
217 #[serde(serialize_with = "serialize_integer")]
218 bound: i64,
219 upper: bool,
220 tasks: Vec<usize>,
221 },
222}
223#[derive(Clone, Debug, Serialize)]
224pub struct SchedulingFinding {
225 pub tasks: Vec<SchedulingTask>,
226 pub capacities: Vec<ArgumentValue>,
227 pub faults: Vec<SchedulingFault>,
228}
229
230impl Scheduling {
231 pub fn name(&self) -> SchedulingName {
232 if self.tasks().ends.is_some() {
233 return match self {
234 Self::Unary { present: None, .. } => SchedulingName::UnaryFlex,
235 Self::Unary {
236 present: Some(_), ..
237 } => SchedulingName::OptionalUnaryFlex,
238 Self::Cumulative { .. } => SchedulingName::CumulativeFlex,
239 Self::OptionalCumulative { .. } => SchedulingName::OptionalCumulativeFlex,
240 Self::Cumulatives { .. } => SchedulingName::CumulativesFlex,
241 Self::Strict(_) => SchedulingName::Strict,
242 };
243 }
244 match self {
245 Self::Strict(_) => SchedulingName::Strict,
246 Self::Unary { present: None, .. } => SchedulingName::Unary,
247 Self::Unary {
248 present: Some(_), ..
249 } => SchedulingName::OptionalUnary,
250 Self::OptionalCumulative { .. } => SchedulingName::OptionalCumulative,
251 Self::Cumulative { .. } => SchedulingName::Cumulative,
252 Self::Cumulatives { .. } => SchedulingName::Cumulatives,
253 }
254 }
255 pub fn arity(&self) -> usize {
256 self.name().arity()
257 }
258 fn tasks(&self) -> &ScheduleTasks {
259 match self {
260 Self::Strict(tasks)
261 | Self::Unary { tasks, .. }
262 | Self::OptionalCumulative { tasks, .. }
263 | Self::Cumulative { tasks, .. }
264 | Self::Cumulatives { tasks, .. } => tasks,
265 }
266 }
267 pub fn presence(&self) -> Option<&[Operand]> {
268 match self {
269 Self::Unary { present, .. } => present.as_deref(),
270 Self::OptionalCumulative { present, .. } => Some(present),
271 _ => None,
272 }
273 }
274 fn usages(&self) -> Option<&[Operand]> {
275 match self {
276 Self::OptionalCumulative { usages, .. }
277 | Self::Cumulative { usages, .. }
278 | Self::Cumulatives { usages, .. } => Some(usages),
279 _ => None,
280 }
281 }
282 fn capacities(&self) -> &[Operand] {
283 match self {
284 Self::OptionalCumulative { capacity, .. } | Self::Cumulative { capacity, .. } => {
285 std::slice::from_ref(capacity)
286 }
287 Self::Cumulatives { capacities, .. } => capacities,
288 _ => &[],
289 }
290 }
291 fn machines(&self) -> Option<&[Operand]> {
292 match self {
293 Self::Cumulatives { machines, .. } => Some(machines),
294 _ => None,
295 }
296 }
297 pub fn integer_operands(&self) -> Vec<&Operand> {
298 let tasks = self.tasks();
299 tasks
300 .starts
301 .iter()
302 .chain(&tasks.durations)
303 .chain(tasks.ends.iter().flatten())
304 .chain(self.usages().into_iter().flatten())
305 .chain(self.machines().into_iter().flatten())
306 .chain(self.capacities())
307 .collect()
308 }
309 pub fn operands(&self) -> Vec<&Operand> {
310 let mut operands = self.integer_operands();
311 operands.extend(self.presence().into_iter().flatten());
312 operands
313 }
314 pub fn validate_shape(&self) -> Result<(), &'static str> {
315 let tasks = self.tasks();
316 let n = tasks.starts.len();
317 if tasks.durations.len() != n
318 || tasks.ends.as_ref().is_some_and(|ends| ends.len() != n)
319 || self.usages().is_some_and(|entries| entries.len() != n)
320 || self.presence().is_some_and(|entries| entries.len() != n)
321 || self.machines().is_some_and(|entries| entries.len() != n)
322 {
323 return Err("scheduling task arrays must have equal lengths");
324 }
325 if matches!(self, Self::Strict(_)) && tasks.ends.is_some() {
326 return Err("strict disjunctive does not accept explicit ends");
327 }
328 let fixed_nonnegative = |entries: &[Operand]| {
329 entries.iter().all(
330 |operand| matches!(operand, Operand::Literal(Value::Integer(value)) if *value >= 0),
331 )
332 };
333 if matches!(self, Self::Unary { .. } | Self::OptionalCumulative { .. })
334 && tasks.ends.is_none()
335 && !fixed_nonnegative(&tasks.durations)
336 {
337 return Err(
338 "native unary and optional cumulative durations must be fixed nonnegative integers",
339 );
340 }
341 if (matches!(self, Self::OptionalCumulative { .. })
342 || self.name() == SchedulingName::CumulativeFlex)
343 && let Some(usages) = self.usages()
344 && !fixed_nonnegative(usages)
345 {
346 return Err("native cumulative usages must be fixed nonnegative integers");
347 }
348 if let Self::Cumulatives { capacities, .. } = self
349 && !capacities
350 .iter()
351 .all(|operand| matches!(operand, Operand::Literal(Value::Integer(_))))
352 {
353 return Err("multi-machine capacities must be fixed integers");
354 }
355 Ok(())
356 }
357 pub fn required_operands(&self, values: &[Option<Value>]) -> Vec<&Operand> {
358 if self.precondition_faults(values).is_empty() {
359 self.relation_operands(values)
360 } else {
361 Vec::new()
362 }
363 }
364 fn precondition_faults(&self, values: &[Option<Value>]) -> Vec<SchedulingFault> {
365 let mut faults = Vec::new();
366 if (matches!(self, Self::OptionalCumulative { .. })
367 || self.name() == SchedulingName::CumulativeFlex)
368 && let Some(Value::Integer(value)) = self.capacities()[0].value(values)
369 && *value < 0
370 {
371 faults.push(SchedulingFault::Capacity { value: *value });
372 }
373 if self.name() != SchedulingName::Cumulative
374 && self.name() != SchedulingName::CumulativesFlex
375 {
376 for (i, duration) in self.tasks().durations.iter().enumerate() {
377 if let Some(Value::Integer(value)) = duration.value(values)
378 && *value < 0
379 {
380 faults.push(SchedulingFault::Duration {
381 position: i + 1,
382 value: *value,
383 });
384 }
385 }
386 }
387 if self.tasks().ends.is_none()
388 && let Some(machines) = self.machines()
389 {
390 for (i, machine) in machines.iter().enumerate() {
391 if let Some(Value::Integer(value)) = machine.value(values)
392 && usize::try_from(*value)
393 .ok()
394 .is_none_or(|index| index >= self.capacities().len())
395 {
396 faults.push(SchedulingFault::Machine {
397 position: i + 1,
398 value: *value,
399 });
400 }
401 }
402 }
403 faults
404 }
405 fn relation_operands(&self, values: &[Option<Value>]) -> Vec<&Operand> {
406 let tasks = self.tasks();
407 if let Some(ends) = &tasks.ends {
408 return self.explicit_operands(ends, values);
409 }
410 if matches!(self, Self::Cumulative { .. }) {
411 return match tasks.starts.len() {
412 0 => Vec::new(),
413 1 => {
414 let mut required = vec![&tasks.durations[0]];
415 if tasks.durations[0]
416 .value(values)
417 .is_some_and(|value| value.integer() > 0)
418 {
419 required.extend(self.usages().into_iter().flatten());
420 required.extend(self.capacities());
421 }
422 required
423 }
424 _ => self.operands(),
425 };
426 }
427 let mut required = Vec::new();
428 required.extend(self.capacities());
429 required.extend(self.presence().into_iter().flatten());
430 for i in 0..tasks.starts.len() {
431 if self
432 .presence()
433 .is_none_or(|present| present[i].value(values).is_some_and(Value::boolean))
434 {
435 required.extend([&tasks.starts[i], &tasks.durations[i]]);
436 if let Some(usages) = self.usages() {
437 required.push(&usages[i]);
438 }
439 if let Some(machines) = self.machines() {
440 required.push(&machines[i]);
441 }
442 }
443 }
444 required
445 }
446 fn explicit_operands<'a>(
447 &'a self,
448 ends: &'a [Operand],
449 values: &[Option<Value>],
450 ) -> Vec<&'a Operand> {
451 let tasks = self.tasks();
452 let mut required = Vec::new();
453 required.extend(self.capacities());
454 required.extend(self.presence().into_iter().flatten());
455 if self.machines().is_none() {
456 required.extend(&tasks.durations);
458 }
459 for (i, end) in ends.iter().enumerate() {
460 if self
461 .presence()
462 .is_some_and(|present| !present[i].value(values).is_some_and(Value::boolean))
463 {
464 continue;
465 }
466 if let Some(machines) = self.machines() {
467 required.push(&machines[i]);
468 if machines[i]
469 .value(values)
470 .is_some_and(|value| !self.valid_machine(value.integer()))
471 {
472 continue;
473 }
474 }
475 required.extend([&tasks.starts[i], end]);
476 if self.machines().is_some()
477 && tasks.starts[i]
478 .value(values)
479 .zip(end.value(values))
480 .is_some_and(|(start, end)| start.integer() >= end.integer())
481 {
482 continue;
483 }
484 if let Some(usages) = self.usages() {
485 required.push(&usages[i]);
486 }
487 }
488 required
489 }
490 fn valid_machine(&self, machine: i64) -> bool {
491 usize::try_from(machine).is_ok_and(|index| index < self.capacities().len())
492 }
493}
494
495struct ConcreteTask {
496 position: usize,
497 start: i64,
498 end: i64,
499 zero_duration: bool,
500 usage: i64,
501 machine: Option<i64>,
502}
503impl Scheduling {
504 fn concrete_tasks(
505 &self,
506 values: &[Option<Value>],
507 constraint: &Constraint,
508 ) -> Result<Vec<ConcreteTask>, CheckError> {
509 let tasks = self.tasks();
510 let integer = |operand: &Operand| {
511 operand
512 .value(values)
513 .expect("available schedule operand")
514 .integer()
515 };
516 let mut concrete = Vec::new();
517 for i in 0..tasks.starts.len() {
518 if self.presence().is_some_and(|present| {
519 !present[i]
520 .value(values)
521 .expect("available presence")
522 .boolean()
523 }) {
524 continue;
525 }
526 let machine = self.machines().map(|machines| integer(&machines[i]));
527 if let Some(machine) = machine
528 && !self.valid_machine(machine)
529 {
530 continue;
531 }
532 let duration = tasks.durations[i].value(values).map(Value::integer);
533 if duration.is_some_and(|duration| duration < 0)
534 && !matches!(
535 self.name(),
536 SchedulingName::Cumulative | SchedulingName::CumulativesFlex
537 )
538 {
539 continue;
540 }
541 let start = integer(&tasks.starts[i]);
542 let end = if let Some(ends) = &tasks.ends {
543 integer(&ends[i])
544 } else {
545 start
546 .checked_add(duration.expect("available legacy duration"))
547 .ok_or_else(|| {
548 constraint.execution_error("checked i64 overflow in scheduling endpoint")
549 })?
550 };
551 if machine.is_some() && tasks.ends.is_some() && start >= end {
552 continue;
553 }
554 concrete.push(ConcreteTask {
555 position: i + 1,
556 start,
557 end,
558 zero_duration: duration == Some(0),
559 usage: self.usages().map_or(1, |usages| integer(&usages[i])),
560 machine,
561 });
562 }
563 Ok(concrete)
564 }
565 fn check_relation(
566 &self,
567 values: &[Option<Value>],
568 constraint: &Constraint,
569 faults: &mut Vec<SchedulingFault>,
570 ) -> Result<(), CheckError> {
571 let tasks = self.tasks();
572 let integer = |operand: &Operand| {
573 operand
574 .value(values)
575 .expect("available schedule operand")
576 .integer()
577 };
578 if self.name() == SchedulingName::Cumulative && tasks.starts.len() <= 1 {
579 if tasks.starts.len() == 1 && integer(&tasks.durations[0]) > 0 {
580 let usage = integer(&self.usages().expect("cumulative usages")[0]);
581 let bound = integer(&self.capacities()[0]);
582 if usage > bound {
583 faults.push(SchedulingFault::TaskUsage {
584 position: 1,
585 usage,
586 bound,
587 });
588 }
589 }
590 } else {
591 let concrete = self.concrete_tasks(values, constraint)?;
592 match self {
593 Self::Strict(_) | Self::Unary { .. } => {
594 for (i, first) in concrete.iter().enumerate() {
595 for second in &concrete[i + 1..] {
596 if first.end > second.start && second.end > first.start {
597 faults.push(SchedulingFault::Conflict {
598 first: first.position,
599 second: second.position,
600 });
601 }
602 }
603 }
604 }
605 Self::OptionalCumulative { capacity, .. } => {
606 let bound = integer(capacity);
607 if bound >= 0 {
608 check_resources(
609 &concrete,
610 ResourceLimit {
611 machine: None,
612 bound,
613 upper: true,
614 },
615 if tasks.ends.is_some() {
616 ResourceChecks::TaskStarts
617 } else {
618 ResourceChecks::ZeroDurationStarts
619 },
620 constraint,
621 faults,
622 )?;
623 }
624 }
625 Self::Cumulative { capacity, .. } => {
626 let bound = integer(capacity);
627 if tasks.ends.is_none() || bound >= 0 {
628 check_resources(
629 &concrete,
630 ResourceLimit {
631 machine: None,
632 bound,
633 upper: true,
634 },
635 if tasks.ends.is_some() {
636 ResourceChecks::TaskStarts
637 } else {
638 ResourceChecks::ConcreteHorizon
639 },
640 constraint,
641 faults,
642 )?;
643 }
644 }
645 Self::Cumulatives {
646 capacities, upper, ..
647 } => {
648 for (machine, capacity) in capacities.iter().enumerate() {
649 let machine = i64::try_from(machine)
650 .map_err(|_| constraint.execution_error("machine index exceeds i64"))?;
651 check_resources(
652 &concrete,
653 ResourceLimit {
654 machine: Some(machine),
655 bound: integer(capacity),
656 upper: *upper,
657 },
658 ResourceChecks::ActiveIntervals,
659 constraint,
660 faults,
661 )?;
662 }
663 }
664 }
665 }
666 Ok(())
667 }
668}
669impl PreparedModel {
670 pub(crate) fn evaluate_schedule(
671 &self,
672 constraint: &Constraint,
673 schedule: &Scheduling,
674 values: &[Option<Value>],
675 ) -> Result<Option<ViolationKind>, CheckError> {
676 let mut faults = schedule.precondition_faults(values);
677 if schedule
678 .relation_operands(values)
679 .iter()
680 .all(|operand| operand.value(values).is_some())
681 {
682 schedule.check_relation(values, constraint, &mut faults)?;
683 }
684 let tasks = schedule.tasks();
685 if faults.is_empty() {
686 return Ok(None);
687 }
688 let argument = |operand: &Operand| self.argument(operand, values);
689 let tasks = (0..tasks.starts.len())
690 .map(|i| SchedulingTask {
691 position: i + 1,
692 start: argument(&tasks.starts[i]),
693 duration: argument(&tasks.durations[i]),
694 end: tasks.ends.as_ref().and_then(|ends| argument(&ends[i])),
695 usage: schedule.usages().and_then(|usages| argument(&usages[i])),
696 present: schedule
697 .presence()
698 .and_then(|present| argument(&present[i])),
699 machine: schedule
700 .machines()
701 .and_then(|machines| argument(&machines[i])),
702 })
703 .collect();
704 Ok(Some(ViolationKind::Scheduling {
705 finding: SchedulingFinding {
706 tasks,
707 capacities: schedule.capacities().iter().filter_map(argument).collect(),
708 faults,
709 },
710 }))
711 }
712}
713
714#[derive(Clone, Copy)]
715struct ResourceLimit {
716 machine: Option<i64>,
717 bound: i64,
718 upper: bool,
719}
720#[derive(Clone, Copy, PartialEq, Eq)]
721enum ResourceChecks {
722 ActiveIntervals,
723 ConcreteHorizon,
724 ZeroDurationStarts,
725 TaskStarts,
726}
727
728fn check_resources(
730 tasks: &[ConcreteTask],
731 limit: ResourceLimit,
732 checks: ResourceChecks,
733 constraint: &Constraint,
734 faults: &mut Vec<SchedulingFault>,
735) -> Result<(), CheckError> {
736 let tasks: Vec<_> = tasks
737 .iter()
738 .filter(|task| task.machine == limit.machine)
739 .collect();
740 let mut boundaries: Vec<_> = tasks
741 .iter()
742 .flat_map(|task| [task.start, task.end])
743 .collect();
744 boundaries.sort_unstable();
745 boundaries.dedup();
746 let horizon_end = tasks.iter().map(|task| task.end).max();
747 let horizon_start = tasks.iter().map(|task| task.start).min();
748 for time in boundaries {
749 let active: Vec<_> = tasks
750 .iter()
751 .copied()
752 .filter(|task| task.start <= time && time < task.end)
753 .collect();
754 if !active.is_empty()
755 || (checks == ResourceChecks::ConcreteHorizon
756 && horizon_start.is_some_and(|start| start <= time)
757 && horizon_end.is_some_and(|end| time < end))
758 {
759 check_resource(&active, None, limit, time, constraint, faults)?;
760 }
761 if matches!(
762 checks,
763 ResourceChecks::ZeroDurationStarts | ResourceChecks::TaskStarts
764 ) {
765 let spanning: Vec<_> = tasks
766 .iter()
767 .copied()
768 .filter(|task| task.start < time && time < task.end)
769 .collect();
770 for starting in tasks.iter().copied().filter(|task| {
771 task.start == time && (checks == ResourceChecks::TaskStarts || task.zero_duration)
772 }) {
773 check_resource(&spanning, Some(starting), limit, time, constraint, faults)?;
774 }
775 }
776 }
777 Ok(())
778}
779fn check_resource(
780 active: &[&ConcreteTask],
781 starting: Option<&ConcreteTask>,
782 limit: ResourceLimit,
783 time: i64,
784 constraint: &Constraint,
785 faults: &mut Vec<SchedulingFault>,
786) -> Result<(), CheckError> {
787 let usage = checked_sum(
788 active
789 .iter()
790 .copied()
791 .chain(starting)
792 .map(|task| (1, task.usage)),
793 )
794 .ok_or_else(|| constraint.execution_error("checked i64 overflow in scheduling resource sum"))?;
795 let ResourceLimit {
796 machine,
797 bound,
798 upper,
799 } = limit;
800 if (upper && usage > bound) || (!upper && usage < bound) {
801 faults.push(SchedulingFault::Resource {
802 machine,
803 time,
804 usage,
805 bound,
806 upper,
807 tasks: active
808 .iter()
809 .copied()
810 .chain(starting)
811 .map(|task| task.position)
812 .collect(),
813 });
814 }
815 Ok(())
816}