stitchlab-optimization 0.0.4__tar.gz → 0.1.0__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.
Files changed (28) hide show
  1. {stitchlab_optimization-0.0.4/src/stitchlab_optimization.egg-info → stitchlab_optimization-0.1.0}/PKG-INFO +1 -1
  2. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/pyproject.toml +1 -1
  3. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/__init__.py +1 -1
  4. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/builder/model.py +81 -78
  5. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/builder/workflow.py +5 -5
  6. stitchlab_optimization-0.1.0/src/stitchlab_optimization/logger/json_logger.py +46 -0
  7. stitchlab_optimization-0.1.0/src/stitchlab_optimization/logger/manager.py +221 -0
  8. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/logger/sqlite_logger.py +14 -26
  9. stitchlab_optimization-0.1.0/src/stitchlab_optimization/solver/params.py +15 -0
  10. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0/src/stitchlab_optimization.egg-info}/PKG-INFO +1 -1
  11. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization.egg-info/SOURCES.txt +2 -2
  12. stitchlab_optimization-0.0.4/src/stitchlab_optimization/logger/file_logger.py +0 -120
  13. stitchlab_optimization-0.0.4/src/stitchlab_optimization/logger/manager.py +0 -94
  14. stitchlab_optimization-0.0.4/src/stitchlab_optimization/solver/config.py +0 -63
  15. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/LICENSE +0 -0
  16. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/README.md +0 -0
  17. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/setup.cfg +0 -0
  18. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/builder/__init__.py +0 -0
  19. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/logger/__init__.py +0 -0
  20. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/solver/__init__.py +0 -0
  21. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/solver/engine.py +0 -0
  22. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/solver/status.py +0 -0
  23. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/tools/query/snowflake.py +0 -0
  24. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/tools/query/sqlite.py +0 -0
  25. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization/tools/utils.py +0 -0
  26. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization.egg-info/dependency_links.txt +0 -0
  27. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization.egg-info/requires.txt +0 -0
  28. {stitchlab_optimization-0.0.4 → stitchlab_optimization-0.1.0}/src/stitchlab_optimization.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stitchlab-optimization
3
- Version: 0.0.4
3
+ Version: 0.1.0
4
4
  Summary: StitchLab Optimization application
5
5
  Author-email: StitchLab Team <evazuliya97@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stitchlab-optimization"
3
- version = "0.0.4"
3
+ version = "0.1.0"
4
4
  description = "StitchLab Optimization application"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  """StitchLab Agent Core - A powerful agent core application for AI development."""
2
2
 
3
- __version__ = "0.0.4"
3
+ __version__ = "0.1.0"
4
4
  __author__ = "StitchLab Team"
5
5
  __license__ = "MIT"
6
6
 
@@ -11,8 +11,8 @@ from typing import Any, Dict, Generic, Type, Optional, TypeVar, final
11
11
 
12
12
  from ..solver.engine import SolverEngine
13
13
  from ..solver.status import SolverStatus
14
- from ..logger.manager import ModelLog, LogManager
15
- from ..solver.config import SolverConfig
14
+ from ..solver.params import SolverParams
15
+ from ..logger.manager import ModelLog, LogManager, ResourceMonitor, NullResourceMonitor
16
16
 
17
17
 
18
18
  ParamsBaseModel = TypeVar("ParamsBaseModel", bound="ModelParams")
@@ -51,17 +51,19 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
51
51
  solution: Optional[SolutionBaseModel] = None
52
52
  solver_engine: SolverEngine
53
53
  solver_status: SolverStatus
54
+ solver_params: SolverParams
54
55
  model: Any = None
55
56
  model_output: Any = None
56
- model_vars: Optional[Dict[str, Any]] = None # Mandatory for OR-Tools CPSAT
57
+ model_vars: Optional[Dict[str, Any]] = None
57
58
  runtime_message: str = ""
58
59
  runtime_seconds: float = 0
59
60
 
60
61
  @final
61
- def __init__(self, params: ParamsBaseModel, solver_engine: SolverEngine):
62
+ def __init__(self, params: ParamsBaseModel, solver_engine: SolverEngine, solver_params: SolverParams):
62
63
  self.params = params
63
64
  self.solver_engine = solver_engine
64
65
  self.solver_status = SolverStatus.UNSOLVED
66
+ self.solver_params = solver_params
65
67
 
66
68
  @final
67
69
  def _set_model(self, model: Any):
@@ -78,13 +80,15 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
78
80
  if self.model is None:
79
81
  raise ValueError("Model must be built before execution.")
80
82
 
81
- if self.solver_engine == SolverEngine.ORTOOLS_CPSAT and self.model_vars is None:
82
- raise ValueError("Model variables (model_vars) must be set in the builder when using OR-Tools CPSAT.")
83
+ if self.model_vars is None:
84
+ raise ValueError("Model variables (model_vars) must be set in the builder before execution.")
83
85
 
84
86
  self.solve()
85
- self.solution = self.construct_solution()
87
+
88
+ if SolverStatus.is_solution_found(self.solver_status):
89
+ return self.construct_solution()
86
90
 
87
- return self.solution
91
+ return None
88
92
 
89
93
  @abstractmethod
90
94
  def build(self):
@@ -112,42 +116,38 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
112
116
 
113
117
  raise ValueError(f"ERROR while Solving Model : Vars is not saved while building model using solver engine {self.solver_engine}")
114
118
 
115
- if self.solver_engine == SolverEngine.PYSCIPOPT:
116
- self.solve_pyscipopt()
117
-
118
- elif self.solver_engine == SolverEngine.GUROBI:
119
- self.solve_gurobi()
120
-
121
- elif self.solver_engine == SolverEngine.ORTOOLS_SCIP:
122
- self.solve_ortools_scip()
119
+ SOLVER = {
120
+ SolverEngine.PYSCIPOPT: self.solve_pyscipopt,
121
+ SolverEngine.GUROBI: self.solve_gurobi,
122
+ SolverEngine.ORTOOLS_SCIP: self.solve_ortools_scip,
123
+ SolverEngine.ORTOOLS_ROUTING: self.solve_ortools_routing,
124
+ SolverEngine.ORTOOLS_CPSAT: self.solve_ortools_cpsat,
125
+ }
123
126
 
