diff --git a/.gitignore b/.gitignore index ce04b90be..aa40bd522 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ MANIFEST .pytest_cache/ .tox/ PySpice.egg-info/ -PySpice/__init__.py build/ dist/ doc/sphinx/build/ @@ -98,3 +97,7 @@ tools/upload-www trash/ unit-test/test.py +PySpicePro.egg-info/ +.settings/ +.project +.pydevproject diff --git a/PySpice/Config/ConfigInstall.py b/PySpice/Config/ConfigInstall.py index 7b4e9d3e3..998d30974 100644 --- a/PySpice/Config/ConfigInstall.py +++ b/PySpice/Config/ConfigInstall.py @@ -5,7 +5,7 @@ #################################################################################################### -import PySpice.Tools.Path as PathTools # Fixme: why ? +from ..Tools import Path as PathTools #################################################################################################### diff --git a/PySpice/Logging/Logging.py b/PySpice/Logging/Logging.py index eab4a4f1e..abd81622e 100644 --- a/PySpice/Logging/Logging.py +++ b/PySpice/Logging/Logging.py @@ -44,7 +44,7 @@ def setup_logging(application_name='PySpice', """ logging_config_file_name = ConfigInstall.Logging.find(config_file) - logging_config = yaml.load(open(logging_config_file_name, 'r')) + logging_config = yaml.load(open(logging_config_file_name, 'r'), Loader=yaml.FullLoader) if ConfigInstall.OS.on_linux: # Fixme: \033 is not interpreted in YAML diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 6885bc997..7221437f5 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -47,9 +47,14 @@ #################################################################################################### import logging -import os +import os, shutil +import tempfile import platform import re +from weakref import WeakValueDictionary +import warnings +import gc + # import time import numpy as np @@ -57,7 +62,6 @@ #################################################################################################### from cffi import FFI -ffi = FFI() #################################################################################################### @@ -78,11 +82,21 @@ #################################################################################################### + def ffi_string_utf8(x): - return ffi.string(x).decode('utf8') # Fixme: ascii ? + bytelist = [] + i = 0 + while True: + c = x[i] + if c == b'\0': + break + bytelist.append(c) + i += 1 + return b''.join(bytelist).decode('utf-8') #################################################################################################### + class Vector: """ This class implements a vector in a simulation output. @@ -166,6 +180,7 @@ def to_waveform(self, abscissa=None, to_real=False, to_float=False): #################################################################################################### + class Plot(dict): """ This class implements a plot in a simulation output. @@ -251,7 +266,7 @@ def _to_sensitivity_analysis(self): # Fixme: separate v(vinput), analysis.R2.m return SensitivityAnalysis( simulation=self._simulation, - elements=self.elements(), # Fixme: internal parameters ??? + elements=self.elements(), # Fixme: internal parameters ??? internal_parameters=self.internal_parameters(), ) @@ -306,7 +321,8 @@ def _to_transient_analysis(self): #################################################################################################### -class NgSpiceShared: + +class NgSpiceShared(object): _logger = _module_logger.getChild('NgSpiceShared') @@ -317,43 +333,92 @@ class NgSpiceShared: ############################################## - _instances = {} - - @classmethod - def new_instance(cls, ngspice_id=0, send_data=False): + _instances = None + _ffis = {} + _ngspice_shared_dict = {} + _temp_dlls = {} + def __new__(cls, ngspice_id=None, send_data=False): + """ + Only one instance exists per ngspice_id. All instances can be called in parallel but only one + callback function exists per instance (the fist one defined). The ngspice library has a limit + on the number of open libraries, do not open too many instances (50 as of writing these lines). + If a duplicate of an instance is required, use the ngspice_id of the previously created instance. + Use ngspice_id=None for auto-numbering of instances (recycling previously closed libraries). + Use get_id() to get the instance id. + """ + assert type(ngspice_id) is int if ngspice_id is not None else True, "ngspice_id must be an int" # Fixme: send_data - - if ngspice_id in cls._instances: - return cls._instances[ngspice_id] + if __class__._instances is None: + __class__._instances = WeakValueDictionary() + + if ngspice_id in __class__._instances: + warnings.warn("""This use case as not been tested as I had no use for it yet. +Creating multiple instances that points to the same library (dll) would be used to have +multiple circuit simulated in lockstep one to the other. The lockstep functionnality is +provided by the get_isrc_data and get_vsrc_data callback function.""") + return __class__._instances[ngspice_id] else: + if ngspice_id is None: # find an empty slot + gc.collect() # Force garbage collection to make sure all instances have been released + slot = 0 # starting point + while True: + if slot not in __class__._instances: + break # found + slot += 1 + ngspice_id = slot # keep this id cls._logger.info("New instance for id {}".format(ngspice_id)) - instance = cls(ngspice_id=ngspice_id, send_data=send_data) - cls._instances[ngspice_id] = instance + instance = object.__new__(cls) + __class__._instances[ngspice_id] = instance + instance._ffi = FFI() + __class__._ffis[ngspice_id] = instance._ffi + instance._ngspice_id = ngspice_id + instance._load_library() return instance ############################################## - def __init__(self, ngspice_id=0, send_data=False): - - """ Set the *send_data* flag if you want to enable the output callback. - - Set the *ngspice_id* to an integer value if you want to run NgSpice in parallel. + def __init__(self, ngspice_id=None, send_data=False): """ - - self._ngspice_id = ngspice_id + Set the *send_data* flag if you want to enable the output callback. + """ + assert ngspice_id == self._ngspice_id if ngspice_id is not None else True, "__new__ must have been called first" + # ## __init__ is not called for an instance copy self._stdout = [] self._stderr = [] - self._load_library() self._init_ngspice(send_data) self._is_running = False ############################################## + def __del__(self): + try: + self.quit() # this function generates a NameError + except NameError: + pass + self._ffi.dlclose(self._ngspice_shared) + del __class__._ngspice_shared_dict[self._ngspice_id] + del __class__._ffis[self._ngspice_id] + try: + del __class__._instances[self._ngspice_id] + except KeyError: + pass + try: + os.unlink(self._temp_dlls[self._ngspice_id]) + except: + "dlclose is not doing its job!" # do not know how to solve + pass + + def get_id(self): + return self._ngspice_id + def _load_library(self): + ############################################## + + ffi = self._ffi if ConfigInstall.OS.on_windows: # https://sourceforge.net/p/ngspice/discussion/133842/thread/1cece652/#4e32/5ab8/9027 @@ -366,20 +431,45 @@ def _load_library(self): with open(api_path) as f: ffi.cdef(f.read()) - if not self._ngspice_id: - library_prefix = '' - else: - library_prefix = '{}'.format(self._ngspice_id) - library_path = self.LIBRARY_PATH.format(library_prefix) - self._logger.debug('Load {}'.format(library_path)) - self._ngspice_shared = ffi.dlopen(library_path) - + library_path = self.LIBRARY_PATH.format('') + # create a new instance of the DLL as per ngspice docs about parallelization + temp_dll = os.path.join(tempfile.gettempdir(), "ngspice_" + str(self._ngspice_id) + ".dll") + try: + shutil.copy(library_path, temp_dll) + except: + pass # Ok already exists + try: # TODO browse through dlls in case it is used??? + __class__._ngspice_shared_dict[self._ngspice_id] = ffi.dlopen(temp_dll) # the file may be busy from other application + except: # The dll may be in use??? MAYBE TODO: try another one in the same folder. + try: + os.unlink(temp_dll) + except: + pass + try: + # On some computers, the dll cannot be copied to another folder + # It generates error 0x7e, dll cannot be loaded. + # So try copying in the same folder as the original + temp_dll = self.LIBRARY_PATH.format(f'_{self._ngspice_id}') + try: + shutil.copy(library_path, temp_dll) + except: + pass # Ok already exists + __class__._ngspice_shared_dict[self._ngspice_id] = ffi.dlopen(temp_dll) # the file may be busy from other application + except: + try: + os.unlink(temp_dll) + except: + pass + raise + self._logger.debug('Loaded {}'.format(temp_dll)) + self._ngspice_shared = __class__._ngspice_shared_dict[self._ngspice_id] + self.__class__._temp_dlls[self._ngspice_id] = temp_dll # Note: cannot yet execute command ############################################## def _init_ngspice(self, send_data): - + ffi = self._ffi self._send_char_c = ffi.callback('int (char *, int, void *)', self._send_char) self._send_stat_c = ffi.callback('int (char *, int, void *)', self._send_stat) self._exit_c = ffi.callback('int (int, bool, bool, int, void *)', self._exit) @@ -389,13 +479,13 @@ def _init_ngspice(self, send_data): if send_data: self._send_data_c = ffi.callback('int (pvecvaluesall, int, int, void *)', self._send_data) else: - self._send_data_c = ffi.NULL + self._send_data_c = FFI.NULL self._get_vsrc_data_c = ffi.callback('int (double *, double, char *, int, void *)', self._get_vsrc_data) self._get_isrc_data_c = ffi.callback('int (double *, double, char *, int, void *)', self._get_isrc_data) self_c = ffi.new_handle(self) - self._self_c = self_c # To prevent garbage collection + self._self_c = self_c # To prevent garbage collection rc = self._ngspice_shared.ngSpice_Init(self._send_char_c, self._send_stat_c, @@ -408,10 +498,10 @@ def _init_ngspice(self, send_data): raise NameError("Ngspice_Init returned {}".format(rc)) ngspice_id_c = ffi.new('int *', self._ngspice_id) - self._ngspice_id = ngspice_id_c # To prevent garbage collection + self._ngspice_id_c = ngspice_id_c # To prevent garbage collection rc = self._ngspice_shared.ngSpice_Init_Sync(self._get_vsrc_data_c, self._get_isrc_data_c, - ffi.NULL, # GetSyncData + FFI.NULL, # GetSyncData ngspice_id_c, self_c) if rc: @@ -441,8 +531,10 @@ def _init_ngspice(self, send_data): def _send_char(message_c, ngspice_id, user_data): """Callback for sending output from stdout, stderr to caller""" + if ngspice_id not in NgSpiceShared._ffis: + return 0 - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) message = ffi_string_utf8(message_c) prefix, _, content = message.partition(' ') @@ -460,7 +552,7 @@ def _send_char(message_c, ngspice_id, user_data): @staticmethod def _send_stat(message, ngspice_id, user_data): """Callback for simulation status to caller""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) return self.send_stat(ffi_string_utf8(message), ngspice_id) ############################################## @@ -468,7 +560,7 @@ def _send_stat(message, ngspice_id, user_data): @staticmethod def _exit(exit_status, immediate_unloding, quit_exit, ngspice_id, user_data): """Callback for asking for a reaction after controlled exit""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) self._logger.debug('ngspice_id-{} exit status={} immediate_unloding={} quit_exit={}'.format( ngspice_id, exit_status, @@ -481,7 +573,7 @@ def _exit(exit_status, immediate_unloding, quit_exit, ngspice_id, user_data): @staticmethod def _send_data(data, number_of_vectors, ngspice_id, user_data): """Callback to send back actual vector data""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) # self._logger.debug('ngspice_id-{} send_data [{}]'.format(ngspice_id, data.vecindex)) actual_vector_values = {} for i in range(int(number_of_vectors)): @@ -495,22 +587,22 @@ def _send_data(data, number_of_vectors, ngspice_id, user_data): ############################################## @staticmethod - def _send_init_data(data, ngspice_id, user_data): + def _send_init_data(data, ngspice_id, user_data): """Callback to send back initialization vector data""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) # if self._logger.isEnabledFor(logging.DEBUG): # self._logger.debug('ngspice_id-{} send_init_data'.format(ngspice_id)) # number_of_vectors = data.veccount # for i in range(number_of_vectors): # self._logger.debug(' Vector: ' + ffi_string_utf8(data.vecs[i].vecname)) - return self.send_init_data(data, ngspice_id) # Fixme: should be a Python object + return self.send_init_data(data, ngspice_id) # Fixme: should be a Python object ############################################## @staticmethod def _background_thread_running(is_running, ngspice_id, user_data): """Callback to indicate if background thread is runnin""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) self._logger.debug('ngspice_id-{} background_thread_running {}'.format(ngspice_id, is_running)) self._is_running = is_running @@ -519,7 +611,7 @@ def _background_thread_running(is_running, ngspice_id, user_data): @staticmethod def _get_vsrc_data(voltage, time, node, ngspice_id, user_data): """FFI Callback""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) return self.get_vsrc_data(voltage, time, ffi_string_utf8(node), ngspice_id) ############################################## @@ -527,7 +619,7 @@ def _get_vsrc_data(voltage, time, node, ngspice_id, user_data): @staticmethod def _get_isrc_data(current, time, node, ngspice_id, user_data): """FFI Callback""" - self = ffi.from_handle(user_data) + self = NgSpiceShared._ffis[ngspice_id].from_handle(user_data) return self.get_isrc_data(current, time, ffi_string_utf8(node), ngspice_id) ############################################## @@ -552,7 +644,7 @@ def send_data(self, actual_vector_values, number_of_vectors, ngspice_id): ############################################## - def send_init_data(self, data, ngspice_id): + def send_init_data(self, data, ngspice_id): """ Reimplement this callback in a subclass to process the initial data. """ return 0 @@ -578,7 +670,7 @@ def _convert_string_array(array): strings = [] i = 0 while (True): - if array[i] == ffi.NULL: + if array[i] == FFI.NULL: break else: strings.append(ffi_string_utf8(array[i])) @@ -717,10 +809,11 @@ def _alter(self, command, device, kwargs): ############################################## - def alter_device(self, device, **kwargs): + def alter_device(self, device=None, **kwargs): """Alter device parameters""" - + if device is None: + device = '' self._alter('alter', device, kwargs) ############################################## @@ -869,7 +962,7 @@ def ressource_usage(self, *ressources): parts = line.split(' = ') else: parts = line.split(': ') - values[parts[0]] = NgSpiceShared._to_python(parts[1]) + values[parts[0]] = NgSpiceShared._to_python(parts[1]) return values ############################################## @@ -962,10 +1055,11 @@ def load_circuit(self, circuit): """Load the given circuit string.""" + ffi = NgSpiceShared._ffis[self._ngspice_id] circuit_lines = [line for line in str(circuit).split(os.linesep) if line] circuit_lines_keepalive = [ffi.new("char[]", line.encode('utf8')) for line in circuit_lines] - circuit_lines_keepalive += [ffi.NULL] + circuit_lines_keepalive += [FFI.NULL] circuit_array = ffi.new("char *[]", circuit_lines_keepalive) rc = self._ngspice_shared.ngSpice_Circ(circuit_array) if rc: @@ -1011,9 +1105,9 @@ def halt(self): ############################################## - def resume(self, background=True): + def resume(self, background=False): - """ Halt the simulation in the background thread. """ + """ Resume the simulation """ command = b'bg_resume' if background else b'resume' rc = self._ngspice_shared.ngSpice_Command(command) @@ -1044,14 +1138,14 @@ def last_plot(self): def _flags_to_str(flags): # enum dvec_flags { - # VF_REAL = (1 << 0), // The data is real. - # VF_COMPLEX = (1 << 1), // The data is complex. - # VF_ACCUM = (1 << 2), // writedata should save this vector. - # VF_PLOT = (1 << 3), // writedata should incrementally plot it. - # VF_PRINT = (1 << 4), // writedata should print this vector. - # VF_MINGIVEN = (1 << 5), // The v_minsignal value is valid. - # VF_MAXGIVEN = (1 << 6), // The v_maxsignal value is valid. - # VF_PERMANENT = (1 << 7) // Don't garbage collect this vector. + # VF_REAL = (1 << 0), // The data is real. + # VF_COMPLEX = (1 << 1), // The data is complex. + # VF_ACCUM = (1 << 2), // writedata should save this vector. + # VF_PLOT = (1 << 3), // writedata should incrementally plot it. + # VF_PRINT = (1 << 4), // writedata should print this vector. + # VF_MINGIVEN = (1 << 5), // The v_minsignal value is valid. + # VF_MAXGIVEN = (1 << 6), // The v_maxsignal value is valid. + # VF_PERMANENT = (1 << 7) // Don't garbage collect this vector. # }; if flags & 1: @@ -1081,11 +1175,12 @@ def plot(self, simulation, plot_name): # plot_name is for example dc with an integer suffix which is increment for each run + ffi = self._ffi plot = Plot(simulation, plot_name) all_vectors_c = self._ngspice_shared.ngSpice_AllVecs(plot_name.encode('utf8')) i = 0 while (True): - if all_vectors_c[i] == ffi.NULL: + if all_vectors_c[i] == FFI.NULL: break else: vector_name = ffi_string_utf8(all_vectors_c[i]) @@ -1098,17 +1193,17 @@ def plot(self, simulation, plot_name): # vector_type, # self._flags_to_str(vector_info.v_flags), # length)) - if vector_info.v_compdata == ffi.NULL: + if vector_info.v_compdata == FFI.NULL: # for k in xrange(length): # print(" [{}] {}".format(k, vector_info.v_realdata[k])) - tmp_array = np.frombuffer(ffi.buffer(vector_info.v_realdata, length*8), dtype=np.float64) - array = np.array(tmp_array, dtype=tmp_array.dtype) # copy data + tmp_array = np.frombuffer(ffi.buffer(vector_info.v_realdata, length * 8), dtype=np.float64) + array = np.array(tmp_array, dtype=tmp_array.dtype) # copy data else: # for k in xrange(length): # value = vector_info.v_compdata[k] # print(ffi.addressof(value, field='cx_real'), ffi.addressof(value, field='cx_imag')) # print(" [{}] {} + i {}".format(k, value.cx_real, value.cx_imag)) - tmp_array = np.frombuffer(ffi.buffer(vector_info.v_compdata, length*8*2), dtype=np.float64) + tmp_array = np.frombuffer(ffi.buffer(vector_info.v_compdata, length * 8 * 2), dtype=np.float64) array = np.array(tmp_array[0::2], dtype=np.complex64) array.imag = tmp_array[1::2] plot[vector_name] = Vector(self, vector_name, vector_type, array) @@ -1121,6 +1216,7 @@ def plot(self, simulation, plot_name): # Platform setup # + if ConfigInstall.OS.on_windows: drive = os.getenv('SystemDrive') or 'C:' root = drive + os.sep @@ -1140,4 +1236,4 @@ def plot(self, simulation, plot_name): _path = 'libngspice{}.so' else: raise NotImplementedError -NgSpiceShared.LIBRARY_PATH= _path +NgSpiceShared.LIBRARY_PATH = _path diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 15ab2d0e7..47074cbc2 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -297,6 +297,37 @@ def to_list(self): #################################################################################################### +class MeasureParameters(AnalysisParameters): + + """ + This class defines measurements on analysis. + """ + + __analysis_name__ = 'meas' + + ############################################## + + def __init__(self, analysis_type, name, *args): + + if (str(analysis_type).upper() not in ('AC', 'DC', 'OP', 'TRAN', 'TF', 'NOISE')): + raise ValueError("Incorrect analysis type") + + self._parameters = [analysis_type, name, *args] + + ############################################## + + @property + def parameters(self): + return self._parameters + + ############################################## + + def to_list(self): + + return self._parameters + +#################################################################################################### + class CircuitSimulation: """Define and generate the spice instruction to perform a circuit simulation. @@ -315,6 +346,7 @@ def __init__(self, circuit, **kwargs): self._circuit = circuit self._options = {} # .options + self._measures = [] # .measure self._initial_condition = {} # .ic self._saved_nodes = set() self._analyses = {} @@ -441,6 +473,12 @@ def _add_analysis(self, analysis_parameters): ############################################## + def _add_measure(self, measure_parameters): + + self._measures.append(measure_parameters) + + ############################################## + def operating_point(self): """Compute the operating point of the circuit with capacitors open and inductors shorted.""" @@ -586,6 +624,28 @@ def ac(self, variation, number_of_points, start_frequency, stop_frequency): ############################################## + def measure(self, analysis_type, name, *args): + + """Add a measure in the circuit. + + Examples of usage:: + + simulator.measure('TRAN', 'tdiff', 'TRIG AT=10m', 'TARG v(n1) VAL=75.0 CROSS=1') + simulator.measure('tran', 'tdiff', 'TRIG AT=0m', f"TARG par('v(n1)-v(derate)') VAL=0 CROSS=1") + + Note: can be used with the .options AUTOSTOP to stop the simulation at Trigger. + Spice syntax: + + .. code:: spice + + .meas tran tdiff TRIG AT=0m TARG v(n1) VAL=75.0 CROSS=1 + + """ + + self._add_measure(MeasureParameters(analysis_type, name, *args)) + + ############################################## + def transient(self, step_time, end_time, start_time=0, max_time=None, use_initial_condition=False): @@ -646,6 +706,8 @@ def __str__(self): else: all_str = '' netlist += '.save ' + all_str + join_list(saved_nodes) + os.linesep + for measure_parameters in self._measures: + netlist += str(measure_parameters) + os.linesep for analysis_parameters in self._analyses.values(): netlist += str(analysis_parameters) + os.linesep netlist += '.end' + os.linesep diff --git a/PySpice/__init__.py b/PySpice/__init__.py new file mode 100644 index 000000000..9786247cb --- /dev/null +++ b/PySpice/__init__.py @@ -0,0 +1 @@ +__version__ = '40.3' \ No newline at end of file diff --git a/README.html b/README.html deleted file mode 100644 index 501738ba1..000000000 --- a/README.html +++ /dev/null @@ -1,559 +0,0 @@ - - - -
- - -PySpice is a Python module which interface Python to the Ngspice and Xyce circuit -simulators.
-The documentation is available on the PySpice Home Page.
-Look at the installation section in the documentation.
-Authors: Fabrice Salvaire
----
-- support Ngspice 30 and Xyce 6.10
-- fixed NgSpice and Xyce support on Windows 10
-- bug fixes
-
-- --
-- Initial support of the Xyce simulator. Xyce is an open source, SPICE-compatible, -high-performance analog circuit simulator, capable of solving extremely large circuit problems -developed at Sandia National Laboratories. Xyce will make PySpice suitable for industry and -research use.
-- Fixed OSX support
-- Splitted G device
-- Implemented partially A XSPICE device
-- Implemented missing transmission line devices
-- Implemented high level current sources -Notice: Some classes were renamed !
-- Implemented node kwarg e.g.
-circuit.Q(1, base=1, collector=2, emitter=3, model='npn')- Implemented raw spice pass through (see User FAQ)
-- Implemented access to internal parameters (cf.
-save @device[parameter])- Implemented check for missing ground node
-- Implemented a way to disable an element and clone netlist
-- Improved SPICE parser
-- Improved unit support:
--
-- Implemented unit prefix cast U_μV(U_mV(1)) to easily convert values
-- Added U_mV, ... shortcuts
-- Added Numpy array support to unit, see UnitValues Notice: this new feature could be buggy !!!
-- Rebased WaveForm to UnitValues
-- Fixed node order so as to not confuse users Now PySpice matches SPICE order for two ports elements !
-- Fixed device shortcuts in Netlist class
-- Fixed model kwarg for BJT Notice: it must be passed exclusively as kwarg !
-- Fixed subcircuit nesting
-- Outsourced documentation generator to Pyterate
-- Updated setup.py for wheel
-
---
-- Enhanced shared mode
-- Shared mode is now set as default on Linux
-
---
-- Bump version to v1.0.0 since it just works!
-- Support Windows platform using Ngspice shared mode
-- Fixed shared mode
-- Fixed and completed Spice parser : tested on example's libraries
-
---
-- Fixed Spice parser for lower case device prefix.
-
---
-- Git repository cleanup: filtered generated doc and useless files so as to shrink the repository size.
-- Improved documentation generator: Implemented
-formatfor RST content and Tikz figure.- Improved unit support: It implements now the International System of Units. -And we can now use unit helper like
-u_mVor compute the value of1.2@u_kΩ / 2@u_mA. -The relevant documentation is on this page.- Added the Simulation instance to the Analysis class.
-- Refactored simulation parameters as classes.
-
---
-- fixed CCCS and CCVS
-
---
-- fixed ngspice shared
-
---
-- Added an example to show how to use the NgSpice Shared Simulation Mode.
-- Completed the Spice netlist parser and added examples, we could now use a schematic editor -to define the circuit. The program cir2py translates a circuit file to Python.
-
Started project
- - -