aspalchemy 1.0.0__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.
- aspalchemy-1.0.0/LICENSE.txt +21 -0
- aspalchemy-1.0.0/PKG-INFO +188 -0
- aspalchemy-1.0.0/docs/README.md +176 -0
- aspalchemy-1.0.0/pyproject.toml +81 -0
- aspalchemy-1.0.0/src/aspalchemy/__init__.py +171 -0
- aspalchemy-1.0.0/src/aspalchemy/aggregates.py +230 -0
- aspalchemy-1.0.0/src/aspalchemy/choice.py +281 -0
- aspalchemy-1.0.0/src/aspalchemy/clingo_handler.py +193 -0
- aspalchemy-1.0.0/src/aspalchemy/conditional_literal.py +94 -0
- aspalchemy-1.0.0/src/aspalchemy/conditioned_element.py +157 -0
- aspalchemy-1.0.0/src/aspalchemy/core.py +1645 -0
- aspalchemy-1.0.0/src/aspalchemy/exceptions.py +67 -0
- aspalchemy-1.0.0/src/aspalchemy/operators.py +92 -0
- aspalchemy-1.0.0/src/aspalchemy/optimization.py +265 -0
- aspalchemy-1.0.0/src/aspalchemy/predicate.py +758 -0
- aspalchemy-1.0.0/src/aspalchemy/program_elements.py +414 -0
- aspalchemy-1.0.0/src/aspalchemy/py.typed +0 -0
- aspalchemy-1.0.0/src/aspalchemy/scoping.py +612 -0
- aspalchemy-1.0.0/src/aspalchemy/segment.py +609 -0
- aspalchemy-1.0.0/src/aspalchemy/solve_result.py +1056 -0
- aspalchemy-1.0.0/src/aspalchemy/solver.py +1950 -0
- aspalchemy-1.0.0/src/aspalchemy/source_location.py +176 -0
- aspalchemy-1.0.0/src/aspalchemy/statistics.py +233 -0
- aspalchemy-1.0.0/src/aspalchemy/version.py +12 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Jolyon Bloomfield
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aspalchemy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Python ORM interface for building and solving clingo ASP programs
|
|
5
|
+
Author: Jolyon Bloomfield
|
|
6
|
+
Author-email: Jolyon Bloomfield <jolyonb84@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE.txt
|
|
9
|
+
Requires-Dist: clingo>=5.8.0,<6
|
|
10
|
+
Requires-Python: >=3.14
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# ASPAlchemy
|
|
14
|
+
|
|
15
|
+
ASPAlchemy is a Python library for building clingo ASP (Answer Set Programming) programs with a clean, object-oriented interface.
|
|
16
|
+
|
|
17
|
+
## Term Hierarchy
|
|
18
|
+
|
|
19
|
+
The library is built around a rich hierarchy of term types that represent different ASP constructs:
|
|
20
|
+
|
|
21
|
+
- `Term` (abstract base class)
|
|
22
|
+
- `ComparableTerm` (abstract: usable in comparisons; provides `==`, `!=`, `<`, `<=`, `>`, `>=`)
|
|
23
|
+
- `Value` (abstract, for basic values — also a `BasicTerm`)
|
|
24
|
+
- `Variable` (e.g., `X`, `Y`)
|
|
25
|
+
- `ConstantBase` (abstract)
|
|
26
|
+
- `Number` (numeric constants, e.g., `42`)
|
|
27
|
+
- `String` (string literals, e.g., `"hello"`)
|
|
28
|
+
- `DefinedConstant` (#const-defined constants, e.g., `max_size`)
|
|
29
|
+
- `Supremum` / `Infimum` (`#sup`/`#inf`, the ordering's end markers; `SUP` and `INF` are the singletons)
|
|
30
|
+
- `Expression` (arithmetic expressions, e.g., `X+Y*2`)
|
|
31
|
+
- `AggregateBase` (abstract marker so core can recognize aggregates)
|
|
32
|
+
- `Aggregate` (abstract)
|
|
33
|
+
- `Count`, `Sum`, `SumPlus`, `Min`, `Max`
|
|
34
|
+
- `BasicTerm` (abstract, can be direct predicate arguments: `Value`, `Predicate`, `Pool`)
|
|
35
|
+
- `Predicate` (e.g., `person(john, 42)`)
|
|
36
|
+
- `Pool` (abstract)
|
|
37
|
+
- `RangePool` (e.g., `1..5`)
|
|
38
|
+
- `ExplicitPool` (e.g., `(1;3;5)`)
|
|
39
|
+
- `Negatable` (abstract mixin: `~` builds `not term` on atoms; on plain comparisons it builds the complement — see `Not`)
|
|
40
|
+
- `Comparison` (comparisons, e.g., `X < Y`)
|
|
41
|
+
- `DefaultNegation` (default negation, e.g., `not p(X)`)
|
|
42
|
+
- `ConditionalLiteral` (e.g., `p(X) : q(X)`)
|
|
43
|
+
- `Choice` (e.g., `{ p(X) : q(X) }`)
|
|
44
|
+
|
|
45
|
+
## Core Concepts
|
|
46
|
+
|
|
47
|
+
### Values
|
|
48
|
+
|
|
49
|
+
Values represent basic elements in an ASP program. Plain Python literals coerce
|
|
50
|
+
automatically wherever terms are expected — an int becomes an ASP number, a str
|
|
51
|
+
becomes a quoted ASP string. Variables you construct by hand, and bare atoms
|
|
52
|
+
(the `n` in `direction(n)`) are zero-arity predicates:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from aspalchemy import Predicate, Variable
|
|
56
|
+
|
|
57
|
+
X = Variable("X") # an ASP variable
|
|
58
|
+
n = Predicate.define("n", [], show=False)() # a bare atom: n, distinct from the string "n"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Predicates
|
|
62
|
+
|
|
63
|
+
Declare predicates as classes; fields are statically checked, so typos and
|
|
64
|
+
missing arguments are type errors, and instances autocomplete:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from aspalchemy import Predicate, PredicateField
|
|
68
|
+
|
|
69
|
+
class Person(Predicate):
|
|
70
|
+
name: PredicateField
|
|
71
|
+
age: PredicateField
|
|
72
|
+
|
|
73
|
+
john = Person(name="john", age=30)
|
|
74
|
+
mary = Person(name="mary", age=25)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The ASP name defaults to the class name, snake-cased (HasSymbol becomes
|
|
78
|
+
has_symbol); class kwargs override it and set namespacing and visibility (`class Pipe(Predicate, name="pipe_seg",
|
|
79
|
+
show=False)`). When the schema is only known at runtime, build the same thing
|
|
80
|
+
dynamically:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
Edge = Predicate.define("edge", ["a", "b"])
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For fully typed fields, annotate them as `Field[int]`, `Field[str]`, or
|
|
87
|
+
`Field[SomePredicate]`. Writes accept rule terms (Variables, Expressions) as
|
|
88
|
+
well as ground values, and are validated per field; reads are plain Python
|
|
89
|
+
values, statically typed — so solution atoms come back as real ints and strs:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from aspalchemy import ASPProgram, Field, Predicate
|
|
93
|
+
|
|
94
|
+
class Score(Predicate):
|
|
95
|
+
player: Field[str]
|
|
96
|
+
points: Field[int]
|
|
97
|
+
|
|
98
|
+
program = ASPProgram()
|
|
99
|
+
program.fact(Score(player="ada", points=3), Score(player="ben", points=5))
|
|
100
|
+
model = program.solve().first() # raises UnsatisfiableError if there is no model
|
|
101
|
+
total = sum(score.points for score in model.atoms(Score)) # plain ints
|
|
102
|
+
assert total == 8
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Rules
|
|
106
|
+
|
|
107
|
+
Build rules with clear syntax:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from aspalchemy import ANY, ASPProgram, Variable
|
|
111
|
+
|
|
112
|
+
program = ASPProgram()
|
|
113
|
+
X, Y = Variable("X"), Variable("Y")
|
|
114
|
+
Adult = Predicate.define("adult", ["name"])
|
|
115
|
+
|
|
116
|
+
# Facts
|
|
117
|
+
program.fact(john)
|
|
118
|
+
program.fact(mary)
|
|
119
|
+
|
|
120
|
+
# Rules
|
|
121
|
+
program.when(Person(name=X, age=Y), Y >= 18).derive(Adult(name=X))
|
|
122
|
+
|
|
123
|
+
# Constraints
|
|
124
|
+
program.forbid(Person(name=ANY, age=Y), Y < 0)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Comparisons
|
|
128
|
+
|
|
129
|
+
Compare terms:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
X < Y # Creates a comparison X < Y
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Aggregates
|
|
136
|
+
|
|
137
|
+
Use aggregates for advanced computations:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from aspalchemy import ANY, Count, Variable
|
|
141
|
+
|
|
142
|
+
X = Variable("X")
|
|
143
|
+
count = Count(X, Person(name=X, age=ANY)) > 5
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Solving
|
|
147
|
+
|
|
148
|
+
Solve your ASP program:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Generate ASP code
|
|
152
|
+
asp_code = program.render()
|
|
153
|
+
print(asp_code)
|
|
154
|
+
|
|
155
|
+
# Solve
|
|
156
|
+
result = program.solve() # a lazy stream: consume the models you want
|
|
157
|
+
for model in result:
|
|
158
|
+
print("Solution found:")
|
|
159
|
+
for adult in model.atoms(Adult):
|
|
160
|
+
print(f" {adult}")
|
|
161
|
+
print(f"Satisfiable: {result.satisfiable}")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Diagnostics point at your code
|
|
165
|
+
|
|
166
|
+
Every statement remembers the Python line that authored it. A `when()` left
|
|
167
|
+
unclosed reports where it was opened; grounding diagnostics from clingo carry
|
|
168
|
+
a "generated by file:line" note back to your source;
|
|
169
|
+
`program.render(annotate=True)` appends a trailing `% file:line` comment to
|
|
170
|
+
each rule — without changing line numbers, so the annotated text doubles as
|
|
171
|
+
a map from raw-clingo error lines back to your source; and when a grounding
|
|
172
|
+
is slow or huge, `program.ground().analyze_grounding()` reports ground-atom
|
|
173
|
+
counts per signature, largest first, each joined back to the lines that
|
|
174
|
+
derive it. Frameworks built on
|
|
175
|
+
aspalchemy call `register_skip_package()` (whole package, or per-module dotted
|
|
176
|
+
prefixes) so locations point at *their* caller's code; mark a single helper
|
|
177
|
+
with `@attribute_to_caller` to attribute its statements to its call sites.
|
|
178
|
+
Switch capture off with `ASPProgram(source_locations=False)` when
|
|
179
|
+
generating rules in bulk.
|
|
180
|
+
|
|
181
|
+
## Requirements
|
|
182
|
+
|
|
183
|
+
- Python 3.14+
|
|
184
|
+
- clingo 5.8+
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT License
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# ASPAlchemy
|
|
2
|
+
|
|
3
|
+
ASPAlchemy is a Python library for building clingo ASP (Answer Set Programming) programs with a clean, object-oriented interface.
|
|
4
|
+
|
|
5
|
+
## Term Hierarchy
|
|
6
|
+
|
|
7
|
+
The library is built around a rich hierarchy of term types that represent different ASP constructs:
|
|
8
|
+
|
|
9
|
+
- `Term` (abstract base class)
|
|
10
|
+
- `ComparableTerm` (abstract: usable in comparisons; provides `==`, `!=`, `<`, `<=`, `>`, `>=`)
|
|
11
|
+
- `Value` (abstract, for basic values — also a `BasicTerm`)
|
|
12
|
+
- `Variable` (e.g., `X`, `Y`)
|
|
13
|
+
- `ConstantBase` (abstract)
|
|
14
|
+
- `Number` (numeric constants, e.g., `42`)
|
|
15
|
+
- `String` (string literals, e.g., `"hello"`)
|
|
16
|
+
- `DefinedConstant` (#const-defined constants, e.g., `max_size`)
|
|
17
|
+
- `Supremum` / `Infimum` (`#sup`/`#inf`, the ordering's end markers; `SUP` and `INF` are the singletons)
|
|
18
|
+
- `Expression` (arithmetic expressions, e.g., `X+Y*2`)
|
|
19
|
+
- `AggregateBase` (abstract marker so core can recognize aggregates)
|
|
20
|
+
- `Aggregate` (abstract)
|
|
21
|
+
- `Count`, `Sum`, `SumPlus`, `Min`, `Max`
|
|
22
|
+
- `BasicTerm` (abstract, can be direct predicate arguments: `Value`, `Predicate`, `Pool`)
|
|
23
|
+
- `Predicate` (e.g., `person(john, 42)`)
|
|
24
|
+
- `Pool` (abstract)
|
|
25
|
+
- `RangePool` (e.g., `1..5`)
|
|
26
|
+
- `ExplicitPool` (e.g., `(1;3;5)`)
|
|
27
|
+
- `Negatable` (abstract mixin: `~` builds `not term` on atoms; on plain comparisons it builds the complement — see `Not`)
|
|
28
|
+
- `Comparison` (comparisons, e.g., `X < Y`)
|
|
29
|
+
- `DefaultNegation` (default negation, e.g., `not p(X)`)
|
|
30
|
+
- `ConditionalLiteral` (e.g., `p(X) : q(X)`)
|
|
31
|
+
- `Choice` (e.g., `{ p(X) : q(X) }`)
|
|
32
|
+
|
|
33
|
+
## Core Concepts
|
|
34
|
+
|
|
35
|
+
### Values
|
|
36
|
+
|
|
37
|
+
Values represent basic elements in an ASP program. Plain Python literals coerce
|
|
38
|
+
automatically wherever terms are expected — an int becomes an ASP number, a str
|
|
39
|
+
becomes a quoted ASP string. Variables you construct by hand, and bare atoms
|
|
40
|
+
(the `n` in `direction(n)`) are zero-arity predicates:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from aspalchemy import Predicate, Variable
|
|
44
|
+
|
|
45
|
+
X = Variable("X") # an ASP variable
|
|
46
|
+
n = Predicate.define("n", [], show=False)() # a bare atom: n, distinct from the string "n"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Predicates
|
|
50
|
+
|
|
51
|
+
Declare predicates as classes; fields are statically checked, so typos and
|
|
52
|
+
missing arguments are type errors, and instances autocomplete:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from aspalchemy import Predicate, PredicateField
|
|
56
|
+
|
|
57
|
+
class Person(Predicate):
|
|
58
|
+
name: PredicateField
|
|
59
|
+
age: PredicateField
|
|
60
|
+
|
|
61
|
+
john = Person(name="john", age=30)
|
|
62
|
+
mary = Person(name="mary", age=25)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The ASP name defaults to the class name, snake-cased (HasSymbol becomes
|
|
66
|
+
has_symbol); class kwargs override it and set namespacing and visibility (`class Pipe(Predicate, name="pipe_seg",
|
|
67
|
+
show=False)`). When the schema is only known at runtime, build the same thing
|
|
68
|
+
dynamically:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
Edge = Predicate.define("edge", ["a", "b"])
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For fully typed fields, annotate them as `Field[int]`, `Field[str]`, or
|
|
75
|
+
`Field[SomePredicate]`. Writes accept rule terms (Variables, Expressions) as
|
|
76
|
+
well as ground values, and are validated per field; reads are plain Python
|
|
77
|
+
values, statically typed — so solution atoms come back as real ints and strs:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from aspalchemy import ASPProgram, Field, Predicate
|
|
81
|
+
|
|
82
|
+
class Score(Predicate):
|
|
83
|
+
player: Field[str]
|
|
84
|
+
points: Field[int]
|
|
85
|
+
|
|
86
|
+
program = ASPProgram()
|
|
87
|
+
program.fact(Score(player="ada", points=3), Score(player="ben", points=5))
|
|
88
|
+
model = program.solve().first() # raises UnsatisfiableError if there is no model
|
|
89
|
+
total = sum(score.points for score in model.atoms(Score)) # plain ints
|
|
90
|
+
assert total == 8
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Rules
|
|
94
|
+
|
|
95
|
+
Build rules with clear syntax:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from aspalchemy import ANY, ASPProgram, Variable
|
|
99
|
+
|
|
100
|
+
program = ASPProgram()
|
|
101
|
+
X, Y = Variable("X"), Variable("Y")
|
|
102
|
+
Adult = Predicate.define("adult", ["name"])
|
|
103
|
+
|
|
104
|
+
# Facts
|
|
105
|
+
program.fact(john)
|
|
106
|
+
program.fact(mary)
|
|
107
|
+
|
|
108
|
+
# Rules
|
|
109
|
+
program.when(Person(name=X, age=Y), Y >= 18).derive(Adult(name=X))
|
|
110
|
+
|
|
111
|
+
# Constraints
|
|
112
|
+
program.forbid(Person(name=ANY, age=Y), Y < 0)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Comparisons
|
|
116
|
+
|
|
117
|
+
Compare terms:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
X < Y # Creates a comparison X < Y
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Aggregates
|
|
124
|
+
|
|
125
|
+
Use aggregates for advanced computations:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from aspalchemy import ANY, Count, Variable
|
|
129
|
+
|
|
130
|
+
X = Variable("X")
|
|
131
|
+
count = Count(X, Person(name=X, age=ANY)) > 5
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Solving
|
|
135
|
+
|
|
136
|
+
Solve your ASP program:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# Generate ASP code
|
|
140
|
+
asp_code = program.render()
|
|
141
|
+
print(asp_code)
|
|
142
|
+
|
|
143
|
+
# Solve
|
|
144
|
+
result = program.solve() # a lazy stream: consume the models you want
|
|
145
|
+
for model in result:
|
|
146
|
+
print("Solution found:")
|
|
147
|
+
for adult in model.atoms(Adult):
|
|
148
|
+
print(f" {adult}")
|
|
149
|
+
print(f"Satisfiable: {result.satisfiable}")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Diagnostics point at your code
|
|
153
|
+
|
|
154
|
+
Every statement remembers the Python line that authored it. A `when()` left
|
|
155
|
+
unclosed reports where it was opened; grounding diagnostics from clingo carry
|
|
156
|
+
a "generated by file:line" note back to your source;
|
|
157
|
+
`program.render(annotate=True)` appends a trailing `% file:line` comment to
|
|
158
|
+
each rule — without changing line numbers, so the annotated text doubles as
|
|
159
|
+
a map from raw-clingo error lines back to your source; and when a grounding
|
|
160
|
+
is slow or huge, `program.ground().analyze_grounding()` reports ground-atom
|
|
161
|
+
counts per signature, largest first, each joined back to the lines that
|
|
162
|
+
derive it. Frameworks built on
|
|
163
|
+
aspalchemy call `register_skip_package()` (whole package, or per-module dotted
|
|
164
|
+
prefixes) so locations point at *their* caller's code; mark a single helper
|
|
165
|
+
with `@attribute_to_caller` to attribute its statements to its call sites.
|
|
166
|
+
Switch capture off with `ASPProgram(source_locations=False)` when
|
|
167
|
+
generating rules in bulk.
|
|
168
|
+
|
|
169
|
+
## Requirements
|
|
170
|
+
|
|
171
|
+
- Python 3.14+
|
|
172
|
+
- clingo 5.8+
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT License
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "aspalchemy"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A Python ORM interface for building and solving clingo ASP programs"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
license-files = ["LICENSE.txt"]
|
|
7
|
+
readme = "docs/README.md"
|
|
8
|
+
requires-python = ">=3.14"
|
|
9
|
+
authors = [{ name = "Jolyon Bloomfield", email = "jolyonb84@gmail.com" }]
|
|
10
|
+
dependencies = ["clingo>=5.8.0,<6"]
|
|
11
|
+
|
|
12
|
+
[dependency-groups]
|
|
13
|
+
dev = [
|
|
14
|
+
"mypy>=2.1",
|
|
15
|
+
"pre-commit>=4.6",
|
|
16
|
+
"pyright>=1.1.411",
|
|
17
|
+
"ruff>=0.15",
|
|
18
|
+
"pytest>=9.1",
|
|
19
|
+
"ty>=0.0.56",
|
|
20
|
+
"pytest-cov>=7.1.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["uv_build>=0.11,<0.13"]
|
|
25
|
+
build-backend = "uv_build"
|
|
26
|
+
|
|
27
|
+
[tool.uv.build-backend]
|
|
28
|
+
# Internal docs and OS litter must not ship in the wheel
|
|
29
|
+
source-exclude = ["src/aspalchemy/CLAUDE.md", "**/.DS_Store"]
|
|
30
|
+
|
|
31
|
+
[tool.mypy]
|
|
32
|
+
python_version = "3.14"
|
|
33
|
+
warn_return_any = true
|
|
34
|
+
warn_unused_configs = true
|
|
35
|
+
disallow_untyped_defs = true
|
|
36
|
+
disallow_incomplete_defs = true
|
|
37
|
+
check_untyped_defs = true
|
|
38
|
+
disallow_untyped_decorators = true
|
|
39
|
+
no_implicit_optional = true
|
|
40
|
+
strict_optional = true
|
|
41
|
+
files = ["src", "tests"]
|
|
42
|
+
|
|
43
|
+
[tool.pyright]
|
|
44
|
+
include = ["src", "tests"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 120
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["A", "B", "C4", "E", "F", "I", "Q", "RET", "RUF", "SIM", "UP", "W"]
|
|
51
|
+
# RUF001/RUF002/RUF003: flag legitimate typography ("9×9 grid") as ambiguous unicode
|
|
52
|
+
ignore = [
|
|
53
|
+
"RUF001", "RUF002", "RUF003", # legitimate typography (9×9 grids)
|
|
54
|
+
"SIM300", # 'yoda' flips change which operand builds the ASP Comparison, churning rendered output
|
|
55
|
+
]
|
|
56
|
+
fixable = ["ALL"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint.isort]
|
|
59
|
+
known-first-party = ["aspalchemy"]
|
|
60
|
+
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
testpaths = ["tests", "src/aspalchemy"]
|
|
64
|
+
addopts = "--doctest-modules"
|
|
65
|
+
markers = [
|
|
66
|
+
"allow_invalid_render: opt this test out of the suite-wide render parse-check (for tests exercising invalid-program error handling)",
|
|
67
|
+
]
|
|
68
|
+
python_files = "test_*.py"
|
|
69
|
+
python_functions = "test_*"
|
|
70
|
+
|
|
71
|
+
[tool.coverage.run]
|
|
72
|
+
source = ["aspalchemy"]
|
|
73
|
+
|
|
74
|
+
[tool.coverage.report]
|
|
75
|
+
# Line coverage of aspalchemy must be total. Genuinely unexecutable lines are
|
|
76
|
+
# excluded here (not with scattered pragmas): abstract-method bodies that
|
|
77
|
+
# subclasses always override, and type-checking-only imports.
|
|
78
|
+
exclude_also = [
|
|
79
|
+
"if TYPE_CHECKING:",
|
|
80
|
+
"@(abc\\.)?abstractmethod",
|
|
81
|
+
]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ASPAlchemy: A Python library for building clingo ASP (Answer Set Programming) programs
|
|
3
|
+
with a clean, object-oriented interface.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .aggregates import (
|
|
7
|
+
Aggregate,
|
|
8
|
+
Count,
|
|
9
|
+
Max,
|
|
10
|
+
Min,
|
|
11
|
+
Sum,
|
|
12
|
+
SumPlus,
|
|
13
|
+
)
|
|
14
|
+
from .choice import CardinalityType, Choice
|
|
15
|
+
from .clingo_handler import ClingoMessage, LogLevel
|
|
16
|
+
from .conditional_literal import ConditionalLiteral
|
|
17
|
+
from .conditioned_element import ConditionedElement, ConditionType
|
|
18
|
+
from .core import (
|
|
19
|
+
ANY,
|
|
20
|
+
INF,
|
|
21
|
+
SUP,
|
|
22
|
+
BasicTerm,
|
|
23
|
+
ComparableTerm,
|
|
24
|
+
Comparison,
|
|
25
|
+
Compl,
|
|
26
|
+
ConstantBase,
|
|
27
|
+
DefaultNegation,
|
|
28
|
+
DefinedConstant,
|
|
29
|
+
ExplicitPool,
|
|
30
|
+
Expression,
|
|
31
|
+
Infimum,
|
|
32
|
+
Negatable,
|
|
33
|
+
Not,
|
|
34
|
+
Number,
|
|
35
|
+
Pool,
|
|
36
|
+
PredicateOccurrence,
|
|
37
|
+
RangePool,
|
|
38
|
+
RenderingContext,
|
|
39
|
+
String,
|
|
40
|
+
Supremum,
|
|
41
|
+
Term,
|
|
42
|
+
V,
|
|
43
|
+
Value,
|
|
44
|
+
Variable,
|
|
45
|
+
Vars,
|
|
46
|
+
pool,
|
|
47
|
+
)
|
|
48
|
+
from .exceptions import ASPAlchemyError, GroundingError, UnsatisfiableError
|
|
49
|
+
from .operators import ComparisonOperator, Operation
|
|
50
|
+
from .optimization import OptStrategy
|
|
51
|
+
from .predicate import Field, FieldAsTermType, NegatedSignature, Predicate, PredicateField, TupleTermType
|
|
52
|
+
from .program_elements import ProgramElement, RenderedLine
|
|
53
|
+
from .segment import Segment, When
|
|
54
|
+
from .solve_result import (
|
|
55
|
+
AtomCollection,
|
|
56
|
+
BraveConsequences,
|
|
57
|
+
CautiousConsequences,
|
|
58
|
+
Consequences,
|
|
59
|
+
CostedModel,
|
|
60
|
+
Model,
|
|
61
|
+
OptimizeSteps,
|
|
62
|
+
Optimum,
|
|
63
|
+
PredicateTypes,
|
|
64
|
+
RefinementSteps,
|
|
65
|
+
Search,
|
|
66
|
+
SolveResult,
|
|
67
|
+
convert_predicate_to_symbol,
|
|
68
|
+
convert_symbol_to_predicate,
|
|
69
|
+
)
|
|
70
|
+
from .solver import ASPProgram, GroundedProgram, SignatureGrounding
|
|
71
|
+
from .source_location import (
|
|
72
|
+
SourceLocation,
|
|
73
|
+
attribute_to_caller,
|
|
74
|
+
capture_location,
|
|
75
|
+
location_override,
|
|
76
|
+
register_skip_package,
|
|
77
|
+
)
|
|
78
|
+
from .version import __version__
|
|
79
|
+
|
|
80
|
+
__all__ = [ # noqa: RUF022 (categorized deliberately, not sorted)
|
|
81
|
+
# The program and its results
|
|
82
|
+
"ASPProgram",
|
|
83
|
+
"Segment",
|
|
84
|
+
"When",
|
|
85
|
+
"GroundedProgram",
|
|
86
|
+
"SignatureGrounding",
|
|
87
|
+
"SolveResult",
|
|
88
|
+
"Model",
|
|
89
|
+
"CostedModel",
|
|
90
|
+
"AtomCollection",
|
|
91
|
+
"Consequences",
|
|
92
|
+
"BraveConsequences",
|
|
93
|
+
"CautiousConsequences",
|
|
94
|
+
"RefinementSteps",
|
|
95
|
+
"OptimizeSteps",
|
|
96
|
+
"Optimum",
|
|
97
|
+
"OptStrategy",
|
|
98
|
+
"Search",
|
|
99
|
+
"LogLevel",
|
|
100
|
+
"ClingoMessage",
|
|
101
|
+
"ASPAlchemyError",
|
|
102
|
+
"GroundingError",
|
|
103
|
+
"UnsatisfiableError",
|
|
104
|
+
"RenderedLine",
|
|
105
|
+
# Source locations (diagnostics point at the authoring Python line)
|
|
106
|
+
"SourceLocation",
|
|
107
|
+
"capture_location",
|
|
108
|
+
"register_skip_package",
|
|
109
|
+
"attribute_to_caller",
|
|
110
|
+
"location_override",
|
|
111
|
+
# Declaring predicates
|
|
112
|
+
"Predicate",
|
|
113
|
+
"NegatedSignature",
|
|
114
|
+
"Field",
|
|
115
|
+
"PredicateField",
|
|
116
|
+
# Rule-building objects
|
|
117
|
+
"Variable",
|
|
118
|
+
"ANY",
|
|
119
|
+
"V",
|
|
120
|
+
"Vars",
|
|
121
|
+
"Choice",
|
|
122
|
+
"ConditionalLiteral",
|
|
123
|
+
"RangePool",
|
|
124
|
+
"ExplicitPool",
|
|
125
|
+
"SUP",
|
|
126
|
+
"INF",
|
|
127
|
+
# Aggregates
|
|
128
|
+
"Count",
|
|
129
|
+
"Sum",
|
|
130
|
+
"SumPlus",
|
|
131
|
+
"Min",
|
|
132
|
+
"Max",
|
|
133
|
+
# Rule-building utilities
|
|
134
|
+
"pool",
|
|
135
|
+
"Not",
|
|
136
|
+
"Compl",
|
|
137
|
+
# Hierarchy types: these appear in public signatures and return types —
|
|
138
|
+
# annotate with them; you rarely construct them directly
|
|
139
|
+
"ConditionType",
|
|
140
|
+
"ConditionedElement",
|
|
141
|
+
"TupleTermType",
|
|
142
|
+
"CardinalityType",
|
|
143
|
+
"PredicateTypes",
|
|
144
|
+
"FieldAsTermType",
|
|
145
|
+
"PredicateOccurrence",
|
|
146
|
+
"Term",
|
|
147
|
+
"BasicTerm",
|
|
148
|
+
"Value",
|
|
149
|
+
"ConstantBase",
|
|
150
|
+
"ComparableTerm",
|
|
151
|
+
"Negatable",
|
|
152
|
+
"ProgramElement",
|
|
153
|
+
"Expression",
|
|
154
|
+
"Comparison",
|
|
155
|
+
"ComparisonOperator",
|
|
156
|
+
"Operation",
|
|
157
|
+
"RenderingContext",
|
|
158
|
+
"DefaultNegation",
|
|
159
|
+
"DefinedConstant",
|
|
160
|
+
"Aggregate",
|
|
161
|
+
"Pool",
|
|
162
|
+
"Number",
|
|
163
|
+
"String",
|
|
164
|
+
"Supremum",
|
|
165
|
+
"Infimum",
|
|
166
|
+
# Interop with raw clingo symbols
|
|
167
|
+
"convert_predicate_to_symbol",
|
|
168
|
+
"convert_symbol_to_predicate",
|
|
169
|
+
# Metadata
|
|
170
|
+
"__version__",
|
|
171
|
+
]
|