Skip to content

Commit

Permalink
Aligning the README.md with the current implementation.
Browse files Browse the repository at this point in the history
Preparing for 4.0 release
Renaming Trace Class used for .raw reads as TraceRead
Adding iteration capability to Datasets
Correcting logging and printout messages
Moving the raw_write tests to its own test file
  • Loading branch information
nunobrum committed Apr 30, 2023
1 parent 971e354 commit cedbd4a
Show file tree
Hide file tree
Showing 16 changed files with 381 additions and 315 deletions.
2 changes: 1 addition & 1 deletion PyLTSpice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Convenience direct imports
from .raw.raw_read import RawRead, SpiceReadException
from .raw.raw_write import RawWrite, Trace as TraceW
from .raw.raw_write import RawWrite, Trace
from .sim.spice_editor import SpiceEditor, SpiceCircuit
from .sim.sim_runner import SimRunner
from .sim.sim_batch import SimCommander
Expand Down
14 changes: 12 additions & 2 deletions PyLTSpice/raw/raw_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ def __str__(self):
def __len__(self):
return len(self.data)

def __iter__(self):
return iter(self.data)

def __getitem__(self, item):
return self.data[item]

def get_wave(self) -> np.array:
"""
:return: Internal data array
Expand Down Expand Up @@ -184,7 +190,7 @@ def get_point(self, n, step: int = 0) -> Union[float, complex]:

def __getitem__(self, item) -> Union[float, complex]:
"""This is only here for compatibility with previous code. """
assert self.step_info is None, "Indexing should not be used with stepped data. Use get_point"
assert self.step_info is None, "Indexing should not be used with stepped data. Use get_point or get_wave"
return self.data.__getitem__(item)

def get_position(self, t, step: int = 0) -> Union[int, float]:
Expand Down Expand Up @@ -231,8 +237,12 @@ def __len__(self):
else:
return self.get_len()

def __iter__(self):
assert self.step_info is None, "Iteration can't be used with stepped data. Use get_wave() method."
return self.data.__iter__()


class Trace(DataSet):
class TraceRead(DataSet):
"""This class is used to represent a trace. It derives from DataSet and implements the additional methods to
support STEPed simulations.
This class is constructed by the get_trace() command.
Expand Down
10 changes: 5 additions & 5 deletions PyLTSpice/raw/raw_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
from typing import Union, List, Tuple, Dict
from pathlib import Path

from .raw_classes import Axis, Trace, DummyTrace, SpiceReadException
from .raw_classes import Axis, TraceRead, DummyTrace, SpiceReadException
from ..utils.detect_encoding import detect_encoding

