solverforge-solver 0.9.0

Solver engine for SolverForge
Documentation
#[derive(Clone, Debug)]
struct Shift {
    worker: Option<usize>,
}

#[derive(Clone, Debug)]
struct Vehicle {
    visits: Vec<usize>,
}

#[derive(Clone, Debug)]
struct MixedPlan {
    shifts: Vec<Shift>,
    vehicles: Vec<Vehicle>,
    score: Option<SoftScore>,
}

impl PlanningSolution for MixedPlan {
    type Score = SoftScore;

    fn score(&self) -> Option<Self::Score> {
        self.score
    }

    fn set_score(&mut self, score: Option<Self::Score>) {
        self.score = score;
    }
}

#[derive(Clone, Debug)]
struct NoopMeter;

impl CrossEntityDistanceMeter<MixedPlan> for NoopMeter {
    fn distance(
        &self,
        _solution: &MixedPlan,
        _src_entity: usize,
        _src_pos: usize,
        _dst_entity: usize,
        _dst_pos: usize,
    ) -> f64 {
        1.0
    }
}

fn get_shifts(solution: &MixedPlan) -> &Vec<Shift> {
    &solution.shifts
}

fn get_shifts_mut(solution: &mut MixedPlan) -> &mut Vec<Shift> {
    &mut solution.shifts
}

fn get_vehicles(solution: &MixedPlan) -> &Vec<Vehicle> {
    &solution.vehicles
}

fn get_vehicles_mut(solution: &mut MixedPlan) -> &mut Vec<Vehicle> {
    &mut solution.vehicles
}

fn get_worker_dyn(entity: &dyn std::any::Any) -> Option<usize> {
    entity
        .downcast_ref::<Shift>()
        .and_then(|shift| shift.worker)
}

fn set_worker_dyn(entity: &mut dyn std::any::Any, value: Option<usize>) {
    if let Some(shift) = entity.downcast_mut::<Shift>() {
        shift.worker = value;
    }
}

fn descriptor(include_scalar_binding: bool) -> SolutionDescriptor {
    let shift_descriptor =
        EntityDescriptor::new("Shift", TypeId::of::<Shift>(), "shifts").with_extractor(Box::new(
            EntityCollectionExtractor::new("Shift", "shifts", get_shifts, get_shifts_mut),
        ));
    let shift_descriptor = if include_scalar_binding {
        shift_descriptor.with_variable(
            VariableDescriptor::genuine("worker")
                .with_allows_unassigned(true)
                .with_value_range("shifts")
                .with_usize_accessors(get_worker_dyn, set_worker_dyn),
        )
    } else {
        shift_descriptor
    };

    SolutionDescriptor::new("MixedPlan", TypeId::of::<MixedPlan>())
        .with_entity(shift_descriptor)
        .with_entity(
            EntityDescriptor::new("Vehicle", TypeId::of::<Vehicle>(), "vehicles").with_extractor(
                Box::new(EntityCollectionExtractor::new(
                    "Vehicle",
                    "vehicles",
                    get_vehicles,
                    get_vehicles_mut,
                )),
            ),
        )
}

fn create_director(
    solution: MixedPlan,
    descriptor: SolutionDescriptor,
) -> ScoreDirector<MixedPlan, ()> {
    ScoreDirector::simple(
        solution,
        descriptor,
        |solution, descriptor_index| match descriptor_index {
            0 => solution.shifts.len(),
            1 => solution.vehicles.len(),
            _ => 0,
        },
    )
}

fn shift_count(solution: &MixedPlan) -> usize {
    solution.shifts.len()
}

fn get_worker(solution: &MixedPlan, entity_index: usize, _variable_index: usize) -> Option<usize> {
    solution.shifts[entity_index].worker
}

fn set_worker(
    solution: &mut MixedPlan,
    entity_index: usize,
    _variable_index: usize,
    value: Option<usize>,
) {
    solution.shifts[entity_index].worker = value;
}

fn worker_count(solution: &MixedPlan, _provider_index: usize) -> usize {
    solution.shifts.len().max(1)
}

fn vehicle_count(solution: &MixedPlan) -> usize {
    solution.vehicles.len()
}

