Configuration Serialization¶
unite configurations can be saved to and loaded from human-readable YAML files. This
supports reproducible fits, offline editing, and sharing configurations between collaborators.
The Configuration Container¶
Configuration is the top-level object that bundles everything needed
to describe a fit:
from unite.config import Configuration
from unite.instrument.config import InstrumentConfig
config = Configuration(
lines=lc, # LineConfiguration
continuum=cc, # ContinuumConfiguration (optional)
dispersers=dc, # InstrumentConfig (optional)
)
Saving and Loading¶
Top-Level Configuration¶
# To a file
config.save('my_fit.yaml')
# To a YAML string (useful for logging or embedding in notebooks)
yaml_str = config.to_yaml()
# From a file
config2 = Configuration.load('my_fit.yaml')
# From a YAML string
config2 = Configuration.from_yaml(yaml_str)
# Access the reconstructed objects
lc2 = config2.lines
cc2 = config2.continuum
dc2 = config2.dispersers
Sub-Configuration Serialization¶
Each sub-configuration also supports standalone YAML I/O:
# Lines only
lc.save('lines.yaml')
lc2 = LineConfiguration.load('lines.yaml')
# Continuum only
cc.save('continuum.yaml')
cc2 = ContinuumConfiguration.load('continuum.yaml')
# Dispersers only
dc.save('dispersers.yaml')
dc2 = InstrumentConfig.load('dispersers.yaml')
This is useful for reusing the same line configuration across different fits, or sharing disperser setups between collaborators.
YAML Format¶
The YAML file is human-readable and editable. Here is the output of a simple two-line configuration:
lines:
lines:
- name: H_alpha
center: 6563.0 AA
profile: Gaussian
redshift:
name: nlr_z
prior: {type: Uniform, low: -0.005, high: 0.005}
fwhm_gauss:
name: nlr_fwhm
prior: {type: Uniform, low: 1.0, high: 10.0}
flux:
name: Ha_flux
prior: {type: Uniform, low: 0, high: 5}
- name: NII_6585
center: 6585.0 AA
profile: Gaussian
redshift: {ref: nlr_z} # ← shared token reference
fwhm_gauss: {ref: nlr_fwhm} # ← shared token reference
flux:
name: NII6585_flux
prior: {type: Uniform, low: 0, high: 5}
continuum:
regions:
- low: 6241.85 AA
high: 6931.05 AA
form: {type: Linear}
Shared tokens appear once as a full definition and subsequently as a {ref: name}
reference. You can edit priors, add lines, or change profile types directly in the YAML.
Dependent Priors in YAML¶
When a prior bound references another parameter, it is serialized with ref, scale, and
offset fields:
fwhm_broad:
name: fwhm_b
prior:
type: Uniform
low: {ref: fwhm_n, scale: 1.0, offset: 150.0}
high: 5000.0
This represents the expression fwhm_narrow * 1.0 + 150.0 as the lower bound.
Dispersers in YAML¶
When a InstrumentConfig is included, calibration
tokens are also serialized:
dispersers:
calib_params:
r_shared:
type: RScale
prior: {type: TruncatedNormal, loc: 1.0, scale: 0.05, low: 0.8, high: 1.2}
entries:
- type: G235H
r_source: point
r_scale: {ref: r_shared}
- type: G395H
r_source: point
r_scale: {ref: r_shared}
flux_scale:
name: flux_g395h
prior: {type: Uniform, low: 0.5, high: 2.0}
Combining Configurations¶
Configurations (and sub-configurations) can be combined using the + operator. This is useful when building complex fits from modular pieces — for example, combining line configs from different spectral regions or adding dispersers incrementally.
Combining Full Configurations¶
# Create two independent configurations
cfg1 = Configuration(lines_1, continuum_1, dispersers_1)
cfg2 = Configuration(lines_2, continuum_2, dispersers_2)
# Combine them
cfg_combined = cfg1 + cfg2
The combined configuration contains:
All lines from both configs
All continuum regions from both configs (if present)
All dispersers from both configs (if present)
Combining Sub-Configurations¶
Each sub-config also supports the + operator for standalone combination:
# Combine line configs
lines_combined = lc1 + lc2
# Combine continuum configs
continuum_combined = cc1 + cc2
# Combine disperser configs
dispersers_combined = dc1 + dc2
Handling Missing Sub-Configs¶
When combining configs where one has continuum/dispersers and the other doesn’t, the non-None config is used:
cfg1 = Configuration(lines_1, continuum_1) # has continuum
cfg2 = Configuration(lines_2) # no continuum
cfg_combined = cfg1 + cfg2
# cfg_combined.continuum is continuum_1
Strict Mode — Name Collisions¶
Configuration combination uses strict mode, raising an error if any names collide:
# This raises ValueError if lc1 and lc2 have lines with the same name
try:
cfg_combined = cfg1 + cfg2
except ValueError as e:
print(f"Name collision: {e}")
Rename conflicting tokens before combining, or use sub-configuration merging with strict=False if you want to share tokens by name:
# LineConfiguration.merge() supports strict=False
lines_combined = lc1.merge(lc2, strict=False) # share same-named tokens
Currently this is only supported for line configurations.
Combined Config Serialization¶
Combined configs serialize and deserialize like any other:
cfg_combined = cfg1 + cfg2
cfg_combined.save('combined.yaml')
cfg_restored = Configuration.load('combined.yaml')
Round-Trip Guarantee¶
unite tests confirm that Configuration.load(config.save(...)) reproduces an identical
configuration, including:
All token names and priors
Token sharing relationships (same token → same model parameter)
Dependent prior expressions (ParameterRef chains)
Profile types and parameters
Continuum region boundaries and functional forms
Calibration token types and priors on dispersers
Editing Configurations Offline¶
A common workflow is to build a configuration programmatically, save it, edit the YAML by hand (e.g., to tighten priors based on a preliminary fit), and reload:
# First pass — broad priors
config.save('fit_v1.yaml')
# Edit fit_v1.yaml in your editor, then:
config_v2 = Configuration.load('fit_v1.yaml')
Warning
The YAML format is part of unite’s public API, but do not rename token name fields
without also updating all {ref: ...} references to that token. Broken references will
raise a KeyError on load.
Dict Interface¶
All configuration objects also support to_dict() / from_dict() for programmatic
manipulation:
d = config.to_dict()
# Modify the dict...
config2 = Configuration.from_dict(d)
This is useful for automated parameter sweeps or template-based configuration generation.