import numpy as np
Expand Down Expand Up @@ -429,10 +429,10 @@ def __init__(self, raw_filename: str, traces_to_read: Union[str, List[str], Tupl
trace = self.axis
elif (traces_to_read == "*") or (name in traces_to_read):
if has_axis: # Reads data
trace = Trace(name, var_type, self.nPoints, self.axis, numerical_type)
trace = TraceRead(name, var_type, self.nPoints, self.axis, numerical_type)
else:
# If an Operation Point or Transfer Function, only one point per step
trace = Trace(name, var_type, self.nPoints, None, 'real')
trace = TraceRead(name, var_type, self.nPoints, None, 'real')
else:
trace = DummyTrace(name, var_type)

Expand Down Expand Up @@ -560,8 +560,8 @@ def __init__(self, raw_filename: str, traces_to_read: Union[str, List[str], Tupl
print("LOG file not found or problems happened while reading it. Auto-detecting steps")
if has_axis:
number_of_steps = 0
for v in self.axis:
if v == self.axis[0]:
for v in self.axis.data:
if v == self.axis.data[0]:
number_of_steps += 1
else:
number_of_steps = self.nPoints
Expand Down
111 changes: 1 addition & 110 deletions PyLTSpice/raw/raw_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def _consolidate(self):
trace.data = self._interpolate(trace.data, my_axis, new_axis)
for imported_trace in self._imported_data:
new_trace = Trace(imported_trace.name,
self._interpolate(imported_trace.data, imported_trace.axis, new_axis),
self._interpolate(imported_trace.get_wave(), imported_trace.axis.get_wave(), new_axis),
imported_trace.whattype, imported_trace.numerical_type)
self._traces.append(new_trace)
self._traces[0] = Trace(old_axis.name, new_axis,
Expand Down Expand Up @@ -355,112 +355,3 @@ def tobytes_for_trace(trace: Trace):
def tobytes(value):
return value.tobytes()
return tobytes


if __name__ == '__main__':
import numpy as np
from raw_read import RawRead

def test_readme_snippet():
LW = RawWrite(fastacces=False)
tx = Trace('time', np.arange(0.0, 3e-3, 997E-11))
vy = Trace('N001', np.sin(2 * np.pi * tx.data * 10000))
vz = Trace('N002', np.cos(2 * np.pi * tx.data * 9970))
LW.add_trace(tx)
LW.add_trace(vy)
LW.add_trace(vz)
LW.save("teste_snippet1.raw")

def test_trc2raw(): # Convert Teledyne-Lecroy trace files to raw files
f = open(r"Current_Lock_Front_Right_8V.trc")
raw_type = '' # Initialization of parameters that need to be overridden by the file header
wave_size = 0
for line in f:
tokens = line.rstrip('\r\n').split(',')
if len(tokens) == 4:
if tokens[0] == 'Segments' and tokens[2] == 'SegmentSize':
wave_size = int(tokens[1]) * int(tokens[3])
if len(tokens) == 2:
if tokens[0] == 'Time' and tokens[1] == 'Ampl':
raw_type = 'transient'
break
if raw_type == 'transient' and wave_size > 0:
data = np.genfromtxt(f, dtype='float,float', delimiter=',', max_rows=wave_size)
LW = RawWrite()
LW.add_trace(Trace('time', [x[0] for x in data]))
LW.add_trace(Trace('Ampl', [x[1] for x in data]))
LW.save("teste_trc.raw")
f.close()


def test_axis_sync(): # Test axis sync
LW = RawWrite()
tx = Trace('time', np.arange(0.0, 3e-3, 997E-11))
vy = Trace('N001', np.sin(2 * np.pi * tx.data * 10000))
vz = Trace('N002', np.cos(2 * np.pi * tx.data * 9970))
LW.add_trace(tx)
LW.add_trace(vy)
LW.add_trace(vz)
LW.save("teste_w.raw")
LR = RawRead("..\\test_files\\testfile.raw")
LW.add_traces_from_raw(LR, ('V(out)',), force_axis_alignment=True)
LW.save("merge.raw")
test = """
equal = True
for ii in range(len(tx)):
if t[ii] != tx[ii]:
print(t[ii], tx[ii])
equal = False
print(equal)
v = LR.get_trace('N001')
max_error = 1.5e-12
for ii in range(len(vy)):
err = abs(v[ii] - vy[ii])
if err > max_error:
max_error = err
print(v[ii], vy[ii], v[ii] - vy[ii])
print(max_error)
"""

def test_write_ac():
LW = RawWrite()
LR = RawRead("..\\tests\\PI_Filter.raw")
LR1 = RawRead("..\\tests\\PI_Filter_resampled.raw")
LW.add_traces_from_raw(LR, ('V(N002)',))
LW.add_traces_from_raw(LR1, 'V(N002)', rename_format='N002_resampled', force_axis_alignment=True)
LW.flag_fastaccess = False
LW.save("..\\tests\\PI_filter_rewritten.raw")
LW.flag_fastaccess = True
LW.save("..\\tests\\PI_filter_rewritten_fast.raw")

def test_write_tran():
LR = RawRead("..\\tests\\TRAN - STEP.raw")
LW = RawWrite()
LW.add_traces_from_raw(LR, ('V(out)', 'I(C1)'))
LW.flag_fastaccess = False
LW.save("..\\tests\\TRAN - STEP0_normal.raw")
LW.flag_fastaccess = True
LW.save("..\\tests\\TRAN - STEP0_fast.raw")

def test_combine_tran():
LW = RawWrite()
for tag, raw in (
("AD820_15", "../tests/Batch_Test_AD820_15.raw"),
# ("AD820_10", "../tests/Batch_Test_AD820_10.raw"),
("AD712_15", "../tests/Batch_Test_AD712_15.raw"),
# ("AD712_10", "../tests/Batch_Test_AD712_10.raw"),
# ("AD820_5", "../tests/Batch_Test_AD820_5.raw"),
# ("AD712_5", "../tests/Batch_Test_AD712_5.raw"),
):
LR = RawRead(raw)
LW.add_traces_from_raw(LR, ("V(out)", "I(R1)"), rename_format="{}_{tag}", tag=tag, force_axis_alignment=True)
LW.flag_fastaccess = False
LW.save("../tests/Batch_Test_Combine.raw")


# test_readme_snippet()
# test_axis_sync()
# test_write_ac()
# test_write_tran()
test_combine_tran()
4 changes: 2 additions & 2 deletions PyLTSpice/sim/ltspice_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def create_netlist(cls, circuit_file: Union[str, Path]) -> Path:
if sys.platform == 'darwin':
NotImplementedError("In this platform LTSpice doesn't have netlist generation capabilities")
cmd_netlist = cls.spice_exe + ['-netlist'] + [circuit_file.as_posix()]
print(f'Creating netlist file from "{circuit_file}"', end='...')
# print(f'Creating netlist file from "{circuit_file}"', end='...')
error = run_function(cmd_netlist)

if error == 0:
Expand All @@ -173,6 +173,6 @@ def create_netlist(cls, circuit_file: Union[str, Path]) -> Path:
print("OK")
return netlist
msg = "Failed to create netlist"
print(msg)
# print(msg)
logging.error(msg)
raise RuntimeError(msg)
36 changes: 13 additions & 23 deletions PyLTSpice/sim/sim_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ def __init__(self, parallel_sims: int = 4, timeout=None, verbose=True, output_fo
self.logger.setLevel(logging.INFO)
# redirect this logger to a file.
self.logger.addHandler(logging.FileHandler('SimRunner.log', mode='w'))
# if verbose is true, all log messages are also printed to the console
if verbose:
self.logger.addHandler(logging.StreamHandler())
self.logger.info("SimRunner started")

self.runno = 0 # number of total runs
Expand Down Expand Up @@ -272,21 +275,12 @@ def create_netlist(self, asc_file: Union[str, Path]):
if not isinstance(asc_file, Path):
asc_file = Path(asc_file)
if asc_file.suffix == '.asc':
netlist_file = asc_file.with_suffix('.net')
if self.verbose:
self.logger.info("Creating Netlist")
retcode = self.simulator.create_netlist(asc_file)
if retcode == 0 and netlist_file.exists():
if self.verbose:
self.logger.info("The Netlist was successfully created")
netlist_file = self._to_output_folder(netlist_file, copy=False)
self.workfiles.append(netlist_file)
return netlist_file
else:
self.logger.error("Unable to create Netlist")
if self.verbose:
self.logger.info("Unable to create the Netlist from %s" % asc_file)
return None
return self.simulator.create_netlist(asc_file)
else:
self.logger.info("Unable to create the Netlist from %s" % asc_file)
return None

def _prepare_sim(self, netlist: Union[str, Path, SpiceEditor], run_filename: str):
"""Internal function"""
Expand Down Expand Up @@ -493,28 +487,24 @@ def file_cleanup(self):
# Delete the log file if exists
logfile = workfile.with_suffix('.log')
if logfile.exists():
self.logger.info("Deleting", logfile)
self.logger.info("Deleting..." + logfile.name)
logfile.unlink()
# Delete the raw file if exists
rawfile = workfile.with_suffix('.raw')
if rawfile.exists():
self.logger.info("Deleting", rawfile)
self.logger.info("Deleting..." + rawfile.name)
rawfile.unlink()

# Delete the op.raw file if exists
oprawfile = workfile.with_suffix('.op.raw')
if oprawfile.exists():
self.logger.info("Deleting", oprawfile)
self.logger.info("Deleting..." + oprawfile.name)
oprawfile.unlink()

# Delete the file
self.logger.info("Deleting", workfile)
self.logger.info("Deleting..." + workfile.name)
workfile.unlink()

def __call__(self, timeout=0):
self._timeout = timeout
return self

def __iter__(self):
return self

Expand All @@ -541,5 +531,5 @@ def __next__(self):
raise StopIteration

sleep(0.2) # Go asleep for a sec
if self._timeout > 0 and ((clock() - t0) > self._timeout):
raise SimRunnerTimeoutError(f"Exceeded {self._timeout} seconds waiting for tasks to finish")
if self.timeout and ((clock() - t0) > self.timeout):
raise SimRunnerTimeoutError(f"Exceeded {self.timeout} seconds waiting for tasks to finish")
Loading

0 comments on commit cedbd4a

Please sign in to comment.