124
- elif self.solver_engine == SolverEngine.ORTOOLS_ROUTING:
125
- self.solve_ortools_routing()
126
-
127
- elif self.solver_engine == SolverEngine.ORTOOLS_CPSAT:
128
- self.solve_ortools_cpsat()
129
-
130
- else:
127
+ # with ResourceMonitor() as monitor:
128
+ try:
129
+ SOLVER[self.solver_engine]()
130
+ except KeyError:
131
131
  raise ValueError(f"Solver engine {self.solver_engine} not supported")
132
132
 
133
133
  def solve_pyscipopt(self):
134
- SOLVER_CONFIG = SolverConfig()
134
+ PARAMS = self.solver_params
135
135
  start_sol = None
136
136
 
137
- self.model.setParam("display/verblevel", SOLVER_CONFIG.MODEL_SOLVER_VERBOSE)
137
+ self.model.setParam("display/verblevel", PARAMS.MODEL_SOLVER_VERBOSE)
138
138
 
139
- self.model.setIntParam("parallel/maxnthreads", SOLVER_CONFIG.LIMIT_MULTI_THREAD)
140
- self.model.setIntParam("parallel/minnthreads", SOLVER_CONFIG.LIMIT_MULTI_THREAD)
139
+ self.model.setIntParam("parallel/maxnthreads", PARAMS.LIMIT_MULTI_THREAD)
140
+ self.model.setIntParam("parallel/minnthreads", PARAMS.LIMIT_MULTI_THREAD)
141
141
 
142
- if SOLVER_CONFIG.APPLY_HEURISTICS:
142
+ if PARAMS.APPLY_HEURISTICS:
143
143
  # Phase 1: Heuristics only
144
144
  self.model.setHeuristics(SCIP_PARAMSETTING.AGGRESSIVE)
145
145
 
146
- self.model.setParam("limits/time", SOLVER_CONFIG.LIMIT_TIME_MINUTES_HEURISTICS*60)
147
- self.model.setParam("limits/gap", SOLVER_CONFIG.LIMIT_OPTIMALITY_GAP_HEURISTICS)
146
+ self.model.setParam("limits/time", PARAMS.LIMIT_TIME_MINUTES_HEURISTICS*60)
147
+ self.model.setParam("limits/gap", PARAMS.LIMIT_OPTIMALITY_GAP_HEURISTICS)
148
148
  self.model.setParam("limits/nodes", 500) # limit nodes so B&B doesn't go far
149
149
  self.model.setParam("presolving/maxrounds", 0) # skip heavy presolve if desired
150
- self.model.setParam("limits/memory", SOLVER_CONFIG.LIMIT_MEMORY_MB)
150
+ self.model.setParam("limits/memory", PARAMS.LIMIT_MEMORY_MB)
151
151
 
152
152
  self.model.optimize()
153
153
 
@@ -162,9 +162,9 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
162
162
  self.model.resetParams()
163
163
  self.model.setHeuristics(SCIP_PARAMSETTING.DEFAULT)
164
164
 
165
- self.model.setParam("limits/time", SOLVER_CONFIG.LIMIT_TIME_MINUTES_DETERMINISTIC*60)
166
- self.model.setParam("limits/gap", SOLVER_CONFIG.LIMIT_OPTIMALITY_GAP_DETERMINISTIC)
167
- self.model.setParam("limits/memory", SOLVER_CONFIG.LIMIT_MEMORY_MB)
165
+ self.model.setParam("limits/time", PARAMS.LIMIT_TIME_MINUTES_DETERMINISTIC*60)
166
+ self.model.setParam("limits/gap", PARAMS.LIMIT_OPTIMALITY_GAP_DETERMINISTIC)
167
+ self.model.setParam("limits/memory", PARAMS.LIMIT_MEMORY_MB)
168
168
 
169
169
  try:
170
170
  if start_sol is not None:
@@ -182,23 +182,20 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
182
182
 
183
183
  self.solver_status = SolverStatus.from_pyscipopt_status(self.model.getStatus())
184
184
  print("STATUS", self.model.getStatus(), self.solver_status, "\n\n")
185
-
186
- if SolverStatus.is_solution_found(self.solver_status):
187
- self.construct_solution()
188
-
185
+
189
186
  def solve_gurobi(self):
190
- SOLVER_CONFIG = SolverConfig()
187
+ PARAMS = self.solver_params
191
188
 
192
- self.model.setParam('OutputFlag', SOLVER_CONFIG.MODEL_SOLVER_VERBOSE)
189
+ self.model.setParam('OutputFlag', PARAMS.MODEL_SOLVER_VERBOSE)
193
190
 
194
191
  start_sol = None
195
- if SOLVER_CONFIG.APPLY_HEURISTICS:
192
+ if PARAMS.APPLY_HEURISTICS:
196
193
  # Phase 1: Heuristics only
197
- self.model.setParam('TimeLimit', SOLVER_CONFIG.LIMIT_TIME_MINUTES_HEURISTICS * 60)
198
- self.model.setParam('MIPGap', SOLVER_CONFIG.LIMIT_OPTIMALITY_GAP_HEURISTICS)
194
+ self.model.setParam('TimeLimit', PARAMS.LIMIT_TIME_MINUTES_HEURISTICS * 60)
195
+ self.model.setParam('MIPGap', PARAMS.LIMIT_OPTIMALITY_GAP_HEURISTICS)
199
196
  self.model.setParam('NodeLimit', 500) # limit nodes so B&B doesn't go far
200
197
  self.model.setParam('Presolve', 0) # skip heavy presolve if desired
201
- self.model.setParam('Threads', SOLVER_CONFIG.LIMIT_MULTI_THREAD)
198
+ self.model.setParam('Threads', PARAMS.LIMIT_MULTI_THREAD)
202
199
 
203
200
  # Set heuristic focus
204
201
  self.model.setParam('Heuristics', 0.8) # Aggressive heuristics
@@ -216,12 +213,12 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
216
213
 
217
214
  # Phase 2: Exact MILP solving
218
215
  # Reset parameters for exact solving
219
- self.model.setParam('TimeLimit', SOLVER_CONFIG.LIMIT_TIME_MINUTES_DETERMINISTIC * 60)
220
- self.model.setParam('MIPGap', SOLVER_CONFIG.LIMIT_OPTIMALITY_GAP_DETERMINISTIC)
216
+ self.model.setParam('TimeLimit', PARAMS.LIMIT_TIME_MINUTES_DETERMINISTIC * 60)
217
+ self.model.setParam('MIPGap', PARAMS.LIMIT_OPTIMALITY_GAP_DETERMINISTIC)
221
218
  self.model.setParam('NodeLimit', 1000000)
