Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a1df0cc
fix conv kernel support truncation by rebuilding from current parame…
InfinityMonkeyAtWork Jul 9, 2026
3da2be7
make conv IRF test fixture identifiable on its time grid
InfinityMonkeyAtWork Jul 9, 2026
aeb1c62
fix plot_comparison on a fresh Simulator
InfinityMonkeyAtWork Jul 10, 2026
a9324fa
fix sign_change infinite loop on all-zero input
InfinityMonkeyAtWork Jul 10, 2026
83064a9
raise a clear error for my_conv on a degenerate x axis
InfinityMonkeyAtWork Jul 10, 2026
7ced405
guard plot_1d y_norm against constant traces
InfinityMonkeyAtWork Jul 10, 2026
894772a
update claude commit settings
InfinityMonkeyAtWork Jul 10, 2026
5ba8a1a
fix partial-range create_value_2d using slice-relative time indices
InfinityMonkeyAtWork Jul 10, 2026
2f50fe6
raise instead of fabricating axes in describe/define_baseline/set_fit…
InfinityMonkeyAtWork Jul 10, 2026
158bf2c
raise clear errors for convolution on a single-point time axis
InfinityMonkeyAtWork Jul 10, 2026
10f0653
raise instead of returning -1.0 for t_vary Par without t_model
InfinityMonkeyAtWork Jul 10, 2026
fa81a4d
validate fit-window data is finite at the fit_wrapper entry
InfinityMonkeyAtWork Jul 10, 2026
f0e5e66
honor silent mode in config errors, MCMC, and fit/setup display paths
InfinityMonkeyAtWork Jul 10, 2026
b139601
speed up 2D evaluator and my_conv hot paths
InfinityMonkeyAtWork Jul 10, 2026
7030f3c
fix benchmark harness to exercise par profiles in example 04
InfinityMonkeyAtWork Jul 10, 2026
7b43441
return profiled-op evaluation to a per-aux loop with hoisted sources
InfinityMonkeyAtWork Jul 10, 2026
992bd29
close GIR/MCP parity coverage gaps (review check 18)
InfinityMonkeyAtWork Jul 10, 2026
2cef348
add Returns sections to profile function docstrings
InfinityMonkeyAtWork Jul 10, 2026
6a0388d
skip MCMC walker/corner figure construction when neither shown nor saved
InfinityMonkeyAtWork Jul 10, 2026
64e61c8
dedupe scalar RPN evaluator: eval_1d reuses graph_ir._eval_expr_scalar
InfinityMonkeyAtWork Jul 10, 2026
9b797f4
extract shared trace-resolution loop into eval_2d.resolve_param_traces
InfinityMonkeyAtWork Jul 10, 2026
1c049f2
plan kernel-matrix convolution for non-uniform time axes
InfinityMonkeyAtWork Jul 10, 2026
55cd1e8
dedupe schedule_2d/schedule_1d profile compilation and op scheduling
InfinityMonkeyAtWork Jul 10, 2026
c50488c
dedupe simulator noise generation and HDF5 metadata serialization
InfinityMonkeyAtWork Jul 10, 2026
8d5c6c4
migrate fit_io reads to shared hdf5 helpers; scope contract to reads
InfinityMonkeyAtWork Jul 10, 2026
37cdf33
move refline styling and panel size into PlotConfig; delegate plot_co…
InfinityMonkeyAtWork Jul 10, 2026
931a642
add z_colormap_res: diverging, zero-centered colormap for 2D residual…
InfinityMonkeyAtWork Jul 10, 2026
b0c91e0
make every PlotConfig field settable via project.yaml; add coverage g…
InfinityMonkeyAtWork Jul 10, 2026
9014377
open sweep HDF5 file once instead of per config
InfinityMonkeyAtWork Jul 10, 2026
3e39f40
nudge toward parameter bounds in LinBack ordering error
InfinityMonkeyAtWork Jul 10, 2026
d081d79
label type-guard asserts in tests; document figure-inspection plot ex…
InfinityMonkeyAtWork Jul 10, 2026
3fcc818
reject cross-model dynamics expressions with a clear error; document …
InfinityMonkeyAtWork Jul 10, 2026
ad68c38
validate sweep specs, conv kernels, noise types, and DataFrame column…
InfinityMonkeyAtWork Jul 11, 2026
d7f0e27
validate noise_type in Simulator constructor via shared helper
InfinityMonkeyAtWork Jul 11, 2026
3dee6be
archive the July 2026 code review; document full-scope review protocol
InfinityMonkeyAtWork Jul 11, 2026
a0a6db6
add 0.10.2 changelog entry
InfinityMonkeyAtWork Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 85 additions & 26 deletions .claude/skills/benchmark/benchmark_gir.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,39 +74,79 @@ def _extract_float(src, key):


