solverforge 0.10.0

SolverForge - A constraint solver in Rust
Documentation
use solverforge::prelude::*;
use solverforge::stream::ConstraintFactory;
use solverforge::IncrementalConstraint;

#[problem_fact]
pub struct Capacity {
    #[planning_id]
    pub id: usize,
    pub bucket: usize,
    pub amount: i64,
}

#[planning_entity]
pub struct Assignment {
    #[planning_id]
    pub id: usize,
    pub bucket: usize,
    pub demand: i64,
}

#[derive(Clone)]
pub struct CapacityEntry {
    pub bucket: usize,
    pub delta: i64,
}

#[planning_solution]
pub struct Plan {
    #[problem_fact_collection]
    pub capacities: Vec<Capacity>,
    #[planning_entity_collection]
    pub assignments: Vec<Assignment>,
    #[planning_score]
    pub score: Option<HardSoftScore>,
}

impl solverforge::__internal::PlanningModelSupport for Plan {
    fn attach_descriptor_scalar_hooks(
        _descriptor: &mut solverforge::__internal::SolutionDescriptor,
    ) {
    }

    fn attach_runtime_scalar_hooks(
        context: solverforge::__internal::ScalarVariableContext<Self>,
    ) -> solverforge::__internal::ScalarVariableContext<Self> {
        context
    }

    fn validate_model(_descriptor: &solverforge::__internal::SolutionDescriptor) {}

    fn update_entity_shadows(
        _solution: &mut Self,
        _descriptor_index: usize,
        _entity_index: usize,
    ) -> bool {
        false
    }

    fn update_all_shadows(_solution: &mut Self) -> bool {
        false
    }
}

struct AssignmentEntries;

impl Projection<Assignment> for AssignmentEntries {
    type Out = CapacityEntry;
    const MAX_EMITS: usize = 1;

    fn project<Sink>(&self, assignment: &Assignment, sink: &mut Sink)
    where
        Sink: ProjectionSink<Self::Out>,
    {
        sink.emit(CapacityEntry {
            bucket: assignment.bucket,
            delta: assignment.demand,
        });
    }
}

struct CapacityEntries;

impl Projection<Capacity> for CapacityEntries {
    type Out = CapacityEntry;
    const MAX_EMITS: usize = 1;

    fn project<Sink>(&self, capacity: &Capacity, sink: &mut Sink)
    where
        Sink: ProjectionSink<Self::Out>,
    {
        sink.emit(CapacityEntry {
            bucket: capacity.bucket,
            delta: -capacity.amount,
        });
    }
}

#[test]
fn projected_stream_is_public_and_infers_output_type() {
    use PlanConstraintStreams;

    let constraint = ConstraintFactory::<Plan, HardSoftScore>::new()
        .assignments()
        .project(AssignmentEntries)
        .merge(
            ConstraintFactory::<Plan, HardSoftScore>::new()
                .capacities()
                .project(CapacityEntries),
        )
        .group_by(
            |entry: &CapacityEntry| entry.bucket,
            sum(|entry: &CapacityEntry| entry.delta),
        )
        .penalize_hard_with(|delta: &i64| HardSoftScore::of_hard((*delta).max(0)))
        .named("capacity shortage");

    let plan = Plan {
        capacities: vec![Capacity {
            id: 0,
            bucket: 0,
            amount: 3,
        }],
        assignments: vec![Assignment {
            id: 0,
            bucket: 0,
            demand: 5,
        }],
        score: None,
    };

    assert_eq!(constraint.evaluate(&plan), HardSoftScore::of(-2, 0));
}