Skip to content

Commit 25dee37

Browse files
committed
added min-cost max-matching solver. Set to version 1.1.0
1 parent 180f714 commit 25dee37

File tree

5 files changed

+239
-29
lines changed

5 files changed

+239
-29
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ members = [
1111
resolver = "2"
1212

1313
[workspace.package]
14-
version = "1.0.0"
14+
version = "1.1.0"
1515
edition = "2021"
1616
authors = ["Leon Sering <sering@math.ethz.ch"]
1717
description = "Solving the rolling-stock scheduling with a local search approach."

internal/src/lib.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ mod test_objective;
22

33
#[allow(unused_imports)]
44
use solver::greedy::Greedy;
5-
use solver::local_search::LocalSearch;
6-
use solver::max_matching_solver::MaxMatchingSolver;
5+
// use solver::local_search::LocalSearch;
6+
// use solver::max_matching_solver::MaxMatchingSolver;
7+
use solver::min_cost_max_matching_solver::MinCostMaxMatchingSolver;
78
use solver::{first_phase_objective, Solver};
89

910
use model::json_serialisation::load_rolling_stock_problem_instance_from_json;
@@ -35,7 +36,8 @@ pub fn run(input_data: serde_json::Value) -> serde_json::Value {
3536
);
3637
let start_solution = greedy.solve();
3738
// */
38-
// /*
39+
40+
/*
3941
// use matching_solver as start solution
4042
let matching_solver = MaxMatchingSolver::initialize(
4143
vehicle_types.clone(),
@@ -49,10 +51,24 @@ pub fn run(input_data: serde_json::Value) -> serde_json::Value {
4951
start_time.elapsed().as_secs_f32()
5052
);
5153
// */
54+
// /*
55+
// use min_cost_max_matching_solver as start solution
56+
let min_cost_max_matching_solver = MinCostMaxMatchingSolver::initialize(
57+
vehicle_types.clone(),
58+
network.clone(),
59+
config.clone(),
60+
objective.clone(),
61+
);
62+
let start_solution = min_cost_max_matching_solver.solve();
63+
println!(
64+
"\n*** MinCostMaxMatchingSolver computed initial schedule (elapsed time: {:0.2}sec) ***",
65+
start_time.elapsed().as_secs_f32()
66+
);
67+
// */
5268
objective.print_objective_value(start_solution.objective_value());
5369

54-
// let final_solution = start_solution;
55-
// /*
70+
let final_solution = start_solution;
71+
/*
5672
// initialize local search
5773
let mut local_search_solver = LocalSearch::initialize(
5874
vehicle_types.clone(),

server/src/lib.rs

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use objective_framework::Objective;
33
use solution::json_serialisation::schedule_to_json;
44
use solution::Schedule;
55
use solver::first_phase_objective;
6-
use solver::greedy::Greedy;
7-
use solver::local_search::LocalSearch;
6+
use solver::min_cost_max_matching_solver::MinCostMaxMatchingSolver;
87
use solver::Solution;
98
use solver::Solver;
109
use time::{DateTime, Duration};
@@ -26,35 +25,17 @@ pub fn solve_instance(input_data: serde_json::Value) -> serde_json::Value {
2625

2726
let objective = Arc::new(first_phase_objective::build());
2827

29-
// initialize local search
30-
let mut local_search_solver = LocalSearch::initialize(
28+
let min_cost_max_matching_solver = MinCostMaxMatchingSolver::initialize(
3129
vehicle_types.clone(),
3230
network.clone(),
3331
config.clone(),
3432
objective.clone(),
3533
);
36-
37-
// use greedy algorithm as start solution
38-
println!(
39-
"\n*** Run Greedy (elapsed time: {:0.2}sec) ***",
40-
start_time.elapsed().as_secs_f32()
41-
);
42-
let greedy = Greedy::initialize(
43-
vehicle_types.clone(),
44-
network.clone(),
45-
config.clone(),
46-
objective.clone(),
47-
);
48-
let start_solution = greedy.solve();
49-
objective.print_objective_value(start_solution.objective_value());
50-
51-
// run local search
34+
let final_solution = min_cost_max_matching_solver.solve();
5235
println!(
53-
"\n*** Run Local Search (elapsed time: {:0.2}sec) ***",
36+
"\n*** MinCostMaxMatchingSolver computed initial schedule (elapsed time: {:0.2}sec) ***",
5437
start_time.elapsed().as_secs_f32()
5538
);
56-
local_search_solver.set_initial_solution(start_solution);
57-
let final_solution = local_search_solver.solve();
5839

5940
let end_time = stdtime::Instant::now();
6041
let runtime_duration = end_time.duration_since(start_time);

solver/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod first_phase_objective;
22
pub mod greedy;
33
pub mod local_search;
44
pub mod max_matching_solver;
5+
pub mod min_cost_max_matching_solver;
56
pub mod one_node_per_tour;
67

78
use model::{config::Config, network::Network, vehicle_types::VehicleTypes};
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
use crate::Solution;
2+
use crate::Solver;
3+
use model::base_types::NodeId;
4+
use model::base_types::VehicleId;
5+
use model::config::Config;
6+
use model::network::Network;
7+
use model::vehicle_types::VehicleTypes;
8+
use objective_framework::Objective;
9+
use solution::path::Path;
10+
use solution::Schedule;
11+
12+
use rs_graph::linkedlistgraph::Edge as RsEdge;
13+
use rs_graph::linkedlistgraph::Node as RsNode;
14+
use rs_graph::mcf::network_simplex;
15+
use rs_graph::traits::Directed;
16+
use rs_graph::Buildable;
17+
use rs_graph::Builder;
18+
use rs_graph::IndexGraph;
19+
use rs_graph::LinkedListGraph;
20+
21+
use std::collections::HashMap;
22+
use std::sync::Arc;
23+
use std::time;
24+
25+
pub struct MinCostMaxMatchingSolver {
26+
vehicles: Arc<VehicleTypes>,
27+
network: Arc<Network>,
28+
config: Arc<Config>,
29+
objective: Arc<Objective<Schedule>>,
30+
}
31+
32+
impl Solver for MinCostMaxMatchingSolver {
33+
fn initialize(
34+
vehicles: Arc<VehicleTypes>,
35+
network: Arc<Network>,
36+
config: Arc<Config>,
37+
objective: Arc<Objective<Schedule>>,
38+
) -> Self {
39+
Self {
40+
vehicles,
41+
network,
42+
config,
43+
objective,
44+
}
45+
}
46+
47+
fn solve(&self) -> Solution {
48+
let start_time = time::Instant::now();
49+
// TODO decide on which vehicle type (biggest or best fitting)
50+
// for now: take biggest vehicles (good for a small count, as it might be reused for
51+
// later trips)
52+
let vehicle_type = self.vehicles.iter().last().unwrap();
53+
let seat_count = self.vehicles.get(vehicle_type).unwrap().seats();
54+
55+
let mut builder = LinkedListGraph::<u32>::new_builder();
56+
57+
let mut left_node_to_trip: HashMap<RsNode, (NodeId, u8)> = HashMap::new();
58+
let mut trip_to_node: HashMap<(NodeId, u8), (RsNode, RsNode)> = HashMap::new();
59+
60+
// (lower_bound, upper_bound, cost)
61+
let mut edges: HashMap<RsEdge, (i64, i64, i64)> = HashMap::new();
62+
63+
let source = builder.add_node();
64+
let sink = builder.add_node();
65+
66+
let mut node_counter: i64 = 0;
67+
let mut max_cost = 0;
68+
69+
let num_service_trips = self.network.service_nodes().count();
70+
for (counter, service_trip) in self.network.service_nodes().enumerate() {
71+
let demand = self.network.node(service_trip).as_service_trip().demand();
72+
for i in 0..demand.div_ceil(seat_count) as u8 {
73+
node_counter += 1;
74+
let left_node = builder.add_node();
75+
let right_node = builder.add_node();
76+
left_node_to_trip.insert(left_node, (service_trip, i));
77+
trip_to_node.insert((service_trip, i), (left_node, right_node));
78+
edges.insert(builder.add_edge(source, left_node), (0, 1, 0));
79+
edges.insert(builder.add_edge(right_node, sink), (0, 1, 0));
80+
self.network
81+
.all_predecessors(service_trip)
82+
.filter(|&pred| self.network.node(pred).is_service())
83+
.for_each(|pred| {
84+
let pred_demand = self.network.node(pred).as_service_trip().demand();
85+
for j in 0..pred_demand.div_ceil(seat_count) as u8 {
86+
let pred_left_node = trip_to_node[&(pred, j)].0;
87+
let cost: i64 = self
88+
.network
89+
.dead_head_distance_between(pred, service_trip)
90+
.in_meter() as i64
91+
* seat_count as i64;
92+
max_cost = i64::max(max_cost, cost);
93+
edges
94+
.insert(builder.add_edge(pred_left_node, right_node), (0, 1, cost));
95+
}
96+
});
97+
}
98+
if counter % 100 == 99 {
99+
println!(
100+
" service trips added to matching graph: {}/{}",
101+
counter + 1,
102+
num_service_trips
103+
);
104+
}
105+
}
106+
let st_cost: i64 = node_counter
107+
.checked_mul(max_cost)
108+
.expect("overflow")
109+
.checked_add(1)
110+
.expect("overflow");
111+
assert!(
112+
st_cost.checked_mul(node_counter).is_some(),
113+
"overflow could happen"
114+
);
115+
edges.insert(builder.add_edge(source, sink), (0, i64::MAX, st_cost));
116+
117+
let graph = builder.into_graph();
118+
119+
println!(
120+
"Min-Cost Matching graph loaded (elapsed time for matching: {:0.2}sec)",
121+
start_time.elapsed().as_secs_f32()
122+
);
123+
124+
let balance = |n| {
125+
if n == source {
126+
node_counter
127+
} else if n == sink {
128+
-node_counter
129+
} else {
130+
0
131+
}
132+
};
133+
134+
let (_, flow) = network_simplex(
135+
&graph,
136+
balance,
137+
|e| edges[&e].0,
138+
|e| edges[&e].1,
139+
|e| edges[&e].2,
140+
)
141+
.unwrap();
142+
143+
println!(
144+
"Min-Cost Max-Matching computed (elapsed time for matching: {:0.2}sec)",
145+
start_time.elapsed().as_secs_f32()
146+
);
147+
148+
let mut schedule = Schedule::empty(
149+
self.vehicles.clone(),
150+
self.network.clone(),
151+
self.config.clone(),
152+
);
153+
154+
let mut last_trip_to_vehicle: HashMap<(NodeId, u8), VehicleId> = HashMap::new();
155+
156+
for service_trip in self.network.service_nodes() {
157+
let demand = self.network.node(service_trip).as_service_trip().demand();
158+
for i in 0..demand.div_ceil(seat_count) as u8 {
159+
let right_node = trip_to_node[&(service_trip, i)].1;
160+
let pred_left_node = graph.inedges(right_node).find_map(|(edge, node)| {
161+
if flow[graph.edge_id(edge)].1 == 1 {
162+
Some(node)
163+
} else {
164+
None
165+
}
166+
});
167+
168+
let candidate =
169+
pred_left_node.map(|n| last_trip_to_vehicle[&left_node_to_trip[&n]]);
170+
171+
match candidate {
172+
Some(v) => {
173+
schedule = schedule
174+
.add_path_to_vehicle_tour(
175+
v,
176+
Path::new_from_single_node(service_trip, self.network.clone()),
177+
)
178+
.unwrap();
179+
last_trip_to_vehicle.insert((service_trip, i), v);
180+
}
181+
None => {
182+
// no vehicle can reach the service trip, spawn a new one
183+
184+
let result =
185+
schedule.spawn_vehicle_for_path(vehicle_type, vec![service_trip]);
186+
187+
match result {
188+
Ok((new_schedule, v)) => {
189+
schedule = new_schedule;
190+
last_trip_to_vehicle.insert((service_trip, i), v);
191+
}
192+
Err(_) => {
193+
println!(
194+
"Greedy: not enough depots space to cover service trip {}",
195+
service_trip
196+
);
197+
}
198+
}
199+
}
200+
}
201+
}
202+
}
203+
204+
schedule = schedule.reassign_end_depots_greedily().unwrap();
205+
println!(
206+
"Min-Cost Max-Matching turned into schedule. (matching running time: {:0.2}sec)",
207+
start_time.elapsed().as_secs_f32()
208+
);
209+
210+
self.objective.evaluate(schedule)
211+
}
212+
}

0 commit comments

Comments
 (0)