Project persistence
===================
.. currentmodule:: asymmetry.core.project
The ``asymmetry.core.project`` module provides versioned JSON project files
(``.asymp``) that capture the full GUI session — loaded datasets, panel
settings, fit configuration, and Fourier settings — so you can close the
application and resume exactly where you left off.
Raw data arrays are **not** embedded in the project file; instead, source
file paths are stored and the files are reloaded on open. This keeps project
files small and avoids duplicating data.
The schema version embedded in every ``.asymp`` file is independent of the
package version, so project files survive package upgrades as long as the
schema version is supported.
Public API
----------
.. autofunction:: load_project
.. autofunction:: save_project
.. autodata:: CURRENT_SCHEMA_VERSION
The integer schema version written into every new project file. Currently
``7``.
.. autoexception:: UnsupportedSchemaVersion
Schema internals
----------------
These functions are used internally by :func:`load_project` and
:func:`save_project` but may be useful for testing or migration scripts.
.. autofunction:: asymmetry.core.project.schema.validate
.. autofunction:: asymmetry.core.project.schema.migrate_to_current
Project file format
-------------------
A project file is a UTF-8 JSON document with the following top-level
structure::
{
"schema_version": 7,
"created_with_app_version": "0.1.0",
"datasets": [
{
"source_file": "/absolute/path/to/file.nxs",
"run_number": 1234,
"metadata_overrides": { "field": 50.0 },
"representations": {
"time_fb_asymmetry": {
"recipe": {},
"fit": {
"model": { "component_names": ["Exponential"], "operators": [] },
"parameters": [...],
"result": { "success": true, "reduced_chi_squared": 0.97, ... },
"provenance": "single",
"batch_id": null,
"diverged": false,
"include_in_trend": true
},
"trend_state": {}
},
"freq_fft": {
"recipe": { "fourier_config": { "window": "gaussian", ... } },
"fit": { "model": null, "provenance": "none", ... },
"trend_state": {}
}
}
}
],
"combined_datasets": [
{ "source_run_numbers": [1234, 1235] }
],
"batches": [
{
"batch_id": "batch-1",
"rep_type": "time_fb_asymmetry",
"member_kind": "runs",
"member_run_numbers": [1234, 1235, 1236],
"member_source_run": {},
"order_key": "field",
"canonical_model": { "component_names": ["Exponential"], "operators": [] },
"param_roles": { "A": "local", "Lambda": "global" },
"nuisance_params": [],
"results_by_run": {
"1234": { "success": true, "parameters": { "Lambda": 0.31 }, ... }
},
"diverged_runs": []
}
],
"browser_state": {
"sort_column": 0,
"sort_order": "ascending",
"filters": {},
"selected_run_numbers": [1234],
"selected_group_ids": [],
"data_groups": [],
"extra_columns": []
},
"plot_state": { ... },
"single_fit_state": {
"model_name": "ExponentialRelaxation",
"parameters": [...],
"result_html": "χ² = ..."
},
"global_fit_state": {
"model_name": "ExponentialRelaxation",
"parameters": [...],
"result_html": "χ² = ..."
},
"fit_ui_state": { "active_tab_index": 0 },
"fit_parameters_state": { "rows": [...], "x_axis": "Auto", ... },
"fourier_state": {
"window": "gaussian",
"filter_start_us": 0.0,
"filter_time_constant_us": 1.5,
"padding": 4,
"display": "Power"
}
}
**Schema versioning history:**
* v1–v4 — initial releases
* v5 — grouping overrides, wizard cache
* v6 — per-dataset ``representations`` map (recipe-only FFT, per-run ``FitSlot``,
trend state); top-level ``batches`` list
* v7 — ``FitSeries`` gains ``member_kind``, ``nuisance_params``,
``member_source_run``; ``trend_state`` normalised to structured shape;
group series recorded in ``batches``
Unknown top-level keys are silently preserved during migration, ensuring
forward compatibility when a newer version of the application adds new state.
Representation
--------------
Each dataset entry's ``representations`` map holds one entry per analysis
view the user has exercised. Keys are ``RepresentationType`` values:
``"time_fb_asymmetry"``, ``"time_groups"``, ``"freq_fft"``, ``"freq_maxent"``.
Each representation stores:
``recipe``
Generation parameters (for FFT spectra: the ``GroupSpectrumConfig``
serialisation; for time-domain views: empty). On project load the
transient arrays are recomputed from the recipe rather than stored.
``fit``
A single :class:`~asymmetry.core.representation.base.FitSlot` — the most
recent fit for this (dataset, representation) pair. Includes the model
dict, fitted-parameter list, result summary, provenance marker
(``"none"``, ``"single"``, ``"batch"``, ``"global"``), the ``batch_id`` of
the owning :class:`~asymmetry.core.representation.series.FitSeries` when
the fit was part of a series, and divergence flags used by the trending
panel.
``trend_state``
Opaque dict persisting the user's x-axis and y-parameter selections in the
Fit Parameters panel for this representation.
FitSeries (``batches``)
-----------------------
The top-level ``batches`` list stores
:class:`~asymmetry.core.representation.series.FitSeries` objects that
accumulate results across multiple members.
Key fields:
``member_kind``
``"runs"`` for F-B asymmetry / FFT batch fits; ``"groups"`` for
multi-run grouped time-domain fits.
``member_run_numbers``
Ordered list of member keys. For ``"runs"`` series these are real run
numbers; for ``"groups"`` series they are synthetic negative keys of the
form ``-(source_run * 1000 + group_index)``.
``member_source_run``
Map from synthetic group key → source run number (``"groups"`` series only).
``param_roles``
Per-parameter classification: ``"global"``, ``"local"``, or ``"fixed"``.
``nuisance_params``
List of per-(run, group) nuisance parameter names excluded from trending
(``"groups"`` series only).
``results_by_run``
Per-member fit result summaries (parameter values, uncertainties, χ²)
used to drive the Fit Parameters trending panel.