Skip to content

Commit a852791

Browse files
committed
feat: expose solve inputs
1 parent 7860492 commit a852791

File tree

2 files changed

+28
-16
lines changed

2 files changed

+28
-16
lines changed

src/opvious/client/handlers.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from collections.abc import AsyncIterator, Iterable, Mapping, Sequence
4+
import dataclasses
45
import json
56
import logging
67
from typing import Any, BinaryIO
@@ -259,9 +260,7 @@ async def register_specification(
259260
tag_name=tag_names[0] if tag_names else None,
260261
)
261262

262-
async def _prepare_problem(
263-
self, problem: Problem
264-
) -> tuple[Json, ProblemOutline]:
263+
async def _prepare_problem(self, problem: Problem) -> _PreparedProblem:
265264
"""Generates solve problem and final outline."""
266265
# First we fetch the outline to validate/coerce inputs later on
267266
if isinstance(problem.specification, FormulationSpecification):
@@ -315,7 +314,7 @@ async def _prepare_problem(
315314
strategy=solve_strategy_to_json(problem.strategy, outline),
316315
options=solve_options_to_json(problem.options),
317316
)
318-
return (problem, outline)
317+
return _PreparedProblem(problem, outline, inputs)
319318

320319
async def serialize_problem(self, problem: Problem) -> Json:
321320
"""Returns a serialized representation of the problem
@@ -326,21 +325,21 @@ async def serialize_problem(self, problem: Problem) -> Json:
326325
Args:
327326
problem: :class:`.Problem` instance to serialize
328327
"""
329-
problem, _outline = await self._prepare_problem(problem)
330-
return problem
328+
prepared = await self._prepare_problem(problem)
329+
return prepared.data
331330

332331
async def summarize_problem(self, problem: Problem) -> ProblemSummary:
333332
"""Returns summary statistics about a problem without solving it
334333
335334
Args:
336335
problem: :class:`.Problem` instance to summarize
337336
"""
338-
problem, _outline = await self._prepare_problem(problem)
337+
prepared = await self._prepare_problem(problem)
339338
async with self._executor.execute(
340339
result_type=JsonExecutorResult,
341340
url="/summarize-problem",
342341
method="POST",
343-
json_data=json_dict(problem=problem),
342+
json_data=json_dict(problem=prepared.data),
344343
) as res:
345344
return problem_summary_from_json(res.json_data())
346345

@@ -379,12 +378,12 @@ async def format_problem(
379378
380379
.. _LP format: https://web.mit.edu/lpsolve/doc/CPLEX-format.htm
381380
"""
382-
problem, _outline = await self._prepare_problem(problem)
381+
prepared = await self._prepare_problem(problem)
383382
async with self._executor.execute(
384383
result_type=PlainTextExecutorResult,
385384
url="/format-problem",
386385
method="POST",
387-
json_data=json_dict(problem=problem),
386+
json_data=json_dict(problem=prepared.data),
388387
) as res:
389388
lines = []
390389
async for line in res.lines():
@@ -447,15 +446,15 @@ async def solve(
447446
See also :meth:`.Client.queue_solve` for an alternative for
448447
long-running solves.
449448
"""
450-
problem, outline = await self._prepare_problem(problem)
449+
prepared = await self._prepare_problem(problem)
451450
if prefer_streaming and self._executor.supports_streaming:
452451
problem_summary = None
453452
response_json = None
454453
async with self._executor.execute(
455454
result_type=JsonSeqExecutorResult,
456455
url="/solve",
457456
method="POST",
458-
json_data=json_dict(problem=problem),
457+
json_data=json_dict(problem=prepared.data),
459458
) as res:
460459
async for data in res.json_seq_data():
461460
kind = data["kind"]
@@ -498,7 +497,8 @@ async def solve(
498497
if not problem_summary or not response_json:
499498
raise Exception("Streaming solve terminated early")
500499
solution = solution_from_json(
501-
outline=outline,
500+
outline=prepared.outline,
501+
inputs=prepared.inputs,
502502
response_json=response_json,
503503
problem_summary=problem_summary,
504504
)
@@ -510,7 +510,7 @@ async def solve(
510510
json_data=json_dict(problem=problem),
511511
) as res:
512512
solution = solution_from_json(
513-
outline=outline,
513+
outline=prepared.outline,
514514
response_json=res.json_data(),
515515
)
516516

@@ -579,13 +579,13 @@ async def queue_solve(
579579
raise Exception(
580580
"Queued solves must have a formulation as specification"
581581
)
582-
problem, _outline = await self._prepare_problem(problem)
582+
prepared = await self._prepare_problem(problem)
583583
async with self._executor.execute(
584584
result_type=JsonExecutorResult,
585585
url="/queue-solve",
586586
method="POST",
587587
json_data=json_dict(
588-
problem=problem,
588+
problem=prepared.data,
589589
annotations=encode_annotations(annotations or []),
590590
),
591591
) as res:
@@ -849,3 +849,10 @@ async def _next_page() -> list[QueuedSolve]:
849849
for solve in solves:
850850
yield solve
851851
limit -= len(solves)
852+
853+
854+
@dataclasses.dataclass(frozen=True)
855+
class _PreparedProblem:
856+
data: Json
857+
outline: ProblemOutline
858+
inputs: SolveInputs

src/opvious/data/solves.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ class Solution:
302302
problem_summary: ProblemSummary
303303
"""Problem summary statistics"""
304304

305+
inputs: SolveInputs
306+
"""Problem inputs"""
307+
305308
outputs: SolveOutputs | None = dataclasses.field(default=None, repr=False)
306309
"""Solution data, present iff the solution is feasible"""
307310

@@ -313,6 +316,7 @@ def feasible(self) -> bool:
313316

314317
def solution_from_json(
315318
outline: ProblemOutline,
319+
inputs: SolveInputs,
316320
response_json: Any,
317321
problem_summary: ProblemSummary | None = None,
318322
) -> Solution:
@@ -341,6 +345,7 @@ def solution_from_json(
341345
outcome=outcome,
342346
problem_summary=problem_summary
343347
or problem_summary_from_json(response_json["summaries"]["problem"]),
348+
inputs=inputs,
344349
outputs=outputs,
345350
)
346351

0 commit comments

Comments
 (0)