diff --git a/PyLTSpice/__init__.py b/PyLTSpice/__init__.py index f105538..40e7ae7 100644 --- a/PyLTSpice/__init__.py +++ b/PyLTSpice/__init__.py @@ -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 diff --git a/PyLTSpice/raw/raw_classes.py b/PyLTSpice/raw/raw_classes.py index b6e7505..d0ac480 100644 --- a/PyLTSpice/raw/raw_classes.py +++ b/PyLTSpice/raw/raw_classes.py @@ -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 @@ -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]: @@ -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. diff --git a/PyLTSpice/raw/raw_read.py b/PyLTSpice/raw/raw_read.py index 58ea1a7..485b298 100644 --- a/PyLTSpice/raw/raw_read.py +++ b/PyLTSpice/raw/raw_read.py @@ -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 @@ -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) @@ -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 diff --git a/PyLTSpice/raw/raw_write.py b/PyLTSpice/raw/raw_write.py index dfa4332..7f13056 100644 --- a/PyLTSpice/raw/raw_write.py +++ b/PyLTSpice/raw/raw_write.py @@ -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, @@ -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() diff --git a/PyLTSpice/sim/ltspice_simulator.py b/PyLTSpice/sim/ltspice_simulator.py index 905e432..26572c6 100644 --- a/PyLTSpice/sim/ltspice_simulator.py +++ b/PyLTSpice/sim/ltspice_simulator.py @@ -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: @@ -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) \ No newline at end of file diff --git a/PyLTSpice/sim/sim_runner.py b/PyLTSpice/sim/sim_runner.py index 9ce3b76..322294f 100644 --- a/PyLTSpice/sim/sim_runner.py +++ b/PyLTSpice/sim/sim_runner.py @@ -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 @@ -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""" @@ -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 @@ -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") diff --git a/README.md b/README.md index 0348ea4..2a48009 100644 --- a/README.md +++ b/README.md @@ -5,48 +5,52 @@ PySpicer is a toolchain of python utilities design to interact with LTSpice and ## What is contained in this repository ## * __LTSteps.py__ -An utility that extracts from LTSpice output files data, and formats it for import in a spreadsheet, such like Excel or Calc. + An utility that extracts from LTSpice output files data, and formats it for import in a spreadsheet, such like Excel + or Calc. * __raw_read.py__ -A pure python class that serves to read raw files into a python class. + A pure python class that serves to read raw files into a python class. * __raw_write.py__ -A class to write RAW files that can be read by LTSpice Wave Application. + A class to write RAW files that can be read by LTSpice Wave Application. * __Histogram.py__ -A python script that uses numpy and matplotlib to create an histogram and calculate the sigma deviations. This is useful for Monte-Carlo analysis. + A python script that uses numpy and matplotlib to create an histogram and calculate the sigma deviations. This is + useful for Monte-Carlo analysis. * __sim_batch.py__ -This is a script to launch Spice Simulations. This is useful because: + This is a script to launch Spice Simulations. This is useful because: - Can overcome the limitation of only stepping 3 parameters - Different types of simulations .TRAN .AC .NOISE can be run in a single batch - The RAW Files are smaller and easier to treat - When used with the RawRead.py and LTSteps.py, validation of the circuit can be done automatically. - Different models can be simulated in a single batch, by using the following instructions: - - `set_element_model('D1', '1N4148') # Replaces the Diode D1 with the model 1N4148 ` - - `set_component_value('R2', '33k') # Replaces the value of R2 by 33k` - - `set_parameters(run=1, TEMP=80) # Creates or updates the netlist to have .PARAM run=1 or .PARAM TEMP=80` - - `add_instructions(".STEP run -1 1023 1", ".dc V1 -5 5") ` - - `remove_instruction(".STEP run -1 1023 1") # Removes previously added instruction` - - `reset_netlist() # Resets all edits done to the netlist.` + - `set_element_model('D1', '1N4148') # Replaces the Diode D1 with the model 1N4148 ` + - `set_component_value('R2', '33k') # Replaces the value of R2 by 33k` + - `set_parameters(run=1, TEMP=80) # Creates or updates the netlist to have .PARAM run=1 or .PARAM TEMP=80` + - `add_instructions(".STEP run -1 1023 1", ".dc V1 -5 5") ` + - `remove_instruction(".STEP run -1 1023 1") # Removes previously added instruction` + - `reset_netlist() # Resets all edits done to the netlist.` - Note: It was only tested with Windows based installations. + Note: It was only tested with Windows based installations. ## How to Install ## -`pip install PyLTSpice ` + +`pip install PyLTSpice ` ### Updating PyLTSpice ### - `pip install --upgrade PyLTSpice ` + +`pip install --upgrade PyLTSpice ` ### Using GITHub ### - `git clone https://github.com/nunobrum/PyLTSpice.git ` +`git clone https://github.com/nunobrum/PyLTSpice.git ` If using this method it would be good to add the path where you cloned the site to python path. - `import sys ` - `sys.path.append() ` +`import sys ` +`sys.path.append() ` ## How to use ## @@ -55,10 +59,12 @@ Here follows a quick outlook on how to use each of the tools. More comprehensive documentation can be found in https://pyltspice.readthedocs.io/en/latest/ ## LICENSE ## + GNU V3 License (refer to the LICENSE file) ### raw_read.py ### + The example below reads the data from a Spice Simulation called "TRAN - STEP.raw" and displays all steps of the "I(R1)" trace in a matplotlib plot @@ -77,89 +83,99 @@ x = LTR.get_trace('time') # Gets the time axis steps = LTR.get_steps() for step in range(len(steps)): # print(steps[step]) - plt.plot(x.get_time_axis(step), IR1.get_wave(step), label=steps[step]) + plt.plot(x.get_wave(step), IR1.get_wave(step), label=steps[step]) plt.legend() # order a legend plt.show() ``` ### raw_write.py ### -The following example writes a RAW file with a 3 milliseconds transient simulation sine with a -10kHz and a cosine with 9.997kHz + +The following example writes a RAW file with a 3 milliseconds transient simulation sine with a 10kHz and a cosine with +9.997kHz + ```python import numpy as np from PyLTSpice import Trace, RawWrite -LW = RawWrite() +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_w.raw") - +LW.save("teste_snippet1.raw") ``` - ### sim_batch.py ### -This module is used to launch LTSPice simulations. Results then can be processed with either the RawRead -or with the LTSteps module to read the log file which can contain .MEAS results. -The script will firstly invoke the LTSpice in command line to generate a netlist, and then this netlist can be -updated directly by the script, in order to change component values, parameters or simulation commands. +This module is used to launch LTSPice simulations. Results then can be processed with either the RawRead or with the +LTSteps module to read the log file which can contain .MEAS results. + +The script will firstly invoke the LTSpice in command line to generate a netlist, and then this netlist can be updated +directly by the script, in order to change component values, parameters or simulation commands. Here follows an example of operation. ```python -import os -from PyLTSpice import SimCommander - -def processing_data(raw_file, log_file): - print("Handling the simulation data of %s, log file %s" % (raw_file, log_file)) +from PyLTSpice import SimRunner +from PyLTSpice import SpiceEditor # select spice model -LTC = SimCommander("Batch_Test.asc") +LTC = SimRunner(output_folder='./temp') +LTC.create_netlist('Batch_Test.asc') +netlist = SpiceEditor('Batch_Test.net') # set default arguments -LTC.set_parameters(res=0, cap=100e-6) -LTC.set_component_value('R2', '2k') -LTC.set_component_value('R1', '4k') -LTC.set_element_model('V3', "SINE(0 1 3k 0 0 0)") -# define simulation -LTC.add_instructions( - "; Simulation settings", - ".param run = 0" +netlist.set_parameters(res=0, cap=100e-6) +netlist.set_component_value('R2', '2k') # Modifying the value of a resistor +netlist.set_component_value('R1', '4k') +netlist.set_element_model('V3', "SINE(0 1 3k 0 0 0)") # Modifying the +netlist.set_component_value('XU1:C2', 20e-12) # modifying a define simulation +netlist.add_instructions( + "; Simulation settings", + ".param run = 0" ) for opamp in ('AD712', 'AD820'): - LTC.set_element_model('XU1', opamp) + netlist.set_element_model('XU1', opamp) for supply_voltage in (5, 10, 15): - LTC.set_component_value('V1', supply_voltage) - LTC.set_component_value('V2', -supply_voltage) - # overriding he automatic netlist naming - run_netlist_file = "{}_{}_{}.net".format(LTC.circuit_radic, opamp, supply_voltage) - LTC.run(run_filename=run_netlist_file, callback=processing_data) - - -LTC.reset_netlist() -LTC.add_instructions( - "; Simulation settings", - ".ac dec 30 10 1Meg", - ".meas AC Gain MAX mag(V(out)) ; find the peak response and call it ""Gain""", - ".meas AC Fcut TRIG mag(V(out))=Gain/sqrt(2) FALL=last" + netlist.set_component_value('V1', supply_voltage) + netlist.set_component_value('V2', -supply_voltage) + print("simulating OpAmp", opamp, "Voltage", supply_voltage) + LTC.run(netlist) + +for raw, log in LTC: + print("Raw file: %s, Log file: %s" % (raw, log)) + # do something with the data + # raw_data = RawRead(raw) + # log_data = LTSteps(log) + # ... + +netlist.reset_netlist() +netlist.add_instructions( + "; Simulation settings", + ".ac dec 30 10 1Meg", + ".meas AC Gain MAX mag(V(out)) ; find the peak response and call it ""Gain""", + ".meas AC Fcut TRIG mag(V(out))=Gain/sqrt(2) FALL=last" ) -LTC.run() -LTC.wait_completion() +# Sim Statistics +print('Successful/Total Simulations: ' + str(LTC.okSim) + '/' + str(LTC.runno)) + +enter = input("Press enter to delete created files") +if enter == '': + LTC.file_cleanup() # Sim Statistics print('Successful/Total Simulations: ' + str(LTC.okSim) + '/' + str(LTC.runno)) ``` ### LTSteps.py ### -This module defines a class that can be used to parse LTSpice log files where the information about .STEP information is written. -There are two possible usages of this module, either programmatically by importing the module and then accessing data through the -class as exemplified here: + +This module defines a class that can be used to parse LTSpice log files where the information about .STEP information is +written. There are two possible usages of this module, either programmatically by importing the module and then +accessing data through the class as exemplified here: ```python from PyLTSpice.LTSteps import LTSpiceLogReader @@ -182,14 +198,16 @@ print("Total number of measures found :", data.measure_count) ``` The second possibility is to use the module directly on the command line - `python -m PyLTSpice.LTSteps ` - The can be either be a log file (.log), a data export file (.txt) or a measurement output file (.meas) - This will process all the data and export it automatically into a text file with the extension (tlog, tsv, tmeas) - where the data read is formatted into a more convenient tab separated format. - In case the is not provided, the script will scan the directory and process the newest log, txt or out file found. +`python -m PyLTSpice.LTSteps ` +The can be either be a log file (.log), a data export file (.txt) or a measurement output file (.meas) +This will process all the data and export it automatically into a text file with the extension (tlog, tsv, tmeas) +where the data read is formatted into a more convenient tab separated format. In case the is not provided, the +script will scan the directory and process the newest log, txt or out file found. ### Histogram.py ### + This module uses the data inside on the filename to produce an histogram image. + ``` Usage: Histogram.py [options] LOG_FILE TRACE @@ -217,9 +235,32 @@ Options: Name of the image File. extension 'png' ``` +### rawconvert.py ### + +A tool to convert .raw files into csv or Excel files. + +``` +Usage: raw_convert.py [options] + +Options: + --version show program's version number and exit + -h, --help show this help message and exit + -o FILE, --output=FILE + Output file name. Use .csv for CSV output, .xlsx for + Excel output + -c, --clipboard Output to clipboard + -v, --verbose Verbose output + -s SEPARATOR, --sep=SEPARATOR + Value separator for CSV output. Default: "\t" + Example: -d ";" + + +``` + ### SemiDevOpReader.py ### -This module is used to read from LTSpice log files Semiconductor Devices Operating Point Information. -A more detailed documentation is directly included in the source file docstrings. + +This module is used to read from LTSpice log files Semiconductor Devices Operating Point Information. A more detailed +documentation is directly included in the source file docstrings. ## To whom do I talk to? ## @@ -228,110 +269,123 @@ A more detailed documentation is directly included in the source file docstrings * Alternative contact : nuno.brum@gmail.com ## History ## + +* Version 4.0.0\ + Separating the SimCommander into two separate classes, one for the spice netlist editing (SpiceEditor) and another for + the simulation execution (SimRunner).\ + Implementing simulation server to allow for remote simulation execution and the respective client.\ + Supporting Wiggler element in the new LTSpiceXVII.\ + Renaming all files into lowercase.\ + Creating Error classes for better error handling.\ + Adding support for other simulators (ex: ngspice) where the simulator is defined by a class. This + support class needs to be a subclass of the abstract class Simulator.\ + Enormous improvement in the documentation of the code. + * Version 3.0\ -Eliminating the LTSpice prefixes from files and classes.\ -Adopting the lowercase convention for filenames. + Eliminating the LTSpice prefixes from files and classes.\ + Adopting the lowercase convention for filenames. * Version 2.3.1\ -Bug fix on the parameter replacement + Bug fix on the parameter replacement * Version 2.3\ -Supporting the creation of RAW Noise Analysis\ -Bug Fixes (See GitHub Log) + Supporting the creation of RAW Noise Analysis\ + Bug Fixes (See GitHub Log) * Version 2.2\ -Making numpy as an requirement and eliminating all code that avoided the use of numpy\ -Using new packaging tool\ -Fixes on the LTSpice_RawWrite\ -Fixes in the handling of stepped operating point simulations + Making numpy as an requirement and eliminating all code that avoided the use of numpy\ + Using new packaging tool\ + Fixes on the LTSpice_RawWrite\ + Fixes in the handling of stepped operating point simulations * Version 2.1\ -Adopting minimum python version 3.7\ -Starting to use unit tests to validate all modules and improving testbenches\ -Compatibility with NGSpice\ -Avoiding the use of setup.py as per PEP517 and PEP518\ -Bug Fixes (See GitHub log for more information)\ -Improvements on the management of stepped data in the LTSpice_RawRead.py + Adopting minimum python version 3.7\ + Starting to use unit tests to validate all modules and improving testbenches\ + Compatibility with NGSpice\ + Avoiding the use of setup.py as per PEP517 and PEP518\ + Bug Fixes (See GitHub log for more information)\ + Improvements on the management of stepped data in the LTSpice_RawRead.py * Version 2.0.2\ -Improvements on Encoding detection + Improvements on Encoding detection * Version 2.0\ -International Support using the correct encoding when loading log files.\ -Code Optimizations on the LTSpice_RawReader that allow faster data loading.\ -Improving the functionality on the LTSpice_RawWriter.py\ -Adding support to editing components inside subcircuits (.subckt)\ -Supporting resistors with Model Definitions\ -Fixing problem with LTSpiceLogReader that would return messed up data\ -Fixing problem with replacing the file extension in certain names\ -Correcting problem with deprecations on the numpy functions used by the Histogram.py\ -Adding back the README.md that somehow was deleted + International Support using the correct encoding when loading log files.\ + Code Optimizations on the LTSpice_RawReader that allow faster data loading.\ + Improving the functionality on the LTSpice_RawWriter.py\ + Adding support to editing components inside subcircuits (.subckt)\ + Supporting resistors with Model Definitions\ + Fixing problem with LTSpiceLogReader that would return messed up data\ + Fixing problem with replacing the file extension in certain names\ + Correcting problem with deprecations on the numpy functions used by the Histogram.py\ + Adding back the README.md that somehow was deleted * Version 1.9\ -Adding support for µ character in the SpiceEditor.\ -Adding get_component_floatvalue() method in the netlist manipulating class that handles the conversion of - numeric fields into a float. This function takes into account the engineering qualifiers 'k' for kilos, 'm' or milis, + Adding support for µ character in the SpiceEditor.\ + Adding get_component_floatvalue() method in the netlist manipulating class that handles the conversion of numeric + fields into a float. This function takes into account the engineering qualifiers 'k' for kilos, 'm' or milis, 'u' or 'µ' for microns, 'n' for nanos, 'f' for femtos and 'Meg' for Megas. * Version 1.8\ -Uniforming License reference across files and improvements on the documentation\ -An enormous and wholehearted thanks to Logan Herrera (lpherr) for the improvements in the documentation.\ -Bugfix on the add_LTspiceRunCmdLineSwitches() ; Supporting .param name value format\ -Allowing the LTSpiceRawRead to proceed when the log file can't be found or when there are problems reading it. + Uniforming License reference across files and improvements on the documentation\ + An enormous and wholehearted thanks to Logan Herrera (lpherr) for the improvements in + the documentation.\ + Bugfix on the add_LTspiceRunCmdLineSwitches() ; Supporting .param name value format\ + Allowing the LTSpiceRawRead to proceed when the log file can't be found or when there are problems reading it. * Version 1.7\ -Running in Linux under wine is now possible + Running in Linux under wine is now possible * Version 1.6\ -Adding LTSpice_RawWrite. Adding documentation. + Adding LTSpice_RawWrite. Adding documentation. * Version 1.5\ -Small fixes and improvements on the class usage. No added features + Small fixes and improvements on the class usage. No added features * Version 1.4 \ -Adding the LTSpice_SemiDevOpReader module\ -Re-enabling the Histogram functions which where disabled by mistake. + Adding the LTSpice_SemiDevOpReader module\ + Re-enabling the Histogram functions which where disabled by mistake. * Version 1.3 \ -Bug fixes on the SpiceEditor Class + Bug fixes on the SpiceEditor Class * Version 1.2 \ -README.md: -Adding link to readthedocs documentation\ -All files: -Comprehensive documentation on how to use each module + README.md: + Adding link to readthedocs documentation\ + All files: + Comprehensive documentation on how to use each module * Version 1.1\ -README.md: -Updated the description\ -LTSpiceBatch.py: -Corrected the name of the returned raw file.\ -Added comments throughout the code and cleanup + README.md: + Updated the description\ + LTSpiceBatch.py: + Corrected the name of the returned raw file.\ + Added comments throughout the code and cleanup * Version 1.0\ -LTSpiceBatch.py: -Implemented an new approach (NOT BACKWARDS COMPATIBLE), that avoids the usage of the sim_settings.inc file. -And allows to modify not only parameters, but also models and even the simulation commands.\ -LTSpice_RawRead.py: -Added the get_time_axis method to the RawRead class to avoid the problems with negative values on -time axis, when 2nd order compression is enabled in LTSpice.\ -LTSteps.py: -Modified the LTSteps so it can also read measurements on log files without any steps done. + LTSpiceBatch.py: + Implemented an new approach (NOT BACKWARDS COMPATIBLE), that avoids the usage of the sim_settings.inc file. And allows + to modify not only parameters, but also models and even the simulation commands.\ + LTSpice_RawRead.py: + Added the get_time_axis method to the RawRead class to avoid the problems with negative values on time axis, when 2nd + order compression is enabled in LTSpice.\ + LTSteps.py: + Modified the LTSteps so it can also read measurements on log files without any steps done. * Version 0.6\ -Histogram.py now has an option to make the histogram directly from values stored in the clipboard + Histogram.py now has an option to make the histogram directly from values stored in the clipboard * Version 0.5\ -The LTSpice_RawReader.py now uses the struc.unpack function for a faster execution + The LTSpice_RawReader.py now uses the struc.unpack function for a faster execution * Version 0.4\ -Added LTSpiceBatch.py to the collection of tools + Added LTSpiceBatch.py to the collection of tools * Version 0.3\ -A version of LTSteps that can be imported to use in a higher level script + A version of LTSteps that can be imported to use in a higher level script * Version 0.2\ -Adding LTSteps.py and Histogram.py + Adding LTSteps.py and Histogram.py * Version 0.1 \ -First commit to the bitbucket repository. + First commit to the bitbucket repository. diff --git a/tests/Batch_Test_1.log b/tests/Batch_Test_1.log index ba8fc05..f696ea8 100644 --- a/tests/Batch_Test_1.log +++ b/tests/Batch_Test_1.log @@ -1,5 +1,8 @@ Circuit: * C:\sandbox\PySpice\tests\Batch_Test.asc +No AC stimulus found: + Set the value of a current or voltage source to "AC 1." + to make it behave as a signal generator for AC analysis. Direct Newton iteration failed to find .op point. (Use ".option noopiter" to skip.) Starting Gmin stepping Gmin = 10 @@ -11,38 +14,54 @@ Gmin = 0.000142725 Gmin = 1.5325e-05 Gmin = 1.6455e-06 Gmin = 1.76685e-07 -Gmin = 1.89714e-08 -Gmin = 2.03704e-09 -Gmin = 2.18725e-10 -Gmin = 2.34854e-11 -Gmin = 2.52173e-12 -Gmin = 2.70769e-13 +vernier = 0.5 +vernier = 0.25 +Gmin = 6.41063e-08 +vernier = 0.125 +vernier = 0.0625 +vernier = 0.0833333 +Gmin = 6.04858e-08 +vernier = 0.111111 +Gmin = 4.7254e-08 +vernier = 0.148148 +vernier = 0.197531 +Gmin = 3.13284e-08 +vernier = 0.263374 +vernier = 0.351165 +Gmin = 1.6399e-08 +vernier = 0.46822 +Gmin = 6.0282e-09 +vernier = 0.624294 +vernier = 0.832392 +Gmin = 1.18541e-09 +vernier = 1 +Gmin = 1.36264e-10 +Gmin = 1.46313e-11 +Gmin = 1.57102e-12 +Gmin = 1.68687e-13 Gmin = 0 Gmin stepping succeeded in finding the operating point. -vout_rms: RMS(v(out))=1.06048 FROM 0 TO 0.001 -vin_rms: RMS(v(in))=0.707048 FROM 0 TO 0.001 -gain: vout_rms/vin_rms=1.49987 -vout1m: v(out)=-0.0013187 at 0.001 -period: time=9.87226e-09 at 9.87226e-09 +Multiply defined .measure result: gain + Each .measure statement needs a unique result name. -Date: Sun Apr 23 22:49:24 2023 -Total elapsed time: 0.126 seconds. +Date: Sun Apr 30 20:47:19 2023 +Total elapsed time: 0.079 seconds. tnom = 27 temp = 27 -method = modified trap -totiter = 2460 -traniter = 2092 -tranpoints = 1047 -accept = 1046 -rejected = 1 -matrix size = 11 -fillins = 2 +method = trap +totiter = 1029 +traniter = 0 +tranpoints = 0 +accept = 0 +rejected = 0 +matrix size = 20 +fillins = 17 solver = Normal -Avg thread counts: 1.3/1.6/1.6/1.3 -Matrix Compiler1: 566 bytes object code size 0.8/0.6/[0.2] -Matrix Compiler2: 1.10 KB object code size 0.3/0.4/[0.2] +Avg thread counts: 2.4/0.0/4.4/1.6 +Matrix Compiler1: 82 opcodes +Matrix Compiler2: off [0.2]/0.4/0.7 diff --git a/tests/PI_Filter.raw b/tests/PI_Filter.raw new file mode 100644 index 0000000..f39c5c5 Binary files /dev/null and b/tests/PI_Filter.raw differ diff --git a/tests/PI_Filter_resampled.raw b/tests/PI_Filter_resampled.raw new file mode 100644 index 0000000..f37f0e9 Binary files /dev/null and b/tests/PI_Filter_resampled.raw differ diff --git a/tests/TRAN - STEP.raw b/tests/TRAN - STEP.raw new file mode 100644 index 0000000..80a735d Binary files /dev/null and b/tests/TRAN - STEP.raw differ diff --git a/tests/batch_test.py b/tests/batch_test.py index 022b13f..668efd0 100644 --- a/tests/batch_test.py +++ b/tests/batch_test.py @@ -1,9 +1,5 @@ -from PyLTSpice.sim.sim_runner import SimRunner -from PyLTSpice.sim.spice_editor import SpiceEditor - -def processing_data(raw_file, log_file): - print("Handling the simulation data of %s, log file %s" % (raw_file, log_file)) - +from PyLTSpice import SimRunner +from PyLTSpice import SpiceEditor # select spice model LTC = SimRunner(output_folder='./temp') @@ -25,12 +21,15 @@ def processing_data(raw_file, log_file): for supply_voltage in (5, 10, 15): netlist.set_component_value('V1', supply_voltage) netlist.set_component_value('V2', -supply_voltage) - # overriding he automatic netlist naming - run_netlist_file = "{}_{}_{}.net".format(netlist.netlist_file.stem, opamp, supply_voltage) print("simulating OpAmp", opamp, "Voltage", supply_voltage) - LTC.run(netlist, callback=processing_data, run_filename=run_netlist_file) + LTC.run(netlist) -LTC.wait_completion() +for raw, log in LTC: + print("Raw file: %s, Log file: %s" % (raw, log)) + # do something with the data + # raw_data = RawRead(raw) + # log_data = LTSteps(log) + # ... netlist.reset_netlist() netlist.add_instructions( @@ -40,12 +39,6 @@ def processing_data(raw_file, log_file): ".meas AC Fcut TRIG mag(V(out))=Gain/sqrt(2) FALL=last" ) - -raw, log = netlist.run("no_callback.net").wait_results() -processing_data(raw, log) - -LTC.wait_completion() - # Sim Statistics print('Successful/Total Simulations: ' + str(LTC.okSim) + '/' + str(LTC.runno)) diff --git a/tests/batch_test3.py b/tests/batch_test3.py index 0a297de..8f7cc8d 100644 --- a/tests/batch_test3.py +++ b/tests/batch_test3.py @@ -3,6 +3,7 @@ from time import sleep from random import random + def processing_data(raw_file, log_file): print("Handling the simulation data of ""%s"", log file ""%s""" % (raw_file, log_file)) time_to_sleep = random( ) * 5 @@ -39,9 +40,9 @@ def processing_data(raw_file, log_file): if use_run_now: runner.run_now(netlist, run_filename=run_netlist_file) else: - runner.run(netlist, run_filename=run_netlist_file) + runner.run(netlist, run_filename=run_netlist_file, callback=processing_data) -for results in runner(4): +for results in runner: print(results) netlist.reset_netlist() diff --git a/tests/batch_test4.py b/tests/batch_test4.py index e947dbd..51ac1a1 100644 --- a/tests/batch_test4.py +++ b/tests/batch_test4.py @@ -40,7 +40,7 @@ def callback(raw_file, log_file): run_netlist_file = "{}_{}_{}.net".format(netlist.netlist_file.stem, opamp, supply_voltage) runner.run(netlist, run_filename=run_netlist_file, callback=CallbackProc) - for results in runner(0.4): + for results in runner: print(results) netlist.reset_netlist() diff --git a/tests/raw_write_tests.py b/tests/raw_write_tests.py new file mode 100644 index 0000000..397e1f0 --- /dev/null +++ b/tests/raw_write_tests.py @@ -0,0 +1,108 @@ + + +import numpy as np +from PyLTSpice import RawRead, Trace, RawWrite + +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("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("PI_Filter.raw") + LR1 = RawRead("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("PI_filter_rewritten.raw") + LW.flag_fastaccess = True + LW.save("PI_filter_rewritten_fast.raw") + +def test_write_tran(): + LR = RawRead("TRAN - STEP.raw") + LW = RawWrite() + LW.add_traces_from_raw(LR, ('V(out)', 'I(C1)')) + LW.flag_fastaccess = False + LW.save("TRAN - STEP0_normal.raw") + LW.flag_fastaccess = True + LW.save("TRAN - STEP0_fast.raw") + +def test_combine_tran(): + LW = RawWrite() + for tag, raw in ( + ("AD820_15", "Batch_Test_AD820_15.raw"), + # ("AD820_10", "Batch_Test_AD820_10.raw"), + ("AD712_15", "Batch_Test_AD712_15.raw"), + # ("AD712_10", "Batch_Test_AD712_10.raw"), + # ("AD820_5", "Batch_Test_AD820_5.raw"), + # ("AD712_5", "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("Batch_Test_Combine.raw") + + +test_readme_snippet() +test_axis_sync() +test_write_ac() +test_write_tran() +test_combine_tran() diff --git a/tests/testfile.raw b/tests/testfile.raw new file mode 100644 index 0000000..0a83fc6 Binary files /dev/null and b/tests/testfile.raw differ