7 releases
| 0.3.0 | Jan 2, 2026 |
|---|---|
| 0.2.6 | Dec 18, 2025 |
#22 in #constraint-solver
93KB
2K
SLoC
solverforge-derive
Procedural macros for SolverForge domain modeling.
Overview
This crate provides derive macros for implementing planning domain types:
#[derive(PlanningEntity)]- Mark structs as planning entities#[derive(PlanningSolution)]- Mark structs as planning solutions
Usage
[dependencies]
solverforge-core = "0.2"
solverforge-derive = "0.2"
Planning Entity
use solverforge_derive::PlanningEntity;
#[derive(PlanningEntity, Clone)]
pub struct Lesson {
#[planning_id]
pub id: String,
pub subject: String,
#[planning_variable(value_range_provider = "timeslots")]
pub timeslot: Option<Timeslot>,
#[planning_variable(value_range_provider = "rooms", allows_unassigned = true)]
pub room: Option<Room>,
}
Entity Attributes
| Attribute | Description |
|---|---|
#[planning_id] |
Unique identifier field (required) |
#[planning_variable(value_range_provider = "...")] |
Field assigned by solver |
#[planning_variable(..., allows_unassigned = true)] |
Variable can remain unassigned |
#[planning_list_variable(value_range_provider = "...")] |
List field assigned by solver |
Shadow Variable Attributes
Shadow variables are automatically updated when their source variable changes.
#[derive(PlanningEntity, Clone)]
pub struct Visit {
#[planning_id]
pub id: String,
// Tracks which vehicle this visit belongs to (inverse of Vehicle.visits)
#[inverse_relation_shadow(source = "visits")]
pub vehicle: Option<String>,
// Tracks position in the vehicle's visit list
#[index_shadow(source = "visits")]
pub index: Option<i32>,
// Tracks the previous visit in the list
#[previous_element_shadow(source = "visits")]
pub previous_visit: Option<String>,
// Tracks the next visit in the list
#[next_element_shadow(source = "visits")]
pub next_visit: Option<String>,
}
#[derive(PlanningEntity, Clone)]
pub struct ChainedEntity {
#[planning_id]
pub id: String,
// For chained planning variables: tracks the anchor
#[anchor_shadow(source = "previous")]
pub anchor: Option<String>,
}
| Attribute | Description |
|---|---|
#[inverse_relation_shadow(source = "...")] |
Back-reference to list containing this entity |
#[index_shadow(source = "...")] |
Position in source list (0-indexed) |
#[previous_element_shadow(source = "...")] |
Previous element in source list |
#[next_element_shadow(source = "...")] |
Next element in source list |
#[anchor_shadow(source = "...")] |
First element in chained variable chain |
Entity Comparators
Control entity ordering for move selection:
#[derive(PlanningEntity, Clone)]
#[difficulty_comparator = "compare_lesson_difficulty"]
pub struct Lesson {
#[planning_id]
pub id: String,
// ...
}
fn compare_lesson_difficulty(a: &Lesson, b: &Lesson) -> std::cmp::Ordering {
a.constraint_count.cmp(&b.constraint_count)
}
| Attribute | Description |
|---|---|
#[difficulty_comparator = "..."] |
Order entities by difficulty for planning |
#[strength_comparator = "..."] |
Order values by strength |
Planning Solution
use solverforge_derive::PlanningSolution;
#[derive(PlanningSolution, Clone)]
#[constraint_provider = "define_constraints"]
pub struct Timetable {
#[problem_fact_collection]
#[value_range_provider(id = "timeslots")]
pub timeslots: Vec<Timeslot>,
#[problem_fact_collection]
#[value_range_provider(id = "rooms")]
pub rooms: Vec<Room>,
#[planning_entity_collection]
pub lessons: Vec<Lesson>,
#[planning_score]
pub score: Option<HardSoftScore>,
}
Solution Attributes
| Attribute | Description |
|---|---|
#[constraint_provider = "..."] |
Constraint function name (struct-level) |
#[problem_fact_collection] |
Immutable fact collection |
#[problem_fact] |
Single immutable fact |
#[planning_entity_collection] |
Entity collection modified by solver |
#[planning_entity] |
Single entity modified by solver |
#[value_range_provider(id = "...")] |
Provides values for planning variables |
#[planning_score] |
Score field |
Vehicle Routing Example
Complete example with list variables and shadows:
#[derive(PlanningEntity, Clone)]
pub struct Vehicle {
#[planning_id]
pub id: String,
pub depot: Location,
#[planning_list_variable(value_range_provider = "visits")]
pub visits: Vec<String>, // Visit IDs
}
#[derive(PlanningEntity, Clone)]
pub struct Visit {
#[planning_id]
pub id: String,
pub location: Location,
pub demand: i32,
#[inverse_relation_shadow(source = "visits")]
pub vehicle: Option<String>,
#[index_shadow(source = "visits")]
pub index: Option<i32>,
#[previous_element_shadow(source = "visits")]
pub previous: Option<String>,
#[next_element_shadow(source = "visits")]
pub next: Option<String>,
}
#[derive(PlanningSolution, Clone)]
#[constraint_provider = "vehicle_routing_constraints"]
pub struct VehicleRoutingPlan {
#[problem_fact_collection]
#[value_range_provider(id = "visits")]
pub visits: Vec<Visit>,
#[planning_entity_collection]
pub vehicles: Vec<Vehicle>,
#[planning_score]
pub score: Option<HardSoftScore>,
}
License
Apache-2.0
Dependencies
~130–510KB
~12K SLoC