luna-usecase-template 0.0.2a2__py3-none-any.whl
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.
- luna_usecase_template/__init__.py +15 -0
- luna_usecase_template/base.py +50 -0
- luna_usecase_template/collection.py +29 -0
- luna_usecase_template/data.py +31 -0
- luna_usecase_template/formulation.py +45 -0
- luna_usecase_template/instance.py +52 -0
- luna_usecase_template/named.py +19 -0
- luna_usecase_template/registry.py +219 -0
- luna_usecase_template/solution.py +38 -0
- luna_usecase_template-0.0.2a2.dist-info/METADATA +129 -0
- luna_usecase_template-0.0.2a2.dist-info/RECORD +12 -0
- luna_usecase_template-0.0.2a2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .collection import UcInstanceCollection
|
|
2
|
+
from .data import UcData
|
|
3
|
+
from .formulation import UcFormulation
|
|
4
|
+
from .instance import UcInstance
|
|
5
|
+
from .registry import Registry
|
|
6
|
+
from .solution import UcSolution
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Registry",
|
|
10
|
+
"UcData",
|
|
11
|
+
"UcFormulation",
|
|
12
|
+
"UcInstance",
|
|
13
|
+
"UcInstanceCollection",
|
|
14
|
+
"UcSolution",
|
|
15
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UcBaseModel(BaseModel):
|
|
8
|
+
"""Use case base model with config."""
|
|
9
|
+
|
|
10
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
11
|
+
|
|
12
|
+
def store(self, path: Path | str) -> None:
|
|
13
|
+
"""Store model to file.
|
|
14
|
+
|
|
15
|
+
Supported file types are `json` and `pkl`
|
|
16
|
+
"""
|
|
17
|
+
if isinstance(path, str):
|
|
18
|
+
path = Path(path)
|
|
19
|
+
|
|
20
|
+
match path.suffix:
|
|
21
|
+
case ".json":
|
|
22
|
+
with open(path, "w") as f:
|
|
23
|
+
f.write(self.model_dump_json())
|
|
24
|
+
case _:
|
|
25
|
+
msg = (
|
|
26
|
+
f"Unsupported suffix '{path.suffix}'. "
|
|
27
|
+
f"Only .json is supported as of now."
|
|
28
|
+
)
|
|
29
|
+
raise ValueError(msg)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def load(cls, path: Path | str) -> Self:
|
|
33
|
+
"""Load model from file.
|
|
34
|
+
|
|
35
|
+
Supported file types are `json` and `pkl`
|
|
36
|
+
"""
|
|
37
|
+
if isinstance(path, str):
|
|
38
|
+
path = Path(path)
|
|
39
|
+
|
|
40
|
+
match path.suffix:
|
|
41
|
+
case ".json":
|
|
42
|
+
with open(path) as f:
|
|
43
|
+
return cls.model_validate_json(f.read())
|
|
44
|
+
|
|
45
|
+
case _:
|
|
46
|
+
msg = (
|
|
47
|
+
f"Unsupported suffix '{path.suffix}'. "
|
|
48
|
+
f"Only .json is supported as of now."
|
|
49
|
+
)
|
|
50
|
+
raise ValueError(msg)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from pydantic import ModelWrapValidatorHandler, model_validator
|
|
4
|
+
|
|
5
|
+
from .base import UcBaseModel
|
|
6
|
+
from .instance import UcInstance
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UcInstanceCollection[I: UcInstance](UcBaseModel):
|
|
10
|
+
"""Instance collection base class."""
|
|
11
|
+
|
|
12
|
+
instances: list[I]
|
|
13
|
+
|
|
14
|
+
@model_validator(mode="wrap")
|
|
15
|
+
@classmethod
|
|
16
|
+
def validate_instance(
|
|
17
|
+
cls, data: ..., handler: ModelWrapValidatorHandler[Self]
|
|
18
|
+
) -> Self:
|
|
19
|
+
"""Validate pydantic model."""
|
|
20
|
+
if cls.__name__ != "UcInstanceCollection":
|
|
21
|
+
return handler(data)
|
|
22
|
+
|
|
23
|
+
from .registry import Registry # noqa: PLC0415
|
|
24
|
+
|
|
25
|
+
return Registry.collection_adapter(base_class=cls).validate_python(data)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def generate_predefined(cls) -> Self:
|
|
29
|
+
"""Generate a predefined instance collection of the use case."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from inspect import isabstract
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
from pydantic import ModelWrapValidatorHandler, model_validator
|
|
6
|
+
|
|
7
|
+
from .base import UcBaseModel
|
|
8
|
+
from .named import Named
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UcData(UcBaseModel, Named):
|
|
12
|
+
"""Use Case data base class."""
|
|
13
|
+
|
|
14
|
+
data_name: str
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def to_string(self) -> str:
|
|
18
|
+
"""Return the model data definition."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
@model_validator(mode="wrap")
|
|
22
|
+
@classmethod
|
|
23
|
+
def validate_instance(
|
|
24
|
+
cls, data: ..., handler: ModelWrapValidatorHandler[Self]
|
|
25
|
+
) -> Self:
|
|
26
|
+
"""Validate pydantic model."""
|
|
27
|
+
if isabstract(cls):
|
|
28
|
+
from .registry import Registry # noqa: PLC0415
|
|
29
|
+
|
|
30
|
+
return Registry.data_adapter(base_class=cls).validate_python(data)
|
|
31
|
+
return handler(data)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from inspect import isabstract
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
from luna_model import Model, Solution
|
|
6
|
+
from pydantic import ModelWrapValidatorHandler, model_validator
|
|
7
|
+
|
|
8
|
+
from .base import UcBaseModel
|
|
9
|
+
from .data import UcData
|
|
10
|
+
from .named import Named
|
|
11
|
+
from .solution import UcSolution
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UcFormulation[D: UcData, S: UcSolution](UcBaseModel, Named):
|
|
15
|
+
"""Use case formulation base class."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def to_string(data: D) -> str:
|
|
20
|
+
"""Print the formulation."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def formulate(data: D) -> Model:
|
|
26
|
+
"""Formulate the use case as a concrete AqModel."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def interpret(solution: Solution, data: D) -> S:
|
|
32
|
+
"""Interpret the solution to the AqModel."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
@model_validator(mode="wrap")
|
|
36
|
+
@classmethod
|
|
37
|
+
def validate_instance(
|
|
38
|
+
cls, data: ..., handler: ModelWrapValidatorHandler[Self]
|
|
39
|
+
) -> Self:
|
|
40
|
+
"""Validate the pydantic model."""
|
|
41
|
+
if isabstract(cls):
|
|
42
|
+
from .registry import Registry # noqa: PLC0415
|
|
43
|
+
|
|
44
|
+
return Registry.formulation_adapter(base_class=cls).validate_python(data)
|
|
45
|
+
return handler(data)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from luna_model import Model, Solution
|
|
4
|
+
from pydantic import ModelWrapValidatorHandler, model_validator
|
|
5
|
+
|
|
6
|
+
from .base import UcBaseModel
|
|
7
|
+
from .data import UcData
|
|
8
|
+
from .formulation import UcFormulation
|
|
9
|
+
from .solution import UcSolution
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UcInstance[D: UcData, F: UcFormulation, S: UcSolution](UcBaseModel):
|
|
13
|
+
"""Use case instance is the collection of the data and formulation."""
|
|
14
|
+
|
|
15
|
+
data: D
|
|
16
|
+
formulation: F
|
|
17
|
+
|
|
18
|
+
def to_string(self) -> str:
|
|
19
|
+
"""Get a string representation of the instance."""
|
|
20
|
+
data_descr = "Data:"
|
|
21
|
+
data_descr += self.data.to_string()
|
|
22
|
+
form_descr = "Formulation:"
|
|
23
|
+
form_descr += self.formulation.to_string(self.data)
|
|
24
|
+
return f"{data_descr}\n{form_descr}"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
"""Get name of the instance."""
|
|
29
|
+
return self.formulation.name + "<" + self.data.data_name + ">"
|
|
30
|
+
|
|
31
|
+
def formulate(self) -> Model:
|
|
32
|
+
"""Formulate the instance as AqModel."""
|
|
33
|
+
model = self.formulation.formulate(self.data)
|
|
34
|
+
model.name = self.name
|
|
35
|
+
return model
|
|
36
|
+
|
|
37
|
+
def interpret(self, solution: Solution) -> S:
|
|
38
|
+
"""Interpret the solution to the AqModel."""
|
|
39
|
+
return self.formulation.interpret(solution=solution, data=self.data)
|
|
40
|
+
|
|
41
|
+
@model_validator(mode="wrap")
|
|
42
|
+
@classmethod
|
|
43
|
+
def validate_instance(
|
|
44
|
+
cls, data: ..., handler: ModelWrapValidatorHandler[Self]
|
|
45
|
+
) -> Self:
|
|
46
|
+
"""Validate the pydantic model."""
|
|
47
|
+
if cls.__name__ != "UcInstance":
|
|
48
|
+
return handler(data)
|
|
49
|
+
|
|
50
|
+
from .registry import Registry # noqa: PLC0415
|
|
51
|
+
|
|
52
|
+
return Registry.instance_adapter(base_class=cls).validate_python(data)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Literal, get_args, get_origin
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Named[N: str]:
|
|
5
|
+
"""Named pydantic class mixin."""
|
|
6
|
+
|
|
7
|
+
name: N
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def get_name(cls) -> str:
|
|
11
|
+
"""Return the name of the class."""
|
|
12
|
+
name_type = cls.__dict__["__annotations__"]["name"]
|
|
13
|
+
if not (get_origin(name_type) is Literal and len(get_args(name_type)) == 1):
|
|
14
|
+
msg = (
|
|
15
|
+
f"Invalid `name` field in '{cls.__name__}'. "
|
|
16
|
+
"Required to be a Literal of a single string."
|
|
17
|
+
)
|
|
18
|
+
raise RuntimeError(msg)
|
|
19
|
+
return get_args(name_type)[0]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import importlib
|
|
3
|
+
import inspect
|
|
4
|
+
import pkgutil
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from typing import ClassVar, Union
|
|
8
|
+
|
|
9
|
+
from pydantic import TypeAdapter
|
|
10
|
+
|
|
11
|
+
from .collection import UcInstanceCollection
|
|
12
|
+
from .data import UcData
|
|
13
|
+
from .formulation import UcFormulation
|
|
14
|
+
from .instance import UcInstance
|
|
15
|
+
from .solution import UcSolution
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _check_concrete[T: type](f: Callable[[T], T]) -> Callable[[T], T]:
|
|
19
|
+
@functools.wraps(f)
|
|
20
|
+
def _inner(t: T) -> T:
|
|
21
|
+
if inspect.isabstract(t):
|
|
22
|
+
msg = (
|
|
23
|
+
f"Abstract class '{t.__name__}' detected. "
|
|
24
|
+
"Registry can only hold concrete models."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
raise ValueError(msg)
|
|
28
|
+
return f(t)
|
|
29
|
+
|
|
30
|
+
return _inner
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _generic_adapter(
|
|
34
|
+
types: Iterable[type], base_class: type | None = None
|
|
35
|
+
) -> TypeAdapter:
|
|
36
|
+
if base_class is not None:
|
|
37
|
+
types = [t for t in types if issubclass(t, base_class)]
|
|
38
|
+
else:
|
|
39
|
+
types = list(types)
|
|
40
|
+
if len(types) == 0:
|
|
41
|
+
msg = "No suitable types registered."
|
|
42
|
+
raise ValueError(msg)
|
|
43
|
+
return TypeAdapter(Union[*types])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Registry:
|
|
47
|
+
"""Registry of all use case classes."""
|
|
48
|
+
|
|
49
|
+
_instances: ClassVar[list[type[UcInstance]]] = []
|
|
50
|
+
_datas: ClassVar[list[type[UcData]]] = []
|
|
51
|
+
_formulations: ClassVar[list[type[UcFormulation]]] = []
|
|
52
|
+
_solutions: ClassVar[list[type[UcSolution]]] = []
|
|
53
|
+
_collections: ClassVar[list[type[UcInstanceCollection]]] = []
|
|
54
|
+
_registered: ClassVar[set[type]] = set()
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def adapter() -> TypeAdapter:
|
|
58
|
+
"""Get the type adapter of all registered classes."""
|
|
59
|
+
types = (
|
|
60
|
+
Registry._instances
|
|
61
|
+
+ Registry._datas
|
|
62
|
+
+ Registry._formulations
|
|
63
|
+
+ Registry._solutions
|
|
64
|
+
+ Registry._collections
|
|
65
|
+
)
|
|
66
|
+
return TypeAdapter(Union[*types])
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _check_registered[T: type](f: Callable[[T], T]) -> Callable[[T], T]:
|
|
70
|
+
@functools.wraps(f)
|
|
71
|
+
def _inner(t: T) -> T:
|
|
72
|
+
if t not in Registry._registered:
|
|
73
|
+
Registry._registered.add(t)
|
|
74
|
+
return f(t)
|
|
75
|
+
return t
|
|
76
|
+
|
|
77
|
+
return _inner
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def instance_adapter(base_class: None | type[UcInstance] = None) -> TypeAdapter:
|
|
81
|
+
"""Get the type adapter of all registered instance classes."""
|
|
82
|
+
return _generic_adapter(Registry._instances, base_class)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def data_adapter(base_class: None | type[UcData] = None) -> TypeAdapter:
|
|
86
|
+
"""Get the type adapter of all registered data classes."""
|
|
87
|
+
return _generic_adapter(Registry._datas, base_class)
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def formulation_adapter(
|
|
91
|
+
base_class: None | type[UcFormulation] = None,
|
|
92
|
+
) -> TypeAdapter:
|
|
93
|
+
"""Get the type adapter of all registered formulation classes."""
|
|
94
|
+
return _generic_adapter(Registry._formulations, base_class)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def solution_adapter(base_class: None | type[UcSolution] = None) -> TypeAdapter:
|
|
98
|
+
"""Get the type adapter of all registered solution classes."""
|
|
99
|
+
return _generic_adapter(Registry._solutions, base_class)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def collection_adapter(
|
|
103
|
+
base_class: None | type[UcInstanceCollection] = None,
|
|
104
|
+
) -> TypeAdapter:
|
|
105
|
+
"""Get the type adapter of all registered collection classes."""
|
|
106
|
+
return _generic_adapter(Registry._collections, base_class)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
@_check_registered
|
|
110
|
+
@_check_concrete
|
|
111
|
+
def add_instance[T: type[UcInstance]](instance: T) -> T:
|
|
112
|
+
"""Register an instance class."""
|
|
113
|
+
Registry._instances.append(instance)
|
|
114
|
+
return instance
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
@_check_registered
|
|
118
|
+
@_check_concrete
|
|
119
|
+
def add_data[T: type[UcData]](data: T) -> T:
|
|
120
|
+
"""Register a data class."""
|
|
121
|
+
Registry._datas.append(data)
|
|
122
|
+
return data
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
@_check_registered
|
|
126
|
+
@_check_concrete
|
|
127
|
+
def add_formulation[T: type[UcFormulation]](formulation: T) -> T:
|
|
128
|
+
"""Register a formulation class."""
|
|
129
|
+
Registry._formulations.append(formulation)
|
|
130
|
+
return formulation
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
@_check_registered
|
|
134
|
+
@_check_concrete
|
|
135
|
+
def add_solution[T: type[UcSolution]](solution: T) -> T:
|
|
136
|
+
"""Register a solution class."""
|
|
137
|
+
Registry._solutions.append(solution)
|
|
138
|
+
return solution
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
@_check_registered
|
|
142
|
+
@_check_concrete
|
|
143
|
+
def add_collection[T: type[UcInstanceCollection]](collection: T) -> T:
|
|
144
|
+
"""Register a collection class."""
|
|
145
|
+
Registry._collections.append(collection)
|
|
146
|
+
return collection
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def add[T: type](input_class: T) -> T:
|
|
150
|
+
"""Register any class."""
|
|
151
|
+
if issubclass(input_class, UcInstance):
|
|
152
|
+
Registry.add_instance(input_class)
|
|
153
|
+
elif issubclass(input_class, UcData):
|
|
154
|
+
Registry.add_data(input_class)
|
|
155
|
+
elif issubclass(input_class, UcFormulation):
|
|
156
|
+
Registry.add_formulation(input_class)
|
|
157
|
+
elif issubclass(input_class, UcSolution):
|
|
158
|
+
Registry.add_solution(input_class)
|
|
159
|
+
elif issubclass(input_class, UcInstanceCollection):
|
|
160
|
+
Registry.add_collection(input_class)
|
|
161
|
+
else:
|
|
162
|
+
msg = f"Attempted to register unknown type {input_class}."
|
|
163
|
+
raise TypeError(msg)
|
|
164
|
+
return input_class
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def can_register(input_class: type) -> bool:
|
|
168
|
+
"""Check if a class can be registered."""
|
|
169
|
+
if input_class in Registry._registered or inspect.isabstract(input_class):
|
|
170
|
+
return False
|
|
171
|
+
if input_class.__name__ in ("UcInstance", "UcInstanceCollection"):
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
return bool(
|
|
175
|
+
issubclass(input_class, UcInstance)
|
|
176
|
+
or issubclass(input_class, UcData)
|
|
177
|
+
or issubclass(input_class, UcFormulation)
|
|
178
|
+
or issubclass(input_class, UcSolution)
|
|
179
|
+
or issubclass(input_class, UcInstanceCollection)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def add_module(mod: ModuleType) -> None:
|
|
184
|
+
"""Add a whole module."""
|
|
185
|
+
for info in pkgutil.walk_packages(mod.__path__, prefix=f"{mod.__name__}."):
|
|
186
|
+
submod = importlib.import_module(info.name) # nosemgrep: non-literal-import
|
|
187
|
+
|
|
188
|
+
for _, obj in inspect.getmembers(submod, inspect.isclass):
|
|
189
|
+
if Registry.can_register(obj):
|
|
190
|
+
Registry.add(obj)
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def validate(s: ...) -> ...:
|
|
194
|
+
"""Validate a python object."""
|
|
195
|
+
return Registry.adapter().validate_python(s)
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def validate_json(s: str | bytes | bytearray) -> ...:
|
|
199
|
+
"""Validate a json string."""
|
|
200
|
+
return Registry.adapter().validate_json(s)
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def to_string() -> str:
|
|
204
|
+
"""Return a string representation of the registry and contents."""
|
|
205
|
+
lines = [
|
|
206
|
+
"UcData: " + ", ".join(f"{t.__name__}" for t in Registry._datas),
|
|
207
|
+
"UcSolution: " + ", ".join(f"{t.__name__}" for t in Registry._solutions),
|
|
208
|
+
"UcInstance: " + ", ".join(f"{t.__name__}" for t in Registry._instances),
|
|
209
|
+
"UcFormulation: "
|
|
210
|
+
+ ", ".join(f"{t.__name__}" for t in Registry._formulations),
|
|
211
|
+
"UcInstanceCollection: "
|
|
212
|
+
+ ", ".join(f"{t.__name__}" for t in Registry._collections),
|
|
213
|
+
]
|
|
214
|
+
return "\n".join(lines)
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def print() -> None:
|
|
218
|
+
"""Print the registry and contents."""
|
|
219
|
+
print(Registry.to_string()) # noqa: T201
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from inspect import isabstract
|
|
3
|
+
from typing import Protocol, Self, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from pydantic import ModelWrapValidatorHandler, model_validator
|
|
6
|
+
|
|
7
|
+
from .base import UcBaseModel
|
|
8
|
+
from .data import UcData
|
|
9
|
+
from .named import Named
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UcSolution(UcBaseModel, Named):
|
|
13
|
+
"""Use case solution base class."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def to_string(self) -> str:
|
|
17
|
+
"""Print the solution."""
|
|
18
|
+
|
|
19
|
+
@model_validator(mode="wrap")
|
|
20
|
+
@classmethod
|
|
21
|
+
def validate_solution(
|
|
22
|
+
cls, data: ..., handler: ModelWrapValidatorHandler[Self]
|
|
23
|
+
) -> Self:
|
|
24
|
+
"""Validate the pydantic model."""
|
|
25
|
+
if isabstract(cls):
|
|
26
|
+
from .registry import Registry # noqa: PLC0415
|
|
27
|
+
|
|
28
|
+
return Registry.solution_adapter(base_class=cls).validate_python(data)
|
|
29
|
+
return handler(data)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@runtime_checkable
|
|
33
|
+
class Visulaizable[T: UcData](Protocol):
|
|
34
|
+
"""Visualizable protocol."""
|
|
35
|
+
|
|
36
|
+
def visualize(self, data: T | None = None) -> None:
|
|
37
|
+
"""Visualize the solution."""
|
|
38
|
+
...
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: luna-usecase-template
|
|
3
|
+
Version: 0.0.2a2
|
|
4
|
+
Summary: Template for optimization use cases.
|
|
5
|
+
Keywords: aqarios,luna,quantum computing,quantum optimization,optimization,sdk
|
|
6
|
+
Author: Aqarios GmbH
|
|
7
|
+
Author-email: Aqarios GmbH <info@aqarios.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering
|
|
12
|
+
Requires-Dist: luna-model>=0.5.3
|
|
13
|
+
Requires-Dist: pydantic>=2.12.5
|
|
14
|
+
Maintainer: Maximilian Janetschek, David Bucher, Jonas Blenninger
|
|
15
|
+
Maintainer-email: Maximilian Janetschek <maximilian.janetschek@aqarios.com>, David Bucher <david.bucher@aqarios.com>, Jonas Blenninger <jonas.blenninger@aqarios.com>
|
|
16
|
+
Requires-Python: >=3.11.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Use Case Library
|
|
20
|
+
|
|
21
|
+
A Python SDK for defining optimization use cases in the [Luna](https://aqarios.com) quantum optimization framework.
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
`usecase-lib` provides a structured template for defining optimization problems with:
|
|
26
|
+
|
|
27
|
+
- **Data** — Problem input data (`UcData`)
|
|
28
|
+
- **Formulation** — How the problem maps to a mathematical model (`UcFormulation`)
|
|
29
|
+
- **Solution** — Interpretation of solver output (`UcSolution`)
|
|
30
|
+
- **Instance** — A concrete pairing of data + formulation (`UcInstance`)
|
|
31
|
+
- **Collection** — Groups of instances (`UcInstanceCollection`)
|
|
32
|
+
|
|
33
|
+
All types support JSON serialization and polymorphic deserialization via a central `Registry`.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python >= 3.13.0
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Define a use case
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from typing import Literal, override
|
|
45
|
+
|
|
46
|
+
from luna_model import Model, Solution
|
|
47
|
+
from usecase_lib import Registry, UcData, UcFormulation, UcInstance, UcSolution
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class MyData(UcData):
|
|
51
|
+
name: Literal["my_data"] = "my_data"
|
|
52
|
+
values: list[float]
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def to_string(self) -> str:
|
|
56
|
+
return str(self.values)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MySolution(UcSolution):
|
|
60
|
+
name: Literal["my_solution"] = "my_solution"
|
|
61
|
+
result: float
|
|
62
|
+
|
|
63
|
+
@override
|
|
64
|
+
def to_string(self) -> str:
|
|
65
|
+
return str(self.result)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class MyFormulation(UcFormulation[MyData, MySolution]):
|
|
69
|
+
name: Literal["my_formulation"] = "my_formulation"
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
@staticmethod
|
|
73
|
+
def to_string(data: MyData) -> str:
|
|
74
|
+
return "minimize sum(values)"
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
@staticmethod
|
|
78
|
+
def formulate(data: MyData) -> Model:
|
|
79
|
+
m = Model("MyProblem")
|
|
80
|
+
# ... build your model ...
|
|
81
|
+
return m
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
@staticmethod
|
|
85
|
+
def interpret(solution: Solution, data: MyData) -> MySolution:
|
|
86
|
+
return MySolution(result=0.0)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class MyInstance(UcInstance):
|
|
90
|
+
data: MyData
|
|
91
|
+
formulation: MyFormulation
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Register and serialize
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# Register classes (also works as @Registry.add decorator)
|
|
98
|
+
Registry.add(MyData)
|
|
99
|
+
Registry.add(MySolution)
|
|
100
|
+
Registry.add(MyFormulation)
|
|
101
|
+
Registry.add(MyInstance)
|
|
102
|
+
|
|
103
|
+
# Create and serialize
|
|
104
|
+
data = MyData(data_name="example", values=[1.0, 2.0, 3.0])
|
|
105
|
+
instance = MyInstance(data=data, formulation=MyFormulation())
|
|
106
|
+
instance.store("instance.json")
|
|
107
|
+
|
|
108
|
+
# Deserialize with polymorphic type dispatch
|
|
109
|
+
loaded = MyInstance.load("instance.json")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Architecture
|
|
113
|
+
|
|
114
|
+
Every concrete `UcData`, `UcFormulation`, and `UcSolution` subclass must define a `name` field as a `Literal["unique_string"]`. This enables the `Registry` to distinguish types during deserialization.
|
|
115
|
+
|
|
116
|
+
Abstract base classes use Pydantic wrap validators to automatically dispatch validation to the correct registered subclass, so you can deserialize from a base type without knowing the concrete type ahead of time.
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
uv sync # Install dependencies
|
|
122
|
+
ruff check . --fix # Lint with auto-fix
|
|
123
|
+
ruff format . # Format code
|
|
124
|
+
uv build # Build package
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
Apache-2.0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
luna_usecase_template/__init__.py,sha256=FlETY5PXi0M255ShJn2oz76bns9rKu9G7a2beEulpsI,336
|
|
2
|
+
luna_usecase_template/base.py,sha256=1IC08mmpBcAq-ANl1ggfoSoTQqSmSIZTaBqLFHscRu0,1397
|
|
3
|
+
luna_usecase_template/collection.py,sha256=paC6cs6tg545_T3InPG1y73lxQl96azW55h4BLMcugw,831
|
|
4
|
+
luna_usecase_template/data.py,sha256=tu1vUXO0HN9s695Hc29ZukND0wYGmIZDgnFQzA4WwQo,807
|
|
5
|
+
luna_usecase_template/formulation.py,sha256=BZzPLcoOKgplkWD3fw8MD03_dXx5QH4KYZxL-oIIpA0,1250
|
|
6
|
+
luna_usecase_template/instance.py,sha256=R3NYiFsirdq5wKK02W-tnQQWV9v3q-_JwImrQRYDypw,1683
|
|
7
|
+
luna_usecase_template/named.py,sha256=qtQJD5tdNmILKCmc7sNneZKu-aIGRlndM5AWt5LAEkA,593
|
|
8
|
+
luna_usecase_template/registry.py,sha256=cIHKhZaTHhGErmmw7ngvLZmBl2xihwY6tK21zUnmZy4,7506
|
|
9
|
+
luna_usecase_template/solution.py,sha256=XI9pGB46sLaecMBFdpJTx6b4zOV0imls6nFYXBDRJ94,1033
|
|
10
|
+
luna_usecase_template-0.0.2a2.dist-info/WHEEL,sha256=mydTeHxOpFHo-DnYhAd_3ATePms-g4rrYvM7wJK8P-U,80
|
|
11
|
+
luna_usecase_template-0.0.2a2.dist-info/METADATA,sha256=9xfwux_zaArnxlet6EKugpPUhZ4T93JIwadHB72UKS0,3775
|
|
12
|
+
luna_usecase_template-0.0.2a2.dist-info/RECORD,,
|