#
def _parse_dynamics_from_notebook(notebook_path):
"""Extract add_time_dependence kwargs from example.ipynb.
def _iter_call_args(src, func_name):
"""Yield the argument text of each ``func_name(...)`` call in *src*.

Matches parentheses so multiple calls per cell stay separate (a
cell-level regex would mix kwargs of neighboring calls).
"""

start = 0
while True:
idx = src.find(func_name + "(", start)
if idx == -1:
return
depth = 0
for j in range(idx + len(func_name), len(src)):
if src[j] == "(":
depth += 1
elif src[j] == ")":
depth -= 1
if depth == 0:
yield src[idx + len(func_name) + 1 : j]
start = j + 1
break
else:
return


#
def _parse_calls_from_notebook(notebook_path):
"""Extract add_par_profile / add_time_dependence kwargs from example.ipynb.

Returns
-------
list of dict
Each dict has keys: target_model, target_parameter,
profile_calls : list of dict
add_par_profile kwargs: target_model, target_parameter,
profile_yaml, profile_model.
dynamics_calls : list of dict
add_time_dependence kwargs: target_model, target_parameter,
dynamics_yaml, dynamics_model, and optionally frequency.
"""

with notebook_path.open() as f:
nb = json.load(f)

calls = []
profile_calls = []
dynamics_calls = []
for cell in nb["cells"]:
if cell["cell_type"] != "code":
continue
src = "".join(cell["source"])
if "add_time_dependence" not in src:
continue

call = {
"target_model": _extract_str(src, "target_model"),
"target_parameter": _extract_str(src, "target_parameter"),
"dynamics_yaml": _extract_str(src, "dynamics_yaml"),
"dynamics_model": _extract_str_or_list(src, "dynamics_model"),
}
freq = _extract_float(src, "frequency")
if freq is not None:
call["frequency"] = freq
calls.append(call)
for args in _iter_call_args(src, "add_par_profile"):
profile_calls.append(
{
"target_model": _extract_str(args, "target_model"),
"target_parameter": _extract_str(args, "target_parameter"),
"profile_yaml": _extract_str(args, "profile_yaml"),
"profile_model": _extract_str_or_list(args, "profile_model"),
}
)

for args in _iter_call_args(src, "add_time_dependence"):
call = {
"target_model": _extract_str(args, "target_model"),
"target_parameter": _extract_str(args, "target_parameter"),
"dynamics_yaml": _extract_str(args, "dynamics_yaml"),
"dynamics_model": _extract_str_or_list(args, "dynamics_model"),
}
freq = _extract_float(args, "frequency")
if freq is not None:
call["frequency"] = freq
dynamics_calls.append(call)

return calls
return profile_calls, dynamics_calls


# ------------------------------------------------------------------
Expand Down Expand Up @@ -140,7 +180,10 @@ def load_example(example_num, *, add_dynamics=True):
-------
file : File
dynamics_calls : list of dict
Parsed add_time_dependence kwargs from the notebook.
Parsed add_time_dependence kwargs from the notebook ("2D" model
only).
profile_calls : list of dict
Parsed add_par_profile kwargs already attached to the model.
"""

folder = _find_example_folder(example_num)
Expand All @@ -152,23 +195,35 @@ def load_example(example_num, *, add_dynamics=True):
energy = np.loadtxt(data_dir / "energy.csv")
time_ax = np.loadtxt(data_dir / "time.csv")
data = np.loadtxt(data_dir / "data.csv", delimiter=",")
aux_path = data_dir / "aux_axis.csv"
aux_axis = np.loadtxt(aux_path) if aux_path.exists() else None

file = File(
parent_project=project,
name="bench",
data=data,
energy=energy,
time=time_ax,
aux_axis=aux_axis,
)
file.load_model(model_yaml="models_energy.yaml", model_info="2D")

