Skip to content

Models API

Concrete implementations of cell, converter, degradation, and thermal models.

Cell Models

SonyLFP

simses.model.cell.sony_lfp.SonyLFP

Bases: CellType

Sony / Murata US26650FTC1 cylindrical LFP cell.

Lithium iron phosphate cell with 3 Ah nominal capacity and 3.2 V nominal voltage. OCV, hysteresis voltage, and entropic coefficient are 1-D lookups in SOC; internal resistance is a 2-D lookup in (SOC, T) with separate charge and discharge tables. Ships a default degradation model (:class:~simses.model.degradation.sony_lfp_calendar.SonyLFPCalendarDegradation + :class:~simses.model.degradation.sony_lfp_cyclic.SonyLFPCyclicDegradation) that :class:~simses.battery.battery.Battery picks up when constructed with degradation=True.

Sources: SONY_US26650FTC1 Product Specification; and Naumann, Maik. Techno-economic evaluation of stationary lithium-ion energy storage systems with special consideration of aging. PhD Thesis, Technical University Munich, 2018.

Source code in src/simses/model/cell/sony_lfp.py
class SonyLFP(CellType):
    """Sony / Murata US26650FTC1 cylindrical LFP cell.

    Lithium iron phosphate cell with 3 Ah nominal capacity and 3.2 V
    nominal voltage. OCV, hysteresis voltage, and entropic coefficient
    are 1-D lookups in SOC; internal resistance is a 2-D lookup in
    ``(SOC, T)`` with separate charge and discharge tables. Ships a
    default degradation model
    (:class:`~simses.model.degradation.sony_lfp_calendar.SonyLFPCalendarDegradation`
    + :class:`~simses.model.degradation.sony_lfp_cyclic.SonyLFPCyclicDegradation`)
    that :class:`~simses.battery.battery.Battery` picks up when
    constructed with ``degradation=True``.

    Sources: SONY_US26650FTC1 Product Specification; and Naumann, Maik.
    *Techno-economic evaluation of stationary lithium-ion energy storage
    systems with special consideration of aging*. PhD Thesis, Technical
    University Munich, 2018.
    """

    def __init__(self):
        super().__init__(
            electrical=ElectricalCellProperties(
                nominal_capacity=3.0,  # Ah
                nominal_voltage=3.2,  # V
                max_voltage=3.6,  # V
                min_voltage=2.0,  # V
                max_charge_rate=1.0,  # 1/h
                max_discharge_rate=6.6,  # 1/h
                self_discharge_rate=0.0 / (365 / 12),
                coulomb_efficiency=1.0,  # p.u.
            ),
            thermal=ThermalCellProperties(
                min_temperature=0.0,  # °C
                max_temperature=60.0,  # °C
                mass=0.07,  # kg per cell
                specific_heat=1001,  # J/kgK
                convection_coefficient=15,  # W/m2K
            ),
            cell_format=RoundCell26650(),
        )
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")

        # 1-D look-up tables. Stored as plain Python lists so the scalar
        # interpolation helpers can use bisect on the raw sequence without
        # numpy boundary overhead.
        df_ocv = pd.read_csv(os.path.join(path, "CLFP_Sony_US26650_OCV.csv"))
        self._ocv_lut_soc = df_ocv["SOC"].tolist()
        self._ocv_lut_ocv = df_ocv["OCV"].tolist()

        df_hyst = pd.read_csv(os.path.join(path, "CLFP_Sony_US26650_HystV.csv"))
        self._hyst_lut_soc = df_hyst["SOC"].tolist()
        self._hyst_lut_hyst = df_hyst["HystV"].tolist()

        df_entropy = pd.read_csv(os.path.join(path, "CLFP_Sony_US26650_entropy.csv"))
        self._entropy_lut_soc = df_entropy["SOC"].tolist()
        self._entropy_lut_entropy = df_entropy["S"].tolist()

        # 2-D internal-resistance look-up tables (charge / discharge).
        df_rint = pd.read_csv(os.path.join(path, "CLFP_Sony_US26650_Rint.csv"))
        self._rint_lut_soc = df_rint["SOC"].tolist()
        self._rint_lut_T = df_rint["Temp"].dropna().tolist()
        self._rint_ch_mat = df_rint.iloc[:, 2:6].values.tolist()
        self._rint_dch_mat = df_rint.iloc[:, 6:].values.tolist()

    def open_circuit_voltage(self, state: BatteryState) -> float:
        return interp1d_scalar(state.soc, self._ocv_lut_soc, self._ocv_lut_ocv)

    def hysteresis_voltage(self, state: BatteryState) -> float:
        return interp1d_scalar(state.soc, self._hyst_lut_soc, self._hyst_lut_hyst)

    def entropic_coefficient(self, state: BatteryState) -> float:
        return interp1d_scalar(state.soc, self._entropy_lut_soc, self._entropy_lut_entropy)

    def internal_resistance(self, state: BatteryState) -> float:
        mat = self._rint_ch_mat if state.is_charge else self._rint_dch_mat
        return interp2d_scalar(state.soc, state.T, self._rint_lut_soc, self._rint_lut_T, mat)

    @classmethod
    def default_degradation_model(
        cls,
        initial_soc: float,
        initial_state: DegradationState | None = None,
    ) -> DegradationModel:
        return DegradationModel(
            cyclic=SonyLFPCyclicDegradation(),
            calendar=SonyLFPCalendarDegradation(),
            initial_soc=initial_soc,
            initial_state=initial_state,
        )

