Constraints

Scheduling constraints for interval and sequence variables.

Precedence Constraints

Before Constraints (Inequalities)

pycsp3_scheduling.constraints.precedence.end_before_start(a, b, delay=0)[source]

Enforce that interval a ends before interval b starts.

This is the classic precedence constraint used in job-shop scheduling.

Semantics (when both present):

start(b) >= end(a) + delay start(b) >= start(a) + length(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable (predecessor).

  • b (IntervalVar) – Second interval variable (successor).

  • delay (int) – Minimum time between end of a and start of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(end_before_start(task1, task2))  # task2 starts after task1 ends
pycsp3_scheduling.constraints.precedence.start_before_start(a, b, delay=0)[source]

Enforce that interval b cannot start before interval a starts (plus delay).

Semantics (when both present):

start(b) >= start(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Minimum time between start of a and start of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(start_before_start(task1, task2))  # task2 starts after task1 starts
pycsp3_scheduling.constraints.precedence.end_before_end(a, b, delay=0)[source]

Enforce that interval b cannot end before interval a ends (plus delay).

Semantics (when both present):

end(b) >= end(a) + delay start(b) + length(b) >= start(a) + length(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Minimum time between end of a and end of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(end_before_end(task1, task2))  # task2 ends after task1 ends
pycsp3_scheduling.constraints.precedence.start_before_end(a, b, delay=0)[source]

Enforce that interval b cannot end before interval a starts (plus delay).

Semantics (when both present):

end(b) >= start(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Minimum time between start of a and end of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(start_before_end(task1, task2))  # task2 ends after task1 starts

At Constraints (Equalities)

pycsp3_scheduling.constraints.precedence.start_at_start(a, b, delay=0)[source]

Enforce that interval b starts exactly when interval a starts (plus delay).

Semantics (when both present):

start(b) == start(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Time delay between start of a and start of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(start_at_start(task1, task2))  # Start together
>>> satisfy(start_at_start(task1, task2, delay=5))  # task2 starts 5 after task1
pycsp3_scheduling.constraints.precedence.start_at_end(a, b, delay=0)[source]

Enforce that interval b starts exactly when interval a ends (plus delay).

Semantics (when both present):

start(b) == end(a) + delay start(b) == start(a) + length(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Time delay between end of a and start of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(start_at_end(task1, task2))  # task2 starts exactly when task1 ends
pycsp3_scheduling.constraints.precedence.end_at_start(a, b, delay=0)[source]

Enforce that interval a ends exactly when interval b starts (plus delay).

Semantics (when both present):

end(a) == start(b) + delay start(a) + length(a) == start(b) + delay

Note: This is equivalent to start_at_end(b, a, -delay) but expressed from a’s perspective.

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Time delay between end of a and start of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(end_at_start(task1, task2))  # task1 ends exactly when task2 starts
pycsp3_scheduling.constraints.precedence.end_at_end(a, b, delay=0)[source]

Enforce that interval b ends exactly when interval a ends (plus delay).

Semantics (when both present):

end(b) == end(a) + delay start(b) + length(b) == start(a) + length(a) + delay

Parameters:
  • a (IntervalVar) – First interval variable.

  • b (IntervalVar) – Second interval variable.

  • delay (int) – Time delay between end of a and end of b. Default 0.

Returns:

A pycsp3 Node representing the constraint.

Raises:

TypeError – If inputs are not IntervalVar or delay is not int.

Example

>>> task1 = IntervalVar(size=10, name="task1")
>>> task2 = IntervalVar(size=5, name="task2")
>>> satisfy(end_at_end(task1, task2))  # Both end at the same time

Grouping Constraints

pycsp3_scheduling.constraints.grouping.span(main, subtasks)[source]

Constrain main interval to span all present subtasks.

The main interval covers exactly the time range occupied by its subtasks: - start(main) = min{start(i) : i present in subtasks} - end(main) = max{end(i) : i present in subtasks}

Semantics for optional intervals: - If main is present, at least one subtask must be present - If main is absent, all subtasks must be absent - If all subtasks are absent, main must be absent

Parameters:
Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If main is not IntervalVar or subtasks contains non-IntervalVar.

  • ValueError – If subtasks is empty.

Return type:

list

Example

>>> main_task = IntervalVar(name="project")
>>> phases = [IntervalVar(size=10, name=f"phase_{i}") for i in range(3)]
>>> satisfy(span(main_task, phases))  # project spans all phases
pycsp3_scheduling.constraints.grouping.alternative(main, alternatives, cardinality=1)[source]

Constrain exactly k alternatives to be selected and match the main interval.

When main is present, exactly cardinality intervals from alternatives are present, and they have the same start and end times as main.

Semantics: - If main is present: exactly cardinality alternatives are present - Selected alternatives have start(alt) == start(main) and end(alt) == end(main) - If main is absent: all alternatives are absent

Parameters:
  • main (IntervalVar) – The main interval variable.

  • alternatives (Sequence[IntervalVar]) – List of alternative interval variables.

  • cardinality (int) – Number of alternatives to select (default 1).

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If main is not IntervalVar or alternatives contains non-IntervalVar.

  • ValueError – If alternatives is empty or cardinality is invalid.

Return type:

list

Example

>>> main_op = IntervalVar(size=10, name="operation")
>>> machines = [IntervalVar(size=10, optional=True, name=f"m{i}") for i in range(3)]
>>> satisfy(alternative(main_op, machines))  # Assign to exactly one machine
pycsp3_scheduling.constraints.grouping.synchronize(main, intervals)[source]

Constrain all present intervals to synchronize with the main interval.

All present intervals in the array have the same start and end times as main.

Semantics: - If main is present: present intervals have same start/end as main - If main is absent: all intervals in array are absent - Intervals in array can be independently present or absent

Parameters:
Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If main is not IntervalVar or intervals contains non-IntervalVar.

  • ValueError – If intervals is empty.

Return type:

list

Example

>>> main = IntervalVar(size=10, name="meeting")
>>> attendees = [IntervalVar(size=10, optional=True, name=f"person_{i}") for i in range(5)]
>>> satisfy(synchronize(main, attendees))  # Present attendees sync with meeting

Sequence Constraints

No-Overlap

pycsp3_scheduling.constraints.sequence.SeqNoOverlap(sequence, transition_matrix=None, is_direct=False, zero_ignored=True)[source]

Enforce non-overlap on a sequence of intervals with optional transition times.

When intervals in the sequence are assigned to the same resource, they cannot overlap in time. If a transition matrix is provided, setup times between consecutive intervals are enforced based on their types.

Parameters:
  • sequence – SequenceVar or iterable of IntervalVar.

  • transition_matrix (list[list[int]] | None) – Optional square matrix of transition times. If sequence has types, matrix[i][j] gives the minimum time between an interval of type i and an interval of type j.

is_direct: If True, transition times apply only between immediately

consecutive intervals in schedule order (IBM “Next” semantics). If False, transition times apply between any pair where one precedes the other (IBM “After” semantics).

zero_ignored: If True, intervals with zero length are ignored

in the non-overlap constraint.

Returns:

A pycsp3 constraint (ECtr) or list of constraints.

Raises:
  • TypeError – If sequence is not a SequenceVar or iterable of IntervalVar.

  • ValueError – If transition_matrix dimensions don’t match types.

Example

>>> # Simple no-overlap
>>> tasks = [IntervalVar(size=10, name=f"t{i}") for i in range(3)]
>>> satisfy(SeqNoOverlap(tasks))
>>> # With transition times by job type
>>> seq = SequenceVar(intervals=tasks, types=[0, 1, 0], name="machine")
>>> matrix = [[0, 5], [3, 0]]  # Setup time from type i to type j
>>> satisfy(SeqNoOverlap(seq, transition_matrix=matrix))

Ordering Constraints

pycsp3_scheduling.constraints.sequence.first(sequence, interval)[source]

Constrain an interval to be the first in a sequence.

The specified interval must start before all other present intervals in the sequence.

Parameters:
  • sequence – SequenceVar or iterable of IntervalVar.

  • interval (IntervalVar) – The interval that must be first.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If interval is not an IntervalVar.

  • ValueError – If interval is not in the sequence.

Return type:

list

Example

>>> tasks = [IntervalVar(size=5, name=f"t{i}") for i in range(3)]
>>> seq = SequenceVar(intervals=tasks, name="machine")
>>> satisfy(first(seq, tasks[0]))  # tasks[0] must be first
pycsp3_scheduling.constraints.sequence.last(sequence, interval)[source]

Constrain an interval to be the last in a sequence.

The specified interval must end after all other present intervals in the sequence.

Parameters:
  • sequence – SequenceVar or iterable of IntervalVar.

  • interval (IntervalVar) – The interval that must be last.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If interval is not an IntervalVar.

  • ValueError – If interval is not in the sequence.

Return type:

list

Example

>>> tasks = [IntervalVar(size=5, name=f"t{i}") for i in range(3)]
>>> seq = SequenceVar(intervals=tasks, name="machine")
>>> satisfy(last(seq, tasks[2]))  # tasks[2] must be last
pycsp3_scheduling.constraints.sequence.before(sequence, interval1, interval2)[source]

Constrain interval1 to come before interval2 in a sequence.

If both intervals are present, interval1 must end before interval2 starts.

Parameters:
  • sequence – SequenceVar or iterable of IntervalVar.

  • interval1 (IntervalVar) – The interval that must come first.

  • interval2 (IntervalVar) – The interval that must come second.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If either interval is not an IntervalVar.

  • ValueError – If either interval is not in the sequence.

Return type:

list

Example

>>> tasks = [IntervalVar(size=5, name=f"t{i}") for i in range(3)]
>>> seq = SequenceVar(intervals=tasks, name="machine")
>>> satisfy(before(seq, tasks[0], tasks[2]))  # t0 before t2
pycsp3_scheduling.constraints.sequence.previous(sequence, interval1, interval2)[source]

Constrain interval1 to immediately precede interval2 in a sequence.

If both intervals are present, interval1 must come directly before interval2 with no other present intervals between them.

Note: This is a complex constraint that requires tracking sequence ordering. The current implementation enforces that interval1 ends before interval2 starts, and that no other interval can fit between them.

Parameters:
  • sequence – SequenceVar or iterable of IntervalVar.

  • interval1 (IntervalVar) – The interval that must immediately precede.

  • interval2 (IntervalVar) – The interval that must immediately follow.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If either interval is not an IntervalVar.

  • ValueError – If either interval is not in the sequence.

Return type:

list

Example

>>> tasks = [IntervalVar(size=5, name=f"t{i}") for i in range(3)]
>>> seq = SequenceVar(intervals=tasks, name="machine")
>>> satisfy(previous(seq, tasks[0], tasks[1]))  # t0 directly before t1

Consistency Constraints

pycsp3_scheduling.constraints.sequence.same_sequence(sequence1, sequence2)[source]

Constrain common intervals to have the same position in both sequences.

For any interval that appears in both sequences, if it is present, it must occupy the same position (index) in both sequences.

This constraint is useful when the same operations need to maintain consistent ordering across different resources.

Parameters:
  • sequence1 – First SequenceVar or iterable of IntervalVar.

  • sequence2 – Second SequenceVar or iterable of IntervalVar.

Returns:

List of pycsp3 constraint nodes.

Return type:

list

Example

>>> # Operations processed on two parallel machines in same order
>>> ops = [IntervalVar(size=5, name=f"op{i}") for i in range(3)]
>>> seq1 = SequenceVar(intervals=ops, name="machine1")
>>> seq2 = SequenceVar(intervals=ops, name="machine2")
>>> satisfy(same_sequence(seq1, seq2))
pycsp3_scheduling.constraints.sequence.same_common_subsequence(sequence1, sequence2)[source]

Constrain common intervals to have the same relative ordering in both sequences.

For any pair of intervals that appear in both sequences, if both are present, their relative order (which one comes first) must be the same in both sequences.

This is weaker than same_sequence - it only requires the same relative order, not the same absolute positions.

Parameters:
  • sequence1 – First SequenceVar or iterable of IntervalVar.

  • sequence2 – Second SequenceVar or iterable of IntervalVar.

Returns:

List of pycsp3 constraint nodes.

Return type:

list

Example

>>> # Same jobs on different machines maintain relative order
>>> jobs = [IntervalVar(size=5, optional=True, name=f"job{i}") for i in range(4)]
>>> seq1 = SequenceVar(intervals=jobs[:3], name="m1")  # jobs 0,1,2
>>> seq2 = SequenceVar(intervals=jobs[1:], name="m2")  # jobs 1,2,3
>>> # Common jobs (1,2) must have same relative order
>>> satisfy(same_common_subsequence(seq1, seq2))

Forbidden Time Constraints

pycsp3_scheduling.constraints.forbidden.forbid_start(interval, forbidden_periods)[source]

Constrain the interval to not start during any of the forbidden time periods.

For each forbidden period (s, e), the interval’s start time must not be in the range [s, e). That is: NOT (s <= start < e).

Parameters:
  • interval (IntervalVar) – The interval variable to constrain.

  • forbidden_periods (Sequence[tuple[int, int]]) – List of (start, end) tuples defining forbidden periods. Each period is a half-open interval [start, end).

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If interval is not an IntervalVar or periods are malformed.

  • ValueError – If any period has start >= end.

Return type:

list

Example

>>> task = IntervalVar(size=10, name="task")
>>> # Cannot start during lunch break (12-13) or after hours (17-24)
>>> satisfy(forbid_start(task, [(12, 13), (17, 24)]))
pycsp3_scheduling.constraints.forbidden.forbid_end(interval, forbidden_periods)[source]

Constrain the interval to not end during any of the forbidden time periods.

For each forbidden period (s, e), the interval’s end time must not be in the range (s, e]. That is: NOT (s < end <= e).

Parameters:
  • interval (IntervalVar) – The interval variable to constrain.

  • forbidden_periods (Sequence[tuple[int, int]]) – List of (start, end) tuples defining forbidden periods. Each period is a half-open interval (start, end].

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If interval is not an IntervalVar or periods are malformed.

  • ValueError – If any period has start >= end.

Return type:

list

Example

>>> task = IntervalVar(size=10, name="task")
>>> # Cannot end during maintenance window
>>> satisfy(forbid_end(task, [(6, 8)]))
pycsp3_scheduling.constraints.forbidden.forbid_extent(interval, forbidden_periods)[source]

Constrain the interval to not overlap any of the forbidden time periods.

For each forbidden period (s, e), the interval must be completely before or completely after. That is: (end <= s) OR (start >= e).

Parameters:
  • interval (IntervalVar) – The interval variable to constrain.

  • forbidden_periods (Sequence[tuple[int, int]]) – List of (start, end) tuples defining forbidden periods. The interval cannot span across any of these periods.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If interval is not an IntervalVar or periods are malformed.

  • ValueError – If any period has start >= end.

Return type:

list

Example

>>> task = IntervalVar(size=10, name="task")
>>> # Task cannot span across lunch break - must be entirely before or after
>>> satisfy(forbid_extent(task, [(12, 13)]))

Presence Constraints

Binary Presence Constraints

pycsp3_scheduling.constraints.presence.presence_implies(a, b)[source]

Constrain that if interval a is present, then interval b must also be present.

Implements the logical implication: presence(a) => presence(b) Equivalent to: NOT presence(a) OR presence(b)

Parameters:
  • a (IntervalVar) – The antecedent interval (if this is present…).

  • b (IntervalVar) – The consequent interval (…then this must be present).

Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If either argument is not an IntervalVar.

Return type:

list

Notes

  • If a is mandatory (not optional), b must be present (mandatory behavior).

  • If b is mandatory (not optional), constraint is always satisfied.

Example

>>> setup = IntervalVar(size=5, optional=True, name="setup")
>>> main_task = IntervalVar(size=20, optional=True, name="main")
>>> # If we do the main task, we must do setup
>>> satisfy(presence_implies(main_task, setup))
pycsp3_scheduling.constraints.presence.presence_or(a, b)[source]

Constrain that at least one of the intervals must be present.

Implements: presence(a) OR presence(b)

Parameters:
Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If either argument is not an IntervalVar.

Return type:

list

Notes

  • If either interval is mandatory, constraint is always satisfied.

Example

>>> option_a = IntervalVar(size=10, optional=True, name="option_a")
>>> option_b = IntervalVar(size=15, optional=True, name="option_b")
>>> # Must choose at least one option
>>> satisfy(presence_or(option_a, option_b))
pycsp3_scheduling.constraints.presence.presence_xor(a, b)[source]

Constrain that exactly one of the intervals must be present (exclusive or).

Implements: presence(a) XOR presence(b) Equivalent to: (presence(a) OR presence(b)) AND NOT (presence(a) AND presence(b))

Parameters:
Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If either argument is not an IntervalVar.

Return type:

list

Notes

  • Both intervals should be optional for meaningful behavior.

  • If either is mandatory, this may result in infeasibility or forced absence.

Example

>>> route_a = IntervalVar(size=30, optional=True, name="route_a")
>>> route_b = IntervalVar(size=45, optional=True, name="route_b")
>>> # Must take exactly one route
>>> satisfy(presence_xor(route_a, route_b))

Group Presence Constraints

pycsp3_scheduling.constraints.presence.all_present_or_all_absent(intervals)[source]

Constrain that either all intervals are present, or all are absent.

All presence values must be equal: either all 0 or all 1.

Parameters:

intervals (Sequence[IntervalVar]) – List of optional interval variables.

Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If any element is not an IntervalVar.

Return type:

list

Notes

  • All intervals should be optional for meaningful behavior.

  • If any interval is mandatory, all must be present.

Example

>>> subtasks = [IntervalVar(size=5, optional=True, name=f"sub_{i}") for i in range(3)]
>>> # Either do all subtasks or none
>>> satisfy(all_present_or_all_absent(subtasks))
pycsp3_scheduling.constraints.presence.presence_or_all(*intervals)[source]

Constrain that at least one of the intervals must be present.

Variadic version of presence_or for multiple intervals.

Parameters:

*intervals (IntervalVar) – Variable number of interval variables.

Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If any argument is not an IntervalVar.

Return type:

list

Example

>>> options = [IntervalVar(size=10, optional=True, name=f"opt_{i}") for i in range(5)]
>>> # At least one option must be selected
>>> satisfy(presence_or_all(*options))
pycsp3_scheduling.constraints.presence.if_present_then(interval, constraint)[source]

Apply a constraint only when the interval is present.

Implements: presence(interval) => constraint The constraint is only enforced if the interval is present.

Parameters:
  • interval (IntervalVar) – The interval whose presence gates the constraint.

  • constraint – A pycsp3 Node constraint to apply when present.

Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If interval is not an IntervalVar.

Return type:

list

Notes

  • If interval is mandatory, constraint is always enforced.

  • The constraint argument should be a pycsp3 Node object.

Example

>>> from pycsp3.classes.nodes import Node, TypeNode
>>> task = IntervalVar(size=10, optional=True, name="task")
>>> start = start_var(task)
>>> # If task is present, it must start after time 5
>>> min_start = Node.build(TypeNode.GE, start, 5)
>>> satisfy(if_present_then(task, min_start))

Cardinality Constraints

pycsp3_scheduling.constraints.presence.at_least_k_present(intervals, k)[source]

Constrain that at least k intervals must be present.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of interval variables.

  • k (int) – Minimum number of intervals that must be present.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If any element is not an IntervalVar or k is not int.

  • ValueError – If k is negative.

Return type:

list

Example

>>> tasks = [IntervalVar(size=10, optional=True, name=f"t_{i}") for i in range(10)]
>>> # Must complete at least 5 tasks
>>> satisfy(at_least_k_present(tasks, 5))
pycsp3_scheduling.constraints.presence.at_most_k_present(intervals, k)[source]

Constrain that at most k intervals can be present.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of interval variables.

  • k (int) – Maximum number of intervals that can be present.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If any element is not an IntervalVar or k is not int.

  • ValueError – If k is negative.

Return type:

list

Example

>>> features = [IntervalVar(size=10, optional=True, name=f"f_{i}") for i in range(10)]
>>> # Can implement at most 3 features
>>> satisfy(at_most_k_present(features, 3))
pycsp3_scheduling.constraints.presence.exactly_k_present(intervals, k)[source]

Constrain that exactly k intervals must be present.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of interval variables.

  • k (int) – Exact number of intervals that must be present.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If any element is not an IntervalVar or k is not int.

  • ValueError – If k is negative.

Return type:

list

Example

>>> shifts = [IntervalVar(size=480, optional=True, name=f"shift_{i}") for i in range(7)]
>>> # Exactly 5 shifts must be worked
>>> satisfy(exactly_k_present(shifts, 5))

Chain Constraints

pycsp3_scheduling.constraints.chain.chain(intervals, delays=None)[source]

Enforce that intervals execute in sequence order with optional minimum delays.

For each consecutive pair (i, i+1):

end(intervals[i]) + delays[i] <= start(intervals[i+1])

This is equivalent to multiple end_before_start constraints but provides a cleaner, more semantic API for sequential workflows.

Parameters:
  • intervals (Sequence[IntervalVar]) – Ordered list of intervals to chain. Must have at least 2.

  • delays (int | Sequence[int] | None) –

    Minimum delays between consecutive intervals. - If None: all delays are 0 (immediate succession allowed) - If int: same delay between all pairs - If list[int]: delays[i] is delay between intervals[i] and intervals[i+1]

    Must have length = len(intervals) - 1

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If intervals are not IntervalVar or delays are not int.

  • ValueError – If fewer than 2 intervals or delays list has wrong length.

Return type:

list

Optional Intervals Handling:

If any interval is optional, the constraint between it and its neighbors is only enforced when both intervals are present. The chain “skips” absent intervals.

Example

>>> steps = [IntervalVar(size=d, name=f"step_{i}") for i, d in enumerate([5, 10, 3, 8])]
>>> # Execute steps in order with no gaps allowed
>>> satisfy(chain(steps))
>>> # With cleaning time of 2 between all steps
>>> satisfy(chain(steps, delays=2))
>>> # With variable delays between steps
>>> satisfy(chain(steps, delays=[1, 2, 1]))
pycsp3_scheduling.constraints.chain.strict_chain(intervals, delays=None)[source]

Enforce strict sequential execution: each interval starts exactly when previous ends.

For each consecutive pair (i, i+1):

end(intervals[i]) + delays[i] == start(intervals[i+1])

Unlike chain(), this enforces equality (no gaps between intervals).

Parameters:
  • intervals (Sequence[IntervalVar]) – Ordered list of intervals to chain. Must have at least 2.

  • delays (int | Sequence[int] | None) – Fixed delays between consecutive intervals. - If None: all delays are 0 (no gap) - If int: same delay between all pairs - If list[int]: delays[i] is delay between intervals[i] and intervals[i+1]

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If intervals are not IntervalVar or delays are not int.

  • ValueError – If fewer than 2 intervals or delays list has wrong length.

Return type:

list

Example

>>> tasks = [IntervalVar(size=d, name=f"task_{i}") for i, d in enumerate([5, 10, 3])]
>>> # Tasks execute back-to-back with no gaps
>>> satisfy(strict_chain(tasks))
>>> # With fixed 2-unit break between tasks
>>> satisfy(strict_chain(tasks, delays=2))

Overlap Constraints

pycsp3_scheduling.constraints.overlap.must_overlap(a, b)[source]

Constrain two intervals to share at least some time (overlap).

The intervals must have a non-zero overlap duration.

Parameters:
Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If either argument is not an IntervalVar.

Return type:

list

Semantics:
  • start(a) < end(b) AND start(b) < end(a)

  • When either interval is optional and absent, the constraint behavior depends on the use case. By default, both must be present for the constraint to be meaningful.

Example

>>> meeting = IntervalVar(size=60, name="meeting")
>>> availability = IntervalVar(start=9*60, end=17*60, size=480, name="available")
>>> # Meeting must occur during availability
>>> satisfy(must_overlap(meeting, availability))
pycsp3_scheduling.constraints.overlap.overlap_at_least(a, b, min_overlap)[source]

Constrain two intervals to overlap by at least a minimum duration.

Parameters:
  • a (IntervalVar) – First interval.

  • b (IntervalVar) – Second interval.

  • min_overlap (int) – Minimum required overlap duration.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If intervals are not IntervalVar or min_overlap is not int.

  • ValueError – If min_overlap is negative.

Return type:

list

Semantics:
  • overlap_length(a, b) >= min_overlap

  • Where overlap_length = max(0, min(end(a), end(b)) - max(start(a), start(b)))

Example

>>> mentor = IntervalVar(size=120, name="mentor_shift")
>>> trainee = IntervalVar(size=120, name="trainee_shift")
>>> # Must overlap by at least 30 minutes for handoff
>>> satisfy(overlap_at_least(mentor, trainee, 30))
pycsp3_scheduling.constraints.overlap.no_overlap_pairwise(intervals)[source]

Constrain intervals to not overlap, using simple pairwise decomposition.

This is a simpler alternative to SeqNoOverlap when you don’t need sequence ordering or transition times.

Parameters:

intervals (Sequence[IntervalVar]) – List of intervals that cannot overlap.

Returns:

List of pycsp3 constraint nodes.

Raises:

TypeError – If any element is not an IntervalVar.

Return type:

list

Semantics:
  • For each pair (i, j): end(i) <= start(j) OR end(j) <= start(i)

  • O(n²) constraints for n intervals

Example

>>> meetings = [IntervalVar(size=30, name=f"meeting_{i}") for i in range(5)]
>>> # No two meetings can overlap
>>> satisfy(no_overlap_pairwise(meetings))
pycsp3_scheduling.constraints.overlap.disjunctive(intervals, transition_times=None)[source]

Constrain that at most one interval can be active at any time (unary resource).

This is equivalent to SeqNoOverlap but without creating a SequenceVar. Use this when you don’t need sequence ordering expressions.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of intervals sharing the unary resource.

  • transition_times (Sequence[Sequence[int]] | None) – Optional transition time matrix. If provided, intervals must have types assigned and transition_times[i][j] is the minimum time between an interval of type i and type j.

Returns:

List of pycsp3 constraint nodes.

Raises:
  • TypeError – If any element is not an IntervalVar.

  • ValueError – If transition_times provided but intervals don’t have types.

Return type:

list

Semantics:
  • For any two intervals: they cannot overlap in time

  • With transition_times: additional setup time required between types

Example

>>> tasks = [IntervalVar(size=d, name=f"task_{i}") for i, d in enumerate(durations)]
>>> # Single machine - only one task at a time
>>> satisfy(disjunctive(tasks))

Bounds Constraints

pycsp3_scheduling.constraints.bounds.release_date(interval, time)[source]

Constrain an interval to not start before a given time.

The interval’s start time must be >= the release date.

Parameters:
  • interval (IntervalVar) – The interval variable to constrain.

  • time (int) – The earliest allowed start time (release date).

Returns:

List of pycsp3 constraint nodes.

Return type:

list

Example

>>> task = IntervalVar(size=10, name="task")
>>> satisfy(release_date(task, 8))  # Cannot start before time 8
pycsp3_scheduling.constraints.bounds.deadline(interval, time)[source]

Constrain an interval to complete by a given time.

The interval’s end time must be <= the deadline.

Parameters:
  • interval (IntervalVar) – The interval variable to constrain.

  • time (int) – The latest allowed end time (deadline).

Returns:

List of pycsp3 constraint nodes.

Return type:

list

Example

>>> task = IntervalVar(size=10, name="task")
>>> satisfy(deadline(task, 50))  # Must finish by time 50
pycsp3_scheduling.constraints.bounds.time_window(interval, earliest_start, latest_end)[source]

Constrain an interval to execute within a time window.

Combines release_date and deadline constraints.

Parameters:
  • interval (IntervalVar) – The interval variable to constrain.

  • earliest_start (int) – The earliest allowed start time.

  • latest_end (int) – The latest allowed end time.

Returns:

List of pycsp3 constraint nodes.

Raises:

ValueError – If earliest_start > latest_end.

Return type:

list

Example

>>> delivery = IntervalVar(size=30, name="delivery")
>>> # Delivery must occur during business hours (9am-5pm as minutes)
>>> satisfy(time_window(delivery, earliest_start=9*60, latest_end=17*60))

State Helpers

pycsp3_scheduling.functions.state_functions.requires_state(interval, state_func, required_state)[source]

Simplified constraint that interval requires a specific state.

This is a convenience wrapper around always_equal with a more intuitive parameter order (interval first, like other constraint functions).

Parameters:
  • interval (IntervalVar) – The interval requiring the state.

  • state_func (StateFunction) – The state function (resource).

  • required_state (int) – The required state value.

Returns:

A StateConstraint representing the requirement.

Return type:

StateConstraint

Example

>>> oven = StateFunction(name="oven_temp")
>>> bake_task = IntervalVar(size=30, name="bake")
>>> # Baking requires oven at temperature state 2 (e.g., 350F)
>>> satisfy(requires_state(bake_task, oven, 2))
pycsp3_scheduling.functions.state_functions.sets_state(interval, state_func, before_state, after_state)[source]

Interval transitions the state from one value to another.

This constraint models a task that changes the state of a resource. The state must be before_state when the interval starts (if specified), and becomes after_state when the interval ends.

Parameters:
  • interval (IntervalVar) – The interval performing the state change.

  • state_func (StateFunction) – The state function.

  • before_state (int | None) – Required state before interval (None = any state).

  • after_state (int) – State after interval completes.

Returns:

List of StateConstraints representing the state transition.

Return type:

list[StateConstraint]

Example

>>> machine_mode = StateFunction(name="machine_mode")
>>> changeover = IntervalVar(size=15, name="changeover_A_to_B")
>>> # This changeover task transitions machine from mode A (0) to mode B (1)
>>> satisfy(sets_state(changeover, machine_mode, before_state=0, after_state=1))

Aggregate Expressions

pycsp3_scheduling.expressions.aggregate.count_present(intervals)[source]

Expression counting how many intervals are present.

Parameters:

intervals (Sequence[IntervalVar]) – List of interval variables (can be optional or mandatory).

Returns:

A pycsp3 Node representing the sum of presence values.

Raises:
  • TypeError – If any element is not an IntervalVar.

  • ValueError – If intervals list is empty.

Return type:

Node

Semantics:
  • Returns sum(presence(i) for i in intervals)

  • Mandatory intervals contribute 1

  • Optional absent intervals contribute 0

Example

>>> tasks = [IntervalVar(size=10, optional=True, name=f"t_{i}") for i in range(10)]
>>> # Must complete at least 5 tasks
>>> satisfy(count_present(tasks) >= 5)
>>> # Maximize number of completed tasks
>>> maximize(count_present(tasks))
pycsp3_scheduling.expressions.aggregate.earliest_start(intervals, absent_value=1073741823)[source]

Expression for the earliest start time among present intervals.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of interval variables.

  • absent_value (int) – Value to use for absent optional intervals. Defaults to INTERVAL_MAX so absent intervals don’t affect the minimum.

Returns:

A pycsp3 Node representing min(start(i) for present i).

Raises:
  • TypeError – If any element is not an IntervalVar.

  • ValueError – If intervals list is empty.

Return type:

Node

Semantics:
  • Returns the minimum start time among all present intervals

  • Absent intervals use absent_value (default: very large, so ignored in min)

Example

>>> tasks = [IntervalVar(size=10, name=f"t_{i}") for i in range(5)]
>>> # All tasks must start after time 10
>>> satisfy(earliest_start(tasks) >= 10)
pycsp3_scheduling.expressions.aggregate.latest_end(intervals, absent_value=0)[source]

Expression for the latest end time among present intervals.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of interval variables.

  • absent_value (int) – Value to use for absent optional intervals. Defaults to INTERVAL_MIN so absent intervals don’t affect the maximum.

Returns:

A pycsp3 Node representing max(end(i) for present i).

Raises:
  • TypeError – If any element is not an IntervalVar.

  • ValueError – If intervals list is empty.

Return type:

Node

Semantics:
  • Returns the maximum end time among all present intervals

  • Absent intervals use absent_value (default: very small, so ignored in max)

Example

>>> tasks = [IntervalVar(size=10, name=f"t_{i}") for i in range(5)]
>>> # Minimize makespan
>>> minimize(latest_end(tasks))
pycsp3_scheduling.expressions.aggregate.span_length(intervals, absent_value=0)[source]

Expression for the total span from earliest start to latest end.

Parameters:
  • intervals (Sequence[IntervalVar]) – List of interval variables.

  • absent_value (int) – Value to return if all intervals are absent.

Returns:

A pycsp3 Node representing latest_end - earliest_start.

Raises:
  • TypeError – If any element is not an IntervalVar.

  • ValueError – If intervals list is empty.

Return type:

Node

Semantics:
  • Returns max(end(i)) - min(start(i)) for present intervals

  • This is the makespan if all intervals must be scheduled

Example

>>> tasks = [IntervalVar(size=10, name=f"t_{i}") for i in range(5)]
>>> # Minimize total span
>>> minimize(span_length(tasks))
pycsp3_scheduling.expressions.aggregate.makespan(intervals)[source]

Expression for the makespan (latest end time) of a set of intervals.

This is a convenience alias for latest_end() commonly used in scheduling.

Parameters:

intervals (Sequence[IntervalVar]) – List of interval variables.

Returns:

A pycsp3 Node representing max(end(i) for all i).

Return type:

Node

Example

>>> tasks = [IntervalVar(size=d, name=f"t_{i}") for i, d in enumerate(durations)]
>>> minimize(makespan(tasks))

Constraint Reference

Precedence Constraint Semantics

Constraint

Semantics (when both present)

end_before_start(a, b, delay=0)

start(b) >= end(a) + delay

start_before_start(a, b, delay=0)

start(b) >= start(a) + delay

end_before_end(a, b, delay=0)

end(b) >= end(a) + delay

start_before_end(a, b, delay=0)

end(b) >= start(a) + delay

start_at_start(a, b, delay=0)

start(b) == start(a) + delay

start_at_end(a, b, delay=0)

start(b) == end(a) + delay

end_at_start(a, b, delay=0)

end(a) == start(b) + delay

end_at_end(a, b, delay=0)

end(b) == end(a) + delay

Optional Interval Behavior

When one or both intervals are optional:

  • If either interval is absent, the constraint is trivially satisfied

  • If both intervals are present, the constraint is enforced

Usage Examples

Precedence Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import IntervalVar, end_before_start, start_at_start

task1 = IntervalVar(size=10, name="task1")
task2 = IntervalVar(size=15, name="task2")

# task1 must complete before task2 starts
satisfy(end_before_start(task1, task2))

# task1 must complete at least 5 time units before task2 starts
satisfy(end_before_start(task1, task2, delay=5))

# task1 and task2 must start at the same time
satisfy(start_at_start(task1, task2))

No-Overlap Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import IntervalVar, SequenceVar, SeqNoOverlap

tasks = [IntervalVar(size=10, name=f"task{i}") for i in range(3)]

# Simple no-overlap
satisfy(SeqNoOverlap(tasks))

# With sequence variable and transition matrix
seq = SequenceVar(intervals=tasks, types=[0, 1, 0], name="machine")
transition_matrix = [
    [0, 5],   # From type 0: 0→0=0, 0→1=5
    [3, 0],   # From type 1: 1→0=3, 1→1=0
]
satisfy(SeqNoOverlap(seq, transition_matrix=transition_matrix))

Grouping Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import IntervalVar, span, alternative, synchronize

# Span: main interval covers all sub-intervals
project = IntervalVar(size=(0, 1000), name="project")
phases = [IntervalVar(size=s, name=f"phase{i}") for i, s in enumerate([10, 15, 20])]
satisfy(span(project, phases))

# Alternative: select exactly one alternative
main = IntervalVar(size=10, name="main")
alts = [IntervalVar(size=10, optional=True, name=f"alt{i}") for i in range(3)]
satisfy(alternative(main, alts))

# Synchronize: all intervals align with main
leader = IntervalVar(size=10, name="leader")
followers = [IntervalVar(size=10, name=f"f{i}") for i in range(2)]
satisfy(synchronize(leader, followers))

Sequence Ordering

from pycsp3 import satisfy
from pycsp3_scheduling import IntervalVar, SequenceVar, first, last, before, previous

tasks = [IntervalVar(size=5, name=f"t{i}") for i in range(4)]
seq = SequenceVar(intervals=tasks, name="machine")

# task[0] must be first in sequence
satisfy(first(seq, tasks[0]))

# task[3] must be last in sequence
satisfy(last(seq, tasks[3]))

# task[1] must come before task[2] (not necessarily immediately)
satisfy(before(seq, tasks[1], tasks[2]))

# task[1] must immediately precede task[2]
satisfy(previous(seq, tasks[1], tasks[2]))

Forbidden Time Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import IntervalVar, forbid_start, forbid_end, forbid_extent

task = IntervalVar(size=10, name="task")

# Cannot start during lunch break (12-13) or after hours (17-24)
satisfy(forbid_start(task, [(12, 13), (17, 24)]))

# Cannot end during maintenance window (6-8)
satisfy(forbid_end(task, [(6, 8)]))

# Cannot overlap the lunch break at all
satisfy(forbid_extent(task, [(12, 13)]))

Presence Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import (
    IntervalVar, presence_implies, presence_or, presence_xor,
    all_present_or_all_absent, at_least_k_present, exactly_k_present
)

# Setup must run if main task runs
setup = IntervalVar(size=5, optional=True, name="setup")
main = IntervalVar(size=20, optional=True, name="main")
satisfy(presence_implies(main, setup))

# At least one delivery method must be chosen
delivery_a = IntervalVar(size=30, optional=True, name="delivery_a")
delivery_b = IntervalVar(size=45, optional=True, name="delivery_b")
satisfy(presence_or(delivery_a, delivery_b))

# Exactly one route must be taken
route_a = IntervalVar(size=30, optional=True, name="route_a")
route_b = IntervalVar(size=45, optional=True, name="route_b")
satisfy(presence_xor(route_a, route_b))

# All subtasks must run together or not at all
subtasks = [IntervalVar(size=5, optional=True, name=f"sub_{i}") for i in range(3)]
satisfy(all_present_or_all_absent(subtasks))

# At least 3 of 5 tasks must be completed
tasks = [IntervalVar(size=10, optional=True, name=f"t_{i}") for i in range(5)]
satisfy(at_least_k_present(tasks, 3))

# Exactly 2 workers must be assigned
workers = [IntervalVar(size=480, optional=True, name=f"w_{i}") for i in range(4)]
satisfy(exactly_k_present(workers, 2))

Chain Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import IntervalVar, chain, strict_chain

# Assembly line: steps must execute in order
steps = [IntervalVar(size=d, name=f"step_{i}") for i, d in enumerate([5, 10, 3, 8])]
satisfy(chain(steps))

# With minimum gap of 2 between each step
satisfy(chain(steps, delays=2))

# With variable gaps between steps
satisfy(chain(steps, delays=[1, 2, 3]))

# Back-to-back execution (no gaps)
satisfy(strict_chain(steps))

Overlap Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import (
    IntervalVar, must_overlap, overlap_at_least,
    no_overlap_pairwise, disjunctive
)

# Two intervals must overlap
meeting = IntervalVar(size=60, name="meeting")
availability = IntervalVar(size=480, name="available")
satisfy(must_overlap(meeting, availability))

# Shifts must overlap by at least 30 minutes for handoff
mentor_shift = IntervalVar(size=120, name="mentor")
trainee_shift = IntervalVar(size=120, name="trainee")
satisfy(overlap_at_least(mentor_shift, trainee_shift, 30))

# Simple pairwise no-overlap (without sequence variable)
meetings = [IntervalVar(size=30, name=f"meeting_{i}") for i in range(5)]
satisfy(no_overlap_pairwise(meetings))

# Disjunctive resource (unary - only one task at a time)
tasks = [IntervalVar(size=d, name=f"task_{i}") for i, d in enumerate([10, 15, 8])]
satisfy(disjunctive(tasks))

# Disjunctive with transition times between types
transition = [[0, 5], [3, 0]]
satisfy(disjunctive(tasks, transition_times=transition))

Bounds Constraints

from pycsp3 import satisfy
from pycsp3_scheduling import (
    IntervalVar, release_date, deadline, time_window
)

task = IntervalVar(size=30, name="delivery")

# Task cannot start before 9am (time 540 in minutes)
satisfy(release_date(task, 540))

# Task must complete by 5pm (time 1020 in minutes)
satisfy(deadline(task, 1020))

# Combined: task must execute within business hours
satisfy(time_window(task, earliest_start=540, latest_end=1020))

# Works with optional intervals too
optional_task = IntervalVar(size=30, optional=True, name="optional_delivery")
satisfy(time_window(optional_task, earliest_start=540, latest_end=1020))

# Operator syntax (shorthand) - more concise!
satisfy(task >= 540)           # same as release_date(task, 540)
satisfy(task <= 1020)          # same as deadline(task, 1020)
satisfy(task > 500)            # start strictly after 500
satisfy(task < 1100)           # end strictly before 1100

# Works great in list comprehensions
tasks = [IntervalVar(size=30, name=f"task_{i}") for i in range(5)]
satisfy(t >= 0 for t in tasks)      # all tasks start at or after 0
satisfy(t <= 1000 for t in tasks)   # all tasks end by time 1000

State Helpers

from pycsp3 import satisfy
from pycsp3_scheduling import (
    IntervalVar, StateFunction, requires_state, sets_state
)

# Create a state function for oven temperature
oven = StateFunction(name="oven_temp")

# Baking task requires oven at temperature state 2
bake = IntervalVar(size=30, name="bake")
satisfy(requires_state(bake, oven, 2))

# Preheat task transitions oven from cold (0) to hot (2)
preheat = IntervalVar(size=15, name="preheat")
satisfy(sets_state(preheat, oven, before_state=0, after_state=2))

# Cooldown task transitions oven from any state to cold
cooldown = IntervalVar(size=20, name="cooldown")
satisfy(sets_state(cooldown, oven, before_state=None, after_state=0))

Aggregate Expressions

from pycsp3 import satisfy, minimize, maximize
from pycsp3_scheduling import (
    IntervalVar, count_present, earliest_start, latest_end,
    span_length, makespan
)

tasks = [IntervalVar(size=10, optional=True, name=f"t_{i}") for i in range(10)]

# Must complete at least 5 tasks
satisfy(count_present(tasks) >= 5)

# Maximize number of completed tasks
maximize(count_present(tasks))

# All tasks must start after time 10
satisfy(earliest_start(tasks) >= 10)

# All tasks must end before time 100
satisfy(latest_end(tasks) <= 100)

# Minimize makespan (latest end time)
minimize(makespan(tasks))

# Minimize total span
minimize(span_length(tasks))