or-tools 0.15.1 → 0.16.1
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/ext/or-tools/bin_packing.cpp +7 -0
- data/ext/or-tools/constraint.cpp +26 -1
- data/ext/or-tools/extconf.rb +1 -1
- data/ext/or-tools/linear.cpp +7 -0
- data/ext/or-tools/math_opt.cpp +12 -1
- data/ext/or-tools/network_flows.cpp +5 -6
- data/ext/or-tools/routing.cpp +16 -2
- data/ext/or-tools/vendor.rb +21 -21
- data/lib/or_tools/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dee4a87960e33b5a357e9549cac01fadbbed11aa0e7b8fda11355383f01f853c
|
|
4
|
+
data.tar.gz: 5cb68b2fb6beb48cf73f5d78fc14930a274da58bf8c743d5199588b4283bd3c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab8788f734a9b525c9d7f385fc32947430c9727aec659a5f898436159a8e90684031508fb5f6bf099381821ff184344f101a52c90e23f716a2bf46f91e7e78e1
|
|
7
|
+
data.tar.gz: 5b90c3c913f171e464da6c162659a7e4fc896fa30f6996e21d145b07f93d4a925d71af71d1ddee2eab27f1850545715abd2016bb52f6b6151badc06eb8a6f37a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 0.16.1 (2025-10-26)
|
|
2
|
+
|
|
3
|
+
- Added `add_allowed_assignments` and `add_forbidden_assignments` methods to `CpModel`
|
|
4
|
+
- Fixed error with Rice 4.7
|
|
5
|
+
|
|
6
|
+
## 0.16.0 (2025-06-19)
|
|
7
|
+
|
|
8
|
+
- Updated OR-Tools to 9.14
|
|
9
|
+
|
|
1
10
|
## 0.15.1 (2025-06-18)
|
|
2
11
|
|
|
3
12
|
- Fixed error with Rice 4.6
|
|
@@ -20,6 +20,10 @@ namespace Rice::detail {
|
|
|
20
20
|
template<>
|
|
21
21
|
class From_Ruby<KnapsackSolver::SolverType> {
|
|
22
22
|
public:
|
|
23
|
+
From_Ruby() = default;
|
|
24
|
+
|
|
25
|
+
explicit From_Ruby(Arg* arg) : arg_(arg) { }
|
|
26
|
+
|
|
23
27
|
Convertible is_convertible(VALUE value) { return Convertible::Cast; }
|
|
24
28
|
|
|
25
29
|
KnapsackSolver::SolverType convert(VALUE x) {
|
|
@@ -30,6 +34,9 @@ namespace Rice::detail {
|
|
|
30
34
|
throw std::runtime_error("Unknown solver type: " + s);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
37
|
+
|
|
38
|
+
private:
|
|
39
|
+
Arg* arg_ = nullptr;
|
|
33
40
|
};
|
|
34
41
|
} // namespace Rice::detail
|
|
35
42
|
|
data/ext/or-tools/constraint.cpp
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
using operations_research::Domain;
|
|
10
10
|
using operations_research::sat::BoolVar;
|
|
11
11
|
using operations_research::sat::Constraint;
|
|
12
|
+
using operations_research::sat::TableConstraint;
|
|
12
13
|
using operations_research::sat::CpModelBuilder;
|
|
13
14
|
using operations_research::sat::CpSolverResponse;
|
|
14
15
|
using operations_research::sat::CpSolverStatus;
|
|
@@ -39,6 +40,10 @@ namespace Rice::detail {
|
|
|
39
40
|
template<>
|
|
40
41
|
class From_Ruby<LinearExpr> {
|
|
41
42
|
public:
|
|
43
|
+
From_Ruby() = default;
|
|
44
|
+
|
|
45
|
+
explicit From_Ruby(Arg* arg) : arg_(arg) { }
|
|
46
|
+
|
|
42
47
|
Convertible is_convertible(VALUE value) { return Convertible::Cast; }
|
|
43
48
|
|
|
44
49
|
LinearExpr convert(VALUE v) {
|
|
@@ -62,6 +67,9 @@ namespace Rice::detail {
|
|
|
62
67
|
|
|
63
68
|
return expr;
|
|
64
69
|
}
|
|
70
|
+
|
|
71
|
+
private:
|
|
72
|
+
Arg* arg_ = nullptr;
|
|
65
73
|
};
|
|
66
74
|
} // namespace Rice::detail
|
|
67
75
|
|
|
@@ -103,6 +111,13 @@ void init_constraint(Rice::Module& m) {
|
|
|
103
111
|
}
|
|
104
112
|
});
|
|
105
113
|
|
|
114
|
+
Rice::define_class_under<TableConstraint, Constraint>(m, "SatTableConstraint")
|
|
115
|
+
.define_method(
|
|
116
|
+
"add_tuple",
|
|
117
|
+
[](TableConstraint& self, std::vector<int64_t> tuple) {
|
|
118
|
+
self.AddTuple(tuple);
|
|
119
|
+
});
|
|
120
|
+
|
|
106
121
|
rb_cBoolVar = Rice::define_class_under<BoolVar>(m, "SatBoolVar")
|
|
107
122
|
.define_method("name", &BoolVar::Name)
|
|
108
123
|
.define_method("index", &BoolVar::index)
|
|
@@ -251,6 +266,16 @@ void init_constraint(Rice::Module& m) {
|
|
|
251
266
|
[](CpModelBuilder& self, std::vector<IntVar> vars) {
|
|
252
267
|
return self.AddAllDifferent(vars);
|
|
253
268
|
})
|
|
269
|
+
.define_method(
|
|
270
|
+
"add_allowed_assignments",
|
|
271
|
+
[](CpModelBuilder& self, std::vector<LinearExpr> expressions) {
|
|
272
|
+
return self.AddAllowedAssignments(expressions);
|
|
273
|
+
})
|
|
274
|
+
.define_method(
|
|
275
|
+
"add_forbidden_assignments",
|
|
276
|
+
[](CpModelBuilder& self, std::vector<LinearExpr> expressions) {
|
|
277
|
+
return self.AddForbiddenAssignments(expressions);
|
|
278
|
+
})
|
|
254
279
|
.define_method(
|
|
255
280
|
"add_inverse_constraint",
|
|
256
281
|
[](CpModelBuilder& self, std::vector<IntVar> variables, std::vector<IntVar> inverse_variables) {
|
|
@@ -391,7 +416,7 @@ void init_constraint(Rice::Module& m) {
|
|
|
391
416
|
auto a = Array();
|
|
392
417
|
auto assumptions = self.sufficient_assumptions_for_infeasibility();
|
|
393
418
|
for (const auto& v : assumptions) {
|
|
394
|
-
a.push(v);
|
|
419
|
+
a.push(v, false);
|
|
395
420
|
}
|
|
396
421
|
return a;
|
|
397
422
|
});
|
data/ext/or-tools/extconf.rb
CHANGED
|
@@ -6,7 +6,7 @@ $CXXFLAGS << " -std=c++17 $(optflags) -DUSE_CBC -DOR_PROTO_DLL="
|
|
|
6
6
|
$CXXFLAGS << " -Wall -Wextra"
|
|
7
7
|
|
|
8
8
|
# hide or-tools warnings
|
|
9
|
-
$CXXFLAGS << " -Wno-sign-compare -Wno-ignored-qualifiers -Wno-unused-parameter -Wno-missing-field-initializers"
|
|
9
|
+
$CXXFLAGS << " -Wno-sign-compare -Wno-ignored-qualifiers -Wno-unused-parameter -Wno-missing-field-initializers -Wno-deprecated-declarations"
|
|
10
10
|
|
|
11
11
|
# hide Rice warnings
|
|
12
12
|
$CXXFLAGS << " -Wno-unused-private-field -Wno-implicit-fallthrough"
|
data/ext/or-tools/linear.cpp
CHANGED
|
@@ -26,6 +26,10 @@ namespace Rice::detail {
|
|
|
26
26
|
|
|
27
27
|
template<>
|
|
28
28
|
struct From_Ruby<MPSolver::OptimizationProblemType> {
|
|
29
|
+
From_Ruby() = default;
|
|
30
|
+
|
|
31
|
+
explicit From_Ruby(Arg* arg) : arg_(arg) { }
|
|
32
|
+
|
|
29
33
|
Convertible is_convertible(VALUE value) { return Convertible::Cast; }
|
|
30
34
|
|
|
31
35
|
static MPSolver::OptimizationProblemType convert(VALUE x) {
|
|
@@ -38,6 +42,9 @@ namespace Rice::detail {
|
|
|
38
42
|
throw std::runtime_error("Unknown optimization problem type: " + s);
|
|
39
43
|
}
|
|
40
44
|
}
|
|
45
|
+
|
|
46
|
+
private:
|
|
47
|
+
Arg* arg_ = nullptr;
|
|
41
48
|
};
|
|
42
49
|
} // namespace Rice::detail
|
|
43
50
|
|
data/ext/or-tools/math_opt.cpp
CHANGED
|
@@ -25,6 +25,10 @@ namespace Rice::detail {
|
|
|
25
25
|
|
|
26
26
|
template<>
|
|
27
27
|
struct From_Ruby<SolverType> {
|
|
28
|
+
From_Ruby() = default;
|
|
29
|
+
|
|
30
|
+
explicit From_Ruby(Arg* arg) : arg_(arg) { }
|
|
31
|
+
|
|
28
32
|
Convertible is_convertible(VALUE value) { return Convertible::Cast; }
|
|
29
33
|
|
|
30
34
|
static SolverType convert(VALUE x) {
|
|
@@ -53,6 +57,9 @@ namespace Rice::detail {
|
|
|
53
57
|
throw std::runtime_error("Unknown solver type: " + s);
|
|
54
58
|
}
|
|
55
59
|
}
|
|
60
|
+
|
|
61
|
+
private:
|
|
62
|
+
Arg* arg_ = nullptr;
|
|
56
63
|
};
|
|
57
64
|
} // namespace Rice::detail
|
|
58
65
|
|
|
@@ -61,7 +68,11 @@ void init_math_opt(Rice::Module& m) {
|
|
|
61
68
|
|
|
62
69
|
Rice::define_class_under<Variable>(mathopt, "Variable")
|
|
63
70
|
.define_method("id", &Variable::id)
|
|
64
|
-
.define_method(
|
|
71
|
+
.define_method(
|
|
72
|
+
"name",
|
|
73
|
+
[](Variable& self) {
|
|
74
|
+
return std::string(self.name());
|
|
75
|
+
})
|
|
65
76
|
.define_method(
|
|
66
77
|
"_eql?",
|
|
67
78
|
[](Variable& self, Variable &other) {
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
#include <rice/rice.hpp>
|
|
6
6
|
#include <rice/stl.hpp>
|
|
7
7
|
|
|
8
|
-
using operations_research::NodeIndex;
|
|
9
8
|
using operations_research::SimpleMaxFlow;
|
|
10
9
|
using operations_research::SimpleMinCostFlow;
|
|
11
10
|
|
|
@@ -25,7 +24,7 @@ void init_network_flows(Rice::Module& m) {
|
|
|
25
24
|
.define_method("flow", &SimpleMaxFlow::Flow)
|
|
26
25
|
.define_method(
|
|
27
26
|
"solve",
|
|
28
|
-
[](SimpleMaxFlow& self, NodeIndex source, NodeIndex sink) {
|
|
27
|
+
[](SimpleMaxFlow& self, SimpleMaxFlow::NodeIndex source, SimpleMaxFlow::NodeIndex sink) {
|
|
29
28
|
auto status = self.Solve(source, sink);
|
|
30
29
|
|
|
31
30
|
if (status == SimpleMaxFlow::Status::OPTIMAL) {
|
|
@@ -43,24 +42,24 @@ void init_network_flows(Rice::Module& m) {
|
|
|
43
42
|
.define_method(
|
|
44
43
|
"source_side_min_cut",
|
|
45
44
|
[](SimpleMaxFlow& self) {
|
|
46
|
-
std::vector<NodeIndex> result;
|
|
45
|
+
std::vector<SimpleMaxFlow::NodeIndex> result;
|
|
47
46
|
self.GetSourceSideMinCut(&result);
|
|
48
47
|
|
|
49
48
|
Array ret;
|
|
50
49
|
for (const auto& it : result) {
|
|
51
|
-
ret.push(it);
|
|
50
|
+
ret.push(it, false);
|
|
52
51
|
}
|
|
53
52
|
return ret;
|
|
54
53
|
})
|
|
55
54
|
.define_method(
|
|
56
55
|
"sink_side_min_cut",
|
|
57
56
|
[](SimpleMaxFlow& self) {
|
|
58
|
-
std::vector<NodeIndex> result;
|
|
57
|
+
std::vector<SimpleMaxFlow::NodeIndex> result;
|
|
59
58
|
self.GetSinkSideMinCut(&result);
|
|
60
59
|
|
|
61
60
|
Array ret;
|
|
62
61
|
for (const auto& it : result) {
|
|
63
|
-
ret.push(it);
|
|
62
|
+
ret.push(it, false);
|
|
64
63
|
}
|
|
65
64
|
return ret;
|
|
66
65
|
});
|
data/ext/or-tools/routing.cpp
CHANGED
|
@@ -36,20 +36,34 @@ namespace Rice::detail {
|
|
|
36
36
|
template<>
|
|
37
37
|
class From_Ruby<RoutingNodeIndex> {
|
|
38
38
|
public:
|
|
39
|
+
From_Ruby() = default;
|
|
40
|
+
|
|
41
|
+
explicit From_Ruby(Arg* arg) : arg_(arg) { }
|
|
42
|
+
|
|
39
43
|
Convertible is_convertible(VALUE value) { return Convertible::Cast; }
|
|
40
44
|
|
|
41
45
|
RoutingNodeIndex convert(VALUE x) {
|
|
42
46
|
const RoutingNodeIndex index{From_Ruby<int>().convert(x)};
|
|
43
47
|
return index;
|
|
44
48
|
}
|
|
49
|
+
|
|
50
|
+
private:
|
|
51
|
+
Arg* arg_ = nullptr;
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
template<>
|
|
48
55
|
class To_Ruby<RoutingNodeIndex> {
|
|
49
56
|
public:
|
|
57
|
+
To_Ruby() = default;
|
|
58
|
+
|
|
59
|
+
explicit To_Ruby(Arg* arg) : arg_(arg) { }
|
|
60
|
+
|
|
50
61
|
VALUE convert(RoutingNodeIndex const & x) {
|
|
51
62
|
return To_Ruby<int>().convert(x.value());
|
|
52
63
|
}
|
|
64
|
+
|
|
65
|
+
private:
|
|
66
|
+
Arg* arg_ = nullptr;
|
|
53
67
|
};
|
|
54
68
|
} // namespace Rice::detail
|
|
55
69
|
|
|
@@ -62,7 +76,7 @@ void init_routing(Rice::Module& m) {
|
|
|
62
76
|
rb_cRoutingSearchParameters
|
|
63
77
|
.define_method(
|
|
64
78
|
"first_solution_strategy=",
|
|
65
|
-
[](RoutingSearchParameters& self,
|
|
79
|
+
[](RoutingSearchParameters& self, Object value) {
|
|
66
80
|
auto s = Symbol(value).str();
|
|
67
81
|
|
|
68
82
|
FirstSolutionStrategy::Value v;
|
|
@@ -102,7 +116,7 @@ void init_routing(Rice::Module& m) {
|
|
|
102
116
|
})
|
|
103
117
|
.define_method(
|
|
104
118
|
"local_search_metaheuristic=",
|
|
105
|
-
[](RoutingSearchParameters& self,
|
|
119
|
+
[](RoutingSearchParameters& self, Object value) {
|
|
106
120
|
auto s = Symbol(value).str();
|
|
107
121
|
|
|
108
122
|
LocalSearchMetaheuristic::Value v;
|
data/ext/or-tools/vendor.rb
CHANGED
|
@@ -3,18 +3,18 @@ require "fileutils"
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "tmpdir"
|
|
5
5
|
|
|
6
|
-
version = "9.
|
|
6
|
+
version = "9.14.6206"
|
|
7
7
|
|
|
8
8
|
arch = RbConfig::CONFIG["host_cpu"]
|
|
9
9
|
arm = arch.match?(/arm|aarch64/i)
|
|
10
10
|
|
|
11
11
|
if RbConfig::CONFIG["host_os"].match?(/darwin/i)
|
|
12
12
|
if arm
|
|
13
|
-
filename = "or-tools_arm64_macOS-15.
|
|
14
|
-
checksum = "
|
|
13
|
+
filename = "or-tools_arm64_macOS-15.5_cpp_v#{version}.tar.gz"
|
|
14
|
+
checksum = "7dd3fc35acc74a85f44e39099dcc2caa698d7a99e659e8d8456ce25bafe4a63b"
|
|
15
15
|
else
|
|
16
|
-
filename = "or-tools_x86_64_macOS-15.
|
|
17
|
-
checksum = "
|
|
16
|
+
filename = "or-tools_x86_64_macOS-15.5_cpp_v#{version}.tar.gz"
|
|
17
|
+
checksum = "de7ed91b0fe90094fb5f5ebd19869b69a8d52b9752e456752208a22a05b14f7f"
|
|
18
18
|
end
|
|
19
19
|
else
|
|
20
20
|
# try /etc/os-release with fallback to /usr/lib/os-release
|
|
@@ -29,22 +29,22 @@ else
|
|
|
29
29
|
|
|
30
30
|
if os == "ubuntu" && os_version == "24.04" && !arm
|
|
31
31
|
filename = "or-tools_amd64_ubuntu-24.04_cpp_v#{version}.tar.gz"
|
|
32
|
-
checksum = "
|
|
32
|
+
checksum = "be3855a32a7390c3957d43ebd3faec1610acdc28f06ef33cb50f1f72a9aa6621"
|
|
33
33
|
elsif os == "ubuntu" && os_version == "22.04" && !arm
|
|
34
34
|
filename = "or-tools_amd64_ubuntu-22.04_cpp_v#{version}.tar.gz"
|
|
35
|
-
checksum = "
|
|
35
|
+
checksum = "127a82bbbf304d26721bb9b41ecce2d66f21c757204ab5aa2cc37eaa6ffb7eb6"
|
|
36
36
|
elsif os == "ubuntu" && os_version == "20.04" && !arm
|
|
37
37
|
filename = "or-tools_amd64_ubuntu-20.04_cpp_v#{version}.tar.gz"
|
|
38
|
-
checksum = "
|
|
39
|
-
elsif os == "debian" && os_version == "11" && !arm
|
|
40
|
-
filename = "or-tools_amd64_debian-11_cpp_v#{version}.tar.gz"
|
|
41
|
-
checksum = "dcee63b726569bd99c134e0e920173f955feae5856c3370a0bed03fdc995af50"
|
|
38
|
+
checksum = "7705a7c11e0db4ec1d7841e184acd204787174c6cbdb2fbd81169823ed148c6c"
|
|
42
39
|
elsif os == "debian" && os_version == "12" && !arm
|
|
43
40
|
filename = "or-tools_amd64_debian-12_cpp_v#{version}.tar.gz"
|
|
44
|
-
checksum = "
|
|
41
|
+
checksum = "285e8ec3a3399e45cdb4f67f48d4b65dbfa9c013b29036d409c72f96f0f34ab9"
|
|
42
|
+
elsif os == "debian" && os_version == "11" && !arm
|
|
43
|
+
filename = "or-tools_amd64_debian-11_cpp_v#{version}.tar.gz"
|
|
44
|
+
checksum = "646b53e8d355290c4627d6bad0d36baeff38dc43605d317ac02cb811688d4dd2"
|
|
45
45
|
elsif os == "arch" && !arm
|
|
46
46
|
filename = "or-tools_amd64_archlinux_cpp_v#{version}.tar.gz"
|
|
47
|
-
checksum = "
|
|
47
|
+
checksum = "6be039a13c3be7a3dbcdc413d455b43bba4590ce38859062898835effefb5ca4"
|
|
48
48
|
else
|
|
49
49
|
platform =
|
|
50
50
|
if Gem.win_platform?
|
|
@@ -127,14 +127,6 @@ Dir.mktmpdir do |extract_path|
|
|
|
127
127
|
tar_args = Gem.win_platform? ? ["--force-local"] : []
|
|
128
128
|
system "tar", "zxf", download_path, "-C", extract_path, "--strip-components=1", *tar_args
|
|
129
129
|
|
|
130
|
-
# licenses
|
|
131
|
-
license_files = Dir.glob("**/*{LICENSE,LICENCE,NOTICE,COPYING,license,licence,notice,copying}*", base: extract_path)
|
|
132
|
-
raise "License not found" unless license_files.any?
|
|
133
|
-
license_files.each do |file|
|
|
134
|
-
FileUtils.mkdir_p(File.join(path, File.dirname(file)))
|
|
135
|
-
FileUtils.mv(File.join(extract_path, file), File.join(path, file))
|
|
136
|
-
end
|
|
137
|
-
|
|
138
130
|
# include
|
|
139
131
|
FileUtils.mv(File.join(extract_path, "include"), File.join(path, "include"))
|
|
140
132
|
|
|
@@ -144,6 +136,14 @@ Dir.mktmpdir do |extract_path|
|
|
|
144
136
|
next if file.include?("libprotoc.")
|
|
145
137
|
FileUtils.mv(File.join(extract_path, file), File.join(path, file))
|
|
146
138
|
end
|
|
139
|
+
|
|
140
|
+
# licenses
|
|
141
|
+
license_files = Dir.glob("**/*{LICENSE,LICENCE,NOTICE,COPYING,license,licence,notice,copying}*", base: extract_path)
|
|
142
|
+
raise "License not found" unless license_files.any?
|
|
143
|
+
license_files.each do |file|
|
|
144
|
+
FileUtils.mkdir_p(File.join(path, File.dirname(file)))
|
|
145
|
+
FileUtils.mv(File.join(extract_path, file), File.join(path, file))
|
|
146
|
+
end
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
# export
|
data/lib/or_tools/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: or-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 4.
|
|
18
|
+
version: '4.7'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 4.
|
|
25
|
+
version: '4.7'
|
|
26
26
|
email: andrew@ankane.org
|
|
27
27
|
executables: []
|
|
28
28
|
extensions:
|
|
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
85
85
|
- !ruby/object:Gem::Version
|
|
86
86
|
version: '0'
|
|
87
87
|
requirements: []
|
|
88
|
-
rubygems_version: 3.6.
|
|
88
|
+
rubygems_version: 3.6.9
|
|
89
89
|
specification_version: 4
|
|
90
90
|
summary: Operations research tools for Ruby
|
|
91
91
|
test_files: []
|