dynamics_calls = _parse_dynamics_from_notebook(folder / "example.ipynb")
profile_calls, dynamics_calls = _parse_calls_from_notebook(folder / "example.ipynb")
# The notebooks also attach to their baseline/SbS models; only calls
# targeting the benchmarked "2D" model apply here.
profile_calls = [c for c in profile_calls if c["target_model"] == "2D"]
dynamics_calls = [c for c in dynamics_calls if c["target_model"] == "2D"]

# Profiles are model structure, not time dependence: attach always,
# and before dynamics (dynamics may target a profile parameter).
for call in profile_calls:
file.add_par_profile(**call)

if add_dynamics:
for call in dynamics_calls:
file.add_time_dependence(**call)

return file, dynamics_calls
return file, dynamics_calls, profile_calls


#
Expand Down Expand Up @@ -333,7 +388,7 @@ def _snapshot(label):
print(f" {label:32s}{call_count[0]:6d} (+{call_count[0] - prev})")

try:
file, dynamics_calls = load_example(example_num, add_dynamics=False)
file, dynamics_calls, _ = load_example(example_num, add_dynamics=False)
file.define_baseline(
time_start=0, time_stop=10, time_type="ind", show_plot=False
)
Expand Down Expand Up @@ -397,7 +452,7 @@ def timed_sched(*args, **kwargs):
graph_ir.schedule_2d = timed_sched

try:
file, dynamics_calls = load_example(example_num, add_dynamics=False)
file, dynamics_calls, _ = load_example(example_num, add_dynamics=False)
file.define_baseline(
time_start=0, time_stop=10, time_type="ind", show_plot=False
)
Expand Down Expand Up @@ -454,7 +509,7 @@ def capture_par_variability(example_num, *, n_starts=4):
redchis = []

for run in range(n_starts + 1):
file, dynamics_calls = load_example(example_num, add_dynamics=False)
file, dynamics_calls, _ = load_example(example_num, add_dynamics=False)
file.define_baseline(
time_start=0, time_stop=10, time_type="ind", show_plot=False
)
Expand Down Expand Up @@ -563,7 +618,7 @@ def bench_fit(example_num, dynamics_calls, *, n_reps=3):
("fit_model_gir", gir_times),
("fit_model_mcp", mcp_times),
]:
file, _ = load_example(example_num, add_dynamics=False)
file, _, _ = load_example(example_num, add_dynamics=False)
file.define_baseline(
time_start=0, time_stop=10, time_type="ind", show_plot=False
)
Expand Down Expand Up @@ -682,11 +737,15 @@ def bench_fit(example_num, dynamics_calls, *, n_reps=3):
folder = _find_example_folder(args.example)
print(f"Example: {folder.name}")

file, dynamics_calls = load_example(args.example)
file, dynamics_calls, profile_attaches = load_example(args.example)
model = file.model_active
assert model is not None
graph = build_graph(model)
print(f" lowerable: {can_lower_2d(graph)}")
for call in profile_attaches:
target_parameter = call["target_parameter"]
profile_model = call["profile_model"]
print(f" profile: {target_parameter} <- {profile_model}")
for call in dynamics_calls:
target_parameter = call["target_parameter"]
dynamics_model = call["dynamics_model"]
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
This file is maintained using the shared changelog workflow in
[`docs/ai/changelog.md`](docs/ai/changelog.md).

## [0.10.2] - 2026-07-10

### Added

- **Diverging colormap for residual maps**: new `z_colormap_res` plot setting (default `'RdBu_r'`). 2D data+fit+residual panels now render the residual with its own zero-centered diverging colormap and symmetric color limits, instead of reusing the data colormap.
- **New plot styling settings**: `refline_color` / `refline_style` for `vlines`/`hlines` reference lines (unified default: grey dotted — previously inconsistent between 1D and 2D plots) and `panel_size` for multi-panel grid plots; `plot_2d_grid` gained a `columns` argument.
- **Every `PlotConfig` field is now settable via `project.yaml`**, with tuple-valued fields (`x_lim`, `panel_size`, ...) accepting YAML lists; a coverage test guards future fields.
- `results_to_fit_2d` accepts `parameter_names` to select and order DataFrame columns, so extra non-parameter columns (e.g. from `results_to_df` output) cannot silently corrupt the reconstructed 2D fit.

