balanced_vrp_clustering 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/balanced_vrp_clustering/ai4r_compat.rb +152 -0
- data/lib/balanced_vrp_clustering/rust_engine.rb +234 -0
- data/lib/balanced_vrp_clustering.rb +1188 -0
- data/lib/balanced_vrp_clustering_native.rb +39 -0
- data/lib/concerns/overloadable_functions.rb +64 -0
- data/lib/helpers/helper.rb +283 -0
- data/lib/helpers/hull.rb +188 -0
- data/rust/Cargo.lock +585 -0
- data/rust/Cargo.toml +3 -0
- data/rust/balanced_vrp_clustering_core/Cargo.toml +25 -0
- data/rust/balanced_vrp_clustering_core/src/clusterer.rs +1280 -0
- data/rust/balanced_vrp_clustering_core/src/compatibility.rs +78 -0
- data/rust/balanced_vrp_clustering_core/src/distance.rs +179 -0
- data/rust/balanced_vrp_clustering_core/src/lib.rs +29 -0
- data/rust/balanced_vrp_clustering_core/src/limits.rs +62 -0
- data/rust/balanced_vrp_clustering_core/src/local_speed.rs +99 -0
- data/rust/balanced_vrp_clustering_core/src/main.rs +46 -0
- data/rust/balanced_vrp_clustering_core/src/relations.rs +143 -0
- data/rust/balanced_vrp_clustering_core/src/rng.rs +50 -0
- data/rust/balanced_vrp_clustering_core/src/types.rs +234 -0
- data/rust/balanced_vrp_clustering_core/tests/integration.rs +71 -0
- data/rust/balanced_vrp_clustering_native/Cargo.toml +33 -0
- data/rust/balanced_vrp_clustering_native/extconf.rb +26 -0
- data/rust/balanced_vrp_clustering_native/src/lib.rs +57 -0
- metadata +102 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Copyright © Cartoway
|
|
2
|
+
//
|
|
3
|
+
// This file is part of balanced_vrp_clustering.
|
|
4
|
+
//
|
|
5
|
+
// Cartoway balanced_vrp_clustering is free software. You can redistribute it and/or
|
|
6
|
+
// modify since you respect the terms of the GNU Affero General
|
|
7
|
+
// Public License as published by the Free Software Foundation,
|
|
8
|
+
// either version 3 of the License, or (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// Cartoway Planner is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
12
|
+
// or FITNESS FOR A PARTICULAR PURPOSE. See the Licenses for more details.
|
|
13
|
+
//
|
|
14
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
// along with Cartoway Planner. If not, see:
|
|
16
|
+
// <http://www.gnu.org/licenses/agpl.html>
|
|
17
|
+
//
|
|
18
|
+
|
|
19
|
+
use serde::{Deserialize, Deserializer, Serialize};
|
|
20
|
+
use std::collections::HashMap;
|
|
21
|
+
|
|
22
|
+
fn deserialize_usize_from_number<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
|
23
|
+
where
|
|
24
|
+
D: Deserializer<'de>,
|
|
25
|
+
{
|
|
26
|
+
#[derive(Deserialize)]
|
|
27
|
+
#[serde(untagged)]
|
|
28
|
+
enum Num {
|
|
29
|
+
Int(usize),
|
|
30
|
+
Float(f64),
|
|
31
|
+
}
|
|
32
|
+
match Num::deserialize(deserializer)? {
|
|
33
|
+
Num::Int(v) => Ok(v),
|
|
34
|
+
Num::Float(v) => Ok(v as usize),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn deserialize_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
|
|
39
|
+
where
|
|
40
|
+
D: Deserializer<'de>,
|
|
41
|
+
{
|
|
42
|
+
Ok(Option::<f64>::deserialize(deserializer)?.unwrap_or(0.0))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn deserialize_f64_default_one<'de, D>(deserializer: D) -> Result<f64, D::Error>
|
|
46
|
+
where
|
|
47
|
+
D: Deserializer<'de>,
|
|
48
|
+
{
|
|
49
|
+
Ok(Option::<f64>::deserialize(deserializer)?.unwrap_or(1.0))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn deserialize_vec_f64<'de, D>(deserializer: D) -> Result<Vec<f64>, D::Error>
|
|
53
|
+
where
|
|
54
|
+
D: Deserializer<'de>,
|
|
55
|
+
{
|
|
56
|
+
let raw: Vec<Option<f64>> = Vec::deserialize(deserializer)?;
|
|
57
|
+
Ok(raw.into_iter().map(|v| v.unwrap_or(0.0)).collect())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn deserialize_option_vec_f64<'de, D>(deserializer: D) -> Result<Option<Vec<f64>>, D::Error>
|
|
61
|
+
where
|
|
62
|
+
D: Deserializer<'de>,
|
|
63
|
+
{
|
|
64
|
+
let raw: Option<Vec<Option<f64>>> = Option::deserialize(deserializer)?;
|
|
65
|
+
Ok(raw.map(|values| values.into_iter().map(|v| v.unwrap_or(0.0)).collect()))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn deserialize_f64_map<'de, D>(deserializer: D) -> Result<HashMap<String, f64>, D::Error>
|
|
69
|
+
where
|
|
70
|
+
D: Deserializer<'de>,
|
|
71
|
+
{
|
|
72
|
+
let raw: HashMap<String, Option<f64>> = HashMap::deserialize(deserializer)?;
|
|
73
|
+
Ok(raw
|
|
74
|
+
.into_iter()
|
|
75
|
+
.map(|(k, v)| (k, v.unwrap_or(0.0)))
|
|
76
|
+
.collect())
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn deserialize_distance_matrix<'de, D>(deserializer: D) -> Result<Option<Vec<Vec<f64>>>, D::Error>
|
|
80
|
+
where
|
|
81
|
+
D: Deserializer<'de>,
|
|
82
|
+
{
|
|
83
|
+
let raw: Option<Vec<Vec<Option<f64>>>> = Option::deserialize(deserializer)?;
|
|
84
|
+
Ok(raw.map(|rows| {
|
|
85
|
+
rows.into_iter()
|
|
86
|
+
.map(|row| row.into_iter().map(|v| v.unwrap_or(0.0)).collect())
|
|
87
|
+
.collect()
|
|
88
|
+
}))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub const INCOMPATIBILITY_DISTANCE_PENALTY: f64 = 4_294_967_296.0; // 2^32
|
|
92
|
+
|
|
93
|
+
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
94
|
+
pub struct InputConfig {
|
|
95
|
+
pub name: Option<String>,
|
|
96
|
+
pub seed: u64,
|
|
97
|
+
pub cut_symbol: Option<String>,
|
|
98
|
+
#[serde(deserialize_with = "deserialize_f64")]
|
|
99
|
+
pub cut_ratio: f64,
|
|
100
|
+
#[serde(deserialize_with = "deserialize_usize_from_number")]
|
|
101
|
+
pub max_iterations: usize,
|
|
102
|
+
pub vehicles: Vec<VehicleInput>,
|
|
103
|
+
pub items: Vec<ItemInput>,
|
|
104
|
+
#[serde(default, deserialize_with = "deserialize_distance_matrix")]
|
|
105
|
+
pub distance_matrix: Option<Vec<Vec<f64>>>,
|
|
106
|
+
pub centroid_indices: Vec<usize>,
|
|
107
|
+
#[serde(default)]
|
|
108
|
+
pub related_item_indices: HashMap<String, Vec<Vec<usize>>>,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
112
|
+
pub struct VehicleInput {
|
|
113
|
+
pub id: Vec<String>,
|
|
114
|
+
pub depot: DepotInput,
|
|
115
|
+
#[serde(default, deserialize_with = "deserialize_f64_map")]
|
|
116
|
+
pub capacities: HashMap<String, f64>,
|
|
117
|
+
#[serde(default)]
|
|
118
|
+
pub skills: Vec<String>,
|
|
119
|
+
#[serde(default)]
|
|
120
|
+
pub day_skills: Vec<String>,
|
|
121
|
+
#[serde(default, deserialize_with = "deserialize_f64")]
|
|
122
|
+
pub duration: f64,
|
|
123
|
+
#[serde(default = "default_one", deserialize_with = "deserialize_f64_default_one")]
|
|
124
|
+
pub total_work_days: f64,
|
|
125
|
+
#[serde(default = "default_one", deserialize_with = "deserialize_f64_default_one")]
|
|
126
|
+
pub vehicle_count: f64,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn default_one() -> f64 {
|
|
130
|
+
1.0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
134
|
+
pub struct DepotInput {
|
|
135
|
+
#[serde(default, deserialize_with = "deserialize_option_vec_f64")]
|
|
136
|
+
pub coordinates: Option<Vec<f64>>,
|
|
137
|
+
pub matrix_index: Option<usize>,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
141
|
+
pub struct ItemInput {
|
|
142
|
+
pub id: String,
|
|
143
|
+
#[serde(deserialize_with = "deserialize_f64")]
|
|
144
|
+
pub lat: f64,
|
|
145
|
+
#[serde(deserialize_with = "deserialize_f64")]
|
|
146
|
+
pub lon: f64,
|
|
147
|
+
#[serde(default, deserialize_with = "deserialize_f64_map")]
|
|
148
|
+
pub quantities: HashMap<String, f64>,
|
|
149
|
+
#[serde(default)]
|
|
150
|
+
pub v_id: Vec<String>,
|
|
151
|
+
#[serde(default)]
|
|
152
|
+
pub skills: Vec<String>,
|
|
153
|
+
#[serde(default)]
|
|
154
|
+
pub day_skills: Vec<String>,
|
|
155
|
+
pub matrix_index: Option<usize>,
|
|
156
|
+
#[serde(default, deserialize_with = "deserialize_vec_f64")]
|
|
157
|
+
pub duration_from_and_to_depot: Vec<f64>,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[derive(Debug, Clone, Serialize)]
|
|
161
|
+
pub struct BuildOutput {
|
|
162
|
+
pub iterations: usize,
|
|
163
|
+
pub clusters: Vec<Vec<String>>,
|
|
164
|
+
pub elapsed_ms: u64,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[derive(Debug, Clone)]
|
|
168
|
+
pub struct CentroidWeights {
|
|
169
|
+
pub limit: Vec<f64>,
|
|
170
|
+
pub compatibility: f64,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[derive(Debug, Clone)]
|
|
174
|
+
pub struct Item {
|
|
175
|
+
pub id: String,
|
|
176
|
+
pub lat: f64,
|
|
177
|
+
pub lon: f64,
|
|
178
|
+
pub quantities: HashMap<String, f64>,
|
|
179
|
+
pub v_id: Vec<String>,
|
|
180
|
+
pub skills: Vec<String>,
|
|
181
|
+
pub day_skills: Vec<String>,
|
|
182
|
+
pub matrix_index: Option<usize>,
|
|
183
|
+
pub duration_from_and_to_depot: Vec<f64>,
|
|
184
|
+
pub linked_next: Option<usize>,
|
|
185
|
+
pub centroid_weights: CentroidWeights,
|
|
186
|
+
pub needs_to_stay_at_the_top: bool,
|
|
187
|
+
pub moved_up: bool,
|
|
188
|
+
pub moved_down: bool,
|
|
189
|
+
pub weighted_visit_distance: f64,
|
|
190
|
+
pub local_speed: Option<f64>,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#[derive(Debug, Clone)]
|
|
194
|
+
pub struct Centroid {
|
|
195
|
+
pub lat: f64,
|
|
196
|
+
pub lon: f64,
|
|
197
|
+
pub id: String,
|
|
198
|
+
pub load: HashMap<String, f64>,
|
|
199
|
+
pub v_id: Vec<String>,
|
|
200
|
+
pub skills: Vec<String>,
|
|
201
|
+
pub day_skills: Vec<String>,
|
|
202
|
+
pub matrix_index: Option<usize>,
|
|
203
|
+
pub duration_from_and_to_depot: f64,
|
|
204
|
+
pub capacity_offence_coeff: f64,
|
|
205
|
+
pub route_time: f64,
|
|
206
|
+
pub area: f64,
|
|
207
|
+
pub visit_count: f64,
|
|
208
|
+
pub visit_density: f64,
|
|
209
|
+
pub min_speed: f64,
|
|
210
|
+
pub max_speed: f64,
|
|
211
|
+
pub speed: f64,
|
|
212
|
+
pub total_work_days: f64,
|
|
213
|
+
pub vehicle_count: f64,
|
|
214
|
+
pub capacities: HashMap<String, f64>,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[derive(Debug, Clone)]
|
|
218
|
+
pub struct Vehicle {
|
|
219
|
+
pub id: Vec<String>,
|
|
220
|
+
pub depot_lat: f64,
|
|
221
|
+
pub depot_lon: f64,
|
|
222
|
+
pub depot_matrix_index: Option<usize>,
|
|
223
|
+
pub capacities: HashMap<String, f64>,
|
|
224
|
+
pub skills: Vec<String>,
|
|
225
|
+
pub day_skills: Vec<String>,
|
|
226
|
+
pub duration: f64,
|
|
227
|
+
pub total_work_days: f64,
|
|
228
|
+
pub vehicle_count: f64,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[derive(Debug, Clone)]
|
|
232
|
+
pub struct CutLimit {
|
|
233
|
+
pub limit: f64,
|
|
234
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright © Cartoway
|
|
2
|
+
//
|
|
3
|
+
// This file is part of balanced_vrp_clustering.
|
|
4
|
+
//
|
|
5
|
+
// Cartoway balanced_vrp_clustering is free software. You can redistribute it and/or
|
|
6
|
+
// modify since you respect the terms of the GNU Affero General
|
|
7
|
+
// Public License as published by the Free Software Foundation,
|
|
8
|
+
// either version 3 of the License, or (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// Cartoway Planner is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
12
|
+
// or FITNESS FOR A PARTICULAR PURPOSE. See the Licenses for more details.
|
|
13
|
+
//
|
|
14
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
// along with Cartoway Planner. If not, see:
|
|
16
|
+
// <http://www.gnu.org/licenses/agpl.html>
|
|
17
|
+
//
|
|
18
|
+
|
|
19
|
+
use balanced_vrp_clustering_core::{build_from_input, InputConfig};
|
|
20
|
+
use std::fs;
|
|
21
|
+
|
|
22
|
+
#[test]
|
|
23
|
+
fn golden_tiny_builds() {
|
|
24
|
+
let path = concat!(
|
|
25
|
+
env!("CARGO_MANIFEST_DIR"),
|
|
26
|
+
"/../../benchmark/fixtures/json/tiny.json"
|
|
27
|
+
);
|
|
28
|
+
if !std::path::Path::new(path).exists() {
|
|
29
|
+
return; // skip when fixtures not exported
|
|
30
|
+
}
|
|
31
|
+
let json = fs::read_to_string(path).expect("read tiny.json");
|
|
32
|
+
let input: InputConfig = serde_json::from_str(&json).expect("parse");
|
|
33
|
+
let output = build_from_input(&input);
|
|
34
|
+
assert_eq!(output.clusters.len(), 2);
|
|
35
|
+
assert_eq!(
|
|
36
|
+
output.clusters.iter().map(|c| c.len()).sum::<usize>(),
|
|
37
|
+
4
|
|
38
|
+
);
|
|
39
|
+
assert!(output.iterations > 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[test]
|
|
43
|
+
fn parses_json_null_numeric_fields() {
|
|
44
|
+
let json = r#"{
|
|
45
|
+
"seed": 1,
|
|
46
|
+
"cut_ratio": null,
|
|
47
|
+
"max_iterations": 10,
|
|
48
|
+
"vehicles": [{
|
|
49
|
+
"id": ["v1"],
|
|
50
|
+
"depot": { "coordinates": [null, 48.5] },
|
|
51
|
+
"duration": null,
|
|
52
|
+
"total_work_days": null,
|
|
53
|
+
"vehicle_count": null,
|
|
54
|
+
"capacities": { "visits": null }
|
|
55
|
+
}],
|
|
56
|
+
"items": [{
|
|
57
|
+
"id": "p1",
|
|
58
|
+
"lat": null,
|
|
59
|
+
"lon": null,
|
|
60
|
+
"quantities": { "visits": null },
|
|
61
|
+
"duration_from_and_to_depot": [null, 120.0]
|
|
62
|
+
}],
|
|
63
|
+
"distance_matrix": [[null, 2.0]],
|
|
64
|
+
"centroid_indices": []
|
|
65
|
+
}"#;
|
|
66
|
+
let input: InputConfig = serde_json::from_str(json).expect("parse nulls as zero");
|
|
67
|
+
assert_eq!(input.cut_ratio, 0.0);
|
|
68
|
+
assert_eq!(input.items[0].lat, 0.0);
|
|
69
|
+
assert_eq!(input.items[0].duration_from_and_to_depot, [0.0, 120.0]);
|
|
70
|
+
assert_eq!(input.distance_matrix.as_ref().unwrap()[0][0], 0.0);
|
|
71
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright © Cartoway
|
|
2
|
+
#
|
|
3
|
+
# This file is part of balanced_vrp_clustering.
|
|
4
|
+
#
|
|
5
|
+
# Cartoway balanced_vrp_clustering is free software. You can redistribute it and/or
|
|
6
|
+
# modify since you respect the terms of the GNU Affero General
|
|
7
|
+
# Public License as published by the Free Software Foundation,
|
|
8
|
+
# either version 3 of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# Cartoway Planner is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
12
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the Licenses for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
# along with Cartoway Planner. If not, see:
|
|
16
|
+
# <http://www.gnu.org/licenses/agpl.html>
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
[package]
|
|
20
|
+
name = "balanced_vrp_clustering_native"
|
|
21
|
+
version = "0.4.0"
|
|
22
|
+
edition = "2021"
|
|
23
|
+
description = "Ruby native extension for balanced VRP clustering"
|
|
24
|
+
license = "AGPL-3.0-or-later"
|
|
25
|
+
|
|
26
|
+
[lib]
|
|
27
|
+
crate-type = ["cdylib"]
|
|
28
|
+
name = "balanced_vrp_clustering_native"
|
|
29
|
+
|
|
30
|
+
[dependencies]
|
|
31
|
+
balanced_vrp_clustering_core = { path = "../balanced_vrp_clustering_core" }
|
|
32
|
+
magnus = { version = "0.8" }
|
|
33
|
+
serde_json = "1"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright © Cartoway
|
|
2
|
+
#
|
|
3
|
+
# This file is part of balanced_vrp_clustering.
|
|
4
|
+
#
|
|
5
|
+
# Cartoway balanced_vrp_clustering is free software. You can redistribute it and/or
|
|
6
|
+
# modify since you respect the terms of the GNU Affero General
|
|
7
|
+
# Public License as published by the Free Software Foundation,
|
|
8
|
+
# either version 3 of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# Cartoway Planner is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
12
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the Licenses for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
# along with Cartoway Planner. If not, see:
|
|
16
|
+
# <http://www.gnu.org/licenses/agpl.html>
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
# frozen_string_literal: true
|
|
20
|
+
|
|
21
|
+
require 'mkmf'
|
|
22
|
+
require 'rb_sys/mkmf'
|
|
23
|
+
|
|
24
|
+
create_rust_makefile('balanced_vrp_clustering_native') do |config|
|
|
25
|
+
config.profile = ENV.fetch('BVRP_NATIVE_PROFILE', 'release').to_sym
|
|
26
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright © Cartoway
|
|
2
|
+
//
|
|
3
|
+
// This file is part of balanced_vrp_clustering.
|
|
4
|
+
//
|
|
5
|
+
// Cartoway balanced_vrp_clustering is free software. You can redistribute it and/or
|
|
6
|
+
// modify since you respect the terms of the GNU Affero General
|
|
7
|
+
// Public License as published by the Free Software Foundation,
|
|
8
|
+
// either version 3 of the License, or (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// Cartoway Planner is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
12
|
+
// or FITNESS FOR A PARTICULAR PURPOSE. See the Licenses for more details.
|
|
13
|
+
//
|
|
14
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
// along with Cartoway Planner. If not, see:
|
|
16
|
+
// <http://www.gnu.org/licenses/agpl.html>
|
|
17
|
+
//
|
|
18
|
+
|
|
19
|
+
use balanced_vrp_clustering_core::{build_from_input, InputConfig};
|
|
20
|
+
use magnus::{function, prelude::*, Error, Ruby, RString};
|
|
21
|
+
|
|
22
|
+
fn build_from_json(input: RString) -> Result<RString, Error> {
|
|
23
|
+
let ruby = Ruby::get().expect("Ruby not initialized");
|
|
24
|
+
let json_str = input
|
|
25
|
+
.to_string()
|
|
26
|
+
.map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))?;
|
|
27
|
+
|
|
28
|
+
let config: InputConfig = serde_json::from_str(&json_str).map_err(|e| {
|
|
29
|
+
Error::new(
|
|
30
|
+
ruby.exception_arg_error(),
|
|
31
|
+
format!("invalid JSON input: {e}"),
|
|
32
|
+
)
|
|
33
|
+
})?;
|
|
34
|
+
|
|
35
|
+
let output = build_from_input(&config);
|
|
36
|
+
let out_json = serde_json::to_string(&output).map_err(|e| {
|
|
37
|
+
Error::new(
|
|
38
|
+
ruby.exception_runtime_error(),
|
|
39
|
+
format!("failed to serialize output: {e}"),
|
|
40
|
+
)
|
|
41
|
+
})?;
|
|
42
|
+
|
|
43
|
+
Ok(ruby.str_new(&out_json))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Returns true when the native extension loaded successfully.
|
|
47
|
+
fn available() -> bool {
|
|
48
|
+
true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[magnus::init]
|
|
52
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
53
|
+
let module = ruby.define_module("BalancedVRPClusteringNative")?;
|
|
54
|
+
module.define_singleton_method("build_from_json", function!(build_from_json, 1))?;
|
|
55
|
+
module.define_singleton_method("available?", function!(available, 0))?;
|
|
56
|
+
Ok(())
|
|
57
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: balanced_vrp_clustering
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cartoway
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-11-10 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rb_sys
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.9.128
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.9.128
|
|
54
|
+
executables: []
|
|
55
|
+
extensions:
|
|
56
|
+
- rust/balanced_vrp_clustering_native/extconf.rb
|
|
57
|
+
extra_rdoc_files: []
|
|
58
|
+
files:
|
|
59
|
+
- lib/balanced_vrp_clustering.rb
|
|
60
|
+
- lib/balanced_vrp_clustering/ai4r_compat.rb
|
|
61
|
+
- lib/balanced_vrp_clustering/rust_engine.rb
|
|
62
|
+
- lib/balanced_vrp_clustering_native.rb
|
|
63
|
+
- lib/concerns/overloadable_functions.rb
|
|
64
|
+
- lib/helpers/helper.rb
|
|
65
|
+
- lib/helpers/hull.rb
|
|
66
|
+
- rust/Cargo.lock
|
|
67
|
+
- rust/Cargo.toml
|
|
68
|
+
- rust/balanced_vrp_clustering_core/Cargo.toml
|
|
69
|
+
- rust/balanced_vrp_clustering_core/src/clusterer.rs
|
|
70
|
+
- rust/balanced_vrp_clustering_core/src/compatibility.rs
|
|
71
|
+
- rust/balanced_vrp_clustering_core/src/distance.rs
|
|
72
|
+
- rust/balanced_vrp_clustering_core/src/lib.rs
|
|
73
|
+
- rust/balanced_vrp_clustering_core/src/limits.rs
|
|
74
|
+
- rust/balanced_vrp_clustering_core/src/local_speed.rs
|
|
75
|
+
- rust/balanced_vrp_clustering_core/src/main.rs
|
|
76
|
+
- rust/balanced_vrp_clustering_core/src/relations.rs
|
|
77
|
+
- rust/balanced_vrp_clustering_core/src/rng.rs
|
|
78
|
+
- rust/balanced_vrp_clustering_core/src/types.rs
|
|
79
|
+
- rust/balanced_vrp_clustering_core/tests/integration.rs
|
|
80
|
+
- rust/balanced_vrp_clustering_native/Cargo.toml
|
|
81
|
+
- rust/balanced_vrp_clustering_native/extconf.rb
|
|
82
|
+
- rust/balanced_vrp_clustering_native/src/lib.rs
|
|
83
|
+
licenses: []
|
|
84
|
+
metadata: {}
|
|
85
|
+
rdoc_options: []
|
|
86
|
+
require_paths:
|
|
87
|
+
- lib
|
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '3.1'
|
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '0'
|
|
98
|
+
requirements: []
|
|
99
|
+
rubygems_version: 4.0.10
|
|
100
|
+
specification_version: 4
|
|
101
|
+
summary: Gem for clustering points of a given VRP.
|
|
102
|
+
test_files: []
|