solverforge 0.2.4__tar.gz → 0.2.6__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 (86) hide show
  1. {solverforge-0.2.4 → solverforge-0.2.6}/Cargo.lock +7 -5
  2. {solverforge-0.2.4 → solverforge-0.2.6}/Cargo.toml +2 -2
  3. {solverforge-0.2.4 → solverforge-0.2.6}/PKG-INFO +1 -1
  4. {solverforge-0.2.4 → solverforge-0.2.6}/pyproject.toml +1 -1
  5. {solverforge-0.2.4 → solverforge-0.2.6}/python/solverforge/__init__.py +25 -0
  6. solverforge-0.2.6/python/solverforge/solver/__init__.py +376 -0
  7. solverforge-0.2.6/python/solverforge/solver/config.py +221 -0
  8. solverforge-0.2.6/python/solverforge/solver/domain.py +45 -0
  9. solverforge-0.2.6/python/solverforge/solver/score.py +51 -0
  10. solverforge-0.2.6/python/solverforge/test/__init__.py +746 -0
  11. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/Cargo.toml +2 -0
  12. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/python/solverforge/__init__.py +25 -0
  13. solverforge-0.2.6/solverforge-python/python/solverforge/solver/__init__.py +376 -0
  14. solverforge-0.2.6/solverforge-python/python/solverforge/solver/config.py +221 -0
  15. solverforge-0.2.6/solverforge-python/python/solverforge/solver/domain.py +45 -0
  16. solverforge-0.2.6/solverforge-python/python/solverforge/solver/score.py +51 -0
  17. solverforge-0.2.6/solverforge-python/python/solverforge/test/__init__.py +746 -0
  18. solverforge-0.2.6/solverforge-python/src/analysis.rs +414 -0
  19. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/collectors.rs +182 -0
  20. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/decorators.rs +3 -16
  21. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/lambda_analyzer.rs +222 -12
  22. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/lib.rs +25 -1
  23. solverforge-0.2.6/solverforge-python/src/score.rs +1714 -0
  24. solverforge-0.2.6/solverforge-python/src/service.rs +299 -0
  25. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/solver.rs +66 -2
  26. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/stream.rs +777 -49
  27. solverforge-0.2.6/solverforge-service/Cargo.toml +33 -0
  28. solverforge-0.2.6/solverforge-service/README.md +89 -0
  29. solverforge-0.2.6/solverforge-service/src/config.rs +106 -0
  30. solverforge-0.2.6/solverforge-service/src/error.rs +51 -0
  31. solverforge-0.2.6/solverforge-service/src/jar.rs +246 -0
  32. solverforge-0.2.6/solverforge-service/src/lib.rs +18 -0
  33. solverforge-0.2.6/solverforge-service/src/service.rs +242 -0
  34. solverforge-0.2.6/solverforge-service/src/util.rs +171 -0
  35. solverforge-0.2.6/solverforge-service/tests/employee_scheduling_integration.rs +984 -0
  36. solverforge-0.2.6/solverforge-service/tests/integration.rs +61 -0
  37. solverforge-0.2.6/solverforge-service/tests/solve_integration.rs +393 -0
  38. solverforge-0.2.4/solverforge-python/src/score.rs +0 -579
  39. {solverforge-0.2.4 → solverforge-0.2.6}/README.md +0 -0
  40. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/Cargo.toml +0 -0
  41. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/README.md +0 -0
  42. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/analysis/explanation.rs +0 -0
  43. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/analysis/manager.rs +0 -0
  44. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/analysis/mod.rs +0 -0
  45. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/bridge.rs +0 -0
  46. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/constraints/collectors.rs +0 -0
  47. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/constraints/constraint.rs +0 -0
  48. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/constraints/joiners.rs +0 -0
  49. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/constraints/mod.rs +0 -0
  50. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/constraints/stream.rs +0 -0
  51. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/domain/annotations.rs +0 -0
  52. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/domain/class.rs +0 -0
  53. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/domain/constraint_config.rs +0 -0
  54. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/domain/mod.rs +0 -0
  55. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/domain/model.rs +0 -0
  56. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/domain/shadow.rs +0 -0
  57. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/error.rs +0 -0
  58. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/handles.rs +0 -0
  59. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/lib.rs +0 -0
  60. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/score/bendable.rs +0 -0
  61. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/score/hard_soft.rs +0 -0
  62. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/score/hard_soft_decimal.rs +0 -0
  63. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/score/mod.rs +0 -0
  64. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/score/simple.rs +0 -0
  65. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/score/simple_decimal.rs +0 -0
  66. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/builder.rs +0 -0
  67. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/client.rs +0 -0
  68. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/config.rs +0 -0
  69. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/environment.rs +0 -0
  70. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/factory.rs +0 -0
  71. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/mod.rs +0 -0
  72. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/request.rs +0 -0
  73. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/response.rs +0 -0
  74. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/solver/termination.rs +0 -0
  75. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/traits.rs +0 -0
  76. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/value.rs +0 -0
  77. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/wasm/expr_builder.rs +0 -0
  78. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/wasm/expression.rs +0 -0
  79. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/wasm/generator.rs +0 -0
  80. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/wasm/host_functions.rs +0 -0
  81. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/wasm/memory.rs +0 -0
  82. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-core/src/wasm/mod.rs +0 -0
  83. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/README.md +0 -0
  84. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/annotations.rs +0 -0
  85. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/bridge.rs +0 -0
  86. {solverforge-0.2.4 → solverforge-0.2.6}/solverforge-python/src/joiners.rs +0 -0
