loopmath 0.2.7__py3-none-any.whl → 0.2.8__py3-none-any.whl
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.
- loopmath/__init__.py +1 -1
- loopmath/belief/compose.py +45 -11
- loopmath/belief/design.py +6 -2
- loopmath/belief/features.py +2 -0
- loopmath/belief/fit.py +36 -13
- loopmath/belief/lookahead.py +1 -1
- loopmath/belief/pricing.py +22 -3
- loopmath/belief/state.py +106 -58
- loopmath/ingest/base.py +1 -0
- loopmath/prices.toml +12 -2
- loopmath/priors/benchmarks.py +4 -4
- loopmath/recommend/commands.py +8 -6
- loopmath/skill/skills/loopmath-plan-task/SKILL.md +2 -2
- loopmath/views/assets/estimates.js +1 -1
- {loopmath-0.2.7.dist-info → loopmath-0.2.8.dist-info}/METADATA +1 -1
- {loopmath-0.2.7.dist-info → loopmath-0.2.8.dist-info}/RECORD +23 -23
- {loopmath-0.2.7.data → loopmath-0.2.8.data}/data/spec/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.7.data → loopmath-0.2.8.data}/data/spec/ocp-v0.schema.json +0 -0
- {loopmath-0.2.7.data → loopmath-0.2.8.data}/data/spec/ocp_conformance.py +0 -0
- {loopmath-0.2.7.dist-info → loopmath-0.2.8.dist-info}/WHEEL +0 -0
- {loopmath-0.2.7.dist-info → loopmath-0.2.8.dist-info}/entry_points.txt +0 -0
- {loopmath-0.2.7.dist-info → loopmath-0.2.8.dist-info}/licenses/LICENSE +0 -0
- {loopmath-0.2.7.dist-info → loopmath-0.2.8.dist-info}/top_level.txt +0 -0
loopmath/__init__.py
CHANGED
loopmath/belief/compose.py
CHANGED
|
@@ -18,6 +18,13 @@ simulated per draw (run noise and gate outcomes), and the interval is its 10th a
|
|
|
18
18
|
percentiles. Rounds, success and `ell` are parameter-draw quantities. Without a generator,
|
|
19
19
|
`compose` computes the expectations only (the mean path in state.py).
|
|
20
20
|
|
|
21
|
+
Tokens (0.2.8): one execution's dollars are `U = exp(eta_u + sigma_u z1)` and its price per token
|
|
22
|
+
`R = exp(eta_r + sigma_r z2)`, from the rate head; its tokens are `T = U / max(R, f)`, with `f` the model's
|
|
23
|
+
cache-read rate (its floor, none for a model without one). So `T f <= U` in every draw, and every shown pair of
|
|
24
|
+
dollars and tokens clears the floor: per draw, in the mean, at every percentile and summed over pieces. The two
|
|
25
|
+
normals are drawn in 0.2.7's order (dollars, then the second one), so the simulated dollars and the gates after
|
|
26
|
+
them are 0.2.7's. The expected tokens are `E[U] E[1 / max(R, f)]` (`inv_rate_mean`).
|
|
27
|
+
|
|
21
28
|
Per piece, `PieceDraws.exp_*` is the piece's expected full-run contribution, summed over
|
|
22
29
|
the rounds it runs, so pieces add up to the run. `cost_per_round()` gives the expected cost of
|
|
23
30
|
one execution: the per-execution prediction when the piece's cost does not depend on the round
|
|
@@ -28,8 +35,11 @@ from __future__ import annotations
|
|
|
28
35
|
|
|
29
36
|
from dataclasses import dataclass, field
|
|
30
37
|
|
|
38
|
+
import math
|
|
39
|
+
from collections.abc import Mapping
|
|
40
|
+
|
|
31
41
|
import numpy as np
|
|
32
|
-
from scipy.special import expit
|
|
42
|
+
from scipy.special import expit, log_ndtr
|
|
33
43
|
|
|
34
44
|
from .design import Structure
|
|
35
45
|
|
|
@@ -64,12 +74,33 @@ class RunDraws:
|
|
|
64
74
|
reach: dict[tuple[str, int], np.ndarray] = field(default_factory=dict) # (piece, k) -> P(piece runs in round k)
|
|
65
75
|
|
|
66
76
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
def inv_rate_mean(m, v, floor: float | None) -> np.ndarray:
|
|
78
|
+
"""`E[1 / max(R, f)]` for `log R ~ N(m, v)` and the floor `f` ($ per token; None or 0: no floor), per entry
|
|
79
|
+
of `m`. With `l = log f`: `exp(-m + v/2) Phi((m - l)/sqrt(v) - sqrt(v)) + exp(-l) Phi((l - m)/sqrt(v))`,
|
|
80
|
+
each term in logs (`log Phi`), so a far tail never multiplies an overflow by 0. With `v = 0` the rate is
|
|
81
|
+
certain: `1 / max(exp(m), f)`."""
|
|
82
|
+
m = np.asarray(m, float)
|
|
83
|
+
v = np.broadcast_to(np.asarray(v, float), m.shape)
|
|
84
|
+
if not floor or floor <= 0:
|
|
85
|
+
return np.exp(-m + 0.5 * v)
|
|
86
|
+
lf = math.log(floor)
|
|
87
|
+
certain = v <= 0
|
|
88
|
+
sd = np.sqrt(np.where(certain, 1.0, v))
|
|
89
|
+
spread = np.exp(-m + 0.5 * v + log_ndtr((m - lf) / sd - sd)) + np.exp(log_ndtr((lf - m) / sd) - lf)
|
|
90
|
+
return np.where(certain, np.exp(-np.maximum(m, lf)), spread)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def compose(st: Structure, cost_eta: dict, rate_eta: dict, gate_eta: dict, *, sigma_usd: float,
|
|
94
|
+
sigma_rate: float, floors: Mapping[str, float] | None = None, rate_var: Mapping | None = None,
|
|
95
|
+
rng: np.random.Generator | None) -> RunDraws:
|
|
96
|
+
"""Compose one configuration. `cost_eta[(piece, k)]`, `rate_eta[(piece, k)]` and `gate_eta[(gate index, k)]`
|
|
97
|
+
are draw arrays; `floors[piece]` is the piece model's floor in $ per token (absent or 0: none). The expected
|
|
98
|
+
tokens take `log R ~ N(rate_eta, rate_var[(piece, k)])`, `sigma_rate ** 2` when `rate_var` is None (the mean
|
|
99
|
+
path passes the rate rows' known parts and adds their unseen variance there). With `rng` None nothing is
|
|
100
|
+
simulated: the `sim_*` arrays stay zero."""
|
|
71
101
|
n = len(next(iter(cost_eta.values())))
|
|
72
|
-
half_u,
|
|
102
|
+
half_u, var_r = 0.5 * sigma_usd ** 2, sigma_rate ** 2
|
|
103
|
+
floors = floors or {}
|
|
73
104
|
exp_u, exp_t = np.zeros(n), np.zeros(n)
|
|
74
105
|
sim_u, sim_t = np.zeros(n), np.zeros(n)
|
|
75
106
|
rounds = np.ones(n)
|
|
@@ -79,7 +110,7 @@ def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *,
|
|
|
79
110
|
pieces[p] = PieceDraws(np.zeros(n), np.zeros(n), np.zeros(n), np.zeros(n), np.ones(n), sim_runs=np.zeros(n))
|
|
80
111
|
if st.piece_loop.get(p) is not None:
|
|
81
112
|
pieces[p].round_free = all(
|
|
82
|
-
np.array_equal(cost_eta[(p, k)], cost_eta[(p, 1)]) and np.array_equal(
|
|
113
|
+
np.array_equal(cost_eta[(p, k)], cost_eta[(p, 1)]) and np.array_equal(rate_eta[(p, k)], rate_eta[(p, 1)])
|
|
83
114
|
for k in range(2, st.k_max + 1))
|
|
84
115
|
gate_after = {g.after: gi for gi, g in enumerate(st.gates)}
|
|
85
116
|
for piece, gi in gate_after.items():
|
|
@@ -88,17 +119,20 @@ def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *,
|
|
|
88
119
|
|
|
89
120
|
def run_piece(piece: str, k: int, weight: np.ndarray, alive: np.ndarray):
|
|
90
121
|
w = st.widths[piece]
|
|
91
|
-
eu,
|
|
122
|
+
eu, er = cost_eta[(piece, k)], rate_eta[(piece, k)]
|
|
123
|
+
f = float(floors.get(piece) or 0.0)
|
|
92
124
|
cu = w * np.exp(eu + half_u)
|
|
93
|
-
ct =
|
|
125
|
+
ct = cu * inv_rate_mean(er, var_r if rate_var is None else rate_var[(piece, k)], f)
|
|
94
126
|
pd = pieces[piece]
|
|
95
127
|
pd.exp_usd += weight * cu
|
|
96
128
|
pd.exp_tokens += weight * ct
|
|
97
129
|
su = np.zeros(n)
|
|
98
130
|
stt = np.zeros(n)
|
|
99
131
|
for _ in range(w if rng is not None else 0):
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
u = np.exp(eu + sigma_usd * rng.standard_normal(n))
|
|
133
|
+
r = np.exp(er + sigma_rate * rng.standard_normal(n))
|
|
134
|
+
su += u
|
|
135
|
+
stt += u / (np.maximum(r, f) if f > 0 else r)
|
|
102
136
|
pd.sim_usd += alive * su
|
|
103
137
|
pd.sim_tokens += alive * stt
|
|
104
138
|
pd.sim_runs += alive
|
loopmath/belief/design.py
CHANGED
|
@@ -30,8 +30,12 @@ from .pricing import price_term
|
|
|
30
30
|
Term = tuple[str, "str | None", float]
|
|
31
31
|
|
|
32
32
|
# Node names of this design (spec 04 section 4). 2 (loopmath 0.2): shape keys, psrc nodes and role groups.
|
|
33
|
-
# 3 (0.2): `horizon:log2h` is relative to the fit's reference horizon.
|
|
34
|
-
|
|
33
|
+
# 3 (0.2): `horizon:log2h` is relative to the fit's reference horizon. 4 (0.2.8): a `rate` head on the price
|
|
34
|
+
# per token replaces the tokens head. A fit of another version is not read.
|
|
35
|
+
DESIGN_VERSION = 4
|
|
36
|
+
# The version the seed key hashes (spec 04 section 3). It stays 3 from 0.2.8: the same data seeds the same
|
|
37
|
+
# draws as in 0.2.7, so every number that is not a token count is 0.2.7's.
|
|
38
|
+
SEED_VERSION = 3
|
|
35
39
|
HEURISTIC_COST_WEIGHT = 0.7 # spec 04 section 2 and D27: allocated (heuristic) cost rows
|
|
36
40
|
USER_SOURCES = ("live", "backlog", "history", "user", "orchestrator", "onboard", "habit", "")
|
|
37
41
|
HORIZON_NODES = ("horizon:timebox", "horizon:log2h") # fixed terms of a timeboxed run (spec 04 section 1)
|
loopmath/belief/features.py
CHANGED
|
@@ -35,6 +35,8 @@ LN2 = math.log(2.0)
|
|
|
35
35
|
# cost and tokens scale with an elasticity of 1 +- 0.5; logit and score heads start flat.
|
|
36
36
|
HORIZON_PRIOR = {
|
|
37
37
|
"cost": {"horizon:log2h": (LN2, 0.35), "horizon:timebox": (0.0, 1.5)},
|
|
38
|
+
# the price per token (0.2.8): a longer horizon buys more tokens, not dearer ones
|
|
39
|
+
"rate": {"horizon:log2h": (0.0, 0.35), "horizon:timebox": (0.0, 1.5)},
|
|
38
40
|
"logit": {"horizon:log2h": (0.0, 0.25), "horizon:timebox": (0.0, 1.0)},
|
|
39
41
|
"score": {"horizon:log2h": (0.0, 0.25), "horizon:timebox": (0.0, 1.0)},
|
|
40
42
|
}
|
loopmath/belief/fit.py
CHANGED
|
@@ -31,12 +31,12 @@ from scipy import sparse
|
|
|
31
31
|
from ..taskmodel import FeatureConfigError, FeatureSet
|
|
32
32
|
from . import design as design_rows
|
|
33
33
|
from . import priors as prior_data
|
|
34
|
-
from .design import (DESIGN_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row,
|
|
35
|
-
task_features, task_terms)
|
|
34
|
+
from .design import (DESIGN_VERSION, SEED_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row,
|
|
35
|
+
structure, task_features, task_terms)
|
|
36
36
|
from .features import FeatureTally, add_horizon, drop_features, horizon_nodes, horizon_specs
|
|
37
37
|
from .block import leaf_split
|
|
38
38
|
from .forest import Forest, default_scale, fixed_sd, scale_group
|
|
39
|
-
from .pricing import PRICE_NODE, PRICE_PRIOR_SD, STREAMS, fit_offsets, price_term
|
|
39
|
+
from .pricing import PRICE_NODE, PRICE_PRIOR_SD, STREAMS, fit_offsets, price_term, rate_floors
|
|
40
40
|
from .gaussian import N_DRAWS, Factors, GaussianHead, HeadFit, LogisticHead, Prior
|
|
41
41
|
from .state import SCORE_CLAMP, inverse_transform, transform # noqa: F401 (re-exported)
|
|
42
42
|
|
|
@@ -44,8 +44,12 @@ FIT_LOCK = ".fit.lock"
|
|
|
44
44
|
KEEP_FITS = 5 # The newest fits kept, besides the ones a receipt or stored recommendation names
|
|
45
45
|
TASK_LEVELS = ("org", "type", "repo", "subtype", "task")
|
|
46
46
|
MIN_SCORE_RUNS = 3 # spec 04 section 2: a score head needs at least 3 measured runs
|
|
47
|
-
|
|
47
|
+
# `tokens` (log tokens) is 0.2.7's head: from 0.2.8 its rows are built for the seed key and the node index
|
|
48
|
+
# only, never fitted. `rate` (log usd - log tokens, the price per token) is fitted in its place (spec 04 section 2).
|
|
49
|
+
HEAD_KIND = {"cost": ("gaussian", "cost"), "tokens": ("gaussian", "cost"), "rate": ("gaussian", "cost"),
|
|
48
50
|
"gate": ("logistic", "logit"), "success": ("logistic", "logit")}
|
|
51
|
+
KEY_ONLY = ("tokens",) # heads whose rows the seed key and the node index read, and no fit
|
|
52
|
+
NOT_KEYED = ("rate",) # heads the seed key leaves out, so 0.2.8 seeds every draw as 0.2.7 did
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
class FitBusy(RuntimeError):
|
|
@@ -273,6 +277,9 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
273
277
|
acc[i] += v
|
|
274
278
|
if a.tokens:
|
|
275
279
|
heads["tokens"].add(terms, math.log(a.tokens), a.weight, pr.run_id, pr.source, ttype)
|
|
280
|
+
if a.usd and a.tokens:
|
|
281
|
+
heads["rate"].add(terms, math.log(a.usd) - math.log(a.tokens), a.weight, pr.run_id, pr.source,
|
|
282
|
+
ttype)
|
|
276
283
|
for gi, k, passed in pr.gates:
|
|
277
284
|
heads["gate"].add(gate_row(pr.task, pr.source, st, st.gates[gi], k, features), 1.0 if passed else 0.0,
|
|
278
285
|
1.0, pr.run_id, pr.source, ttype)
|
|
@@ -305,6 +312,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
305
312
|
for hr in heads.values():
|
|
306
313
|
hr.rows = add_horizon(drop_features(hr.rows, tally.nodes), hr.runs, tally.run_horizon, coding)
|
|
307
314
|
add_prices(heads["cost"], {m: len(r) for m, r in model_runs.items()}, model_streams)
|
|
315
|
+
heads["rate"].prices = heads["cost"].prices # the price per token carries the same offset (spec 04 section 2)
|
|
316
|
+
_price_rows(heads["rate"])
|
|
308
317
|
return (heads, score_info, dict(dropped), dict(runs_by_source), {c: dict(n) for c, n in config_support.items()},
|
|
309
318
|
dict(checks), dict(overlap), dict(labels), tally)
|
|
310
319
|
|
|
@@ -313,7 +322,12 @@ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float
|
|
|
313
322
|
"""The price offset (spec 04 section 2): the fit's offsets from its runs and token mix under the current price
|
|
314
323
|
table, and each cost row with its model's `price:offset` term (`pricing.price_term`, as at prediction)."""
|
|
315
324
|
hr.prices = fit_offsets(runs, streams, design_rows._price_table())
|
|
316
|
-
|
|
325
|
+
_price_rows(hr)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _price_rows(hr: HeadRows) -> None:
|
|
329
|
+
"""Each row with its model's `price:offset` term under `hr.prices`."""
|
|
330
|
+
offsets = hr.prices.get("models")
|
|
317
331
|
if not offsets:
|
|
318
332
|
return
|
|
319
333
|
rows = []
|
|
@@ -325,10 +339,11 @@ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float
|
|
|
325
339
|
|
|
326
340
|
|
|
327
341
|
def price_spec(hr: HeadRows) -> list:
|
|
328
|
-
"""The prior that holds `price:offset` at 1, on the cost
|
|
342
|
+
"""The prior that holds `price:offset` at 1, on the cost and rate heads of a fit with offsets, --no-prior
|
|
343
|
+
included."""
|
|
329
344
|
if not hr.prices.get("models"):
|
|
330
345
|
return []
|
|
331
|
-
return [prior_data.FactorSpec(
|
|
346
|
+
return [prior_data.FactorSpec(hr.name, [(PRICE_NODE, None, 1.0)], 1.0, PRICE_PRIOR_SD ** 2,
|
|
332
347
|
f"{PRICE_NODE} held at 1, prior N(1, {PRICE_PRIOR_SD}^2)")]
|
|
333
348
|
|
|
334
349
|
|
|
@@ -417,18 +432,21 @@ def seed_for(fit_id: str, head: str) -> int:
|
|
|
417
432
|
|
|
418
433
|
|
|
419
434
|
def input_key(heads_rows: dict[str, HeadRows], specs: list, settings: dict) -> str:
|
|
420
|
-
"""The fit's `seed_key` (spec 04 section 3): the sha256 of the
|
|
435
|
+
"""The fit's `seed_key` (spec 04 section 3): the sha256 of the seed version, the settings that change the
|
|
421
436
|
posterior, the prior factors and every head's rows (terms, response, weight, run, source), each head's rows
|
|
422
437
|
sorted, so the same data and settings seed the same draws whatever the store's reading order.
|
|
423
438
|
|
|
424
439
|
A success row's weight is its q. With `outcome.q` configured, the key takes each row's q under the built-in
|
|
425
|
-
map (`HeadRows.seed_w`), so setting q redraws nothing: only the success head's posterior moves (0.2.7).
|
|
440
|
+
map (`HeadRows.seed_w`), so setting q redraws nothing: only the success head's posterior moves (0.2.7).
|
|
441
|
+
|
|
442
|
+
From 0.2.8 the key reads 0.2.7's heads: the tokens rows, which no head fits any more, and not the rate rows,
|
|
443
|
+
with the seed version 0.2.7 hashed as its design version. The same data seeds the same draws as in 0.2.7."""
|
|
426
444
|
h = hashlib.sha256()
|
|
427
|
-
h.update(repr((
|
|
445
|
+
h.update(repr((SEED_VERSION, design_rows.TIMEBOX_EFFORT_COST, design_rows.TIMEBOX_LEVELS,
|
|
428
446
|
sorted(settings.items()))).encode())
|
|
429
447
|
for spec in specs:
|
|
430
448
|
h.update(repr((spec.head, spec.terms, spec.mean, spec.var)).encode())
|
|
431
|
-
for name in sorted(heads_rows):
|
|
449
|
+
for name in sorted(n for n in heads_rows if n not in NOT_KEYED):
|
|
432
450
|
hr = heads_rows[name]
|
|
433
451
|
h.update(f"\n{name}:{len(hr.rows)}".encode())
|
|
434
452
|
weights = hr.seed_w or hr.w
|
|
@@ -618,9 +636,12 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
618
636
|
continue
|
|
619
637
|
nodes = horizon_nodes(hr.rows)
|
|
620
638
|
if nodes: # the horizon priors, --no-prior included (spec 04 section 1)
|
|
621
|
-
head_specs += horizon_specs(name, hr.kind, nodes)
|
|
622
|
-
if name
|
|
639
|
+
head_specs += horizon_specs(name, "rate" if name == "rate" else hr.kind, nodes)
|
|
640
|
+
if name in ("cost", "rate"): # the price offset (spec 04 section 2)
|
|
623
641
|
head_specs += price_spec(hr)
|
|
642
|
+
if name in KEY_ONLY: # 0.2.7's tokens head: its nodes enter the index where they did, unfitted
|
|
643
|
+
build_matrix(hr.rows, forest, [(n, p) for spec in head_specs for n, p, _ in spec.terms])
|
|
644
|
+
continue
|
|
624
645
|
fitted[name] = fit_head(hr, forest, head_specs, eb=eb)
|
|
625
646
|
fit_id = new_fit_id(fits, now)
|
|
626
647
|
partial = fits / f"{fit_id}.partial"
|
|
@@ -685,6 +706,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
685
706
|
"features": {**tally.report, **({"error": features_error} if features_error else {})},
|
|
686
707
|
"horizons": tally.horizon_report(),
|
|
687
708
|
"price_offsets": heads_rows["cost"].prices,
|
|
709
|
+
# the cheapest rate of each priced model, $ per token: a draw's tokens never cost less (spec 04 section 3)
|
|
710
|
+
"rate_floors": rate_floors(design_rows._price_table()),
|
|
688
711
|
}
|
|
689
712
|
if full:
|
|
690
713
|
meta["full"] = _full_check(fitted)
|
loopmath/belief/lookahead.py
CHANGED
|
@@ -394,7 +394,7 @@ def _condition_head(head: HeadState, terms: Sequence[tuple], tau2: float) -> Hea
|
|
|
394
394
|
def conditioned(state: FitState, task: Task, config: Configuration, rule: AcceptanceRule | None = None) -> FitState:
|
|
395
395
|
heads = dict(state.heads)
|
|
396
396
|
terms, n_obs = _explore_cost_obs(state, task, config)
|
|
397
|
-
for name in ("cost", "
|
|
397
|
+
for name in ("cost", "rate"):
|
|
398
398
|
heads[name] = _condition_head(state.heads[name], terms, state.heads[name].sigma ** 2 / n_obs)
|
|
399
399
|
run_terms = state.task_terms(task) + list(state._plan(config)["run"])
|
|
400
400
|
succ = state.heads["success"]
|
loopmath/belief/pricing.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"""The price offset on the cost
|
|
1
|
+
"""The price offset on the cost and rate heads (spec 04 section 2, 0.2.2), and the rate floors (0.2.8).
|
|
2
2
|
|
|
3
|
-
A model's run cost moves with its list price. Every cost row of a model version `m
|
|
4
|
-
|
|
3
|
+
A model's run cost moves with its list price. Every cost row of a model version `m`, and from 0.2.8 every rate
|
|
4
|
+
row (the price per token), carries the fixed term `price:offset` (not the gate, success or score rows), with
|
|
5
|
+
the value
|
|
5
6
|
|
|
6
7
|
o_m = s_m * log(P_m / R_m), s_m = n0 / (n0 + n_m),
|
|
7
8
|
|
|
@@ -18,6 +19,10 @@ the same at fit and at prediction:
|
|
|
18
19
|
|
|
19
20
|
`fit_offsets` computes the offset of every model with cost rows and of every model in the price table once per
|
|
20
21
|
fit; `meta.json` `price_offsets` stores them, and prediction and the workflow search read them from there.
|
|
22
|
+
|
|
23
|
+
`rate_floors` gives each priced model's cache-read rate in $ per token, the cheapest rate any of its tokens
|
|
24
|
+
bills at (in every row of the packaged table, no nonzero rate is lower). A draw's price per token is held at or
|
|
25
|
+
above it, so its tokens never cost less than its dollars (spec 04 section 3). `meta.json` stores them too.
|
|
21
26
|
"""
|
|
22
27
|
|
|
23
28
|
from __future__ import annotations
|
|
@@ -140,3 +145,17 @@ def price_term(offsets: Mapping[str, float] | None, model: str) -> tuple[tuple[s
|
|
|
140
145
|
return ()
|
|
141
146
|
o = offsets.get(model)
|
|
142
147
|
return ((PRICE_NODE, None, float(o)),) if o else ()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def rate_floors(table) -> dict[str, float]:
|
|
151
|
+
"""Model (canonical id) to its cache-read rate in $ per token, for every model with a positive one; empty
|
|
152
|
+
without a table. Two table keys of one model keep the lower rate."""
|
|
153
|
+
out: dict[str, float] = {}
|
|
154
|
+
if table is None:
|
|
155
|
+
return out
|
|
156
|
+
for key, rate in table.rates.items():
|
|
157
|
+
floor = float(rate.get("cache_read") or 0.0) / 1e6
|
|
158
|
+
if floor > 0:
|
|
159
|
+
model = canonical_model_id(key)
|
|
160
|
+
out[model] = min(floor, out.get(model, floor))
|
|
161
|
+
return dict(sorted(out.items()))
|
loopmath/belief/state.py
CHANGED
|
@@ -16,15 +16,21 @@ Prediction:
|
|
|
16
16
|
(setting, role, topology, position, round, control) is cached per fit, so `predict_many`
|
|
17
17
|
over thousands of candidates reuses rows.
|
|
18
18
|
- The composition (compose.py) runs over the draws. Cost intervals are predictive.
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
- Tokens (0.2.8): a draw's tokens are its dollars over its price per token (the rate head),
|
|
20
|
+
the rate held at or above the model's cache-read rate (`meta.rate_floors`), so no shown
|
|
21
|
+
tokens cost less than the dollars beside them (compose.py).
|
|
22
|
+
- Means do not sample the unseen nodes: their effects are normal, so each cost row's
|
|
23
|
+
expected cost is log-normal in closed form over the known draws, and the expected tokens
|
|
24
|
+
are that times `E[1 / max(R, f)]` with the rate row's unseen variance added to its own
|
|
25
|
+
(`compose.inv_rate_mean`). The unseen task effect on the gates (one normal shared by every
|
|
26
|
+
gate row of the task) and on success or a score are integrated by Gauss-Hermite, and a
|
|
27
|
+
score's reach chance is a normal CDF.
|
|
23
28
|
The `ell.tokens` mean prices the rescue at each draw's own tokens per dollar, as its draws
|
|
24
|
-
do: `E[
|
|
25
|
-
(
|
|
26
|
-
over
|
|
27
|
-
|
|
29
|
+
do: `E[T_run / C_run]`, the mean of the run's tokens per dollar, an average of each group's
|
|
30
|
+
(pieces with the same unseen cost nodes) weighted by its cost (`_tokens_per_usd`). It is
|
|
31
|
+
integrated over every unseen cost effect, the task's and each piece's (a new model, role or
|
|
32
|
+
position): exact when the pieces share them (the task's cancels), otherwise over their
|
|
33
|
+
differences, by Gauss-Hermite up to four axes and a fixed Sobol set beyond, never more than 256 points.
|
|
28
34
|
A new task's means therefore do not depend on its id, and the Monte Carlo error of 400
|
|
29
35
|
seeded draws of a large unseen effect (task, repo, org) leaves them. Intervals, the draws
|
|
30
36
|
(`success_draws`, `ell_draws`) and the simulated runs keep the sampled effects. Unseen
|
|
@@ -57,14 +63,14 @@ from ..types import (
|
|
|
57
63
|
)
|
|
58
64
|
from .compose import Intervals, RunDraws, compose, cost_per_round, interval
|
|
59
65
|
from .design import Structure, cost_rest, gate_rest, other_design, run_rest, structure, task_features, task_terms
|
|
60
|
-
from .forest import LEVEL_ALIASES, default_scale, display_level, level_of, node_key, scale_group
|
|
66
|
+
from .forest import LEVEL_ALIASES, canonical_model_id, default_scale, display_level, level_of, node_key, scale_group
|
|
61
67
|
from .block import BlockFactor, blocks, leading_diagonal
|
|
62
68
|
from .gaussian import _cho, cov_factor
|
|
63
69
|
|
|
64
70
|
N_DRAWS = 400
|
|
65
71
|
PREDICT_SOURCE = "user"
|
|
66
72
|
MIN_SCORE_SWITCH = 5 # spec 04 section 2: g = p_reach once the score head has 5 runs in the task's type
|
|
67
|
-
BASE_HEADS = {"cost": ("gaussian", "cost"), "
|
|
73
|
+
BASE_HEADS = {"cost": ("gaussian", "cost"), "rate": ("gaussian", "cost"), "gate": ("logistic", "logit"),
|
|
68
74
|
"success": ("logistic", "logit")}
|
|
69
75
|
TASK_CHAIN = ("task", "subtype", "repo", "type")
|
|
70
76
|
FIXED_LEVELS = ("fixed", "round", "control", "price")
|
|
@@ -123,7 +129,7 @@ def normal_mean(f: Callable[[np.ndarray], np.ndarray], eta: np.ndarray, s2: floa
|
|
|
123
129
|
MAX_POINTS = 256 # The most points `_rule` gives, whatever the number of axes
|
|
124
130
|
_GRID_NODES = {1: MEAN_NODES, 2: 8, 3: 6, 4: 4} # Gauss-Hermite nodes per axis; nodes ** k <= MAX_POINTS
|
|
125
131
|
_SOBOL_SEED = 106
|
|
126
|
-
_CHUNK = 64 # points evaluated at once in `
|
|
132
|
+
_CHUNK = 64 # points evaluated at once in `_ratio_mean`
|
|
127
133
|
|
|
128
134
|
|
|
129
135
|
@lru_cache(maxsize=32)
|
|
@@ -146,6 +152,32 @@ def _rule(k: int) -> tuple[np.ndarray, np.ndarray]:
|
|
|
146
152
|
return ndtri(u), np.full(MAX_POINTS, 1.0 / MAX_POINTS)
|
|
147
153
|
|
|
148
154
|
|
|
155
|
+
|
|
156
|
+
def _ratio_mean(A: np.ndarray, B: np.ndarray, L: np.ndarray) -> np.ndarray:
|
|
157
|
+
"""`E[sum_i B_i exp(L_i . z) / sum_i A_i exp(L_i . z)]` per column of `A` and `B` (groups by entries), `z`
|
|
158
|
+
standard normal: a mean of the `B_i / A_i` weighted by `A_i exp(L_i . z)`, so it lies between their least and
|
|
159
|
+
greatest. Against a reference group `r` the ratio depends only on `d = (L_G - L_r) . z ~ N(0, Cov(d))`,
|
|
160
|
+
integrated on the principal axes of `Cov(d)` by `_rule`: at most `MAX_POINTS` points, evaluated `_CHUNK` at a
|
|
161
|
+
time, so memory and work stay bounded for any number of pieces. The reference is the group with the largest
|
|
162
|
+
cost. With one group the ratio is `B / A`."""
|
|
163
|
+
ref = int(np.argmax(A.mean(axis=1)))
|
|
164
|
+
others = [i for i in range(len(A)) if i != ref]
|
|
165
|
+
if not others:
|
|
166
|
+
return np.divide(B[ref], A[ref], out=np.zeros_like(A[ref]), where=A[ref] > 0)
|
|
167
|
+
D = L[others] - L[ref]
|
|
168
|
+
w, V = np.linalg.eigh(D @ D.T)
|
|
169
|
+
keep = w > 1e-12 * max(float(w.max()), 1e-300)
|
|
170
|
+
pts, wts = _rule(int(keep.sum()))
|
|
171
|
+
axes = (V[:, keep] * np.sqrt(w[keep]))[:, ::-1] # the largest axis first
|
|
172
|
+
out = np.zeros(A.shape[1])
|
|
173
|
+
for i in range(0, len(wts), _CHUNK):
|
|
174
|
+
E = np.exp(pts[i:i + _CHUNK] @ axes.T)
|
|
175
|
+
S = A[ref][None, :] + E @ A[others]
|
|
176
|
+
N = B[ref][None, :] + E @ B[others]
|
|
177
|
+
out += wts[i:i + _CHUNK] @ np.divide(N, S, out=np.zeros_like(S), where=S > 0)
|
|
178
|
+
return out
|
|
179
|
+
|
|
180
|
+
|
|
149
181
|
class HeadState:
|
|
150
182
|
"""One head's posterior: node ids, mean, covariance factor and joint draws (lazy).
|
|
151
183
|
|
|
@@ -341,6 +373,8 @@ class FitState:
|
|
|
341
373
|
self.timebox_effort = float(self.meta.get("timebox_effort", 1.0))
|
|
342
374
|
# spec 04 section 2: the fit's price offset per model (log scale); none in fits before 0.2.2
|
|
343
375
|
self.price_offsets: dict[str, float] = dict((self.meta.get("price_offsets") or {}).get("models") or {})
|
|
376
|
+
# spec 04 section 3 (0.2.8): each priced model's cache-read rate, $ per token; a draw's rate stays above it
|
|
377
|
+
self.rate_floors: dict[str, float] = dict(self.meta.get("rate_floors") or {})
|
|
344
378
|
if heads is None:
|
|
345
379
|
heads = {}
|
|
346
380
|
for name, hmeta in (self.meta.get("heads") or {}).items():
|
|
@@ -426,7 +460,9 @@ class FitState:
|
|
|
426
460
|
for gi, g in enumerate(st.gates):
|
|
427
461
|
for k in range(1, (st.k_max if st.gate_loop.get(gi) is not None else 1) + 1):
|
|
428
462
|
gate[(gi, k)] = gate_rest(st, g, k)
|
|
429
|
-
|
|
463
|
+
floors = {piece: float(self.rate_floors.get(canonical_model_id(st.settings[piece].model), 0.0))
|
|
464
|
+
for piece in st.pieces}
|
|
465
|
+
return {"config": config, "st": st, "cost": cost, "gate": gate, "run": run_rest(st), "floors": floors}
|
|
430
466
|
|
|
431
467
|
# ------------------------------------------------------------ the task as the fit reads it
|
|
432
468
|
|
|
@@ -554,7 +590,7 @@ class FitState:
|
|
|
554
590
|
gate_keys = [k for p in plans for k in p["gate"].values()]
|
|
555
591
|
run_keys = [p["run"] for p in plans]
|
|
556
592
|
self._cached(self.heads["cost"], cost_keys)
|
|
557
|
-
self._cached(self.heads["
|
|
593
|
+
self._cached(self.heads["rate"], cost_keys)
|
|
558
594
|
if gate_keys:
|
|
559
595
|
self._cached(self.heads["gate"], gate_keys)
|
|
560
596
|
for name, head in self.heads.items():
|
|
@@ -652,8 +688,9 @@ class FitState:
|
|
|
652
688
|
"""The composition's expectations with the unseen effects integrated out, and the map
|
|
653
689
|
from its arrays to one value per known draw.
|
|
654
690
|
|
|
655
|
-
A cost
|
|
656
|
-
|
|
691
|
+
A cost row enters the composition only through `exp(eta)`, times weights that depend on
|
|
692
|
+
the gates alone, so its unseen effect `N(0, s2)` becomes `eta + s2 / 2`. A rate row enters
|
|
693
|
+
through `E[1 / max(R, f)]`: its known part goes in with the variance `sigma^2 + s2`. The
|
|
657
694
|
unseen task effect on the gates is one normal added to every gate row, integrated by
|
|
658
695
|
Gauss-Hermite (the loop's reach is not linear in it): the draws are tiled once per node.
|
|
659
696
|
"""
|
|
@@ -665,7 +702,10 @@ class FitState:
|
|
|
665
702
|
return known + 0.5 * s2
|
|
666
703
|
|
|
667
704
|
cost = {k: lognormal("cost", r) for k, r in plan["cost"].items()}
|
|
668
|
-
|
|
705
|
+
rate, rate_var = {}, {}
|
|
706
|
+
for k, r in plan["cost"].items():
|
|
707
|
+
rate[k], s2 = self._known(heads["rate"], tparts["rate"], r)
|
|
708
|
+
rate_var[k] = heads["rate"].sigma ** 2 + s2
|
|
669
709
|
tg: Row = tparts["gate"]
|
|
670
710
|
gate = {k: tg.known + self._row(heads["gate"], r).draws for k, r in plan["gate"].items()}
|
|
671
711
|
nodes = MEAN_NODES if gate and tg.s2 > 0 else 1
|
|
@@ -673,57 +713,45 @@ class FitState:
|
|
|
673
713
|
shift = math.sqrt(tg.s2) * _GX[:, None]
|
|
674
714
|
gate = {k: (v[None, :] + shift).ravel() for k, v in gate.items()}
|
|
675
715
|
cost = {k: np.tile(v, nodes) for k, v in cost.items()}
|
|
676
|
-
|
|
677
|
-
run = compose(st, cost,
|
|
716
|
+
rate = {k: np.tile(v, nodes) for k, v in rate.items()}
|
|
717
|
+
run = compose(st, cost, rate, gate, sigma_usd=heads["cost"].sigma, sigma_rate=heads["rate"].sigma,
|
|
718
|
+
floors=plan["floors"], rate_var=rate_var, rng=None)
|
|
678
719
|
if nodes == 1:
|
|
679
720
|
return run, lambda a: a
|
|
680
721
|
return run, lambda a: _GW @ np.reshape(a, (nodes, -1))
|
|
681
722
|
|
|
682
|
-
def
|
|
683
|
-
"""`E[
|
|
723
|
+
def _tokens_per_usd(self, plan: dict, tparts: dict, mr: RunDraws) -> np.ndarray:
|
|
724
|
+
"""`E[T_run / C_run]` per entry of the mean composition `mr`, every unseen cost and rate effect
|
|
684
725
|
integrated out (the task's, and a new model's, role's or position's).
|
|
685
726
|
|
|
686
727
|
`C_run = sum over pieces of A_p exp(u_p)`: `A_p` is the piece's expected cost at its known
|
|
687
728
|
nodes (its `mr` cost without the log-normal factor), `u_p` its unseen effect. Every round of
|
|
688
729
|
a piece has the same unseen nodes (round and control terms are fixed effects), and pieces
|
|
689
|
-
with the same unseen nodes form one group.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
`E[
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
The reference is the group with the largest cost, which bounds the integrand by
|
|
696
|
-
`1 / A_r`.
|
|
730
|
+
with the same unseen nodes form one group G. A piece's tokens are its cost times
|
|
731
|
+
`Q = 1 / max(R, f)`, and Q is independent of every cost effect, so given those effects the run's
|
|
732
|
+
tokens are `sum over G of B_G exp(u_G)`, `B_G` the group's `mr` tokens without the same factor.
|
|
733
|
+
So `E[T / C] = E[sum B_G exp(u_G) / sum A_G exp(u_G)]`: the run's tokens per dollar, the groups'
|
|
734
|
+
`B_G / A_G` weighted by their cost. The task's unseen effect is common and cancels; with one group
|
|
735
|
+
this is `B / A`. `_ratio_mean` integrates the rest.
|
|
697
736
|
"""
|
|
698
737
|
head = self.heads["cost"]
|
|
738
|
+
s2 = tparts["cost"].s2
|
|
699
739
|
groups: dict[tuple, np.ndarray] = {}
|
|
740
|
+
tokens: dict[tuple, np.ndarray] = {}
|
|
700
741
|
for piece in plan["st"].pieces:
|
|
701
742
|
load = self._unseen(head, plan["cost"][(piece, 1)])
|
|
702
|
-
|
|
743
|
+
known = math.exp(-0.5 * (s2 + sum(b * b for _, b in load)))
|
|
744
|
+
a = mr.pieces[piece].exp_usd * known
|
|
745
|
+
t = mr.pieces[piece].exp_tokens * known
|
|
703
746
|
groups[load] = groups[load] + a if load in groups else a
|
|
747
|
+
tokens[load] = tokens[load] + t if load in tokens else t
|
|
704
748
|
loads = list(groups)
|
|
705
|
-
A = np.stack([groups[g] for g in loads])
|
|
706
749
|
names = {n: j for j, n in enumerate(sorted({n for g in loads for n, _ in g}))}
|
|
707
750
|
L = np.zeros((len(loads), len(names)))
|
|
708
751
|
for i, g in enumerate(loads):
|
|
709
752
|
for n, b in g:
|
|
710
753
|
L[i, names[n]] = b
|
|
711
|
-
|
|
712
|
-
scale = math.exp(0.5 * (tparts["cost"].s2 + float(L[ref] @ L[ref])))
|
|
713
|
-
others = [i for i in range(len(loads)) if i != ref]
|
|
714
|
-
if not others:
|
|
715
|
-
return scale * np.divide(1.0, A[ref], out=np.zeros_like(A[ref]), where=A[ref] > 0)
|
|
716
|
-
D = L[others] - L[ref]
|
|
717
|
-
w, V = np.linalg.eigh(D @ D.T)
|
|
718
|
-
keep = w > 1e-12 * max(float(w.max()), 1e-300)
|
|
719
|
-
pts, wts = _rule(int(keep.sum()))
|
|
720
|
-
axes = (V[:, keep] * np.sqrt(w[keep]))[:, ::-1] # the largest axis first
|
|
721
|
-
mu = -(D @ L[ref])
|
|
722
|
-
out = np.zeros(A.shape[1])
|
|
723
|
-
for i in range(0, len(wts), _CHUNK):
|
|
724
|
-
S = A[ref][None, :] + np.exp(mu[None, :] + pts[i:i + _CHUNK] @ axes.T) @ A[others]
|
|
725
|
-
out += wts[i:i + _CHUNK] @ np.divide(1.0, S, out=np.zeros_like(S), where=S > 0)
|
|
726
|
-
return scale * out
|
|
754
|
+
return _ratio_mean(np.stack([groups[g] for g in loads]), np.stack([tokens[g] for g in loads]), L)
|
|
727
755
|
|
|
728
756
|
def _predict(self, task: Task, plan: dict, tparts: dict, rule: AcceptanceRule | None,
|
|
729
757
|
rescue_usd: float | None) -> tuple[Prediction, dict]:
|
|
@@ -731,11 +759,11 @@ class FitState:
|
|
|
731
759
|
cfg: Configuration = plan["config"]
|
|
732
760
|
heads = self.heads
|
|
733
761
|
cost_eta = {k: self._eta(heads["cost"], tparts["cost"], r)[1] for k, r in plan["cost"].items()}
|
|
734
|
-
|
|
762
|
+
rate_eta = {k: self._eta(heads["rate"], tparts["rate"], r)[1] for k, r in plan["cost"].items()}
|
|
735
763
|
gate_eta = {k: self._eta(heads["gate"], tparts["gate"], r)[1] for k, r in plan["gate"].items()}
|
|
736
764
|
rng = np.random.default_rng(self.sim_seed)
|
|
737
|
-
rd: RunDraws = compose(st, cost_eta,
|
|
738
|
-
|
|
765
|
+
rd: RunDraws = compose(st, cost_eta, rate_eta, gate_eta, sigma_usd=heads["cost"].sigma,
|
|
766
|
+
sigma_rate=heads["rate"].sigma, floors=plan["floors"], rng=rng)
|
|
739
767
|
_, s_eta = self._eta(heads["success"], tparts["success"], plan["run"])
|
|
740
768
|
g = expit(s_eta)
|
|
741
769
|
s_known, s_s2 = self._known(heads["success"], tparts["success"], plan["run"])
|
|
@@ -758,11 +786,10 @@ class FitState:
|
|
|
758
786
|
|
|
759
787
|
m_usd, m_tok = per_draw(mr.exp_usd), per_draw(mr.exp_tokens)
|
|
760
788
|
# The ell.tokens mean is the expectation of what its draws hold, the rescue priced at
|
|
761
|
-
# the draw's own tokens per dollar
|
|
762
|
-
#
|
|
763
|
-
#
|
|
764
|
-
|
|
765
|
-
m_per_usd = per_draw(mr.exp_tokens * self._inv_cost(plan, tparts, mr)) if rescue > 0 else 0.0
|
|
789
|
+
# the draw's own tokens per dollar, E[T / C]: `_tokens_per_usd` integrates it over every
|
|
790
|
+
# unseen cost and rate effect. The gates' unseen task effect stays under Gauss-Hermite.
|
|
791
|
+
# Without a rescue the ratio does not enter.
|
|
792
|
+
m_per_usd = per_draw(self._tokens_per_usd(plan, tparts, mr)) if rescue > 0 else 0.0
|
|
766
793
|
iv = Intervals()
|
|
767
794
|
handles = {}
|
|
768
795
|
for piece, pd in rd.pieces.items():
|
|
@@ -869,15 +896,22 @@ class FitState:
|
|
|
869
896
|
return out
|
|
870
897
|
|
|
871
898
|
def node_summary(self, level: str | None = None, head: str | None = None) -> list[NodeSummary]:
|
|
872
|
-
"""Every fitted node's effect: its own deviation plus its ancestors' (the node's belief).
|
|
899
|
+
"""Every fitted node's effect: its own deviation plus its ancestors' (the node's belief).
|
|
900
|
+
|
|
901
|
+
From 0.2.8 the tokens entries are derived, in the rate head's place: a node's tokens multiplier is its
|
|
902
|
+
cost multiplier over its rate multiplier on the same draws (log T = log U - log R), with the rate head's
|
|
903
|
+
support. It is the effect before the floor: a prediction holds each draw's rate at or above the model's
|
|
904
|
+
cache-read rate, so where that binds, the predicted tokens move less than the multiplier says."""
|
|
873
905
|
parents = {n["id"]: n.get("parent") for n in self.design.get("nodes") or []}
|
|
874
906
|
support = self.design.get("support") or {}
|
|
875
907
|
wanted = set(LEVEL_ALIASES.get(level, (level,))) if level else None
|
|
876
908
|
out: list[NodeSummary] = []
|
|
877
909
|
for name, hs in self.heads.items():
|
|
878
|
-
|
|
910
|
+
shown = "tokens" if name == "rate" else name
|
|
911
|
+
if (head and shown != head) or not hs.fitted or not hs.nodes:
|
|
879
912
|
continue
|
|
880
913
|
draws = hs.draws
|
|
914
|
+
cost = self.heads["cost"] if name == "rate" else None
|
|
881
915
|
hsup = support.get(name) or {}
|
|
882
916
|
for j, node in enumerate(hs.nodes):
|
|
883
917
|
lvl = display_level(node)
|
|
@@ -894,13 +928,27 @@ class FitState:
|
|
|
894
928
|
if k is not None:
|
|
895
929
|
chain.append(k)
|
|
896
930
|
cur = parents.get(cur)
|
|
897
|
-
|
|
931
|
+
eff = draws[chain].sum(axis=0)
|
|
932
|
+
if cost is not None: # tokens: the cost chain's draws less the rate chain's
|
|
933
|
+
eff = self._chain(cost, node, parents) - eff
|
|
934
|
+
effect, display = self._effect(hs, eff)
|
|
898
935
|
n_runs, mix = hsup.get(node) or [0, {}]
|
|
899
|
-
out.append(NodeSummary(level=lvl, key=node_key(node), head=
|
|
936
|
+
out.append(NodeSummary(level=lvl, key=node_key(node), head=shown, effect=effect, display=display,
|
|
900
937
|
support=int(n_runs), parent=parents.get(node),
|
|
901
938
|
source_mix={str(k): int(v) for k, v in (mix or {}).items()}))
|
|
902
939
|
return out
|
|
903
940
|
|
|
941
|
+
@staticmethod
|
|
942
|
+
def _chain(hs: HeadState, node: str, parents: dict) -> np.ndarray:
|
|
943
|
+
"""The draws of a node's effect in one head: its own and its ancestors' that the head has."""
|
|
944
|
+
idx, cur = [], node
|
|
945
|
+
while cur:
|
|
946
|
+
k = hs.index.get(cur)
|
|
947
|
+
if k is not None:
|
|
948
|
+
idx.append(k)
|
|
949
|
+
cur = parents.get(cur)
|
|
950
|
+
return hs.draws[idx].sum(axis=0) if idx else np.zeros(N_DRAWS)
|
|
951
|
+
|
|
904
952
|
def _effect(self, hs: HeadState, eff: np.ndarray) -> tuple[Interval, Interval]:
|
|
905
953
|
"""cost: multiplier (x1.4); success and gate: log-odds shift, shown in pp at the baseline;
|
|
906
954
|
score: shift on the modelled scale, shown in raw units (log: ratio; fraction: pp)."""
|
loopmath/ingest/base.py
CHANGED
loopmath/prices.toml
CHANGED
|
@@ -142,7 +142,10 @@ output = 0.00
|
|
|
142
142
|
source = "OpenCode 1.17.11 model catalog, opencode free route"
|
|
143
143
|
|
|
144
144
|
# ---------------------------------------------------------------------------
|
|
145
|
-
# Rows checked on the provider pages on 2026-09-23.
|
|
145
|
+
# Rows checked on the provider pages on 2026-09-23; gpt-6.1-sol added and
|
|
146
|
+
# gpt-6-luna checked again on 2026-10-12 (no newer luna is listed). as_of
|
|
147
|
+
# stays the copied table's date: a row with no "checked" date takes it as
|
|
148
|
+
# the date of its tariff, and those rows were not read again.
|
|
146
149
|
# Each source names its page and the date it was read; the tariff record
|
|
147
150
|
# written with every priced attempt takes its date from "checked YYYY-MM-DD".
|
|
148
151
|
#
|
|
@@ -179,12 +182,19 @@ cache_write = 2.50
|
|
|
179
182
|
output = 10.00
|
|
180
183
|
source = "official list, https://developers.openai.com/api/docs/models/gpt-6-sol, checked 2026-09-23: standard tier, $2 input, $0.20 cached input, $2.50 cache write, $10 output; prompts over 272K input tokens bill more and are priced at this rate (a floor)"
|
|
181
184
|
|
|
185
|
+
["gpt-6.1-sol"]
|
|
186
|
+
input = 2.00
|
|
187
|
+
cache_read = 0.10
|
|
188
|
+
cache_write = 2.50
|
|
189
|
+
output = 10.00
|
|
190
|
+
source = "official list, https://developers.openai.com/api/docs/models/gpt-6.1-sol, checked 2026-10-12: standard tier, $2 input, $0.10 cached input, $2.50 cache write, $10 output; prompts over 272K input tokens bill more and are priced at this rate (a floor)"
|
|
191
|
+
|
|
182
192
|
["gpt-6-luna"]
|
|
183
193
|
input = 0.10
|
|
184
194
|
cache_read = 0.01
|
|
185
195
|
cache_write = 0.125
|
|
186
196
|
output = 0.50
|
|
187
|
-
source = "official list, https://developers.openai.com/api/docs/models/gpt-6-luna, checked 2026-
|
|
197
|
+
source = "official list, https://developers.openai.com/api/docs/models/gpt-6-luna, checked 2026-10-12: standard tier, $0.10 input, $0.01 cached input, $0.125 cache write, $0.50 output; prompts over 272K input tokens bill more and are priced at this rate (a floor)"
|
|
188
198
|
|
|
189
199
|
["opus-4.8"]
|
|
190
200
|
input = 5.00
|
loopmath/priors/benchmarks.py
CHANGED
|
@@ -11,7 +11,7 @@ results on it (0.2.3; spec 04 section 5).
|
|
|
11
11
|
|
|
12
12
|
This module loads the file and checks it: every value is a published number
|
|
13
13
|
with its own source line, success values are fractions, the reference
|
|
14
|
-
model has a result, and only the
|
|
14
|
+
model has a result, and only the 13 current models appear. A model with no
|
|
15
15
|
result anywhere is listed by `missing_models`, never filled in.
|
|
16
16
|
"""
|
|
17
17
|
|
|
@@ -28,7 +28,7 @@ BENCHMARKS_TOML = Path(__file__).resolve().parent / "benchmarks.toml"
|
|
|
28
28
|
# The current models in the packaged price table.
|
|
29
29
|
MODELS = (
|
|
30
30
|
"claude-opus-5-5", "claude-opus-5", "claude-fable-5-1", "claude-fable-5", "claude-sonnet-5", "haiku-4.5",
|
|
31
|
-
"gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
31
|
+
"gpt-6-astra", "gpt-6.1-sol", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
32
32
|
)
|
|
33
33
|
KINDS = ("success", "cost", "tokens")
|
|
34
34
|
_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
@@ -67,7 +67,7 @@ def check_benchmarks(data: dict) -> list[str]:
|
|
|
67
67
|
problems.append(f"{where}: unknown benchmark")
|
|
68
68
|
continue
|
|
69
69
|
if r.get("model") not in MODELS:
|
|
70
|
-
problems.append(f"{where}: model is not one of the
|
|
70
|
+
problems.append(f"{where}: model is not one of the 13 current models (D50)")
|
|
71
71
|
if r.get("effort") is not None and r["effort"] not in EFFORTS:
|
|
72
72
|
problems.append(f"{where}: effort {r['effort']!r} is not one of {sorted(EFFORTS)}")
|
|
73
73
|
value = r.get("value")
|
|
@@ -98,7 +98,7 @@ def check_benchmarks(data: dict) -> list[str]:
|
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def coverage(data: dict) -> dict[str, list[str]]:
|
|
101
|
-
"""Model to the benchmark ids that have a result for it, for the
|
|
101
|
+
"""Model to the benchmark ids that have a result for it, for the 13 current models."""
|
|
102
102
|
out: dict[str, list[str]] = {m: [] for m in MODELS}
|
|
103
103
|
for r in data.get("result") or []:
|
|
104
104
|
ids = out.get(r.get("model"))
|
loopmath/recommend/commands.py
CHANGED
|
@@ -40,8 +40,9 @@ FALLBACK_REVIEWER = ("codex", "gpt-6-astra", "xhigh")
|
|
|
40
40
|
# The models offered when neither `--models` nor `models.allowed` is set (0.2.2): the current ones only. Any
|
|
41
41
|
# other model is retired: its runs stay data for the fit and its workflow can be the reference line, but it is
|
|
42
42
|
# never a pick, a choice or a candidate. First the fallback implementer, then the fallback reviewer, as
|
|
43
|
-
# `models.allowed` order is read, so a store with no habits keeps the default usual it had.
|
|
44
|
-
|
|
43
|
+
# `models.allowed` order is read, so a store with no habits keeps the default usual it had. gpt-6.1-sol is the
|
|
44
|
+
# current sol (0.2.8); gpt-6-sol stays in the catalog, and `--models` can still offer it.
|
|
45
|
+
DEFAULT_MODELS = ("claude-opus-5-5", "gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna", "claude-sonnet-5", "claude-fable-5-1")
|
|
45
46
|
REVIEW_ROLES = ("reviewer", "referee", "review", "select")
|
|
46
47
|
# `--json --brief`: what an agent needs to show the choices and start one. The stored
|
|
47
48
|
# recommendation and the full JSON keep everything; a key added to recommend/2 stays out unless listed.
|
|
@@ -601,9 +602,10 @@ def typical(rec: Recommendation | None, p) -> str:
|
|
|
601
602
|
|
|
602
603
|
def line_numbers(p, *, first: bool = False, marks: tuple[str, ...] = (), rec: Recommendation | None = None,
|
|
603
604
|
lead_typical: bool = False) -> str:
|
|
604
|
-
"""Chance, run cost with its median, expected rescue and `ell`, which the first line spells
|
|
605
|
-
mean above its interval's upper end gets the tail note in its parentheses, after any `marks` such as
|
|
606
|
-
"uncertain".
|
|
605
|
+
"""Chance, run cost with its tokens, then its median, expected rescue and `ell`, which the first line spells
|
|
606
|
+
out; a mean above its interval's upper end gets the tail note in its parentheses, after any `marks` such as
|
|
607
|
+
"uncertain". The tokens are the mean run's, so they follow the mean cost; after the median they read as the
|
|
608
|
+
median run's. With `lead_typical` the run cost is the typical run with its range; the caller prints the mean."""
|
|
607
609
|
ell = fmt_usd(p.ell.usd.mean)
|
|
608
610
|
note = TAIL if heavy(p.ell.usd) else ""
|
|
609
611
|
per = (f"expected cost per accepted result {ell}{paren('lower is better', *marks, note)}" if first
|
|
@@ -616,7 +618,7 @@ def line_numbers(p, *, first: bool = False, marks: tuple[str, ...] = (), rec: Re
|
|
|
616
618
|
if lead_typical:
|
|
617
619
|
return f"{chance}, typical run {typical_cost(p.cost)}, {rescue}{per}"
|
|
618
620
|
return (f"{chance}, {fmt_usd(p.cost.usd.mean)} a run "
|
|
619
|
-
f"({(
|
|
621
|
+
f"({fmt_tokens(p.cost.tokens.mean)} tokens{('; ' + med) if med else ''}"
|
|
620
622
|
f"{tail(p.cost.usd, p.cost.tokens)}), {rescue}{per}")
|
|
621
623
|
|
|
622
624
|
|
|
@@ -58,9 +58,9 @@ Then ask exactly one question: "Which one should I start? (1 to N, or describe a
|
|
|
58
58
|
> 1. Recommended: plan_implement: opus-5-5/high, sonnet-5/high: 78% in one run (61 to 90%), 90% within 3 attempts, $1.20 a run (median $0.95), $1.73 per accepted result
|
|
59
59
|
> 2. Pair: 1 plus plan_implement_review: sonnet-5/high x3: extra $0.80 now, judged blind; pays back after 4 similar tasks
|
|
60
60
|
> 3. Your usual (reference): 71% in one run (52 to 86%), 85% within 3 attempts, $1.45 a run (median $1.10), $2.36 per accepted result
|
|
61
|
-
> 4. Most likely to reach the target: best_of_n: gpt-6-sol/xhigh x3: 88% in one run (70 to 97%), 96% within 3 attempts, $4.10 a run (median $3.80), $4.90 per accepted result
|
|
61
|
+
> 4. Most likely to reach the target: best_of_n: gpt-6.1-sol/xhigh x3: 88% in one run (70 to 97%), 96% within 3 attempts, $4.10 a run (median $3.80), $4.90 per accepted result
|
|
62
62
|
> 5. Cheapest run: solo: sonnet-5/medium: 52% in one run (30 to 73%), 70% within 3 attempts, $0.35 a run, $2.40 per accepted result
|
|
63
|
-
> Run cost is what the agents cost for one run; cost per accepted result adds the expected cost of fixing a miss by retrying with best_of_n: gpt-6-sol/xhigh x3, each extra attempt having half the chance of the one before, up to 3 attempts.
|
|
63
|
+
> Run cost is what the agents cost for one run; cost per accepted result adds the expected cost of fixing a miss by retrying with best_of_n: gpt-6.1-sol/xhigh x3, each extra attempt having half the chance of the one before, up to 3 attempts.
|
|
64
64
|
> Planning page: /Users/me/.loopmath/views/recommend-20260924-181502.html
|
|
65
65
|
> Which one should I start? (1 to 5, or describe another workflow)
|
|
66
66
|
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
var arrow = m.better === 'lower' ? ' Lower is better (↓).' : m.better === 'higher' ? ' Higher is better (↑).' : '';
|
|
70
70
|
if (head.indexOf('score:') === 0 && k === 'multiplier') return 'How much a node multiplies the score ' + head.slice(6) + ' on one run: x1.00 is no change.' + arrow;
|
|
71
71
|
if (head.indexOf('score:') === 0 && k === 'pp') return 'How a node shifts the score ' + head.slice(6) + ' on one run, in percentage points.' + arrow;
|
|
72
|
-
if (head === 'tokens') return 'How much a node multiplies tokens per round, relative to its parent: x1.00 is no change, below x1 is fewer.';
|
|
72
|
+
if (head === 'tokens') return 'How much a node multiplies tokens per round, relative to its parent: x1.00 is no change, below x1 is fewer. It is the cost multiplier over the price-per-token multiplier, before the floor: a prediction never prices a token below its model\'s cache-read rate, so where that floor binds, the predicted tokens move less than this.';
|
|
73
73
|
if (k === 'multiplier') return 'How much a node multiplies dollars per round, relative to its parent: x1.00 is no change, below x1 is cheaper.';
|
|
74
74
|
if (head === 'gate') return 'How a node shifts the chance that a gate passes on a round, in percentage points at the group\'s base rate.';
|
|
75
75
|
if (k === 'pp') return 'How a node shifts the chance of an accepted result, in percentage points at the group\'s base rate.';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
loopmath/__init__.py,sha256=
|
|
1
|
+
loopmath/__init__.py,sha256=qoxVgTJW5APTeuqbAUGn9fc40vF7vi_cOBSq0yI2vAY,244
|
|
2
2
|
loopmath/__main__.py,sha256=X6nV3FjHmxDbIjccOYrBts_0KyJwOmDEPI5URzMO_-8,126
|
|
3
3
|
loopmath/adapter_host.py,sha256=KBLHsP-LzrRDzOaDQMJRDq-NfCJeT5nY_CQAPLqyKQ4,10284
|
|
4
4
|
loopmath/cli.py,sha256=6oJ8m3KkAv7-yTh-p7WmqCNjbs1moy1jBExVzk-Et54,23459
|
|
@@ -21,7 +21,7 @@ loopmath/output.py,sha256=8dm7eh37D0cKAUFSIlcLtbJ8Td1gxxj6vjy99pOGHqs,4584
|
|
|
21
21
|
loopmath/pool.py,sha256=s3DfC2G2S6rK47PEyzSpIM1wYNQKnScpCtIallHhego,1953
|
|
22
22
|
loopmath/price.py,sha256=Mr3yINrwwH9dZ2aKrv7BTsyQcukkU9fU3I4M1l1GR2A,24880
|
|
23
23
|
loopmath/price_validation.py,sha256=7VY-vli5JFBh1FV2dRC241Akr6gLbJ7qos6fhuBr8_c,13246
|
|
24
|
-
loopmath/prices.toml,sha256=
|
|
24
|
+
loopmath/prices.toml,sha256=5bYkgpWzK0D6numXZM6y-Ok3EuKevDH-iWBnsElIu1c,11376
|
|
25
25
|
loopmath/receipts.py,sha256=gW_c1ZSyrL0czo1ilFdpeyonkqFuzKYbeaUknbrFDvE,7034
|
|
26
26
|
loopmath/research_defaults.py,sha256=tKbCtQGdj5E37qaIZeQ72MaX0eZOVSX31wNtOTxtr6E,248
|
|
27
27
|
loopmath/research_paths.py,sha256=dTXGaD0X0AqvMAZ90FuZZV5Qdxwjlb25ZplDhRTc8T8,2342
|
|
@@ -62,18 +62,18 @@ loopmath/belief/__init__.py,sha256=Ny_4xJWWvvAyI8gkrQtBBO-9uKXGvNWFQZcyAkQ06BA,1
|
|
|
62
62
|
loopmath/belief/block.py,sha256=GoZ269Vql8L6rB5-klT7cGlx42Rc4m9KsRO9ccU7E8g,7037
|
|
63
63
|
loopmath/belief/check_pymc.py,sha256=9BLVla7HqRMjF_G4biLyxWY223XVAklJdy1PuCbUOIU,6007
|
|
64
64
|
loopmath/belief/commands.py,sha256=AxRn2XLiNk_RTbqQpVl_CovpRyv5c00vH5QCbLfiOGc,10298
|
|
65
|
-
loopmath/belief/compose.py,sha256=
|
|
66
|
-
loopmath/belief/design.py,sha256=
|
|
67
|
-
loopmath/belief/features.py,sha256=
|
|
68
|
-
loopmath/belief/fit.py,sha256=
|
|
65
|
+
loopmath/belief/compose.py,sha256=MjwvQHh2YpnTKvlQNWguiPm-lcnqIiHaClOH1aK77M4,11841
|
|
66
|
+
loopmath/belief/design.py,sha256=e5mcCqeDNA1wtDmXnYrZ9Bfc7vVM5WJc_NKXRYMjjwY,42727
|
|
67
|
+
loopmath/belief/features.py,sha256=4nStB7E1a3cgAXdh7xXCCH4Ddf9T9vNi0AFnx5DP_LQ,11615
|
|
68
|
+
loopmath/belief/fit.py,sha256=W7XxO9LVoxaS2eP0VInqEY1yt2gEEzDnq-9WY5McaXE,38957
|
|
69
69
|
loopmath/belief/forest.py,sha256=efHhbNdSl1_KIpPK317AlrzOvE6Jx80UiOZfjABC0EQ,8658
|
|
70
70
|
loopmath/belief/gaussian.py,sha256=YgN-SUOzSKj-dXndjPaaDMOw2oCx_NVj3hoGtn2B_IM,22090
|
|
71
|
-
loopmath/belief/lookahead.py,sha256=
|
|
71
|
+
loopmath/belief/lookahead.py,sha256=u-Z9xZ-nI0I1qumxfdONhMwct1ub0n4MwrNJG_P10dI,19295
|
|
72
72
|
loopmath/belief/outcome.py,sha256=c2bnM0LEFBL3IjiibDq6DnyIbBuSPzK1MY48T4X8z9Y,6744
|
|
73
|
-
loopmath/belief/pricing.py,sha256=
|
|
73
|
+
loopmath/belief/pricing.py,sha256=vTKf7iheX9zW9TjpbNOk9BhLhxN0BZNLoSZXD0eKo6k,7914
|
|
74
74
|
loopmath/belief/priors.py,sha256=Fi1dImT2rNQJzJrFU4XmAKnhsEK8ay__VFRuMkEvO-E,8053
|
|
75
75
|
loopmath/belief/rules.py,sha256=nEZFBiDIxwDdqcxkeMiiKNrJ4SGRtBirTkCvTViv8As,6352
|
|
76
|
-
loopmath/belief/state.py,sha256=
|
|
76
|
+
loopmath/belief/state.py,sha256=NjycTv40skGlcvBKnypCiQX3eDsBW_8gZWS6QrbFHgU,52020
|
|
77
77
|
loopmath/builder/__init__.py,sha256=E9_JLObtnM_r2to6bqH13xV3LHN00zVIRH1nzJTCv7s,105
|
|
78
78
|
loopmath/builder/cli.py,sha256=NPMkFE0vIOAsEMCtGVkvzdzmszrNd7uZ3CNX_E-zV9M,2429
|
|
79
79
|
loopmath/builder/context.py,sha256=1NCKZSx9Fn64pSixnLJqV80fPDtoPusttnj5ZnjhDrU,18387
|
|
@@ -145,7 +145,7 @@ loopmath/graph/scan.py,sha256=-7dU9xVKWjHLdnK7LFjSIk2X0-jVUowdnFZhCc8sAnI,10413
|
|
|
145
145
|
loopmath/graph/schema.py,sha256=qWb2FZnaGU5q0CcfNpStrKwAO0Hb_p3HnMCj8fLlO7Y,6601
|
|
146
146
|
loopmath/graph/token_completeness.py,sha256=PWFJHbo5EZhC3vCwkLLRF-yi-hpGjP3Zw3ofoC-rvcw,916
|
|
147
147
|
loopmath/ingest/__init__.py,sha256=4wLZg89gTwI9h8yXPfcrtgO_Z_AqDFt74_7Zxbrhjo0,19889
|
|
148
|
-
loopmath/ingest/base.py,sha256=
|
|
148
|
+
loopmath/ingest/base.py,sha256=TEj7OLysgbGvMHLxTLxDlF6i_C34_3PsTE8pK4JEl_o,20063
|
|
149
149
|
loopmath/ingest/claude_code.py,sha256=IMdjzzuzTCNO6iL6fLl2zwSvzWQGwzgyqMjaisM7Mjo,24846
|
|
150
150
|
loopmath/ingest/claude_code_support.py,sha256=BU3aSxTOvhpUlyPOSmBOcwnTfMP8cnnGlAwmuNFX8OU,2423
|
|
151
151
|
loopmath/ingest/codex.py,sha256=oKJKol69ps83Y_0ZqjkbMP6qCQPD6zwbXW_D0nn6c-g,27262
|
|
@@ -183,7 +183,7 @@ loopmath/onboard/history.py,sha256=qQqidw7pg-XOSQir8bcImTfP3fdFcGt8VsnrpuRMX4A,2
|
|
|
183
183
|
loopmath/onboard/label.py,sha256=nKlveJNggqxKcK3I60QQF8L0y0iEcm2d-LIP0Ca3n50,19571
|
|
184
184
|
loopmath/onboard/usual.py,sha256=rEyg6WOCzEN8Y-UsHLh2br218qdca3IzRp1FVaA_qgw,3444
|
|
185
185
|
loopmath/priors/__init__.py,sha256=vruMFu0UAW_IDoRquKw9skmaqfrOXXYGhtfW_wTxc90,5749
|
|
186
|
-
loopmath/priors/benchmarks.py,sha256=
|
|
186
|
+
loopmath/priors/benchmarks.py,sha256=hqxOvDFS72A2tfTUpexFPogj7Y30b4CJvXNCQ3F9-rY,5760
|
|
187
187
|
loopmath/priors/benchmarks.toml,sha256=geoPVCpSnhrgey2ynO7Kkn6picV0suBjhcOWUoCp_QE,54412
|
|
188
188
|
loopmath/priors/build.py,sha256=bkT1JPcr-198jC9FZZiUWYY7M9uc-YG7S3VtJvA3PpU,16664
|
|
189
189
|
loopmath/priors/commands.py,sha256=01HoZNVoH_6BVkhfusa2JEz4JuwPPOLqL_g2VGu0M20,5691
|
|
@@ -205,7 +205,7 @@ loopmath/priors/bundle/rq1.jsonl.gz,sha256=Ejk6cKab9aFYpjT9kdbek46T3m_lzjCUbtJGU
|
|
|
205
205
|
loopmath/priors/bundle/sweep.jsonl.gz,sha256=0f6kxs-DdWt51FUec4zeYk5EAhD7OQjZ5gpQPIOG4Xg,135748
|
|
206
206
|
loopmath/recommend/__init__.py,sha256=KsxeqH9dZFG2y9vV8JfKfcXtb-neWDshjQwdkRgIeB0,138
|
|
207
207
|
loopmath/recommend/backlog.py,sha256=Y_GsWF5S4bR6V66HckotQtNr3IV6bpwbhzbQ0XjV1rQ,31613
|
|
208
|
-
loopmath/recommend/commands.py,sha256=
|
|
208
|
+
loopmath/recommend/commands.py,sha256=CGbK7OgI6fggJEm0LPAxrMNHFYdCMZHo4GRbeWAe2_o,38340
|
|
209
209
|
loopmath/recommend/curve.py,sha256=cxe2pR9-Ye5XciEUlKFlJpYKeo-FNokYaqJBuChTpsU,9831
|
|
210
210
|
loopmath/recommend/engine.py,sha256=YjW-D8jVYOvKdL6DtvfOgmdEb6-Xkqacmw36Q1kJNqs,44058
|
|
211
211
|
loopmath/recommend/gain.py,sha256=PPd6D3XKyPPwcE13X0Q3T82LoNEQF45KVBuabhAvKPw,7446
|
|
@@ -233,7 +233,7 @@ loopmath/skill/skills/reference.md,sha256=feqZIhyKCRDb7Cl46g73IqD_0D99aXy6frfg4V
|
|
|
233
233
|
loopmath/skill/skills/loopmath/SKILL.md,sha256=MUb_5Nu_0HwuD_pKHiQgoopxG6YLV2gj99oXT6umuAA,2473
|
|
234
234
|
loopmath/skill/skills/loopmath-import-runs/SKILL.md,sha256=A-mSL5-a31-umQCtpHgnclHD3V1_eSKGMV1SmrxTHdE,3131
|
|
235
235
|
loopmath/skill/skills/loopmath-onboard/SKILL.md,sha256=hQDyFW5FBgw03p6pHl3CxJIiHrQ3edbZmSwcmZMSJ0s,4814
|
|
236
|
-
loopmath/skill/skills/loopmath-plan-task/SKILL.md,sha256=
|
|
236
|
+
loopmath/skill/skills/loopmath-plan-task/SKILL.md,sha256=TE6UcVtRUGy3gkCKZLl05i472IGKeT14-gVb6gA_dzc,10691
|
|
237
237
|
loopmath/skill/skills/loopmath-record-run/SKILL.md,sha256=rp6rI0fkeE5Fcng1dGKdae6q0K6R_kK8aLbHpDMWazk,6723
|
|
238
238
|
loopmath/skill/skills/loopmath-update-fit/SKILL.md,sha256=JEO8sEm2GsBZ7IoJNOkSdAshTtBofFa0Rfy34P-0aN4,2648
|
|
239
239
|
loopmath/store/__init__.py,sha256=vCGuqOSRKmKoT4eLbjEl8kxJRwdHWqxouGTp6gqPLFE,708
|
|
@@ -264,7 +264,7 @@ loopmath/views/assets/builder.html,sha256=I9tWu6V_iN2iT9rrDcZvy_LdTHHifPIdvmfD-9
|
|
|
264
264
|
loopmath/views/assets/builder.js,sha256=YFY9t9ZxodZxV4srXIv4xUWFNE7rYdBGoWomRbFZUdc,100685
|
|
265
265
|
loopmath/views/assets/common.js,sha256=DUQsTagXBdsMd17iAhyDAMAUw1Qu8xzNmqqLhJPxczA,15314
|
|
266
266
|
loopmath/views/assets/estimates.css,sha256=6svx7lENnkXf-38emFJUxONGmXXbU77lnWupJFWLuF0,4321
|
|
267
|
-
loopmath/views/assets/estimates.js,sha256=
|
|
267
|
+
loopmath/views/assets/estimates.js,sha256=ZgR_gqQOg9GPsGBtS42XXqnirUF14YDfYNMfPYBjQQU,52109
|
|
268
268
|
loopmath/views/assets/graph.css,sha256=6-jiJ1T5aiiMVeEse7Oz-w72r-zqb2tX6UeApmdmp4c,1799
|
|
269
269
|
loopmath/views/assets/graph.js,sha256=mv-yCXwoMYcRhDFzlKFZPaiTKjbv6uTNvt2UyU0bzYk,36947
|
|
270
270
|
loopmath/views/assets/plans.css,sha256=mRjg4FEiB2a_cGcMZwyFEqvwNHNR24UJ26nkPbVp0nk,2986
|
|
@@ -291,12 +291,12 @@ loopmath/workflows/catalog/plan_implement.toml,sha256=qxAs6G3JGOUuW6TnhiaUXzaFfy
|
|
|
291
291
|
loopmath/workflows/catalog/plan_implement_review.toml,sha256=rRtiactglcJAynFWbrjnI-Ftbl3qe971ov9HJ3Vr7uU,862
|
|
292
292
|
loopmath/workflows/catalog/solo.toml,sha256=aJWBEEfS4DNCLqznDI3yp-7s1ZXx_rdqsEIPX7n0-nM,373
|
|
293
293
|
loopmath/workflows/catalog/swarm.toml,sha256=1hbQR41nl7VS_XN1z2fmwLCNw6kn2syowpwwauFUL3o,850
|
|
294
|
-
loopmath-0.2.
|
|
295
|
-
loopmath-0.2.
|
|
296
|
-
loopmath-0.2.
|
|
297
|
-
loopmath-0.2.
|
|
298
|
-
loopmath-0.2.
|
|
299
|
-
loopmath-0.2.
|
|
300
|
-
loopmath-0.2.
|
|
301
|
-
loopmath-0.2.
|
|
302
|
-
loopmath-0.2.
|
|
294
|
+
loopmath-0.2.8.data/data/spec/ocp-v0.2.schema.json,sha256=T4P5gg6H2dW-28MRbg9fSNct_ejXezI69-SaKR9WogU,31855
|
|
295
|
+
loopmath-0.2.8.data/data/spec/ocp-v0.schema.json,sha256=tOGqmvAjh0Ap09zV3QLQp3PovQB8inpVNgxSnrPS354,15279
|
|
296
|
+
loopmath-0.2.8.data/data/spec/ocp_conformance.py,sha256=VzRGKvC6f-64U72xrDOSJEeYa92Bpj3tEf5x0uJXqhk,893
|
|
297
|
+
loopmath-0.2.8.dist-info/licenses/LICENSE,sha256=QMvrQqRQRR-OjtyK0CiOxBV8NgWwr_QacIeFUWXeI5o,1065
|
|
298
|
+
loopmath-0.2.8.dist-info/METADATA,sha256=UfU5gNcFnHHgA6EeaV1A-xRdQ_b6j3HcI-i7F4_C8G8,21861
|
|
299
|
+
loopmath-0.2.8.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
300
|
+
loopmath-0.2.8.dist-info/entry_points.txt,sha256=WQwYqfGAwvoQ_vu6_lwQpq3V8xbK3i9SyNKPDdQ1VmQ,72
|
|
301
|
+
loopmath-0.2.8.dist-info/top_level.txt,sha256=SH9Y0Z7thA0zx1PAgHAwc1vOJaS6o3yS5HazS-LzXOg,9
|
|
302
|
+
loopmath-0.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|