HBV-Lab 1.3.0__tar.gz → 1.4.0__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.
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/__init__.py +1 -1
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/mcp_server.py +200 -5
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab.egg-info/PKG-INFO +22 -11
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/PKG-INFO +22 -11
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/README.md +21 -10
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/setup.py +1 -1
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/HBV_model.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/calibration.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/hbv_step.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/response.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/routing.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/snow.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/soil.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab/uncertainty.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab.egg-info/SOURCES.txt +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab.egg-info/dependency_links.txt +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab.egg-info/entry_points.txt +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab.egg-info/requires.txt +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/HBV_Lab.egg-info/top_level.txt +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/LICENSE +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/setup.cfg +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/tests/test_hbv_model.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/tests/test_hbv_step.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/tests/test_response.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/tests/test_snow.py +0 -0
- {hbv_lab-1.3.0 → hbv_lab-1.4.0}/tests/test_soil.py +0 -0
|
@@ -82,6 +82,26 @@ def _downsample(seq, n: int = 20) -> list:
|
|
|
82
82
|
return [seq[i] for i in idx]
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def _at_bound(model: HBVModel, rel_tol: float = 0.005) -> list:
|
|
86
|
+
"""List parameters whose value sits within ``rel_tol`` of their min or max bound.
|
|
87
|
+
|
|
88
|
+
A parameter pinned to a bound usually means the search range is clipping the true
|
|
89
|
+
optimum — surfacing it lets the agent widen the range with set_parameter_ranges.
|
|
90
|
+
"""
|
|
91
|
+
flags = []
|
|
92
|
+
for group, params in model.params.items():
|
|
93
|
+
for name, info in params.items():
|
|
94
|
+
lo, hi, val = info.get("min"), info.get("max"), info.get("default")
|
|
95
|
+
if lo is None or hi is None or hi <= lo:
|
|
96
|
+
continue
|
|
97
|
+
margin = rel_tol * (hi - lo)
|
|
98
|
+
if val <= lo + margin:
|
|
99
|
+
flags.append({"parameter": name, "bound": "lower", "value": round(float(val), 4), "limit": float(lo)})
|
|
100
|
+
elif val >= hi - margin:
|
|
101
|
+
flags.append({"parameter": name, "bound": "upper", "value": round(float(val), 4), "limit": float(hi)})
|
|
102
|
+
return flags
|
|
103
|
+
|
|
104
|
+
|
|
85
105
|
# --- tools --------------------------------------------------------------------
|
|
86
106
|
@mcp.tool()
|
|
87
107
|
def create_model(name: str = "") -> dict:
|
|
@@ -155,14 +175,61 @@ def load_data(
|
|
|
155
175
|
|
|
156
176
|
@mcp.tool()
|
|
157
177
|
def get_parameters(model_id: str) -> dict:
|
|
158
|
-
"""Return the model's current parameter values (the 'default' of each), grouped
|
|
178
|
+
"""Return the model's current parameter values (the 'default' of each), grouped,
|
|
179
|
+
plus an ``at_bound`` list flagging any parameter pinned to its min/max range."""
|
|
159
180
|
model = _get(model_id)
|
|
160
181
|
return {
|
|
161
|
-
|
|
182
|
+
"parameters": {
|
|
183
|
+
group: {name: float(info["default"]) for name, info in params.items()}
|
|
184
|
+
for group, params in model.params.items()
|
|
185
|
+
},
|
|
186
|
+
"at_bound": _at_bound(model),
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.tool()
|
|
191
|
+
def get_parameter_ranges(model_id: str) -> dict:
|
|
192
|
+
"""Return the min / max / default of every parameter (the ranges the optimizer and
|
|
193
|
+
Monte-Carlo uncertainty sampling use), grouped."""
|
|
194
|
+
model = _get(model_id)
|
|
195
|
+
return {
|
|
196
|
+
group: {
|
|
197
|
+
name: {
|
|
198
|
+
"min": float(info["min"]),
|
|
199
|
+
"max": float(info["max"]),
|
|
200
|
+
"default": float(info["default"]),
|
|
201
|
+
}
|
|
202
|
+
for name, info in params.items()
|
|
203
|
+
}
|
|
162
204
|
for group, params in model.params.items()
|
|
163
205
|
}
|
|
164
206
|
|
|
165
207
|
|
|
208
|
+
@mcp.tool()
|
|
209
|
+
def set_parameter_ranges(model_id: str, ranges: dict) -> dict:
|
|
210
|
+
"""Widen or narrow parameter search ranges from a flat mapping, e.g.
|
|
211
|
+
{"FC": {"min": 50, "max": 500}, "CFMAX": {"max": 10}}.
|
|
212
|
+
|
|
213
|
+
Each entry may set any of "min" / "max" / "default"; the group is resolved
|
|
214
|
+
automatically. Use this when calibrate reports a parameter ``at_bound`` — widen its
|
|
215
|
+
range, then calibrate again. Unknown names are reported and ignored.
|
|
216
|
+
"""
|
|
217
|
+
model = _get(model_id)
|
|
218
|
+
update: dict = {}
|
|
219
|
+
unknown: list[str] = []
|
|
220
|
+
for name, spec in ranges.items():
|
|
221
|
+
group = _find_group(model, name)
|
|
222
|
+
if group is None:
|
|
223
|
+
unknown.append(name)
|
|
224
|
+
continue
|
|
225
|
+
allowed = {k: spec[k] for k in ("min", "max", "default") if k in spec}
|
|
226
|
+
if allowed:
|
|
227
|
+
update.setdefault(group, {})[name] = allowed
|
|
228
|
+
if update:
|
|
229
|
+
model.set_parameters(update)
|
|
230
|
+
return {"updated": update, "unknown_parameters": unknown}
|
|
231
|
+
|
|
232
|
+
|
|
166
233
|
@mcp.tool()
|
|
167
234
|
def set_parameters(model_id: str, values: dict) -> dict:
|
|
168
235
|
"""Set parameter values from a flat mapping, e.g. {"FC": 250, "MAXBAS": 4}.
|
|
@@ -292,18 +359,52 @@ async def calibrate(
|
|
|
292
359
|
|
|
293
360
|
result = out["optimization_result"]
|
|
294
361
|
traj = out.get("trajectory", [])
|
|
362
|
+
success = bool(getattr(result, "success", True))
|
|
363
|
+
message = str(getattr(result, "message", ""))
|
|
364
|
+
|
|
365
|
+
# Honest convergence reporting: a maxiter-capped run isn't a "failure" — it just
|
|
366
|
+
# ran out of budget and (usually) is still improving. Distinguish the cases.
|
|
367
|
+
still_improving = bool(
|
|
368
|
+
len(traj) >= 2 and abs(float(traj[-1]) - float(traj[-min(6, len(traj))])) > 1e-3
|
|
369
|
+
)
|
|
370
|
+
if success:
|
|
371
|
+
status, guidance = "converged", "Optimizer converged. No further iterations needed."
|
|
372
|
+
elif "iteration" in message.lower() or "maxiter" in message.lower():
|
|
373
|
+
status = "hit_iteration_budget"
|
|
374
|
+
guidance = (
|
|
375
|
+
"Hit the iteration budget; still improving - call calibrate again to "
|
|
376
|
+
"continue (it resumes from the current parameters)."
|
|
377
|
+
if still_improving
|
|
378
|
+
else "Hit the iteration budget but the objective has plateaued; likely converged."
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
status, guidance = "failed", f"Optimizer stopped without converging: {message}"
|
|
382
|
+
|
|
383
|
+
at_bound = _at_bound(model)
|
|
384
|
+
if at_bound:
|
|
385
|
+
guidance += (
|
|
386
|
+
" Some parameters are at their range limits "
|
|
387
|
+
f"({', '.join(b['parameter'] for b in at_bound)}); consider widening them "
|
|
388
|
+
"with set_parameter_ranges before re-calibrating."
|
|
389
|
+
)
|
|
390
|
+
|
|
295
391
|
return {
|
|
296
392
|
"model_id": model_id,
|
|
297
393
|
"objective": objective,
|
|
298
394
|
"method": method,
|
|
299
395
|
"n_iterations": int(getattr(result, "nit", len(traj))),
|
|
300
|
-
"
|
|
301
|
-
"
|
|
396
|
+
"converged": success,
|
|
397
|
+
"status": status,
|
|
398
|
+
"still_improving": still_improving,
|
|
399
|
+
"guidance": guidance,
|
|
400
|
+
"success": success, # kept for backward compatibility
|
|
401
|
+
"message": message,
|
|
302
402
|
"optimized_parameters": {
|
|
303
|
-
group: {name: round(info["default"], 6) for name, info in params.items()}
|
|
403
|
+
group: {name: round(float(info["default"]), 6) for name, info in params.items()}
|
|
304
404
|
for group, params in model.params.items()
|
|
305
405
|
},
|
|
306
406
|
"performance_metrics": _metrics(model),
|
|
407
|
+
"at_bound": at_bound,
|
|
307
408
|
"objective_trajectory": _downsample(traj, 20),
|
|
308
409
|
}
|
|
309
410
|
|
|
@@ -323,6 +424,8 @@ def evaluate_uncertainty(
|
|
|
323
424
|
model and can be persisted with save_results.
|
|
324
425
|
"""
|
|
325
426
|
model = _get(model_id)
|
|
427
|
+
import numpy as np
|
|
428
|
+
|
|
326
429
|
out = model.evaluate_uncertainty(
|
|
327
430
|
n_runs=n_runs,
|
|
328
431
|
objective=objective,
|
|
@@ -331,12 +434,41 @@ def evaluate_uncertainty(
|
|
|
331
434
|
plot_results=False,
|
|
332
435
|
verbose=False,
|
|
333
436
|
)
|
|
437
|
+
|
|
438
|
+
# 95% prediction-interval diagnostics from the best runs
|
|
439
|
+
df = out["best_runs"]
|
|
440
|
+
obs = np.asarray(df["observed"].values, dtype=float)
|
|
441
|
+
lo = np.asarray(df["q5"].values, dtype=float)
|
|
442
|
+
hi = np.asarray(df["q95"].values, dtype=float)
|
|
443
|
+
valid = ~np.isnan(obs)
|
|
444
|
+
if valid.any():
|
|
445
|
+
inside = (obs[valid] >= lo[valid]) & (obs[valid] <= hi[valid])
|
|
446
|
+
coverage_95 = round(float(inside.mean()), 3)
|
|
447
|
+
mean_band_width = round(float(np.nanmean(hi[valid] - lo[valid])), 4)
|
|
448
|
+
else:
|
|
449
|
+
coverage_95 = mean_band_width = None
|
|
450
|
+
|
|
451
|
+
# Per-parameter posterior ranges across the best parameter sets
|
|
452
|
+
posterior: dict = {}
|
|
453
|
+
for s in out["best_parameter_sets"]:
|
|
454
|
+
for params in s["parameters"].values():
|
|
455
|
+
for name, info in params.items():
|
|
456
|
+
posterior.setdefault(name, []).append(info["default"])
|
|
457
|
+
posterior_ranges = {
|
|
458
|
+
name: {"min": round(float(min(v)), 4), "max": round(float(max(v)), 4)}
|
|
459
|
+
for name, v in posterior.items()
|
|
460
|
+
}
|
|
461
|
+
|
|
334
462
|
return {
|
|
335
463
|
"model_id": model_id,
|
|
336
464
|
"objective": objective,
|
|
337
465
|
"n_runs": n_runs,
|
|
466
|
+
"save_best": save_best,
|
|
338
467
|
"best_performance": round(float(out["best_performance"]), 4),
|
|
339
468
|
"current_performance": round(float(out["original_performance"]), 4),
|
|
469
|
+
"coverage_95": coverage_95, # fraction of observations inside the 95% band
|
|
470
|
+
"mean_band_width": mean_band_width, # mean (q95 - q5), mm/day
|
|
471
|
+
"parameter_posterior_ranges": posterior_ranges,
|
|
340
472
|
}
|
|
341
473
|
|
|
342
474
|
|
|
@@ -375,6 +507,69 @@ def load_model(model_path: str) -> dict:
|
|
|
375
507
|
return {"model_id": model_id, "performance_metrics": _metrics(model)}
|
|
376
508
|
|
|
377
509
|
|
|
510
|
+
@mcp.tool()
|
|
511
|
+
def clone_model(model_id: str, name: str = "") -> dict:
|
|
512
|
+
"""Deep-copy a model (parameters, data, states, results) into a new model_id.
|
|
513
|
+
|
|
514
|
+
The canonical split-sample pattern: calibrate ``model-A``, ``clone_model`` it, then
|
|
515
|
+
``load_data`` the validation window on the clone and ``run_model`` — the calibrated
|
|
516
|
+
parameters carry over, no manual parameter transfer needed.
|
|
517
|
+
"""
|
|
518
|
+
import copy
|
|
519
|
+
|
|
520
|
+
src = _get(model_id)
|
|
521
|
+
new_id = _new_id()
|
|
522
|
+
_MODELS[new_id] = copy.deepcopy(src)
|
|
523
|
+
return {"model_id": new_id, "cloned_from": model_id, "name": name}
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@mcp.tool()
|
|
527
|
+
def copy_parameters(from_model_id: str, to_model_id: str) -> dict:
|
|
528
|
+
"""Copy the calibrated parameters (and ranges) from one model to another.
|
|
529
|
+
|
|
530
|
+
Use this to transfer a calibration to a validation model without copying its data.
|
|
531
|
+
"""
|
|
532
|
+
import copy
|
|
533
|
+
|
|
534
|
+
src = _get(from_model_id)
|
|
535
|
+
dst = _get(to_model_id)
|
|
536
|
+
dst.params = copy.deepcopy(src.params)
|
|
537
|
+
return {
|
|
538
|
+
"from_model_id": from_model_id,
|
|
539
|
+
"to_model_id": to_model_id,
|
|
540
|
+
"parameters": {
|
|
541
|
+
group: {name: float(info["default"]) for name, info in params.items()}
|
|
542
|
+
for group, params in dst.params.items()
|
|
543
|
+
},
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
@mcp.tool()
|
|
548
|
+
def get_metrics(model_id: str) -> dict:
|
|
549
|
+
"""Return the model's current performance metrics without re-running it.
|
|
550
|
+
|
|
551
|
+
Empty if the model hasn't been run with observed discharge yet.
|
|
552
|
+
"""
|
|
553
|
+
model = _get(model_id)
|
|
554
|
+
return {"model_id": model_id, "performance_metrics": _metrics(model)}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
@mcp.tool()
|
|
558
|
+
def compare_models(model_ids: list) -> dict:
|
|
559
|
+
"""Tabulate performance metrics across several models side by side.
|
|
560
|
+
|
|
561
|
+
Handy for comparing calibration vs validation, or several calibration variants.
|
|
562
|
+
"""
|
|
563
|
+
rows = []
|
|
564
|
+
for mid in model_ids:
|
|
565
|
+
try:
|
|
566
|
+
model = _get(mid)
|
|
567
|
+
rows.append({"model_id": mid, "metrics": _metrics(model)})
|
|
568
|
+
except ValueError as e:
|
|
569
|
+
rows.append({"model_id": mid, "error": str(e)})
|
|
570
|
+
return {"comparison": rows}
|
|
571
|
+
|
|
572
|
+
|
|
378
573
|
def main() -> None:
|
|
379
574
|
"""Console-script / module entry point.
|
|
380
575
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: HBV_Lab
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: An intuitive, object-oriented and user-friendly Python implementation of a lumped conceptual HBV hydrological model for educational and research purposes.
|
|
5
5
|
Home-page: https://github.com/abdallaox/HBV_python_implementation
|
|
6
6
|
Author: Abdalla Mohammed
|
|
@@ -250,19 +250,30 @@ mode deploys cleanly to platforms like Railway, Render or Fly.
|
|
|
250
250
|
|
|
251
251
|
#### What the agent can do
|
|
252
252
|
|
|
253
|
-
Tools: `create_model`, `load_data` (from a CSV/Excel file path),
|
|
254
|
-
`
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
Tools: `create_model`, `clone_model`, `copy_parameters`, `load_data` (from a CSV/Excel file path),
|
|
254
|
+
`get_parameters`, `set_parameters`, `get_parameter_ranges`, `set_parameter_ranges`,
|
|
255
|
+
`set_initial_conditions`, `run_model`, `calibrate`, `evaluate_uncertainty`, `get_metrics`,
|
|
256
|
+
`compare_models`, `plot_results`, `save_results`, `save_model`, `load_model`, `list_models`. A typical
|
|
257
|
+
agent flow is *create → load → run → calibrate → plot*. Model state is kept server-side (each tool
|
|
258
|
+
takes a `model_id`) and large time series are passed by **file path**, not through the agent's
|
|
259
|
+
context — tools return compact metrics and output-file paths.
|
|
259
260
|
|
|
260
261
|
**Calibration progress & agent steering.** `calibrate` emits MCP progress notifications every
|
|
261
262
|
optimizer iteration (clients that surface them show a live progress/log view), and its result
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
reports an honest convergence `status` (`converged` / `hit_iteration_budget` / `failed`),
|
|
264
|
+
`still_improving`, the best-objective-per-iteration `objective_trajectory`, and an `at_bound` list of
|
|
265
|
+
parameters pinned to their range limits. Because each call continues from the model's *current*
|
|
266
|
+
parameters, an agent can calibrate incrementally — call `calibrate` with a small `iterations` budget,
|
|
267
|
+
inspect the improving metric, and decide whether to keep going, widen ranges (via
|
|
268
|
+
`set_parameter_ranges`), or switch objective between rounds.
|
|
269
|
+
|
|
270
|
+
**Split-sample made easy.** Calibrate one model, `clone_model` it (or `copy_parameters` to a second
|
|
271
|
+
model), `load_data` the validation window on the clone, and `run_model` — the calibrated parameters
|
|
272
|
+
carry over with no manual transfer. `compare_models` tabulates calibration vs. validation metrics.
|
|
273
|
+
|
|
274
|
+
**Richer uncertainty.** `evaluate_uncertainty` returns the 95% prediction-band **coverage** (fraction
|
|
275
|
+
of observations inside the band), **mean band width**, and per-parameter **posterior ranges**, not
|
|
276
|
+
just the best/current objective.
|
|
266
277
|
|
|
267
278
|
## Inputs & outputs
|
|
268
279
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: HBV_Lab
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: An intuitive, object-oriented and user-friendly Python implementation of a lumped conceptual HBV hydrological model for educational and research purposes.
|
|
5
5
|
Home-page: https://github.com/abdallaox/HBV_python_implementation
|
|
6
6
|
Author: Abdalla Mohammed
|
|
@@ -250,19 +250,30 @@ mode deploys cleanly to platforms like Railway, Render or Fly.
|
|
|
250
250
|
|
|
251
251
|
#### What the agent can do
|
|
252
252
|
|
|
253
|
-
Tools: `create_model`, `load_data` (from a CSV/Excel file path),
|
|
254
|
-
`
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
Tools: `create_model`, `clone_model`, `copy_parameters`, `load_data` (from a CSV/Excel file path),
|
|
254
|
+
`get_parameters`, `set_parameters`, `get_parameter_ranges`, `set_parameter_ranges`,
|
|
255
|
+
`set_initial_conditions`, `run_model`, `calibrate`, `evaluate_uncertainty`, `get_metrics`,
|
|
256
|
+
`compare_models`, `plot_results`, `save_results`, `save_model`, `load_model`, `list_models`. A typical
|
|
257
|
+
agent flow is *create → load → run → calibrate → plot*. Model state is kept server-side (each tool
|
|
258
|
+
takes a `model_id`) and large time series are passed by **file path**, not through the agent's
|
|
259
|
+
context — tools return compact metrics and output-file paths.
|
|
259
260
|
|
|
260
261
|
**Calibration progress & agent steering.** `calibrate` emits MCP progress notifications every
|
|
261
262
|
optimizer iteration (clients that surface them show a live progress/log view), and its result
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
reports an honest convergence `status` (`converged` / `hit_iteration_budget` / `failed`),
|
|
264
|
+
`still_improving`, the best-objective-per-iteration `objective_trajectory`, and an `at_bound` list of
|
|
265
|
+
parameters pinned to their range limits. Because each call continues from the model's *current*
|
|
266
|
+
parameters, an agent can calibrate incrementally — call `calibrate` with a small `iterations` budget,
|
|
267
|
+
inspect the improving metric, and decide whether to keep going, widen ranges (via
|
|
268
|
+
`set_parameter_ranges`), or switch objective between rounds.
|
|
269
|
+
|
|
270
|
+
**Split-sample made easy.** Calibrate one model, `clone_model` it (or `copy_parameters` to a second
|
|
271
|
+
model), `load_data` the validation window on the clone, and `run_model` — the calibrated parameters
|
|
272
|
+
carry over with no manual transfer. `compare_models` tabulates calibration vs. validation metrics.
|
|
273
|
+
|
|
274
|
+
**Richer uncertainty.** `evaluate_uncertainty` returns the 95% prediction-band **coverage** (fraction
|
|
275
|
+
of observations inside the band), **mean band width**, and per-parameter **posterior ranges**, not
|
|
276
|
+
just the best/current objective.
|
|
266
277
|
|
|
267
278
|
## Inputs & outputs
|
|
268
279
|
|
|
@@ -209,19 +209,30 @@ mode deploys cleanly to platforms like Railway, Render or Fly.
|
|
|
209
209
|
|
|
210
210
|
#### What the agent can do
|
|
211
211
|
|
|
212
|
-
Tools: `create_model`, `load_data` (from a CSV/Excel file path),
|
|
213
|
-
`
|
|
214
|
-
`
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
Tools: `create_model`, `clone_model`, `copy_parameters`, `load_data` (from a CSV/Excel file path),
|
|
213
|
+
`get_parameters`, `set_parameters`, `get_parameter_ranges`, `set_parameter_ranges`,
|
|
214
|
+
`set_initial_conditions`, `run_model`, `calibrate`, `evaluate_uncertainty`, `get_metrics`,
|
|
215
|
+
`compare_models`, `plot_results`, `save_results`, `save_model`, `load_model`, `list_models`. A typical
|
|
216
|
+
agent flow is *create → load → run → calibrate → plot*. Model state is kept server-side (each tool
|
|
217
|
+
takes a `model_id`) and large time series are passed by **file path**, not through the agent's
|
|
218
|
+
context — tools return compact metrics and output-file paths.
|
|
218
219
|
|
|
219
220
|
**Calibration progress & agent steering.** `calibrate` emits MCP progress notifications every
|
|
220
221
|
optimizer iteration (clients that surface them show a live progress/log view), and its result
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
reports an honest convergence `status` (`converged` / `hit_iteration_budget` / `failed`),
|
|
223
|
+
`still_improving`, the best-objective-per-iteration `objective_trajectory`, and an `at_bound` list of
|
|
224
|
+
parameters pinned to their range limits. Because each call continues from the model's *current*
|
|
225
|
+
parameters, an agent can calibrate incrementally — call `calibrate` with a small `iterations` budget,
|
|
226
|
+
inspect the improving metric, and decide whether to keep going, widen ranges (via
|
|
227
|
+
`set_parameter_ranges`), or switch objective between rounds.
|
|
228
|
+
|
|
229
|
+
**Split-sample made easy.** Calibrate one model, `clone_model` it (or `copy_parameters` to a second
|
|
230
|
+
model), `load_data` the validation window on the clone, and `run_model` — the calibrated parameters
|
|
231
|
+
carry over with no manual transfer. `compare_models` tabulates calibration vs. validation metrics.
|
|
232
|
+
|
|
233
|
+
**Richer uncertainty.** `evaluate_uncertainty` returns the 95% prediction-band **coverage** (fraction
|
|
234
|
+
of observations inside the band), **mean band width**, and per-parameter **posterior ranges**, not
|
|
235
|
+
just the best/current objective.
|
|
225
236
|
|
|
226
237
|
## Inputs & outputs
|
|
227
238
|
|
|
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
|
|
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
|