@@ -1507,7 +1507,7 @@ dependencies = [
1507
1507
 
1508
1508
  [[package]]
1509
1509
  name = "solverforge"
1510
- version = "0.2.4"
1510
+ version = "0.2.6"
1511
1511
  dependencies = [
1512
1512
  "solverforge-core",
1513
1513
  "solverforge-derive",
@@ -1516,7 +1516,7 @@ dependencies = [
1516
1516
 
1517
1517
  [[package]]
1518
1518
  name = "solverforge-core"
1519
- version = "0.2.4"
1519
+ version = "0.2.6"
1520
1520
  dependencies = [
1521
1521
  "base64",
1522
1522
  "indexmap",
@@ -1532,7 +1532,7 @@ dependencies = [
1532
1532
 
1533
1533
  [[package]]
1534
1534
  name = "solverforge-derive"
1535
- version = "0.2.4"
1535
+ version = "0.2.6"
1536
1536
  dependencies = [
1537
1537
  "proc-macro2",
1538
1538
  "quote",
@@ -1544,20 +1544,22 @@ dependencies = [
1544
1544
 
1545
1545
  [[package]]
1546
1546
  name = "solverforge-python"
1547
- version = "0.2.4"
1547
+ version = "0.2.6"
1548
1548
  dependencies = [
1549
1549
  "log",
1550
1550
  "pyo3",
1551
+ "rust_decimal",
1551
1552
  "serde",
1552
1553
  "serde_json",
1553
1554
  "solverforge-core",
1555
+ "solverforge-service",
1554
1556
  "tempfile",
1555
1557
  "thiserror",
1556
1558
  ]
1557
1559
 
1558
1560
  [[package]]
1559
1561
  name = "solverforge-service"
1560
- version = "0.2.4"
1562
+ version = "0.2.6"
1561
1563
  dependencies = [
1562
1564
  "base64",
1563
1565
  "chrono",
@@ -1,9 +1,9 @@
1
1
  [workspace]
2
- members = ["solverforge-core", "solverforge-python"]
2
+ members = ["solverforge-core", "solverforge-python", "solverforge-service"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.4"
6
+ version = "0.2.6"
7
7
  edition = "2021"
8
8
  license = "Apache-2.0"
9
9
  repository = "https://github.com/solverforge/solverforge"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solverforge
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "solverforge"
7
- version = "0.2.4"
7
+ version = "0.2.6"
8
8
  description = "Python bindings for SolverForge constraint solver"
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -31,6 +31,10 @@ from solverforge._solverforge import (
31
31
  SimpleScore,
32
32
  HardSoftScore,
33
33
  HardMediumSoftScore,
34
+ HardSoftDecimalScore,
35
+ HardMediumSoftDecimalScore,
36
+ BendableScore,
37
+ BendableDecimalScore,
34
38
  # Decorators
35
39
  planning_entity,
36
40
  planning_solution,
@@ -70,6 +74,16 @@ from solverforge._solverforge import (
70
74
  ConstraintCollectors,
71
75
  )
72
76
 
77
+ # Embedded service management
78
+ from solverforge._solverforge import (
79
+ ServiceConfig,
80
+ EmbeddedService,
81
+ ensure_service,
82
+ is_service_available,
83
+ get_service_url,
84
+ stop_service,
85
+ )
86
+
73
87
  __all__ = [
74
88
  "__version__",
75
89
  # Annotation marker classes
@@ -91,6 +105,10 @@ __all__ = [
91
105
  "SimpleScore",
92
106
  "HardSoftScore",
93
107
  "HardMediumSoftScore",
108
+ "HardSoftDecimalScore",
109
+ "HardMediumSoftDecimalScore",
110
+ "BendableScore",
111
+ "BendableDecimalScore",
94
112
  # Decorators
95
113
  "planning_entity",
96
114
  "planning_solution",
@@ -128,4 +146,11 @@ __all__ = [
128
146
  # Collectors
129
147
  "Collector",
130
148
  "ConstraintCollectors",
149
+ # Embedded service
150
+ "ServiceConfig",
151
+ "EmbeddedService",
152
+ "ensure_service",
153
+ "is_service_available",
154
+ "get_service_url",
155
+ "stop_service",
131
156
  ]
@@ -0,0 +1,376 @@
1
+ """Solver module - compatibility layer for Timefold-style imports."""
2
+
3
+ from datetime import timedelta
4
+ from enum import Enum
5
+ from threading import Thread, Lock
6
+ from typing import Callable, Optional, TypeVar, Generic
7
+
8
+ from solverforge._solverforge import (
9
+ SolverFactory,
10
+ Solver,
11
+ SolveHandle,
12
+ SolveResponse,
13
+ SolutionManager as _SolutionManager,
14
+ ScoreExplanation,
15
+ ConstraintMatch,
16
+ Indictment,
17
+ ScoreDto,
18
+ )
19
+
20
+
21
+ Solution_ = TypeVar("Solution_")
22
+ ProblemId_ = TypeVar("ProblemId_")
23
+
24
+
25
+ class SolverStatus(Enum):
26
+ """Solver status enum matching Timefold's SolverStatus.
27
+
28
+ NOT_SOLVING: No active solve operation.
29
+ SOLVING_SCHEDULED: Solve has been submitted but not yet started.
30
+ SOLVING_ACTIVE: Actively solving the problem.
31
+ """
32
+
33
+ NOT_SOLVING = "NOT_SOLVING"
34
+ SOLVING_SCHEDULED = "SOLVING_SCHEDULED"
35
+ SOLVING_ACTIVE = "SOLVING_ACTIVE"
36
+
37
+
38
+ class SolverJob(Generic[Solution_, ProblemId_]):
39
+ """Represents a problem that has been submitted to solve on the SolverManager.
40
+
41
+ Use get_final_best_solution() to wait for the solve to complete and get the result.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ problem_id: ProblemId_,
47
+ solver: "Solver",
48
+ problem: Solution_,
49
+ best_solution_consumer: Optional[Callable[[Solution_], None]] = None,
50
+ final_best_solution_consumer: Optional[Callable[[Solution_], None]] = None,
51
+ ):
52
+ self._problem_id = problem_id
53
+ self._solver = solver
54
+ self._problem = problem
55
+ self._best_solution_consumer = best_solution_consumer
56
+ self._final_best_solution_consumer = final_best_solution_consumer
57
+ self._status = SolverStatus.SOLVING_SCHEDULED
58
+ self._final_solution: Optional[Solution_] = None
59
+ self._response: Optional[SolveResponse] = None
60
+ self._exception: Optional[Exception] = None
61
+ self._terminated_early = False
62
+ self._lock = Lock()
63
+ self._thread: Optional[Thread] = None
64
+
65
+ def _run(self):
66
+ """Internal method to run the solver in a thread."""
67
+ try:
68
+ with self._lock:
69
+ self._status = SolverStatus.SOLVING_ACTIVE
70
+
71
+ response = self._solver.solve(self._problem)
72
+
73
+ with self._lock:
74
+ self._response = response
75
+ self._final_solution = response.solution
76
+ self._status = SolverStatus.NOT_SOLVING
77
+
78
+ if self._best_solution_consumer and response.solution:
79
+ self._best_solution_consumer(response.solution)
80
+
81
+ if self._final_best_solution_consumer and response.solution:
82
+ self._final_best_solution_consumer(response.solution)
83
+
84
+ except Exception as e:
85
+ with self._lock:
86
+ self._exception = e
87
+ self._status = SolverStatus.NOT_SOLVING
88
+
89
+ def _start(self):
90
+ """Start the solver in a background thread."""
91
+ self._thread = Thread(target=self._run, daemon=True)
92
+ self._thread.start()
93
+
94
+ def get_problem_id(self) -> ProblemId_:
95
+ """Get the problem id for this solver job.
96
+
97
+ Returns:
98
+ The problem id.
99
+ """
100
+ return self._problem_id
101
+
102
+ def get_solver_status(self) -> SolverStatus:
103
+ """Get the current solver status.
104
+
105
+ Returns:
106
+ NOT_SOLVING if terminated, SOLVING_SCHEDULED if waiting,
107
+ SOLVING_ACTIVE if actively solving.
108
+ """
109
+ with self._lock:
110
+ return self._status
111
+
112
+ def get_solving_duration(self) -> timedelta:
113
+ """Get the duration spent solving.
114
+
115
+ Note: Currently returns timedelta(0) as precise timing is not yet implemented.
116
+
117
+ Returns:
118
+ Duration spent solving.
119
+ """
120
+ return timedelta(0)
121
+
122
+ def get_final_best_solution(self) -> Solution_:
123
+ """Wait for the solver to complete and return the final best solution.
124
+
125
+ Returns:
126
+ The best solution found.
127
+
128
+ Raises:
129
+ Exception: If the solver encountered an error.
130
+ """
131
+ if self._thread:
132
+ self._thread.join()
133
+
134
+ with self._lock:
135
+ if self._exception:
136
+ raise self._exception
137
+ return self._final_solution
138
+
139
+ def terminate_early(self) -> None:
140
+ """Terminate the solver early.
141
+
142
+ Currently waits for the solve to complete as streaming termination
143
+ is not yet implemented.
144
+ """
145
+ with self._lock:
146
+ self._terminated_early = True
147
+
148
+ def is_terminated_early(self) -> bool:
149
+ """Check if terminate_early was called.
150
+
151
+ Returns:
152
+ True if terminate_early was called.
153
+ """
154
+ with self._lock:
155
+ return self._terminated_early
156
+
157
+
158
+ class SolverManager(Generic[Solution_, ProblemId_]):
159
+ """High-level solver manager providing Timefold-compatible API.
160
+
161
+ Manages solver lifecycle and provides synchronous solving capabilities.
162
+ """
163
+
164
+ @staticmethod
165
+ def create(solver_config, service_url=None):
166
+ """Create a SolverManager from config.
167
+
168
+ Args:
169
+ solver_config: SolverConfig with solution/entity classes and constraints
170
+ service_url: Optional URL for the solver service
171
+
172
+ Returns:
173
+ Configured SolverManager instance
174
+ """
175
+ return SolverManager(solver_config, service_url)
176
+
177
+ def __init__(self, solver_config, service_url=None):
178
+ self._config = solver_config
179
+ self._service_url = service_url
180
+ self._active_solves = {}
181
+
182
+ # Extract constraint provider from score_director_factory_config
183
+ constraint_provider = None
184
+ if solver_config.score_director_factory_config:
185
+ constraint_provider = (
186
+ solver_config.score_director_factory_config.constraint_provider
187
+ )
188
+
189
+ # Get the native config wrapper
190
+ inner_config = (
191
+ solver_config._inner if hasattr(solver_config, "_inner") else solver_config
192
+ )
193
+
194
+ if not constraint_provider:
195
+ raise ValueError(
196
+ "SolverConfig must include score_director_factory_config with constraint_provider"
197
+ )
198
+
199
+ self._factory = SolverFactory.create(
200
+ inner_config, constraint_provider, service_url
201
+ )
202
+
203
+ def solve(
204
+ self,
205
+ problem_id: ProblemId_,
206
+ problem: Solution_,
207
+ final_best_solution_consumer: Optional[Callable[[Solution_], None]] = None,
208
+ ) -> SolverJob[Solution_, ProblemId_]:
209
+ """Submits a problem to solve asynchronously and returns immediately.
210
+
211
+ The problem is solved on a background thread. Use SolverJob.get_final_best_solution()
212
+ to wait for the result.
213
+
214
+ Args:
215
+ problem_id: Unique identifier for this solve operation
216
+ problem: The planning problem instance to solve
217
+ final_best_solution_consumer: Optional callback invoked with final solution
218
+
219
+ Returns:
220
+ SolverJob that can be used to get the result or terminate early
221
+ """
222
+ solver = self._factory.build_solver()
223
+ job = SolverJob(
224
+ problem_id=problem_id,
225
+ solver=solver,
226
+ problem=problem,
227
+ best_solution_consumer=None,
228
+ final_best_solution_consumer=final_best_solution_consumer,
229
+ )
230
+ self._active_solves[problem_id] = job
231
+ job._start()
232
+ return job
233
+
234
+ def solve_and_listen(
235
+ self,
236
+ problem_id: ProblemId_,
237
+ problem: Solution_,
238
+ listener: Callable[[Solution_], None],
239
+ ) -> SolverJob[Solution_, ProblemId_]:
240
+ """Submits a problem to solve and calls listener for best solutions.
241
+
242
+ The problem is solved on a background thread. The listener is called
243
+ when new best solutions are found (currently called once with final solution).
244
+
245
+ Args:
246
+ problem_id: Unique identifier for this solve operation
247
+ problem: The planning problem instance to solve
248
+ listener: Callback for solution updates
249
+
250
+ Returns:
251
+ SolverJob that can be used to get the result or terminate early
252
+ """
253
+ solver = self._factory.build_solver()
254
+ job = SolverJob(
255
+ problem_id=problem_id,
256
+ solver=solver,
257
+ problem=problem,
258
+ best_solution_consumer=listener,
259
+ final_best_solution_consumer=None,
260
+ )
261
+ self._active_solves[problem_id] = job
262
+ job._start()
263
+ return job
264
+
265
+ def get_solver_status(self, problem_id: ProblemId_) -> SolverStatus:
266
+ """Get the status of a solver job.
267
+
268
+ Args:
269
+ problem_id: The problem id to check
270
+
271
+ Returns:
272
+ SolverStatus for the given problem, or NOT_SOLVING if not found
273
+ """
274
+ job = self._active_solves.get(problem_id)
275
+ if job:
276
+ return job.get_solver_status()
277
+ return SolverStatus.NOT_SOLVING
278
+
279
+ def terminate_early(self, problem_id: ProblemId_) -> None:
280
+ """Terminate a solver job early.
281
+
282
+ Args:
283
+ problem_id: The problem id to terminate
284
+ """
285
+ job = self._active_solves.get(problem_id)
286
+ if job:
287
+ job.terminate_early()
288
+
289
+ def close(self) -> None:
290
+ """Release solver manager resources."""
291
+ self._active_solves.clear()
292
+
293
+ def __enter__(self) -> "SolverManager[Solution_, ProblemId_]":
294
+ """Context manager entry."""
295
+ return self
296
+
297
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
298
+ """Context manager exit - calls close()."""
299
+ self.close()
300
+
301
+
302
+ class SolutionManager:
303
+ """Solution manager for score calculation and constraint analysis.
304
+
305
+ Wraps the native SolutionManager to provide Timefold-compatible API.
306
+ """
307
+
308
+ @staticmethod
309
+ def create(solver_factory_or_manager):
310
+ """Create a SolutionManager from a SolverFactory or SolverManager.
311
+
312
+ Args:
313
+ solver_factory_or_manager: SolverFactory or SolverManager instance
314
+
315
+ Returns:
316
+ Configured SolutionManager instance
317
+ """
318
+ return SolutionManager(solver_factory_or_manager)
319
+
320
+ def __init__(self, solver_factory_or_manager):
321
+ if isinstance(solver_factory_or_manager, SolverManager):
322
+ self._factory = solver_factory_or_manager._factory
323
+ else:
324
+ self._factory = solver_factory_or_manager
325
+
326
+ # Create native SolutionManager from factory
327
+ self._inner = _SolutionManager.create(self._factory)
328
+
329
+ def update(self, solution):
330
+ """Calculate and update the score for a solution.
331
+
332
+ Args:
333
+ solution: Planning solution to score
334
+
335
+ Returns:
336
+ ScoreDto with the calculated score
337
+ """
338
+ return self._inner.update(solution)
339
+
340
+ def analyze(self, solution):
341
+ """Analyze constraint matches for a solution.
342
+
343
+ Args:
344
+ solution: Planning solution to analyze
345
+
346
+ Returns:
347
+ ScoreExplanation with constraint match details
348
+ """
349
+ return self._inner.analyze(solution)
350
+
351
+ def explain(self, solution):
352
+ """Get score explanation for a solution.
353
+
354
+ Args:
355
+ solution: Planning solution to explain
356
+
357
+ Returns:
358
+ ScoreExplanation with detailed constraint breakdown
359
+ """
360
+ return self._inner.explain(solution)
361
+
362
+
363
+ __all__ = [
364
+ "SolverStatus",
365
+ "SolverJob",
366
+ "SolverFactory",
367
+ "Solver",
368
+ "SolveHandle",
369
+ "SolveResponse",
370
+ "SolverManager",
371
+ "SolutionManager",
372
+ "ScoreExplanation",
373
+ "ConstraintMatch",
374
+ "Indictment",
375
+ "ScoreDto",
376
+ ]