From 1b8e8dace3f3ab391efe74b918bbf4f11ce16ef6 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Thu, 19 Dec 2024 09:26:49 -0500 Subject: [PATCH] fix docs? --- src/ilpy/_functional.py | 15 +++++++---- src/ilpy/_solver.py | 7 +++-- src/ilpy/{event_data.pyi => event_data.py} | 30 +++++++++++++++++----- src/ilpy/solver_backends/_base.py | 8 +++--- 4 files changed, 40 insertions(+), 20 deletions(-) rename src/ilpy/{event_data.pyi => event_data.py} (83%) diff --git a/src/ilpy/_functional.py b/src/ilpy/_functional.py index 51a8c36..3de78e5 100644 --- a/src/ilpy/_functional.py +++ b/src/ilpy/_functional.py @@ -9,7 +9,9 @@ from .solver_backends import Preference if TYPE_CHECKING: - from collections.abc import Iterable, Mapping, Sequence + from collections.abc import Iterable, Sequence + + from .event_data import EventData ConstraintTuple = tuple[list[float], Relation | str, float] SenseType = Sense | Literal["minimize", "maximize"] @@ -24,7 +26,7 @@ def solve( variable_type: VariableTypeType = VariableType.Continuous, verbose: bool = False, preference: PreferenceType = Preference.Any, - on_event: Callable[[Mapping], None] | None = None, + on_event: Callable[[EventData], None] | None = None, ) -> Solution: """Solve an objective subject to constraints. @@ -58,7 +60,7 @@ def solve( preference : Preference | Literal["any", "cplex", "gurobi", "scip"] Backend preference, either an `ilpy.Preference` or a string in {"any", "cplex", "gurobi", "scip"}. By default, `Preference.Any`. - on_event : Callable[[Mapping], None], optional + on_event : Callable[[EventData], None], optional A callback function that is called when an event occurs, by default None. The callback function should accept a dict which will contain statics about the solving or presolving process. You can import `ilpy.EventData` from ilpy and use @@ -76,13 +78,16 @@ def solve( def callback(data: EventData) -> None: - # backend and event_type are guaranteed to be present - # they will narrow down the available keys if data["backend"] == "gurobi" and data["event_type"] == "MIP": print(data["gap"]) ilpy.solve(..., on_event=callback) + + Returns + ------- + Solution + The solution to the problem. """ if isinstance(sense, str): sense = Sense[sense.title()] diff --git a/src/ilpy/_solver.py b/src/ilpy/_solver.py index e614c91..dbe95e1 100644 --- a/src/ilpy/_solver.py +++ b/src/ilpy/_solver.py @@ -7,12 +7,13 @@ from .solver_backends import Preference, SolverBackend, create_solver_backend if TYPE_CHECKING: - from collections.abc import Iterator, Mapping, Sequence + from collections.abc import Iterator, Sequence import numpy as np from ._components import Constraint, Constraints, Objective from ._constants import SolverStatus, VariableType + from .event_data import EventData @dataclass @@ -82,9 +83,7 @@ def set_num_threads(self, num_threads: int) -> None: def set_verbose(self, verbose: bool) -> None: self._backend.set_verbose(verbose) - def set_event_callback( - self, callback: Callable[[Mapping[str, float | str]], None] | None - ) -> None: + def set_event_callback(self, callback: Callable[[EventData], None] | None) -> None: self._backend.set_event_callback(callback) def solve(self) -> Solution: diff --git a/src/ilpy/event_data.pyi b/src/ilpy/event_data.py similarity index 83% rename from src/ilpy/event_data.pyi rename to src/ilpy/event_data.py index a285c4e..960ae35 100644 --- a/src/ilpy/event_data.pyi +++ b/src/ilpy/event_data.py @@ -1,18 +1,25 @@ -from typing import Literal, TypeAlias, TypedDict +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict __all__ = ["EventData", "GurobiData", "SCIPData"] -GurobiEventType: TypeAlias = Literal[ - "PRESOLVE", "SIMPLEX", "MIP", "MIPSOL", "MIPNODE", "MESSAGE", "UNKNOWN" -] -SCIPEventType: TypeAlias = Literal["PRESOLVEROUND", "BESTSOLFOUND"] -EventType: TypeAlias = GurobiEventType | SCIPEventType +if TYPE_CHECKING: + from typing import Literal, TypeAlias + + GurobiEventType: TypeAlias = Literal[ + "PRESOLVE", "SIMPLEX", "MIP", "MIPSOL", "MIPNODE", "MESSAGE", "UNKNOWN" + ] + SCIPEventType: TypeAlias = Literal["PRESOLVEROUND", "BESTSOLFOUND"] + EventType: TypeAlias = GurobiEventType | SCIPEventType + class _GurobiData(TypedDict, total=False): backend: Literal["gurobi"] runtime: float # Elapsed solver runtime (seconds). work: float # Elapsed solver work (work units). + class GurobiPresolve(_GurobiData): event_type: Literal["PRESOLVE"] pre_coldel: int @@ -21,6 +28,7 @@ class GurobiPresolve(_GurobiData): pre_bndchg: int pre_coechg: int + class GurobiSimplex(_GurobiData): event_type: Literal["SIMPLEX"] itrcnt: float @@ -29,6 +37,7 @@ class GurobiSimplex(_GurobiData): dualinf: float ispert: int + class _GurobiMipData(_GurobiData): objbst: float objbnd: float @@ -40,24 +49,29 @@ class _GurobiMipData(_GurobiData): dualbound: float # alias for objbnd gap: float # calculated manually from objbst and objbnd + class GurobiMip(_GurobiMipData): event_type: Literal["MIP"] cutcnt: int nodlft: float itrcnt: float + class GurobiMipSol(_GurobiMipData): event_type: Literal["MIPSOL"] obj: float + class GurobiMipNode(_GurobiMipData): event_type: Literal["MIPNODE"] status: int + class GurobiMessage(_GurobiData): event_type: Literal["MESSAGE"] message: str + GurobiData = ( GurobiPresolve | GurobiSimplex @@ -67,10 +81,12 @@ class GurobiMessage(_GurobiData): | GurobiMessage ) + class _SCIPData(TypedDict, total=False): backend: Literal["scip"] deterministictime: float + class SCIPPresolve(_SCIPData): event_type: Literal["PRESOLVEROUND"] nativeconss: int @@ -83,6 +99,7 @@ class SCIPPresolve(_SCIPData): cutoffbound: float nfixedvars: int + class SCIPBestSol(_SCIPData): event_type: Literal["BESTSOLFOUND"] avgdualbound: float @@ -100,6 +117,7 @@ class SCIPBestSol(_SCIPData): nlps: int nnzs: int + SCIPData = SCIPPresolve | SCIPBestSol EventData = GurobiData | SCIPData diff --git a/src/ilpy/solver_backends/_base.py b/src/ilpy/solver_backends/_base.py index b4da162..5645518 100644 --- a/src/ilpy/solver_backends/_base.py +++ b/src/ilpy/solver_backends/_base.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, Callable if TYPE_CHECKING: from collections.abc import Mapping @@ -16,10 +16,8 @@ class SolverBackend(ABC): def __init__(self) -> None: self._event_callback: Callable[[EventData], None] | None = None - def set_event_callback( - self, callback: Callable[[Mapping[str, float | str]], None] | None - ) -> None: - self._event_callback = cast("Callable[[EventData], None] | None", callback) + def set_event_callback(self, callback: Callable[[EventData], None] | None) -> None: + self._event_callback = callback def emit_event_data(self, data: EventData) -> None: if self._event_callback: