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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: softopt
3
- Version: 0.5.0
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
- SoftOpt is free, fully local, and requires no server, license key, or network access — the same way `torch.optim.Adam` requires none. It is not a wrapper around Adam or any other optimizer: it is a complete, drop-in optimizer with its own Adam-equivalent base update, plus an exact Newton-style correction built on Klein–Maimon soft-number calculus (*Foundations of Soft Logic*, Klein & Maimon, Springer 2024).
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
- SoftOpt is free, fully local, and requires no server, license key, or network access — the same way `torch.optim.Adam` requires none. It is not a wrapper around Adam or any other optimizer: it is a complete, drop-in optimizer with its own Adam-equivalent base update, plus an exact Newton-style correction built on Klein–Maimon soft-number calculus (*Foundations of Soft Logic*, Klein & Maimon, Springer 2024).
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.0"
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" }
@@ -23,4 +23,4 @@ __all__ = [
23
23
  "sadd", "sneg", "ssub", "smul", "sinv", "sdiv",
24
24
  "spow", "sroot", "ssqrt", "ssin", "scos", "sexp", "slog",
25
25
  ]
26
- __version__ = "0.5.0"
26
+ __version__ = "0.5.2"
@@ -1,9 +1,6 @@
1
1
  """
2
- SoftOpt: a COMPLETE, standalone optimizer -- not a wrapper around Adam.
3
- Exactly one step() call does both the Adam-style base update AND the
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.0
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
- SoftOpt is free, fully local, and requires no server, license key, or network access — the same way `torch.optim.Adam` requires none. It is not a wrapper around Adam or any other optimizer: it is a complete, drop-in optimizer with its own Adam-equivalent base update, plus an exact Newton-style correction built on Klein–Maimon soft-number calculus (*Foundations of Soft Logic*, Klein & Maimon, Springer 2024).
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):
@@ -11,5 +11,6 @@ softopt.egg-info/dependency_links.txt
11
11
  softopt.egg-info/requires.txt
12
12
  softopt.egg-info/top_level.txt
13
13
  tests/test_basic.py
14
+ tests/test_causal.py
14
15
  tests/test_compile.py
15
16
  tests/test_soft_number.py
@@ -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