Samsung94AhNMC

simses.model.cell.samsung94Ah_nmc.Samsung94AhNMC

Bases: CellType

Samsung 94 Ah prismatic NMC cell.

High-energy lithium nickel-manganese-cobalt-oxide cell typical of stationary and automotive applications. Analytical OCV(SOC) as a sum of sigmoids and a linear term; constant internal resistance (SoC- and temperature-independent).

Source: Collath et al., "Suitability of late-life lithium-ion cells for battery energy storage systems", Journal of Energy Storage 87 (2024) 111508, doi:10.1016/j.est.2024.111508.

Source code in src/simses/model/cell/samsung94Ah_nmc.py
class Samsung94AhNMC(CellType):
    """Samsung 94 Ah prismatic NMC cell.

    High-energy lithium nickel-manganese-cobalt-oxide cell typical of
    stationary and automotive applications. Analytical ``OCV(SOC)`` as a
    sum of sigmoids and a linear term; constant internal resistance
    (SoC- and temperature-independent).

    Source: Collath et al., "Suitability of late-life lithium-ion cells
    for battery energy storage systems", Journal of Energy Storage 87
    (2024) 111508, doi:10.1016/j.est.2024.111508.
    """

    def __init__(self) -> None:
        super().__init__(
            electrical=ElectricalCellProperties(
                nominal_capacity=94.0,  # Ah
                nominal_voltage=3.68,  # V
                max_voltage=4.15,  # V
                min_voltage=2.7,  # V
                max_charge_rate=2.0,  # 1/h
                max_discharge_rate=2.0,  # 1/h
                self_discharge_rate=0.0 / (365 / 12),
                coulomb_efficiency=1.0,  # p.u.
            ),
            thermal=ThermalCellProperties(
                min_temperature=-40.0,  # °C
                max_temperature=60.0,  # °C
                mass=2.1,  # kg per cell
                specific_heat=1000,  # J/kgK
                convection_coefficient=15,  # W/m2K
            ),
            cell_format=PrismaticCell(
                height=125,  # mm
                width=45.0,  # mm
                length=173.0,  # mm
            ),
        )

    def open_circuit_voltage(self, state: BatteryState) -> float:
        a1 = 3.3479
        a2 = -6.7241
        a3 = 2.5958
        a4 = -61.9684
        b1 = 0.6350
        b2 = 1.4376
        k0 = 4.5868
        k1 = 3.1768
        k2 = -3.8418
        k3 = -4.6932
        k4 = 0.3618
        k5 = 0.9949

        soc = state.soc

        ocv = (
            k0
            + k1 / (1 + math.exp(a1 * (soc - b1)))
            + k2 / (1 + math.exp(a2 * (soc - b2)))
            + k3 / (1 + math.exp(a3 * (soc - 1)))
            + k4 / (1 + math.exp(a4 * soc))
            + k5 * soc
        )
        return ocv

    def internal_resistance(self, state: BatteryState) -> float:
        return 0.819e-3

Converter Loss Models

FixedEfficiency

simses.model.converter.fix_efficiency.FixedEfficiency

Constant-efficiency converter loss model.

Applies a fixed efficiency factor in each direction. Pass a single float for a symmetric model (same efficiency for charging and discharging), or a (charge, discharge) tuple for distinct efficiencies per direction.

Examples:

>>> FixedEfficiency(0.95)            # symmetric 95% round-trip factor
>>> FixedEfficiency((0.96, 0.94))    # 96% charging, 94% discharging
Source code in src/simses/model/converter/fix_efficiency.py
class FixedEfficiency:
    """Constant-efficiency converter loss model.

    Applies a fixed efficiency factor in each direction. Pass a single
    ``float`` for a symmetric model (same efficiency for charging and
    discharging), or a ``(charge, discharge)`` tuple for distinct
    efficiencies per direction.

    Examples:
        >>> FixedEfficiency(0.95)            # symmetric 95% round-trip factor
        >>> FixedEfficiency((0.96, 0.94))    # 96% charging, 94% discharging
    """

    def __init__(self, eff: float | tuple[float, float]) -> None:
        """
        Args:
            eff: Either a single efficiency in p.u. (``0 < eff <= 1``), or
                a ``(charge, discharge)`` tuple of per-direction
                efficiencies in p.u.
        """
        if isinstance(eff, tuple):
            self.eff_charge, self.eff_discharge = eff
        else:
            self.eff_charge = self.eff_discharge = eff

    def ac_to_dc(self, power_ac: float) -> float:
        if power_ac >= 0:
            return power_ac * self.eff_charge
        else:  # power_ac < 0
            return power_ac / self.eff_discharge

    def dc_to_ac(self, power_dc: float) -> float:
        if power_dc >= 0:
            return power_dc / self.eff_charge
        else:  # power_dc < 0
            return power_dc * self.eff_discharge

