use solverforge_config::{
ConstructionHeuristicConfig, ConstructionHeuristicType, LocalSearchConfig, MoveSelectorConfig,
PartitionedSearchConfig, SelectionOrder, SolverConfig, UnionSelectionOrder, UnionWeighting,
};
use crate::builder::{
RuntimeCandidateMetricBinding, RuntimeProviderHandle, RuntimeScalarSlotId, ScalarGroupBinding,
SearchContext,
};
use crate::descriptor::ResolvedVariableBinding;
use crate::phase::construction::{ScalarConstructionSchedule, ScalarOrMixedSlotOrder};
use super::defaults::DefaultRuntimeBindings;
use super::types::{CompiledListSlot, CompiledScalarSlot};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ScalarLeafKind {
Change,
Swap,
NearbyChange,
NearbySwap,
PillarChange,
PillarSwap,
RuinRecreate,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ListLeafKind {
Change,
NearbyChange,
Swap,
Permute,
Precedence,
NearbySwap,
SublistChange,
SublistSwap,
Reverse,
KOpt,
Ruin,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ListConstructionKind {
RoundRobin,
CheapestInsertion,
RegretInsertion,
ClarkeWright,
KOpt,
}
impl ListConstructionKind {
pub(super) fn from_heuristic(heuristic: ConstructionHeuristicType) -> Option<Self> {
match heuristic {
ConstructionHeuristicType::ListRoundRobin => Some(Self::RoundRobin),
ConstructionHeuristicType::ListCheapestInsertion => Some(Self::CheapestInsertion),
ConstructionHeuristicType::ListRegretInsertion => Some(Self::RegretInsertion),
ConstructionHeuristicType::ListClarkeWright => Some(Self::ClarkeWright),
ConstructionHeuristicType::ListKOpt => Some(Self::KOpt),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ProviderBindingPlan {
pub handle: RuntimeProviderHandle,
pub declared_schema_index: usize,
pub allowed_slots: Vec<RuntimeScalarSlotId>,
pub policy: ProviderBindingPolicy,
pub candidate_contract: ProviderCandidateContract,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ProviderBindingPolicy {
CallbackGroup {
rotation_seed_salt: u64,
pull_timing: ProviderPullTiming,
},
StaticGroup {
rotation_seed_salt: u64,
declared_max_moves_per_step: Option<usize>,
pull_timing: ProviderPullTiming,
},
CallbackRepair {
rotation_seed_salt: u64,
pull_timing: ProviderPullTiming,
},
StaticRepair {
constraint_rotation_seed_salt: u64,
provider_rotation_seed_salt: u64,
spec_rotation_seed_salt: u64,
pull_timing: ProviderPullTiming,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ProviderPullTiming {
FirstReachableNext,
OpenCursor,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ProviderSchedule {
Group {
value_candidate_limit: Option<usize>,
requested_max_moves_per_step: Option<usize>,
},
Repair {
constraints: Vec<String>,
max_matches_per_step: usize,
max_repairs_per_match: usize,
max_moves_per_step: usize,
include_soft_matches: bool,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CompiledProviderPlan {
pub move_kind: ProviderMoveKind,
pub schedule: ProviderSchedule,
pub bindings: Vec<ProviderBindingPlan>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub(crate) enum ProviderMoveKind {
Grouped = 1,
ConflictRepair = 2,
CompoundConflictRepair = 3,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ProviderCandidateContract {
pub reason_storage: ProviderReasonStorage,
pub deduplication: ProviderCandidateDeduplication,
pub tabu_identity: ProviderTabuIdentity,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ProviderReasonStorage {
PerRunInternedId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ProviderCandidateDeduplication {
PerProviderReasonAndOrderedEdits,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ProviderTabuIdentity {
ProviderKindAndOrderedEdits,
}
pub(super) const GROUP_PROVIDER_ROTATION_SALT: u64 = 0xC0A1_E5CE_DA7A_0001;
pub(super) const REPAIR_PROVIDER_ROTATION_SALT: u64 = 0xC0AF_11C7_DA7A_0001;
pub(super) const STATIC_GROUP_PROVIDER_ROTATION_SALT: u64 = 0xC0A1_E5CE_AAA0_0001;
pub(super) const STATIC_REPAIR_CONSTRAINT_ROTATION_SALT: u64 = 0xC0AF_11C7_0000_0001;
pub(super) const STATIC_REPAIR_PROVIDER_ROTATION_SALT: u64 = 0xC0AF_11C7_0000_0002;
pub(super) const STATIC_REPAIR_SPEC_ROTATION_SALT: u64 = 0xC0AF_11C7_0000_0003;
pub(super) const PYTHON_PROVIDER_CANDIDATE_CONTRACT: ProviderCandidateContract =
ProviderCandidateContract {
reason_storage: ProviderReasonStorage::PerRunInternedId,
deduplication: ProviderCandidateDeduplication::PerProviderReasonAndOrderedEdits,
tabu_identity: ProviderTabuIdentity::ProviderKindAndOrderedEdits,
};
#[derive(Clone, Debug)]
#[expect(
clippy::large_enum_variant,
reason = "compiled selector nodes are an immutable value-owned graph"
)]
pub(crate) enum CompiledSelectorNode<S, V, DM, IDM> {
Scalar {
kind: ScalarLeafKind,
config: MoveSelectorConfig,
candidate_order: SelectionOrder,
candidate_metric: Option<RuntimeCandidateMetricBinding<S>>,
slots: Vec<CompiledScalarSlot<S>>,
},
List {
kind: ListLeafKind,
config: MoveSelectorConfig,
candidate_order: SelectionOrder,
candidate_metric: Option<RuntimeCandidateMetricBinding<S>>,
slots: Vec<CompiledListSlot<S, V, DM, IDM>>,
},
GroupedScalar {
config: MoveSelectorConfig,
candidate_order: SelectionOrder,
candidate_metric: Option<RuntimeCandidateMetricBinding<S>>,
group_index: usize,
group: ScalarGroupBinding<S>,
},
Provider {
config: MoveSelectorConfig,
candidate_order: SelectionOrder,
candidate_metric: Option<RuntimeCandidateMetricBinding<S>>,
plan: CompiledProviderPlan,
},
Limited {
selected_count_limit: usize,
selector: Box<Self>,
},
Union {
selection_order: UnionSelectionOrder,
weighting: UnionWeighting,
weights: Vec<u64>,
children: Vec<Self>,
},
Cartesian {
require_hard_improvement: bool,
left: Box<Self>,
right: Box<Self>,
},
}
impl<S, V, DM, IDM> CompiledSelectorNode<S, V, DM, IDM> {
pub(super) fn requires_score_during_move(&self) -> bool {
match self {
Self::Scalar { kind, .. } => matches!(kind, ScalarLeafKind::RuinRecreate),
Self::List { kind, .. } => matches!(kind, ListLeafKind::Ruin),
Self::GroupedScalar { .. } | Self::Provider { .. } => false,
Self::Limited { selector, .. } => selector.requires_score_during_move(),
Self::Union { children, .. } => children.iter().any(Self::requires_score_during_move),
Self::Cartesian { left, right, .. } => {
left.requires_score_during_move() || right.requires_score_during_move()
}
}
}
pub(super) fn contains_provider(&self) -> bool {
match self {
Self::Provider { .. } => true,
Self::Limited { selector, .. } => selector.contains_provider(),
Self::Union { children, .. } => children.iter().any(Self::contains_provider),
Self::Cartesian { left, right, .. } => {
left.contains_provider() || right.contains_provider()
}
Self::Scalar { .. } | Self::List { .. } | Self::GroupedScalar { .. } => false,
}
}
}
#[derive(Clone, Debug)]
#[expect(
clippy::large_enum_variant,
reason = "compiled construction stays value-owned through preparation"
)]
pub(crate) enum CompiledConstruction<S, V, DM, IDM> {
ScalarOrMixed {
config: ConstructionHeuristicConfig,
schedule: ScalarConstructionSchedule,
scalar_slots: Vec<CompiledScalarSlot<S>>,
list_slots: Vec<CompiledListSlot<S, V, DM, IDM>>,
slot_order: Vec<ScalarOrMixedSlotOrder>,
},
List {
kind: ListConstructionKind,
config: ConstructionHeuristicConfig,
slots: Vec<CompiledListSlot<S, V, DM, IDM>>,
},
GroupedScalar {
config: ConstructionHeuristicConfig,
group_index: usize,
group: ScalarGroupBinding<S>,
scalar_bindings: Vec<ResolvedVariableBinding<S>>,
},
}
#[derive(Clone, Debug)]
#[expect(
clippy::large_enum_variant,
reason = "compiled selector declarations stay value-owned"
)]
pub(crate) enum CompiledAcceptorForagerSelector<S, V, DM, IDM> {
Explicit(CompiledSelectorNode<S, V, DM, IDM>),
OmittedDefault,
}
#[derive(Clone, Debug)]
#[expect(
clippy::large_enum_variant,
reason = "compiled local search is an immutable value-owned declaration"
)]
pub(crate) enum CompiledLocalSearch<S, V, DM, IDM> {
AcceptorForager {
config: LocalSearchConfig,
selector: CompiledAcceptorForagerSelector<S, V, DM, IDM>,
},
VariableNeighborhoodDescent {
config: LocalSearchConfig,
neighborhoods: Vec<CompiledSelectorNode<S, V, DM, IDM>>,
},
}
#[derive(Clone, Debug)]
#[expect(
clippy::large_enum_variant,
reason = "compiled phases remain one value-owned execution graph"
)]
pub(crate) enum CompiledRuntimePhase<S, V, DM, IDM> {
Construction(CompiledConstruction<S, V, DM, IDM>),
LocalSearch(CompiledLocalSearch<S, V, DM, IDM>),
Extension(CompiledRuntimeExtension),
DefaultRuntime,
}
#[derive(Clone, Debug)]
pub(crate) enum CompiledRuntimeExtension {
Custom {
name: String,
},
Partitioned {
name: String,
config: PartitionedSearchConfig,
},
}
pub(crate) struct CompiledRuntimeGraph<S, V, DM, IDM, E>
where
S: solverforge_core::domain::PlanningSolution,
{
pub(super) context: SearchContext<S, V, DM, IDM>,
pub(super) extensions: E,
pub(super) config: SolverConfig,
pub(super) default_bindings: DefaultRuntimeBindings<S, V, DM, IDM>,
pub(super) phases: Vec<CompiledRuntimePhase<S, V, DM, IDM>>,
}
impl<S, V, DM, IDM, E> std::fmt::Debug for CompiledRuntimeGraph<S, V, DM, IDM, E>
where
S: solverforge_core::domain::PlanningSolution,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CompiledRuntimeGraph")
.field("config", &self.config)
.finish_non_exhaustive()
}
}
impl<S, V, DM, IDM, E> CompiledRuntimeGraph<S, V, DM, IDM, E>
where
S: solverforge_core::domain::PlanningSolution,
{
pub(crate) fn context(&self) -> &SearchContext<S, V, DM, IDM> {
&self.context
}
pub(crate) fn config(&self) -> &SolverConfig {
&self.config
}
pub(crate) fn phases(&self) -> &[CompiledRuntimePhase<S, V, DM, IDM>] {
&self.phases
}
pub(crate) fn extensions(&self) -> &E {
&self.extensions
}
pub(crate) fn default_bindings(&self) -> &DefaultRuntimeBindings<S, V, DM, IDM> {
&self.default_bindings
}
}