solverforge-solver 0.12.0

Solver engine for SolverForge
Documentation

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

#[derive(Clone, Debug)]
struct RevisionTask {
    worker_idx: Option<usize>,
}

#[derive(Clone, Debug)]
struct RevisionPlan {
    score: Option<SoftScore>,
    workers: Vec<RevisionWorker>,
    tasks: Vec<RevisionTask>,
    routes: Vec<Vec<usize>>,
    route_pool: Vec<usize>,
}

#[derive(Clone, Debug)]
struct RevisionDirector {
    working_solution: RevisionPlan,
    descriptor: SolutionDescriptor,
}

impl PlanningSolution for RevisionPlan {
    type Score = SoftScore;

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

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

impl Director<RevisionPlan> for RevisionDirector {
    fn working_solution(&self) -> &RevisionPlan {
        &self.working_solution
    }

    fn working_solution_mut(&mut self) -> &mut RevisionPlan {
        &mut self.working_solution
    }

    fn calculate_score(&mut self) -> SoftScore {
        let route_ready = !self.working_solution.routes[0].is_empty();
        let assigned = self.working_solution.tasks[0].worker_idx.is_some();
        let score = match (route_ready, assigned) {
            (false, false) => SoftScore::of(0),
            (false, true) => SoftScore::of(-1),
            (true, false) => SoftScore::of(0),
            (true, true) => SoftScore::of(10),
        };
        self.working_solution.set_score(Some(score));
        score
    }

    fn solution_descriptor(&self) -> &SolutionDescriptor {
        &self.descriptor
    }

    fn clone_working_solution(&self) -> RevisionPlan {
        self.working_solution.clone()
    }

    fn before_variable_changed(&mut self, _descriptor_index: usize, _entity_index: usize) {}

    fn after_variable_changed(&mut self, _descriptor_index: usize, _entity_index: usize) {}

    fn entity_count(&self, descriptor_index: usize) -> Option<usize> {
        match descriptor_index {
            0 => Some(self.working_solution.tasks.len()),
            1 => Some(self.working_solution.routes.len()),
            _ => None,
        }
    }

    fn total_entity_count(&self) -> Option<usize> {
        Some(self.working_solution.tasks.len() + self.working_solution.routes.len())
    }

    fn constraint_metadata(&self) -> Vec<solverforge_scoring::ConstraintMetadata<'_>> {
        Vec::new()
    }
}

fn revision_task_getter(entity: &dyn std::any::Any) -> Option<usize> {
    entity
        .downcast_ref::<RevisionTask>()
        .expect("task expected")
        .worker_idx
}

fn revision_task_setter(entity: &mut dyn std::any::Any, value: Option<usize>) {
    entity
        .downcast_mut::<RevisionTask>()
        .expect("task expected")
        .worker_idx = value;
}

fn revision_descriptor() -> SolutionDescriptor {
    SolutionDescriptor::new("RevisionPlan", TypeId::of::<RevisionPlan>())
        .with_entity(
            EntityDescriptor::new("Task", TypeId::of::<RevisionTask>(), "tasks")
                .with_extractor(Box::new(EntityCollectionExtractor::new(
                    "Task",
                    "tasks",
                    |solution: &RevisionPlan| &solution.tasks,
                    |solution: &mut RevisionPlan| &mut solution.tasks,
                )))
                .with_variable(
                    VariableDescriptor::genuine("worker_idx")
                        .with_allows_unassigned(true)
                        .with_value_range("workers")
                        .with_usize_accessors(revision_task_getter, revision_task_setter),
                ),
        )
        .with_entity(
            EntityDescriptor::new("Route", TypeId::of::<Vec<usize>>(), "routes").with_extractor(
                Box::new(EntityCollectionExtractor::new(
                    "Route",
                    "routes",
                    |solution: &RevisionPlan| &solution.routes,
                    |solution: &mut RevisionPlan| &mut solution.routes,
                )),
            ),
        )
        .with_problem_fact(
            ProblemFactDescriptor::new("Worker", TypeId::of::<RevisionWorker>(), "workers")
                .with_extractor(Box::new(EntityCollectionExtractor::new(
                    "Worker",
                    "workers",
                    |solution: &RevisionPlan| &solution.workers,
                    |solution: &mut RevisionPlan| &mut solution.workers,
                ))),
        )
}

fn revision_task_count(solution: &RevisionPlan) -> usize {
    solution.tasks.len()
}

fn revision_worker_count(solution: &RevisionPlan, _provider_index: usize) -> usize {
    solution.workers.len()
}

fn revision_worker_get(
    solution: &RevisionPlan,
    entity_index: usize,
    _variable_index: usize,
) -> Option<usize> {
    solution.tasks[entity_index].worker_idx
}

