Skip to content

sources ¤

Parse SPICE independent-source specifications into waveform source settings.

The returned dictionaries use the keyword names of :class:circulax.components.electronic.WaveformVoltageSource and :class:~circulax.components.electronic.WaveformCurrentSource, so an SDK client binds parsed text with WaveformVoltageSource(**parse_source("DC 0.5 SIN(...)")). Numeric arguments use the shared safe expression evaluator with SPICE suffix semantics (M is milli, meg is mega). Unit letters after a suffix, AC magnitudes and unsupported waveform kinds raise :class:NetlistError instead of silently degrading to a DC source.

Functions:

Name Description
parse_sine_waveform

Parse SIN with an explicit frequency; return phase in radians.

parse_source

Parse the value part of a SPICE V/I source card: [DC] value and/or a waveform.

parse_waveform

Parse SIN(...), PULSE(...) or PWL(...) into waveform source settings.

parse_sine_waveform ¤

parse_sine_waveform(waveform: str, dialect: str = 'spice') -> dict[str, float]

Parse SIN with an explicit frequency; return phase in radians.

Source code in circulax/netlist_io/sources.py
def parse_sine_waveform(waveform: str, dialect: str = "spice") -> dict[str, float]:
    """Parse SIN with an explicit frequency; return phase in radians."""
    match = _CALL.fullmatch(waveform.strip())
    if match is None or match["name"].lower() != "sin":
        msg = f"unsupported transient source waveform {waveform!r}"
        raise NetlistError(msg)
    return _sine(match["args"], dialect, require_frequency=True)

parse_source ¤

parse_source(
    spec: str,
    *,
    dialect: str = "spice",
    tstep: float | None = None,
    tstop: float | None = None,
    pwl_points: int | None = None
) -> dict[str, float | tuple[float, ...]]

Parse the value part of a SPICE V/I source card: [DC] value and/or a waveform.

Accepts "1.2", "DC 1.2", "SIN(...)" and "DC 0.5 SIN(...)" (in either order). dc_given preserves whether an operating-point override was supplied. Without one, DC analysis uses the waveform's time-zero value. Transients always initialize from the waveform, independently of an explicit DC override. AC clauses are rejected (source-driven AC is out of scope).

Returns:

Type Description
dict[str, float | tuple[float, ...]]

Keyword settings with kind, dc and dc_given plus waveform fields, ready

dict[str, float | tuple[float, ...]]

for WaveformVoltageSource(**settings) / WaveformCurrentSource(**settings).

@tags circulax-simulation

Source code in circulax/netlist_io/sources.py
def parse_source(
    spec: str,
    *,
    dialect: str = "spice",
    tstep: float | None = None,
    tstop: float | None = None,
    pwl_points: int | None = None,
) -> dict[str, float | tuple[float, ...]]:
    """Parse the value part of a SPICE V/I source card: ``[DC] value`` and/or a waveform.

    Accepts ``"1.2"``, ``"DC 1.2"``, ``"SIN(...)"`` and ``"DC 0.5 SIN(...)"`` (in
    either order). ``dc_given`` preserves whether an operating-point override
    was supplied. Without one, DC analysis uses the waveform's time-zero value.
    Transients always initialize from the waveform, independently of an explicit
    DC override. ``AC`` clauses are rejected (source-driven AC is out of scope).

    Returns:
        Keyword settings with ``kind``, ``dc`` and ``dc_given`` plus waveform fields, ready
        for ``WaveformVoltageSource(**settings)`` / ``WaveformCurrentSource(**settings)``.

    @tags circulax-simulation

    """
    calls = list(_CALL.finditer(spec))
    if len(calls) > 1:
        msg = f"only one transient waveform is allowed per source; got {spec!r}"
        raise NetlistError(msg)
    call = calls[0] if calls else None
    settings: dict[str, float | tuple[float, ...]] = {"kind": WAVE_DC}
    rest = spec
    if call is not None:
        settings, end = _parse_call(spec, call, dialect)
        rest = f"{spec[: call.start()]} {spec[end:]}"
    tokens = _tokens(rest)
    if tokens and tokens[0].lower() == "dc":
        tokens = tokens[1:]
        if not tokens:
            msg = "DC requires a value"
            raise NetlistError(msg)
    if tokens and tokens[0].lower() == "ac":
        msg = "AC source magnitudes are not supported"
        raise NetlistError(msg)
    if len(tokens) > 1:
        msg = f"unsupported source clauses {' '.join(tokens[1:])!r}"
        raise NetlistError(msg)
    # An empty specification is SPICE's zero-valued source (e.g. an ammeter).
    dc_given = float(bool(tokens))
    dc = _number(tokens[0], dialect, "DC") if tokens else 0.0
    settings = _finish_settings(settings, tstep, tstop, pwl_points)
    return {**settings, "dc": dc, "dc_given": dc_given}

