# 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
```toml
[dependencies]
solverforge-core = "0.2"
solverforge-derive = "0.2"
```
## Planning Entity
```rust
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
| `#[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.
```rust
#[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>,
}
```
| `#[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:
```rust
#[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)
}
```
| `#[difficulty_comparator = "..."]` | Order entities by difficulty for planning |
| `#[strength_comparator = "..."]` | Order values by strength |
## Planning Solution
```rust
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
| `#[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:
```rust
#[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