From 6c7f941b23eb2e49d2773d0d856758d17d92da2f Mon Sep 17 00:00:00 2001 From: Pronovost Date: Thu, 6 Jun 2019 16:08:24 -0400 Subject: [PATCH 1/8] Added measure support --- PySpice/Spice/Simulation.py | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 15ab2d0e7..4461a9ca8 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:: + + analysis = simulator.transient('TRAN', 'tdiff', 'TRIG AT=10m', 'TARG v(n1) VAL=75.0 CROSS=1') + analysis = simulator.transient('TRAN', 'tdiff', 'TARG v(n1) VAL=75.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 From 241e31b55da6e4cdc5aa6f95ac040c627be82831 Mon Sep 17 00:00:00 2001 From: ceprio Date: Fri, 14 Jun 2019 09:42:36 -0400 Subject: [PATCH 2/8] Modified comments --- PySpice/Spice/Simulation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 4461a9ca8..47074cbc2 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -630,8 +630,8 @@ def measure(self, analysis_type, name, *args): Examples of usage:: - analysis = simulator.transient('TRAN', 'tdiff', 'TRIG AT=10m', 'TARG v(n1) VAL=75.0 CROSS=1') - analysis = simulator.transient('TRAN', 'tdiff', 'TARG v(n1) VAL=75.0 CROSS=1') + 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: From feff3153f19553c246f0abdba0baab2d50241035 Mon Sep 17 00:00:00 2001 From: ceprio Date: Wed, 22 Jan 2020 09:09:39 -0500 Subject: [PATCH 3/8] Fixed in ConfigInstall.py: path to the tools module Modified Shared.py: Harmonised default background value, modified alter_device to allow device value change. --- PySpice/Config/ConfigInstall.py | 2 +- PySpice/Spice/NgSpice/Shared.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) 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/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 6885bc997..4302251d3 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -717,10 +717,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) ############################################## @@ -1011,9 +1012,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) From 21190708279dc17bd03107d582006d7808a1f915 Mon Sep 17 00:00:00 2001 From: ceprio Date: Wed, 22 Jan 2020 10:59:41 -0500 Subject: [PATCH 4/8] Modified for PySpicePro pypi package --- .gitignore | 1 + invoke.yaml | 4 ++-- setup.py | 2 +- setup_data.py | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index ce04b90be..7a5515adb 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,4 @@ tools/upload-www trash/ unit-test/test.py +PySpicePro.egg-info/ diff --git a/invoke.yaml b/invoke.yaml index b17dc38d7..e52476135 100644 --- a/invoke.yaml +++ b/invoke.yaml @@ -1,4 +1,4 @@ -Package: PySpice +Package: PySpicePro ngspice: directory: [ngspice-build, 'ngspice-{}'] - version: 30 + version: 31 diff --git a/setup.py b/setup.py index f6db4b045..0658994b7 100755 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], install_requires=[ diff --git a/setup_data.py b/setup_data.py index 6012e0ad6..376b9baf5 100644 --- a/setup_data.py +++ b/setup_data.py @@ -87,10 +87,10 @@ def read_readme(file_name): #################################################################################################### setup_dict = dict( - name='PySpice', + name='PySpicePro', version=version, - author='Fabrice Salvaire', - author_email='fabrice.salvaire@orange.fr', + author='ceprio', + author_email='c.pypi@zone-c5.com', description='Simulate electronic circuit using Python and the Ngspice / Xyce simulators', license='GPLv3', keywords= 'spice berkeley ngspice xyce electronic circuit simulation simulator', From 5f43d4f9d34685ea11178f2b191f6ab19b21eddd Mon Sep 17 00:00:00 2001 From: ceprio Date: Wed, 18 Mar 2020 10:46:38 -0400 Subject: [PATCH 5/8] Added parallelisation capability --- PySpice/Spice/NgSpice/Shared.py | 105 ++++++++++++++++++++++---------- README.rst | 56 ++++++++--------- 2 files changed, 102 insertions(+), 59 deletions(-) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 4302251d3..7bd57efd7 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -47,7 +47,8 @@ #################################################################################################### import logging -import os +import os, shutil +import tempfile import platform import re # import time @@ -57,7 +58,6 @@ #################################################################################################### from cffi import FFI -ffi = FFI() #################################################################################################### @@ -79,7 +79,15 @@ #################################################################################################### 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') #################################################################################################### @@ -317,16 +325,27 @@ class NgSpiceShared: ############################################## + __number_of_instances = 0 _instances = {} + _ffis = {} @classmethod - def new_instance(cls, ngspice_id=0, send_data=False): - + def new_instance(cls, ngspice_id=None, send_data=False): + """ + Use NgSpiceShared.new_instance() to create NgSpice instances. All instances can be called in parallel. + If a duplicate of an instance is required, use the ngspice_id of the previously created instance. + Use get_id() to get the instance id. + """ # Fixme: send_data if ngspice_id in cls._instances: return cls._instances[ngspice_id] else: + while True: + cls.__number_of_instances += 1 + if cls.__number_of_instances not in cls._instances: + break + ngspice_id = cls.__number_of_instances 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 @@ -336,11 +355,16 @@ def new_instance(cls, ngspice_id=0, send_data=False): 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. """ - + Do not use this unless you manage the ngspice_ids yourself or only need one instance. + + Use NgSpiceShared.new_instance() instead that creates one instance per call unless + ngspice_id is specified (as an int). This allows to run NgSpice in parallel. + + Set the *send_data* flag if you want to enable the output callback. + """ + assert ngspice_id not in self.__class__._instances, "You cannot instantiate two instances of NgSpiceShared with the same ngspice_id" + self.__class__._ffis[ngspice_id] = FFI() self._ngspice_id = ngspice_id self._stdout = [] @@ -352,8 +376,21 @@ def __init__(self, ngspice_id=0, send_data=False): self._is_running = False ############################################## + + def __del__(self): + ffi=self.__class__._ffis[self._ngspice_id] + ffi.dlclose(self._ngspice_shared) + del self.__class__._ffis[self._ngspice_id] + del NgSpiceShared._instances[self._ngspice_id] + os.unlink(self.temp_dll) + def get_id(self): + return self._ngspice_id + def _load_library(self): + ############################################## + + ffi = NgSpiceShared._ffis[self._ngspice_id] if ConfigInstall.OS.on_windows: # https://sourceforge.net/p/ngspice/discussion/133842/thread/1cece652/#4e32/5ab8/9027 @@ -366,11 +403,13 @@ 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) + library_path = self.LIBRARY_PATH.format('') + if self._ngspice_id: + # create a new instance of the DLL as per ngspice docs about parallelization + self.temp_dll = os.path.join(tempfile.gettempdir(), "ngspice_"+ str(self._ngspice_id) +".dll") + shutil.copy(library_path, self.temp_dll) + library_path = self.temp_dll + self._logger.debug('Load {}'.format(library_path)) self._ngspice_shared = ffi.dlopen(library_path) @@ -379,7 +418,7 @@ def _load_library(self): ############################################## def _init_ngspice(self, send_data): - + ffi = NgSpiceShared._ffis[self._ngspice_id] 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,7 +428,7 @@ 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) @@ -408,10 +447,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 +480,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 +501,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 +509,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 +522,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)): @@ -497,7 +538,7 @@ def _send_data(data, number_of_vectors, ngspice_id, user_data): @staticmethod 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 @@ -510,7 +551,7 @@ def _send_init_data(data, ngspice_id, user_data): @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 +560,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 +568,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) ############################################## @@ -578,7 +619,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])) @@ -962,11 +1003,12 @@ def where(self): 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: @@ -1082,11 +1124,12 @@ def plot(self, simulation, plot_name): # plot_name is for example dc with an integer suffix which is increment for each run + ffi = NgSpiceShared._ffis[self._ngspice_id] 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]) @@ -1099,7 +1142,7 @@ 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) diff --git a/README.rst b/README.rst index 259b1b4f1..700ccec40 100644 --- a/README.rst +++ b/README.rst @@ -26,16 +26,16 @@ .. .. _PySpice@pypi: https://pypi.python.org/pypi/PySpice .. |Pypi Version| image:: https://img.shields.io/pypi/v/PySpice.svg - :target: https://pypi.python.org/pypi/PySpice - :alt: PySpice last version + :target: https://pypi.python.org/pypi/PySpicePro + :alt: PySpicePro last version .. |Pypi License| image:: https://img.shields.io/pypi/l/PySpice.svg - :target: https://pypi.python.org/pypi/PySpice + :target: https://pypi.python.org/pypi/PySpicePro :alt: PySpice license .. |Pypi Python Version| image:: https://img.shields.io/pypi/pyversions/PySpice.svg - :target: https://pypi.python.org/pypi/PySpice - :alt: PySpice python version + :target: https://pypi.python.org/pypi/PySpicePro + :alt: PySpicePro python version .. |Build Status| image:: https://travis-ci.org/FabriceSalvaire/PySpice.svg?branch=master :target: https://travis-ci.org/FabriceSalvaire/PySpice @@ -47,10 +47,6 @@ :height: 15px :width: 80px -.. coverage test -.. https://img.shields.io/pypi/status/Django.svg -.. https://img.shields.io/github/stars/badges/shields.svg?style=social&label=Star - .. End .. -*- Mode: rst -*- @@ -84,21 +80,37 @@ .. |Tikz| replace:: Tikz .. |Xyce| replace:: Xyce -===================================================================================== - PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators -===================================================================================== +==================================================================================== +PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators +==================================================================================== |Pypi License| |Pypi Python Version| |Pypi Version| -* Quick Link to `Production Branch `_ -* Quick Link to `Devel Branch `_ - Overview ======== +What is PySpicePro ? +-------------------- + +PySpicePro is a Python module based on the PySpice module created by Fabrice Salvaire with additions that are not yet +integrated into PySpice. + +Additions: +"""""""""" + +* The .meas(ure) keyword has been added through the simulator.measure(..) member function. This allows the use of +simulator.options('AUTOSTOP'). +* Parallelisation is now more convenient with the use of NgSpiceShared.new_instance() where the management of new instances +is now completely automated (temporary dll, instances deletions). + +Installation: +""""""""""""" + + pip install PySpicePro + What is PySpice ? ----------------- @@ -126,23 +138,11 @@ What are the main features ? * implement a **documentation generator** * provides many **examples** -How to install it ? -------------------- - -Look at the `installation `_ section in the documentation. - Credits ======= -Authors: `Fabrice Salvaire `_ - -News -==== - -.. -*- Mode: rst -*- - +Author of the original PySpice: `Fabrice Salvaire `_ -.. no title here V1.4.0 (development release) ---------------------------- From 0641755cc604c095450aae089dee9320fb9e2db8 Mon Sep 17 00:00:00 2001 From: ceprio Date: Thu, 19 Mar 2020 15:11:02 -0400 Subject: [PATCH 6/8] Added some robustness --- PySpice/Logging/Logging.py | 2 +- PySpice/Spice/NgSpice/Shared.py | 37 ++- README.html | 559 -------------------------------- README.rst | 4 + README.txt | 63 ---- setup_data.py | 4 +- 6 files changed, 37 insertions(+), 632 deletions(-) delete mode 100644 README.html delete mode 100644 README.txt 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 7bd57efd7..ed292c63a 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -328,6 +328,7 @@ class NgSpiceShared: __number_of_instances = 0 _instances = {} _ffis = {} + __dll_id = 0 @classmethod def new_instance(cls, ngspice_id=None, send_data=False): @@ -378,11 +379,19 @@ def __init__(self, ngspice_id=0, send_data=False): ############################################## def __del__(self): + try: + self.quit() # this function generates a NameError + except: + pass ffi=self.__class__._ffis[self._ngspice_id] ffi.dlclose(self._ngspice_shared) del self.__class__._ffis[self._ngspice_id] del NgSpiceShared._instances[self._ngspice_id] - os.unlink(self.temp_dll) + try: + os.unlink(self.temp_dll) + except: + "dlclose is not doing its job!" # do not know how to solve + pass def get_id(self): return self._ngspice_id @@ -406,12 +415,26 @@ def _load_library(self): library_path = self.LIBRARY_PATH.format('') if self._ngspice_id: # create a new instance of the DLL as per ngspice docs about parallelization - self.temp_dll = os.path.join(tempfile.gettempdir(), "ngspice_"+ str(self._ngspice_id) +".dll") - shutil.copy(library_path, self.temp_dll) - library_path = self.temp_dll - - self._logger.debug('Load {}'.format(library_path)) - self._ngspice_shared = ffi.dlopen(library_path) + for n in range(1000): # remove this and make automatic temp file if the dlclose problem is solved (see __del__) + self.__class__.__dll_id += 1 + self.temp_dll = os.path.join(tempfile.gettempdir(), "ngspice_"+ str(self.__class__.__dll_id) +".dll") + try: + shutil.copy(library_path, self.temp_dll) + except: + continue + self._logger.debug('Load {}'.format(self.temp_dll)) + try: + self._ngspice_shared = ffi.dlopen(self.temp_dll) # the file may be busy from other application + except: # dll may be in use + try: + os.unlink(self.temp_dll) + except: + pass + continue + break + else: + self._logger.debug('Load {}'.format(library_path)) + self._ngspice_shared = ffi.dlopen(library_path) # Note: cannot yet execute command 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 : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators - - - -
-

PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators

- - - - - - - - - - - - - -

PySpice license -PySpice python version

-

PySpice last version

- -
-

Overview

-
-

What is PySpice ?

-

PySpice is a Python module which interface Python to the Ngspice and Xyce circuit -simulators.

-
-
-

Where is the Documentation ?

-

The documentation is available on the PySpice Home Page.

-
-
-

What are the main features ?

-
    -
  • support Ngspice and Xyce circuit simulators
  • -
  • support Linux, Windows and Mac OS X platforms
  • -
  • licensed under GPLv3 therms
  • -
  • implement an Ngspice shared library binding using CFFI which support external sources
  • -
  • implement (partial) SPICE netlist parser
  • -
  • implement an Oriented Object API to define circuit
  • -
  • export simulation output to Numpy arrays
  • -
  • plot using Matplotlib
  • -
  • handle units
  • -
  • work with Kicad schematic editor
  • -
  • implement a documentation generator
  • -
  • provides many examples
  • -
-
-
-

How to install it ?

-

Look at the installation section in the documentation.

-
-
-
-

Credits

-

Authors: Fabrice Salvaire

-
-
-

News

- - -
-

V1.4.0 (development release)

-
-
-

V1.3.2 (production release) 2019-03-11

-
-
    -
  • support Ngspice 30 and Xyce 6.10
  • -
  • fixed NgSpice and Xyce support on Windows 10
  • -
  • bug fixes
  • -