222
219
  self.model.setParam('Presolve', -1) # Default presolve
223
220
  self.model.setParam('Heuristics', 0.05) # Default heuristics
224
- self.model.setParam('Threads', SOLVER_CONFIG.LIMIT_MULTI_THREAD)
221
+ self.model.setParam('Threads', PARAMS.LIMIT_MULTI_THREAD)
225
222
 
226
223
  try:
227
224
  if start_sol is not None:
@@ -237,11 +234,8 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
237
234
  self.solver_status = SolverStatus.from_gurobi_status(self.model.status)
238
235
  print("STATUS", self.model.status, self.solver_status, "\n\n")
239
236
 
240
- if SolverStatus.is_solution_found(self.solver_status):
241
- self.construct_solution()
242
-
243
237
  def solve_ortools_routing(self):
244
- SOLVER_CONFIG = SolverConfig()
238
+ PARAMS = self.solver_params
245
239
 
246
240
  search_parameters = pywrapcp.DefaultRoutingSearchParameters()
247
241
  search_parameters.first_solution_strategy = (
@@ -253,24 +247,21 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
253
247
  )
254
248
 
255
249
  search_parameters.solution_limit = 100
256
- search_parameters.time_limit.seconds = int(SOLVER_CONFIG.LIMIT_TIME_MINUTES_DETERMINISTIC * 60)
250
+ search_parameters.time_limit.seconds = int(PARAMS.LIMIT_TIME_MINUTES_DETERMINISTIC * 60)
257
251
 
258
252
  self.solution = self.model.SolveWithParameters(search_parameters)
259
253
 
260
254
  self.solver_status = SolverStatus.from_ortools_routing_status(self.model.status())
261
255
  print("STATUS", self.solver_status)
262
256
 
263
- if SolverStatus.is_solution_found(self.solver_status):
264
- self.construct_solution()
265
-
266
257
  def solve_ortools_cpsat(self):
267
- SOLVER_CONFIG = SolverConfig()
258
+ PARAMS = self.solver_params
268
259
 
269
260
  solver = cp_model.CpSolver()
270
- solver.parameters.max_time_in_seconds = SOLVER_CONFIG.LIMIT_TIME_MINUTES_DETERMINISTIC * 60
271
- solver.parameters.num_search_workers = SOLVER_CONFIG.LIMIT_MULTI_THREAD
261
+ solver.parameters.max_time_in_seconds = PARAMS.LIMIT_TIME_MINUTES_DETERMINISTIC * 60
262
+ solver.parameters.num_search_workers = PARAMS.LIMIT_MULTI_THREAD
272
263
 
273
- solver.parameters.log_search_progress = SOLVER_CONFIG.MODEL_SOLVER_VERBOSE
264
+ solver.parameters.log_search_progress = PARAMS.MODEL_SOLVER_VERBOSE
274
265
 
275
266
  result_status = solver.Solve(self.model)
276
267
  self.model_output = solver
@@ -278,20 +269,17 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
278
269
  self.solver_status = SolverStatus.from_ortools_cpsat_status(result_status)
279
270
  print("STATUS", result_status, self.solver_status)
280
271
 
281
- if SolverStatus.is_solution_found(self.solver_status):
282
- self.construct_solution()
283
-
284
272
  def solve_ortools_scip(self):
285
- SOLVER_CONFIG = SolverConfig()
273
+ PARAMS = self.solver_params
286
274
 
287
- self.model.SetTimeLimit(int(SOLVER_CONFIG.LIMIT_TIME_MINUTES_DETERMINISTIC * 60 * 1000))
288
- self.model.SetNumThreads(int(SOLVER_CONFIG.LIMIT_MULTI_THREAD))
275
+ self.model.SetTimeLimit(int(PARAMS.LIMIT_TIME_MINUTES_DETERMINISTIC * 60 * 1000))
276
+ self.model.SetNumThreads(int(PARAMS.LIMIT_MULTI_THREAD))
289
277
 
290
278
  params_str = (
291
- f"limits/gap={SOLVER_CONFIG.LIMIT_OPTIMALITY_GAP_DETERMINISTIC}\n"
292
- f"limits/memory={SOLVER_CONFIG.LIMIT_MEMORY_MB}\n"
293
- f"parallel/maxnthreads={int(SOLVER_CONFIG.LIMIT_MULTI_THREAD)}\n"
294
- f"lp/threads={int(SOLVER_CONFIG.LIMIT_MULTI_THREAD)}\n"
279
+ f"limits/gap={PARAMS.LIMIT_OPTIMALITY_GAP_DETERMINISTIC}\n"
280
+ f"limits/memory={PARAMS.LIMIT_MEMORY_MB}\n"
281
+ f"parallel/maxnthreads={int(PARAMS.LIMIT_MULTI_THREAD)}\n"
282
+ f"lp/threads={int(PARAMS.LIMIT_MULTI_THREAD)}\n"
295
283
  f"display/verblevel=5\n"
296
284
  )
297
285
 
@@ -301,9 +289,6 @@ class ModelBuilder(Generic[ParamsBaseModel, SolutionBaseModel], ABC):
301
289
  self.solver_status = SolverStatus.from_ortools_scip_status(status)
302
290
  print("STATUS", status, self.solver_status, "\n\n")
303
291
 
304
- if SolverStatus.is_solution_found(self.solver_status):
305
- self.construct_solution()
306
-
307
292
 
308
293
  class OptimizationModel(Generic[ParamsBaseModel, SolutionBaseModel], ABC, metaclass=ModelMeta):
309
294
  id: str
