From 93897e63e9a3bb771c03f6424a1b66975aadd391 Mon Sep 17 00:00:00 2001 From: Vittorio Distefano Date: Thu, 5 Nov 2026 11:25:19 +0100 Subject: [PATCH] docs(scoring): document ConstraintSet::constraint_metadata_entries constraint_metadata_entries is a required method of the public ConstraintSet trait but was #[doc(hidden)], so an implementor had to write a method the documentation did not show. It cannot get a default, since tuples and chains build their entries from their members' raw entries and constraint_metadata deduplicates the result. It is now documented with that contract in the trait, the scoring wireframe and docs/extend-scoring.md. Closes #212. --- crates/solverforge-scoring/WIREFRAME.md | 2 +- crates/solverforge-scoring/public-api.txt | 3 ++- .../src/api/constraint_set/incremental.rs | 11 +++++++++-- docs/extend-scoring.md | 4 +++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/solverforge-scoring/WIREFRAME.md b/crates/solverforge-scoring/WIREFRAME.md index bc38665d..a2bc6a91 100644 --- a/crates/solverforge-scoring/WIREFRAME.md +++ b/crates/solverforge-scoring/WIREFRAME.md @@ -451,7 +451,7 @@ Implementing it by hand is a supported extension point; the contract an implemen | `evaluate_all` | `fn evaluate_all(&self, solution: &S) -> Sc` | Sum all constraints | | `constraint_count` | `fn constraint_count(&self) -> usize` | Number of constraints | | `constraint_metadata` | `fn constraint_metadata(&self) -> Vec>` | Deduplicated borrowed ref/name/hardness metadata | -| `constraint_metadata_entries` | `fn constraint_metadata_entries(&self) -> Vec>` | Hidden raw metadata entries in authored order | +| `constraint_metadata_entries` | `fn constraint_metadata_entries(&self) -> Vec>` | Required: one entry per constraint in authored order, duplicates kept; tuples and chains concatenate their members' entries and `constraint_metadata` deduplicates them | | `evaluate_each` | `fn evaluate_each<'a>(&'a self, solution: &S) -> Vec>` | Per-constraint results | | `evaluate_detailed` | `fn evaluate_detailed<'a>(&'a self, solution: &S) -> Vec>` | With match details | | `initialize_all` | `fn initialize_all(&mut self, solution: &S) -> Sc` | Initialize all for incremental | diff --git a/crates/solverforge-scoring/public-api.txt b/crates/solverforge-scoring/public-api.txt index bad2e947..caa9e045 100644 --- a/crates/solverforge-scoring/public-api.txt +++ b/crates/solverforge-scoring/public-api.txt @@ -437,9 +437,10 @@ enum stream::ExistenceMode: pub enum ExistenceMode { Exists, NotExists, } enum stream::joiner::Strategy: pub enum Strategy { EquiHash, OrderedScan, IntervalNarrow, Scan, } stream::joiner::Strategy: pub const fn combine(self, other: Strategy) -> Strategy enum stream::relational::operator::ComplementView: pub enum ComplementView<'a, L, R, T> { Real(Pair), Default(ProjectView<'a, L, T>), } -trait api::constraint_set::ConstraintSet: pub trait ConstraintSet: Send + Sync { Show 16 methods // Required methods fn evaluate_all(&self, solution: &S) -> Sc; fn constraint_count(&self) -> usize; fn evaluate_each<'a>( &'a self, solution: &S, ) -> Vec>; fn evaluate_detailed<'a>( &'a self, solution: &S, ) -> Vec>; fn initialize_all(&mut self, solution: &S) -> Sc; fn on_insert_all( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, ) -> Sc; fn on_retract_all( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, ) -> Sc; fn reset_all(&mut self); fn constraint_weight_refs<'a>(&'a self, refs: &mut Vec<&'a ConstraintRef>); fn evaluate_all_weighted(&self, solution: &S, multipliers: &[u32]) -> Sc; fn evaluate_each_weighted<'a>( &'a self, solution: &S, multipliers: &[u32], ) -> Vec>; fn evaluate_detailed_weighted<'a>( &'a self, solution: &S, multipliers: &[u32], ) -> Vec>; fn initialize_all_weighted( &mut self, solution: &S, multipliers: &[u32], ) -> Sc; fn on_insert_all_weighted( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, multipliers: &[u32], ) -> Sc; fn on_retract_all_weighted( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, multipliers: &[u32], ) -> Sc; // Provided method fn constraint_metadata(&self) -> Vec> { ... } } +trait api::constraint_set::ConstraintSet: pub trait ConstraintSet: Send + Sync { Show 17 methods // Required methods fn evaluate_all(&self, solution: &S) -> Sc; fn constraint_count(&self) -> usize; fn constraint_metadata_entries(&self) -> Vec>; fn evaluate_each<'a>( &'a self, solution: &S, ) -> Vec>; fn evaluate_detailed<'a>( &'a self, solution: &S, ) -> Vec>; fn initialize_all(&mut self, solution: &S) -> Sc; fn on_insert_all( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, ) -> Sc; fn on_retract_all( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, ) -> Sc; fn reset_all(&mut self); fn constraint_weight_refs<'a>(&'a self, refs: &mut Vec<&'a ConstraintRef>); fn evaluate_all_weighted(&self, solution: &S, multipliers: &[u32]) -> Sc; fn evaluate_each_weighted<'a>( &'a self, solution: &S, multipliers: &[u32], ) -> Vec>; fn evaluate_detailed_weighted<'a>( &'a self, solution: &S, multipliers: &[u32], ) -> Vec>; fn initialize_all_weighted( &mut self, solution: &S, multipliers: &[u32], ) -> Sc; fn on_insert_all_weighted( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, multipliers: &[u32], ) -> Sc; fn on_retract_all_weighted( &mut self, solution: &S, entity_index: usize, descriptor_index: usize, multipliers: &[u32], ) -> Sc; // Provided method fn constraint_metadata(&self) -> Vec> { ... } } api::constraint_set::ConstraintSet: fn evaluate_all(&self, solution: &S) -> Sc api::constraint_set::ConstraintSet: fn constraint_count(&self) -> usize + api::constraint_set::ConstraintSet: fn constraint_metadata_entries(&self) -> Vec> api::constraint_set::ConstraintSet: fn evaluate_each<'a>(&'a self, solution: &S) -> Vec> api::constraint_set::ConstraintSet: fn evaluate_detailed<'a>( &'a self, solution: &S, ) -> Vec> api::constraint_set::ConstraintSet: fn initialize_all(&mut self, solution: &S) -> Sc diff --git a/crates/solverforge-scoring/src/api/constraint_set/incremental.rs b/crates/solverforge-scoring/src/api/constraint_set/incremental.rs index 8a0486c2..5319f27d 100644 --- a/crates/solverforge-scoring/src/api/constraint_set/incremental.rs +++ b/crates/solverforge-scoring/src/api/constraint_set/incremental.rs @@ -292,8 +292,15 @@ pub trait ConstraintSet: Send + Sync { deduplicate_constraint_metadata(self.constraint_metadata_entries()) } - /// Returns one metadata entry per constraint in authored order. - #[doc(hidden)] + /// Returns one metadata entry per constraint in authored order, keeping + /// duplicates. + /// + /// This is the method an implementation provides: tuples and chains build + /// their entries from their members' entries, and + /// [`constraint_metadata`](Self::constraint_metadata) deduplicates the + /// result. A set of one constraint returns its single entry; a set that + /// scores several named constraints returns one entry per name, in the + /// order its terminals were authored. fn constraint_metadata_entries(&self) -> Vec>; /// Evaluates each constraint individually and returns per-constraint results. diff --git a/docs/extend-scoring.md b/docs/extend-scoring.md index 47ab4db4..e2ee45af 100644 --- a/docs/extend-scoring.md +++ b/docs/extend-scoring.md @@ -115,7 +115,9 @@ slot per named constraint through `constraint_weight_refs` and scale each one's contribution by its own slot; never apply one multiplier to a delta that sums several named constraints. A hand-written `ConstraintSet` must implement the weighted methods by splitting `multipliers` across its children in -`constraint_weight_refs` order. +`constraint_weight_refs` order, and `constraint_metadata_entries` by returning +one entry per named constraint in authored order, duplicates kept; +`constraint_metadata` deduplicates them. ## Evaluation ownership