fn revision_worker_set(
    solution: &mut RevisionPlan,
    entity_index: usize,
    _variable_index: usize,
    value: Option<usize>,
) {
    solution.tasks[entity_index].worker_idx = value;
}

fn revision_route_count(solution: &RevisionPlan) -> usize {
    solution.routes.len()
}

fn revision_route_element_count(solution: &RevisionPlan) -> usize {
    solution.route_pool.len()
}

fn revision_assigned_route_elements(solution: &RevisionPlan) -> Vec<usize> {
    solution
        .routes
        .iter()
        .flat_map(|route| route.iter().copied())
        .collect()
}

fn revision_route_len(solution: &RevisionPlan, entity_index: usize) -> usize {
    solution.routes[entity_index].len()
}

fn revision_route_remove(
    solution: &mut RevisionPlan,
    entity_index: usize,
    pos: usize,
) -> Option<usize> {
    let route = solution.routes.get_mut(entity_index)?;
    (pos < route.len()).then(|| route.remove(pos))
}

fn revision_route_remove_for_construction(
    solution: &mut RevisionPlan,
    entity_index: usize,
    pos: usize,
) -> usize {
    solution.routes[entity_index].remove(pos)
}

fn revision_route_insert(
    solution: &mut RevisionPlan,
    entity_index: usize,
    pos: usize,
    value: usize,
) {
    solution.routes[entity_index].insert(pos, value);
}

fn revision_route_get(solution: &RevisionPlan, entity_index: usize, pos: usize) -> Option<usize> {
    solution.routes[entity_index].get(pos).copied()
}

fn revision_route_set(solution: &mut RevisionPlan, entity_index: usize, pos: usize, value: usize) {
    solution.routes[entity_index][pos] = value;
}

fn revision_route_reverse(
    solution: &mut RevisionPlan,
    entity_index: usize,
    start: usize,
    end: usize,
) {
    solution.routes[entity_index][start..end].reverse();
}

fn revision_route_sublist_remove(
    solution: &mut RevisionPlan,
    entity_index: usize,
    start: usize,
    end: usize,
) -> Vec<usize> {
    solution.routes[entity_index].drain(start..end).collect()
}

fn revision_route_sublist_insert(
    solution: &mut RevisionPlan,
    entity_index: usize,
    pos: usize,
    values: Vec<usize>,
) {
    solution.routes[entity_index].splice(pos..pos, values);
}

fn revision_route_ruin_remove(
    solution: &mut RevisionPlan,
    entity_index: usize,
    pos: usize,
) -> usize {
    solution.routes[entity_index].remove(pos)
}

fn revision_route_ruin_insert(
    solution: &mut RevisionPlan,
    entity_index: usize,
    pos: usize,
    value: usize,
) {
    solution.routes[entity_index].insert(pos, value);
}

fn revision_route_index_to_element(solution: &RevisionPlan, idx: usize) -> usize {
    solution.route_pool[idx]
}

fn revision_model() -> RuntimeModel<RevisionPlan, usize, DefaultMeter, DefaultMeter> {
    RuntimeModel::new(vec![
        VariableSlot::Scalar(ScalarVariableSlot::new(
            0,
            0,
            "Task",
            revision_task_count,
            "worker_idx",
            revision_worker_get,
            revision_worker_set,
            ValueSource::SolutionCount {
                count_fn: revision_worker_count,
                provider_index: 0,
            },
            true,
        )),
        VariableSlot::List(ListVariableSlot::new(
            "Route",
            revision_route_element_count,
            revision_assigned_route_elements,
            revision_route_len,
            revision_route_remove,
            revision_route_remove_for_construction,
            revision_route_insert,
            revision_route_get,
            revision_route_set,
            revision_route_reverse,
            revision_route_sublist_remove,
            revision_route_sublist_insert,
            revision_route_ruin_remove,
            revision_route_ruin_insert,
            revision_route_index_to_element,
            revision_route_count,
            DefaultMeter::default(),
            DefaultMeter::default(),
            "visits",
            1,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
        )),
    ])
}

#[test]
fn generic_mixed_phase_reopens_optional_none_after_list_commit() {
    let descriptor = revision_descriptor();
    let plan = RevisionPlan {
        score: None,
        workers: vec![RevisionWorker],
        tasks: vec![RevisionTask { worker_idx: None }],
        routes: vec![Vec::new()],
        route_pool: vec![10],
    };
    let director = RevisionDirector {
        working_solution: plan,
        descriptor: descriptor.clone(),
    };
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(None, descriptor, revision_model());
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().routes[0], vec![10]);
    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, Some(0));
    assert_eq!(solver_scope.stats().moves_accepted, 2);
}