Coverage for src / tracekit / exporters / spice_export.py: 56%
45 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""SPICE PWL export functionality for TraceKit.
3This module provides export to SPICE Piece-Wise Linear (PWL) format for
4use in circuit simulation tools like LTspice, ngspice, Cadence, etc.
7Example:
8 >>> from tracekit.exporters.spice_export import export_pwl
9 >>> export_pwl(trace, "stimulus.pwl")
10 >>> # Use in SPICE: V1 in 0 PWL file=stimulus.pwl
11"""
13from __future__ import annotations
15from pathlib import Path
16from typing import TYPE_CHECKING, Any
18import numpy as np
20from tracekit.core.types import DigitalTrace, WaveformTrace
22if TYPE_CHECKING:
23 from numpy.typing import NDArray
26def export_pwl(
27 data: WaveformTrace | DigitalTrace | NDArray[Any] | tuple[NDArray[Any], NDArray[Any]],
28 path: str | Path,
29 *,
30 time_scale: float = 1.0,
31 amplitude_scale: float = 1.0,
32 time_offset: float = 0.0,
33 amplitude_offset: float = 0.0,
34 precision: int = 12,
35 comment: str | None = None,
36 downsample: int = 1,
37 format_style: str = "ltspice",
38) -> None:
39 """Export data to SPICE PWL (Piece-Wise Linear) format.
41 Creates a PWL file that can be used as a stimulus source in SPICE
42 circuit simulators. The format consists of time-value pairs.
44 Args:
45 data: Data to export. Can be:
46 - WaveformTrace or DigitalTrace
47 - NumPy array (uses trace.time_vector or generates index-based time)
48 - Tuple of (time_array, value_array)
49 path: Output file path.
50 time_scale: Scaling factor for time values (e.g., 1e-9 for ns).
51 amplitude_scale: Scaling factor for amplitude values.
52 time_offset: Offset to add to all time values.
53 amplitude_offset: Offset to add to all amplitude values.
54 precision: Decimal precision for output values.
55 comment: Optional comment to include at top of file.
56 downsample: Downsample factor to reduce file size (1 = no downsampling).
57 format_style: Output format style:
58 - "ltspice": LTspice compatible (time value pairs)
59 - "ngspice": ngspice compatible (same as ltspice)
60 - "hspice": HSPICE compatible (with header)
62 Raises:
63 TypeError: If data type is not supported.
65 Example:
66 >>> # Export as stimulus for simulation
67 >>> export_pwl(trace, "input.pwl")
68 >>> # In LTspice: V1 in 0 PWL file=input.pwl
70 >>> # Scale time to nanoseconds for display
71 >>> export_pwl(trace, "input.pwl", time_scale=1e9)
73 References:
74 EXP-005
75 """
76 path = Path(path)
78 # Extract time and value arrays
79 if isinstance(data, WaveformTrace | DigitalTrace):
80 time = data.time_vector
81 values = data.data
82 elif isinstance(data, tuple) and len(data) == 2: 82 ↛ 84line 82 didn't jump to line 84 because the condition on line 82 was always true
83 time, values = data
84 elif isinstance(data, np.ndarray):
85 # Generate time based on array index (assume 1 unit per sample)
86 time = np.arange(len(data), dtype=np.float64)
87 values = data
88 else:
89 raise TypeError(f"Unsupported data type: {type(data)}")
91 # Apply downsampling
92 if downsample > 1: 92 ↛ 93line 92 didn't jump to line 93 because the condition on line 92 was never true
93 time = time[::downsample]
94 values = values[::downsample]
96 # Apply scaling and offset
97 time = time * time_scale + time_offset
98 values = values * amplitude_scale + amplitude_offset
100 # Write to file
101 with open(path, "w") as f:
102 # Write comment/header
103 if format_style == "hspice": 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 f.write("* HSPICE PWL Data\n")
105 if comment:
106 f.write(f"* {comment}\n")
107 f.write(f"* Points: {len(time)}\n")
108 f.write("*\n")
109 elif comment: 109 ↛ 110line 109 didn't jump to line 110 because the condition on line 109 was never true
110 f.write(f"; {comment}\n")
112 # Write time-value pairs
113 fmt = f"{{:.{precision}g}} {{:.{precision}g}}\n"
114 f.writelines(fmt.format(t, v) for t, v in zip(time, values, strict=False))
117def export_pwl_multi(
118 traces: dict[str, WaveformTrace | DigitalTrace | NDArray[Any]],
119 path: str | Path,
120 *,
121 time_scale: float = 1.0,
122 amplitude_scale: float = 1.0,
123 precision: int = 12,
124 downsample: int = 1,
125) -> None:
126 """Export multiple traces to individual PWL files.
128 Creates separate PWL files for each trace in the dictionary,
129 with filenames based on the dictionary keys.
131 Args:
132 traces: Dictionary mapping signal names to trace data.
133 path: Output directory path.
134 time_scale: Scaling factor for time values.
135 amplitude_scale: Scaling factor for amplitude values.
136 precision: Decimal precision for output values.
137 downsample: Downsample factor to reduce file size.
139 Example:
140 >>> traces = {
141 ... "clk": clock_trace,
142 ... "data": data_trace,
143 ... "reset": reset_trace,
144 ... }
145 >>> export_pwl_multi(traces, "stimuli/")
146 >>> # Creates: stimuli/clk.pwl, stimuli/data.pwl, stimuli/reset.pwl
148 References:
149 EXP-005
150 """
151 path = Path(path)
152 path.mkdir(parents=True, exist_ok=True)
154 for name, data in traces.items():
155 # Sanitize filename
156 safe_name = "".join(c if c.isalnum() or c in "_-" else "_" for c in name)
157 file_path = path / f"{safe_name}.pwl"
159 export_pwl(
160 data,
161 file_path,
162 time_scale=time_scale,
163 amplitude_scale=amplitude_scale,
164 precision=precision,
165 downsample=downsample,
166 comment=f"Signal: {name}",
167 )
170def generate_spice_source(
171 pwl_path: str | Path,
172 node_positive: str = "in",
173 node_negative: str = "0",
174 source_name: str = "V1",
175 source_type: str = "voltage",
176) -> str:
177 """Generate SPICE source definition for a PWL file.
179 Args:
180 pwl_path: Path to PWL file.
181 node_positive: Positive node name.
182 node_negative: Negative node name (usually "0" for ground).
183 source_name: Source instance name.
184 source_type: Source type ("voltage" or "current").
186 Returns:
187 SPICE source definition string.
189 Example:
190 >>> line = generate_spice_source("input.pwl", "in", "0", "V1")
191 >>> print(line)
192 V1 in 0 PWL file=input.pwl
194 References:
195 EXP-005
196 """
197 prefix = "V" if source_type == "voltage" else "I"
199 # Ensure source name starts with correct prefix
200 if not source_name.upper().startswith(prefix):
201 source_name = f"{prefix}{source_name}"
203 return f"{source_name} {node_positive} {node_negative} PWL file={pwl_path}"
206__all__ = [
207 "export_pwl",
208 "export_pwl_multi",
209 "generate_spice_source",
210]