An explainable finite-domain constraint solver and scheduling toolkit for MoonBit.
Dependencies
moon check
moon test
moon run cmd/main
moon run cmd/weekpub(all) enum BudgetedSolveOutcome {
Found(Solution, SolveStats)
ProvedUnsatisfiable(SolveStats)
BudgetExhausted(SolveStats)
} derive(Eq, ToJson, Debug)pub(all) struct ConflictItem {
index : Int
name : String?
constraint : Constraint
} derive(Eq, Debug)pub(all) enum Constraint {
Equal(Var, Int)
NotEqual(Var, Int)
Different(Var, Var)
OffsetDifferent(Var, Var, Int)
LessThan(Var, Var)
AllDifferent(Array[Var])
SumEquals(Array[Var], Int)
CountAtMost(Array[Var], Int, Int)
CountBetween(Array[Var], Int, Int, Int)
AllowedTuples(Array[Var], Array[Array[Int]])
Element(Var, Array[Int], Var)
LinearEquals(Array[Var], Array[Int], Int)
LinearAtMost(Array[Var], Array[Int], Int)
} derive(Eq, Debug)pub(all) struct CoverageEntry {
requirement : CoverageRequirement
position : Int
worker : Worker
} derive(Eq, ToJson, Debug)pub(all) struct CoverageRequest {
workers : Array[Worker]
requirements : Array[CoverageRequirement]
policy : RosterPolicy
} derive(Eq, ToJson, Debug, FromJson)fn CoverageRequest::solve_balanced(self : CoverageRequest, candidate_limit : Int) -> Result[OptimizedCoverageRoster, RosterError]fn CoverageRequest::solve_fairest(self : CoverageRequest, node_limit : Int) -> Result[FairCoverageOutcome, RosterError]fn CoverageRequest::solve_with_consecutive_limit(self : CoverageRequest, maximum_consecutive_slots : Int) -> Result[CoverageRoster, RosterError]fn CoverageRequest::solve_with_node_limit(self : CoverageRequest, node_limit : Int) -> Result[CoverageSolveOutcome, RosterError]fn CoverageRequest::solve_with_strategy(self : CoverageRequest, node_limit : Int, strategy : SearchStrategy) -> Result[CoverageSolveOutcome, RosterError]pub(all) struct CoverageRoster {
entries : Array[CoverageEntry]
stats : SolveStats
} derive(Eq, ToJson, Debug)fn CoverageRoster::workers_for_requirement(self : CoverageRoster, requirement_id : Int) -> Array[Worker]pub(all) enum CoverageSolveOutcome {
CoverageFound(CoverageRoster)
CoverageProvedUnsatisfiable(SolveStats)
CoverageBudgetExhausted(SolveStats)
} derive(Eq, Debug)pub(all) enum ExplainOutcome {
Consistent(SolveStats)
Inconsistent(ConflictReport)
} derive(Eq, Debug)pub(all) enum FairCoverageOutcome {
FairCoverageFound(FairCoverageRoster)
FairCoverageProvedUnsatisfiable(SolveStats, Int)
FairCoverageBudgetExhausted(SolveStats, Int)
} derive(Eq, Debug)pub(all) struct FairCoverageRoster {
roster : CoverageRoster
balance : BalanceScore
windows_checked : Int
total_nodes : Int
total_backtracks : Int
} derive(Eq, ToJson, Debug)pub(all) enum ModelError {
EmptyName
DuplicateName(String)
EmptyConstraintName
DuplicateConstraintName(String)
EmptyDomain(String)
DuplicateDomainValue(String, Int)
ForeignVariable(String)
EmptyConstraint
EmptyTupleTable
EmptyElementTable
InvalidTupleArity(Int, Int, Int)
TupleValueOutsideDomain(Int, String, Int)
DuplicateTuple(Int, Int)
ElementIndexOutsideTable(String, Int, Int)
InvalidCoefficientArity(Int, Int)
InvalidConstraintLimit(Int)
InvalidCountBounds(Int, Int)
InvalidSolutionLimit(Int)
InvalidTraceLimit(Int)
InvalidNodeLimit(Int)
} derive(Eq, Debug)pub(all) struct OptimizedCoverageRoster {
roster : CoverageRoster
balance : BalanceScore
candidates_evaluated : Int
} derive(Eq, ToJson, Debug)pub(all) struct OptimizedRoster {
roster : Roster
balance : BalanceScore
candidates_evaluated : Int
} derive(Eq, Debug)pub(all) struct PreferredRoster {
roster : Roster
total_penalty : Int
penalty_breakdown : RosterPenaltyBreakdown
objective_order : RosterObjectiveOrder
balance : BalanceScore
candidates_evaluated : Int
} derive(Eq, ToJson, Debug)fn Problem::add_named_constraint(self : Problem, name : String, constraint : Constraint) -> Result[Unit, ModelError]fn Problem::add_variable(self : Problem, name : String, domain : Array[Int]) -> Result[Var, ModelError]fn Problem::solve_all(self : Problem, limit : Int) -> Result[(Array[Solution], SolveStats), ModelError]fn Problem::solve_all_with_strategy(self : Problem, limit : Int, strategy : SearchStrategy) -> Result[(Array[Solution], SolveStats), ModelError]fn Problem::solve_with_node_limit(self : Problem, node_limit : Int, strategy : SearchStrategy) -> Result[BudgetedSolveOutcome, ModelError]pub(all) enum RosterError {
DuplicateWorkerId(Int)
DuplicateShiftId(Int)
DuplicateCoverageRequirementId(Int)
InvalidCoverageWorkerCount(Int, Int)
NoEligibleWorker(Int, String)
InsufficientSlotCapacity(Int, Int, Int)
InvalidCandidateLimit(Int)
InvalidWorkloadLimit(Int)
InvalidRestGap(Int)
UnknownPenaltyWorker(Int)
UnknownPenaltyShift(Int)
DuplicateAssignmentPenalty(Int, Int)
InvalidAssignmentPenalty(Int, Int, Int)
InvalidConsecutiveSlotPenalty(Int)
InvalidConsecutiveWorkLimit(Int)
UnknownQuotaWorker(Int)
DuplicateWorkerQuota(Int)
InvalidWorkerQuota(Int, Int, Int)
MinimumWorkerQuotaExceedsShiftCount(Int, Int, Int)
InvalidModel(ModelError)
Unsatisfiable(SolveStats)
} derive(Eq, Debug)pub(all) struct RosterOptimizationRequest {
workers : Array[Worker]
shifts : Array[Shift]
policy : RosterPolicy
objective : RosterObjective
candidate_limit : Int
} derive(Eq, ToJson, Debug, FromJson)fn RosterOptimizationRequest::solve(self : RosterOptimizationRequest) -> Result[PreferredRoster, RosterError]pub(all) struct RosterPenaltyBreakdown {
assignment_charges : Array[AssignmentPenaltyCharge]
consecutive_slot_charges : Array[ConsecutiveSlotCharge]
assignment_total : Int
consecutive_slot_total : Int
total_penalty : Int
} derive(Eq, ToJson, Debug)pub(all) struct RosterRepairPlan {
issues : Array[RosterIssue]
conflict : ConflictReport?
repairs : Array[RosterRepair]
} derive(Eq, Debug)pub(all) enum SolveOutcome {
Satisfied(Solution, SolveStats)
Unsatisfied(SolveStats)
} derive(Eq, ToJson, Debug)pub(all) struct SolveTrace {
outcome : SolveOutcome
events : Array[SearchEvent]
truncated : Bool
} derive(Eq, ToJson, Debug)fn analyze_coverage(workers : Array[Worker], requirements : Array[CoverageRequirement]) -> Result[CoverageAnalysis, RosterError]fn analyze_coverage_with_policy(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy) -> Result[CoverageAnalysis, RosterError]fn build_balanced_coverage_roster(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy, candidate_limit : Int) -> Result[OptimizedCoverageRoster, RosterError]fn build_balanced_roster(workers : Array[Worker], shifts : Array[Shift], candidate_limit : Int) -> Result[OptimizedRoster, RosterError]fn build_balanced_roster_with_policy(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy, candidate_limit : Int) -> Result[OptimizedRoster, RosterError]fn build_coverage_roster(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy) -> Result[CoverageRoster, RosterError]fn build_coverage_roster_with_consecutive_limit(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy, maximum_consecutive_slots : Int) -> Result[CoverageRoster, RosterError]fn build_coverage_roster_with_node_limit(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy, node_limit : Int) -> Result[CoverageSolveOutcome, RosterError]fn build_coverage_roster_with_strategy(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy, node_limit : Int, strategy : SearchStrategy) -> Result[CoverageSolveOutcome, RosterError]fn build_fairest_coverage_roster(workers : Array[Worker], requirements : Array[CoverageRequirement], policy : RosterPolicy, node_limit : Int) -> Result[FairCoverageOutcome, RosterError]fn build_preferred_roster(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy, penalties : Array[AssignmentPenalty], candidate_limit : Int) -> Result[PreferredRoster, RosterError]fn build_preferred_roster_with_objective(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy, objective : RosterObjective, candidate_limit : Int) -> Result[PreferredRoster, RosterError]fn build_preferred_roster_with_order(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy, objective : RosterObjective, order : RosterObjectiveOrder, candidate_limit : Int) -> Result[PreferredRoster, RosterError]fn build_roster_with_consecutive_limit(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy, maximum_consecutive_slots : Int) -> Result[Roster, RosterError]fn build_roster_with_policy(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy) -> Result[Roster, RosterError]fn build_roster_with_quotas(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy, quotas : Array[WorkerQuota]) -> Result[Roster, RosterError]fn decode_roster_optimization_request(text : String) -> Result[RosterOptimizationRequest, RosterJsonError]fn suggest_capped_roster_repairs(workers : Array[Worker], shifts : Array[Shift], max_shifts_per_worker : Int) -> Result[RosterRepairPlan, RosterError]fn suggest_roster_policy_repairs(workers : Array[Worker], shifts : Array[Shift], policy : RosterPolicy) -> Result[RosterRepairPlan, RosterError]Install
Download zipAn explainable finite-domain constraint solver and scheduling toolkit for MoonBit.
Dependencies