softopt 0.5.0__tar.gz → 0.5.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.
- {softopt-0.5.0/softopt.egg-info → softopt-0.5.2}/PKG-INFO +8 -2
- {softopt-0.5.0 → softopt-0.5.2}/README.md +7 -1
- {softopt-0.5.0 → softopt-0.5.2}/pyproject.toml +1 -1
- {softopt-0.5.0 → softopt-0.5.2}/softopt/__init__.py +1 -1
- {softopt-0.5.0 → softopt-0.5.2}/softopt/numpy_optimizer.py +2 -5
- {softopt-0.5.0 → softopt-0.5.2/softopt.egg-info}/PKG-INFO +8 -2
- {softopt-0.5.0 → softopt-0.5.2}/softopt.egg-info/SOURCES.txt +1 -0
- softopt-0.5.2/tests/test_causal.py +149 -0
- {softopt-0.5.0 → softopt-0.5.2}/LICENSE +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/setup.cfg +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/softopt/compile.py +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/softopt/soft_number.py +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/softopt.egg-info/dependency_links.txt +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/softopt.egg-info/requires.txt +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/softopt.egg-info/top_level.txt +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/tests/test_basic.py +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/tests/test_compile.py +0 -0
- {softopt-0.5.0 → softopt-0.5.2}/tests/test_soft_number.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: softopt
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A standalone optimizer for problems with a known computation graph, built on Klein–Maimon soft-number calculus
|
|
5
5
|
Author: Ido Angel
|
|
6
6
|
License: MIT
|
|
@@ -26,7 +26,7 @@ Dynamic: license-file
|
|
|
26
26
|
|
|
27
27
|
**A standalone optimizer that excels against leading market optimizers like Adam, in problems with a known, differentiable computation graph.**
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Each step is an update followed by an exact Newton correction computed from your own model, through Klein–Maimon soft-number calculus (*Foundations of Soft Logic*, Klein & Maimon, Springer 2024). Free, open source, and runs locally.
|
|
30
30
|
|
|
31
31
|
## Where it helps
|
|
32
32
|
|
|
@@ -124,6 +124,12 @@ All results below use IBM's `FakeFez` noise model via Qiskit + Aer, or realistic
|
|
|
124
124
|
|
|
125
125
|
See `benchmarks/` for the exact, runnable scripts behind every one of these numbers, including the raw per-seed results.
|
|
126
126
|
|
|
127
|
+
A performance table cannot show *why* a method wins. `tests/test_causal.py` runs the optimizer's own code path while replacing the genuine soft-computed curvature with a frozen constant, or with real curvature measured at an unrelated point. Both perform worse than using no correction at all — the specific computed value is doing the work. Run it directly for the full report:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
python3 tests/test_causal.py
|
|
131
|
+
```
|
|
132
|
+
|
|
127
133
|
## The math
|
|
128
134
|
|
|
129
135
|
The full soft-number algebra is available from the package (`from softopt import SoftNumber, sadd, smul, ...`), each operation cited to its exact source in *Foundations of Soft Logic* (Klein & Maimon, Springer 2024):
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**A standalone optimizer that excels against leading market optimizers like Adam, in problems with a known, differentiable computation graph.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Each step is an update followed by an exact Newton correction computed from your own model, through Klein–Maimon soft-number calculus (*Foundations of Soft Logic*, Klein & Maimon, Springer 2024). Free, open source, and runs locally.
|
|
6
6
|
|
|
7
7
|
## Where it helps
|
|
8
8
|
|
|
@@ -100,6 +100,12 @@ All results below use IBM's `FakeFez` noise model via Qiskit + Aer, or realistic
|
|
|
100
100
|
|
|
101
101
|
See `benchmarks/` for the exact, runnable scripts behind every one of these numbers, including the raw per-seed results.
|
|
102
102
|
|
|
103
|
+
A performance table cannot show *why* a method wins. `tests/test_causal.py` runs the optimizer's own code path while replacing the genuine soft-computed curvature with a frozen constant, or with real curvature measured at an unrelated point. Both perform worse than using no correction at all — the specific computed value is doing the work. Run it directly for the full report:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python3 tests/test_causal.py
|
|
107
|
+
```
|
|
108
|
+
|
|
103
109
|
## The math
|
|
104
110
|
|
|
105
111
|
The full soft-number algebra is available from the package (`from softopt import SoftNumber, sadd, smul, ...`), each operation cited to its exact source in *Foundations of Soft Logic* (Klein & Maimon, Springer 2024):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "softopt"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "A standalone optimizer for problems with a known computation graph, built on Klein–Maimon soft-number calculus"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
|
-
SoftOpt:
|
|
3
|
-
|
|
4
|
-
validated Newton-style correction. You don't bring your own optimizer;
|
|
5
|
-
this IS the optimizer, in the same sense that torch.optim.Adam IS an
|
|
6
|
-
optimizer (internally momentum + RMS-scaling), not a wrapper around SGD.
|
|
2
|
+
SoftOpt: each step() call performs an update followed by an exact Newton
|
|
3
|
+
correction computed from your own model.
|
|
7
4
|
|
|
8
5
|
Requirement: you supply g_delta(theta, delta) -> the exact directional
|
|
9
6
|
derivative of your true objective along delta, computed from your own
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: softopt
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A standalone optimizer for problems with a known computation graph, built on Klein–Maimon soft-number calculus
|
|
5
5
|
Author: Ido Angel
|
|
6
6
|
License: MIT
|
|
@@ -26,7 +26,7 @@ Dynamic: license-file
|
|
|
26
26
|
|
|
27
27
|
**A standalone optimizer that excels against leading market optimizers like Adam, in problems with a known, differentiable computation graph.**
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Each step is an update followed by an exact Newton correction computed from your own model, through Klein–Maimon soft-number calculus (*Foundations of Soft Logic*, Klein & Maimon, Springer 2024). Free, open source, and runs locally.
|
|
30
30
|
|
|
31
31
|
## Where it helps
|
|
32
32
|
|
|
@@ -124,6 +124,12 @@ All results below use IBM's `FakeFez` noise model via Qiskit + Aer, or realistic
|
|
|
124
124
|
|
|
125
125
|
See `benchmarks/` for the exact, runnable scripts behind every one of these numbers, including the raw per-seed results.
|
|
126
126
|
|
|
127
|
+
A performance table cannot show *why* a method wins. `tests/test_causal.py` runs the optimizer's own code path while replacing the genuine soft-computed curvature with a frozen constant, or with real curvature measured at an unrelated point. Both perform worse than using no correction at all — the specific computed value is doing the work. Run it directly for the full report:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
python3 tests/test_causal.py
|
|
131
|
+
```
|
|
132
|
+
|
|
127
133
|
## The math
|
|
128
134
|
|
|
129
135
|
The full soft-number algebra is available from the package (`from softopt import SoftNumber, sadd, smul, ...`), each operation cited to its exact source in *Foundations of Soft Logic* (Klein & Maimon, Springer 2024):
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The causal test: does the SPECIFIC value the soft algebra computes matter,
|
|
3
|
+
or would any plausible number do?
|
|
4
|
+
|
|
5
|
+
This is the question a performance table cannot answer. Here the optimizer's
|
|
6
|
+
code path is held identical while the genuine soft-computed curvature is
|
|
7
|
+
replaced by:
|
|
8
|
+
|
|
9
|
+
- a frozen constant (right magnitude, no content)
|
|
10
|
+
- a foreign point's value (real curvature, wrong location)
|
|
11
|
+
|
|
12
|
+
If the algebra were decorative, these would perform the same. They don't --
|
|
13
|
+
they perform worse than using no correction at all, which is what makes the
|
|
14
|
+
computed value load-bearing rather than incidental.
|
|
15
|
+
|
|
16
|
+
Run directly for the full report: python3 tests/test_causal.py
|
|
17
|
+
"""
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pytest
|
|
20
|
+
from softopt import SoftOpt, soft_compile
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# A model whose curvature genuinely varies from point to point -- otherwise
|
|
24
|
+
# a curvature borrowed from elsewhere would be just as good, and the test
|
|
25
|
+
# would prove nothing. It is written as an ordinary Python function, and the
|
|
26
|
+
# derivative function is generated from it by the package's own compiler, so
|
|
27
|
+
# what is being ablated is the value the soft-number path produces.
|
|
28
|
+
SCALE = [1.0, 3.0, 0.4, 7.0, 2.0]
|
|
29
|
+
N = len(SCALE)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def objective(theta):
|
|
33
|
+
q = sum(s * t**2 for s, t in zip(SCALE, theta))
|
|
34
|
+
return q + 0.35 * sum(s * t**4 for s, t in zip(SCALE, theta))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
g_delta = soft_compile(objective, n_params=N)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _axis(i):
|
|
41
|
+
e = np.zeros(N)
|
|
42
|
+
e[i] = 1.0
|
|
43
|
+
return e
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def foreign_sampler(rng):
|
|
47
|
+
"""An unrelated point in the same space."""
|
|
48
|
+
return rng.normal(0.0, 2.0, N)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _run(arm, seed, steps=120, lr=0.05, noise=0.05):
|
|
52
|
+
"""One optimization run. `arm` selects what the correction is fed."""
|
|
53
|
+
rng = np.random.default_rng(seed * 1000 + 17)
|
|
54
|
+
theta = np.random.default_rng(seed).uniform(-1.5, 1.5, N)
|
|
55
|
+
opt = SoftOpt(N, g_delta, lr=lr, seed=seed * 1000 + 555)
|
|
56
|
+
for _ in range(steps):
|
|
57
|
+
# a noisy gradient estimate, as any real measurement would be
|
|
58
|
+
# a noisy gradient estimate, as any real measurement would be --
|
|
59
|
+
# built from the compiled directional derivative along each axis
|
|
60
|
+
exact_grad = np.array([g_delta(theta, _axis(i)) for i in range(N)])
|
|
61
|
+
grad = exact_grad + rng.normal(0, noise, N)
|
|
62
|
+
theta = opt.step(
|
|
63
|
+
theta, grad,
|
|
64
|
+
_test_magnitude_source=arm,
|
|
65
|
+
_test_foreign_sampler=foreign_sampler,
|
|
66
|
+
_test_rng=rng,
|
|
67
|
+
)
|
|
68
|
+
return objective(theta)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
ARMS = ["real", "plain", "frozen", "foreign_point_and_dir", "foreign_point_same_dir"]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def results(n_seeds=50):
|
|
75
|
+
return {arm: np.array([_run(arm, s) for s in range(n_seeds)]) for arm in ARMS}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.fixture(scope="module")
|
|
79
|
+
def ablation():
|
|
80
|
+
"""Computed once and shared -- every arm is deterministic given its seed."""
|
|
81
|
+
return results()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_genuine_curvature_beats_no_correction(ablation):
|
|
85
|
+
wins = int(np.sum(ablation["real"] < ablation["plain"]))
|
|
86
|
+
assert wins >= 30, f"real beat plain in only {wins}/50 runs"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.parametrize("corrupted", ["frozen",
|
|
90
|
+
"foreign_point_and_dir",
|
|
91
|
+
"foreign_point_same_dir"])
|
|
92
|
+
def test_corrupted_curvature_is_worse_than_genuine(ablation, corrupted):
|
|
93
|
+
"""The load-bearing claim: feeding a non-genuine value degrades the result."""
|
|
94
|
+
wins = int(np.sum(ablation["real"] < ablation[corrupted]))
|
|
95
|
+
assert wins >= 28, (
|
|
96
|
+
f"genuine curvature beat {corrupted!r} in only {wins}/50 runs -- "
|
|
97
|
+
"the specific computed value is not doing the work here"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_the_ablation_actually_uses_the_soft_number_path():
|
|
102
|
+
"""Guards against the derivative being hand-written by accident: if the
|
|
103
|
+
model's derivative stops flowing through SoftNumber, this ablation would
|
|
104
|
+
be testing ordinary calculus and proving nothing about the library."""
|
|
105
|
+
import softopt.soft_number as sn
|
|
106
|
+
|
|
107
|
+
counts = {"n": 0}
|
|
108
|
+
originals = {}
|
|
109
|
+
for name in ("__mul__", "__rmul__", "__add__", "__radd__", "__pow__"):
|
|
110
|
+
originals[name] = getattr(sn.SoftNumber, name)
|
|
111
|
+
|
|
112
|
+
def instrument(original):
|
|
113
|
+
def wrapped(self, *args):
|
|
114
|
+
counts["n"] += 1
|
|
115
|
+
return original(self, *args)
|
|
116
|
+
return wrapped
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
for name, original in originals.items():
|
|
120
|
+
setattr(sn.SoftNumber, name, instrument(original))
|
|
121
|
+
g_delta(np.array([0.5, 1.0, -0.3, 0.8, 0.2]), _axis(0))
|
|
122
|
+
finally:
|
|
123
|
+
for name, original in originals.items():
|
|
124
|
+
setattr(sn.SoftNumber, name, original)
|
|
125
|
+
|
|
126
|
+
assert counts["n"] > 0, (
|
|
127
|
+
"g_delta produced a derivative without invoking any SoftNumber "
|
|
128
|
+
"operation -- the ablation would not be testing the soft-number path"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
from scipy.stats import wilcoxon
|
|
134
|
+
r = results()
|
|
135
|
+
print("Causal ablation -- 50 seeds, identical code path, "
|
|
136
|
+
"only the curvature value differs\n")
|
|
137
|
+
print(f" {'arm':<26} {'mean objective':>16} {'real wins':>10} {'p':>10}")
|
|
138
|
+
print(" " + "-" * 68)
|
|
139
|
+
for arm in ARMS:
|
|
140
|
+
if arm == "real":
|
|
141
|
+
print(f" {'real (genuine)':<26} {r[arm].mean():>16.6e} {'':>10} {'':>10}")
|
|
142
|
+
continue
|
|
143
|
+
wins = int(np.sum(r["real"] < r[arm]))
|
|
144
|
+
p = wilcoxon(r["real"], r[arm]).pvalue
|
|
145
|
+
label = {"plain": "plain (no correction)",
|
|
146
|
+
"frozen": "frozen constant",
|
|
147
|
+
"foreign_point_and_dir": "foreign point + direction",
|
|
148
|
+
"foreign_point_same_dir": "foreign point, same dir"}[arm]
|
|
149
|
+
print(f" {label:<26} {r[arm].mean():>16.6e} {wins:>7}/50 {p:>10.2e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|