Skip to content

qlroms.utils.builder

Config-driven qlROM building: one YAML file per model, one CLI.

python -m qlroms.utils.builder configs/ks1d_chaotic.yml [--set rom.K=20]

Three blocks, in the same style as the sibling packages' configs. case names the test case and its window (<case module>/<TEST_CASES key> plus the overrides that case accepts), rom says which quantized-local family to fit and with how many charts and modes, and check says what to measure once it is fitted: a closed-loop forecast, optionally against the global (K = 1) ROM of the same family and rank. Unknown keys fail loudly.

Both the case trajectory and the fitted ROM come from the case's own cached pipeline, so re-running a config only pays for what changed.

load_config(path, block_keys=None, defaults=None, verbose=False, **overrides)

Read a YAML config, apply dotted overrides, fill defaults, reject unknown keys.

block_keys / defaults default to this module's schema; pass another pair to validate a config of a different shape with the same rules. verbose=True prints the resolved YAML after defaults and overrides are applied.

Source code in qlroms/utils/builder.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def load_config(path, block_keys=None, defaults=None, verbose=False, **overrides) -> dict:
    """Read a YAML config, apply dotted overrides, fill defaults, reject unknown keys.

    `block_keys` / `defaults` default to this module's schema; pass another pair to
    validate a config of a different shape with the same rules. `verbose=True` prints
    the resolved YAML after defaults and overrides are applied.
    """
    block_keys = BLOCK_KEYS if block_keys is None else block_keys
    defaults = DEFAULTS if defaults is None else defaults

    with open(path) as fh:
        cfg = yaml.safe_load(fh) or {}
    for dotted, value in overrides.items():
        block, _, key = dotted.partition(".")
        if not key:
            raise ValueError(f"Override {dotted!r} must be '<block>.<key>'.")
        cfg.setdefault(block, {})[key] = value

    unknown = sorted(set(cfg) - set(block_keys) - {"name"})
    if unknown:
        raise ValueError(f"Unknown config block(s) {unknown}; allowed: {sorted(block_keys)} plus 'name'.")
    for block, allowed in block_keys.items():
        cfg[block] = {**defaults[block], **(cfg.get(block) or {})}
        bad = sorted(set(cfg[block]) - set(allowed))
        if bad:
            raise ValueError(f"Unknown {block} key(s) {bad}; allowed: {sorted(allowed)}.")
    if verbose:
        print(yaml.dump(cfg, sort_keys=False), end="")
    return cfg

build_case(cfg, K=None)

Case FOM, fitted qlROM and snapshot windows a resolved config describes.

Returns (fom, rom, Xtrain, Xtest, save_dir). K overrides the config's chart count, which is how the global (K = 1) reference ROM is built from the same charts pipeline. Trajectory and ROM are both cached under qlroms.utils.paths.data_dir.

Source code in qlroms/utils/builder.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def build_case(cfg: dict, K=None) -> tuple:
    """Case FOM, fitted qlROM and snapshot windows a resolved config describes.

    Returns (fom, rom, Xtrain, Xtest, save_dir). `K` overrides the config's chart
    count, which is how the global (K = 1) reference ROM is built from the same
    charts pipeline. Trajectory and ROM are both cached under
    `qlroms.utils.paths.data_dir`.
    """
    kind = cfg["rom"]["kind"]
    if kind not in KINDS:
        raise ValueError(f"Unknown rom.kind {kind!r}; choose from {sorted(KINDS)}.")
    fom, Xtrain, Xtest, save_dir, module, settings = _case_windows(cfg)
    K, r, clustering = _rom_settings(cfg, settings, K)
    rom = module.build_local_model(Xtrain, fom, r=r, K=K, save_dir=save_dir,
                                   clustering_kwargs=clustering)
    return fom, rom, Xtrain, Xtest, save_dir

run_build(config, **overrides)

Fit the qlROM a config describes and score it; return the metrics dict.

With check.compare_global the same family is also fitted at K = 1 and scored on the same window, which is the comparison the ql-ROM claim rests on. With check.save_figs the FOM-vs-ROMs page lands next to the cached model.

Source code in qlroms/utils/builder.py
197
198
199
200
201
202
203
204
205
def run_build(config, **overrides) -> dict:
    """Fit the qlROM a config describes and score it; return the metrics dict.

    With ``check.compare_global`` the same family is also fitted at ``K = 1`` and
    scored on the same window, which is the comparison the ql-ROM claim rests on.
    With ``check.save_figs`` the FOM-vs-ROMs page lands next to the cached model.
    """
    cfg = load_config(config, **overrides) if isinstance(config, (str, os.PathLike)) else config
    return _run(cfg, build_case, _score)