### Changed

- **Silent mode is honored throughout**: with `show_output=0`, MCMC no longer prints its banner/progress bar or leaves walker/corner figures open, and `fit_2d`/`fit_slice_by_slice` no longer display timing and parameter tables. A broken `project.yaml` now raises `ValueError` instead of silently falling back to defaults.
- **Clear errors instead of silent misbehavior**: fits reject NaN/Inf inside the fit window with a message naming the count and pointing to `set_fit_limits()`; `describe`/`define_baseline`/`set_fit_limits` raise when axes are missing instead of fabricating index axes; convolution on a single-point time axis, zero-sum convolution kernels, unknown `Simulator` noise types (constructor and setter), unknown sweep distribution types, empty parameter sweeps, and dynamics-model expressions referencing parameters outside their own dynamics model all raise immediately with actionable messages; a `t_vary` parameter without a dynamics model raises instead of returning `-1.0`; `LinBack` ordering errors suggest setting bounds on `xStart`/`xStop`.
- **Performance**: the compiled 2D evaluator avoids per-instruction array allocation and copies (preallocated profile buffers, hoisted profiled-op parameter sources), `my_conv` no longer builds a discarded padded axis, MCMC walker/corner figures are only constructed when shown or saved, and parameter sweeps write to one open HDF5 file instead of reopening it per configuration.

### Fixed

- **Convolution kernel support no longer truncates during fits**: the IRF kernel time axis was built once from the initial width parameter and never rebuilt, silently truncating the kernel and biasing the fitted width once it grew past its initial value. Both evaluation paths now rebuild the kernel support from current parameter values on every evaluation.
- **Partial-range 2D evaluation used wrong time indices**: `Model.create_value_2d(t_ind=[start, stop])` computed dynamics for `t[0:stop-start]` instead of `t[start:stop]`.
- `Simulator.plot_comparison` on a fresh simulator now auto-simulates with current settings instead of failing.
- `sign_change` no longer hangs on all-zero input.
- `plot_1d` with `y_norm=1` no longer produces all-NaN plots for constant traces.

## [0.10.0] - 2026-07-08

### Added
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# General behavior

- **Confidence Rule:** Do not make changes until you have 95% confidence. Understand the relevant files before editing and ask follow-up questions until you reach this threshold or when tradeoffs are non-obvious.
- **Commits:** Never commit or rewrite history unless explicitly asked. Always show the exact commit message and wait for approval before committing. Never add AI attribution trailers (`Co-Authored-By` etc.) to commit messages.
- **Context Discipline:** Monitor context usage. At 60% usage (or if it starts getting tight), summarize progress and prompt me to `/compact` or `/clear`.
- **Token Efficiency:** Be concise. Reference file paths and line numbers rather than quoting large code blocks.
- **Subagent Protocol:** Use subagents for repo-wide scans, parallel research, or scanning large directories. Instruct them to return only concise summaries to keep the main context window lean.
Expand Down Expand Up @@ -41,7 +42,7 @@
- **Pattern:** Use plain pytest. Avoid `unittest.TestCase` and fixtures; prefer explicit helper builders named by intent.
- **API Usage:** Use the public API (`Project`, `File.load_model`, etc.) in tests to avoid masking bugs by skipping validation or setup. Use internals only for pure-math unit tests or explicit invariant checks.
- **Execution:** Run `pytest -q`. Keep YAML test assets in `tests/models/`.
- **Plots:** Always suppress plot display in tests: pass `show_plot=False` where available, or `save_img=-2`.
- **Plots:** Always suppress plot display in tests: pass `show_plot=False` where available, or `save_img=-2`. Exception: figure-inspection tests that assert on the live axes cannot pass `save_img=-2` (it closes the figure); they rely on the module-level Agg backend and must call `plt.close("all")` after the assertions.
- **Type Guards:** When `assert x is not None` narrows an `X | None` type, add a `# type guard` comment.
- **Variable Naming:** For variables derived from registry parameters or components, keep original casing (e.g., `SD = 2.0`, `c_Shirley = Component("Shirley")`). Name derived variables as `{par}_{qualifier}` (e.g., `A_early`, `mean_A`).

Expand Down
Loading