Spec¶
The living, caveman-encoded design spec — goals, constraints, interfaces, invariants, open research questions, and the task/bug ledger. This is the authoritative source for anything that contradicts a summary elsewhere on this site; the pages under Reference are prose explanations of what's formalized here.
SPEC — itis-sumo¶
Caveman-encoded (drop articles/filler; → becomes, ! must, ? may/uncertain, ⊥ never). Standalone surrogate-modeling library aggregating prior MetaModeling trials.
LINKS¶
- engine ! PyPI wheel
itis-dakota==1.5.9(Dakota 6.20, parity w/ mmux/vite). In-repoitis-dakota/= fork checkout, REFERENCE-ONLY: pkg consumes pre-built wheels, ⊥ builds fork (6.24 checkout kept for T16mo ladder) - feeding repos (per-branch/commit inventory + more-to-port →
PORTING.md): ../mmux_vite(GiulianaRenc/mmux_vite): v1 port source = branchjgo/sobol-indices@22685f7→flaskapi/src/mmux_flaskapi/{dakota,data_preprocessor}/; E1 inspiration = branchjgo/sumo-model-export-import@d5150b9→ re-implemented in-repo onfeat/sumo-model-export-import(T12ze-T14qa ✓)../itis_dakota_projects(ITISFoundation/itis-a-dakota-project) +../itis_dakota_projects_clean(ITISFoundation/itis_dakota_projects): recycledvalidate_dakota_installation/get_dakota_version+validateCLI../mmux_python(ITISFoundation/mmux_gui): ancestor lineage only (⊥ direct port source)- future consumer →
../mmux_vite/flaskapi(swaps in-tree core → this pkg) - research →
DAKOTA-STUBS.md(stubs + JSON→pydantic POC)
§G¶
Standalone pkg itis-sumo (SuMo = Surrogate Model): aggregate MetaModeling core from mmux/vite + prior trials (mmux_python, itis_dakota_projects) into one wheel-driven library, usable BOTH inside mmux/vite AND headless (notebook/scripts) → build/evaluate/cross-validate surrogates + UQ (Sobol/correlation) + sampling (LHS/grid) + MOGA. First expansion (E1): SuMo model export/import persistence.
§C¶
- engine ! itis-dakota wheel
dakota.environment.study(input_string=...); ⊥ subprocessdakotabin, ⊥ vendor surfpack; in-repoitis-dakota/fork ! REFERENCE-ONLY (⊥ build; runtime = PyPI wheel) - v1 config ! NIDR string composers (proven); JSON/pydantic seam later (DAKOTA-STUBS POC, Dakota 6.24 experimental)
- port ! near-verbatim from mmux_flaskapi
dakota/+data_preprocessor/; re-rootmmux_flaskapi.→itis_sumo.; prune known-dead (process_json_file,add_interface_s4l,create_function_sampling_conffile,create_moga_iterative_optimization_conffile); de-webJobVariableSelection/required_completed_jobs(move from blueprints into pkg) - recycle itis_dakota_projects:
validate_dakota_installation(),get_dakota_version(),validateCLI, config sanity-guard; ⊥ subprocessDakotaProject/factory/generator/parser/activity-logger - deps ! itis-dakota==1.5.9 (Dakota 6.20, parity w/ mmux/vite; stay until T16mo resolves the 6.23+
interface_cacheregression — R5), numpy, pandas, scipy; scikit-learn ⊥ KFold only - py.typed;
src/layout; docs MkDocs + headless notebook; tests standalone ⊥ flask/osparc - versioning single-source; uv + pip-compile pins; Conventional Commits
- E1: artifacts keyed server
sumo_model_id(uuid); metadata sidecar{id}.metadata.json; ⊥ user-supplied path/prefix keys (traversal) - py ! 3.11 (match mmux/vite; 1.5.9 ships no cp313 wheel); 3.13 only via T16mo rung 1 (1.5.11 cp313) or rung 2 (6.24)
- cross-repo call surface ! narrow, versioned, "deep" entrypoints — one stitched public function per feature (e.g.
analyze_dataset()), ⊥ flaskapi orchestrating several itis-sumo internals itself; version-pinning itis-sumo is fine (same pattern as the itis-dakota engine pin, T16mo) — the coupling risk is a wide call surface, not the pin
§I¶
- wheel:
dakota.environment.study,study.execute()(⊖dakota.surrogatesuntil wheel bump, R4) - CLI:
itis-sumo validate - python:
itis_sumo.{core,config,data,sampling,evaluate,preprocess,utils}public funcs (+ forthcominganalyze_datasetnarrow diagnostics entrypoint, T18ry) - artifacts: run_dir
{dakota_stdout.txt,dakota_stderr.txt,*.dat,*.sps/.alg}; models dir{id}.metadata.json(E1)
§V¶
V1pm: DakotaObject.run ! execute wheel in ProcessPoolExecutor; ⊥ subprocess dakota bin
V2qw: run dirs ! explicit paths; process-cwd mutation confined to Dakota worker (os.chdir guard)
V3er: create_manual_uq_samples ! draw ∀ dist via seeded np.random.Generator (random_state=); ⊥ scipy global state
V4ty: core modules ! zero flask/osparc imports (test-enforced)
V5ui: NIDR config ! pure string composers; seam for JSON/pydantic layer later
V6op: sanitize_varnames ! preserve literal - in var names
V7as: data processing ! heal-or-drop malformed rows w/ trace; ⊥ silent wipe
V8df: {output}_std_hat availability ! from surrogate results post-evaluate_sumo, ⊥ raw job outputs
V9gh: Sobol ! scipy-based (Saltelli A/B/AB + closed-form 2nd-order), validated vs Ishigami analytical
V10jk: E1 export ! {id}.metadata.json sidecar w/ verbatim conf block + ordered input descriptors + output descriptor + format + a copy of the real training-data file ({id}.processed_training.dat, kept for reference/debuggability per user request — NOT technically required, archive alone is sufficient for reload, R9)
V11lz: E1 import ! conf block identical to export block bar export↔import swap; import_build_points_file satisfied by staging back the stored real training-data copy (V10); if that copy is missing (legacy model / deleted out-of-band) ⇒ loud warning log + fall back to a header-only placeholder synthesized from the sidecar descriptors (R9 proves this is safe — Dakota never reads row values back), avoiding a crash
V12xc: E1 keying ! server sumo_model_id uuid; ⊥ user-supplied path keys
V13vb: E1 models dir ! single env-overridable source (⊥ branch's dual-strategy split)
V14nm: ⊥ standalone surfpack dep; future standalone-eval → bump itis-dakota (dakota.surrogates)
V15zx (proposed, T17bq): add_surrogate_model training-file header layout ! explicit has_eval_id_column: bool param supplied by caller (whoever wrote/staged the file knows its structure); ⊥ infer from "processed" substring in filename — closes B2's root cause (workaround only renamed the staged file to preserve the substring)
V16qf: itis-sumo's cross-repo-facing API (flaskapi/mmux_vite) ! consumed only via §I's documented top-level entrypoints; ⊥ consumer reaches into internal submodules/functions directly — keeps refactors inside itis-sumo from forcing coordinated cross-repo changes; grows w/ each new feature surfaced (starts w/ analyze_dataset, T18ry)
V17ab: ∀ .py file in repo ! ruff check + ruff format --check clean (CI-enforced via prek on changed files; catches unsorted imports, unformatted code, executable-bit-without-shebang at review time — prevents B3-class regressions)
V18rs: ∀ .github/workflows/** change ! at least one validation job executes the affected workflow path; shared CI workflow changes ⊥ leave the validation matrix entirely skipped
§R¶
R1: export_model/import_model child keywords; formats text_archive(.sps)/binary_archive(.bsps)/algebraic_file(.alg); naming {prefix}.{resp}.{ext} | branch R2
R2: import_model needs conf block identical to export block bar keyword swap; ? whether training VALUES must match — empirical round-trip test | branch R3 — RESOLVED T14qa: values need not match by literal path/identity, only content+structure — a byte-identical copy of the export-time training file at a DIFFERENT path (fresh run_dir, simulated separate session) reproduces export-time predictions exactly (atol 1e-6); real Dakota round-trip, not mocked
R3: .sps archive self-describing vs positional UNVERIFIED ? → sidecar regardless | branch R8
R4: dakota.surrogates ⊖ in wheel 1.5.9; upstream 6.24 adds it — wheel bump over new dep | branch R5-R7
R5: 6.23+ regression — study() ctor → Interface::interface_cache(problem_db) static map (DakotaInterface.hpp:71), populated only via SimulationModel/NestedModel ctor (Interface::get_interface); pure data-fit surrogate confs (import_build_points_file, no interface) never populate → DakotaInterface.cpp:77 .at() throws → IndexError: map::at before execute(). 6.20 ctor used problem_description_db().interface_list() (no cache → no crash). Upstream fix candidate = get-or-create (operator[])
R8: resolves R3's ? — variable-label matching on import happens between the staged points file's header and the Dakota study's OWN declared variables block descriptors (our conf, sourced from the metadata sidecar) — NOT by introspecting the .sps/.bsps archive itself. Verified (dakota/src/SurfpackApproximation.cpp in the itis-dakota fork checkout + live Dakota runs): reordering the points-file header (x2 x1 y1 vs x1 x2 y1) still round-trips correctly — Dakota permutes to match by descriptor, per doc DUPLICATE-import_model ("matched by descriptor... allows order... to change"); supplying wrong (foo bar baz) or incomplete (x1 y1, dropping x2) descriptors fails loudly at Dakota's own import_build_points check ("not a permutation of expected variable labels") rather than silently mis-mapping columns. Confirms the archive is not introspectable from outside Dakota — the metadata sidecar's ordered descriptors remain the sole source of truth for what to declare (V10's sidecar design was necessary, not just defensive)
R9: import_build_points_file's row VALUES are never read back into the reloaded surrogate — SurfpackApproximation::import_model() (dakota/src/SurfpackApproximation.cpp:837-856) does spModel.reset(SurfpackInterface::LoadModel(filename)) straight from the archive; approxData/the points file's rows are never touched by that path. Empirically confirmed: importing with a header-only placeholder (0 data rows), 1 row of out-of-range dummy values (999.0 × N), and 2 rows of zeros ALL reproduce export-time predictions exactly (atol 1e-6) — only the header's descriptor SET matters (R8), not row count or values. → real training data is persisted by default anyway, for reference/debuggability (user preference, not a Dakota requirement); stage_model_for_import stages that stored copy back, falling back to a synthesized header-only placeholder + loud warning only if it's missing, e.g. a model exported before this persistence was added (T17bc)
R10: format support, per Dakota docs (dakota/docs/keywords/DUPLICATE-export_model, DUPLICATE-import_model) + source (SurfpackApproximation::export_model, .cpp:467-516) — only text_archive(.sps)/binary_archive(.bsps) call SurfpackInterface::Save() and are re-loadable via import_model; algebraic_file(.alg)/algebraic_console just dump spModel->asString() (one-way; docs state explicitly "not compatible with Dakota or the surfpack executable") — NOT valid for round-trip, Dakota's NIDR parser rejects them as import_model children outright (fails loud, not silent). Both text_archive and binary_archive verified round-trip exactly (atol 1e-6); no documented/observed precision difference — recommend text_archive (current default, human-inspectable/debuggable) with binary_archive as an opt-in for storage-sensitive cases
§T¶
id|status|task|cites
T1pw|✓|scaffold pyproject.toml ([project], uv, py.typed, pip-compile pins), src/ layout, CLI entry|§C
T2cf|✓|port core/dakota_object.py + core/wiofiles.py|V1,V2
T3qd|✓|port config/ NIDR composers (incl. existing sumo_export/import params)|V5
T4gs|✓|port data/ + sampling/ (lhs, manual-uq, axis/grid, pareto, correlation)|V3,V6,V7
T5as|✓|port evaluate/ (sumo eval, CV+metrics/ttest/convergence, MOGA, Sobol)|V8,V9
T6hy|✓|port preprocess/ DataPreprocessor + integration; move pydantic models in; de-web|V4
T7nv|✓|recycle utils: validate_dakota_installation, get_dakota_version, validate CLI, config guard|§C
T8bx|✓|prune dead paths in ported modules|§C
T9kz|✓|port pure-core tests (test_dakota_*, cv-stats, preprocessor, sobol, correlation) + headless smoke|V4
T10le|✓|docs README + MkDocs + headless notebook — README ✓, MkDocs site ✓ (mkdocs.yml, algorithm pages + live V&V report w/ real pytest results, served via mkdocs serve), headless notebook explicitly deferred past alpha|§G
T11rt|✓|verify: standalone pytest green (⊖ flask), clean-venv install, itis-sumo validate, headless surrogate→CV→Sobol|§C
T12ze|✓|E1: core/sumo_model_store.py (models dir single env-overridable source ITIS_SUMO_MODELS_DIR, uuid keying, metadata sidecar) + evaluate/funs_evaluate.py::export_sumo_model/import_sumo_model public API|V10,V12,V13
T13uv|✓|E1: capture verbatim conf block for sidecar (close branch placeholder gap)|V10
T14qa|✓|E1: empirical export→import round-trip test (real, unmocked Dakota run — tests/test_sumo_model_store.py); resolves R2 ?|R2,V11
T15mn|.|E1: wire mmux/vite flaskapi endpoints to pkg export/import — separate PR in ../mmux_vite (jgo/sumo-model-export-import branch T24, itself still . there); NOT part of this repo|§I,R1-R4
T16mo|.|stepwise engine modernization: rung 1 1.5.9→1.5.11 (packaging-only: drop py3.8, add cp313; same 6.20 engine — behavior-identical; re-run smoke); rung 2 1.5.11→6.24.x co-shipped w/ JSON input seam (R5 regression fixed upstream first, re-align py 3.13, re-run full smoke)|R4,R5
T17bq|✓|add_surrogate_model (config/funs_create_dakota_conf.py) ! replaced filename-substring sniffing w/ explicit has_eval_id_column param (infer-or-override idiom); all 5 create_sumo_*_conffile call sites updated (landed via feat/vv-port-tests-and-docs, a6d694e, ahead of this merge)|V15,B2
T17bc|✓|E1: persist real training data on export for reference ({id}.processed_training.dat sidecar copy, user preference over the leaner archive-only design); stage_model_for_import stages it back, falling back to a synthesized header-only placeholder + loud warning log only when that stored copy is missing (legacy model / deleted out-of-band) — fallback verified safe vs Dakota source + fake-points empirical tests (header reorder, wrong/missing descriptors, 0/1/2-row placeholders)|R8,R9,V10,V11
T18ry|.|design+implement analyze_dataset(df, input_cols, output_cols, alpha=0.05, include_detail=False) -> DatasetDiagnostics narrow entrypoint (scale/distribution auto-selection + outlier surfacing, plain dataclasses, JSON-serializable via dataclasses.asdict()) as the sole flaskapi-facing dataset-diagnostics API, replacing any per-function flaskapi orchestration; BLOCKED — building blocks select_variable_scale/auto_select_distributions (+ a new raw-value outlier detector, reusing _tukey_outlier_mask's IQR technique) currently exist only on confidential incubator branch feat/nih-in-silico-example, not develop — needs individual promotion first, same promotion rule as T15mn/E1|§C,V16qf,I
T19kp|.|headless notebook, post-alpha — add runnable notebook counterpart to docs getting-started flow once alpha docs publish is stable|T10le
T20hm|.|CI workflow changes ! activate shared validation jobs and regression-check detector classification|V18rs
§B¶
id|date|cause|fix
B1|2026-08-07|itis-dakota 6.23+ Interface::interface_cache() throws IndexError: map::at on interface-less surrogate confs (study() ctor, before execute()) — pure data-fit surrogates never instantiate an interface → static map missing entry (R5)|pin ==1.5.9 (Dakota 6.20, no cache) for flaskapi parity; ladder T16mo; upstream get-or-create fix
B2|2026-08-07|E1 import round-trip: add_surrogate_model (config/funs_create_dakota_conf.py) decided whether the training file has a leading eval_id column purely from whether the substring "processed" appears in the training filename, not an explicit flag — staging the re-import training file as {id}.training.dat (T12ze) desynced Dakota's use_variable_labels column expectation from the actual CSV, surfacing as Cannot reorder variables ... not a permutation of expected variable labels|immediate: sumo_model_store.py::_training_file_name names both the stored and staged file {id}.processed_training.dat, preserving the substring; root-cause: add_surrogate_model heuristic replaced by explicit has_eval_id_column param, T17bq/V15zx (now ✓)
B3|2026-08-18|CI prek job fails on PRs that touch unformatted .py files — ruff I001 (unsorted imports) + EXE002 (executable bit, no shebang) + ruff format violations in funs_evaluate.py, funs_create_dakota_conf.py, test_property_invariants.py, generate_docs_figures.py, test_metamodeling_analytical.py, test_unit_solver.py — pre-commit config dropped but ruff formatting not enforced repo-wide first|fix: ruff check --fix + ruff format all touched files + chmod -x executable bits; preventive: §V17ab invariant — ∀ .py file must pass ruff check + ruff format --check
B4kt|2026-08-18|CI workflow-only changes were absent from detect_changes, so all downstream jobs could be skipped and an invalid astral-sh/setup-uv@v10 reference remained unresolved behind an apparently green PR|classify .github/workflows/** as CI changes, run shared validation matrix, replace nonexistent action tag; V18rs
B6nr|2026-08-18|the workflow regression test hard-coded a Dependabot-managed action version and failed on a valid setup-uv bump; CI already validates the workflow itself|remove tests/test_ci_workflow.py and rely on CI validation
B7pv|2026-08-18|prek passed deleted Python paths from the workflow diff to Ruff, causing E902 after removing a test file|exclude deleted paths with --diff-filter=d before xargs