pyepo 2.2.3__tar.gz → 2.2.4__tar.gz
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.
- {pyepo-2.2.3 → pyepo-2.2.4}/PKG-INFO +3 -3
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/bases.py +0 -24
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/grbmodel.py +2 -1
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/tsp.py +0 -3
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/vrp.py +0 -3
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/opt.py +41 -7
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo.egg-info/PKG-INFO +3 -3
- {pyepo-2.2.3 → pyepo-2.2.4}/pyproject.toml +3 -2
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_10_utils.py +40 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/LICENSE +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/README.md +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/EPO.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/_validation.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/dataset.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/knapsack.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/portfolio.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/shortestpath.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/data/tsp.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/dsl/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/dsl/compiled.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/dsl/expression.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/dsl/objective.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/dsl/problem.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/_common.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/abcmodule.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/blackbox.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/cave.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/contrastive.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/abcmodule.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/blackbox.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/cave.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/contrastive.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/perturbed.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/rank.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/regularized.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/surrogate.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/jax/utils.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/perturbed.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/rank.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/regularized.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/runtime.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/surrogate.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/func/utils.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/metric/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/metric/_common.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/metric/metrics.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/metric/mse.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/metric/regret.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/metric/unambregret.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/_common.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/compile.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/coptmodel.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/knapsack.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/portfolio.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/shortestpath.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/tsp.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/copt/vrp.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/compile.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/knapsack.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/portfolio.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/grb/shortestpath.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/mpax/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/mpax/compile.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/mpax/knapsack.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/mpax/mpaxmodel.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/mpax/shortestpath.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/compile.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/knapsack.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/omomodel.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/portfolio.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/shortestpath.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/tsp.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/omo/vrp.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/ort/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/ort/compile.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/ort/knapsack.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/ort/ortcpmodel.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/ort/ortmodel.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/ort/shortestpath.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/predefined.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/model/utils.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/py.typed +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/twostage/__init__.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/twostage/autosklearnpred.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/twostage/sklearnpred.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo/utils.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo.egg-info/SOURCES.txt +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo.egg-info/dependency_links.txt +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo.egg-info/requires.txt +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/pyepo.egg-info/top_level.txt +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/setup.cfg +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_00_constants.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_15_dsl.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_20_data_gen.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_30_model.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_40_dataset.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_50_func.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_55_jax.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_60_metric.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_61_metric_validation.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_70_twostage.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_80_integration.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_85_backend_pipeline.py +0 -0
- {pyepo-2.2.3 → pyepo-2.2.4}/tests/test_90_cuda.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyepo
|
|
3
|
-
Version: 2.2.
|
|
4
|
-
Summary: PyTorch-based End-to-End Predict-then-Optimize Tool
|
|
3
|
+
Version: 2.2.4
|
|
4
|
+
Summary: PyTorch/JAX-based End-to-End Predict-then-Optimize Tool
|
|
5
5
|
Author-email: Bo Tang <bolucas.tang@mail.utoronto.ca>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/khalil-research/PyEPO
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://khalil-research.github.io/PyEPO
|
|
|
9
9
|
Project-URL: Repository, https://github.com/khalil-research/PyEPO
|
|
10
10
|
Project-URL: Issues, https://github.com/khalil-research/PyEPO/issues
|
|
11
11
|
Project-URL: Paper, https://link.springer.com/article/10.1007/s12532-024-00255-x
|
|
12
|
-
Keywords: predict-then-optimize,end-to-end,decision-focused learning,optimization,deep learning,pytorch,linear programming,integer programming
|
|
12
|
+
Keywords: predict-then-optimize,end-to-end,decision-focused learning,optimization,deep learning,pytorch,jax,linear programming,integer programming
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -16,7 +16,6 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import math
|
|
18
18
|
from collections import defaultdict
|
|
19
|
-
from copy import deepcopy
|
|
20
19
|
from itertools import combinations
|
|
21
20
|
from numbers import Integral, Real
|
|
22
21
|
from typing import TYPE_CHECKING
|
|
@@ -105,9 +104,6 @@ class shortestPathBase(optModel):
|
|
|
105
104
|
self.arcs = _get_grid_arcs(self.grid)
|
|
106
105
|
super().__init__(*args, **kwargs)
|
|
107
106
|
|
|
108
|
-
def get_config(self) -> dict:
|
|
109
|
-
return {**super().get_config(), "grid": self.grid}
|
|
110
|
-
|
|
111
107
|
@property
|
|
112
108
|
def num_cost(self) -> int:
|
|
113
109
|
return len(self.arcs)
|
|
@@ -208,14 +204,6 @@ class portfolioBase(optModel):
|
|
|
208
204
|
raise ValueError("gamma must be greater than or equal to zero.")
|
|
209
205
|
super().__init__(*args, **kwargs)
|
|
210
206
|
|
|
211
|
-
def get_config(self) -> dict:
|
|
212
|
-
return {
|
|
213
|
-
**super().get_config(),
|
|
214
|
-
"num_assets": self.num_assets,
|
|
215
|
-
"covariance": self.covariance.copy(),
|
|
216
|
-
"gamma": self.gamma,
|
|
217
|
-
}
|
|
218
|
-
|
|
219
207
|
@property
|
|
220
208
|
def num_cost(self) -> int:
|
|
221
209
|
return self.num_assets
|
|
@@ -263,9 +251,6 @@ class tspABBase(optModel):
|
|
|
263
251
|
self._extra_constrs: list = []
|
|
264
252
|
super().__init__(*args, **kwargs)
|
|
265
253
|
|
|
266
|
-
def get_config(self) -> dict:
|
|
267
|
-
return {**super().get_config(), "num_nodes": self.num_nodes}
|
|
268
|
-
|
|
269
254
|
@property
|
|
270
255
|
def num_cost(self) -> int:
|
|
271
256
|
# use edges; backend's self.x has 2*num_edges directed Vars
|
|
@@ -381,15 +366,6 @@ class vrpABBase(optModel):
|
|
|
381
366
|
self._extra_constrs: list = []
|
|
382
367
|
super().__init__(*args, **kwargs)
|
|
383
368
|
|
|
384
|
-
def get_config(self) -> dict:
|
|
385
|
-
return {
|
|
386
|
-
**super().get_config(),
|
|
387
|
-
"num_nodes": self.num_nodes,
|
|
388
|
-
"demands": deepcopy(self.demands),
|
|
389
|
-
"capacity": self.capacity,
|
|
390
|
-
"num_vehicle": self.num_vehicle,
|
|
391
|
-
}
|
|
392
|
-
|
|
393
369
|
@property
|
|
394
370
|
def num_cost(self) -> int:
|
|
395
371
|
# one predicted cost per undirected edge
|
|
@@ -171,7 +171,8 @@ class optGrbModel(optModel):
|
|
|
171
171
|
else:
|
|
172
172
|
# LinExpr(coeffs, vars) builds the affine expression in one C call
|
|
173
173
|
vars_list = new_model._vars_list
|
|
174
|
-
|
|
174
|
+
if vars_list is None:
|
|
175
|
+
raise RuntimeError("Gurobi variable list is unavailable.")
|
|
175
176
|
expr = gp.LinExpr(coefs_np.tolist(), vars_list) <= rhs
|
|
176
177
|
new_model._model.addConstr(expr)
|
|
177
178
|
# track for replay on relax
|
|
@@ -178,9 +178,6 @@ class tspDFJModel(tspABModel):
|
|
|
178
178
|
self._recycled_keys: set = set()
|
|
179
179
|
super().__init__(num_nodes, *args, **kwargs)
|
|
180
180
|
|
|
181
|
-
def get_config(self) -> dict:
|
|
182
|
-
return {**super().get_config(), "recycle_cuts": self.recycle_cuts}
|
|
183
|
-
|
|
184
181
|
def _getModel(self) -> tuple:
|
|
185
182
|
"""
|
|
186
183
|
A method to build Gurobi model
|
|
@@ -70,9 +70,6 @@ class vrpRCIModel(vrpABModel):
|
|
|
70
70
|
self._recycled_keys: set = set()
|
|
71
71
|
super().__init__(num_nodes, demands, capacity, num_vehicle)
|
|
72
72
|
|
|
73
|
-
def get_config(self) -> dict:
|
|
74
|
-
return {**super().get_config(), "recycle_cuts": self.recycle_cuts}
|
|
75
|
-
|
|
76
73
|
def _getModel(self) -> tuple:
|
|
77
74
|
"""
|
|
78
75
|
A method to build Gurobi model
|
|
@@ -5,6 +5,8 @@ Abstract optimization model
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import functools
|
|
9
|
+
import inspect
|
|
8
10
|
from abc import ABC, abstractmethod
|
|
9
11
|
from copy import deepcopy
|
|
10
12
|
from dataclasses import dataclass
|
|
@@ -41,6 +43,27 @@ class ModelSpec:
|
|
|
41
43
|
return self.model_type.from_config(self._config)
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
def _capture_init_config(init, args, kwargs) -> dict:
|
|
47
|
+
"""Flatten a constructor call into keyword arguments that rebuild the model."""
|
|
48
|
+
sig = inspect.signature(init)
|
|
49
|
+
bound = sig.bind(None, *args, **kwargs)
|
|
50
|
+
config = {}
|
|
51
|
+
for i, (name, value) in enumerate(bound.arguments.items()):
|
|
52
|
+
# the first bound argument is self
|
|
53
|
+
if i == 0:
|
|
54
|
+
continue
|
|
55
|
+
kind = sig.parameters[name].kind
|
|
56
|
+
# **kwargs: merge captured keywords in directly
|
|
57
|
+
if kind is inspect.Parameter.VAR_KEYWORD:
|
|
58
|
+
config.update(deepcopy(value))
|
|
59
|
+
# nameless positionals cannot replay by keyword; override get_config to keep them
|
|
60
|
+
elif kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.POSITIONAL_ONLY):
|
|
61
|
+
continue
|
|
62
|
+
else:
|
|
63
|
+
config[name] = deepcopy(value)
|
|
64
|
+
return config
|
|
65
|
+
|
|
66
|
+
|
|
44
67
|
class optModel(ABC):
|
|
45
68
|
"""
|
|
46
69
|
Abstract base class for predict-then-optimize models.
|
|
@@ -53,11 +76,6 @@ class optModel(ABC):
|
|
|
53
76
|
and MPAX (``optMpaxModel``); subclass ``optModel`` directly to integrate
|
|
54
77
|
any other solver or algorithm.
|
|
55
78
|
|
|
56
|
-
Models that take constructor arguments should override ``get_config`` and
|
|
57
|
-
cooperatively merge ``super().get_config()``. The resulting configuration
|
|
58
|
-
powers ``rebuild()``, multiprocessing workers, and sklearn scorers without
|
|
59
|
-
inspecting constructor signatures or runtime solver state.
|
|
60
|
-
|
|
61
79
|
The default objective sense is minimization; set
|
|
62
80
|
``self.modelSense = EPO.MAXIMIZE`` in ``_getModel`` or ``__init__`` for
|
|
63
81
|
maximization problems (some backends, e.g. Gurobi and COPT, detect this
|
|
@@ -73,6 +91,22 @@ class optModel(ABC):
|
|
|
73
91
|
arcs: list
|
|
74
92
|
_cost_vars: list
|
|
75
93
|
|
|
94
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
95
|
+
super().__init_subclass__(**kwargs)
|
|
96
|
+
# only wrap a subclass that defines its own __init__
|
|
97
|
+
if "__init__" not in cls.__dict__:
|
|
98
|
+
return
|
|
99
|
+
user_init = cls.__init__
|
|
100
|
+
|
|
101
|
+
@functools.wraps(user_init)
|
|
102
|
+
def _init_capturing(self, *args, **kwargs):
|
|
103
|
+
# record only the outermost call; nested super().__init__ leaves it intact
|
|
104
|
+
if "_init_config" not in self.__dict__:
|
|
105
|
+
self._init_config = _capture_init_config(user_init, args, kwargs)
|
|
106
|
+
user_init(self, *args, **kwargs)
|
|
107
|
+
|
|
108
|
+
cls.__init__ = _init_capturing
|
|
109
|
+
|
|
76
110
|
def __init__(self) -> None:
|
|
77
111
|
# Cache for models whose solver variables do not map one-to-one to
|
|
78
112
|
# predicted costs (for example directed TSP/VRP formulations).
|
|
@@ -86,8 +120,8 @@ class optModel(ABC):
|
|
|
86
120
|
return "optModel " + self.__class__.__name__
|
|
87
121
|
|
|
88
122
|
def get_config(self) -> dict:
|
|
89
|
-
"""Return the
|
|
90
|
-
return {}
|
|
123
|
+
"""Return the constructor configuration for this model."""
|
|
124
|
+
return deepcopy(self.__dict__.get("_init_config", {}))
|
|
91
125
|
|
|
92
126
|
@classmethod
|
|
93
127
|
def from_config(cls, config: dict) -> Self:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyepo
|
|
3
|
-
Version: 2.2.
|
|
4
|
-
Summary: PyTorch-based End-to-End Predict-then-Optimize Tool
|
|
3
|
+
Version: 2.2.4
|
|
4
|
+
Summary: PyTorch/JAX-based End-to-End Predict-then-Optimize Tool
|
|
5
5
|
Author-email: Bo Tang <bolucas.tang@mail.utoronto.ca>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/khalil-research/PyEPO
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://khalil-research.github.io/PyEPO
|
|
|
9
9
|
Project-URL: Repository, https://github.com/khalil-research/PyEPO
|
|
10
10
|
Project-URL: Issues, https://github.com/khalil-research/PyEPO/issues
|
|
11
11
|
Project-URL: Paper, https://link.springer.com/article/10.1007/s12532-024-00255-x
|
|
12
|
-
Keywords: predict-then-optimize,end-to-end,decision-focused learning,optimization,deep learning,pytorch,linear programming,integer programming
|
|
12
|
+
Keywords: predict-then-optimize,end-to-end,decision-focused learning,optimization,deep learning,pytorch,jax,linear programming,integer programming
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyepo"
|
|
7
|
-
version = "2.2.
|
|
8
|
-
description = "PyTorch-based End-to-End Predict-then-Optimize Tool"
|
|
7
|
+
version = "2.2.4"
|
|
8
|
+
description = "PyTorch/JAX-based End-to-End Predict-then-Optimize Tool"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
authors = [{ name = "Bo Tang", email = "bolucas.tang@mail.utoronto.ca" }]
|
|
@@ -17,6 +17,7 @@ keywords = [
|
|
|
17
17
|
"optimization",
|
|
18
18
|
"deep learning",
|
|
19
19
|
"pytorch",
|
|
20
|
+
"jax",
|
|
20
21
|
"linear programming",
|
|
21
22
|
"integer programming",
|
|
22
23
|
]
|
|
@@ -43,6 +43,24 @@ class ConfigModel(optModel):
|
|
|
43
43
|
return np.zeros(len(self.values)), 0.0
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
class AutoConfigModel(optModel):
|
|
47
|
+
"""Solver-free model that relies on optModel's automatic config capture."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, values, **kwargs):
|
|
50
|
+
self.values = values
|
|
51
|
+
self.kwargs = kwargs
|
|
52
|
+
super().__init__()
|
|
53
|
+
|
|
54
|
+
def _getModel(self):
|
|
55
|
+
return None, list(range(len(self.values)))
|
|
56
|
+
|
|
57
|
+
def setObj(self, c):
|
|
58
|
+
self.cost = np.asarray(c)
|
|
59
|
+
|
|
60
|
+
def solve(self):
|
|
61
|
+
return np.zeros(len(self.values)), 0.0
|
|
62
|
+
|
|
63
|
+
|
|
46
64
|
# ============================================================
|
|
47
65
|
# unionFind (pure)
|
|
48
66
|
# ============================================================
|
|
@@ -222,6 +240,28 @@ class TestModelSpec:
|
|
|
222
240
|
np.testing.assert_array_equal(sol, [0.0, 0.0, 0.0])
|
|
223
241
|
assert obj == 0.0
|
|
224
242
|
|
|
243
|
+
def test_auto_config_snapshots_constructor_inputs(self):
|
|
244
|
+
values = [1, 2, 3]
|
|
245
|
+
nested = {"tag": ["x"]}
|
|
246
|
+
model = AutoConfigModel(values, nested=nested)
|
|
247
|
+
values[0] = 99
|
|
248
|
+
nested["tag"][0] = "changed"
|
|
249
|
+
|
|
250
|
+
rebuilt = model.rebuild()
|
|
251
|
+
|
|
252
|
+
assert rebuilt.values == [1, 2, 3]
|
|
253
|
+
assert rebuilt.kwargs == {"nested": {"tag": ["x"]}}
|
|
254
|
+
|
|
255
|
+
def test_auto_config_export_is_independent(self):
|
|
256
|
+
model = AutoConfigModel([1, 2, 3], nested={"tag": ["x"]})
|
|
257
|
+
config = model.get_config()
|
|
258
|
+
config["values"][0] = 99
|
|
259
|
+
config["nested"]["tag"][0] = "changed"
|
|
260
|
+
|
|
261
|
+
assert model.get_config()["values"] == [1, 2, 3]
|
|
262
|
+
assert model.get_config()["nested"] == {"tag": ["x"]}
|
|
263
|
+
assert model.rebuild().kwargs == {"nested": {"tag": ["x"]}}
|
|
264
|
+
|
|
225
265
|
|
|
226
266
|
# ============================================================
|
|
227
267
|
# getArgs (needs a real optModel)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|