Source code for psc_sim.settings

"""User settings persisted as JSON (preset, sweep, Rs mask, LTspice path, optional params)."""

from __future__ import annotations

import json
import os
from dataclasses import dataclass, field
from pathlib import Path

from psc_sim.parameters import CellParameters
from psc_sim.presets import DEFAULT_PRESET_ID
from psc_sim.simulation.defaults import DEFAULT_TRAN_SPEC, PEROVSKITE_IV_SWEEP
from psc_sim.simulation.eis_plot_display import DEFAULT_EIS_PLOT_F_MIN_HZ
from psc_sim.simulation.specs import sweep_spec_from_dict, tran_spec_from_dict
from psc_sim.simulation.types import SweepSpec, TranSpec
from psc_sim.validation import params_from_dict, params_to_dict


def settings_path() -> Path:
    if os.name == "nt":
        base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
    else:
        base = Path.home() / ".config"
    return base / "psc-sim" / "settings.json"


[docs] @dataclass class UserSettings: last_preset: str = DEFAULT_PRESET_ID iv_spec: SweepSpec = field(default_factory=lambda: PEROVSKITE_IV_SWEEP) rs_fit_lo_mA: float = 5.0 rs_fit_hi_mA: float = 28.0 ltspice_exe: str | None = None custom_params: CellParameters | None = None tran_spec: TranSpec | None = None eis_plot_f_min_hz: float = DEFAULT_EIS_PLOT_F_MIN_HZ def to_json_dict(self) -> dict: d: dict = { "last_preset": self.last_preset, "iv_spec": { "v_min": self.iv_spec.v_min, "v_max": self.iv_spec.v_max, "step": self.iv_spec.step, }, "rs_fit_lo_mA": self.rs_fit_lo_mA, "rs_fit_hi_mA": self.rs_fit_hi_mA, "ltspice_exe": self.ltspice_exe, "eis_plot_f_min_hz": self.eis_plot_f_min_hz, } if self.custom_params is not None: d["params"] = params_to_dict(self.custom_params) if self.tran_spec is not None: t = self.tran_spec d["tran_spec"] = { "t_stop_s": t.t_stop_s, "pulse_v_high": t.pulse_v_high, } return d @classmethod def from_json_dict(cls, data: dict) -> UserSettings: spec = sweep_spec_from_dict(data.get("iv_spec") or {}, base=PEROVSKITE_IV_SWEEP) custom = None raw_params = data.get("params") if isinstance(raw_params, dict): custom = params_from_dict(raw_params) tran_spec = None raw_tran = data.get("tran_spec") if isinstance(raw_tran, dict): tran_spec = tran_spec_from_dict(raw_tran, base=DEFAULT_TRAN_SPEC) return cls( last_preset=str(data.get("last_preset", DEFAULT_PRESET_ID)), iv_spec=spec, rs_fit_lo_mA=float(data.get("rs_fit_lo_mA", 5.0)), rs_fit_hi_mA=float(data.get("rs_fit_hi_mA", 28.0)), ltspice_exe=data.get("ltspice_exe"), custom_params=custom, tran_spec=tran_spec, eis_plot_f_min_hz=float(data.get("eis_plot_f_min_hz", DEFAULT_EIS_PLOT_F_MIN_HZ)), )
def load_settings() -> UserSettings: path = settings_path() if not path.is_file(): return UserSettings() try: data = json.loads(path.read_text(encoding="utf-8")) return UserSettings.from_json_dict(data) except (json.JSONDecodeError, TypeError, ValueError): return UserSettings() def save_settings(settings: UserSettings) -> Path: path = settings_path() path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(settings.to_json_dict(), indent=2), encoding="utf-8") return path