symprog 0.1.0a1__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.
Files changed (45) hide show
  1. symprog/__init__.py +7 -0
  2. symprog/__main__.py +0 -0
  3. symprog/base.py +30 -0
  4. symprog/config.py +243 -0
  5. symprog/lang/__init__.py +2 -0
  6. symprog/lang/language.py +489 -0
  7. symprog/lang/languages/__init__.py +2 -0
  8. symprog/lang/languages/constraint.py +685 -0
  9. symprog/lang/languages/plp.py +1487 -0
  10. symprog/lang/languages/utils.py +212 -0
  11. symprog/registry.py +124 -0
  12. symprog/solvers/__init__.py +2 -0
  13. symprog/solvers/factory.py +46 -0
  14. symprog/solvers/klay_interface.py +161 -0
  15. symprog/solvers/mapper.py +283 -0
  16. symprog/solvers/numerical.py +612 -0
  17. symprog/solvers/numerical_interfaces/__init__.py +4 -0
  18. symprog/solvers/numerical_interfaces/jax.py +186 -0
  19. symprog/solvers/numerical_interfaces/python.py +175 -0
  20. symprog/solvers/numerical_interfaces/pytorch.py +377 -0
  21. symprog/solvers/numerical_interfaces/symbolic.py +494 -0
  22. symprog/solvers/semiring.py +470 -0
  23. symprog/solvers/semirings/__init__.py +6 -0
  24. symprog/solvers/semirings/common.py +306 -0
  25. symprog/solvers/semirings/count.py +76 -0
  26. symprog/solvers/semirings/entropy.py +118 -0
  27. symprog/solvers/semirings/formula.py +41 -0
  28. symprog/solvers/semirings/fuzzy.py +63 -0
  29. symprog/solvers/semirings/prob.py +114 -0
  30. symprog/solvers/semirings/viterbi.py +167 -0
  31. symprog/solvers/solver.py +1231 -0
  32. symprog/solvers/solver_interfaces/__init__.py +6 -0
  33. symprog/solvers/solver_interfaces/bdd.py +327 -0
  34. symprog/solvers/solver_interfaces/pysat.py +263 -0
  35. symprog/solvers/solver_interfaces/sdd.py +484 -0
  36. symprog/solvers/solver_interfaces/sympy.py +139 -0
  37. symprog/solvers/solver_interfaces/utils.py +6 -0
  38. symprog/solvers/solver_interfaces/z3.py +180 -0
  39. symprog/solvers/solver_utils.py +193 -0
  40. symprog/solvers/tensorization.py +411 -0
  41. symprog/version.py +1 -0
  42. symprog-0.1.0a1.dist-info/METADATA +348 -0
  43. symprog-0.1.0a1.dist-info/RECORD +45 -0
  44. symprog-0.1.0a1.dist-info/WHEEL +4 -0
  45. symprog-0.1.0a1.dist-info/licenses/LICENSE +202 -0
