sparc-pc 0.5.3__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.
- sparc_pc-0.5.3/LICENSE +21 -0
- sparc_pc-0.5.3/MANIFEST.in +3 -0
- sparc_pc-0.5.3/PKG-INFO +205 -0
- sparc_pc-0.5.3/README.md +161 -0
- sparc_pc-0.5.3/pyproject.toml +100 -0
- sparc_pc-0.5.3/setup.cfg +4 -0
- sparc_pc-0.5.3/setup.py +73 -0
- sparc_pc-0.5.3/sparc/__init__.py +114 -0
- sparc_pc-0.5.3/sparc/_graph.cpp +44853 -0
- sparc_pc-0.5.3/sparc/_graph.pxd +123 -0
- sparc_pc-0.5.3/sparc/_graph.pyx +869 -0
- sparc_pc-0.5.3/sparc/_mathutils.pxd +56 -0
- sparc_pc-0.5.3/sparc/builders/__init__.py +21 -0
- sparc_pc-0.5.3/sparc/builders/_factory.py +64 -0
- sparc_pc-0.5.3/sparc/builders/embedding.py +252 -0
- sparc_pc-0.5.3/sparc/builders/region_graph.py +116 -0
- sparc_pc-0.5.3/sparc/circuit.py +206 -0
- sparc_pc-0.5.3/sparc/eval.cpp +36340 -0
- sparc_pc-0.5.3/sparc/eval.pxd +15 -0
- sparc_pc-0.5.3/sparc/eval.pyx +250 -0
- sparc_pc-0.5.3/sparc/grad.cpp +36997 -0
- sparc_pc-0.5.3/sparc/grad.pxd +6 -0
- sparc_pc-0.5.3/sparc/grad.pyx +377 -0
- sparc_pc-0.5.3/sparc/io/__init__.py +12 -0
- sparc_pc-0.5.3/sparc/io/learned_pc.py +47 -0
- sparc_pc-0.5.3/sparc/io/serializer.py +265 -0
- sparc_pc-0.5.3/sparc/metrics.cpp +11445 -0
- sparc_pc-0.5.3/sparc/metrics.pxd +11 -0
- sparc_pc-0.5.3/sparc/metrics.pyx +58 -0
- sparc_pc-0.5.3/sparc/nodes.cpp +37522 -0
- sparc_pc-0.5.3/sparc/nodes.pxd +107 -0
- sparc_pc-0.5.3/sparc/nodes.pyx +588 -0
- sparc_pc-0.5.3/sparc/optim.py +233 -0
- sparc_pc-0.5.3/sparc/py.typed +0 -0
- sparc_pc-0.5.3/sparc/queries/__init__.py +71 -0
- sparc_pc-0.5.3/sparc/queries/_engine.cpp +14798 -0
- sparc_pc-0.5.3/sparc/queries/_engine.pxd +58 -0
- sparc_pc-0.5.3/sparc/queries/_engine.pyx +220 -0
- sparc_pc-0.5.3/sparc/queries/cw.cpp +47302 -0
- sparc_pc-0.5.3/sparc/queries/cw.pyx +784 -0
- sparc_pc-0.5.3/sparc/queries/esd.cpp +35244 -0
- sparc_pc-0.5.3/sparc/queries/esd.pyx +522 -0
- sparc_pc-0.5.3/sparc/queries/expectation.cpp +58335 -0
- sparc_pc-0.5.3/sparc/queries/expectation.pyx +1248 -0
- sparc_pc-0.5.3/sparc/queries/gcw.cpp +57765 -0
- sparc_pc-0.5.3/sparc/queries/gcw.pyx +1953 -0
- sparc_pc-0.5.3/sparc/solvers/__init__.py +0 -0
- sparc_pc-0.5.3/sparc/solvers/assignment.cpp +9665 -0
- sparc_pc-0.5.3/sparc/solvers/assignment.pxd +9 -0
- sparc_pc-0.5.3/sparc/solvers/assignment.pyx +134 -0
- sparc_pc-0.5.3/sparc/solvers/northwest.cpp +6022 -0
- sparc_pc-0.5.3/sparc/solvers/northwest.pxd +38 -0
- sparc_pc-0.5.3/sparc/solvers/northwest.pyx +160 -0
- sparc_pc-0.5.3/sparc/solvers/transport.cpp +12306 -0
- sparc_pc-0.5.3/sparc/solvers/transport.pxd +12 -0
- sparc_pc-0.5.3/sparc/solvers/transport.pyx +404 -0
- sparc_pc-0.5.3/sparc/structures/__init__.py +43 -0
- sparc_pc-0.5.3/sparc/structures/_blocks.py +105 -0
- sparc_pc-0.5.3/sparc/structures/_chowliu.py +168 -0
- sparc_pc-0.5.3/sparc/structures/distributions.py +138 -0
- sparc_pc-0.5.3/sparc/structures/hclt.py +143 -0
- sparc_pc-0.5.3/sparc/structures/hmm.py +108 -0
- sparc_pc-0.5.3/sparc/structures/pd.py +298 -0
- sparc_pc-0.5.3/sparc/structures/rat_spn.py +112 -0
- sparc_pc-0.5.3/sparc_pc.egg-info/PKG-INFO +205 -0
- sparc_pc-0.5.3/sparc_pc.egg-info/SOURCES.txt +95 -0
- sparc_pc-0.5.3/sparc_pc.egg-info/dependency_links.txt +1 -0
- sparc_pc-0.5.3/sparc_pc.egg-info/requires.txt +21 -0
- sparc_pc-0.5.3/sparc_pc.egg-info/top_level.txt +1 -0
- sparc_pc-0.5.3/tests/test_bernoulli_integration.py +84 -0
- sparc_pc-0.5.3/tests/test_circuit_api.py +118 -0
- sparc_pc-0.5.3/tests/test_circuit_serializer.py +158 -0
- sparc_pc-0.5.3/tests/test_compiled_eval.py +155 -0
- sparc_pc-0.5.3/tests/test_compiled_queries.py +56 -0
- sparc_pc-0.5.3/tests/test_cw_distance.py +290 -0
- sparc_pc-0.5.3/tests/test_distributions.py +120 -0
- sparc_pc-0.5.3/tests/test_embedding_builder.py +135 -0
- sparc_pc-0.5.3/tests/test_esd_grad.py +163 -0
- sparc_pc-0.5.3/tests/test_exp_query.py +398 -0
- sparc_pc-0.5.3/tests/test_gcw_coupling.py +436 -0
- sparc_pc-0.5.3/tests/test_gcw_crossterm.py +100 -0
- sparc_pc-0.5.3/tests/test_gcw_grad.py +250 -0
- sparc_pc-0.5.3/tests/test_likelihood.py +137 -0
- sparc_pc-0.5.3/tests/test_likelihood_grad.py +204 -0
- sparc_pc-0.5.3/tests/test_log_exp_grad.py +133 -0
- sparc_pc-0.5.3/tests/test_metrics.py +78 -0
- sparc_pc-0.5.3/tests/test_nodes.py +69 -0
- sparc_pc-0.5.3/tests/test_nodes_extended.py +83 -0
- sparc_pc-0.5.3/tests/test_optim.py +133 -0
- sparc_pc-0.5.3/tests/test_properties.py +124 -0
- sparc_pc-0.5.3/tests/test_query_compatibility.py +103 -0
- sparc_pc-0.5.3/tests/test_refresh_parameters.py +34 -0
- sparc_pc-0.5.3/tests/test_region_graph.py +45 -0
- sparc_pc-0.5.3/tests/test_sampling.py +116 -0
- sparc_pc-0.5.3/tests/test_solvers.py +140 -0
- sparc_pc-0.5.3/tests/test_sparc_extras.py +251 -0
- sparc_pc-0.5.3/tests/test_structures.py +152 -0
sparc_pc-0.5.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adrian Ciotinga
|
|
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.
|
sparc_pc-0.5.3/PKG-INFO
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sparc-pc
|
|
3
|
+
Version: 0.5.3
|
|
4
|
+
Summary: SparC: fast, modular sparse probabilistic circuits in Cython (CPU-only)
|
|
5
|
+
Author: Adrian Ciotinga
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/aciotinga/sparc
|
|
8
|
+
Project-URL: Documentation, https://sparc-docs.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/aciotinga/sparc
|
|
10
|
+
Project-URL: Issues, https://github.com/aciotinga/sparc/issues
|
|
11
|
+
Keywords: probabilistic-circuits,sum-product-networks,cython,wasserstein,machine-learning
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Cython
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Requires-Python: <3.15,>=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.20
|
|
26
|
+
Provides-Extra: gurobi
|
|
27
|
+
Requires-Dist: gurobipy>=11.0; extra == "gurobi"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-randomly>=3.15; extra == "dev"
|
|
32
|
+
Requires-Dist: scipy>=1.7; extra == "dev"
|
|
33
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
34
|
+
Provides-Extra: docs
|
|
35
|
+
Requires-Dist: Cython>=3.0; extra == "docs"
|
|
36
|
+
Requires-Dist: numpy>=1.20; extra == "docs"
|
|
37
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
|
|
39
|
+
Requires-Dist: mkdocstrings-python; extra == "docs"
|
|
40
|
+
Requires-Dist: mkdocs-gen-files; extra == "docs"
|
|
41
|
+
Requires-Dist: mkdocs-literate-nav; extra == "docs"
|
|
42
|
+
Requires-Dist: mkdocs-section-index; extra == "docs"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# SparC
|
|
46
|
+
|
|
47
|
+
[](https://pypi.org/project/sparc-pc/)
|
|
48
|
+
|
|
49
|
+
**Spar**se **C**ircuits — a fast, modular, CPU-only library for probabilistic
|
|
50
|
+
circuits (PCs) written in Cython.
|
|
51
|
+
|
|
52
|
+
Documentation: [https://sparc-docs.readthedocs.io](https://sparc-docs.readthedocs.io)
|
|
53
|
+
(build locally with `pip install -e ".[docs]" && mkdocs serve`).
|
|
54
|
+
|
|
55
|
+
## Highlights
|
|
56
|
+
|
|
57
|
+
- **Speed**: typed Cython with C++ STL containers, `nogil` inner loops, and a
|
|
58
|
+
compiled/batched evaluation path.
|
|
59
|
+
- **Modularity**: nodes dispatch through a C-level vtable, and all pairwise
|
|
60
|
+
queries share one tape/gradient engine. New leaf types and new queries are
|
|
61
|
+
added without editing existing files.
|
|
62
|
+
- **Minimal deps**: `numpy` only at runtime. Transportation and assignment
|
|
63
|
+
problems are solved by built-in pure-Cython solvers.
|
|
64
|
+
|
|
65
|
+
## Supported queries
|
|
66
|
+
|
|
67
|
+
| Query | Math | Gradients w.r.t. | Docs |
|
|
68
|
+
|-------|------|------------------|------|
|
|
69
|
+
| CW | $W_p^p$ (Circuit-Wasserstein objective) | circuit2 | [queries](docs/guides/queries.md) |
|
|
70
|
+
| GCW cross-term | Gromov-Circuit-Wasserstein cross-term | circuit2 | [queries](docs/guides/queries.md) |
|
|
71
|
+
| Expectation | $E_Q[P(X)]$ | both circuits | [queries](docs/guides/queries.md) |
|
|
72
|
+
| Log expectation | $\log E_Q[P(X)]$ | both circuits | [queries](docs/guides/queries.md) |
|
|
73
|
+
| ESD | $E[d(X,X')^p]$ (two i.i.d. draws) | single circuit | [queries](docs/guides/queries.md) |
|
|
74
|
+
|
|
75
|
+
CW and GCW gradient variants return gradients with respect to the **second**
|
|
76
|
+
circuit only. Expectation queries return gradients for both circuits.
|
|
77
|
+
|
|
78
|
+
## Supported structures
|
|
79
|
+
|
|
80
|
+
| Constructor | Description | Docs |
|
|
81
|
+
|-------------|-------------|------|
|
|
82
|
+
| `HMM` / `GeneralizedHMM` | Latent-chain sequence models | [structures](docs/guides/structures.md) |
|
|
83
|
+
| `HCLT` | Hidden tree from data (MI + MST) | [structures](docs/guides/structures.md) |
|
|
84
|
+
| `PD` / `PDHCLT` | Recursive grid decompositions | [structures](docs/guides/structures.md) |
|
|
85
|
+
| `RAT_SPN` | Randomized tensorized sum-product network | [structures](docs/guides/structures.md) |
|
|
86
|
+
| `EmbeddingBuilder` | Random recursive PC with node reuse | [builders](docs/guides/builders.md) |
|
|
87
|
+
|
|
88
|
+
Import structures from `sparc.structures` and builders from `sparc.builders`.
|
|
89
|
+
|
|
90
|
+
## Install
|
|
91
|
+
|
|
92
|
+
Prebuilt wheels are available for Linux, Windows, and macOS (Python 3.10–3.14):
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install sparc-pc
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The PyPI package name is `sparc-pc`; import as `sparc`:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import sparc
|
|
102
|
+
from sparc import Circuit
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### From source (developers)
|
|
106
|
+
|
|
107
|
+
SparC ships Cython/C++17 extensions, so a C++ compiler is required when
|
|
108
|
+
installing from source or in editable mode:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install -e . # build extensions in place
|
|
112
|
+
pip install -e ".[dev]" # + pytest, scipy, build tools
|
|
113
|
+
pip install -e ".[docs]" # + MkDocs documentation builder
|
|
114
|
+
pip install -e ".[gurobi]" # optional Gurobi extra (unused by core library)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
See [Releasing](docs/releasing.md) for maintainer release steps.
|
|
118
|
+
|
|
119
|
+
## Quick start
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import numpy as np
|
|
123
|
+
from sparc import CategoricalInputNode, SumNode, ProductNode, Circuit
|
|
124
|
+
|
|
125
|
+
x0 = CategoricalInputNode(id=0, scope_var=0, probabilities=[0.7, 0.3])
|
|
126
|
+
x1 = CategoricalInputNode(id=1, scope_var=1, probabilities=[0.5, 0.5])
|
|
127
|
+
prod = ProductNode(id=2, children=[x0, x1])
|
|
128
|
+
root = SumNode(id=3, children=[prod], parameters=[1.0])
|
|
129
|
+
|
|
130
|
+
circuit = Circuit(root)
|
|
131
|
+
point = np.array([0, 1], dtype=np.int32)
|
|
132
|
+
circuit.log_likelihood(point)
|
|
133
|
+
circuit.sample(5, seed=0) # ndarray (5, max_var+1)
|
|
134
|
+
|
|
135
|
+
data = np.random.randint(0, 2, size=(1000, 2)).astype(np.int32)
|
|
136
|
+
circuit.compile().log_likelihood(data)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Training
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from sparc.optim import MLETrainer
|
|
143
|
+
|
|
144
|
+
trainer = MLETrainer(circuit, lr=0.5)
|
|
145
|
+
trainer.fit(dataset, epochs=100)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Circuit distances
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from sparc import cw_distance, gcw_crossterm, gcw_coupling_circuit, PNormMetric
|
|
152
|
+
|
|
153
|
+
cw_distance(p, q, metric=PNormMetric(p=2.0, scale=1.0))
|
|
154
|
+
gcw_crossterm(circuit1, circuit2)
|
|
155
|
+
gcw_coupling_circuit(circuit1, circuit2).sample(1000)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
See the [documentation](docs/index.md) for compatibility rules, all leaf types,
|
|
159
|
+
and extension points.
|
|
160
|
+
|
|
161
|
+
## Package layout
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
sparc/
|
|
165
|
+
circuit.py # high-level Circuit wrapper
|
|
166
|
+
nodes.pyx # CircuitNode / Sum / Product / leaf nodes (+ vtable)
|
|
167
|
+
eval.pyx # likelihood / sampling + CompiledCircuit
|
|
168
|
+
grad.pyx # GradBundle + mean_log_likelihood_and_grad
|
|
169
|
+
metrics.pyx # GroundMetric, PNormMetric
|
|
170
|
+
solvers/ # transport, assignment, northwest
|
|
171
|
+
queries/ # CW, GCW, expectation, ESD
|
|
172
|
+
builders/ # region graphs, embedding builders
|
|
173
|
+
structures/ # HMM, HCLT, PD, RAT-SPN, ...
|
|
174
|
+
io/ # gcw-circuit-v1 JSON serializer
|
|
175
|
+
optim.py # simplex_step, apply_grads, MLETrainer
|
|
176
|
+
docs/ # MkDocs site (guides, handbook, API reference)
|
|
177
|
+
examples/ # runnable demo scripts
|
|
178
|
+
tests/
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Examples
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
PYTHONPATH=. python examples/mle.py
|
|
185
|
+
PYTHONPATH=. python examples/cw_minimization.py
|
|
186
|
+
PYTHONPATH=. python examples/gcw_optimization.py --direction max
|
|
187
|
+
PYTHONPATH=. python examples/dro.py
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Full list: [docs/examples/overview.md](docs/examples/overview.md).
|
|
191
|
+
|
|
192
|
+
## Development
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pip install -e ".[dev]"
|
|
196
|
+
pytest
|
|
197
|
+
|
|
198
|
+
pip install -e ".[docs]"
|
|
199
|
+
mkdocs serve # browse docs at http://127.0.0.1:8000
|
|
200
|
+
mkdocs build # static site in site/
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
sparc_pc-0.5.3/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# SparC
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/sparc-pc/)
|
|
4
|
+
|
|
5
|
+
**Spar**se **C**ircuits — a fast, modular, CPU-only library for probabilistic
|
|
6
|
+
circuits (PCs) written in Cython.
|
|
7
|
+
|
|
8
|
+
Documentation: [https://sparc-docs.readthedocs.io](https://sparc-docs.readthedocs.io)
|
|
9
|
+
(build locally with `pip install -e ".[docs]" && mkdocs serve`).
|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
- **Speed**: typed Cython with C++ STL containers, `nogil` inner loops, and a
|
|
14
|
+
compiled/batched evaluation path.
|
|
15
|
+
- **Modularity**: nodes dispatch through a C-level vtable, and all pairwise
|
|
16
|
+
queries share one tape/gradient engine. New leaf types and new queries are
|
|
17
|
+
added without editing existing files.
|
|
18
|
+
- **Minimal deps**: `numpy` only at runtime. Transportation and assignment
|
|
19
|
+
problems are solved by built-in pure-Cython solvers.
|
|
20
|
+
|
|
21
|
+
## Supported queries
|
|
22
|
+
|
|
23
|
+
| Query | Math | Gradients w.r.t. | Docs |
|
|
24
|
+
|-------|------|------------------|------|
|
|
25
|
+
| CW | $W_p^p$ (Circuit-Wasserstein objective) | circuit2 | [queries](docs/guides/queries.md) |
|
|
26
|
+
| GCW cross-term | Gromov-Circuit-Wasserstein cross-term | circuit2 | [queries](docs/guides/queries.md) |
|
|
27
|
+
| Expectation | $E_Q[P(X)]$ | both circuits | [queries](docs/guides/queries.md) |
|
|
28
|
+
| Log expectation | $\log E_Q[P(X)]$ | both circuits | [queries](docs/guides/queries.md) |
|
|
29
|
+
| ESD | $E[d(X,X')^p]$ (two i.i.d. draws) | single circuit | [queries](docs/guides/queries.md) |
|
|
30
|
+
|
|
31
|
+
CW and GCW gradient variants return gradients with respect to the **second**
|
|
32
|
+
circuit only. Expectation queries return gradients for both circuits.
|
|
33
|
+
|
|
34
|
+
## Supported structures
|
|
35
|
+
|
|
36
|
+
| Constructor | Description | Docs |
|
|
37
|
+
|-------------|-------------|------|
|
|
38
|
+
| `HMM` / `GeneralizedHMM` | Latent-chain sequence models | [structures](docs/guides/structures.md) |
|
|
39
|
+
| `HCLT` | Hidden tree from data (MI + MST) | [structures](docs/guides/structures.md) |
|
|
40
|
+
| `PD` / `PDHCLT` | Recursive grid decompositions | [structures](docs/guides/structures.md) |
|
|
41
|
+
| `RAT_SPN` | Randomized tensorized sum-product network | [structures](docs/guides/structures.md) |
|
|
42
|
+
| `EmbeddingBuilder` | Random recursive PC with node reuse | [builders](docs/guides/builders.md) |
|
|
43
|
+
|
|
44
|
+
Import structures from `sparc.structures` and builders from `sparc.builders`.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
Prebuilt wheels are available for Linux, Windows, and macOS (Python 3.10–3.14):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install sparc-pc
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The PyPI package name is `sparc-pc`; import as `sparc`:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import sparc
|
|
58
|
+
from sparc import Circuit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### From source (developers)
|
|
62
|
+
|
|
63
|
+
SparC ships Cython/C++17 extensions, so a C++ compiler is required when
|
|
64
|
+
installing from source or in editable mode:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install -e . # build extensions in place
|
|
68
|
+
pip install -e ".[dev]" # + pytest, scipy, build tools
|
|
69
|
+
pip install -e ".[docs]" # + MkDocs documentation builder
|
|
70
|
+
pip install -e ".[gurobi]" # optional Gurobi extra (unused by core library)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See [Releasing](docs/releasing.md) for maintainer release steps.
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import numpy as np
|
|
79
|
+
from sparc import CategoricalInputNode, SumNode, ProductNode, Circuit
|
|
80
|
+
|
|
81
|
+
x0 = CategoricalInputNode(id=0, scope_var=0, probabilities=[0.7, 0.3])
|
|
82
|
+
x1 = CategoricalInputNode(id=1, scope_var=1, probabilities=[0.5, 0.5])
|
|
83
|
+
prod = ProductNode(id=2, children=[x0, x1])
|
|
84
|
+
root = SumNode(id=3, children=[prod], parameters=[1.0])
|
|
85
|
+
|
|
86
|
+
circuit = Circuit(root)
|
|
87
|
+
point = np.array([0, 1], dtype=np.int32)
|
|
88
|
+
circuit.log_likelihood(point)
|
|
89
|
+
circuit.sample(5, seed=0) # ndarray (5, max_var+1)
|
|
90
|
+
|
|
91
|
+
data = np.random.randint(0, 2, size=(1000, 2)).astype(np.int32)
|
|
92
|
+
circuit.compile().log_likelihood(data)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Training
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from sparc.optim import MLETrainer
|
|
99
|
+
|
|
100
|
+
trainer = MLETrainer(circuit, lr=0.5)
|
|
101
|
+
trainer.fit(dataset, epochs=100)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Circuit distances
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from sparc import cw_distance, gcw_crossterm, gcw_coupling_circuit, PNormMetric
|
|
108
|
+
|
|
109
|
+
cw_distance(p, q, metric=PNormMetric(p=2.0, scale=1.0))
|
|
110
|
+
gcw_crossterm(circuit1, circuit2)
|
|
111
|
+
gcw_coupling_circuit(circuit1, circuit2).sample(1000)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
See the [documentation](docs/index.md) for compatibility rules, all leaf types,
|
|
115
|
+
and extension points.
|
|
116
|
+
|
|
117
|
+
## Package layout
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
sparc/
|
|
121
|
+
circuit.py # high-level Circuit wrapper
|
|
122
|
+
nodes.pyx # CircuitNode / Sum / Product / leaf nodes (+ vtable)
|
|
123
|
+
eval.pyx # likelihood / sampling + CompiledCircuit
|
|
124
|
+
grad.pyx # GradBundle + mean_log_likelihood_and_grad
|
|
125
|
+
metrics.pyx # GroundMetric, PNormMetric
|
|
126
|
+
solvers/ # transport, assignment, northwest
|
|
127
|
+
queries/ # CW, GCW, expectation, ESD
|
|
128
|
+
builders/ # region graphs, embedding builders
|
|
129
|
+
structures/ # HMM, HCLT, PD, RAT-SPN, ...
|
|
130
|
+
io/ # gcw-circuit-v1 JSON serializer
|
|
131
|
+
optim.py # simplex_step, apply_grads, MLETrainer
|
|
132
|
+
docs/ # MkDocs site (guides, handbook, API reference)
|
|
133
|
+
examples/ # runnable demo scripts
|
|
134
|
+
tests/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Examples
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
PYTHONPATH=. python examples/mle.py
|
|
141
|
+
PYTHONPATH=. python examples/cw_minimization.py
|
|
142
|
+
PYTHONPATH=. python examples/gcw_optimization.py --direction max
|
|
143
|
+
PYTHONPATH=. python examples/dro.py
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Full list: [docs/examples/overview.md](docs/examples/overview.md).
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pip install -e ".[dev]"
|
|
152
|
+
pytest
|
|
153
|
+
|
|
154
|
+
pip install -e ".[docs]"
|
|
155
|
+
mkdocs serve # browse docs at http://127.0.0.1:8000
|
|
156
|
+
mkdocs build # static site in site/
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel", "Cython>=3.0", "numpy>=1.20"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sparc-pc"
|
|
7
|
+
version = "0.5.3"
|
|
8
|
+
description = "SparC: fast, modular sparse probabilistic circuits in Cython (CPU-only)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10,<3.15"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Adrian Ciotinga" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"probabilistic-circuits",
|
|
16
|
+
"sum-product-networks",
|
|
17
|
+
"cython",
|
|
18
|
+
"wasserstein",
|
|
19
|
+
"machine-learning",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"Programming Language :: Cython",
|
|
31
|
+
"Topic :: Scientific/Engineering",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"numpy>=1.20",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
gurobi = ["gurobipy>=11.0"]
|
|
39
|
+
dev = [
|
|
40
|
+
"build>=1.0",
|
|
41
|
+
"pytest>=7.0",
|
|
42
|
+
"pytest-randomly>=3.15",
|
|
43
|
+
"scipy>=1.7",
|
|
44
|
+
"twine>=4.0",
|
|
45
|
+
]
|
|
46
|
+
docs = [
|
|
47
|
+
"Cython>=3.0",
|
|
48
|
+
"numpy>=1.20",
|
|
49
|
+
"mkdocs>=1.6,<2",
|
|
50
|
+
"mkdocs-material>=9.5,<10",
|
|
51
|
+
"mkdocstrings-python",
|
|
52
|
+
"mkdocs-gen-files",
|
|
53
|
+
"mkdocs-literate-nav",
|
|
54
|
+
"mkdocs-section-index",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://github.com/aciotinga/sparc"
|
|
59
|
+
Documentation = "https://sparc-docs.readthedocs.io"
|
|
60
|
+
Repository = "https://github.com/aciotinga/sparc"
|
|
61
|
+
Issues = "https://github.com/aciotinga/sparc/issues"
|
|
62
|
+
|
|
63
|
+
[tool.setuptools]
|
|
64
|
+
include-package-data = true
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.packages.find]
|
|
67
|
+
where = ["."]
|
|
68
|
+
include = ["sparc*"]
|
|
69
|
+
|
|
70
|
+
[tool.setuptools.package-data]
|
|
71
|
+
"sparc" = ["*.pyx", "*.pxd", "py.typed"]
|
|
72
|
+
|
|
73
|
+
[tool.cibuildwheel]
|
|
74
|
+
build = "cp310-* cp311-* cp312-* cp313-* cp314-*"
|
|
75
|
+
skip = "*-musllinux_* cp*t-*"
|
|
76
|
+
test-command = "python -c \"import sparc; from sparc import Circuit; print(sparc.__version__)\""
|
|
77
|
+
|
|
78
|
+
[tool.cibuildwheel.macos]
|
|
79
|
+
archs = ["x86_64", "arm64"]
|
|
80
|
+
|
|
81
|
+
[tool.pytest.ini_options]
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
markers = [
|
|
84
|
+
"gurobi: requires gurobipy and a valid Gurobi license",
|
|
85
|
+
"gcw: GCW coupling / materialization tests (order- and platform-sensitive)",
|
|
86
|
+
"gcw_stress: heap/order stress tests for GCW memoization isolation",
|
|
87
|
+
"solver: direct unit tests for OT / assignment solvers",
|
|
88
|
+
"metrics: ground metric and metric-driven query behavior",
|
|
89
|
+
"eval: likelihood, sampling, and compiled batched evaluation",
|
|
90
|
+
"optim: simplex_step, apply_grads, MLETrainer",
|
|
91
|
+
"circuit: Circuit wrapper (clone, save/load, normalization)",
|
|
92
|
+
"queries: CW / GCW / exp / log_exp query behavior",
|
|
93
|
+
"integration: cross-module leaf-type integration tests",
|
|
94
|
+
"property: cross-cutting invariants (normalization, determinism)",
|
|
95
|
+
"nodes: node construction and validation",
|
|
96
|
+
"no_dual_path: opt out of object-graph vs compiled parametrization",
|
|
97
|
+
]
|
|
98
|
+
filterwarnings = [
|
|
99
|
+
"error::pytest.PytestUnraisableExceptionWarning",
|
|
100
|
+
]
|
sparc_pc-0.5.3/setup.cfg
ADDED
sparc_pc-0.5.3/setup.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import sysconfig
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from Cython.Build import cythonize
|
|
7
|
+
from setuptools import Extension, setup
|
|
8
|
+
|
|
9
|
+
_fast_build = os.environ.get("SPARC_FAST_BUILD", "").lower() in ("1", "true", "yes")
|
|
10
|
+
|
|
11
|
+
_cc = sysconfig.get_config_var("CC") or ""
|
|
12
|
+
_is_msvc = (
|
|
13
|
+
sys.platform == "win32"
|
|
14
|
+
and "gcc" not in _cc.lower()
|
|
15
|
+
and "clang" not in _cc.lower()
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if _is_msvc:
|
|
19
|
+
extra_compile_args = ["/std:c++17", "/Ox", "/fp:fast"]
|
|
20
|
+
extra_link_args = []
|
|
21
|
+
if not _fast_build:
|
|
22
|
+
extra_compile_args.append("/GL")
|
|
23
|
+
extra_link_args.append("/LTCG")
|
|
24
|
+
else:
|
|
25
|
+
# Avoid -ffast-math: it breaks log-sum-exp guards (isfinite(-inf), -inf
|
|
26
|
+
# comparisons) and causes log_exp_query to return -1 instead of -inf on Linux.
|
|
27
|
+
extra_compile_args = ["-std=c++17", "-O3", "-funroll-loops"]
|
|
28
|
+
extra_link_args = []
|
|
29
|
+
if not _fast_build:
|
|
30
|
+
extra_compile_args.append("-flto")
|
|
31
|
+
extra_link_args.append("-flto")
|
|
32
|
+
|
|
33
|
+
_numpy_include = np.get_include()
|
|
34
|
+
|
|
35
|
+
# Every Cython extension module in SparC. All are C++ (libcpp containers,
|
|
36
|
+
# C++ <random>, etc.).
|
|
37
|
+
_pyx_modules = [
|
|
38
|
+
"sparc.nodes",
|
|
39
|
+
"sparc._graph",
|
|
40
|
+
"sparc.eval",
|
|
41
|
+
"sparc.grad",
|
|
42
|
+
"sparc.metrics",
|
|
43
|
+
"sparc.solvers.northwest",
|
|
44
|
+
"sparc.solvers.transport",
|
|
45
|
+
"sparc.solvers.assignment",
|
|
46
|
+
"sparc.queries._engine",
|
|
47
|
+
"sparc.queries.esd",
|
|
48
|
+
"sparc.queries.expectation",
|
|
49
|
+
"sparc.queries.cw",
|
|
50
|
+
"sparc.queries.gcw",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _to_path(dotted: str) -> str:
|
|
55
|
+
return dotted.replace(".", "/") + ".pyx"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
ext_modules = cythonize(
|
|
59
|
+
[
|
|
60
|
+
Extension(
|
|
61
|
+
name,
|
|
62
|
+
[_to_path(name)],
|
|
63
|
+
language="c++",
|
|
64
|
+
include_dirs=[_numpy_include],
|
|
65
|
+
extra_compile_args=extra_compile_args,
|
|
66
|
+
extra_link_args=extra_link_args,
|
|
67
|
+
)
|
|
68
|
+
for name in _pyx_modules
|
|
69
|
+
],
|
|
70
|
+
compiler_directives={"language_level": "3", "embedsignature": True},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
setup(ext_modules=ext_modules)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""SparC: fast, modular sparse probabilistic circuits in Cython (CPU-only).
|
|
2
|
+
|
|
3
|
+
SparC implements probabilistic circuits (PCs) with typed Cython evaluation,
|
|
4
|
+
differentiable Wasserstein-type queries, and composable structure builders.
|
|
5
|
+
Import the high-level :class:`~sparc.circuit.Circuit` wrapper and query
|
|
6
|
+
functions from this package; see subpackages :mod:`sparc.optim`,
|
|
7
|
+
:mod:`sparc.builders`, :mod:`sparc.structures`, and :mod:`sparc.io` for
|
|
8
|
+
training, random construction, built-in structures, and serialization.
|
|
9
|
+
|
|
10
|
+
Install from PyPI with ``pip install sparc-pc`` (import name: ``sparc``).
|
|
11
|
+
Full documentation: https://sparc-docs.readthedocs.io
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib
|
|
17
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
__version__ = version("sparc-pc")
|
|
21
|
+
except PackageNotFoundError:
|
|
22
|
+
__version__ = "0.0.0+dev"
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Circuit",
|
|
26
|
+
"CircuitNode",
|
|
27
|
+
"SumNode",
|
|
28
|
+
"ProductNode",
|
|
29
|
+
"InputNode",
|
|
30
|
+
"FiniteDiscreteInputNode",
|
|
31
|
+
"CategoricalInputNode",
|
|
32
|
+
"BernoulliInputNode",
|
|
33
|
+
"IndicatorInputNode",
|
|
34
|
+
"LiteralInputNode",
|
|
35
|
+
"DiscreteLogisticInputNode",
|
|
36
|
+
"Evidence",
|
|
37
|
+
"RandomState",
|
|
38
|
+
"likelihood",
|
|
39
|
+
"log_likelihood",
|
|
40
|
+
"sample",
|
|
41
|
+
"CompiledCircuit",
|
|
42
|
+
"mean_log_likelihood_and_grad",
|
|
43
|
+
"GradBundle",
|
|
44
|
+
"GroundMetric",
|
|
45
|
+
"PNormMetric",
|
|
46
|
+
"CircuitSerializer",
|
|
47
|
+
"load_learned_pc",
|
|
48
|
+
"cw_distance",
|
|
49
|
+
"cw_distance_and_grad",
|
|
50
|
+
"expected_squared_distance",
|
|
51
|
+
"expected_squared_distance_and_grad",
|
|
52
|
+
"exp_query",
|
|
53
|
+
"exp_query_and_grad",
|
|
54
|
+
"log_exp_query",
|
|
55
|
+
"log_exp_query_and_grad",
|
|
56
|
+
"gcw_crossterm",
|
|
57
|
+
"gcw_crossterm_and_grad",
|
|
58
|
+
"gcw_coupling_circuit",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
_LAZY_EXPORTS = {
|
|
62
|
+
"Circuit": ("sparc.circuit", "Circuit"),
|
|
63
|
+
"CompiledCircuit": ("sparc._graph", "CompiledCircuit"),
|
|
64
|
+
"likelihood": ("sparc.eval", "likelihood"),
|
|
65
|
+
"log_likelihood": ("sparc.eval", "log_likelihood"),
|
|
66
|
+
"sample": ("sparc.eval", "sample"),
|
|
67
|
+
"GradBundle": ("sparc.grad", "GradBundle"),
|
|
68
|
+
"mean_log_likelihood_and_grad": ("sparc.grad", "mean_log_likelihood_and_grad"),
|
|
69
|
+
"CircuitSerializer": ("sparc.io", "CircuitSerializer"),
|
|
70
|
+
"load_learned_pc": ("sparc.io", "load_learned_pc"),
|
|
71
|
+
"GroundMetric": ("sparc.metrics", "GroundMetric"),
|
|
72
|
+
"PNormMetric": ("sparc.metrics", "PNormMetric"),
|
|
73
|
+
"CircuitNode": ("sparc.nodes", "CircuitNode"),
|
|
74
|
+
"SumNode": ("sparc.nodes", "SumNode"),
|
|
75
|
+
"ProductNode": ("sparc.nodes", "ProductNode"),
|
|
76
|
+
"InputNode": ("sparc.nodes", "InputNode"),
|
|
77
|
+
"FiniteDiscreteInputNode": ("sparc.nodes", "FiniteDiscreteInputNode"),
|
|
78
|
+
"CategoricalInputNode": ("sparc.nodes", "CategoricalInputNode"),
|
|
79
|
+
"BernoulliInputNode": ("sparc.nodes", "BernoulliInputNode"),
|
|
80
|
+
"IndicatorInputNode": ("sparc.nodes", "IndicatorInputNode"),
|
|
81
|
+
"LiteralInputNode": ("sparc.nodes", "LiteralInputNode"),
|
|
82
|
+
"DiscreteLogisticInputNode": ("sparc.nodes", "DiscreteLogisticInputNode"),
|
|
83
|
+
"Evidence": ("sparc.nodes", "Evidence"),
|
|
84
|
+
"RandomState": ("sparc.nodes", "RandomState"),
|
|
85
|
+
"cw_distance": ("sparc.queries", "cw_distance"),
|
|
86
|
+
"cw_distance_and_grad": ("sparc.queries", "cw_distance_and_grad"),
|
|
87
|
+
"expected_squared_distance": ("sparc.queries", "expected_squared_distance"),
|
|
88
|
+
"expected_squared_distance_and_grad": (
|
|
89
|
+
"sparc.queries",
|
|
90
|
+
"expected_squared_distance_and_grad",
|
|
91
|
+
),
|
|
92
|
+
"exp_query": ("sparc.queries", "exp_query"),
|
|
93
|
+
"exp_query_and_grad": ("sparc.queries", "exp_query_and_grad"),
|
|
94
|
+
"log_exp_query": ("sparc.queries", "log_exp_query"),
|
|
95
|
+
"log_exp_query_and_grad": ("sparc.queries", "log_exp_query_and_grad"),
|
|
96
|
+
"gcw_crossterm": ("sparc.queries", "gcw_crossterm"),
|
|
97
|
+
"gcw_crossterm_and_grad": ("sparc.queries", "gcw_crossterm_and_grad"),
|
|
98
|
+
"gcw_coupling_circuit": ("sparc.queries", "gcw_coupling_circuit"),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def __getattr__(name: str):
|
|
103
|
+
spec = _LAZY_EXPORTS.get(name)
|
|
104
|
+
if spec is None:
|
|
105
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
106
|
+
module_name, attr_name = spec
|
|
107
|
+
module = importlib.import_module(module_name)
|
|
108
|
+
value = getattr(module, attr_name)
|
|
109
|
+
globals()[name] = value
|
|
110
|
+
return value
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def __dir__():
|
|
114
|
+
return sorted(set(globals()) | set(__all__))
|