Skip to content

Commit

Permalink
lograwfile
Browse files Browse the repository at this point in the history
  • Loading branch information
nunobrum committed Aug 15, 2023
1 parent 1cb4160 commit ec520e7
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 10 deletions.
2 changes: 1 addition & 1 deletion PyLTSpice/Histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def main():

if options.range is None:
# Automatic calculation of the range
axisXmin = mu - (options.sigma+1)*sd
axisXmin = mu - (options.sigma + 1) * sd
axisXmax = mu + (options.sigma + 1) * sd

if mn < axisXmin:
Expand Down
2 changes: 1 addition & 1 deletion PyLTSpice/editor/asc_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

_logger = logging.getLogger("PyLTSpice.AscEditor")

TEXT_REGEX = re.compile(r"TEXT (\d+)\s+(\d+)\s+(Left|Right|Top|Bottom)\s\d+\s*(?P<type>[!;])(?P<text>.*)",
TEXT_REGEX = re.compile(r"TEXT (-?\d+)\s+(-?\d+)\s+(Left|Right|Top|Bottom)\s\d+\s*(?P<type>[!;])(?P<text>.*)",
re.IGNORECASE)
TEXT_REGEX_X = 1
TEXT_REGEX_Y = 2
Expand Down
6 changes: 6 additions & 0 deletions PyLTSpice/sim/sim_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ def file_cleanup(self):
if logfile.exists():
_logger.info("Deleting..." + logfile.name)
logfile.unlink()

# Delete the log.raw file if exists
lograwfile = workfile.with_suffix('.log.raw')
if lograwfile.exists():
_logger.info("Deleting..." + lograwfile.name)
lograwfile.unlink()
# Delete the raw file if exists
rawfile = workfile.with_suffix('.raw')
if rawfile.exists():
Expand Down
107 changes: 105 additions & 2 deletions PyLTSpice/sim/tookit/sim_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,74 @@
from ...sim.sim_runner import AnyRunner
from ...editor.base_editor import BaseEditor
from ...sim.simulator import Simulator
from ...log.ltsteps import LTSpiceLogReader


class LogFileAnalysis(LTSpiceLogReader):
"""
This is a subclass of LTSpiceLogReader that is used to analyse the log file of a simulation.
The super class constructor is bypassed and only their attributes are initialized
"""
def __init__(self, stepset, dataset, encoding):
self.stepset = stepset
self.dataset = dataset
self.logname = None
self.encoding = encoding
self.step_count = len(stepset)
self.measure_count = len(dataset)

def plot_histogram(self, param, bins=50, normalized=True, sigma=3.0, title=None, image_file=None, **kwargs):
"""
Plots a histogram of the parameter
"""
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt

x = np.array(self.dataset[param], dtype=float)
mu = x.mean()
mn = x.min()
mx = x.max()
sd = np.std(x)
sigmin = mu - sigma*sd
sigmax = mu + sigma*sd

# Automatic calculation of the range
axisXmin = mu - (sigma + 1) * sd
axisXmax = mu + (sigma + 1) * sd

if mn < axisXmin:
axisXmin = mn

if mx > axisXmax:
axisXmax = mx

n, bins, patches = plt.hist(x, bins, density=normalized, facecolor='green', alpha=0.75,
range=(axisXmin, axisXmax))
axisYmax = n.max() * 1.1

if normalized:
# add a 'best fit' line
y = norm.pdf(bins, mu, sd)
l = plt.plot(bins, y, 'r--', linewidth=1)
plt.axvspan(mu - sigma*sd, mu + sigma*sd, alpha=0.2, color="cyan")
plt.ylabel('Distribution [Normalised]')
else:
plt.ylabel('Distribution')
plt.xlabel(param)

if title is None:
fmt = '%g'
title = (r'$\mathrm{Histogram\ of\ %s:}\ \mu='+fmt+r',\ stdev='+fmt+r',\ \sigma=%d$') % (param, mu, sd, sigma)

plt.title(title)

plt.axis([axisXmin, axisXmax, 0, axisYmax ])
plt.grid(True)
if image_file is not None:
plt.savefig(image_file)
else:
plt.show()


class SimAnalysis(object):
Expand Down Expand Up @@ -60,12 +128,47 @@ def runner(self, new_runner: AnyRunner):

def run(self, **kwargs):
"""
Runs the simulations. See runner.run for details on keyword arguments.
Runs the simulations. See runner.run() method for details on keyword arguments.
"""
sim = self.runner.run(self.editor, **kwargs)
self.simulations.append(sim)
self.runner.wait_completion()
if 'callback' in kwargs:
return (sim.callback_return if sim is not None else None for sim in self.simulations)

@wraps(BaseEditor.reset_netlist)
def reset_netlist(self):
self.editor.reset_netlist()
self.num_runs = 0

def cleanup_files(self):
"""Clears all simulation files. Typically used after a simulation run and analysis."""
self.runner.file_cleanup()

def simulation(self, index: int) -> Simulator:
"""Returns a simulation object"""
return self.simulations[index]

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

def read_logfiles(self) -> LogFileAnalysis:
"""Reads the log files and returns a dictionary with the results"""
all_stepset = {}
all_dataset = OrderedDict()
for sim in self.simulations:
if sim is None:
continue
log_results = LTSpiceLogReader(sim.log_file)
for param in log_results.stepset:
if param not in all_stepset:
all_stepset[param] = log_results.stepset[param]
else:
all_stepset[param].extend(log_results.stepset[param])
for param in log_results.dataset:
if param not in all_dataset:
all_dataset[param] = log_results.dataset[param]
else:
all_dataset[param].extend(log_results.dataset[param])
# Now reusing the last log_results object to store the results
return LogFileAnalysis(all_stepset, all_dataset, log_results.encoding)

10 changes: 7 additions & 3 deletions PyLTSpice/sim/tookit/tolerance_deviations.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# Licence: refer to the LICENSE file
# -------------------------------------------------------------------------------

from .sim_analysis import SimAnalysis, AnyRunner, Simulator
from .sim_analysis import SimAnalysis, AnyRunner
from enum import Enum


Expand Down Expand Up @@ -130,11 +130,15 @@ def run(self, max_runs_per_sim: int = 512, **kwargs):
the simulation is split in multiple runs.
"""
self.reset_netlist()
self.prepare_testbench()
self.prepare_testbench(self.num_runs)
self.editor.remove_instruction(".step param run -1 %d 1" % self.num_runs) # Needs to remove this instruction
for sim_no in range(-1, self.num_runs, max_runs_per_sim):
run_stepping = ".step param run {} {} 1 ".format(sim_no, sim_no + max_runs_per_sim)
run_stepping = ".step param run {} {} 1".format(sim_no, sim_no + max_runs_per_sim)
self.editor.add_instruction(run_stepping)
sim = self.runner.run(self.editor, **kwargs)
self.simulations.append(sim)
self.editor.remove_instruction(run_stepping)
self.runner.wait_completion()
if 'callback' in kwargs:
return (sim.callback_return if sim is not None else None for sim in self.simulations)
return None
11 changes: 8 additions & 3 deletions examples/prepare_montecarlo.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from PyLTSpice import AscEditor # Imports the class that manipulates the asc file
from PyLTSpice import AscEditor, SimRunner # Imports the class that manipulates the asc file
from PyLTSpice.sim.tookit.montecarlo import Montecarlo # Imports the Montecarlo toolkit class

sallenkey = AscEditor("./testfiles/sallenkey.asc") # Reads the asc file into memory

mc = Montecarlo(sallenkey) # Instantiates the Montecarlo class, with the asc file already in memory
runner = SimRunner(output_folder='./temp_mc') # Instantiates the runner class, with the output folder already set
mc = Montecarlo(sallenkey, runner) # Instantiates the Montecarlo class, with the asc file already in memory

# The following lines set the default tolerances for the components
mc.set_tolerance('R', 0.01) # 1% tolerance, default distribution is uniform
Expand All @@ -20,3 +20,8 @@
# Finally the netlist is saved to a file
mc.save_netlist('./testfiles/sallenkey_mc.net')

mc.run(100) # Runs the simulation with splits of 100 runs each
logs = mc.read_logfiles() # Reads the log files and stores the results in the results attribute
logs.export_data('./temp_mc/data.csv') # Exports the data to a csv file
logs.plot_histogram('fcut') # Plots the histograms for the results
mc.cleanup_files() # Deletes the temporary files

0 comments on commit ec520e7

Please sign in to comment.