symprog/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .version import __version__
2
+ from .base import ExperimentalWarning
3
+ from .config import *
4
+ from .registry import *
5
+ from .solvers import Solver
6
+ from .lang import *
7
+
symprog/__main__.py ADDED
File without changes
symprog/base.py ADDED
@@ -0,0 +1,30 @@
1
+ import logging
2
+ from abc import ABC
3
+ from typing import TypeVar, Type
4
+
5
+ C = TypeVar("C", bound="UtilBase")
6
+
7
+ class UtilBase(ABC):
8
+ """Library base class"""
9
+
10
+ @property
11
+ def logging(self) -> logging.RootLogger:
12
+ """Returns an instance logger
13
+
14
+ :rtype: logging.RootLogger
15
+ """
16
+ return logging.getLogger(f"{__name__}.{type(self).__name__}") # type: ignore
17
+
18
+ @classmethod
19
+ def from_config(cls: Type[C], **kwargs) -> C:
20
+ """Builds an instance from configuration
21
+
22
+ Arguments:
23
+ config: The incoming configuration.
24
+ """
25
+ return cls(**kwargs)
26
+
27
+ class ExperimentalWarning(UserWarning):
28
+ """Warning for experimental functionality (e.g., tensorization, the JAX backend),
29
+ whose behavior and API may change. Can be silenced with
30
+ `warnings.filterwarnings("ignore", category=symprog.ExperimentalWarning)`."""
symprog/config.py ADDED
@@ -0,0 +1,243 @@
1
+ import json
2
+ import sys
3
+ import inspect
4
+ from abc import abstractmethod
5
+ from typing import TypeVar, Optional, Any, Type
6
+ from pydantic import BaseModel, Field, ConfigDict
7
+ from dataclasses import dataclass, fields, asdict
8
+ from argparse import ArgumentParser, BooleanOptionalAction
9
+
10
+ C = TypeVar("C", bound="BaseConfig")
11
+
12
+ __all__ = ["UtilityConfig", "FastConfig"]
13
+
14
+ class BaseConfig:
15
+ """Base class for configuration objects.
16
+
17
+ Configuration objects are used throughout for instantiating objects of all kinds.
18
+ """
19
+
20
+ def to_dict(self) -> dict[str, Any]:
21
+ """Maps configuration to a dictionary."""
22
+ raise NotImplementedError
23
+
24
+ def dump(self, path: str) -> None:
25
+ """Dumps the configuration to file
26
+
27
+ Arguments:
28
+ path: The path to dump configuration to.
29
+ """
30
+ with open(path, "w") as config_out:
31
+ config_out.write(json.dumps(self.to_dict(), indent=4))
32
+
33
+ @classmethod
34
+ def load_from_file(cls: Type[C], file_path: str) -> C:
35
+ """Loads a configuration from file.
36
+
37
+ Arguments:
38
+ file_path: The path to the file containing configuration
39
+ values.
40
+ """
41
+ with open(file_path) as config_in:
42
+ config_json = json.loads(config_in.read())
43
+ return cls(**config_json)
44
+
45
+ @classmethod
46
+ def build_parser(cls, **kwargs) -> ArgumentParser:
47
+ """Builds
48
+
49
+ Arguments:
50
+ argv: The command line input.
51
+ """
52
+ parser = ArgumentParser()
53
+ return cls._populate_parser(parser, **kwargs)
54
+
55
+ @classmethod
56
+ def _populate_parser(cls: Type[C], parser: ArgumentParser, **kwargs) -> ArgumentParser:
57
+ """Adds parameters to an argument parser base on underlyinh configuration class.
58
+
59
+ Arguments:
60
+ parser: The input argument parser.
61
+ """
62
+ raise NotImplementedError
63
+
64
+ @classmethod
65
+ def parse_cli(cls: Type[C], argv: Optional[list[str]] = sys.argv) -> C:
66
+ """Creates configuration instance from CLI arguments. Automatically
67
+ builds corresponding argparse parser.
68
+
69
+ Arguments:
70
+ argv: The raw CLI input.
71
+ """
72
+ parser = cls.build_parser()
73
+ config = parser.parse_args(argv)
74
+
75
+ return cls(**vars(config))
76
+
77
+ @classmethod
78
+ def from_kwargs(cls: Type[C], **kwargs: Any) -> C:
79
+ """Build config from direct kwargs only.
80
+
81
+ Arguments:
82
+ kwargs: The input kwargs.
83
+ """
84
+ return cls(**kwargs)
85
+
86
+ @classmethod
87
+ def build_config(
88
+ cls: Type[C],
89
+ argv: Optional[list[str]] = None,
90
+ config_path: Optional[str] = None,
91
+ **kwargs,
92
+ ) -> C:
93
+ """Builds a configuration instance, either from CLI input
94
+ or direct arguments.
95
+
96
+ Arguments:
97
+ argv: (Optional) command line configuration values.
98
+ Returns:
99
+ A configuration object with the values based on either CLI
100
+ input, an external file or direct keyword args (in that order).
101
+ """
102
+ if argv is not None:
103
+ return cls.parse_cli(argv)
104
+ elif config_path is not None:
105
+ return cls.load_from_file(config_path)
106
+ return cls.from_kwargs(**kwargs)
107
+
108
+
109
+ class UtilityConfig(BaseModel, BaseConfig):
110
+ """A Pydantic configuration implementation.
111
+
112
+ This kind of verification is most useful for complex configurations
113
+ that might involve certain types of pydantic-style verification.
114
+ """
115
+
116
+ model_config = ConfigDict(protected_namespaces=())
117
+
118
+ def to_dict(self) -> dict[str, Any]:
119
+ """Maps configuration to a dictionary.
120
+
121
+ Returns:
122
+ Returns a string indexed dictionary representation of the
123
+ configuration.
124
+ """
125
+ return self.model_dump()
126
+
127
+ # @classmethod
128
+ # def _populate_parser(
129
+ # cls: Type[C], parser: ArgumentParser, **kwargs
130
+ # ) -> Type[ArgumentParser]:
131
+ # """Adds parameters to an argument parser base on underlyinh configuration class.
132
+
133
+ # Arguments:
134
+ # parser: The input argument parser.
135
+ # """
136
+ # for field_name, field in cls.model_fields.items(): # type: ignore
137
+ # type_annot = None
138
+ # if inspect.isclass(field.annotation):
139
+ # type_annot = field.annotation
140
+
141
+ # args = {
142
+ # "help": field.description,
143
+ # "type": type_annot,
144
+ # "required": field.is_required(),
145
+ # "default": field.default,
146
+ # }
147
+
148
+ # if isinstance(field.default, bool):
149
+ # args.pop("type", None)
150
+ # # args["type"] = bool
151
+ # args["required"] = False
152
+ # args["action"] = BooleanOptionalAction
153
+
154
+ # parser.add_argument(f"--{field_name}", **args)
155
+
156
+ # return parser
157
+
158
+
159
+ class DataclassMeta(type):
160
+ """Metaclass for avoiding needing to use dataclass decorator explicitly when building config"""
161
+
162
+ def __new__(mcs, name, bases, dct):
163
+ cls = super().__new__(mcs, name, bases, dct)
164
+ return dataclass(cls) # type: ignore
165
+
166
+
167
+ class FastConfig(BaseConfig, metaclass=DataclassMeta):
168
+ """An alternative configuration based instead on dataclasses. Should be used for
169
+ lower-level object configurations.
170
+
171
+ NOTE: Do not use a dataclass decorator with this class, it is applied automatically.
172
+ """
173
+
174
+ @classmethod
175
+ def _populate_parser(cls, parser: ArgumentParser, **kwargs) -> ArgumentParser:
176
+ """Adds parameters to an argument parser base on underlying
177
+ configuration class.
178
+
179
+ Arguments:
180
+ parser: The input argument parser.
181
+ """
182
+ for field in fields(cls): # type: ignore
183
+
184
+ type_annot = None
185
+ if inspect.isclass(field.type):
186
+ type_annot = field.type
187
+
188
+ args = {"type": type_annot, "default": field.default}
189
+
190
+ if isinstance(field.type, bool):
191
+ args["type"] = bool
192
+ args["required"] = None
193
+ args["action"] = BooleanOptionalAction
194
+
195
+ parser.add_argument(f"--{field.name}", **args)
196
+
197
+ return parser
198
+
199
+ def to_dict(self) -> dict[str, Any]:
200
+ """Maps configuration to a dictionary.
201
+
202
+ Returns:
203
+ Returns a string indexed dictionary representation of the
204
+ configuration.
205
+ """
206
+ return asdict(self) # type: ignore
207
+
208
+ @classmethod
209
+ def from_dict(
210
+ cls: Type[C],
211
+ name: Optional[str] = "config",
212
+ data: Optional[dict[str, Any]] = None,
213
+ ) -> Type[C]:
214
+ """Create new FastConfig subclass from a dictionary.
215
+
216
+ Each key becomes a field; its type is inferred via `type(value)`,
217
+ and its default is `value`.
218
+
219
+ Arguments:
220
+ name: The name of the new configuration class.
221
+ data: Key/value pairs to turn into fields.
222
+
223
+ For example:
224
+
225
+ >>> from symprog import FastConfig
226
+ >>> config = FastConfig.from_dict("new", {"a" : 10})()
227
+ >>> config.a
228
+ 10
229
+ """
230
+ if data is None:
231
+ data = {}
232
+ annotations: dict[str, type] = {}
233
+ namespace: dict[str, Any] = {}
234
+
235
+ for key, value in data.items():
236
+ annotations[key] = type(value)
237
+ namespace[key] = value
238
+
239
+ namespace["__annotations__"] = annotations
240
+
241
+ meta = type(cls)
242
+ DynamicConfig = meta(name, (cls,), namespace)
243
+ return DynamicConfig
@@ -0,0 +1,2 @@
1
+ from .languages import *
2
+ from .language import *