-
-
-
-

V1.2.0 2018-06-07

-
-
    -
  • 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
  • -
-
- -
-
-

V1.1.0 2017-09-06

-
-
    -
  • Enhanced shared mode
  • -
  • Shared mode is now set as default on Linux
  • -
-
-
-
-

V1.0.0 2017-09-06

-
-
    -
  • 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
  • -
-
-
-
-

V0.4.2

-
-
    -
  • Fixed Spice parser for lower case device prefix.
  • -
-
-
-
-

V0.4.0 2017-07-31

-
-
    -
  • Git repository cleanup: filtered generated doc and useless files so as to shrink the repository size.
  • -
  • Improved documentation generator: Implemented format for 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_mV or compute the value of 1.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.
  • -
-
-
-
-

V0.3.2 2017-02-22

-
-
    -
  • fixed CCCS and CCVS
  • -
-
-
-
-

V0.3.1 2017-02-22

-
-
    -
  • fixed ngspice shared
  • -
-
-
-
-

V0.3.0 2015-12-08

-
-
    -
  • 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.
  • -
-
-
-
-

V0 2014-03-21

-

Started project

- - -
-
-
- - diff --git a/README.rst b/README.rst index 700ccec40..909368055 100644 --- a/README.rst +++ b/README.rst @@ -98,6 +98,8 @@ What is PySpicePro ? PySpicePro is a Python module based on the PySpice module created by Fabrice Salvaire with additions that are not yet integrated into PySpice. +PySpicePro only works with ngspice version 30 (install from ngspice-30_dll_64.zip). + Additions: """""""""" @@ -111,6 +113,8 @@ Installation: pip install PySpicePro +PySpicePro needs to work with a specific version of ngspice. Currently only supporting v30. + What is PySpice ? ----------------- diff --git a/README.txt b/README.txt deleted file mode 100644 index 2ce91c11e..000000000 --- a/README.txt +++ /dev/null @@ -1,63 +0,0 @@ -.. -*- Mode: rst -*- - -.. include:: project-links.txt -.. include:: abbreviation.txt - -===================================================================================== - PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators -===================================================================================== - -|Pypi License| -|Pypi Python Version| - -|Pypi Version| - -* Quick Link to `Production Branch `_ -* Quick Link to `Devel Branch `_ - -Overview -======== - -What is PySpice ? ------------------ - -PySpice is a Python module which interface |Python|_ to the |Ngspice|_ and |Xyce|_ circuit -simulators. - -Where is the Documentation ? ----------------------------- - -The documentation is available on the |PySpiceHomePage|_. - -What are the main features ? ----------------------------- - -* support Ngspice and Xyce circuit simulators -* support **Linux**, **Windows** and Mac **OS X** platforms -* licensed under **GPLv3** therms -* implement an **Ngspice shared library binding** using CFFI which support external sources -* implement (partial) **SPICE netlist parser** -* implement an **Oriented Object API** to define circuit -* export simulation output to |Numpy|_ arrays -* plot using |Matplotlib|_ -* handle **units** -* work with **Kicad schematic editor** -* implement a **documentation generator** -* provides many **examples** - -How to install it ? -------------------- - -Look at the `installation `_ section in the documentation. - -Credits -======= - -Authors: `Fabrice Salvaire `_ - -News -==== - -.. include:: news.txt - -.. End diff --git a/setup_data.py b/setup_data.py index 376b9baf5..aa7110754 100644 --- a/setup_data.py +++ b/setup_data.py @@ -80,7 +80,7 @@ def read_readme(file_name): #################################################################################################### if not __file__.endswith('conf.py'): - long_description = read_readme('README.txt') + long_description = read_readme('README.rst') else: long_description = '' @@ -94,6 +94,6 @@ def read_readme(file_name): description='Simulate electronic circuit using Python and the Ngspice / Xyce simulators', license='GPLv3', keywords= 'spice berkeley ngspice xyce electronic circuit simulation simulator', - url='https://github.com/FabriceSalvaire/PySpice', + url='https://github.com/ceprio/PySpicePro', long_description=long_description, ) From a5f652003c71aaebf0f0073c30f1924052821ae9 Mon Sep 17 00:00:00 2001 From: ceprio Date: Thu, 19 Mar 2020 16:10:06 -0400 Subject: [PATCH 7/8] Release v40.1 --- README.rst | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 909368055..c0a3937e2 100644 --- a/README.rst +++ b/README.rst @@ -95,18 +95,16 @@ Overview What is PySpicePro ? -------------------- -PySpicePro is a Python module based on the PySpice module created by Fabrice Salvaire with additions that are not yet -integrated into PySpice. +PySpicePro is a Python module based on the PySpice module created by Fabrice Salvaire with additions that are not yet integrated into PySpice. PySpicePro only works with ngspice version 30 (install from ngspice-30_dll_64.zip). + Additions: """""""""" -* The .meas(ure) keyword has been added through the simulator.measure(..) member function. This allows the use of -simulator.options('AUTOSTOP'). -* Parallelisation is now more convenient with the use of NgSpiceShared.new_instance() where the management of new instances -is now completely automated (temporary dll, instances deletions). +* The .meas(ure) keyword has been added through the simulator.measure(..) member function. This allows the use of simulator.options('AUTOSTOP'). +* Parallelisation is now more convenient with the use of NgSpiceShared.new_instance() where the management of new instances is now completely automated (temporary dll, instances deletions). Installation: """"""""""""" @@ -167,29 +165,29 @@ V1.2.0 2018-06-07 research use. * Fixed OSX support * Splitted G device - * Implemented partially `A` XSPICE 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. :code:`circuit.Q(1, base=1, collector=2, emitter=3, model='npn')` - * Implemented raw spice pass through (see `User FAQ `_) + * Implemented raw spice pass through (see 'User FAQ '_) * Implemented access to internal parameters (cf. :code:`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` + * 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 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 + * Updated 'setup.py' for wheel .. :ref:`user-faq-page` From a223538f1ea0511fdbd40600dbee0288d7e3cca9 Mon Sep 17 00:00:00 2001 From: ceprio Date: Sat, 28 Mar 2020 10:41:11 -0400 Subject: [PATCH 8/8] Correction of the deletion of instances that was not done properly. This limited the number of instanciations to about 50. Note: There is still a limitation of about 50 instances living at the same time. Delete your instances when you are finished with them. --- PySpice/Spice/NgSpice/Shared.py | 130 +++++++++++++++++--------------- 1 file changed, 70 insertions(+), 60 deletions(-) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index ed292c63a..6c88fc8d7 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -51,6 +51,10 @@ import tempfile import platform import re +from weakref import WeakValueDictionary +import warnings +import gc + # import time import numpy as np @@ -314,7 +318,7 @@ def _to_transient_analysis(self): #################################################################################################### -class NgSpiceShared: +class NgSpiceShared(object): _logger = _module_logger.getChild('NgSpiceShared') @@ -325,53 +329,62 @@ class NgSpiceShared: ############################################## - __number_of_instances = 0 - _instances = {} + _instances=None _ffis = {} - __dll_id = 0 + _ngspice_shared_dict = {} + _temp_dlls = {} + - @classmethod - def new_instance(cls, ngspice_id=None, send_data=False): + def __new__(cls, ngspice_id=None, send_data=False): """ - Use NgSpiceShared.new_instance() to create NgSpice instances. All instances can be called in parallel. + 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: - while True: - cls.__number_of_instances += 1 - if cls.__number_of_instances not in cls._instances: - break - ngspice_id = cls.__number_of_instances + 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): - + def __init__(self, ngspice_id=None, send_data=False): """ - Do not use this unless you manage the ngspice_ids yourself or only need one instance. - - Use NgSpiceShared.new_instance() instead that creates one instance per call unless - ngspice_id is specified (as an int). This allows to run NgSpice in parallel. - Set the *send_data* flag if you want to enable the output callback. """ - assert ngspice_id not in self.__class__._instances, "You cannot instantiate two instances of NgSpiceShared with the same ngspice_id" - self.__class__._ffis[ngspice_id] = FFI() - self._ngspice_id = ngspice_id + 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 @@ -381,14 +394,17 @@ def __init__(self, ngspice_id=0, send_data=False): def __del__(self): try: self.quit() # this function generates a NameError - except: + except NameError: pass - ffi=self.__class__._ffis[self._ngspice_id] - ffi.dlclose(self._ngspice_shared) - del self.__class__._ffis[self._ngspice_id] - del NgSpiceShared._instances[self._ngspice_id] + self._ffi.dlclose(self._ngspice_shared) + del __class__._ngspice_shared_dict[self._ngspice_id] + del __class__._ffis[self._ngspice_id] try: - os.unlink(self.temp_dll) + 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 @@ -399,7 +415,7 @@ def get_id(self): def _load_library(self): ############################################## - ffi = NgSpiceShared._ffis[self._ngspice_id] + ffi = self._ffi if ConfigInstall.OS.on_windows: # https://sourceforge.net/p/ngspice/discussion/133842/thread/1cece652/#4e32/5ab8/9027 @@ -413,35 +429,29 @@ def _load_library(self): ffi.cdef(f.read()) library_path = self.LIBRARY_PATH.format('') - if self._ngspice_id: # create a new instance of the DLL as per ngspice docs about parallelization - for n in range(1000): # remove this and make automatic temp file if the dlclose problem is solved (see __del__) - self.__class__.__dll_id += 1 - self.temp_dll = os.path.join(tempfile.gettempdir(), "ngspice_"+ str(self.__class__.__dll_id) +".dll") - try: - shutil.copy(library_path, self.temp_dll) - except: - continue - self._logger.debug('Load {}'.format(self.temp_dll)) - try: - self._ngspice_shared = ffi.dlopen(self.temp_dll) # the file may be busy from other application - except: # dll may be in use - try: - os.unlink(self.temp_dll) - except: - pass - continue - break - else: - self._logger.debug('Load {}'.format(library_path)) - self._ngspice_shared = ffi.dlopen(library_path) - + temp_dll = os.path.join(tempfile.gettempdir(), "ngspice_"+ str(self._ngspice_id) +".dll") + try: + shutil.copy(library_path, temp_dll) + except: + pass # Already exist? + self._logger.debug('Load {}'.format(temp_dll)) + try: # TODO browse through dlls??? + __class__._ngspice_shared_dict[self._ngspice_id] = ffi.dlopen(temp_dll) # the file may be busy from other application + self._ngspice_shared = __class__._ngspice_shared_dict[self._ngspice_id] + except: # dll may be in use + try: + os.unlink(temp_dll) + except: + pass + raise + self.__class__._temp_dlls[self._ngspice_id] = temp_dll # Note: cannot yet execute command ############################################## def _init_ngspice(self, send_data): - ffi = NgSpiceShared._ffis[self._ngspice_id] + 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) @@ -1147,7 +1157,7 @@ def plot(self, simulation, plot_name): # plot_name is for example dc with an integer suffix which is increment for each run - ffi = NgSpiceShared._ffis[self._ngspice_id] + ffi = self._ffi plot = Plot(simulation, plot_name) all_vectors_c = self._ngspice_shared.ngSpice_AllVecs(plot_name.encode('utf8')) i = 0