fxtensor-salmon 1.2.0__tar.gz → 1.2.2__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.
- {fxtensor_salmon-1.2.0/src/fxtensor_salmon.egg-info → fxtensor_salmon-1.2.2}/PKG-INFO +42 -1
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/README.md +41 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/pyproject.toml +1 -1
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/operations.py +50 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2/src/fxtensor_salmon.egg-info}/PKG-INFO +42 -1
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon.egg-info/SOURCES.txt +1 -0
- fxtensor_salmon-1.2.2/tests/test_almost_surely.py +158 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/LICENSE +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/setup.cfg +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/__init__.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/core.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/discopy.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/io.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/profile.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon/special.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon.egg-info/dependency_links.txt +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon.egg-info/requires.txt +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon.egg-info/top_level.txt +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/tests/test_axioms.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/tests/test_discopy.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/tests/test_fxtensor.py +0 -0
- {fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/tests/test_inference.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fxtensor_salmon
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Markov Tensor Module
|
|
5
5
|
Author-email: Tadachika Oki <sakesnare@gmail.com>
|
|
6
6
|
License: Copyright 2025 Tadachika Oki
|
|
@@ -371,6 +371,22 @@ The `fxtensor-salmon` library is designed based on the **Markov Category**, a fr
|
|
|
371
371
|
- **Objects**: State spaces, represented in `FXTensor` as `domain` or `codomain` (e.g., `[['Urban', 'Rural']]` or `[[2]]`).
|
|
372
372
|
- **Morphisms**: Markov kernels (probabilistic transitions), represented by `FXTensor` instances with profile and data.
|
|
373
373
|
|
|
374
|
+
### String diagrams
|
|
375
|
+
|
|
376
|
+
Diagrams are read top to bottom (inputs at the top, outputs at the bottom). The drawing conventions are referenced from T. Fritz (arXiv:1908.07021), implemented independently in TikZ. Boxes are morphisms, triangles are states `I → X`, a black dot branching downwards is `copy`, and a wire ending in a black dot is discard `!`. Sources are in [`docs/diagrams/`](https://github.com/salmonsnare/fxtensor-salmon/tree/v1.2.2/docs/diagrams/).
|
|
377
|
+
|
|
378
|
+
Copy is coassociative, counital and cocommutative:
|
|
379
|
+
|
|
380
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_associativity.svg" alt="coassociativity">
|
|
381
|
+
|
|
382
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_counitality.svg" alt="counitality">
|
|
383
|
+
|
|
384
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_commutativity.svg" alt="cocommutativity">
|
|
385
|
+
|
|
386
|
+
Every morphism is Markov (discard is natural), `f ; ! = !` — in `FXTensor`, each row of a kernel sums to 1:
|
|
387
|
+
|
|
388
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/discard_naturality.svg" alt="f ; ! = !">
|
|
389
|
+
|
|
374
390
|
### Markov Category Operations
|
|
375
391
|
|
|
376
392
|
| Method | Role | When to use |
|
|
@@ -383,6 +399,8 @@ The `fxtensor-salmon` library is designed based on the **Markov Category**, a fr
|
|
|
383
399
|
| `jointification` | Joint of two states | Combine two independent states (states only) |
|
|
384
400
|
| `conditional` | Kernel `A → X ⊗ Y` → `A ⊗ X → Y` | Conditionalize a kernel (generalizes `conditionalization`) |
|
|
385
401
|
| `bayesian_inversion` | `f: X → Y` and prior `I → X` → `f†: Y → X` | Bayesian update / posterior kernel |
|
|
402
|
+
| `almost_surely_equal` | `f = g` p-a.s. | Compare kernels only where the prior has mass |
|
|
403
|
+
| `is_absolutely_continuous` | `p ≪ q` | Check support inclusion of states / kernels |
|
|
386
404
|
| `exclamation` | Discard morphism `X → I` | Build an all-ones discarding tensor |
|
|
387
405
|
| `copy_tensor` | Copy morphism `X → X^{⊗ n}` | Deterministic copy / diagonal (`n=0` is discard) |
|
|
388
406
|
| `delta_tensor` | Identity (alias of `identity_tensor`) | Build an identity kernel |
|
|
@@ -396,8 +414,12 @@ result = tensor1.composition(tensor2)
|
|
|
396
414
|
assert np.allclose(result.data, [[0.78, 0.22], [0.54, 0.46]])
|
|
397
415
|
```
|
|
398
416
|
|
|
417
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/composition.svg" alt="f ; g">
|
|
418
|
+
|
|
399
419
|
#### `tensor_product` — independent systems in parallel
|
|
400
420
|
|
|
421
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/tensor_product.svg" alt="f ⊗ g">
|
|
422
|
+
|
|
401
423
|
Use to put two morphisms (or two states) next to each other without coupling.
|
|
402
424
|
|
|
403
425
|
```python
|
|
@@ -471,6 +493,8 @@ assert np.allclose(joint_xy.data, [[0.06, 0.09, 0.15], [0.14, 0.21, 0.35]])
|
|
|
471
493
|
|
|
472
494
|
#### `bayesian_inversion` — posterior kernel
|
|
473
495
|
|
|
496
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/bayesian_inversion.svg" alt="p;copy;(id⊗f) = p;f;copy;(f†⊗id)">
|
|
497
|
+
|
|
474
498
|
For a prior `p: I → X` and a kernel `f: X → Y`, `f.bayesian_inversion(p)` returns `f†: Y → X` with `f†(x|y) = p(x) f(y|x) / Σ p(x') f(y|x')` (Cho & Jacobs). It satisfies `p ; copy ; (id ⊗ f) = p ; f ; copy ; (f† ⊗ id)`. Outputs with zero evidence stay zero.
|
|
475
499
|
|
|
476
500
|
```python
|
|
@@ -494,6 +518,22 @@ assert c.labels == ([['a'], ['x0', 'x1']], [['y0', 'y1']])
|
|
|
494
518
|
assert np.allclose(c.data, [[[0.25, 0.75], [1/3, 2/3]]])
|
|
495
519
|
```
|
|
496
520
|
|
|
521
|
+
#### `support`, `almost_surely_equal`, `is_absolutely_continuous` — a.s. reasoning
|
|
522
|
+
|
|
523
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/almost_surely_equal.svg" alt="p;copy;(id⊗f) = p;copy;(id⊗g)">
|
|
524
|
+
|
|
525
|
+
`support()` returns the 0/1 indicator of nonzero entries (same profile and labels). `f.almost_surely_equal(g, p)` checks Fritz's `p ; copy ; (id ⊗ f) = p ; copy ; (id ⊗ g)`, i.e. `f` and `g` agree on the support of `p`. `p.is_absolutely_continuous(q)` checks `p ≪ q` (zero in `q` implies zero in `p`; input-wise for kernels, cf. arXiv:2308.00651). Zero is judged with `_ATOL`; labels are not compared.
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
prior = FXTensor([[], [['x0', 'x1', 'x2']]], data=np.array([0.4, 0.6, 0.0]))
|
|
529
|
+
f = FXTensor([[['x0', 'x1', 'x2']], [['y0', 'y1']]], data=np.array([[0.2, 0.8], [0.5, 0.5], [1.0, 0.0]]))
|
|
530
|
+
g = FXTensor([[['x0', 'x1', 'x2']], [['y0', 'y1']]], data=np.array([[0.2, 0.8], [0.5, 0.5], [0.3, 0.7]]))
|
|
531
|
+
assert np.array_equal(prior.support().data, [1.0, 1.0, 0.0])
|
|
532
|
+
assert f.almost_surely_equal(g, prior) # f and g differ only on the null point x2
|
|
533
|
+
assert f.is_absolutely_continuous(g) # f(·|x) ≪ g(·|x) for every x
|
|
534
|
+
assert not g.is_absolutely_continuous(f)
|
|
535
|
+
```
|
|
536
|
+
|
|
497
537
|
### Probabilistic Properties
|
|
498
538
|
|
|
499
539
|
- `is_markov()`: Verifies if the tensor satisfies the normalization condition (sum of outputs equals 1 or 0). States (empty domain) always return `False`; check `np.isclose(state.data.sum(), 1)` for a normalized state.
|
|
@@ -513,3 +553,4 @@ pytest
|
|
|
513
553
|
- [2] [檜山正幸のキマイラ飼育記 (はてなBlog), マルコフ圏におけるテンソル計算の手順とコツ](https://m-hiyama.hatenablog.com/entry/2021/04/05/153325)
|
|
514
554
|
- [3] [K. Cho and B. Jacobs, Disintegration and Bayesian Inversion via String Diagrams (arXiv:1709.00322)](https://arxiv.org/abs/1709.00322)
|
|
515
555
|
- [4] [T. Fritz, A synthetic approach to Markov kernels, conditional independence and theorems on sufficient statistics (arXiv:1908.07021)](https://arxiv.org/abs/1908.07021)
|
|
556
|
+
- [5] [T. Fritz, T. Gonda, A. Lorenzin, P. Perrone, D. Stein, Absolute continuity, supports and idempotent splitting in categorical probability (arXiv:2308.00651)](https://arxiv.org/abs/2308.00651)
|
|
@@ -342,6 +342,22 @@ The `fxtensor-salmon` library is designed based on the **Markov Category**, a fr
|
|
|
342
342
|
- **Objects**: State spaces, represented in `FXTensor` as `domain` or `codomain` (e.g., `[['Urban', 'Rural']]` or `[[2]]`).
|
|
343
343
|
- **Morphisms**: Markov kernels (probabilistic transitions), represented by `FXTensor` instances with profile and data.
|
|
344
344
|
|
|
345
|
+
### String diagrams
|
|
346
|
+
|
|
347
|
+
Diagrams are read top to bottom (inputs at the top, outputs at the bottom). The drawing conventions are referenced from T. Fritz (arXiv:1908.07021), implemented independently in TikZ. Boxes are morphisms, triangles are states `I → X`, a black dot branching downwards is `copy`, and a wire ending in a black dot is discard `!`. Sources are in [`docs/diagrams/`](https://github.com/salmonsnare/fxtensor-salmon/tree/v1.2.2/docs/diagrams/).
|
|
348
|
+
|
|
349
|
+
Copy is coassociative, counital and cocommutative:
|
|
350
|
+
|
|
351
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_associativity.svg" alt="coassociativity">
|
|
352
|
+
|
|
353
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_counitality.svg" alt="counitality">
|
|
354
|
+
|
|
355
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_commutativity.svg" alt="cocommutativity">
|
|
356
|
+
|
|
357
|
+
Every morphism is Markov (discard is natural), `f ; ! = !` — in `FXTensor`, each row of a kernel sums to 1:
|
|
358
|
+
|
|
359
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/discard_naturality.svg" alt="f ; ! = !">
|
|
360
|
+
|
|
345
361
|
### Markov Category Operations
|
|
346
362
|
|
|
347
363
|
| Method | Role | When to use |
|
|
@@ -354,6 +370,8 @@ The `fxtensor-salmon` library is designed based on the **Markov Category**, a fr
|
|
|
354
370
|
| `jointification` | Joint of two states | Combine two independent states (states only) |
|
|
355
371
|
| `conditional` | Kernel `A → X ⊗ Y` → `A ⊗ X → Y` | Conditionalize a kernel (generalizes `conditionalization`) |
|
|
356
372
|
| `bayesian_inversion` | `f: X → Y` and prior `I → X` → `f†: Y → X` | Bayesian update / posterior kernel |
|
|
373
|
+
| `almost_surely_equal` | `f = g` p-a.s. | Compare kernels only where the prior has mass |
|
|
374
|
+
| `is_absolutely_continuous` | `p ≪ q` | Check support inclusion of states / kernels |
|
|
357
375
|
| `exclamation` | Discard morphism `X → I` | Build an all-ones discarding tensor |
|
|
358
376
|
| `copy_tensor` | Copy morphism `X → X^{⊗ n}` | Deterministic copy / diagonal (`n=0` is discard) |
|
|
359
377
|
| `delta_tensor` | Identity (alias of `identity_tensor`) | Build an identity kernel |
|
|
@@ -367,8 +385,12 @@ result = tensor1.composition(tensor2)
|
|
|
367
385
|
assert np.allclose(result.data, [[0.78, 0.22], [0.54, 0.46]])
|
|
368
386
|
```
|
|
369
387
|
|
|
388
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/composition.svg" alt="f ; g">
|
|
389
|
+
|
|
370
390
|
#### `tensor_product` — independent systems in parallel
|
|
371
391
|
|
|
392
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/tensor_product.svg" alt="f ⊗ g">
|
|
393
|
+
|
|
372
394
|
Use to put two morphisms (or two states) next to each other without coupling.
|
|
373
395
|
|
|
374
396
|
```python
|
|
@@ -442,6 +464,8 @@ assert np.allclose(joint_xy.data, [[0.06, 0.09, 0.15], [0.14, 0.21, 0.35]])
|
|
|
442
464
|
|
|
443
465
|
#### `bayesian_inversion` — posterior kernel
|
|
444
466
|
|
|
467
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/bayesian_inversion.svg" alt="p;copy;(id⊗f) = p;f;copy;(f†⊗id)">
|
|
468
|
+
|
|
445
469
|
For a prior `p: I → X` and a kernel `f: X → Y`, `f.bayesian_inversion(p)` returns `f†: Y → X` with `f†(x|y) = p(x) f(y|x) / Σ p(x') f(y|x')` (Cho & Jacobs). It satisfies `p ; copy ; (id ⊗ f) = p ; f ; copy ; (f† ⊗ id)`. Outputs with zero evidence stay zero.
|
|
446
470
|
|
|
447
471
|
```python
|
|
@@ -465,6 +489,22 @@ assert c.labels == ([['a'], ['x0', 'x1']], [['y0', 'y1']])
|
|
|
465
489
|
assert np.allclose(c.data, [[[0.25, 0.75], [1/3, 2/3]]])
|
|
466
490
|
```
|
|
467
491
|
|
|
492
|
+
#### `support`, `almost_surely_equal`, `is_absolutely_continuous` — a.s. reasoning
|
|
493
|
+
|
|
494
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/almost_surely_equal.svg" alt="p;copy;(id⊗f) = p;copy;(id⊗g)">
|
|
495
|
+
|
|
496
|
+
`support()` returns the 0/1 indicator of nonzero entries (same profile and labels). `f.almost_surely_equal(g, p)` checks Fritz's `p ; copy ; (id ⊗ f) = p ; copy ; (id ⊗ g)`, i.e. `f` and `g` agree on the support of `p`. `p.is_absolutely_continuous(q)` checks `p ≪ q` (zero in `q` implies zero in `p`; input-wise for kernels, cf. arXiv:2308.00651). Zero is judged with `_ATOL`; labels are not compared.
|
|
497
|
+
|
|
498
|
+
```python
|
|
499
|
+
prior = FXTensor([[], [['x0', 'x1', 'x2']]], data=np.array([0.4, 0.6, 0.0]))
|
|
500
|
+
f = FXTensor([[['x0', 'x1', 'x2']], [['y0', 'y1']]], data=np.array([[0.2, 0.8], [0.5, 0.5], [1.0, 0.0]]))
|
|
501
|
+
g = FXTensor([[['x0', 'x1', 'x2']], [['y0', 'y1']]], data=np.array([[0.2, 0.8], [0.5, 0.5], [0.3, 0.7]]))
|
|
502
|
+
assert np.array_equal(prior.support().data, [1.0, 1.0, 0.0])
|
|
503
|
+
assert f.almost_surely_equal(g, prior) # f and g differ only on the null point x2
|
|
504
|
+
assert f.is_absolutely_continuous(g) # f(·|x) ≪ g(·|x) for every x
|
|
505
|
+
assert not g.is_absolutely_continuous(f)
|
|
506
|
+
```
|
|
507
|
+
|
|
468
508
|
### Probabilistic Properties
|
|
469
509
|
|
|
470
510
|
- `is_markov()`: Verifies if the tensor satisfies the normalization condition (sum of outputs equals 1 or 0). States (empty domain) always return `False`; check `np.isclose(state.data.sum(), 1)` for a normalized state.
|
|
@@ -484,3 +524,4 @@ pytest
|
|
|
484
524
|
- [2] [檜山正幸のキマイラ飼育記 (はてなBlog), マルコフ圏におけるテンソル計算の手順とコツ](https://m-hiyama.hatenablog.com/entry/2021/04/05/153325)
|
|
485
525
|
- [3] [K. Cho and B. Jacobs, Disintegration and Bayesian Inversion via String Diagrams (arXiv:1709.00322)](https://arxiv.org/abs/1709.00322)
|
|
486
526
|
- [4] [T. Fritz, A synthetic approach to Markov kernels, conditional independence and theorems on sufficient statistics (arXiv:1908.07021)](https://arxiv.org/abs/1908.07021)
|
|
527
|
+
- [5] [T. Fritz, T. Gonda, A. Lorenzin, P. Perrone, D. Stein, Absolute continuity, supports and idempotent splitting in categorical probability (arXiv:2308.00651)](https://arxiv.org/abs/2308.00651)
|
|
@@ -152,6 +152,56 @@ class OperationsMixin:
|
|
|
152
152
|
one = np.isclose(rows, 1, rtol=rtol, atol=atol)
|
|
153
153
|
return bool(np.all(zero | one) and np.all(one.sum(axis=1) == 1))
|
|
154
154
|
|
|
155
|
+
def support(self) -> 'FXTensor':
|
|
156
|
+
"""Indicator tensor of the nonzero entries (same profile and labels).
|
|
157
|
+
|
|
158
|
+
For a state ``p: I → X`` this is the support ``{x : p(x) > 0}``; for a
|
|
159
|
+
kernel ``f: A → X`` it is the support relation ``{(a, x) : f(x|a) > 0}``.
|
|
160
|
+
Entries within ``_ATOL`` of zero count as zero.
|
|
161
|
+
"""
|
|
162
|
+
rtol = type(self)._RTOL
|
|
163
|
+
atol = type(self)._ATOL
|
|
164
|
+
mask = ~np.isclose(self.data, 0, rtol=rtol, atol=atol)
|
|
165
|
+
return self._spawn(self._profile[0], self._profile[1], self._labels, mask.astype(float))
|
|
166
|
+
|
|
167
|
+
def almost_surely_equal(self, other: 'FXTensor', prior: 'FXTensor') -> bool:
|
|
168
|
+
"""Check ``self = other`` ``prior``-almost surely.
|
|
169
|
+
|
|
170
|
+
For ``f, g: X → Y`` and a state ``p: I → X`` this is Fritz's
|
|
171
|
+
``p ; copy ; (id ⊗ f) = p ; copy ; (id ⊗ g)``; for finite tensors it
|
|
172
|
+
means ``f(·|x) = g(·|x)`` for every ``x`` with ``p(x) > 0``. Only
|
|
173
|
+
profiles are compared, not labels.
|
|
174
|
+
"""
|
|
175
|
+
if self._profile != other._profile:
|
|
176
|
+
raise ValueError("Tensors must have the same profile")
|
|
177
|
+
if prior._profile[0]:
|
|
178
|
+
raise ValueError("prior must be a state (empty domain)")
|
|
179
|
+
if prior._profile[1] != self._profile[0]:
|
|
180
|
+
raise ValueError("prior codomain must match the domain of self")
|
|
181
|
+
weights = prior.data.reshape(self._profile[0] + [1] * len(self._profile[1]))
|
|
182
|
+
return bool(np.allclose(
|
|
183
|
+
weights * self.data,
|
|
184
|
+
weights * other.data,
|
|
185
|
+
rtol=type(self)._RTOL,
|
|
186
|
+
atol=type(self)._ATOL,
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
def is_absolutely_continuous(self, other: 'FXTensor') -> bool:
|
|
190
|
+
"""Check ``self ≪ other``: wherever ``other`` is zero, ``self`` is zero.
|
|
191
|
+
|
|
192
|
+
For states this is the usual absolute continuity of distributions;
|
|
193
|
+
for kernels ``f, g: A → X`` it holds input-wise, i.e. ``f(·|a) ≪ g(·|a)``
|
|
194
|
+
for every ``a`` (finite case of Fritz et al., arXiv:2308.00651).
|
|
195
|
+
Only profiles are compared, not labels.
|
|
196
|
+
"""
|
|
197
|
+
if self._profile != other._profile:
|
|
198
|
+
raise ValueError("Tensors must have the same profile")
|
|
199
|
+
rtol = type(self)._RTOL
|
|
200
|
+
atol = type(self)._ATOL
|
|
201
|
+
other_zero = np.isclose(other.data, 0, rtol=rtol, atol=atol)
|
|
202
|
+
self_zero = np.isclose(self.data, 0, rtol=rtol, atol=atol)
|
|
203
|
+
return bool(np.all(self_zero | ~other_zero))
|
|
204
|
+
|
|
155
205
|
def marginalization(self, start_B: int) -> 'FXTensor':
|
|
156
206
|
"""Marginalize out a part of the codomain by summing over it."""
|
|
157
207
|
domain_len = len(self._profile[0])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fxtensor_salmon
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Markov Tensor Module
|
|
5
5
|
Author-email: Tadachika Oki <sakesnare@gmail.com>
|
|
6
6
|
License: Copyright 2025 Tadachika Oki
|
|
@@ -371,6 +371,22 @@ The `fxtensor-salmon` library is designed based on the **Markov Category**, a fr
|
|
|
371
371
|
- **Objects**: State spaces, represented in `FXTensor` as `domain` or `codomain` (e.g., `[['Urban', 'Rural']]` or `[[2]]`).
|
|
372
372
|
- **Morphisms**: Markov kernels (probabilistic transitions), represented by `FXTensor` instances with profile and data.
|
|
373
373
|
|
|
374
|
+
### String diagrams
|
|
375
|
+
|
|
376
|
+
Diagrams are read top to bottom (inputs at the top, outputs at the bottom). The drawing conventions are referenced from T. Fritz (arXiv:1908.07021), implemented independently in TikZ. Boxes are morphisms, triangles are states `I → X`, a black dot branching downwards is `copy`, and a wire ending in a black dot is discard `!`. Sources are in [`docs/diagrams/`](https://github.com/salmonsnare/fxtensor-salmon/tree/v1.2.2/docs/diagrams/).
|
|
377
|
+
|
|
378
|
+
Copy is coassociative, counital and cocommutative:
|
|
379
|
+
|
|
380
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_associativity.svg" alt="coassociativity">
|
|
381
|
+
|
|
382
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_counitality.svg" alt="counitality">
|
|
383
|
+
|
|
384
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/copy_commutativity.svg" alt="cocommutativity">
|
|
385
|
+
|
|
386
|
+
Every morphism is Markov (discard is natural), `f ; ! = !` — in `FXTensor`, each row of a kernel sums to 1:
|
|
387
|
+
|
|
388
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/discard_naturality.svg" alt="f ; ! = !">
|
|
389
|
+
|
|
374
390
|
### Markov Category Operations
|
|
375
391
|
|
|
376
392
|
| Method | Role | When to use |
|
|
@@ -383,6 +399,8 @@ The `fxtensor-salmon` library is designed based on the **Markov Category**, a fr
|
|
|
383
399
|
| `jointification` | Joint of two states | Combine two independent states (states only) |
|
|
384
400
|
| `conditional` | Kernel `A → X ⊗ Y` → `A ⊗ X → Y` | Conditionalize a kernel (generalizes `conditionalization`) |
|
|
385
401
|
| `bayesian_inversion` | `f: X → Y` and prior `I → X` → `f†: Y → X` | Bayesian update / posterior kernel |
|
|
402
|
+
| `almost_surely_equal` | `f = g` p-a.s. | Compare kernels only where the prior has mass |
|
|
403
|
+
| `is_absolutely_continuous` | `p ≪ q` | Check support inclusion of states / kernels |
|
|
386
404
|
| `exclamation` | Discard morphism `X → I` | Build an all-ones discarding tensor |
|
|
387
405
|
| `copy_tensor` | Copy morphism `X → X^{⊗ n}` | Deterministic copy / diagonal (`n=0` is discard) |
|
|
388
406
|
| `delta_tensor` | Identity (alias of `identity_tensor`) | Build an identity kernel |
|
|
@@ -396,8 +414,12 @@ result = tensor1.composition(tensor2)
|
|
|
396
414
|
assert np.allclose(result.data, [[0.78, 0.22], [0.54, 0.46]])
|
|
397
415
|
```
|
|
398
416
|
|
|
417
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/composition.svg" alt="f ; g">
|
|
418
|
+
|
|
399
419
|
#### `tensor_product` — independent systems in parallel
|
|
400
420
|
|
|
421
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/tensor_product.svg" alt="f ⊗ g">
|
|
422
|
+
|
|
401
423
|
Use to put two morphisms (or two states) next to each other without coupling.
|
|
402
424
|
|
|
403
425
|
```python
|
|
@@ -471,6 +493,8 @@ assert np.allclose(joint_xy.data, [[0.06, 0.09, 0.15], [0.14, 0.21, 0.35]])
|
|
|
471
493
|
|
|
472
494
|
#### `bayesian_inversion` — posterior kernel
|
|
473
495
|
|
|
496
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/bayesian_inversion.svg" alt="p;copy;(id⊗f) = p;f;copy;(f†⊗id)">
|
|
497
|
+
|
|
474
498
|
For a prior `p: I → X` and a kernel `f: X → Y`, `f.bayesian_inversion(p)` returns `f†: Y → X` with `f†(x|y) = p(x) f(y|x) / Σ p(x') f(y|x')` (Cho & Jacobs). It satisfies `p ; copy ; (id ⊗ f) = p ; f ; copy ; (f† ⊗ id)`. Outputs with zero evidence stay zero.
|
|
475
499
|
|
|
476
500
|
```python
|
|
@@ -494,6 +518,22 @@ assert c.labels == ([['a'], ['x0', 'x1']], [['y0', 'y1']])
|
|
|
494
518
|
assert np.allclose(c.data, [[[0.25, 0.75], [1/3, 2/3]]])
|
|
495
519
|
```
|
|
496
520
|
|
|
521
|
+
#### `support`, `almost_surely_equal`, `is_absolutely_continuous` — a.s. reasoning
|
|
522
|
+
|
|
523
|
+
<img src="https://raw.githubusercontent.com/salmonsnare/fxtensor-salmon/v1.2.2/docs/diagrams/almost_surely_equal.svg" alt="p;copy;(id⊗f) = p;copy;(id⊗g)">
|
|
524
|
+
|
|
525
|
+
`support()` returns the 0/1 indicator of nonzero entries (same profile and labels). `f.almost_surely_equal(g, p)` checks Fritz's `p ; copy ; (id ⊗ f) = p ; copy ; (id ⊗ g)`, i.e. `f` and `g` agree on the support of `p`. `p.is_absolutely_continuous(q)` checks `p ≪ q` (zero in `q` implies zero in `p`; input-wise for kernels, cf. arXiv:2308.00651). Zero is judged with `_ATOL`; labels are not compared.
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
prior = FXTensor([[], [['x0', 'x1', 'x2']]], data=np.array([0.4, 0.6, 0.0]))
|
|
529
|
+
f = FXTensor([[['x0', 'x1', 'x2']], [['y0', 'y1']]], data=np.array([[0.2, 0.8], [0.5, 0.5], [1.0, 0.0]]))
|
|
530
|
+
g = FXTensor([[['x0', 'x1', 'x2']], [['y0', 'y1']]], data=np.array([[0.2, 0.8], [0.5, 0.5], [0.3, 0.7]]))
|
|
531
|
+
assert np.array_equal(prior.support().data, [1.0, 1.0, 0.0])
|
|
532
|
+
assert f.almost_surely_equal(g, prior) # f and g differ only on the null point x2
|
|
533
|
+
assert f.is_absolutely_continuous(g) # f(·|x) ≪ g(·|x) for every x
|
|
534
|
+
assert not g.is_absolutely_continuous(f)
|
|
535
|
+
```
|
|
536
|
+
|
|
497
537
|
### Probabilistic Properties
|
|
498
538
|
|
|
499
539
|
- `is_markov()`: Verifies if the tensor satisfies the normalization condition (sum of outputs equals 1 or 0). States (empty domain) always return `False`; check `np.isclose(state.data.sum(), 1)` for a normalized state.
|
|
@@ -513,3 +553,4 @@ pytest
|
|
|
513
553
|
- [2] [檜山正幸のキマイラ飼育記 (はてなBlog), マルコフ圏におけるテンソル計算の手順とコツ](https://m-hiyama.hatenablog.com/entry/2021/04/05/153325)
|
|
514
554
|
- [3] [K. Cho and B. Jacobs, Disintegration and Bayesian Inversion via String Diagrams (arXiv:1709.00322)](https://arxiv.org/abs/1709.00322)
|
|
515
555
|
- [4] [T. Fritz, A synthetic approach to Markov kernels, conditional independence and theorems on sufficient statistics (arXiv:1908.07021)](https://arxiv.org/abs/1908.07021)
|
|
556
|
+
- [5] [T. Fritz, T. Gonda, A. Lorenzin, P. Perrone, D. Stein, Absolute continuity, supports and idempotent splitting in categorical probability (arXiv:2308.00651)](https://arxiv.org/abs/2308.00651)
|
|
@@ -13,6 +13,7 @@ src/fxtensor_salmon.egg-info/SOURCES.txt
|
|
|
13
13
|
src/fxtensor_salmon.egg-info/dependency_links.txt
|
|
14
14
|
src/fxtensor_salmon.egg-info/requires.txt
|
|
15
15
|
src/fxtensor_salmon.egg-info/top_level.txt
|
|
16
|
+
tests/test_almost_surely.py
|
|
16
17
|
tests/test_axioms.py
|
|
17
18
|
tests/test_discopy.py
|
|
18
19
|
tests/test_fxtensor.py
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Tests for support, almost_surely_equal and is_absolutely_continuous.
|
|
2
|
+
|
|
3
|
+
References: T. Fritz (arXiv:1908.07021), almost sure equality;
|
|
4
|
+
T. Fritz, T. Gonda, A. Lorenzin, P. Perrone, D. Stein, "Absolute continuity,
|
|
5
|
+
supports and idempotent splitting in categorical probability" (arXiv:2308.00651).
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from fxtensor_salmon import FXTensor
|
|
11
|
+
|
|
12
|
+
from test_axioms import random_kernel
|
|
13
|
+
|
|
14
|
+
X = ["x0", "x1", "x2"]
|
|
15
|
+
Y = ["y0", "y1"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def joint(prior, f):
|
|
19
|
+
"""prior ; copy ; (id ⊗ f)"""
|
|
20
|
+
x = prior.profile[1]
|
|
21
|
+
return prior.composition(FXTensor.copy_tensor(x)).composition(
|
|
22
|
+
FXTensor.identity_tensor(x).tensor_product(f)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def prior():
|
|
28
|
+
# x2 has probability zero
|
|
29
|
+
return FXTensor([[], [X]], data=np.array([0.4, 0.6, 0.0]))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def f():
|
|
34
|
+
return FXTensor([[X], [Y]], data=np.array([[0.2, 0.8], [0.5, 0.5], [1.0, 0.0]]))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def g():
|
|
39
|
+
# differs from f only on the null point x2
|
|
40
|
+
return FXTensor([[X], [Y]], data=np.array([[0.2, 0.8], [0.5, 0.5], [0.3, 0.7]]))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestSupport:
|
|
44
|
+
def test_state(self, prior):
|
|
45
|
+
s = prior.support()
|
|
46
|
+
assert s.labels == (None, [X])
|
|
47
|
+
assert np.array_equal(s.data, [1.0, 1.0, 0.0])
|
|
48
|
+
|
|
49
|
+
def test_kernel_relation(self, f):
|
|
50
|
+
s = f.support()
|
|
51
|
+
assert s.profile == f.profile
|
|
52
|
+
assert s.labels == f.labels
|
|
53
|
+
assert np.array_equal(s.data, [[1, 1], [1, 1], [1, 0]])
|
|
54
|
+
|
|
55
|
+
def test_tolerance(self):
|
|
56
|
+
p = FXTensor([[], [2]], data=np.array([1 - 1e-12, 1e-12]))
|
|
57
|
+
assert np.array_equal(p.support().data, [1.0, 0.0])
|
|
58
|
+
|
|
59
|
+
def test_support_of_composition(self, prior, f):
|
|
60
|
+
# supp(p;f) = {y : exists x in supp(p) with f(y|x) > 0}
|
|
61
|
+
expected = (prior.support().data @ f.support().data) > 0
|
|
62
|
+
assert np.array_equal(prior.composition(f).support().data, expected.astype(float))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TestAlmostSurelyEqual:
|
|
66
|
+
def test_differs_only_on_null_set(self, prior, f, g):
|
|
67
|
+
assert f.almost_surely_equal(g, prior)
|
|
68
|
+
assert f != g
|
|
69
|
+
|
|
70
|
+
def test_matches_joint_equation(self, prior, f, g):
|
|
71
|
+
assert joint(prior, f) == joint(prior, g)
|
|
72
|
+
|
|
73
|
+
def test_differs_on_support(self, prior, f):
|
|
74
|
+
h = FXTensor([[X], [Y]], data=np.array([[0.3, 0.7], [0.5, 0.5], [1.0, 0.0]]))
|
|
75
|
+
assert not f.almost_surely_equal(h, prior)
|
|
76
|
+
assert joint(prior, f) != joint(prior, h)
|
|
77
|
+
|
|
78
|
+
def test_full_support_prior_means_equality(self, f, g):
|
|
79
|
+
uniform = FXTensor([[], [X]], data=np.full(3, 1 / 3))
|
|
80
|
+
assert not f.almost_surely_equal(g, uniform)
|
|
81
|
+
assert f.almost_surely_equal(f.copy(), uniform)
|
|
82
|
+
|
|
83
|
+
def test_double_bayesian_inversion_is_almost_surely_equal(self, prior, g):
|
|
84
|
+
# with a non-full-support prior, (g†)† differs from g on x2 but agrees a.s.
|
|
85
|
+
inv = g.bayesian_inversion(prior)
|
|
86
|
+
double = inv.bayesian_inversion(prior.composition(g))
|
|
87
|
+
assert double != g
|
|
88
|
+
assert double.almost_surely_equal(g, prior)
|
|
89
|
+
|
|
90
|
+
def test_random_multi_axis(self):
|
|
91
|
+
p = random_kernel([], [2, 3], 40)
|
|
92
|
+
data = p.data.copy()
|
|
93
|
+
data[1, 2] = 0.0
|
|
94
|
+
p = FXTensor([[], [2, 3]], data=data / data.sum())
|
|
95
|
+
f = random_kernel([2, 3], [2], 41)
|
|
96
|
+
g_data = f.data.copy()
|
|
97
|
+
g_data[1, 2] = [0.9, 0.1]
|
|
98
|
+
g = FXTensor([[2, 3], [2]], data=g_data)
|
|
99
|
+
assert f.almost_surely_equal(g, p)
|
|
100
|
+
assert joint(p, f) == joint(p, g)
|
|
101
|
+
|
|
102
|
+
def test_labels_are_ignored(self, prior, f):
|
|
103
|
+
unlabeled = FXTensor([[3], [2]], data=f.data)
|
|
104
|
+
assert f.almost_surely_equal(unlabeled, prior)
|
|
105
|
+
|
|
106
|
+
def test_errors(self, prior, f):
|
|
107
|
+
with pytest.raises(ValueError):
|
|
108
|
+
f.almost_surely_equal(FXTensor([[3], [3]], data=np.eye(3)), prior)
|
|
109
|
+
with pytest.raises(ValueError):
|
|
110
|
+
f.almost_surely_equal(f, f)
|
|
111
|
+
with pytest.raises(ValueError):
|
|
112
|
+
f.almost_surely_equal(f, FXTensor([[], [2]], data=np.array([0.5, 0.5])))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TestAbsoluteContinuity:
|
|
116
|
+
def test_states(self):
|
|
117
|
+
p = FXTensor([[], [3]], data=np.array([0.5, 0.5, 0.0]))
|
|
118
|
+
q = FXTensor([[], [3]], data=np.array([0.2, 0.3, 0.5]))
|
|
119
|
+
assert p.is_absolutely_continuous(q)
|
|
120
|
+
assert not q.is_absolutely_continuous(p)
|
|
121
|
+
|
|
122
|
+
def test_equivalent_to_support_inclusion(self):
|
|
123
|
+
rng = np.random.default_rng(50)
|
|
124
|
+
for _ in range(20):
|
|
125
|
+
a = rng.random(4) * (rng.random(4) > 0.4)
|
|
126
|
+
b = rng.random(4) * (rng.random(4) > 0.4)
|
|
127
|
+
p, q = FXTensor([[], [4]], data=a), FXTensor([[], [4]], data=b)
|
|
128
|
+
inclusion = np.all(p.support().data <= q.support().data)
|
|
129
|
+
assert p.is_absolutely_continuous(q) == inclusion
|
|
130
|
+
|
|
131
|
+
def test_kernels_inputwise(self, f, g):
|
|
132
|
+
assert f.is_absolutely_continuous(g)
|
|
133
|
+
assert not g.is_absolutely_continuous(f)
|
|
134
|
+
|
|
135
|
+
def test_reflexive_and_transitive(self):
|
|
136
|
+
p = FXTensor([[], [3]], data=np.array([1.0, 0.0, 0.0]))
|
|
137
|
+
q = FXTensor([[], [3]], data=np.array([0.5, 0.5, 0.0]))
|
|
138
|
+
r = FXTensor([[], [3]], data=np.full(3, 1 / 3))
|
|
139
|
+
assert p.is_absolutely_continuous(p)
|
|
140
|
+
assert p.is_absolutely_continuous(q) and q.is_absolutely_continuous(r)
|
|
141
|
+
assert p.is_absolutely_continuous(r)
|
|
142
|
+
|
|
143
|
+
def test_preserved_by_composition(self, f):
|
|
144
|
+
p = FXTensor([[], [X]], data=np.array([1.0, 0.0, 0.0]))
|
|
145
|
+
q = FXTensor([[], [X]], data=np.array([0.5, 0.0, 0.5]))
|
|
146
|
+
assert p.is_absolutely_continuous(q)
|
|
147
|
+
assert p.composition(f).is_absolutely_continuous(q.composition(f))
|
|
148
|
+
|
|
149
|
+
def test_almost_sure_equality_transfers(self, prior, f, g):
|
|
150
|
+
# if p ≪ q and f = g q-a.s., then f = g p-a.s.
|
|
151
|
+
p = FXTensor([[], [X]], data=np.array([1.0, 0.0, 0.0]))
|
|
152
|
+
assert p.is_absolutely_continuous(prior)
|
|
153
|
+
assert f.almost_surely_equal(g, prior)
|
|
154
|
+
assert f.almost_surely_equal(g, p)
|
|
155
|
+
|
|
156
|
+
def test_profile_mismatch(self):
|
|
157
|
+
with pytest.raises(ValueError):
|
|
158
|
+
FXTensor([[], [2]], data=np.ones(2)).is_absolutely_continuous(FXTensor([[], [3]], data=np.ones(3)))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fxtensor_salmon-1.2.0 → fxtensor_salmon-1.2.2}/src/fxtensor_salmon.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|