Skip to content

IO API

Placeholder for IO-related API docs.

Trace dataclass

Um traço: nome, unidade (quando existir) e vetor de valores (np.ndarray).

Source code in src/cat/io/raw_reader.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@dataclass(frozen=True)
class Trace:
    """Um traço: nome, unidade (quando existir) e vetor de valores (np.ndarray)."""

    name: str
    unit: str | None
    values: NDArray[Any]
    _complex: NDArray[Any] | None = None  # apenas AC: vetor complex

    def magnitude(self) -> NDArray[Any]:
        if self._complex is not None:
            return cast(NDArray[Any], np.abs(self._complex))
        return cast(NDArray[Any], np.abs(self.values))

    def real(self) -> NDArray[Any]:
        if self._complex is not None:
            return self._complex.real
        return self.values

    def imag(self) -> NDArray[Any]:
        if self._complex is not None:
            return self._complex.imag
        return np.zeros_like(self.values, dtype=float)

    def phase_deg(self) -> NDArray[Any]:
        if self._complex is not None:
            return np.angle(self._complex, deg=True)
        return np.zeros_like(self.values, dtype=float)

TraceSet

Conjunto de traços indexado por nome. O primeiro traço é o eixo X (time/freq).

Acesso

ts["V(out)"] -> Trace ts.x -> Trace (primeira coluna) ts.names -> lista de nomes ts.to_dataframe() -> pandas.DataFrame (se pandas instalado)

Source code in src/cat/io/raw_reader.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class TraceSet:
    """
    Conjunto de traços indexado por nome. O primeiro traço é o eixo X (time/freq).

    Acesso:
        ts["V(out)"] -> Trace
        ts.x -> Trace (primeira coluna)
        ts.names -> lista de nomes
        ts.to_dataframe() -> pandas.DataFrame (se pandas instalado)
    """

    def __init__(self, traces: list[Trace]) -> None:
        if not traces:
            raise ValueError("TraceSet requires at least one trace")
        self._traces = traces
        self._by_name: dict[str, Trace] = {t.name: t for t in traces}

        # valida tamanhos
        n = len(self._traces[0].values)
        for t in self._traces[1:]:
            if len(t.values) != n:
                raise ValueError("All traces must have same length")

    @property
    def x(self) -> Trace:
        return self._traces[0]

    @property
    def names(self) -> list[str]:
        return [t.name for t in self._traces]

    def __getitem__(self, key: str) -> Trace:
        try:
            return self._by_name[key]
        except KeyError as e:
            raise KeyError(f"Trace '{key}' not found. Available: {self.names}") from e

    def to_dataframe(self) -> Any:
        # Evita depender de stubs do pandas: import dinâmico via importlib, tipado como Any
        try:
            pd: Any = importlib.import_module("pandas")
        except Exception as exc:  # pragma: no cover
            raise RuntimeError("pandas is required for to_dataframe()") from exc
        data = {t.name: t.values for t in self._traces}
        return pd.DataFrame(data)

parse_ngspice_ascii_raw(path)

Parser robusto para NGSpice ASCII RAW.

Retorna TraceSet onde a primeira coluna é o eixo X (tipicamente 'time' ou 'frequency').

Source code in src/cat/io/raw_reader.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def parse_ngspice_ascii_raw(path: str) -> TraceSet:
    """
    Parser robusto para NGSpice ASCII RAW.

    Retorna TraceSet onde a primeira coluna é o eixo X (tipicamente 'time' ou 'frequency').
    """
    with open(path, encoding="utf-8", errors="ignore") as f:
        lines = f.read().splitlines()

    meta, i0 = _parse_header(lines)
    nvars = int(meta["nvars"])
    npoints = int(meta["npoints"])
    vars_meta, i1 = _parse_variables(lines, i0, nvars)
    data, complex_cols = _parse_values(lines, i1, nvars, npoints)

    traces: list[Trace] = []
    for j, (name, unit) in enumerate(vars_meta):
        traces.append(
            Trace(
                name=name,
                unit=unit,
                values=data[:, j].copy(),
                _complex=complex_cols[j],
            )
        )
    return TraceSet(traces)

parse_ngspice_ascii_raw_multi(path)

Lê um arquivo ASCII com múltiplos plots (p.ex. .step nativo) e retorna uma lista de TraceSet (um por bloco).

Source code in src/cat/io/raw_reader.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def parse_ngspice_ascii_raw_multi(path: str) -> list[TraceSet]:
    """
    Lê um arquivo ASCII com múltiplos plots (p.ex. .step nativo) e retorna
    uma lista de TraceSet (um por bloco).
    """
    with open(path, encoding="utf-8", errors="ignore") as f:
        lines = f.read().splitlines()

    i = 0
    out: list[TraceSet] = []
    while i < len(lines):
        # procurar início de um bloco (Title:/Plotname:/Variables:)
        # Reutiliza as funções privadas para cada bloco
        # pular linhas vazias
        while i < len(lines) and not lines[i].strip():
            i += 1
        if i >= len(lines):
            break
        # precisa ver se há um cabeçalho válido
        try:
            meta, i0 = _parse_header(lines[i:])
            nvars = int(meta["nvars"])
            npoints = int(meta["npoints"])
            vars_meta, i1 = _parse_variables(lines[i:], i0, nvars)
            data, complex_cols = _parse_values(lines[i:], i1, nvars, npoints)
        except Exception:
            # se não conseguiu, avança uma linha e tenta de novo
            i += 1
            continue

        traces: list[Trace] = []
        for j, (name, unit) in enumerate(vars_meta):
            traces.append(
                Trace(name=name, unit=unit, values=data[:, j].copy(), _complex=complex_cols[j])
            )
        out.append(TraceSet(traces))
        # avançar: i += i1 + npoints ... mas já usamos slices; então mova i para frente
        # tenta achar próximo 'Title:' após o bloco atual
        # heurística simples: move i até encontrar próxima 'Title:' ou EOF
        k = i + i1 + npoints + 4  # + margem
        i = max(i + 1, k)
    return out

parse_ngspice_raw(path)

Dispatcher: se for ASCII, usa parse_ngspice_ascii_raw; se for 'Binary', tenta fallback convertendo para ASCII não invasivo (a depender da geração do arquivo). Por ora, detecta 'Binary' e dispara erro com mensagem clara.

Source code in src/cat/io/raw_reader.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def parse_ngspice_raw(path: str) -> TraceSet:
    """
    Dispatcher: se for ASCII, usa parse_ngspice_ascii_raw; se for 'Binary', tenta
    fallback convertendo para ASCII não invasivo (a depender da geração do arquivo).
    Por ora, detecta 'Binary' e dispara erro com mensagem clara.
    """
    with open(path, "rb") as f:
        head = f.read(256)
    if b"Binary:" in head or b"binary" in head:
        # Implementação binária completa é extensa; orientar uso de ASCII no runner.
        raise NotImplementedError(
            "Binary RAW not supported yet. Configure NGSpice to write ASCII RAW "
            "(set filetype=ascii)."
        )
    # ASCII
    return parse_ngspice_ascii_raw(path)