fn list_len(solution: &MixedPlan, entity_index: usize) -> usize {
    solution.vehicles[entity_index].visits.len()
}

fn list_remove(solution: &mut MixedPlan, entity_index: usize, pos: usize) -> Option<usize> {
    let visits = &mut solution.vehicles.get_mut(entity_index)?.visits;
    if pos < visits.len() {
        Some(visits.remove(pos))
    } else {
        None
    }
}

fn list_insert(solution: &mut MixedPlan, entity_index: usize, pos: usize, value: usize) {
    solution.vehicles[entity_index].visits.insert(pos, value);
}

fn list_get(solution: &MixedPlan, entity_index: usize, pos: usize) -> Option<usize> {
    solution.vehicles[entity_index].visits.get(pos).copied()
}

fn list_set(solution: &mut MixedPlan, entity_index: usize, pos: usize, value: usize) {
    solution.vehicles[entity_index].visits[pos] = value;
}

fn list_reverse(solution: &mut MixedPlan, entity_index: usize, start: usize, end: usize) {
    solution.vehicles[entity_index].visits[start..end].reverse();
}

fn sublist_remove(
    solution: &mut MixedPlan,
    entity_index: usize,
    start: usize,
    end: usize,
) -> Vec<usize> {
    solution.vehicles[entity_index]
        .visits
        .drain(start..end)
        .collect()
}

fn sublist_insert(solution: &mut MixedPlan, entity_index: usize, pos: usize, values: Vec<usize>) {
    solution.vehicles[entity_index]
        .visits
        .splice(pos..pos, values);
}

fn ruin_remove(solution: &mut MixedPlan, entity_index: usize, pos: usize) -> usize {
    solution.vehicles[entity_index].visits.remove(pos)
}

fn ruin_insert(solution: &mut MixedPlan, entity_index: usize, pos: usize, value: usize) {
    solution.vehicles[entity_index].visits.insert(pos, value);
}

fn assigned_visits(solution: &MixedPlan) -> Vec<usize> {
    solution
        .vehicles
        .iter()
        .flat_map(|vehicle| vehicle.visits.iter().copied())
        .collect()
}

fn visit_count(solution: &MixedPlan) -> usize {
    assigned_visits(solution).len()
}

fn construction_list_remove(solution: &mut MixedPlan, entity_index: usize, pos: usize) -> usize {
    solution.vehicles[entity_index].visits.remove(pos)
}

fn index_to_visit(solution: &MixedPlan, idx: usize) -> usize {
    assigned_visits(solution).get(idx).copied().unwrap_or(idx)
}

fn scalar_context() -> ScalarVariableContext<MixedPlan> {
    ScalarVariableContext::new(
        0,
        0,
        "Shift",
        shift_count,
        "worker",
        get_worker,
        set_worker,
        ValueSource::SolutionCount {
            count_fn: worker_count,
            provider_index: 0,
        },
        true,
    )
}

fn list_context() -> ListVariableContext<MixedPlan, usize, NoopMeter, NoopMeter> {
    ListVariableContext::new(
        "Vehicle",
        visit_count,
        assigned_visits,
        list_len,
        list_remove,
        construction_list_remove,
        list_insert,
        list_get,
        list_set,
        list_reverse,
        sublist_remove,
        sublist_insert,
        ruin_remove,
        ruin_insert,
        index_to_visit,
        vehicle_count,
        NoopMeter,
        NoopMeter,
        "visits",
        1,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
}

fn scalar_only_model() -> ModelContext<MixedPlan, usize, NoopMeter, NoopMeter> {
    ModelContext::new(vec![VariableContext::Scalar(scalar_context())])
}

fn list_only_model() -> ModelContext<MixedPlan, usize, NoopMeter, NoopMeter> {
    ModelContext::new(vec![VariableContext::List(list_context())])
}

fn mixed_model() -> ModelContext<MixedPlan, usize, NoopMeter, NoopMeter> {
    ModelContext::new(vec![
        VariableContext::Scalar(scalar_context()),
        VariableContext::List(list_context()),
    ])
}

fn empty_model() -> ModelContext<MixedPlan, usize, NoopMeter, NoopMeter> {
    ModelContext::new(vec![])
}