__init__(eff)

Parameters:

Name Type Description Default
eff float | tuple[float, float]

Either a single efficiency in p.u. (0 < eff <= 1), or a (charge, discharge) tuple of per-direction efficiencies in p.u.

required
Source code in src/simses/model/converter/fix_efficiency.py
def __init__(self, eff: float | tuple[float, float]) -> None:
    """
    Args:
        eff: Either a single efficiency in p.u. (``0 < eff <= 1``), or
            a ``(charge, discharge)`` tuple of per-direction
            efficiencies in p.u.
    """
    if isinstance(eff, tuple):
        self.eff_charge, self.eff_discharge = eff
    else:
        self.eff_charge = self.eff_discharge = eff

Notton

simses.model.converter.notton.Notton

Generic parametric PV-inverter loss family — symmetric form.

Efficiency curve of the form η(p) = p / (p + P0 + K·p²) where p is the magnitude of normalised power (p.u. of the converter's rated max power). Same coefficients apply to charge and discharge.

For the three published inverter presets see :class:NottonType1, :class:NottonType2, :class:NottonType3. For Notton-form fits with per-direction coefficients see :class:AsymmetricNotton.

Source: Notton, G.; Lazarov, V.; Stoyanov, L. (2010). Optimal sizing of a grid-connected PV system for various PV module technologies and inclinations, inverter efficiency characteristics and locations. Renewable Energy 35(2) 541–554, doi:10.1016/j.renene.2009.07.013.

Source code in src/simses/model/converter/notton.py
class Notton:
    """Generic parametric PV-inverter loss family — symmetric form.

    Efficiency curve of the form ``η(p) = p / (p + P0 + K·p²)`` where
    ``p`` is the magnitude of normalised power (p.u. of the converter's
    rated max power). Same coefficients apply to charge and discharge.

    For the three published inverter presets see :class:`NottonType1`,
    :class:`NottonType2`, :class:`NottonType3`. For Notton-form fits
    with per-direction coefficients see :class:`AsymmetricNotton`.

    Source: Notton, G.; Lazarov, V.; Stoyanov, L. (2010). *Optimal sizing
    of a grid-connected PV system for various PV module technologies and
    inclinations, inverter efficiency characteristics and locations.*
    Renewable Energy 35(2) 541–554, doi:10.1016/j.renene.2009.07.013.
    """

    def __init__(self, P0: float, K: float) -> None:
        """
        Args:
            P0: No-load loss coefficient (p.u.).
            K: Quadratic-loss coefficient (p.u.).
        """
        self._inp, self._out = _notton_lut(P0, K, P0, K)

    def ac_to_dc(self, power_ac: float) -> float:
        return interp1d_scalar(power_ac, self._inp, self._out)

    def dc_to_ac(self, power_dc: float) -> float:
        return interp1d_scalar(power_dc, self._out, self._inp)

__init__(P0, K)

Parameters:

Name Type Description Default
P0 float

No-load loss coefficient (p.u.).

required
K float

Quadratic-loss coefficient (p.u.).

required
Source code in src/simses/model/converter/notton.py
def __init__(self, P0: float, K: float) -> None:
    """
    Args:
        P0: No-load loss coefficient (p.u.).
        K: Quadratic-loss coefficient (p.u.).
    """
    self._inp, self._out = _notton_lut(P0, K, P0, K)

AsymmetricNotton

simses.model.converter.notton.AsymmetricNotton

Notton-form loss family with per-direction coefficients.

Same efficiency law as :class:Notton but with independent charge and discharge parameter sets — each a (P0, K) pair. Used by manufacturer product models whose measured efficiency differs between charging and discharging (e.g. :class:BonfiglioliTL4QFieldData, :class:SungrowSC1000TL).

Source code in src/simses/model/converter/notton.py
class AsymmetricNotton:
    """Notton-form loss family with per-direction coefficients.

    Same efficiency law as :class:`Notton` but with independent charge
    and discharge parameter sets — each a ``(P0, K)`` pair. Used by
    manufacturer product models whose measured efficiency differs
    between charging and discharging (e.g. :class:`BonfiglioliTL4QFieldData`,
    :class:`SungrowSC1000TL`).
    """

    def __init__(
        self,
        charge: tuple[float, float],
        discharge: tuple[float, float],
    ) -> None:
        """
        Args:
            charge: ``(P0, K)`` coefficients for the charge branch.
            discharge: ``(P0, K)`` coefficients for the discharge branch.
        """
        P0_ch, K_ch = charge
        P0_dch, K_dch = discharge
        self._inp, self._out = _notton_lut(P0_ch, K_ch, P0_dch, K_dch)

    def ac_to_dc(self, power_ac: float) -> float:
        return interp1d_scalar(power_ac, self._inp, self._out)

    def dc_to_ac(self, power_dc: float) -> float:
        return interp1d_scalar(power_dc, self._out, self._inp)

__init__(charge, discharge)

Parameters:

Name Type Description Default
charge tuple[float, float]

(P0, K) coefficients for the charge branch.

required
discharge tuple[float, float]

(P0, K) coefficients for the discharge branch.

required
Source code in src/simses/model/converter/notton.py
def __init__(
    self,
    charge: tuple[float, float],
    discharge: tuple[float, float],
) -> None:
    """
    Args:
        charge: ``(P0, K)`` coefficients for the charge branch.
        discharge: ``(P0, K)`` coefficients for the discharge branch.
    """
    P0_ch, K_ch = charge
    P0_dch, K_dch = discharge
    self._inp, self._out = _notton_lut(P0_ch, K_ch, P0_dch, K_dch)

NottonType1

simses.model.converter.notton.NottonType1

Bases: Notton

Notton Type-1 inverter preset (P0 = 0.0145, K = 0.0437).

Source: Notton et al. 2010, Renewable Energy 35(2) 541–554.

Source code in src/simses/model/converter/notton.py
class NottonType1(Notton):
    """Notton Type-1 inverter preset (``P0 = 0.0145, K = 0.0437``).

    Source: Notton et al. 2010, Renewable Energy 35(2) 541–554.
    """

    def __init__(self) -> None:
        super().__init__(P0=0.0145, K=0.0437)

NottonType2

simses.model.converter.notton.NottonType2

Bases: Notton

Notton Type-2 inverter preset (P0 = 0.0072, K = 0.0345).

Source: Notton et al. 2010, Renewable Energy 35(2) 541–554.

Source code in src/simses/model/converter/notton.py
class NottonType2(Notton):
    """Notton Type-2 inverter preset (``P0 = 0.0072, K = 0.0345``).

    Source: Notton et al. 2010, Renewable Energy 35(2) 541–554.
    """

    def __init__(self) -> None:
        super().__init__(P0=0.0072, K=0.0345)

NottonType3

simses.model.converter.notton.NottonType3

Bases: Notton

Notton Type-3 inverter preset (P0 = 0.0088, K = 0.1149).

Source: Notton et al. 2010, Renewable Energy 35(2) 541–554.

Source code in src/simses/model/converter/notton.py
class NottonType3(Notton):
    """Notton Type-3 inverter preset (``P0 = 0.0088, K = 0.1149``).

    Source: Notton et al. 2010, Renewable Energy 35(2) 541–554.
    """

    def __init__(self) -> None:
        super().__init__(P0=0.0088, K=0.1149)

Rampinelli

simses.model.converter.rampinelli.Rampinelli

Generic parametric PV-inverter loss family.

Efficiency curve of the form η(p) = p / (p + K0 + K1·p + K2·p²) where p is the magnitude of normalised power (p.u. of the converter's rated max power). Three-parameter extension of the Notton form — the extra linear term lets the fit capture a wider range of measured efficiency curves. Symmetric about zero. The fit is sampled at 201 points (101 per direction, mirrored about zero) at construction and interpolated at runtime, so ac_to_dc and dc_to_ac remain numerical inverses of each other.

Source: Rampinelli, G. A.; Krenzinger, A.; Chenlo Romero, F. (2014). Mathematical models for efficiency of inverters used in grid connected photovoltaic systems. Renewable and Sustainable Energy Reviews 34, 578–587, doi:10.1016/j.rser.2014.03.047.

Source code in src/simses/model/converter/rampinelli.py
class Rampinelli:
    """Generic parametric PV-inverter loss family.

    Efficiency curve of the form
    ``η(p) = p / (p + K0 + K1·p + K2·p²)``
    where ``p`` is the magnitude of normalised power (p.u. of the
    converter's rated max power). Three-parameter extension of the
    Notton form — the extra linear term lets the fit capture a wider
    range of measured efficiency curves. Symmetric about zero. The fit
    is sampled at 201 points (101 per direction, mirrored about zero)
    at construction and interpolated at runtime, so ``ac_to_dc`` and
    ``dc_to_ac`` remain numerical inverses of each other.

    Source: Rampinelli, G. A.; Krenzinger, A.; Chenlo Romero, F. (2014).
    *Mathematical models for efficiency of inverters used in grid
    connected photovoltaic systems.* Renewable and Sustainable Energy
    Reviews 34, 578–587, doi:10.1016/j.rser.2014.03.047.
    """

    def __init__(self, K0: float, K1: float, K2: float) -> None:
        """
        Args:
            K0: No-load loss coefficient (p.u.).
            K1: Linear loss coefficient (p.u.).
            K2: Quadratic loss coefficient (p.u.).
        """
        p = np.linspace(0, 1, 101)
        eff = np.zeros_like(p)
        eff[1:] = p[1:] / (p[1:] + K0 + K1 * p[1:] + K2 * p[1:] ** 2)

        input_ch = p
        output_ch = input_ch * eff

        input_dch = -p
        output_dch = np.zeros_like(p)
        output_dch[1:] = input_dch[1:] / eff[1:]

        self._inp = np.hstack((input_dch[1:][::-1], 0.0, input_ch[1:])).tolist()
        self._out = np.hstack((output_dch[1:][::-1], 0.0, output_ch[1:])).tolist()

    def ac_to_dc(self, power_ac: float) -> float:
        return interp1d_scalar(power_ac, self._inp, self._out)

    def dc_to_ac(self, power_dc: float) -> float:
        return interp1d_scalar(power_dc, self._out, self._inp)

__init__(K0, K1, K2)

Parameters:

Name Type Description Default
K0 float

No-load loss coefficient (p.u.).

required
K1 float

Linear loss coefficient (p.u.).

required
K2 float

Quadratic loss coefficient (p.u.).

required
Source code in src/simses/model/converter/rampinelli.py
def __init__(self, K0: float, K1: float, K2: float) -> None:
    """
    Args:
        K0: No-load loss coefficient (p.u.).
        K1: Linear loss coefficient (p.u.).
        K2: Quadratic loss coefficient (p.u.).
    """
    p = np.linspace(0, 1, 101)
    eff = np.zeros_like(p)
    eff[1:] = p[1:] / (p[1:] + K0 + K1 * p[1:] + K2 * p[1:] ** 2)

    input_ch = p
    output_ch = input_ch * eff

    input_dch = -p
    output_dch = np.zeros_like(p)
    output_dch[1:] = input_dch[1:] / eff[1:]

    self._inp = np.hstack((input_dch[1:][::-1], 0.0, input_ch[1:])).tolist()
    self._out = np.hstack((output_dch[1:][::-1], 0.0, output_ch[1:])).tolist()

BonfiglioliTL4Q

simses.model.converter.bonfiglioli.BonfiglioliTL4Q

Bases: Notton

Bonfiglioli RPS TL-4Q converter — datasheet parameterisation.

Symmetric Notton-form fit with P0 = 0.0072, K = 0.034 measured under manufacturer datasheet conditions.

See :class:BonfiglioliTL4QFieldData for the asymmetric variant parameterised from FCR field data.

Source: F. Müller (M.Sc. thesis, TUM) — Notton fit of the Bonfiglioli RPS TL-4Q datasheet <http://www.docsbonfiglioli.com/pdf_documents/catalogue/VE_CAT_RTL-4Q_STD_ENG-ITA_R00_5_WEB.pdf>_.

Source code in src/simses/model/converter/bonfiglioli.py
class BonfiglioliTL4Q(Notton):
    """Bonfiglioli RPS TL-4Q converter — datasheet parameterisation.

    Symmetric Notton-form fit with ``P0 = 0.0072, K = 0.034`` measured
    under manufacturer datasheet conditions.

    See :class:`BonfiglioliTL4QFieldData` for the asymmetric variant
    parameterised from FCR field data.

    Source: F. Müller (M.Sc. thesis, TUM) — Notton fit of the
    `Bonfiglioli RPS TL-4Q datasheet
    <http://www.docsbonfiglioli.com/pdf_documents/catalogue/VE_CAT_RTL-4Q_STD_ENG-ITA_R00_5_WEB.pdf>`_.
    """

    def __init__(self) -> None:
        super().__init__(P0=0.0072, K=0.034)

BonfiglioliTL4QFieldData

simses.model.converter.bonfiglioli.BonfiglioliTL4QFieldData

Bases: AsymmetricNotton

Bonfiglioli RPS TL-4Q converter — FCR field-data parameterisation.

Asymmetric Notton-form fit measured on frequency containment reserve (FCR) battery systems; reflects real deployment losses including auxiliary consumption. Charge: P0 = 0.00195, K = 0.01349. Discharge: P0 = 0.00292, K = 0.03609.

See :class:BonfiglioliTL4Q for the symmetric datasheet variant.

Source: F. Müller (M.Sc. thesis, TUM) — field fit on FCR BESS deployments of the Bonfiglioli RPS TL-4Q.

Source code in src/simses/model/converter/bonfiglioli.py
class BonfiglioliTL4QFieldData(AsymmetricNotton):
    """Bonfiglioli RPS TL-4Q converter — FCR field-data parameterisation.

    Asymmetric Notton-form fit measured on frequency containment reserve
    (FCR) battery systems; reflects real deployment losses including
    auxiliary consumption. Charge: ``P0 = 0.00195, K = 0.01349``.
    Discharge: ``P0 = 0.00292, K = 0.03609``.

    See :class:`BonfiglioliTL4Q` for the symmetric datasheet variant.

    Source: F. Müller (M.Sc. thesis, TUM) — field fit on FCR BESS
    deployments of the Bonfiglioli RPS TL-4Q.
    """

    def __init__(self) -> None:
        super().__init__(
            charge=(0.00195, 0.01349),
            discharge=(0.00292, 0.03609),
        )

SungrowSC1000TL

simses.model.converter.sungrow.SungrowSC1000TL

Bases: AsymmetricNotton

Sungrow SC1000TL converter — FCR field-data parameterisation.

Asymmetric Notton-form fit, backed by field data from a frequency containment reserve storage system. Charge: P0 = 0.007701864, K = 0.017290859. Discharge: P0 = 0.005511580, K = 0.018772838.

Source: field fit by F. Müller (M.Sc. thesis, TUM) on a Sungrow SC1000TL inverter deployed in an FCR BESS. The thesis also provides Rampinelli and rational-form fits of the same dataset; the Notton fit was the configured default in the legacy simses implementation and is the one reproduced here.

Source code in src/simses/model/converter/sungrow.py
class SungrowSC1000TL(AsymmetricNotton):
    """Sungrow SC1000TL converter — FCR field-data parameterisation.

    Asymmetric Notton-form fit, backed by field data from a frequency
    containment reserve storage system. Charge: ``P0 = 0.007701864,
    K = 0.017290859``. Discharge: ``P0 = 0.005511580, K = 0.018772838``.

    Source: field fit by F. Müller (M.Sc. thesis, TUM) on a Sungrow
    SC1000TL inverter deployed in an FCR BESS. The thesis also provides
    Rampinelli and rational-form fits of the same dataset; the Notton
    fit was the configured default in the legacy simses implementation
    and is the one reproduced here.
    """

    def __init__(self) -> None:
        super().__init__(
            charge=(0.007701864, 0.017290859),
            discharge=(0.005511580, 0.018772838),
        )

SinamicsS120

simses.model.converter.sinamics.SinamicsS120

Siemens Sinamics S120 converter loss model from measured efficiency curves.

Lookup-table model built from a CSV of measured efficiency values for charging (AC→DC) and discharging (DC→AC) at 101 normalised power points. All power arguments are in per-unit of the converter's rated max power.

The CSV carries separate Charging and Discharging curves; in the bundled measurement data they differ by a mean of 0.23% and a maximum of 0.40% (the discharging curve is systematically ~0.2 efficiency-points higher).

Source: Schimpe et al., "Energy efficiency evaluation of grid connection scenarios for stationary battery energy storage systems", Energy Procedia 155 (2018) 77–101, doi:10.1016/j.egypro.2018.11.065.

Source code in src/simses/model/converter/sinamics.py
class SinamicsS120:
    """Siemens Sinamics S120 converter loss model from measured efficiency curves.

    Lookup-table model built from a CSV of measured efficiency values for
    charging (AC→DC) and discharging (DC→AC) at 101 normalised power points.
    All power arguments are in per-unit of the converter's rated max power.

    The CSV carries separate ``Charging`` and ``Discharging`` curves; in the
    bundled measurement data they differ by a mean of 0.23% and a maximum of
    0.40% (the discharging curve is systematically ~0.2 efficiency-points
    higher).

    Source: Schimpe et al., "Energy efficiency evaluation of grid
    connection scenarios for stationary battery energy storage systems",
    Energy Procedia 155 (2018) 77–101, doi:10.1016/j.egypro.2018.11.065.
    """

    def __init__(self, use_discharging_curve: bool = False) -> None:
        """
        Args:
            use_discharging_curve: If ``True``, use the measured
                ``Discharging`` column for the discharge branch. If
                ``False`` (default), use the ``Charging`` column for both
                directions — keeps the model strictly symmetric about zero
                power. Set to ``True`` to preserve the measured
                charge/discharge asymmetry.
        """
        path = os.path.dirname(os.path.abspath(__file__))
        file = os.path.join(path, "data", "sinamics_S120_efficiency.csv")
        df_eff = pd.read_csv(file)  # efficiency curves

        eff_ch = df_eff["Charging"][::10]  # every 10th row of the 1001-row table
        eff_dch = df_eff["Discharging"][::10] if use_discharging_curve else eff_ch

        input_ch = np.linspace(0, 1, 101)
        output_ch = input_ch * eff_ch

        input_dch = np.linspace(0, 1, 101)
        output_dch = input_dch / eff_dch

        self._inp = np.hstack((-input_dch[1:][::-1], 0, input_ch[1:])).tolist()
        self._out = np.hstack((-output_dch[1:][::-1], 0, output_ch[1:])).tolist()

    def ac_to_dc(self, power_ac: float) -> float:
        return interp1d_scalar(power_ac, self._inp, self._out)

    def dc_to_ac(self, power_dc: float) -> float:
        return interp1d_scalar(power_dc, self._out, self._inp)

__init__(use_discharging_curve=False)

Parameters:

Name Type Description Default
use_discharging_curve bool

If True, use the measured Discharging column for the discharge branch. If False (default), use the Charging column for both directions — keeps the model strictly symmetric about zero power. Set to True to preserve the measured charge/discharge asymmetry.

False
Source code in src/simses/model/converter/sinamics.py
def __init__(self, use_discharging_curve: bool = False) -> None:
    """
    Args:
        use_discharging_curve: If ``True``, use the measured
            ``Discharging`` column for the discharge branch. If
            ``False`` (default), use the ``Charging`` column for both
            directions — keeps the model strictly symmetric about zero
            power. Set to ``True`` to preserve the measured
            charge/discharge asymmetry.
    """
    path = os.path.dirname(os.path.abspath(__file__))
    file = os.path.join(path, "data", "sinamics_S120_efficiency.csv")
    df_eff = pd.read_csv(file)  # efficiency curves

    eff_ch = df_eff["Charging"][::10]  # every 10th row of the 1001-row table
    eff_dch = df_eff["Discharging"][::10] if use_discharging_curve else eff_ch

    input_ch = np.linspace(0, 1, 101)
    output_ch = input_ch * eff_ch

    input_dch = np.linspace(0, 1, 101)
    output_dch = input_dch / eff_dch

    self._inp = np.hstack((-input_dch[1:][::-1], 0, input_ch[1:])).tolist()
    self._out = np.hstack((-output_dch[1:][::-1], 0, output_ch[1:])).tolist()

SinamicsS120Fit

simses.model.converter.sinamics.SinamicsS120Fit

Siemens Sinamics S120 converter loss model from a parametric fit.

Symmetric loss model of the form loss(p) = k0 × (1 − exp(−m0·|p|)) + k1·|p| + k2·|p|² where p is normalised power (p.u. of the converter's rated max power). The coefficients are a least-squares fit to the same measurement data used by :class:SinamicsS120. At construction the fit is sampled at 101 points and interpolated at runtime — so the evaluation cost matches :class:SinamicsS120; the two variants differ only in how their interpolation points are generated (parametric fit vs measured CSV).

Source: Schimpe et al., "Energy efficiency evaluation of grid connection scenarios for stationary battery energy storage systems", Energy Procedia 155 (2018) 77–101, doi:10.1016/j.egypro.2018.11.065.

Source code in src/simses/model/converter/sinamics.py
class SinamicsS120Fit:
    """Siemens Sinamics S120 converter loss model from a parametric fit.

    Symmetric loss model of the form
    ``loss(p) = k0 × (1 − exp(−m0·|p|)) + k1·|p| + k2·|p|²``
    where ``p`` is normalised power (p.u. of the converter's rated max
    power). The coefficients are a least-squares fit to the same
    measurement data used by :class:`SinamicsS120`. At construction the
    fit is sampled at 101 points and interpolated at runtime — so the
    evaluation cost matches :class:`SinamicsS120`; the two variants
    differ only in how their interpolation points are generated
    (parametric fit vs measured CSV).

    Source: Schimpe et al., "Energy efficiency evaluation of grid
    connection scenarios for stationary battery energy storage systems",
    Energy Procedia 155 (2018) 77–101, doi:10.1016/j.egypro.2018.11.065.
    """

    def __init__(self) -> None:
        # self.params = {"k0": 0.00601144, "k1": 0.00863612, "k2": 0.01195589, "m0": 97}
        params = (0.00601144, 0.00863612, 0.01195589, 97)
        k0, k1, k2, m0 = params

        def loss(power):
            power_factor = np.abs(power)
            return (
                k0 * (1 - np.exp(-m0 * power_factor))  # constant loss + activation
                + k1 * power_factor
                + k2 * power_factor**2
            )

        input_ch = np.linspace(0, 1, 101)
        output_ch = input_ch - loss(input_ch)

        input_dch = -np.linspace(0, 1, 101)
        output_dch = input_dch - loss(input_dch)

        self._inp = np.hstack((input_dch[1:][::-1], 0, input_ch[1:])).tolist()
        self._out = np.hstack((output_dch[1:][::-1], 0, output_ch[1:])).tolist()

    def ac_to_dc(self, power_ac: float) -> float:
        return interp1d_scalar(power_ac, self._inp, self._out)

    def dc_to_ac(self, power_dc: float) -> float:
        return interp1d_scalar(power_dc, self._out, self._inp)

Degradation Models

SonyLFP Calendar Degradation

simses.model.degradation.sony_lfp_calendar.SonyLFPCalendarDegradation

Bases: CalendarDegradation

Calendar aging for Sony/Murata LFP cells (Naumann 2018).

Capacity loss follows a sqrt(t) model with virtual time continuation. Resistance increase is linear in time.

This model is stateless: accumulated values are owned by the :class:~simses.degradation.degradation.DegradationModel and passed in on every call.

Source code in src/simses/model/degradation/sony_lfp_calendar.py
class SonyLFPCalendarDegradation(CalendarDegradation):
    """Calendar aging for Sony/Murata LFP cells (Naumann 2018).

    Capacity loss follows a sqrt(t) model with virtual time continuation.
    Resistance increase is linear in time.

    This model is **stateless**: accumulated values are owned by the
    :class:`~simses.degradation.degradation.DegradationModel` and passed in
    on every call.
    """

    def update_capacity(self, state: BatteryState, dt: float, accumulated_qloss: float) -> float:
        if dt == 0.0:
            return 0.0

        T_K = state.T + 273.15
        T_REF_K = T_REF + 273.15
        k_T_q = K_REF_QLOSS * math.exp(-EA_QLOSS / R * (1.0 / T_K - 1.0 / T_REF_K))
        k_soc_q = C_QLOSS * (state.soc - 0.5) ** 3 + D_QLOSS
        stress_q = k_T_q * k_soc_q

        if stress_q > 0.0:
            virtual_time = (accumulated_qloss / stress_q) ** 2
            delta_q = stress_q * math.sqrt(virtual_time + dt) - accumulated_qloss
        else:
            delta_q = 0.0

        return delta_q

    def update_resistance(self, state: BatteryState, dt: float) -> float:
        if dt == 0.0:
            return 0.0

        T_K = state.T + 273.15
        T_REF_K = T_REF + 273.15
        k_T_r = K_REF_RINC * math.exp(-EA_RINC / R * (1.0 / T_K - 1.0 / T_REF_K))
        k_soc_r = C_RINC * (state.soc - 0.5) ** 2 + D_RINC
        return k_T_r * k_soc_r * dt

SonyLFP Cyclic Degradation

simses.model.degradation.sony_lfp_cyclic.SonyLFPCyclicDegradation

Bases: CyclicDegradation

Cyclic aging for Sony/Murata LFP cells (Naumann 2020).

Capacity loss follows a sqrt(FEC) model with virtual FEC continuation. Resistance increase is linear in FEC.

This model is stateless: accumulated values are owned by the :class:~simses.degradation.degradation.DegradationModel and passed in on every call.

Source code in src/simses/model/degradation/sony_lfp_cyclic.py
class SonyLFPCyclicDegradation(CyclicDegradation):
    """Cyclic aging for Sony/Murata LFP cells (Naumann 2020).

    Capacity loss follows a sqrt(FEC) model with virtual FEC continuation.
    Resistance increase is linear in FEC.

    This model is **stateless**: accumulated values are owned by the
    :class:`~simses.degradation.degradation.DegradationModel` and passed in
    on every call.
    """

    def update_capacity(self, state: BatteryState, half_cycle: HalfCycle, accumulated_qloss: float) -> float:
        delta_fec = half_cycle.full_equivalent_cycles
        if delta_fec == 0.0:
            return 0.0

        k_crate_q = A_QLOSS * half_cycle.c_rate + B_QLOSS
        k_dod_q = C_QLOSS * (half_cycle.depth_of_discharge - 0.6) ** 3 + D_QLOSS
        stress_q = k_crate_q * k_dod_q

        if stress_q > 0.0:
            virtual_fec = (accumulated_qloss * 100.0 / stress_q) ** 2
            delta_q = stress_q * math.sqrt(virtual_fec + delta_fec) / 100.0 - accumulated_qloss
        else:
            delta_q = 0.0

        return delta_q

    def update_resistance(self, state: BatteryState, half_cycle: HalfCycle) -> float:
        delta_fec = half_cycle.full_equivalent_cycles
        if delta_fec == 0.0:
            return 0.0

        k_crate_r = A_RINC * half_cycle.c_rate + B_RINC
        k_dod_r = C_RINC * (half_cycle.depth_of_discharge - 0.5) ** 3 + D_RINC
        return k_crate_r * k_dod_r * delta_fec / 100.0

Thermal Container Presets

simses.model.thermal.containers

Predefined ContainerProperties instances for common shipping container sizes.