Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions crates/solverforge-scoring/WIREFRAME.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ src/
│ ├── traits.rs — Director<S> trait
│ ├── score_director.rs — Re-exports ScoreDirector pieces
│ │ ├── score_director/incremental.rs — ScoreDirector<S,C,Desc,M> (zero-erasure incremental)
│ │ ├── score_director/constructors.rs — ScoreDirector constructors, weighted constructors, into_weight_mode
│ │ ├── score_director/descriptor.rs — DirectorDescriptor, ReplicaDescriptor
│ │ ├── score_director/constructors.rs — ScoreDirector constructors, weighted constructors, replica-forking constructors, into_weight_mode
│ │ ├── score_director/descriptor.rs — DirectorDescriptor (owned or borrowed SolutionDescriptor)
│ │ ├── score_director/list_shadows.rs — Retract/insert of list-shadowed element rows around owner shadow updates
│ │ ├── score_director/weight_mode.rs — WeightMode (sealed), UnweightedMode, WeightedMode, requires_weighted_mode, static per-mode scoring dispatch
│ │ ├── score_director/analysis.rs — ScoreDirector constraint_results/constraint_analyses/constraint_match_totals
Expand Down Expand Up @@ -304,6 +304,7 @@ src/
│ ├── projected_stream/bi.rs — stream::projected::Bi and terminal builder
│ ├── projected_stream/directed_bi.rs — stream::projected::DirectedBi and terminal builder for directed projected self-joins
│ ├── projected_stream/join_target.rs — ProjectedJoinTarget dispatch for symmetric and directed projected self-joins
│ ├── projected_stream/row_closures.rs — Filter and key closures over projected rows, generic only over what they capture
│ ├── projected_stream/grouped.rs — stream::projected::Grouped, reward, complement, and terminal builder
│ ├── projected_stream/complemented_grouped.rs — stream::projected::ComplementedGrouped and builder
│ ├── projected_stream/source.rs — Projection, projected row coordinates, Source trait
Expand All @@ -314,6 +315,7 @@ src/
│ ├── collection_extract.rs — CollectionExtract trait, hidden source metadata and FilteredExtract membership adapter, source() constructor
│ ├── unassigned.rs — UnassignedEntity and the `.unassigned()` stream method
│ ├── weighting_support.rs — ConstraintWeight, FixedWeight, HardWeight, and dynamic closure-weight adapters
│ ├── closures.rs — Filter, key and weight closures built in functions generic only over what they capture, so constraint types do not repeat upstream stream types
│ ├── join_target.rs — JoinTarget/ToViewPlan dispatch for self-join, keyed cross-join, comparison, overlap, composed, and predicate cross-join
│ ├── key_extract.rs — KeyExtract trait, EntityKeyAdapter struct
│ ├── arity_stream_macros/
Expand Down Expand Up @@ -374,7 +376,7 @@ pub use api::constraint_set::{
pub use api::node_sharing::{SharedNodeDiagnostics, SharedNodeId, SharedNodeOperation};

// Score Directors
pub use director::score_director::{DirectorDescriptor, ReplicaDescriptor, ScoreDirector};
pub use director::score_director::{DirectorDescriptor, ScoreDirector};
pub use director::{Director, DirectorScoreState, SolvableSolution};

// Analysis
Expand Down Expand Up @@ -622,22 +624,20 @@ Stock collectors include `count()`, `sum()`, `load_balance()`,
- Returns borrowed constraint metadata views from the monomorphized `ConstraintSet` on demand.
- `simple(solution, descriptor, entity_counter)` — creates `ScoreDirector<S, ()>` with empty constraint set
- `simple_zero(solution)` — creates `ScoreDirector<S, ()>` with empty descriptor and zero entity counter
- `with_descriptor(solution, constraints, descriptor: Desc, entity_counter)` — `Desc` is an owned `SolutionDescriptor` or a `ReplicaDescriptor`; `new()`, `simple()` and `simple_zero()` build owned-descriptor directors.
- `Director::fork_replica` forks only directors holding a `ReplicaDescriptor`, in the same weight mode: the replica clones the working solution, builds its constraint set from that clone with the recorded constructor, initializes it, and shares the same borrowed descriptor. Owned-descriptor directors return `None`.
- `Director::fork_for_solution` forks the same way over a given solution instead of a clone of the working solution, and leaves the incremental state uninitialized so the first score calculation, on the thread that solves it, initializes it. Owned-descriptor directors return `None`.
- `reset()` discards incremental state after an external rewrite of the working solution. Directors holding a `ReplicaDescriptor` rebuild their constraint set from the current working solution with the recorded constructor, so solution-derived constraint plans follow fact and entity changes; owned-descriptor directors reset their existing set.
- `with_descriptor(solution, constraints, descriptor: Desc, entity_counter)` — `Desc` is an owned `SolutionDescriptor` or a borrowed `&SolutionDescriptor`; `new()`, `simple()` and `simple_zero()` build owned-descriptor directors.
- `with_replicas(solution, constraints, constraint_builder: fn(&S) -> C, descriptor: &'d SolutionDescriptor, entity_counter)` (plus `with_replicas_weighted` and `with_replicas_in_mode`) — a director that records `constraint_builder` and borrows the descriptor, so it can fork replicas. The constructor is a field of the director rather than part of its descriptor type, so `C` appears once in the director's type and debug-info type names stay proportional to the constraint set.
- `Director::fork_replica` forks only directors built with `with_replicas`, in the same weight mode: the replica clones the working solution, builds its constraint set from that clone with the recorded constructor, initializes it, and shares the same borrowed descriptor. Other directors return `None`.
- `Director::fork_for_solution` forks the same way over a given solution instead of a clone of the working solution, and leaves the incremental state uninitialized so the first score calculation, on the thread that solves it, initializes it. Other directors return `None`.
- `reset()` discards incremental state after an external rewrite of the working solution. Directors built with `with_replicas` rebuild their constraint set from the current working solution with the recorded constructor, so solution-derived constraint plans follow fact and entity changes; other directors reset their existing set.
- Implements `Director<S>`

**`WeightMode`** (sealed trait) — `const WEIGHTED: bool`; implemented only by `UnweightedMode` (zero-sized, no weight storage) and `WeightedMode` (bound per-slot multipliers).

**`requires_weighted_mode<S: PlanningSolution>(solution: &S) -> bool`** — `S::HAS_CONSTRAINT_WEIGHTS` and some multiplier of `solution.constraint_weights()` differs from `1`.

**`DirectorDescriptor<S, C>`** (trait) — how a `ScoreDirector` holds its descriptor.
**`DirectorDescriptor`** (trait) — how a `ScoreDirector` holds its descriptor; implemented for an owned `SolutionDescriptor` and a borrowed `&SolutionDescriptor`.
- `solution_descriptor(&self) -> &SolutionDescriptor`
- `replica(&self) -> Option<(Self, fn(&S) -> C)>` — the handle and constraint-set constructor for an evaluator replica; `None` for an owned `SolutionDescriptor`.

**`ReplicaDescriptor<'d, S, C>`** — `Copy`; a borrowed `&'d SolutionDescriptor` plus the constraint-set constructor `fn(&S) -> C`.
- `new(descriptor: &'d SolutionDescriptor, constraints: fn(&S) -> C)`
- `share(&self) -> Option<Self>` — the same descriptor for an evaluator replica: `Some` for a borrowed descriptor, `None` for an owned one.

### Constraint Types

Expand Down Expand Up @@ -1004,6 +1004,8 @@ The entire pipeline from stream builder to constraint evaluation is fully monomo

All closures are stored as concrete generic type parameters. No `Box<dyn Fn>`, no `Arc`. The constraint types carry the full closure types through their generics.

A closure written inside a generic method has a type naming every generic parameter of that method, and debug info prints that type in full. Combinators therefore never build the closures they store inline: `stream/closures.rs`, `projected_stream/row_closures.rs` and the `narrow*` functions inside the n-ary stream macros build them in functions generic only over what the closure captures, so a filter chain or weight names its own parts once instead of repeating the whole upstream stream type at every stage.

### Incremental Scoring Protocol

Score directors use a retract-then-insert protocol:
Expand Down
11 changes: 6 additions & 5 deletions crates/solverforge-scoring/public-api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ struct constraint::relational::SharedOperatorTerminal: pub struct SharedOperator
constraint::relational::SharedOperatorTerminal: pub fn update_count(&self) -> usize
constraint::relational::SharedOperatorTerminal: pub fn changed_key_count(&self) -> usize
struct director::DirectorScoreState: pub struct DirectorScoreState<Sc> { pub solution_score: Option<Sc>, pub committed_score: Option<Sc>, pub initialized: bool, }
struct director::score_director::ReplicaDescriptor: pub struct ReplicaDescriptor<'d, S, C> { /* private fields */ }
director::score_director::ReplicaDescriptor: pub fn new(descriptor: &'d SolutionDescriptor, constraints: fn(&S) -> C) -> Self
struct director::score_director::ScoreDirector: pub struct ScoreDirector<S, C, Desc = SolutionDescriptor, M = UnweightedMode> where S: PlanningSolution, C: ConstraintSet<S, S::Score>, Desc: DirectorDescriptor<S, C>, M: WeightMode,{ /* private fields */ }
struct director::score_director::ScoreDirector: pub struct ScoreDirector<S, C, Desc = SolutionDescriptor, M = UnweightedMode> where S: PlanningSolution, C: ConstraintSet<S, S::Score>, Desc: DirectorDescriptor, M: WeightMode,{ /* private fields */ }
director::score_director::ScoreDirector: pub fn constraint_results(&self) -> Vec<ConstraintResult<'_, S::Score>>
director::score_director::ScoreDirector: pub fn constraint_analyses(&self) -> Vec<ConstraintAnalysis<'_, S::Score>>
director::score_director::ScoreDirector: pub fn constraint_match_totals(&self) -> Vec<(String, u32, S::Score, usize)>
Expand All @@ -77,6 +75,9 @@ struct director::score_director::ScoreDirector: pub struct ScoreDirector<S, C, D
director::score_director::ScoreDirector: pub fn with_descriptor_weighted( solution: S, constraints: C, solution_descriptor: Desc, entity_counter: fn(&S, usize) -> usize, ) -> Self
director::score_director::ScoreDirector: pub fn with_descriptor_in_mode( solution: S, constraints: C, solution_descriptor: Desc, entity_counter: fn(&S, usize) -> usize, ) -> Self
director::score_director::ScoreDirector: pub fn into_weight_mode<M2: WeightMode>(self) -> ScoreDirector<S, C, Desc, M2>
director::score_director::ScoreDirector: pub fn with_replicas_in_mode( solution: S, constraints: C, constraint_builder: fn(&S) -> C, solution_descriptor: &'d SolutionDescriptor, entity_counter: fn(&S, usize) -> usize, ) -> Self
director::score_director::ScoreDirector: pub fn with_replicas( solution: S, constraints: C, constraint_builder: fn(&S) -> C, solution_descriptor: &'d SolutionDescriptor, entity_counter: fn(&S, usize) -> usize, ) -> Self
director::score_director::ScoreDirector: pub fn with_replicas_weighted( solution: S, constraints: C, constraint_builder: fn(&S) -> C, solution_descriptor: &'d SolutionDescriptor, entity_counter: fn(&S, usize) -> usize, ) -> Self
director::score_director::ScoreDirector: pub fn simple( solution: S, descriptor: SolutionDescriptor, entity_counter: fn(&S, usize) -> usize, ) -> Self
director::score_director::ScoreDirector: pub fn simple_zero(solution: S) -> Self
director::score_director::ScoreDirector: pub fn working_solution(&self) -> &S
Expand Down Expand Up @@ -522,9 +523,9 @@ trait director::Director: pub trait Director<S: PlanningSolution>: Send { Show 1
director::Director: fn reset(&mut self)
director::Director: fn fork_replica(&self) -> Option<Self> where Self: Sized,
director::Director: fn fork_for_solution(&self, _solution: S) -> Option<Self> where Self: Sized,
trait director::score_director::DirectorDescriptor: pub trait DirectorDescriptor<S, C> { // Required methods fn solution_descriptor(&self) -> &SolutionDescriptor; fn replica(&self) -> Option<(Self, fn(&S) -> C)> where Self: Sized; }
trait director::score_director::DirectorDescriptor: pub trait DirectorDescriptor { // Required methods fn solution_descriptor(&self) -> &SolutionDescriptor; fn share(&self) -> Option<Self> where Self: Sized; }
director::score_director::DirectorDescriptor: fn solution_descriptor(&self) -> &SolutionDescriptor
director::score_director::DirectorDescriptor: fn replica(&self) -> Option<(Self, fn(&S) -> C)> where Self: Sized,
director::score_director::DirectorDescriptor: fn share(&self) -> Option<Self> where Self: Sized,
trait director::score_director::WeightMode: pub trait WeightMode: Mode + Send + Sync + Sized + 'static { const WEIGHTED: bool; }
director::score_director::WeightMode: const WEIGHTED: bool
trait director::shadow_aware::SolvableSolution: pub trait SolvableSolution: PlanningSolution { // Required methods fn descriptor() -> SolutionDescriptor; fn entity_count(solution: &Self, descriptor_index: usize) -> usize; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::any::TypeId;

use super::support::*;
use crate::api::constraint_set::IncrementalConstraint;
use crate::director::score_director::{requires_weighted_mode, ReplicaDescriptor, UnweightedMode};
use crate::director::score_director::{requires_weighted_mode, UnweightedMode};
use solverforge_core::domain::SolutionDescriptor;

fn unassigned() -> impl IncrementalConstraint<Plan, HardSoftScore> {
Expand Down Expand Up @@ -63,10 +63,11 @@ fn fork_replica_keeps_the_weighted_mode() {
let descriptor = SolutionDescriptor::new("", TypeId::of::<()>());
let build = (|_: &Plan| (unassigned(),)) as fn(&Plan) -> _;
let solution = unassigned_weighted(5);
let director = ScoreDirector::with_descriptor_weighted(
let director = ScoreDirector::with_replicas_weighted(
solution.clone(),
build(&solution),
ReplicaDescriptor::new(&descriptor, build),
build,
&descriptor,
|_, _| 0,
);

Expand Down
2 changes: 1 addition & 1 deletion crates/solverforge-scoring/src/director/score_director.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ mod incremental;
mod list_shadows;
mod weight_mode;

pub use descriptor::{DirectorDescriptor, ReplicaDescriptor};
pub use descriptor::DirectorDescriptor;
pub use incremental::ScoreDirector;
pub use weight_mode::{requires_weighted_mode, UnweightedMode, WeightMode, WeightedMode};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ where
S: PlanningSolution + std::fmt::Debug,
S::Score: std::fmt::Debug,
C: ConstraintSet<S, S::Score>,
Desc: DirectorDescriptor<S, C>,
Desc: DirectorDescriptor,
M: WeightMode,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand All @@ -23,7 +23,7 @@ where
.field("weighted", &M::WEIGHTED)
.field(
"forks_replicas",
&self.solution_descriptor.replica().is_some(),
&(self.constraint_builder.is_some() && self.solution_descriptor.share().is_some()),
)
.finish()
}
Expand All @@ -34,7 +34,7 @@ where
S: PlanningSolution,
S::Score: Score,
C: ConstraintSet<S, S::Score> + Send,
Desc: DirectorDescriptor<S, C> + Send,
Desc: DirectorDescriptor + Send,
M: WeightMode,
{
fn working_solution(&self) -> &S {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ where
S: PlanningSolution,
S::Score: Score,
C: ConstraintSet<S, S::Score>,
Desc: DirectorDescriptor<S, C>,
Desc: DirectorDescriptor,
M: WeightMode,
{
/// Evaluates every constraint of the working solution under the director's weight mode.
Expand Down
Loading
Loading