@@ -311,15 +296,19 @@ class OptimizationModel(Generic[ParamsBaseModel, SolutionBaseModel], ABC, metacl
311
296
  builders_registry: Dict[SolverEngine, Type[ModelBuilder[ParamsBaseModel, SolutionBaseModel]]]
312
297
  builder: ModelBuilder[ParamsBaseModel, SolutionBaseModel]
313
298
 
314
- def __init__(self, params: ParamsBaseModel, solver_engine: Optional[SolverEngine] = None):
299
+ def __init__(self, params: ParamsBaseModel, solver_engine: Optional[SolverEngine] = None, solver_params: Optional[SolverParams] = None):
315
300
  self.id = str(uuid.uuid4())
316
301
 
317
302
  if solver_engine is None or solver_engine not in self.builders_registry.keys():
318
303
  solver_engine = next(iter(self.builders_registry.keys()))
319
304
 
305
+ if solver_params is None:
306
+ solver_params = SolverParams()
307
+
320
308
  self.builder = self.builders_registry[solver_engine](
321
309
  params=params,
322
- solver_engine=solver_engine
310
+ solver_engine=solver_engine,
311
+ solver_params=solver_params
323
312
  )
324
313
 
325
314
  @final
@@ -327,9 +316,18 @@ class OptimizationModel(Generic[ParamsBaseModel, SolutionBaseModel], ABC, metacl
327
316
  start_time = time.time()
328
317
  solution = None
329
318
 
319
+ monitor = (
320
+ ResourceMonitor(
321
+ interval_seconds=logger.monitor_resource_interval_seconds
322
+ )
323
+ if logger and logger.enable_monitor_resource
324
+ else NullResourceMonitor()
325
+ )
326
+
330
327
  try :
331
- solution = self.builder.execute()
332
- self.builder.runtime_message = "success"
328
+ with monitor:
329
+ solution = self.builder.execute()
330
+ self.builder.runtime_message = "success"
333
331
 
334
332
  except Exception as e:
335
333
  print(f"\033[91m\n>>> ERROR while Solving Model : {e}\n\033[0m")
@@ -339,8 +337,11 @@ class OptimizationModel(Generic[ParamsBaseModel, SolutionBaseModel], ABC, metacl
339
337
  end_time = time.time()
340
338
  self.builder.runtime_seconds = end_time - start_time
341
339
 
342
- if logger is not None and logger.is_monitor_optimality:
343
- logger.put_model_log(model_log=self._model_log)
340
+ if logger is not None and logger.enable_monitor_optimality:
341
+ log = self._model_log
342
+ log.resource_stats = monitor.stats
343
+
344
+ logger.put_model_log(model_log=log)
344
345
 
345
346
  return solution
346
347
 
@@ -354,6 +355,7 @@ class OptimizationModel(Generic[ParamsBaseModel, SolutionBaseModel], ABC, metacl
354
355
  def _model_log(self) -> ModelLog:
355
356
  builder = self.builder
356
357
  solver_engine = self.builder.solver_engine
358
+ solver_params = self.builder.solver_params
357
359
 
358
360
  if solver_engine == SolverEngine.GUROBI:
359
361
  # Gurobi Python API
@@ -427,6 +429,7 @@ class OptimizationModel(Generic[ParamsBaseModel, SolutionBaseModel], ABC, metacl
427
429
 
428
430
  return ModelLog(
429
431
  solver_engine=solver_engine,
432
+ solver_params=solver_params,
430
433
  model_id=self.id,
431
434
  model_name=self.name,
432
435
  status=builder.solver_status,
@@ -10,7 +10,7 @@ import traceback
10
10
  from ..logger.manager import LogManager, WorkflowLog
11
11
  from ..builder.model import OptimizationModel
12
12
  from ..solver.engine import SolverEngine
13
- from ..solver.config import SolverConfig
13
+ from ..solver.params import SolverParams
14
14
 
15
15
 
16
16
  InputBaseModel = TypeVar("InputBaseModel", bound=BaseModel)
@@ -105,7 +105,7 @@ class OptimizationWorkflow(Generic[InputBaseModel, OutputBaseModel], ABC, metacl
105
105
  self.runtime_seconds = time.time() - start_time
106
106
  self.end_timestamp = datetime.now(timezone.utc).isoformat()
107
107
 
108
- if self._logger is not None and self._logger.is_monitor_runtime:
108
+ if self._logger is not None and self._logger.enable_monitor_runtime:
109
109
  self._logger.put_workflow_log(workflow_log=self._workflow_log)
110
110
 
111
111
  if self.runtime_message == "success" and result is not None:
@@ -118,14 +118,15 @@ class OptimizationWorkflow(Generic[InputBaseModel, OutputBaseModel], ABC, metacl
118
118
  pass
119
119
 
120
120
  @final
121
- def execute_model(self, model_name: str, params: Any, solver_engine: Optional[SolverEngine] = None) -> Any:
121
+ def execute_model(self, model_name: str, params: Any, solver_engine: Optional[SolverEngine] = None, solver_params: Optional[SolverParams] = None) -> Any:
122
122
  if model_name not in self.models_registry:
123
123
  raise ValueError(f"Model '{model_name}' not found in models_registry.")
124
124
 
125
125
  model_cls = self.models_registry[model_name]
126
126
  model_instance = model_cls(
127
127
  params=params,
128
- solver_engine=solver_engine
128
+ solver_engine=solver_engine,
129
+ solver_params=solver_params
129
130
  )
130
131
 
131
132
  output = model_instance.execute(logger=self._logger)
@@ -140,7 +141,6 @@ class OptimizationWorkflow(Generic[InputBaseModel, OutputBaseModel], ABC, metacl
140
141
  workflow_name=self.name,
141
142
  model_ids_execution=self.model_ids_execution,
142
143
  payload=self.payload.model_dump(),
143
- solver_parameter=SolverConfig().SOLVER_PARAMETER,
144
144
  message=self.runtime_message,
145
145
  start_timestamp=self.start_timestamp,
146
146
  end_timestamp=self.end_timestamp,
@@ -0,0 +1,46 @@
1
+ from .manager import LogManager, ModelLog, WorkflowLog
2
+ from pathlib import Path
3
+
4
+
5
+ class JSONLogManager(LogManager):
6
+ directory_path: str
7
+
8
+ def __init__(
9
+ self, directory_path: str,
10
+ enable_monitor_optimality: bool = True,
11
+ enable_monitor_runtime: bool = True,
12
+ enable_monitor_resource: bool = False
13
+ ):
14
+ self.directory_path = directory_path
15
+ self.enable_monitor_optimality = enable_monitor_optimality
16
+ self.enable_monitor_resource = enable_monitor_resource
17
+ self.enable_monitor_runtime = enable_monitor_runtime
18
+
19
+ self.init_directory()
20
+
21
+ def init_directory(self):
22
+ if self.enable_monitor_optimality:
23
+ Path(
24
+ f"{self.directory_path}/{self._dir_model_execution_log}"
25
+ ).mkdir(parents=True, exist_ok=True)
26
+
27
+ if self.enable_monitor_runtime:
28
+ Path(
29
+ f"{self.directory_path}/{self._dir_workflow_execution_log}"
30
+ ).mkdir(parents=True, exist_ok=True)
31
+
32
+ def put_model_log(self, model_log: ModelLog):
33
+ file_path = Path(f"{self.directory_path}/{self._dir_model_execution_log}/{model_log.model_id}.json")
34
+
35
+ file_path.write_text(
36
+ model_log.model_dump_json(indent=2),
37
+ encoding="utf-8"
38
+ )
39
+
40
+ def put_workflow_log(self, workflow_log: WorkflowLog):
41
+ file_path = Path(f"{self.directory_path}/{self._dir_workflow_execution_log}/{workflow_log.workflow_id}.json")
42
+
43
+ file_path.write_text(
44
+ workflow_log.model_dump_json(indent=2),
45
+ encoding="utf-8"
46
+ )
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+ from abc import abstractmethod, ABC
3
+ from pydantic import BaseModel
4
+ from typing import Optional
5
+ import json
6
+ import os
7
+ import time
8
+ import psutil
9
+ import threading
10
+ from statistics import mean
11
+
12
+ from ..solver.engine import SolverEngine
13
+ from ..solver.status import SolverStatus
14
+ from ..solver.params import SolverParams
15
+
16
+
17
+ class ResourceStats(BaseModel):
18
+ peak_cpu_percent: float
19
+ avg_cpu_percent: float
20
+ peak_memory_mb: float
21
+ avg_memory_mb: float
22
+ max_num_threads: int
23
+
24
+
25
+ class ResourceSnapshot(BaseModel):
26
+ timestamp: float
27
+ cpu_percent: float
28
+ memory_mb: float
29
+ num_threads: int
30
+
31
+
32
+ class ModelLog(BaseModel):
33
+ solver_engine: SolverEngine
34
+ solver_params: SolverParams
35
+ model_id: str
36
+ model_name: str
37
+ status: SolverStatus
38
+ problem_size_vars: Optional[int]
39
+ problem_size_cons: Optional[int]
40
+ optimality_gap: Optional[float]
41
+ objective_value: Optional[float]
42
+ message: Optional[str]
43
+ runtime_sec: float
44
+ resource_stats: Optional[ResourceStats] = None
45
+ created_timestamp: str
46
+
47
+ def to_sql_log(self) -> dict:
48
+ return {
49
+ "id": None,
50
+ "solver_engine": self.solver_engine.value,
51
+ "solver_params": self.solver_params.model_dump_json(),
52
+ "model_id": self.model_id,
53
+ "model_name": self.model_name,
54
+ "problem_size_vars": self.problem_size_vars,
55
+ "problem_size_cons": self.problem_size_cons,
56
+ "optimality_gap": self.optimality_gap,
57
+ "objective_value": self.objective_value,
58
+ "status": self.status.value,
59
+ "message": self.message,
60
+ "runtime_sec": self.runtime_sec,
61
+ "resource_stats": self.resource_stats.model_dump_json(),
62
+ "created_timestamp": self.created_timestamp
63
+ }
64
+
65
+
66
+ class WorkflowLog(BaseModel):
67
+ workflow_id: str
68
+ workflow_name: str
69
+ model_ids_execution: dict
70
+ payload: dict
71
+ message: Optional[str]
72
+ start_timestamp: str
73
+ end_timestamp: str
74
+ runtime_sec: float
75
+ created_timestamp: str
76
+
77
+ def to_sql_log(self) -> dict:
78
+ model_ids = json.dumps(self.model_ids_execution)
79
+ payload = json.dumps(self.payload)
80
+
81
+ return {
82
+ "id": None,
83
+ "workflow_id": self.workflow_id,
84
+ "workflow_name": self.workflow_name,
85
+ "model_ids": model_ids,
86
+ "payload": payload,
87
+ "message": self.message,
88
+ "start_timestamp": self.start_timestamp,
89
+ "end_timestamp": self.end_timestamp,
90
+ "runtime_sec": self.runtime_sec,
91
+ "created_timestamp": self.created_timestamp
92
+ }
93
+
94
+
95
+ class LogManager(ABC):
96
+ enable_monitor_optimality: bool = True
97
+ enable_monitor_runtime: bool = True
98
+ enable_monitor_resource: bool = False
99
+ monitor_resource_interval_seconds: int = 5
100
+
101
+ _dir_model_execution_log: str = "log_execution_model"
102
+ _dir_workflow_execution_log: str = "log_execution_workflow"
103
+
104
+ def __init__(self):
105
+ pass
106
+
107
+ @abstractmethod
108
+ def put_model_log(self, model_log: ModelLog):
109
+ pass
110
+
111
+ @abstractmethod
112
+ def put_workflow_log(self, workflow_log: WorkflowLog):
113
+ pass
114
+
115
+
116
+ class BaseResourceMonitor(ABC):
117
+ stats: Optional[ResourceStats] = None
118
+
119
+ @abstractmethod
120
+ def __enter__(self):
121
+ pass
122
+
123
+ @abstractmethod
124
+ def __exit__(self, exc_type, exc_val, exc_tb):
125
+ pass
126
+
127
+
128
+ class NullResourceMonitor(BaseResourceMonitor):
129
+ stats = None
130
+
131
+ def __enter__(self):
132
+ return self
133
+
134
+ def __exit__(self, exc_type, exc_val, exc_tb):
135
+ pass
136
+
137
+
138
+ class ResourceMonitor(BaseResourceMonitor):
139
+
140
+ def __init__(self, interval_seconds: int = 1):
141
+ self._running = False
142
+ self.interval_seconds = interval_seconds
143
+ self.stats: Optional[ResourceStats] = None
144
+ self._thread: Optional[threading.Thread] = None
145
+ self._snapshots: list[ResourceSnapshot] = []
146
+ self._stop_event = threading.Event()
147
+
148
+ def __enter__(self):
149
+ self.start()
150
+ return self
151
+
152
+ def __exit__(self, exc_type, exc_val, exc_tb):
153
+ self.stop()
154
+
155
+ def start(self):
156
+ if self._running:
157
+ return
158
+
159
+ self._running = True
160
+ self._stop_event.clear()
161
+
162
+ proc = psutil.Process(os.getpid())
163
+ proc.cpu_percent(interval=None)
164
+ self._take_sample(proc)
165
+
166
+ self._thread = threading.Thread(
167
+ target=self._sampling_loop,
168
+ daemon=True
169
+ )
170
+
171
+ self._thread.start()
172
+
173
+ def stop(self):
174
+ if not self._running:
175
+ return
176
+
177
+ self._running = False
178
+ self._stop_event.set()
179
+
180
+ if self._thread is not None:
181
+ self._thread.join()
182
+
183
+ self.stats = self._aggregate()
184
+
185
+ def _take_sample(self, proc: psutil.Process):
186
+ try:
187
+ self._snapshots.append(
188
+ ResourceSnapshot(
189
+ timestamp=time.time(),
190
+ cpu_percent=proc.cpu_percent(interval=None),
191
+ memory_mb=proc.memory_info().rss / 1024 / 1024,
192
+ num_threads=proc.num_threads()
193
+ )
194
+ )
195
+ except (
196
+ psutil.NoSuchProcess,
197
+ psutil.AccessDenied,
198
+ ):
199
+ pass
200
+
201
+ def _sampling_loop(self):
202
+ proc = psutil.Process(os.getpid())
203
+
204
+ while not self._stop_event.wait(self.interval_seconds):
205
+ self._take_sample(proc)
206
+
207
+ def _aggregate(self) -> Optional[ResourceStats]:
208
+ if not self._snapshots:
209
+ return None
210
+
211
+ cpu_values = [x.cpu_percent for x in self._snapshots]
212
+ mem_values = [x.memory_mb for x in self._snapshots]
213
+ thread_values = [x.num_threads for x in self._snapshots]
214
+
215
+ return ResourceStats(
216
+ peak_cpu_percent=max(cpu_values),
217
+ avg_cpu_percent=mean(cpu_values),
218
+ peak_memory_mb=max(mem_values),
219
+ avg_memory_mb=mean(mem_values),
220
+ max_num_threads=max(thread_values)
221
+ )
@@ -6,21 +6,27 @@ import pandas as pd
6
6
  class SQLiteLogManager(LogManager):
7
7
  db_path: str
8
8
 
9
- def __init__(self, db_path: str, is_monitor_optimality: bool = True, is_monitor_runtime: bool = True, is_monitor_resource: bool = False):
9
+ def __init__(
10
+ self, db_path: str,
11
+ enable_monitor_optimality: bool = True,
12
+ enable_monitor_runtime: bool = True,
13
+ enable_monitor_resource: bool = False
14
+ ):
10
15
  self.db_path = db_path
11
- self.is_monitor_optimality = is_monitor_optimality
12
- self.is_monitor_resource = is_monitor_resource
13
- self.is_monitor_runtime = is_monitor_runtime
16
+ self.enable_monitor_optimality = enable_monitor_optimality
17
+ self.enable_monitor_resource = enable_monitor_resource
18
+ self.enable_monitor_runtime = enable_monitor_runtime
14
19
 
15
20
  self.init_db()
16
21
 
17
22
  def init_db(self):
18
23
  with sqlite3.connect(self.db_path) as conn:
19
- if self.is_monitor_optimality:
24
+ if self.enable_monitor_optimality:
20
25
  conn.execute(f"""
21
26
  CREATE TABLE IF NOT EXISTS {self._dir_model_execution_log} (
22
27
  id INTEGER PRIMARY KEY AUTOINCREMENT,
23
28
  solver_engine TEXT, -- gurobi / ortools / routing
29
+ solver_params TEXT, -- store JSON solver parameter as string
24
30
  model_id TEXT,
25
31
  model_name TEXT,
26
32
  problem_size_vars INTEGER, -- number of decision variables
@@ -30,11 +36,12 @@ class SQLiteLogManager(LogManager):
30
36
  status TEXT, -- START / DONE / ERROR
31
37
  message TEXT, -- optional note
32
38
  runtime_sec REAL, -- optional
39
+ resource_stats TEXT, -- store JSON resource stats as string
33
40
  created_timestamp TEXT
34
41
  );
35
42
  """)
36
43
 
37
- if self.is_monitor_runtime:
44
+ if self.enable_monitor_runtime:
38
45
  conn.execute(f"""
39
46
  CREATE TABLE IF NOT EXISTS {self._dir_workflow_execution_log} (
40
47
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -42,7 +49,6 @@ class SQLiteLogManager(LogManager):
42
49
  workflow_name TEXT,
43
50
  model_ids TEXT, -- store list of str as JSON string
44
51
  payload TEXT, -- store JSON payload as string
45
- solver_parameter TEXT, -- store JSON solver parameter as string
46
52
  message TEXT, -- store error message
47
53
  start_timestamp TEXT, -- store start timestamp
48
54
  end_timestamp TEXT, -- store end timestamp
@@ -51,21 +57,6 @@ class SQLiteLogManager(LogManager):
51
57
  );
52
58
  """)
53
59
 
54
- if self.is_monitor_resource:
55
- conn.execute(f"""
56
- CREATE TABLE IF NOT EXISTS {self._dir_resource_occupation_log} (
57
- id INTEGER PRIMARY KEY AUTOINCREMENT,
58
- machine_id TEXT,
59
- model_id TEXT,
60
- cpu_percent REAL,
61
- num_cores INTEGER,
62
- limit_thread INTEGER,
63
- memory_mb REAL,
64
- limit_memory_mb REAL,
65
- timestamp TEXT
66
- );
67
- """)
68
-
69
60
  conn.commit()
70
61
 
71
62
  def put_model_log(self, model_log: ModelLog):
@@ -76,7 +67,4 @@ class SQLiteLogManager(LogManager):
76
67
  def put_workflow_log(self, workflow_log: WorkflowLog):
77
68
  with sqlite3.connect(self.db_path) as conn:
78
69
  data = pd.DataFrame([workflow_log.to_sql_log()])
79
- data.to_sql(self._dir_workflow_execution_log, conn, if_exists="append", index=False)
80
-
81
- def put_resource_log(self, resource_log):
82
- return super().put_resource_log(resource_log)
70
+ data.to_sql(self._dir_workflow_execution_log, conn, if_exists="append", index=False)
@@ -0,0 +1,15 @@
1
+ import os
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class SolverParams(BaseModel):
6
+ TRUE_THRESHOLD: float = 0.5
7
+ APPLY_HEURISTICS: bool = True
8
+ MODEL_SOLVER_VERBOSE: bool = False
9
+
10
+ LIMIT_TIME_MINUTES_HEURISTICS: float = 3
11
+ LIMIT_TIME_MINUTES_DETERMINISTIC: float = 3
12
+ LIMIT_OPTIMALITY_GAP_DETERMINISTIC: float = 0.35
13
+ LIMIT_OPTIMALITY_GAP_HEURISTICS: float = 0.75
14
+ LIMIT_MEMORY_MB: int = 1024*8
15
+ LIMIT_MULTI_THREAD: int = 6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stitchlab-optimization
3
- Version: 0.0.4
3
+ Version: 0.1.0
4
4
  Summary: StitchLab Optimization application
5
5
  Author-email: StitchLab Team <evazuliya97@gmail.com>
6
6
  License: MIT
@@ -11,12 +11,12 @@ src/stitchlab_optimization/builder/__init__.py
11
11
  src/stitchlab_optimization/builder/model.py
12
12
  src/stitchlab_optimization/builder/workflow.py
13
13
  src/stitchlab_optimization/logger/__init__.py
14
- src/stitchlab_optimization/logger/file_logger.py
14
+ src/stitchlab_optimization/logger/json_logger.py
15
15
  src/stitchlab_optimization/logger/manager.py
16
16
  src/stitchlab_optimization/logger/sqlite_logger.py
17
17
  src/stitchlab_optimization/solver/__init__.py
18
- src/stitchlab_optimization/solver/config.py
19
18
  src/stitchlab_optimization/solver/engine.py
19
+ src/stitchlab_optimization/solver/params.py
20
20
  src/stitchlab_optimization/solver/status.py
21
21
  src/stitchlab_optimization/tools/utils.py
22
22
  src/stitchlab_optimization/tools/query/snowflake.py
@@ -1,120 +0,0 @@
1
- class ModelLog(BaseModel):
2
- solver_engine: SolverEngine
3
- model_id: str
4
- exec_id: str
5
- model_name: str
6
- status: SolverStatus
7
- problem_size_vars: Optional[int]
8
- problem_size_cons: Optional[int]
9
- optimality_gap: Optional[float]
10
- objective_value: Optional[float]
11
- message: Optional[str]
12
- runtime_sec: float
13
- created_at: str
14
-
15
- @classmethod
16
- def from_model(cls, model: OptimizationModel, solver_engine: SolverEngine):
17
-
18
- builder = model.builders[solver_engine]
19
-
20
- if solver_engine == SolverEngine.GUROBI:
21
- # Gurobi Python API
22
- problem_size_vars = builder.model.NumVars
23
- problem_size_cons = builder.model.NumConstrs
24
-
25
- if builder.model.NumObj <= 1:
26
- optimality_gap = builder.model.MIPGap
27
- objective_value = builder.model.ObjVal
28
-
29
- else:
30
- objective_value = builder.model.getAttr("ObjNVal")
31
- optimality_gap = builder.model.getAttr("ObjNRelTol")
32
-
33
- elif solver_engine == SolverEngine.ORTOOLS_SCIP:
34
- # OR-Tools CP-SAT solver (pywraplp.Solver)
35
- problem_size_vars = builder.model.NumVariables()
36
- problem_size_cons = builder.model.NumConstraints()
37
-
38
- try:
39
- optimality_gap = builder.model.MipGap()
40
- except AttributeError:
41
- optimality_gap = None
42
-
43
- objective_value = builder.model.Objective().Value()
44
-
45
- elif solver_engine == SolverEngine.ORTOOLS_ROUTING:
46
- # OR-Tools RoutingModel
47
- count_nodes = builder.model.Size()
48
- count_vehicles = builder.model.vehicles()
49
- problem_size_vars = count_nodes * count_nodes * count_vehicles
50
-
51
- # RoutingModel does not expose number of constraints directly
52
- problem_size_cons = None
53
- objective_value = builder.solution.ObjectiveValue()
54
- optimality_gap = None
55
-
56
- elif solver_engine == SolverEngine.ORTOOLS_CPSAT:
57
- problem_size_vars = len(builder.model.Proto().variables)
58
- problem_size_cons = len(builder.model.Proto().constraints)
59
-
60
- # Objective value (only available if model solved)
61
- try:
62
- objective_value = builder.solution.ObjectiveValue()
63
- except Exception:
64
- objective_value = None
65
-
66
- optimality_gap = None
67
-
68
- elif solver_engine == SolverEngine.PYSCIPOPT:
69
- problem_size_vars = builder.model.getNVars()
70
- problem_size_cons = builder.model.getNConss()
71
- optimality_gap = builder.model.getGap()
72
-
73
- try:
74
- objective_value = builder.model.getObjVal()
75
- except Exception:
76
- objective_value = None
77
-
78
- elif solver_engine == SolverEngine.SKLEARN:
79
- # scikit-learn is not an optimization solver, so these are not applicable
80
- problem_size_vars = None
81
- problem_size_cons = None
82
- optimality_gap = None
83
- objective_value = None
84
-
85
- else:
86
- raise ValueError(f"Solver engine {solver_engine} not supported")
87
-
88
- return ModelLog(
89
- solver_engine=solver_engine,
90
- model_id=model.id,
91
- exec_id=builder.id,
92
- model_name=model.name,
93
- status=builder.solver_status,
94
- problem_size_vars=problem_size_vars,
95
- problem_size_cons=problem_size_cons,
96
- optimality_gap=optimality_gap,
97
- objective_value=objective_value,
98
- message=builder.runtime_message,
99
- runtime_sec=builder.runtime_seconds,
100
- created_at=datetime.now(timezone.utc).isoformat()
101
- )
102
-
103
- def write_to_db(self):
104
- log = {
105
- "id": None,
106
- "solver_engine": self.solver_engine.value,
107
- "model_id": self.model_id,
108
- "exec_id": self.exec_id,
109
- "model_name": self.model_name,
110
- "problem_size_vars": self.problem_size_vars,
111
- "problem_size_cons": self.problem_size_cons,
112
- "optimality_gap": self.optimality_gap,
113
- "objective_value": self.objective_value,
114
- "status": self.status.value,
115
- "message": self.message,
116
- "runtime_sec": self.runtime_sec,
117
- "created_at": self.created_at
118
- }
119
-
120
- insert_to_sqlite(table_name="execution_log", data=log)
@@ -1,94 +0,0 @@
1
- from abc import abstractmethod, ABC
2
- from pydantic import BaseModel
3
- from typing import Optional
4
- import json
5
-
6
- from ..solver.engine import SolverEngine
7
- from ..solver.status import SolverStatus
8
-
9
-
10
- class ModelLog(BaseModel):
11
- solver_engine: SolverEngine
12
- model_id: str
13
- model_name: str
14
- status: SolverStatus
15
- problem_size_vars: Optional[int]
16
- problem_size_cons: Optional[int]
17
- optimality_gap: Optional[float]
18
- objective_value: Optional[float]
19
- message: Optional[str]
20
- runtime_sec: float
21
- created_timestamp: str
22
-
23
- def to_sql_log(self) -> dict:
24
- return {
25
- "id": None,
26
- "solver_engine": self.solver_engine.value,
27
- "model_id": self.model_id,
28
- "model_name": self.model_name,
29
- "problem_size_vars": self.problem_size_vars,
30
- "problem_size_cons": self.problem_size_cons,
31
- "optimality_gap": self.optimality_gap,
32
- "objective_value": self.objective_value,
33
- "status": self.status.value,
34
- "message": self.message,
35
- "runtime_sec": self.runtime_sec,
36
- "created_timestamp": self.created_timestamp
37
- }
38
-
39
-
40
- class WorkflowLog(BaseModel):
41
- workflow_id: str
42
- workflow_name: str
43
- model_ids_execution: dict
44
- payload: dict
45
- solver_parameter: dict
46
- message: Optional[str]
47
- start_timestamp: str
48
- end_timestamp: str
49
- runtime_sec: float
50
- created_timestamp: str
51
-
52
- def to_sql_log(self) -> dict:
53
- model_ids = json.dumps(self.model_ids_execution)
54
- payload = json.dumps(self.payload)
55
- solver_parameter = json.dumps(self.solver_parameter)
56
-
57
- return {
58
- "id": None,
59
- "workflow_id": self.workflow_id,
60
- "workflow_name": self.workflow_name,
61
- "model_ids": model_ids,
62
- "payload": payload,
63
- "solver_parameter": solver_parameter,
64
- "message": self.message,
65
- "start_timestamp": self.start_timestamp,
66
- "end_timestamp": self.end_timestamp,
67
- "runtime_sec": self.runtime_sec,
68
- "created_timestamp": self.created_timestamp
69
- }
70
-
71
-
72
- class LogManager(ABC):
73
- is_monitor_optimality: bool = True
74
- is_monitor_runtime: bool = True
75
- is_monitor_resource: bool = False
76
-
77
- _dir_model_execution_log: str = "log_execution_model"
78
- _dir_workflow_execution_log: str = "log_execution_workflow"
79
- _dir_resource_occupation_log: str = "log_resource_occupation"
80
-
81
- def __init__(self):
82
- pass
83
-
84
- @abstractmethod
85
- def put_model_log(self, model_log: ModelLog):
86
- pass
87
-
88
- @abstractmethod
89
- def put_workflow_log(self, workflow_log: WorkflowLog):
90
- pass
91
-
92
- @abstractmethod
93
- def put_resource_log(self, resource_log: dict):
94
- pass
@@ -1,63 +0,0 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
-
5
- class SolverConfig:
6
- _instance = None
7
-
8
- def __new__(cls):
9
- if cls._instance is None:
10
- if cls._instance is None:
11
- load_dotenv() # load .env
12
- cls._instance = super().__new__(cls)
13
- cls._instance._load_env()
14
-
15
- return cls._instance
16
-
17
- def _load_env(self):
18
- self.TRUE_THRESHOLD = float(os.getenv("TRUE_THRESHOLD", 0.5))
19
- self.APPLY_HEURISTICS = os.getenv("APPLY_HEURISTICS", "True").lower() in ("true", "1", "yes")
20
- self.MODEL_SOLVER_VERBOSE = os.getenv("MODEL_SOLVER_VERBOSE", "False").lower() in ("true", "1", "yes")
21
-
22
- self.LIMIT_TIME_MINUTES_HEURISTICS = float(os.getenv("LIMIT_TIME_MINUTES_HEURISTICS", 3))
23
- self.LIMIT_TIME_MINUTES_DETERMINISTIC = float(os.getenv("LIMIT_TIME_MINUTES_DETERMINISTIC", 3))
24
- self.LIMIT_OPTIMALITY_GAP_DETERMINISTIC = float(os.getenv("LIMIT_OPTIMALITY_GAP_DETERMINISTIC", 0.35))
25
- self.LIMIT_OPTIMALITY_GAP_HEURISTICS = float(os.getenv("LIMIT_OPTIMALITY_GAP_HEURISTICS", 0.75))
26
- self.LIMIT_MEMORY_MB = int(os.getenv("LIMIT_MEMORY_MB", 1024*8))
27
- self.LIMIT_MULTI_THREAD = int(os.getenv("LIMIT_MULTI_THREAD", 6))
28
-
29
- self.SOLVER_PARAMETER = {
30
- "true_threshold": self.TRUE_THRESHOLD,
31
- "limit_minutes_heuristics": self.LIMIT_TIME_MINUTES_HEURISTICS,
32
- "limit_minutes_deterministic": self.LIMIT_TIME_MINUTES_DETERMINISTIC,
33
- "limit_optimality_gap_deterministic": self.LIMIT_OPTIMALITY_GAP_DETERMINISTIC,
34
- "limit_optimality_gap_heuristics": self.LIMIT_OPTIMALITY_GAP_HEURISTICS,
35
- "limit_memory_mb": self.LIMIT_MEMORY_MB,
36
- "limit_multi_thread": self.LIMIT_MULTI_THREAD,
37
- "apply_heuristics": self.APPLY_HEURISTICS,
38
- }
39
-
40
- self.MONITOR_EXECUTION = os.getenv("MONITOR_EXECUTION", "False").lower() in ("true", "1", "yes")
41
- self.MONITOR_RESOURCE = os.getenv("MONITOR_RESOURCE", "False").lower() in ("true", "1", "yes")
42
- self.MONITOR_RESOURCE_INTERVAL_SECONDS = int(os.getenv("MONITOR_RESOURCE_INTERVAL_SECONDS", 60))
43
-
44
- self.SQLITE_DB_PATH = os.getenv("SQLITE_DB_PATH", "log.sqlite")
45
- self.EXPORT_DIRECTORY = os.getenv("EXPORT_DIRECTORY", "export")
46
-
47
- self._load_gurobi_credentials()
48
-
49
-
50
- def _load_gurobi_credentials(self):
51
- self.GUROBI_ENV = None
52
-
53
- GUROBI_KEYS = {
54
- "WLSAccessID": os.getenv("WLSACCESSID", None),
55
- "WLSSecret": os.getenv("WLSSECRET", None),
56
- "LicenseID": int(os.getenv("LICENSEID", 0)),
57
- }
58
-
59
- if GUROBI_KEYS["WLSAccessID"] is not None and GUROBI_KEYS["WLSSecret"] is not None:
60
- import gurobipy as gp
61
-
62
- self.GUROBI_ENV = gp.Env(params=GUROBI_KEYS)
63
-