Source code for qpdk.simulation.vacask.netlist
"""VACASK netlists and the value formatting used to write them."""
from __future__ import annotations
import numbers
import re
import textwrap
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from functools import cache
from pathlib import Path
from typing import Any
import jinja2
import numpy as np
VACASK_MODELS_DIR = Path(__file__).parents[1] / "vacask_models"
VACASK_TEMPLATES_DIR = Path(__file__).parents[1] / "vacask_templates"
_ANALYSIS = re.compile(r"^\s*analysis\s+(\S+)", re.MULTILINE)
def _dedent(text: str) -> str:
"""Remove common indentation and surrounding blank lines."""
return textwrap.dedent(text).strip()
@cache
def _templates() -> jinja2.Environment:
"""Jinja2 environment for the netlist templates."""
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(VACASK_TEMPLATES_DIR),
undefined=jinja2.StrictUndefined,
trim_blocks=True,
lstrip_blocks=True,
keep_trailing_newline=True,
autoescape=False, # ruff: ignore[jinja2-autoescape-false] # netlists, not HTML
)
env.filters["dedent"] = _dedent
env.filters["number"] = format_value
return env
[docs]
@dataclass(frozen=True)
class Netlist:
"""A VACASK netlist, rendered from ``vacask_templates/netlist.sim.j2``.
``circuit``, ``control`` and ``subckts`` are VACASK netlist text and are
inserted as-is, after removing their common indentation. The control block always
starts with ``abort always``, so that a failed analysis gives a nonzero
exit code.
Attributes:
title: First line of the netlist.
circuit: Models and instances, in VACASK syntax.
control: Contents of the ``control`` block: options, sweeps and
analyses. Each ``analysis <name> ...`` line produces a result.
loads: Files to load, e.g. ``"capacitor.osdi"`` from the VACASK
installation or ``"josephson_junction.va"``. Verilog-A models
shipped with qpdk are copied next to the netlist automatically.
subckts: Subcircuit definitions, e.g. from :func:`vector_fit_subckt`.
Each one is dedented on its own, so they can come from different
sources.
files: Extra files to place next to the netlist, as ``{name: content}``.
A :class:`~pathlib.Path` value is copied; a string is written as text.
"""
title: str
circuit: str = ""
control: str = ""
loads: Sequence[str] = ()
subckts: Sequence[str] = ()
files: Mapping[str, str | Path] = field(default_factory=dict)
@property
def analyses(self) -> list[str]:
"""Names of the analyses in :attr:`control`, in order."""
return _ANALYSIS.findall(self.control)
[docs]
def render(self) -> str:
"""Return the netlist text."""
return _templates().get_template("netlist.sim.j2").render(**vars(self))
[docs]
def all_files(self) -> dict[str, str | Path]:
"""Files to write next to the netlist, including loaded qpdk models."""
files: dict[str, str | Path] = {
name: VACASK_MODELS_DIR / name
for name in self.loads
if (VACASK_MODELS_DIR / name).is_file()
}
files.update(self.files)
return files