parse_waveform ¤

parse_waveform(
    waveform: str,
    *,
    dialect: str = "spice",
    tstep: float | None = None,
    tstop: float | None = None,
    pwl_points: int | None = None
) -> dict[str, float | tuple[float, ...]]

Parse SIN(...), PULSE(...) or PWL(...) into waveform source settings.

Parameters:

Name Type Description Default
waveform str

SPICE waveform text, e.g. "PULSE(0 1 1n 1n 1n 10n 20n)".

required
dialect str

Number-suffix dialect; "spice" reads M as milli.

'spice'
tstep float | None

SPICE TSTEP for zero/omitted PULSE rise and fall times. When absent, the transient solver supplies its initial time step.

None
tstop float | None

SPICE TSTOP for zero/omitted PULSE width and period and SIN frequency 1 / tstop. When absent, the analysis supplies it.

None
pwl_points int | None

When set, emit pwl_t/pwl_v of exactly this length for every kind (PWL padded by repeating its last point, others zeros) so instances of different kinds stack into one batched group. PWL instances with more points raise.

None

Returns:

Type Description
dict[str, float | tuple[float, ...]]

Keyword settings for the waveform source components, including kind.

dict[str, float | tuple[float, ...]]

Fields not needed by the waveform keep their component defaults.

@tags circulax-simulation

Source code in circulax/netlist_io/sources.py
def parse_waveform(
    waveform: str,
    *,
    dialect: str = "spice",
    tstep: float | None = None,
    tstop: float | None = None,
    pwl_points: int | None = None,
) -> dict[str, float | tuple[float, ...]]:
    """Parse ``SIN(...)``, ``PULSE(...)`` or ``PWL(...)`` into waveform source settings.

    Args:
        waveform: SPICE waveform text, e.g. ``"PULSE(0 1 1n 1n 1n 10n 20n)"``.
        dialect: Number-suffix dialect; ``"spice"`` reads ``M`` as milli.
        tstep: SPICE ``TSTEP`` for zero/omitted PULSE rise and fall times.
            When absent, the transient solver supplies its initial time step.
        tstop: SPICE ``TSTOP`` for zero/omitted PULSE width and period and
            SIN frequency ``1 / tstop``. When absent, the analysis supplies it.
        pwl_points: When set, emit ``pwl_t``/``pwl_v`` of exactly this length for
            *every* kind (PWL padded by repeating its last point, others zeros) so
            instances of different kinds stack into one batched group. PWL
            instances with more points raise.

    Returns:
        Keyword settings for the waveform source components, including ``kind``.
        Fields not needed by the waveform keep their component defaults.

    @tags circulax-simulation

    """
    text = waveform.strip()
    match = _CALL.match(text)
    if match is None:
        msg = f"unsupported transient source waveform {waveform!r}"
        raise NetlistError(msg)
    result, end = _parse_call(text, match, dialect)
    tail = text[end:].strip()
    if tail:
        msg = f"unsupported source clauses {tail!r}"
        raise NetlistError(msg)
    return _finish_settings(result, tstep, tstop, pwl_points)