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