pax_global_header 0000666 0000000 0000000 00000000064 13654121544 0014517 g ustar 00root root 0000000 0000000 52 comment=3fc99eccfcfebbbcf4ccafdfc9f4c353feb24caf
PySpice-pr-191/ 0000775 0000000 0000000 00000000000 13654121544 0013366 5 ustar 00root root 0000000 0000000 PySpice-pr-191/.gitattributes 0000664 0000000 0000000 00000000234 13654121544 0016260 0 ustar 00root root 0000000 0000000 # https://git-scm.com/docs/gitattributes
# *.cir text eol=lf
# *.lib text eol=lf
# *.mod text eol=lf
*.py text eol=lf
*.rst text eol=lf
*.yml text eol=lf
PySpice-pr-191/.github/ 0000775 0000000 0000000 00000000000 13654121544 0014726 5 ustar 00root root 0000000 0000000 PySpice-pr-191/.github/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000006247 13654121544 0017536 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pyspice at domain name fabrice-salvaire.fr. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
PySpice-pr-191/.github/CONTRIBUTING.md 0000664 0000000 0000000 00000000271 13654121544 0017157 0 ustar 00root root 0000000 0000000 PySpice is an open-source project, and relies on its community of users to keep getting better.
To read further, look at this page https://pyspice.fabrice-salvaire.fr/development.html
PySpice-pr-191/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000000225 13654121544 0017432 0 ustar 00root root 0000000 0000000 ### Environment (OS, Python version, PySpice version, simulator)
### Expected Behaviour
### Actual Behaviour
### Steps to reproduce the behaviour
PySpice-pr-191/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000000022 13654121544 0020521 0 ustar 00root root 0000000 0000000 To be written ...
PySpice-pr-191/.github/SUPPORT.md 0000664 0000000 0000000 00000000227 13654121544 0016425 0 ustar 00root root 0000000 0000000 There is no mailing list or forum actually, so you can either contact me or fill an issue on Github.
If you encounter an issue, please fill an issue.
PySpice-pr-191/.gitignore 0000664 0000000 0000000 00000004317 13654121544 0015363 0 ustar 00root root 0000000 0000000 *.stderr
*.stdout
*/tex/auto
*~
__pycache__
du.log
parser.out
parsetab.py
####################################################################################################
#
MANIFEST
#
####################################################################################################
.cache/
.pytest_cache/
.tox/
PySpice.egg-info/
PySpice/__init__.py
build/
dist/
doc/sphinx/build/
doc/sphinx/source/api/
doc/sphinx/source/examples/
####################################################################################################
PySpice/Spice/notes.txt
PySpice/Spice/rf.diff
PySpice/Unit/numpy-notes.txt
TRASH/
devel-test/
devel/
doc/extern/
doc/sphinx/source/-to-be-removed.txt
doc/sphinx/source/_static/logo-square.png
doc/sphinx/source/_static/logo-v1.jpg
doc/sphinx/source/_static/logo-v1.png
doc/sphinx/source/_static/logo-v2.jpg
doc/sphinx/source/_static/logo-v2.png
doc/sphinx/source/_static/logo.png
doc/sphinx/source/_static/stargazer.png
doc/sphinx/source/project-links.txt-
example-test/
examples/diode/tex/auto/
examples/libraries/2N2907A.SP3.TXT
examples/libraries/2N6035.SP3
examples/libraries/2N6038.SP3
examples/libraries/Infineon-SimulationModel_OptiMOS_PowerMOSFET_PSpice_20V_Complementary-SM-v01_00-EN.zip
examples/libraries/Infineon-SimulationModel_OptiMOS_PowerMOSFET_PSpice_20V_N-Channel-SM-v01_00-EN.zip
examples/libraries/Infineon-SimulationModel_OptiMOS_PowerMOSFET_PSpice_20V_P-Channel-SM-v01_00-EN.zip
examples/libraries/S_AFBJT*
examples/libraries/S_bjtmir.lib
examples/power-supplies/notes/
examples/power-supplies/references/
examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example.bak
issues/Tubemods/
issues/internal-parameters/
issues/multi-shared.py
issues/nazir/
issues/warning-example/
make-examples-log.txt
ngspice-build
ngspice-shared/log
ngspice-shared/sharedspice.h
notes/
open-doc.sh
resources
rsync-vps.sh
spice-examples/eg6.dat
spice-examples/gnucap.cir
spice-examples/mosfet-characterization.cir
spice-examples/ring-modulator.cir
spice-examples/xyce-error.cir
spice-examples/xyce-error.cir.FD.prn
spice-examples/xyce-raw.data
spice-examples/xyce-test.cir
spice-examples/xyce-test2.cir
spice-examples/xyce-test2.csv
tools/run-tox
tools/upload-www
trash/
unit-test/test.py
PySpice-pr-191/.travis.yml 0000664 0000000 0000000 00000000340 13654121544 0015474 0 ustar 00root root 0000000 0000000 dist: xenial
language: python
python:
- 3.7
install:
- pip install invoke
- invoke release.update-git-sha
- pip install -r requirements.txt
- pip install .
script:
- python unit-test/Spice/test_BasicElement.py
PySpice-pr-191/CHANGES.txt 0000664 0000000 0000000 00000000045 13654121544 0015176 0 ustar 00root root 0000000 0000000 v1.0, 08/08/2014 -- Initial release.
PySpice-pr-191/GPL-V3.0.txt 0000664 0000000 0000000 00000104513 13654121544 0015201 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
PySpice-pr-191/LICENSE.txt 0000664 0000000 0000000 00000104513 13654121544 0015215 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
PySpice-pr-191/MANIFEST.in 0000664 0000000 0000000 00000001743 13654121544 0015131 0 ustar 00root root 0000000 0000000 include .gitignore
include .travis.yml
include CHANGES.txt
include GPL-V3.0.txt
include LICENSE.txt
include MANIFEST.in
include README.html
include README.rst
include README.txt
include TODO.rst
include bin/cir2py
include bower/bower.json
include invoke.yaml
include make-release.sh
include pylintrc.ini
include requirements.txt requirements-dev.txt
include setenv.sh start.sh
include setup_data.py
include tox.ini
recursive-include PySpice *.py *.yml *.h *py.in
recursive-include doc *
recursive-include doc/sphinx/source/_static *
recursive-include doc/sphinx/source/_templates *
recursive-include examples *
recursive-include gh-pages *
recursive-include spec *.spec *.desktop
recursive-include tools *
recursive-include unit-test *.py
recursive-include spice-examples *
recursive-include issues *.py *.lib
global-exclude *~
global-exclude *__pycache__*
global-exclude *.pyc
global-exclude *.pdf
prune build
prune doc/sphinx/build
prune doc/sphinx/source/api
prune doc/sphinx/source/examples
PySpice-pr-191/PySpice/ 0000775 0000000 0000000 00000000000 13654121544 0014742 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Config/ 0000775 0000000 0000000 00000000000 13654121544 0016147 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Config/ConfigInstall.py 0000664 0000000 0000000 00000003312 13654121544 0021254 0 ustar 00root root 0000000 0000000 ####################################################################################################
import os
import sys
####################################################################################################
import PySpice.Tools.Path as PathTools # Fixme: why ?
####################################################################################################
class OsFactory:
##############################################
def __init__(self):
if sys.platform.startswith('linux'):
self._name = 'linux'
elif sys.platform.startswith('win'):
self._name = 'windows'
elif sys.platform.startswith('darwin'):
self._name = 'osx'
##############################################
@property
def name(self):
return self._name
@property
def on_linux(self):
return self._name == 'linux'
@property
def on_windows(self):
return self._name == 'windows'
@property
def on_osx(self):
return self._name == 'osx'
OS = OsFactory()
####################################################################################################
_this_file = PathTools.to_absolute_path(__file__)
class Path:
pyspice_module_directory = PathTools.parent_directory_of(_this_file, step=2)
config_directory = os.path.dirname(_this_file)
####################################################################################################
class Logging:
default_config_file = 'logging.yml'
directories = (Path.config_directory,)
##############################################
@staticmethod
def find(config_file):
return PathTools.find(config_file, Logging.directories)
PySpice-pr-191/PySpice/Config/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0020246 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Config/logging.yml 0000664 0000000 0000000 00000002213 13654121544 0020316 0 ustar 00root root 0000000 0000000 ####################################################################################################
version: 1
####################################################################################################
formatters:
simple:
format: '%(asctime)s - %(name)s - %(module)s.%(levelname)s - %(message)s'
ansi:
# RESET_SEQ = "\033[0m"
# COLOR_SEQ = "\033[1;%dm"
# BOLD_SEQ = "\033[1m"
format: '[1;32m%(asctime)s[0m - [1;34m%(name)s.%(funcName)s[0m - [1;31m%(levelname)s[0m - %(message)s'
####################################################################################################
handlers:
console:
class: logging.StreamHandler
# level: INFO
# formatter: ansi
stream: ext://sys.stdout
####################################################################################################
root:
#level: DEBUG
level: INFO
#level: WARNING
handlers: [console]
####################################################################################################
# loggers:
# PySpice:
# level: DEBUG
# #level: INFO
# #level: WARNING
# handlers: [console]
PySpice-pr-191/PySpice/Doc/ 0000775 0000000 0000000 00000000000 13654121544 0015447 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Doc/ExampleTools.py 0000664 0000000 0000000 00000003534 13654121544 0020442 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import logging
import os
import sys
from PySpice.Tools.Path import parent_directory_of
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
def find_libraries():
try:
library_path = os.environ['PySpiceLibraryPath']
except KeyError:
# Fixme: only works for one level
python_file = os.path.abspath(sys.argv[0])
examples_root = parent_directory_of(python_file, step=2)
# .../PySpice/examples/diode/__example_rst_factory__nlrrr2fh.py .../PySpice/examples
library_path = os.path.join(examples_root, 'libraries')
_module_logger.info('SPICE library path is {}'.format(library_path))
return library_path
PySpice-pr-191/PySpice/Doc/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0017546 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Logging/ 0000775 0000000 0000000 00000000000 13654121544 0016330 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Logging/Logging.py 0000664 0000000 0000000 00000005233 13654121544 0020273 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import yaml
import logging
import logging.config
import os
####################################################################################################
import PySpice.Config.ConfigInstall as ConfigInstall
####################################################################################################
def setup_logging(application_name='PySpice',
config_file=ConfigInstall.Logging.default_config_file,
logging_level=None):
"""Setup the logging.
Logging configuration is set by a YAML file given by *config_file*. Alternatively we can set
the logging level using the environment variable 'PySpiceLogLevel' or using *logging_level*,
level can be a integer or a string
"""
logging_config_file_name = ConfigInstall.Logging.find(config_file)
logging_config = yaml.load(open(logging_config_file_name, 'r'))
if ConfigInstall.OS.on_linux:
# Fixme: \033 is not interpreted in YAML
formatter_config = logging_config['formatters']['ansi']['format']
logging_config['formatters']['ansi']['format'] = formatter_config.replace('', '\033')
if ConfigInstall.OS.on_windows:
formatter = 'simple'
else:
formatter = 'ansi'
logging_config['handlers']['console']['formatter'] = formatter
logging.config.dictConfig(logging_config)
logger = logging.getLogger(application_name)
if logging_level:
logger.setLevel(logging_level)
elif 'PySpiceLogLevel' in os.environ: # used by tools/make-examples
level = getattr(logging, os.environ['PySpiceLogLevel'], None)
logger.setLevel(level) # level can be int or string
return logger
PySpice-pr-191/PySpice/Logging/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0020427 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Math/ 0000775 0000000 0000000 00000000000 13654121544 0015633 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Math/Calculus.py 0000664 0000000 0000000 00000014731 13654121544 0017766 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module provides algorithms to compute the derivative of a function sampled on an uniform
grid.
"""
####################################################################################################
import fractions
import numpy as np
####################################################################################################
from PySpice.Math import odd
####################################################################################################
def compute_exact_finite_difference_coefficients(derivative_order, grid, x0=0):
"""This function compute the finite difference coefficients for the given derivative order and
grid. The parameter *x* specifies where is computed the derivative on the grid. The grid is
given as a list of integer offsets.
The algorithm is derived from the article: Generation of Finite Difference Formulas on Arbitrary Space
Grids, Bengt Fornberg, Mathematics of computation, volume 51, number 184, october 1988
"""
N = len(grid)
# d[m,n,v]
d = [[[0
for v in range(N)]
for n in range(N)]
for m in range(derivative_order +1)]
d[0][0][0] = fractions.Fraction(1,1)
c1 = 1
for n in range(1, N):
c2 = 1
for v in range(n):
c3 = grid[n] - grid[v]
c2 *= c3
if n <= derivative_order:
d[n][n-1][v] = 0
for m in range(min(n, derivative_order) +1):
d[m][n][v] = ( (grid[n] - x0)*d[m][n-1][v] - m*d[m-1][n-1][v] ) / c3
for m in range(min(n, derivative_order) +1):
d[m][n][n] = fractions.Fraction(c1,c2)*( m*d[m-1][n-1][n-1] - (grid[n-1] - x0)*d[m][n-1][n-1] )
c1 = c2
return d[-1][-1]
####################################################################################################
def compute_finite_difference_coefficients(derivative_order, grid):
return [float(x) for x in compute_exact_finite_difference_coefficients(derivative_order, grid)]
####################################################################################################
_coefficient_cache = dict(centred={}, forward={}, backward={})
def get_finite_difference_coefficients(derivative_order, accuracy_order, grid_type):
if derivative_order < 1:
raise ValueError("Wrong derivative order")
if odd(accuracy_order) or accuracy_order < 2:
raise ValueError("Wrong accuracy order")
if grid_type == 'centred':
window_size = accuracy_order // 2
grid = list(range(-window_size, window_size +1))
elif grid_type == 'forward':
grid = list(range(derivative_order + accuracy_order))
elif grid_type == 'backward':
grid = list(range(-(derivative_order + accuracy_order) +1, 1))
grid = list(reversed(grid)) # Fixme: why ?
else:
raise ValueError("Wrong grid type")
key = '{}-{}'.format(derivative_order, accuracy_order)
coefficients = _coefficient_cache[grid_type].get(key, None)
if coefficients is None:
coefficients = compute_finite_difference_coefficients(derivative_order, grid)
_coefficient_cache[grid_type][key] = coefficients
return grid, coefficients
####################################################################################################
def simple_derivative(x, values):
""" Compute the derivative as a simple slope. """
return x[:-1], np.diff(values)/np.diff(x)
####################################################################################################
def derivative(x, values, derivative_order=1, accuracy_order=4):
"""Compute the derivative at the given derivative order and accuracy order. The precision of the
Taylor expansion is :math:`\mathcal{O}(dx^{accuracy})`.
"""
dx = np.diff(x)
# if not np.all(dx == dx[0]):
# raise ValueError("Sampling is not uniform")
dx = dx[0]
values_size, = values.shape
derivative = np.zeros(values_size, dtype=values.dtype)
grid, coefficients = get_finite_difference_coefficients(derivative_order, accuracy_order, 'centred')
window_size = grid[-1]
# print grid, coefficients
vector_size = values_size - 2*window_size
if not vector_size:
raise ValueError("The size of the value's array is not sufficient for the given accuracy order")
lower_index = window_size
upper_index = values_size - window_size
derivative_view = derivative[window_size:-window_size]
for offset, coefficient in zip(grid, coefficients):
if coefficient:
# print offset, lower_index + offset, upper_index + offset
derivative_view += values[lower_index + offset:upper_index + offset] * coefficient
grid, coefficients = get_finite_difference_coefficients(derivative_order, accuracy_order, 'forward')
# print grid, coefficients
grid_size = len(grid)
upper_index = window_size
derivative_view = derivative[:window_size]
for offset, coefficient in zip(grid, coefficients):
# print offset, offset, window_size+offset
derivative_view += values[offset:upper_index + offset] * coefficient
grid, coefficients = get_finite_difference_coefficients(derivative_order, accuracy_order, 'backward')
# print grid, coefficients
grid_size = len(grid)
lower_index = values_size - window_size
upper_index = values_size
derivative_view = derivative[-window_size:]
for offset, coefficient in zip(grid, coefficients):
# print offset, lower_index + offset, upper_index + offset
derivative_view += values[lower_index + offset:upper_index + offset] * coefficient
return derivative / dx**derivative_order
PySpice-pr-191/PySpice/Math/__init__.py 0000664 0000000 0000000 00000003117 13654121544 0017746 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module implements mathematical functions.
"""
####################################################################################################
import math
####################################################################################################
def odd(x):
"""Return True is *x* is odd"""
return x & 1
def even(x):
"""Return True is *x* is even"""
return not(odd(x))
####################################################################################################
def rms_to_amplitude(x):
"""Return :math:`x \sqrt{2}`"""
return x * math.sqrt(2)
def amplitude_to_rms(x):
"""Return :math:`x / \sqrt{2}`"""
return x / math.sqrt(2)
PySpice-pr-191/PySpice/Physics/ 0000775 0000000 0000000 00000000000 13654121544 0016364 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Physics/MaterialProperties.py 0000664 0000000 0000000 00000003041 13654121544 0022547 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
class Copper:
atomic_number = 29
atomic_mass = 63.546 * 1e-3 # kg
density = 8.96 * 1e3 # kg·m−3
thermal_conductivity = 401 # W·m−1·K−1
electrical_resistivity = 16.78 * 1e-9 # Ω·m @20 °C
electron_mobility = - 4.6 * 1e3 # m2·V−1·s−1
##############################################
def electrical_resistance_for_conductor(self, degree):
""" Used to compute conductor resistance. """
rho0 = 16e-3 # Ω·m·mm−2
return rho0 * (1 + .00393 * degree)
PySpice-pr-191/PySpice/Physics/PhysicalConstants.py 0000664 0000000 0000000 00000005156 13654121544 0022416 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
#
# Physical Constants from Particle Data Group 2013
# http://pdg.lbl.gov/2013/reviews/rpp2013-rev-phys-constants.pdf
#
####################################################################################################
####################################################################################################
pi = 3.141592653589793238 # π = 3.141 592 653 589 793 238
####################################################################################################
speed_of_light_in_vacuum = c = 299792458 # 299 792 458 m s−1
electron_charge_magnitude = e = q = 1.602176565e-19 # 1.602 176 565(35)×10−19 C = 4.803 204 50(11)×10−10 esu
permeability_of_free_space = mu0 = 4*pi*1e-7 # 4π × 10−7 N A−2 = 12.566 370 614 ... ×10−7 N A−2
permittivity_of_free_space = epsilon0 = 1./(mu0*c**2) # 8.854 187 817 ... ×10−12 F m −1
avogadro_constant = Na = 6.02214129e23 # 6.022 141 29(27)×1023 mol−1
boltzmann_constant = k = 1.3806488e-23 # 1.380 6488(13)×10−23 J K−1 = 8.617 3324(78)×10−5 eV K−1
# 1 eV = 1.602 176 565(35) × 10−19 J
# 1 eV/c2 = 1.782 661 845(39) × 10−36 kg
####################################################################################################
# 0 ◦C ≡ 273.15 K
def degree_to_kelvin(x):
return x + 273.15
def kelvin_to_degree(x):
return x - 273.15
def temperature(degree=None, kelvin=None):
if degree is not None:
return degree_to_kelvin(degree)
else:
return kelvin
# kT at 300 K = [38.681 731(35)]−1 eV
def kT(degree=None, kelvin=None):
return k*temperature(degree=degree, kelvin=kelvin)
PySpice-pr-191/PySpice/Physics/Resistor.py 0000664 0000000 0000000 00000002531 13654121544 0020551 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
#
# Notes: Drude model, Nernst-Einstein law
#
####################################################################################################
####################################################################################################
def conductor_resistance(resistivity, length, section):
return resistivity * length / section
PySpice-pr-191/PySpice/Physics/SemiConductor.py 0000664 0000000 0000000 00000004337 13654121544 0021523 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
""" This module provides semiconductor models.
"""
####################################################################################################
import numpy as np
####################################################################################################
import PySpice.Physics.PhysicalConstants as Cst
####################################################################################################
class ShockleyDiode:
""" This class provides an implementation of the Shockley Diode Model.
"""
##############################################
def __init__(self,
Is=10e-12, # 10 pA
n=1,
degree=25, kelvin=None):
self.Is = Is # reverse bias saturation current
self.n = n # ideality factor or emission coefficient
self.T = Cst.temperature(degree=degree, kelvin=kelvin)
##############################################
@property
def Vt(self):
""" Thermal Voltage """
return Cst.kT(kelvin=self.T) / Cst.q
##############################################
def I(self, Vd):
return self.Is*(np.exp(Vd/(self.n*self.Vt)) - 1)
##############################################
def rd(self, Vd):
""" Dynamic resistance defined by dVd/dI. """
return self.n*self.Vt/self.I(Vd)
PySpice-pr-191/PySpice/Physics/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0020463 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Plot/ 0000775 0000000 0000000 00000000000 13654121544 0015660 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Plot/BodeDiagram.py 0000664 0000000 0000000 00000004517 13654121544 0020377 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module provides helpers to plot Bode diagrams using Matplolib.
Frequency is in Hz, gain in dB, phase in radians between -π and π.
"""
####################################################################################################
import math
from matplotlib import pyplot
####################################################################################################
def bode_diagram_gain(axe, frequency, gain, **kwargs):
axe.semilogx(frequency, gain, basex=10, **kwargs)
axe.grid(True)
axe.grid(True, which='minor')
axe.set_xlabel("Frequency [Hz]")
axe.set_ylabel("Gain [dB]")
####################################################################################################
def bode_diagram_phase(axe, frequency, phase, **kwargs):
axe.semilogx(frequency, phase, basex=10, **kwargs)
axe.set_ylim(-math.pi, math.pi)
axe.grid(True)
axe.grid(True, which='minor')
axe.set_xlabel("Frequency [Hz]")
axe.set_ylabel("Phase [rads]")
# axe.set_yticks # Fixme:
pyplot.yticks((-math.pi, -math.pi/2,0, math.pi/2, math.pi),
(r"$-\pi$", r"$-\frac{\pi}{2}$", "0", r"$\frac{\pi}{2}$", r"$\pi$"))
####################################################################################################
def bode_diagram(axes, frequency, gain, phase, **kwargs):
bode_diagram_gain(axes[0], frequency, gain, **kwargs)
bode_diagram_phase(axes[1], frequency, phase, **kwargs)
PySpice-pr-191/PySpice/Plot/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0017757 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Probe/ 0000775 0000000 0000000 00000000000 13654121544 0016011 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Probe/Plot.py 0000664 0000000 0000000 00000003263 13654121544 0017305 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
# Fixme: versus PySpice.Plot ???
####################################################################################################
"""This module implements plotting helper."""
####################################################################################################
import matplotlib.pyplot as plt
####################################################################################################
def plot(waveform, *args, **kwargs):
"""Plot a waveform using the current Axes instance or the one specified by the *axis* key
argument. Additional parameters are passed to the Matplotlib plot function.
"""
axis = kwargs.get('axis', plt.gca())
if 'axis' in kwargs:
del kwargs['axis']
axis.plot(waveform.abscissa, waveform, *args, **kwargs)
PySpice-pr-191/PySpice/Probe/WaveForm.py 0000664 0000000 0000000 00000033452 13654121544 0020120 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
"""This module implements classes to handle analysis output.
"""
####################################################################################################
import logging
import os
import numpy as np
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
from PySpice.Unit.Unit import UnitValues
####################################################################################################
class WaveForm(UnitValues):
"""This class implements waveform on top of a Numpy Array.
Public Attributes:
:attr:`name`
:attr:`unit`
:attr:`title`
:attr:`abscissa`
Numpy array of the analysis abscissa
"""
_logger = _module_logger.getChild('WaveForm')
##############################################
@classmethod
def from_unit_values(cls, name, array, title=None, abscissa=None):
obj = cls(
name,
array.prefixed_unit,
array.shape,
dtype=array.dtype,
title=title,
abscissa=abscissa,
)
obj[...] = array[...]
return obj
##############################################
@classmethod
def from_array(cls, name, array, title=None, abscissa=None):
# Fixme: ok ???
obj = cls(name, None, array.shape, title=title, abscissa=abscissa)
obj[...] = array[...]
return obj
##############################################
def __new__(cls, name, prefixed_unit,
shape, dtype=float, buffer=None, offset=0, strides=None, order=None,
title=None, abscissa=None):
# cls._logger.info(str((cls, prefixed_unit, shape, dtype, buffer, offset, strides, order)))
obj = super(WaveForm, cls).__new__(cls, prefixed_unit, shape, dtype, buffer, offset, strides, order)
# obj = np.asarray(data).view(cls)
obj._name = str(name)
obj._title = title # str(title)
obj._abscissa = abscissa
return obj
##############################################
def __array_finalize__(self, obj):
# self._logger.info('\n {}'.format(obj))
# Fixme: ??? else _prefixed_unit is not set
super().__array_finalize__(obj)
if obj is None:
return
self._name = getattr(obj, 'name', None)
self._title = getattr(obj, 'title', None)
self._abscissa = getattr(obj, 'abscissa', None)
##############################################
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
# Fixme: check abscissa
result = super().__array_ufunc__(ufunc, method, *inputs, **kwargs)
# self._logger.info("result\n{}".format(result))
if isinstance(result, UnitValues):
return self.from_unit_values(name='', array=result, title='', abscissa=self._abscissa)
else:
return result # e.g. foo <= 0
##############################################
@property
def name(self):
return self._name
@property
def abscissa(self):
return self._abscissa
@property
def title(self):
return self._title
@title.setter
def title(self, value):
self._title = value
##############################################
def __repr__(self):
return '{0.__class__.__name__} {0._name} {1}'.format(self, super().__str__())
##############################################
def __str__(self):
if self._title is not None:
return self._title
else:
return self._name
##############################################
def str_data(self):
# Fixme: ok ???
return repr(self.as_ndarray())
####################################################################################################
class Analysis:
"""Base class for the simulation output.
Depending of the simulation type, the simulator will return waveforms as a function of
* time
* frequency
* sweep
* ...
and corresponding to
* a node's voltage
* a source's current
* ...
The name of a waveform is
* node's voltage: node's name
* source's current: source'name
* ...
If the waveform name is a valid Python identifier, then you can get the corresponding waveform using::
analysis.waveforme_name
else you have to use this fallback::
analysis['waveforme_name']
Examples of usages::
# Operating point analysis
for node in analysis.nodes.values():
print('Node {}: {:5.2f} V'.format(str(node), float(node)))
for node in analysis.branches.values():
print('Node {}: {:5.2f} A'.format(str(node), float(node)))
# DC sensitivity analysis
for element in analysis.elements.values():
print(element, float(element))
# Plot the voltage of the "out" node
plt.plot(analysis.out.abscissa, analysis.out)
Public Attributes:
:attr:`nodes`
Dictionary for node voltages indexed by node names
:attr:`branches`
Dictionary for branch currents indexed by source names
:attr:`elements`
Dictionary for elements ...
"""
##############################################
def __init__(self, simulation, nodes=(), branches=(), elements=(), internal_parameters=()):
# Fixme: branches are elements in fact, and elements is not yet supported ...
self._simulation = simulation
# Fixme: to func?
self._nodes = {waveform.name:waveform for waveform in nodes}
self._branches = {waveform.name:waveform for waveform in branches}
self._elements = {waveform.name:waveform for waveform in elements}
self._internal_parameters = {waveform.name:waveform for waveform in internal_parameters}
##############################################
@property
def simulation(self):
"""Return the simulation instance"""
return self._simulation
@property
def nodes(self):
return self._nodes
@property
def branches(self):
return self._branches
@property
def elements(self):
return self._elements
@property
def internal_parameters(self):
return self._internal_parameters
##############################################
def _get_item(self, name):
# Fixme: cache dict ???
if name in self._nodes:
return self._nodes[name]
elif name in self._branches:
return self._branches[name]
elif name in self._elements:
return self._elements[name]
elif name in self._internal_parameters:
return self._internal_parameters[name]
else:
raise IndexError(name)
##############################################
def __getitem__(self, name):
try:
return self._get_item(name)
except IndexError:
return self._get_item(name.lower())
##############################################
@staticmethod
def _format_dict(d):
return os.linesep.join([' '*2 + str(x) for x in d])
##############################################
def __getattr__(self, name):
try:
return self.__getitem__(name)
except IndexError:
raise AttributeError(name + os.linesep +
'Nodes :' + os.linesep + self._format_dict(self._nodes) + os.linesep +
'Branches :' + os.linesep + self._format_dict(self._branches) + os.linesep +
'Elements :' + os.linesep + self._format_dict(self._elements) + os.linesep +
'Internal Parameters :' + os.linesep + self._format_dict(self._internal_parameters)
)
####################################################################################################
class OperatingPoint(Analysis):
"""This class implements an operating point analysis."""
pass
####################################################################################################
class SensitivityAnalysis(Analysis):
"""This class implements an sensitivity analysis."""
##############################################
def __init__(self, simulation, elements, internal_parameters):
super().__init__(simulation=simulation, elements=elements,
internal_parameters=internal_parameters)
####################################################################################################
class DcAnalysis(Analysis):
"""This class implements a DC analysis.
When the DC analysis is performed with multiple sources, sweep is the last source.
The loop scheme is::
for v1 in vsource1:
for v2 in vsource2:
...
"""
##############################################
def __init__(self, simulation, sweep, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._sweep = sweep
##############################################
@property
def sweep(self):
"""Return an Numpy array for the sweep abscissa"""
return self._sweep
####################################################################################################
class AcAnalysis(Analysis):
"""This class implements an AC analysis."""
##############################################
def __init__(self, simulation, frequency, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._frequency = frequency
##############################################
@property
def frequency(self):
"""Return an Numpy array for the frequency abscissa"""
return self._frequency
####################################################################################################
class TransientAnalysis(Analysis):
"""This class implements a transient analysis."""
##############################################
def __init__(self, simulation, time, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._time = time
##############################################
@property
def time(self):
"""Return an Numpy array for the time abscissa"""
return self._time
####################################################################################################
class PoleZeroAnalysis(Analysis):
"""This class implements a Pole-Zero analysis."""
##############################################
def __init__(self, simulation, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
####################################################################################################
class NoiseAnalysis(Analysis):
"""This class implements Noise analysis."""
##############################################
def __init__(self, simulation, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
####################################################################################################
class DistortionAnalysis(Analysis):
"""This class implements Distortion analysis."""
##############################################
def __init__(self, simulation, frequency, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._frequency = frequency
@property
def frequency(self):
"""Return an Numpy array for the frequency abscissa"""
return self._frequency
####################################################################################################
class TransferFunctionAnalysis(Analysis):
"""This class implements Transfer Function (TF) analysis."""
##############################################
def __init__(self, simulation, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
####################################################################################################
PySpice-pr-191/PySpice/Probe/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0020110 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/ 0000775 0000000 0000000 00000000000 13654121544 0016005 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/BasicElement.py 0000664 0000000 0000000 00000120732 13654121544 0020717 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module implements SPICE circuit elements.
.. warning:: Some elements are partially implemented.
.. warning:: The API documentation generated by Sphinx is perfectible for this module. The source
code can be more informative.
.. note:: It would be nice to have a useful and working documentation in the interactive environment.
The element's parameters are internally implemented using class attributes and descriptors.
There are two types of parameters, positionals which are required and where the order mater, and
optional parameters which are passed as keyword arguments.
Parameters are registered with more expressive and longer names than their Spice counterparts. For
optional parameters, we can use both Spice and longer name for convenience.
See Ngspice documentation for details.
+--------------+------------------------------------------------------+
| First letter + Element description |
+--------------+------------------------------------------------------+
| A + XSPICE code model |
+--------------+------------------------------------------------------+
| B + Behavioral (arbitrary) source |
+--------------+------------------------------------------------------+
| C + Capacitor |
+--------------+------------------------------------------------------+
| D + Diode |
+--------------+------------------------------------------------------+
| E + Voltage-controlled voltage source (VCVS) |
+--------------+------------------------------------------------------+
| F + Current-controlled current source (CCCs) |
+--------------+------------------------------------------------------+
| G + Voltage-controlled current source (VCCS) |
+--------------+------------------------------------------------------+
| H + Current-controlled voltage source (CCVS) |
+--------------+------------------------------------------------------+
| I + Current source |
+--------------+------------------------------------------------------+
| J + Junction field effect transistor (JFET) |
+--------------+------------------------------------------------------+
| K + Coupled (Mutual) Inductors |
+--------------+------------------------------------------------------+
| L + Inductor |
+--------------+------------------------------------------------------+
| M + Metal oxide field effect transistor (MOSFET) |
+--------------+------------------------------------------------------+
| N + Numerical device for GSS |
+--------------+------------------------------------------------------+
| O + Lossy transmission line |
+--------------+------------------------------------------------------+
| P + Coupled multiconductor line (CPL) |
+--------------+------------------------------------------------------+
| Q + Bipolar junction transistor (BJT) |
+--------------+------------------------------------------------------+
| R + Resistor |
+--------------+------------------------------------------------------+
| S + Switch (voltage-controlled) |
+--------------+------------------------------------------------------+
| T + Lossless transmission line |
+--------------+------------------------------------------------------+
| U + Uniformly distributed RC line |
+--------------+------------------------------------------------------+
| V + Voltage source |
+--------------+------------------------------------------------------+
| W + Switch (current-controlled) |
+--------------+------------------------------------------------------+
| X + Subcircuit |
+--------------+------------------------------------------------------+
| Y + Single lossy transmission line (TXL) |
+--------------+------------------------------------------------------+
| Z + Metal semiconductor field effect transistor (MESFET) |
+--------------+------------------------------------------------------+
"""
####################################################################################################
from ..Tools.StringTools import str_spice, join_list, join_dict
from ..Unit import U_m, U_s, U_A, U_V, U_Degree, U_Ω, U_F, U_H, U_Hz
from .Netlist import (Element, AnyPinElement, FixedPinElement, NPinElement, OptionalPin)
from .ElementParameter import (
# KeyValueParameter,
BoolKeyParameter,
ElementNamePositionalParameter,
ExpressionKeyParameter,
ExpressionPositionalParameter,
FlagParameter,
FloatKeyParameter,
FloatPairKeyParameter,
FloatTripletKeyParameter,
FloatPositionalParameter,
InitialStatePositionalParameter,
IntKeyParameter,
ModelPositionalParameter,
)
####################################################################################################
class DipoleElement(FixedPinElement):
"""This class implements a base class for dipole element."""
__pins__ = ('plus', 'minus')
class TwoPortElement(FixedPinElement):
"""This class implements a base class for two-port element."""
__pins__ = ('output_plus', 'output_minus', 'input_plus', 'input_minus')
####################################################################################################
class SubCircuitElement(NPinElement):
"""This class implements a sub-circuit.
Spice syntax:
.. code-block:: none
XYYYYYY node1 node2 ... subcircuit_name parameter1=value1 ...
Attributes:
:attr:`subcircuit_name`
.. note:: As opposite to Spice, the circuit's name is specified before the nodes so as to act as `*args`.
"""
__alias__ = 'X'
__prefix__ = 'X'
subcircuit_name = ElementNamePositionalParameter(position=0, key_parameter=False)
##############################################
def __init__(self, netlist, name, subcircuit_name, *nodes, **parameters):
super().__init__(netlist, name, nodes, subcircuit_name)
# Fixme: match parameters to subcircuit
self.parameters = parameters
# Fixme: investigate
# for key, value in parameters.items():
# parameter = KeyValueParameter(key)
# parameter.__set__(self, value)
# self.optional_parameters[key] = parameter
# setattr(self, key, parameter)
##############################################
def copy_to(self, netlist):
element = self.__class__(netlist, self._name, self.subcircuit_name, *self.node_names, **self.parameters)
# Element.copy_to(self, element)
return element
##############################################
def format_spice_parameters(self):
""" Return the formatted list of parameters. """
spice_parameters = super().format_spice_parameters()
if self.parameters:
spice_parameters += ' ' + join_dict(self.parameters)
return spice_parameters
####################################################################################################
#
# Elementary devices: Resistor, Capacitor, Inductor, Switch (VCSw/CCSw)
#
####################################################################################################
class Resistor(DipoleElement):
"""This class implements a resistor.
Spice syntax:
.. code-block:: none
RXXXXXXX n+ n- value
Keyword Parameters:
:attr:`ac`
:attr:`multiplier`
alias `m`
:attr:`scale`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
:attr:`noisy`
Attributes:
:attr:`resistance`
:attr:`ac`
:attr:`multiplier`
:attr:`scale`
:attr:`temperature`
:attr:`device_temperature`
:attr:`noisy`
"""
__alias__ = 'R'
__prefix__ = 'R'
resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω)
ac = FloatKeyParameter('ac', unit=U_Ω)
multiplier = IntKeyParameter('m')
scale = FloatKeyParameter('scale')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
noisy = BoolKeyParameter('noisy')
####################################################################################################
class SemiconductorResistor(DipoleElement):
"""This class implements a Semiconductor resistor.
Spice syntax:
.. code-block:: none
RXXXXXXX n+ n- m=
Keyword Parameters:
:attr:`model`
:attr:`length`
alias `l`
:attr:`width`
alias `w`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
:attr:`multiplier`
alias `m`
:attr:`ac`
:attr:`scale`
:attr:`noisy`
Attributes:
:attr:`resistance`
:attr:`model`
:attr:`length`
:attr:`width`
:attr:`temperature`
:attr:`device_temperature`
:attr:`multiplier`
:attr:`ac`
:attr:`scale`
:attr:`noisy`
"""
__alias__ = 'SemiconductorResistor'
__prefix__ = 'R'
resistance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_Ω)
model = ModelPositionalParameter(position=1, key_parameter=True)
length = FloatKeyParameter('l', unit=U_m)
width = FloatKeyParameter('w', unit=U_m)
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
multiplier = IntKeyParameter('m')
ac = FloatKeyParameter('ac', unit=U_Ω)
scale = FloatKeyParameter('scale')
noisy = BoolKeyParameter('noisy')
####################################################################################################
class BehavioralResistor(DipoleElement):
# Behavioral / Behavioural
"""This class implements a behavioral resistor.
Spice syntax:
.. code-block:: none
RXXXXXXX n+ n- 'expression'
Rxxxxxxx n+ n- R='expression'
Keyword Parameters:
:attr:`tc1`
:attr:`tc2`
Attributes:
:attr:`resistance_expression`
:attr:`tc1`
:attr:`tc2`
"""
__alias__ = 'BehavioralResistor'
__prefix__ = 'R'
resistance_expression = ExpressionPositionalParameter(position=0, key_parameter=False)
tc1 = FloatKeyParameter('tc1')
tc2 = FloatKeyParameter('tc2')
####################################################################################################
class Capacitor(DipoleElement):
"""This class implements a capacitor.
Spice syntax:
.. code-block:: none
CXXXXXXX n+ n-
Keyword Parameters:
:attr:`model`
:attr:`multiplier`
alias `m`
:attr:`scale`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
:attr:`initial_condition`
alias `ic`
Attributes:
:attr:`capacitance`
:attr:`model`
:attr:`multiplier`
:attr:`scale`
:attr:`temperature`
:attr:`device_temperature`
:attr:`initial_condition`
"""
__alias__ = 'C'
__prefix__ = 'C'
capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F)
model = ModelPositionalParameter(position=1, key_parameter=True)
multiplier = IntKeyParameter('m')
scale = FloatKeyParameter('scale')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
initial_condition = FloatKeyParameter('ic')
####################################################################################################
class SemiconductorCapacitor(DipoleElement):
"""This class implements a semiconductor capacitor.
Spice syntax:
.. code-block:: none
CXXXXXXX n+ n-
Keyword Parameters:
:attr:`model`
:attr:`length`
alias `l`
:attr:`width`
alias `w`
:attr:`multiplier`
alias `m`
:attr:`scale`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
:attr:`initial_condition`
alias `ic`
Attributes:
:attr:`capacitance`
:attr:`model`
:attr:`length`
:attr:`width`
:attr:`multiplier`
:attr:`scale`
:attr:`temperature`
:attr:`device_temperature`
:attr:`initial_condition`
"""
__alias__ = 'SemiconductorCapacitor'
__prefix__ = 'C'
capacitance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_F)
model = ModelPositionalParameter(position=1, key_parameter=True)
length = FloatKeyParameter('l', unit=U_m)
width = FloatKeyParameter('w', unit=U_m)
multiplier = IntKeyParameter('m')
scale = FloatKeyParameter('scale')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
initial_condition = FloatKeyParameter('ic')
####################################################################################################
class BehavioralCapacitor(DipoleElement):
"""This class implements a behavioral capacitor.
Spice syntax:
.. code-block:: none
CXXXXXXX n+ n- 'expression'
CXXXXXXX n+ n- C='expression'
Keyword Parameters:
:attr:`tc1`
:attr:`tc2`
Attributes:
:attr:`capacitance_expression`
:attr:`tc1`
:attr:`tc2`
"""
__alias__ = 'BehavioralCapacitor'
__prefix__ = 'C'
capacitance_expression = ExpressionPositionalParameter(position=0, key_parameter=False)
tc1 = FloatKeyParameter('tc1')
tc2 = FloatKeyParameter('tc2')
####################################################################################################
class Inductor(DipoleElement):
"""This class implements an inductor.
Spice syntax:
.. code-block:: none
LYYYYYYY n+ n-
Keyword Parameters:
:attr:`nt`
:attr:`multiplier`
alias `m`
:attr:`scale`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
:attr:`initial_condition`
alias `ic`
Attributes:
:attr:`inductance`
:attr:`model`
:attr:`nt`
:attr:`multiplier`
:attr:`scale`
:attr:`temperature`
:attr:`device_temperature`
:attr:`initial_condition`
"""
__alias__ = 'L'
__prefix__ = 'L'
inductance = FloatPositionalParameter(position=0, key_parameter=False, unit=U_H)
model = ModelPositionalParameter(position=1, key_parameter=True)
nt = FloatKeyParameter('nt')
multiplier = IntKeyParameter('m')
scale = FloatKeyParameter('scale')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
initial_condition = FloatKeyParameter('ic')
####################################################################################################
class BehavioralInductor(DipoleElement):
"""This class implements a behavioral inductor.
Spice syntax:
.. code-block:: none
LXXXXXXX n+ n- 'expression'
LXXXXXXX n+ n- L='expression'
Keyword Parameters:
:attr:`tc1`
:attr:`tc2`
Attributes:
:attr:`inductance_expression`
:attr:`tc1`
:attr:`tc2`
"""
__alias__ = 'BehavioralInductor'
__prefix__ = 'L'
inductance_expression = ExpressionPositionalParameter(position=0, key_parameter=False)
tc1 = FloatKeyParameter('tc1')
tc2 = FloatKeyParameter('tc2')
####################################################################################################
class CoupledInductor(AnyPinElement):
"""This class implementss a coupled (mutual) inductors.
Spice syntax:
.. code-block:: none
KXXXXXXX LYYYYYYY LZZZZZZZ value
Keyword Parameters:
Attributes:
:attr:`inductor1`
:attr:`inductor2`
:attr:`coupling_factor`
"""
__alias__ = 'K'
__prefix__ = 'K'
inductor1 = ElementNamePositionalParameter(position=0, key_parameter=False)
inductor2 = ElementNamePositionalParameter(position=1, key_parameter=False)
coupling_factor = FloatPositionalParameter(position=2, key_parameter=False)
##############################################
def __init__(self, name, *args, **kwargs):
super().__init__(name, *args, **kwargs)
self._inductors = (self.inductor1, self.inductor2)
####################################################################################################
class VoltageControlledSwitch(TwoPortElement):
"""This class implements a voltage controlled switch.
Spice syntax:
.. code-block:: none
SXXXXXXX n+ n- nc+ nc- model
Keyword Parameters:
:attr:`model`
:attr:`initial_state`
Attributes:
:attr:`model`
:attr:`initial_state`
"""
__alias__ = 'S'
__long_alias__ = 'VCS'
__prefix__ = 'S'
model = ModelPositionalParameter(position=0, key_parameter=True)
initial_state = InitialStatePositionalParameter(position=1, key_parameter=True)
####################################################################################################
class CurrentControlledSwitch(DipoleElement):
"""This class implements a current controlled switch.
Spice syntax:
.. code-block:: none
WYYYYYYY n+ n- vname model
Keyword Parameters:
:attr:`source`
:attr:`model`
:attr:`initial_state`
Attributes:
:attr:`source`
:attr:`model`
:attr:`initial_state`
"""
__alias__ = 'W'
__long_alias__ = 'CCS'
__prefix__ = 'W'
source = ElementNamePositionalParameter(position=0, key_parameter=True)
model = ModelPositionalParameter(position=1, key_parameter=True)
initial_state = InitialStatePositionalParameter(position=2, key_parameter=True)
####################################################################################################
#
# Voltage and Current Sources
#
####################################################################################################
class VoltageSource(DipoleElement):
"""This class implements an independent sources for voltage.
Spice syntax:
.. code-block:: none
VXXXXXXX n+ n- < dc/tran value> >> >> >>
Keyword Parameters:
Attributes:
:attr:`dc_value`
"""
__alias__ = 'V'
__prefix__ = 'V'
# Fixme: ngspice manual doesn't describe well the syntax
dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_V)
####################################################################################################
class CurrentSource(DipoleElement):
"""This class implements an independent sources for current.
Spice syntax:
.. code-block:: none
IYYYYYYY n+ n- < dc/tran value> >> >> >>
Keyword Parameters:
Attributes:
:attr:`dc_value`
"""
__alias__ = 'I'
__prefix__ = 'I'
# Fixme: ngspice manual doesn't describe well the syntax
dc_value = FloatPositionalParameter(position=0, key_parameter=False, unit=U_A)
####################################################################################################
class VoltageControlledCurrentSource(TwoPortElement):
"""This class implements a linear voltage-controlled current sources (VCCS).
Spice syntax:
.. code-block:: none
Gxxx n+ n- nc+ nc- value
Keyword Parameters:
:attr:`multiplier`
alias `m`
Attributes:
:attr:`transconductance`
"""
__alias__ = 'VCCS'
__prefix__ = 'G'
transconductance = ExpressionPositionalParameter(position=0, key_parameter=False)
multiplier = IntKeyParameter('m')
####################################################################################################
class VoltageControlledVoltageSource(TwoPortElement):
"""This class implements a linear voltage-controlled voltage sources (VCVS).
Spice syntax:
.. code-block:: none
EXXXXXXX n+ n- nc+ nc- value
Keyword Parameters:
Attributes:
:attr:`voltage_gain`
"""
__alias__ = 'VCVS'
__prefix__ = 'E'
voltage_gain = ExpressionPositionalParameter(position=0, key_parameter=False)
####################################################################################################
class CurrentControlledCurrentSource(DipoleElement):
"""This class implements a linear current-controlled current sources (CCCS).
Spice syntax:
.. code-block:: none
FXXXXXXX n+ n- vname value
Keyword Parameters:
:attr:`multiplier`
alias `m`
Attributes:
:attr:`source`
:attr:`current_gain`
"""
__alias__ = 'F'
__long_alias__ = 'CCCS'
__prefix__ = 'F'
source = ElementNamePositionalParameter(position=0, key_parameter=False)
current_gain = ExpressionPositionalParameter(position=1, key_parameter=False)
multiplier = IntKeyParameter('m')
####################################################################################################
class CurrentControlledVoltageSource(DipoleElement):
"""This class implements a linear current-controlled voltage sources (CCVS).
Spice syntax:
.. code-block:: none
HXXXXXXX n+ n- vname value
Keyword Parameters:
Attributes:
:attr:`source`
:attr:`transresistance`
"""
__alias__ = 'H'
__long_alias__ = 'CCVS'
__prefix__ = 'H'
source = ElementNamePositionalParameter(position=0, key_parameter=False)
transresistance = ExpressionPositionalParameter(position=1, key_parameter=False)
####################################################################################################
#
# Non-Linear Dependent Sources (Behavioral Sources)
#
####################################################################################################
class BehavioralSource(DipoleElement):
"""This class implements a behavioral source.
Spice syntax:
.. code-block:: none
BXXXXXXX n+ n-
Keyword Parameters:
:attr:`current_expression`
alias `i`
:attr:`voltage_expression`
alias `v`
:attr:`tc1`
:attr:`tc2`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
Attributes:
:attr:`current_expression`
:attr:`voltage_expression`
:attr:`tc1`
:attr:`tc2`
:attr:`temperature`
:attr:`device_temperature`
"""
__alias__ = 'B'
__prefix__ = 'B'
current_expression = ExpressionKeyParameter('i')
voltage_expression = ExpressionKeyParameter('v')
tc1 = FloatKeyParameter('tc1')
tc2 = FloatKeyParameter('tc2')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
####################################################################################################
class NonLinearVoltageSource(DipoleElement):
"""This class implements a non-linear voltage source.
.. warning:: Partially implemented
Spice syntax:
.. code-block:: none
Exxx n+ n- vol='expr'
Exxx n+ n- value={expr}
Exxx n1 n2 TABLE {expression}=(x0,y0) (x1,y1) (x2,y2)
Exxx n+ n- ( POLY (nd) ) nc1+ nc1- ( nc2+ nc2- ... ) p0 ( p1 ... )
Laplace
Keyword Parameters:
Attributes:
"""
__alias__ = 'NonLinearVoltageSource'
__prefix__ = 'E'
##############################################
def __init__(self, name, *args, **kwargs):
super().__init__(name, *args, **kwargs)
self.expression = kwargs.get('expression', None)
self.table = kwargs.get('table', None)
##############################################
def __str__(self):
spice_element = self.format_node_names()
# Fixme: expression
if self.table is not None:
# TABLE {expression} = (x0, y0) (x1, y1) ...
table = ['({}, {})'.format(str_spice(x), str_spice(y)) for x, y in self.table]
spice_element += ' TABLE {%s} = %s' % (self.expression, join_list(table))
return spice_element
####################################################################################################
class NonLinearCurrentSource(DipoleElement):
"""This class implements a non-linear current sources.
.. warning:: Partially implemented
Spice syntax:
.. code-block:: none
Gxxx n+ n- value={expr}
Gxxx n1 n2 TABLE {expression}=(x0,y0) (x1,y1) (x2,y2)
Gxxx n+ n- ( POLY (nd) ) nc1+ nc1- ( nc2+ nc2- ... ) p0 ( p1 ... )
Laplace
Keyword Parameters:
Attributes:
:attr:`transconductance`
"""
__alias__ = 'NonLinearCurrentSource'
__prefix__ = 'G'
transconductance = ExpressionPositionalParameter(position=0, key_parameter=False)
####################################################################################################
#
# Diode
#
####################################################################################################
class Diode(FixedPinElement):
"""This class implements a junction diode.
Spice syntax:
.. code-block:: none
DXXXXXXX n+ n- mname
Keyword Parameters:
:attr:`model`
:attr:`area`
:attr:`multiplier`
alias `m`
:attr:`pj`
:attr:`off`
:attr:`ic`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
Attributes:
:attr:`model`
:attr:`area`
:attr:`multiplier`
:attr:`pj`
:attr:`off`
:attr:`ic`
:attr:`temperature`
:attr:`device_temperature`
"""
__alias__ = 'D'
__prefix__ = 'D'
__pins__ = (('cathode', 'plus'), ('anode', 'minus'))
model = ModelPositionalParameter(position=0, key_parameter=True)
area = FloatKeyParameter('area')
multiplier = IntKeyParameter('m')
pj = FloatKeyParameter('pj')
off = FlagParameter('off')
ic = FloatPairKeyParameter('ic')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
####################################################################################################
#
# BJTs
#
####################################################################################################
class BipolarJunctionTransistor(FixedPinElement):
"""This class implements a bipolar junction transistor.
Spice syntax:
.. code-block:: none
QXXXXXXX nc nb ne mname
Keyword Parameters:
:attr:`model`
:attr:`area`
:attr:`areac`
:attr:`areab`
:attr:`multiplier`
alias `m`
:attr:`off`
:attr:`ic`
:attr:`temperature`
alias `temp`
:attr:`device_temperature`
alias `dtemp`
Attributes:
:attr:`model`
:attr:`area`
:attr:`areac`
:attr:`areab`
:attr:`multiplier`
:attr:`off`
:attr:`ic`
:attr:`temperature`
:attr:`device_temperature`
"""
# Fixme: off doesn't fit in kwargs !
__alias__ = 'Q'
__long_alias__ = 'BJT'
__prefix__ = 'Q'
__pins__ = ('collector', 'base', 'emitter', OptionalPin('substrate'))
model = ModelPositionalParameter(position=0, key_parameter=True)
area = FloatKeyParameter('area')
areac = FloatKeyParameter('areac')
areab = FloatKeyParameter('areab')
multiplier = IntKeyParameter('m')
off = FlagParameter('off')
ic = FloatPairKeyParameter('ic')
temperature = FloatKeyParameter('temp', unit=U_Degree)
device_temperature = FloatKeyParameter('dtemp', unit=U_Degree)
####################################################################################################
#
# JFETs
#
####################################################################################################
class JfetElement(FixedPinElement):
__pins__ = ('drain', 'gate', 'source')
class JunctionFieldEffectTransistor(JfetElement):
"""This class implements a bipolar junction transistor.
Spice syntax:
.. code-block:: none
JXXXXXXX nd ng ns mname
Keyword Parameters:
:attr:`model`
:attr:`area`
:attr:`off`
:attr:`ic`
:attr:`temperature`
alias `temp`
Attributes:
:attr:`model`
:attr:`area`
:attr:`off`
:attr:`ic`
:attr:`temperature`
"""
# Fixme: off doesn't fit in kwargs !
__alias__ = 'J'
__long_alias__ = 'JFET'
__prefix__ = 'J'
model = ModelPositionalParameter(position=0, key_parameter=True)
area = FloatKeyParameter('area')
multiplier = IntKeyParameter('m')
off = FlagParameter('off')
ic = FloatPairKeyParameter('ic')
temperature = FloatKeyParameter('temp', unit=U_Degree)
####################################################################################################
#
# MESFETs
#
####################################################################################################
class Mesfet(JfetElement):
"""This class implements a Metal Semiconductor Field Effect Transistor.
Spice syntax:
.. code-block:: none
ZXXXXXXX nd ng ns mname
Keyword Parameters:
:attr:`model`
:attr:`area`
:attr:`off`
:attr:`ic`
Attributes:
:attr:`model`
:attr:`area`
:attr:`off`
:attr:`ic`
"""
# Fixme: off doesn't fit in kwargs !
__alias__ = 'Z'
__long_alias__ = 'MESFET'
__prefix__ = 'Z'
model = ModelPositionalParameter(position=0, key_parameter=True)
area = FloatKeyParameter('area')
multiplier = IntKeyParameter('m')
off = FlagParameter('off')
ic = FloatPairKeyParameter('ic')
####################################################################################################
#
# MOSFETs
#
####################################################################################################
class Mosfet(FixedPinElement):
"""This class implements a Metal Oxide Field Effect Transistor.
Spice syntax:
.. code-block:: none
MXXXXXXX nd ng ns nb mname
+
+
Keyword Parameters:
:attr:`model`
:attr:`multiplier`
alias `m`
:attr:`length`
alias `l`
:attr:`width`
alias `w`
:attr:`drain_area`
alias `ad`
:attr:`source_area`
alias `as`
:attr:`drain_perimeter`
alias `pd`
:attr:`source_perimeter`
alias `ps`
:attr:`drain_number_square`
alias `nrd`
:attr:`source_number_square`
alias `nrs`
:attr:`off`
:attr:`ic`
:attr:`temperature`
alias `temp`
Attributes:
:attr:`model`
:attr:`multiplier`
:attr:`length`
:attr:`width`
:attr:`drain_area`
:attr:`source_area`
:attr:`drain_perimeter`
:attr:`source_perimeter`
:attr:`drain_number_square`
:attr:`source_number_square`
:attr:`off`
:attr:`ic`
:attr:`temperature`
"""
# Fixme: off doesn't fit in kwargs !
__alias__ = 'M'
__long_alias__ = 'MOSFET'
__prefix__ = 'M'
__pins__ = ('drain', 'gate', 'source', ('bulk', 'substrate'))
model = ModelPositionalParameter(position=0, key_parameter=True)
multiplier = IntKeyParameter('m')
length = FloatKeyParameter('l', unit=U_m)
width = FloatKeyParameter('w', unit=U_m)
drain_area = FloatKeyParameter('ad')
source_area = FloatKeyParameter('as')
drain_perimeter = FloatKeyParameter('pd')
source_perimeter = FloatKeyParameter('ps')
drain_number_square = FloatKeyParameter('nrd')
source_number_square = FloatKeyParameter('nrs')
off = FlagParameter('off')
ic = FloatTripletKeyParameter('ic')
temperature = FloatKeyParameter('temp', unit=U_Degree)
####################################################################################################
#
# Transmission Lines
#
####################################################################################################
class LosslessTransmissionLine(TwoPortElement):
"""This class implements a lossless transmission line.
Spice syntax:
.. code-block:: none
TXXXXXXX N1 N2 N3 N4 Z0=VALUE
>
where TD or F, NL must be specified.
Keyword Parameters:
:attr:`impedance`
alias:`Z0`
:attr:`time_delay`
alias:`TD`
:attr:`frequency`
alias:`F`
:attr:`normalized_length`
alias:`NL`
Attributes:
:attr:`impedance`
:attr:`time_delay`
:attr:`frequency`
:attr:`normalized_length`
Note: Either time_delay or frequency must be given.
"""
__alias__ = 'TransmissionLine'
__prefix__ = 'T'
impedance = FloatKeyParameter('Z0', default=50, unit=U_Ω)
time_delay = FloatKeyParameter('TD', unit=U_s)
frequency = FloatKeyParameter('F', unit=U_Hz)
normalized_length = FloatKeyParameter('NL')
##############################################
def __init__(self, name, *args, **kwargs):
# check: ^ xor, & bitwise and
if not (('time_delay' in kwargs) ^
(('frequency' in kwargs) & ('normalized_length' in kwargs))):
raise NameError('Either TD or F, NL must be specified')
super().__init__(name, *args, **kwargs)
####################################################################################################
class LossyTransmission(TwoPortElement):
"""This class implements lossy transmission lines.
Spice syntax:
.. code-block:: none
OXXXXXXX n1 n2 n3 n4 model
Attributes:
:attr:`model`
.. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.
"""
__alias__ = 'O'
__prefix__ = 'O'
model = ModelPositionalParameter(position=0, key_parameter=True)
####################################################################################################
class CoupledMulticonductorLine(NPinElement):
"""This class implements coupled multiconductor lines.
Spice syntax:
.. code-block:: none
PXXXXXXX NI1 NI2 ... NIX GND1 NO1 NO2 ... NOX GND2 model
Attributes:
:attr:`model`
:attr:`length`
alias `len`
length of the line in meters
.. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.
"""
__alias__ = 'P'
__prefix__ = 'P'
model = ModelPositionalParameter(position=0, key_parameter=True)
length = FloatKeyParameter('len', unit=U_m)
##############################################
def __init__(self, netlist, name, *nodes, **parameters):
super().__init__(netlist, name, nodes, **parameters)
####################################################################################################
class UniformDistributedRCLine(FixedPinElement):
"""This class implements uniform distributed RC lines.
Spice syntax:
.. code-block:: none
UXXXXXXX n1 n2 n3 model l=length
Attributes:
:attr:`model`
:attr:`length`
alias `l`
length of the RC line in meters
:attr:`number_of_lumps`
alias `n`
.. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.
"""
__alias__ = 'U'
__prefix__ = 'U'
__pins__ = ('output', 'input', 'capacitance_node')
model = ModelPositionalParameter(position=0, key_parameter=True)
length = FloatKeyParameter('l', unit=U_m)
number_of_lumps = IntKeyParameter('n')
####################################################################################################
class SingleLossyTransmissionLine(TwoPortElement):
# Fixme: special TwoPortElement
"""This class implements single lossy transmission lines.
Spice syntax:
.. code-block:: none
YXXXXXXX N1 0 N2 0 model
Attributes:
:attr:`model`
:attr:`length`
alias `len`
length of the line in meters
.. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.
"""
__alias__ = 'Y'
__prefix__ = 'Y'
model = ModelPositionalParameter(position=0, key_parameter=True)
length = FloatKeyParameter('len', unit=U_m)
####################################################################################################
#
# XSPICE
#
####################################################################################################
class XSpiceElement(NPinElement):
"""This class implements a sub-circuit.
Spice syntax:
.. code-block:: none
AXXXXXXX <%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd , or %d>
+ <[> <~><%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd , or %d>
+
+ <~>...< NIN2 .. <]> >
+ <%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd ,%d or %vnam >
+ <[> <~><%v ,%i ,%vd ,%id ,%g,%gd ,%h,%hd ,
or %d>< NOUT1 or +NOUT1 -NOUT1 >
+ <~>...< NOUT2 .. <]>>
+ MODELNAME
. MODEL MODELNAME MODELTYPE
+ <( PARAMNAME1 = <[> VAL1 > PARAMNAME2 ..>)>
Attributes:
:attr:`model`
.. note:: As opposite to Spice, the model is specified before the nodes so as to act as `*args`.
.. warning:: Partially implemented.
"""
__alias__ = 'A'
__prefix__ = 'A'
model = ModelPositionalParameter(position=0, key_parameter=True)
##############################################
def __init__(self, netlist, name, *nodes, **parameters):
# Fixme: ok ???
super().__init__(netlist, name, nodes, **parameters)
####################################################################################################
#
# GSS
#
####################################################################################################
class GSSElement(NPinElement):
"""This class implements GSS device.
.. warning:: Not implemented
"""
__alias__ = 'N'
__prefix__ = 'N'
##############################################
def __init__(self):
raise NotImplementedError
PySpice-pr-191/PySpice/Spice/ElementParameter.py 0000664 0000000 0000000 00000025076 13654121544 0021623 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This modules implements the machinery to define element's parameters as descriptors.
"""
####################################################################################################
from ..Unit import Unit
from ..Tools.StringTools import str_spice
####################################################################################################
class ParameterDescriptor:
"""This base class implements a descriptor for element parameters.
Public Attributes:
:attr:`attribute_name`
Name of the attribute in the element's class
:attr:`default_value`
The default value
"""
##############################################
def __init__(self, default=None):
self._default_value = default
self._attribute_name = None
##############################################
@property
def default_value(self):
return self._default_value
@property
def attribute_name(self):
return self._attribute_name
@attribute_name.setter
def attribute_name(self, name):
self._attribute_name = name
##############################################
def __get__(self, instance, owner=None):
try:
return getattr(instance, '_' + self._attribute_name)
except AttributeError:
return self.default_value
##############################################
def __set__(self, instance, value):
setattr(instance, '_' + self._attribute_name, value)
##############################################
def __repr__(self):
return self.__class__.__name__
##############################################
def validate(self, value):
"""Validate the parameter's value."""
return value
##############################################
def nonzero(self, instance):
return self.__get__(instance) is not None
##############################################
def to_str(self, instance):
"""Convert the parameter's value to SPICE syntax."""
raise NotImplementedError
##############################################
def __lt__(self, other):
return self._attribute_name < other.attribute_name
####################################################################################################
class PositionalElementParameter(ParameterDescriptor):
"""This class implements a descriptor for positional element parameters.
Public Attributes:
:attr:`key_parameter`
Flag to specify if the parameter is passed as key parameter in Python
:attr:`position`
Position of the parameter in the element definition
"""
##############################################
def __init__(self, position, default=None, key_parameter=False):
super().__init__(default)
self._position = position
self._key_parameter = key_parameter
##############################################
@property
def position(self):
return self._position
@property
def key_parameter(self):
return self._key_parameter
##############################################
def to_str(self, instance):
return str_spice(self.__get__(instance))
##############################################
def __lt__(self, other):
return self._position < other.position
####################################################################################################
class ElementNamePositionalParameter(PositionalElementParameter):
"""This class implements an element name positional parameter."""
##############################################
def validate(self, value):
return str(value)
####################################################################################################
class ExpressionPositionalParameter(PositionalElementParameter):
"""This class implements an expression positional parameter. """
##############################################
def validate(self, value):
return str(value)
####################################################################################################
class FloatPositionalParameter(PositionalElementParameter):
"""This class implements a float positional parameter."""
##############################################
def __init__(self, position, unit=None, **kwargs):
super().__init__(position, **kwargs)
self._unit = unit
##############################################
def validate(self, value):
if isinstance(value, Unit):
return value
else:
return Unit(value)
####################################################################################################
class InitialStatePositionalParameter(PositionalElementParameter):
"""This class implements an initial state (on, off) positional parameter."""
##############################################
def validate(self, value):
return bool(value) # Fixme: check KeyParameter
##############################################
def to__str_(self, instance):
if self.__get__(instance):
return 'on'
else:
return 'off'
####################################################################################################
class ModelPositionalParameter(PositionalElementParameter):
"""This class implements a model positional parameter. """
##############################################
def validate(self, value):
return str(value)
####################################################################################################
class FlagParameter(ParameterDescriptor):
"""This class implements a flag parameter.
Public Attributes:
:attr:`spice_name`
Name of the parameter
"""
##############################################
def __init__(self, spice_name, default=False):
super().__init__(default)
self.spice_name = spice_name
##############################################
def nonzero(self, instance):
return bool(self.__get__(instance))
##############################################
def to_str(self, instance):
if self.nonzero(instance):
return 'off'
else:
return ''
####################################################################################################
class KeyValueParameter(ParameterDescriptor):
"""This class implements a key value pair parameter.
Public Attributes:
:attr:`spice_name`
Name of the parameter
"""
##############################################
def __init__(self, spice_name, default=None):
super().__init__(default)
self.spice_name = spice_name
##############################################
def str_value(self, instance):
return str_spice(self.__get__(instance))
##############################################
def to_str(self, instance):
if bool(self):
return '{}={}'.format(self.spice_name, self.str_value(instance))
else:
return ''
####################################################################################################
class BoolKeyParameter(KeyValueParameter):
"""This class implements a boolean key parameter."""
##############################################
def nonzero(self, instance):
return bool(self.__get__(instance))
##############################################
def str_value(self, instance):
if self.nonzero(instance):
return '1'
else:
return '0'
####################################################################################################
class ExpressionKeyParameter(KeyValueParameter):
"""This class implements an expression key parameter."""
##############################################
def validate(self, value):
return str(value)
####################################################################################################
class FloatKeyParameter(KeyValueParameter):
"""This class implements a float key parameter."""
##############################################
def __init__(self, spice_name, unit=None, **kwargs):
super().__init__(spice_name, **kwargs)
self._unit = unit
##############################################
def validate(self, value):
return float(value)
####################################################################################################
class FloatPairKeyParameter(KeyValueParameter):
"""This class implements a float pair key parameter. """
##############################################
def validate(self, pair):
if len(pair) == 2:
return (float(pair[0]), float(pair[1]))
else:
raise ValueError()
##############################################
def str_value(self, instance):
return ','.join([str(value) for value in self.__get__(instance)])
####################################################################################################
class FloatTripletKeyParameter(FloatPairKeyParameter):
"""This class implements a triplet key parameter."""
##############################################
def validate(self, uplet):
if len(uplet) == 3:
return (float(uplet[0]), float(uplet[1]), float(uplet[2]))
else:
raise ValueError()
####################################################################################################
class IntKeyParameter(KeyValueParameter):
"""This class implements an integer key parameter."""
##############################################
def validate(self, value):
return int(value)
PySpice-pr-191/PySpice/Spice/Expression/ 0000775 0000000 0000000 00000000000 13654121544 0020144 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/Expression/Ast.py 0000664 0000000 0000000 00000025015 13654121544 0021250 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module implements Abstract Syntactic Tree for Spice expressions.
"""
####################################################################################################
import logging
import os
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class StatementList:
##############################################
def __init__(self, *statements):
self._statements = list(statements)
##############################################
def __nonzero__(self):
return bool(self._statements)
##############################################
def __iter__(self):
return iter(self._statements)
##############################################
def add(self, statement):
self._statements.append(statement)
##############################################
def __str__(self):
return os.linesep.join([str(statement) for statement in self])
####################################################################################################
class Program(StatementList):
pass
####################################################################################################
class Variable:
##############################################
def __init__(self, name):
self._name = name
##############################################
@property
def name(self):
return self._name
##############################################
def __str__(self):
return self._name
####################################################################################################
class Constant:
##############################################
def __init__(self, value):
self._value = value
##############################################
def __str__(self):
return str(self._value)
####################################################################################################
class IntConstant(Constant):
##############################################
def __int__(self):
return self._value
####################################################################################################
class FloatConstant(Constant):
##############################################
def __float__(self):
return self._value
####################################################################################################
class Expression:
__number_of_operands__ = None
##############################################
def __init__(self, *args, **kwargs):
if (self.__number_of_operands__ is not None
and len(args) != self.__number_of_operands__):
raise ValueError("Wrong number of operands")
self._operands = args
##############################################
def iter_on_operands(self):
return iter(self._operands)
##############################################
@property
def operand(self):
return self._operands[0]
@property
def operand1(self):
return self._operands[0]
@property
def operand2(self):
return self._operands[1]
@property
def operand3(self):
return self._operands[2]
class UnaryExpression(Expression):
__number_of_operands__ = 1
class BinaryExpression(Expression):
__number_of_operands__ = 2
class TernaryExpression(Expression):
__number_of_operands__ = 3
####################################################################################################
class OperatorMetaclass(type):
"""Metaclass to register operators"""
__declaration_order__ = 0
__operators__ = []
__unary_operator_map__ = {}
__binary_operator_map__ = {}
##############################################
def __new__(meta, class_name, base_classes, attributes):
cls = type.__new__(meta, class_name, base_classes, attributes)
if cls.__operator__ is not None:
meta.register_prefix(cls)
return cls
##############################################
@classmethod
def register_prefix(meta, cls):
cls.__declaration_order__ = meta.__declaration_order__
meta.__declaration_order__ += 1
meta.__operators__.append(cls)
if issubclass(cls, UnaryOperator):
d = meta.__unary_operator_map__
elif issubclass(cls, BinaryOperator):
d = meta.__binary_operator_map__
d[cls.__operator__] = cls
##############################################
@classmethod
def operator_iter(cls):
return iter(cls.__operators__)
##############################################
@classmethod
def get_unary(cls, operator):
return cls.__unary_operator_map__[operator]
##############################################
@classmethod
def get_binary(cls, operator):
return cls.__binary_operator_map__[operator]
####################################################################################################
class OperatorMixin(metaclass=OperatorMetaclass):
__operator__ = None
__declaration_order__ = 0
####################################################################################################
class UnaryOperator(UnaryExpression, OperatorMixin):
def __str__(self):
return ' '.join((self.__operator__, str(self.operand1)))
####################################################################################################
class BinaryOperator(BinaryExpression, OperatorMixin):
def __str__(self):
return ' '.join((str(self.operand1), self.__operator__, str(self.operand2)))
####################################################################################################
class Assignation(BinaryExpression):
@property
def variable(self):
return self._operands[1]
@property
def value(self):
return self._operands[0]
def __str__(self):
return ' '.join((str(self.destination), '=', str(self.value)))
####################################################################################################
class Negation(UnaryOperator):
__operator__ = '-'
__precedence__ = 1
class Not(UnaryOperator):
__operator__ = '!'
__precedence__ = 1
####################################################################################################
class power(BinaryOperator):
__operator__ = '**'
__precedence__ = 2
class Multiplication(BinaryOperator):
__operator__ = '*'
__precedence__ = 3
class Division(BinaryOperator):
__operator__ = '/'
__precedence__ = 3
class Modulo(BinaryOperator):
__operator__ = '%'
__precedence__ = 3
class IntegerDivision(BinaryOperator):
__operator__ = '\\'
__precedence__ = 3
class Addition(BinaryOperator):
__operator__ = '+'
__precedence__ = 4
class Subtraction(BinaryOperator):
__operator__ = '-'
__precedence__ = 4
####################################################################################################
class Equal(BinaryOperator):
__operator__ = '=='
__precedence__ = 5
class NotEqual(BinaryOperator):
__operator__ = '!='
__precedence__ = 5
class LessEqual(BinaryOperator):
__operator__ = '<='
class GreaterEqual(BinaryOperator):
__operator__ = '>='
__precedence__ = 5
class Less(BinaryOperator):
__operator__ = '<'
__precedence__ = 5
class Greater(BinaryOperator):
__operator__ = '>'
__precedence__ = 5
####################################################################################################
class And(BinaryOperator):
__operator__ = '&&'
__precedence__ = 6
class Or(BinaryOperator):
__operator__ = '||'
__precedence__ = 7
####################################################################################################
class If: #(TernaryExpression)
# c ? x : y
__precedence__ = 8
##############################################
def __init__(self, condition, then_expression, else_expression):
self._condition = condition
self._then_expression = then_expression
self._else_expression = else_expression
##############################################
@property
def condition(self):
return self._condition
##############################################
@property
def then_expression(self):
return self._then_expression
##############################################
@property
def else_expression(self):
return self._else_expression
##############################################
# def _str_compound_expression(self, expressions):
# string = '(' + os.linesep
# if expressions:
# string += str(expressions) + os.linesep
# string += ')'
# return string
##############################################
def __str__(self):
return '{} ? {} : {}'.format(self._condition, self._then_expression, self._else_expression)
####################################################################################################
class Function(Expression):
##############################################
def __init__(self, name, *args):
super(Function, self).__init__(*args)
self._name = name
##############################################
@property
def name(self):
return self._name
##############################################
def __str__(self):
parameters = ', '.join([str(operand) for operand in self.iter_on_operands()])
return self._name + ' (' + parameters + ')'
PySpice-pr-191/PySpice/Spice/Expression/Parser.py 0000664 0000000 0000000 00000023262 13654121544 0021757 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module implements a parser for Spice expressions.
"""
####################################################################################################
import logging
####################################################################################################
import ply.lex as lex
import ply.yacc as yacc
####################################################################################################
from .Ast import *
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
# def ensure_statement_list(x):
# if isinstance(x, StatementList):
# return x
# else:
# return StatementList(x)
####################################################################################################
class Parser:
_logger = _module_logger.getChild('Parser')
##############################################
reserved = {
}
tokens = [
'NAME',
# 'INT', 'FLOAT',
'NUMBER',
'SEMICOLON',
'LEFT_PARENTHESIS', 'RIGHT_PARENTHESIS',
'SET',
'NOT',
'POWER',
'MULTIPLY',
'DIVIDE', 'INT_DIVIDE', 'MODULO',
'PLUS', 'MINUS',
'EQUAL', 'NOT_EQUAL', 'LESS', 'GREATER', 'LESS_EQUAL', 'GREATER_EQUAL',
'AND', 'OR',
'IF', 'COLON',
] + list(reserved.values())
##############################################
def t_error(self, token):
self._logger.error("Illegal character '%s' at line %u and position %u" %
(token.value[0],
token.lexer.lineno,
token.lexer.lexpos - self._previous_newline_position))
# token.lexer.skip(1)
raise NameError('Lexer error')
##############################################
t_ignore = ' \t'
def t_newline(self, t):
r'\r?\n+'
# Track newline
t.lexer.lineno += len(t.value)
self._previous_newline_position = t.lexer.lexpos
# t.type = 'SEMICOLON'
# return t
t_ignore_COMMENT = r'\#[^\n]*'
##############################################
t_SEMICOLON = r';'
t_LEFT_PARENTHESIS = r'\('
t_RIGHT_PARENTHESIS = r'\)'
t_SET = r'='
t_NOT = r'!'
t_POWER = r'\*\*'
t_MULTIPLY = r'\*'
t_DIVIDE = r'/'
t_MODULO = r'%'
t_INT_DIVIDE = r'\/'
t_PLUS = r'\+'
t_MINUS = r'-'
t_EQUAL = r'=='
t_NOT_EQUAL = r'!='
t_LESS = r'<'
t_GREATER = r'>'
t_LESS_EQUAL = r'<='
t_GREATER_EQUAL = r'>='
t_AND = r'&&'
t_OR = r'\|\|'
t_IF = r'\?'
t_COLON = r':'
def t_NAME(self, t):
r'[a-zA-Z_][a-zA-Z_0-9]*'
# Check for reserved words
t.type = self.reserved.get(t.value, 'NAME') # Fixme: ???
return t
# def t_INT(self, t):
# r'\d+'
# t.value = int(t.value)
# return t
# exponent_part = r"""([eE][-+]?[0-9]+)"""
# fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)"""
# floating_constant = '(((('+fractional_constant+')'+exponent_part+'?)|([0-9]+'+exponent_part+'))[FfLl]?)'
def t_NUMBER(self, t):
# 1 1. 1.23 .1
# r'\d*\.?\d+([eE][-+]?\d+)?'
# r'(\d+)(\.\d+)(e(\+|-)?(\d+))? | (\d+)e(\+|-)?(\d+)'
r'\d+\.\d+(e(\+|-)?(\d+))? | \d+\.(e(\+|-)?(\d+))? | \.\d+(e(\+|-)?(\d+))? | \d+'
t.value = t.value
return t
##############################################
#
# Grammar
#
# from lowest
precedence = (
('left', 'IF'),
('left', 'OR'),
('left', 'AND'),
('left', 'GREATER', 'LESS', 'GREATER_EQUAL', 'LESS_EQUAL', 'NOT_EQUAL', 'EQUAL'),
('left', 'MINUS', 'PLUS'),
('left', 'INT_DIVIDE', 'MODULO', 'DIVIDE', 'MULTIPLY'),
('left', 'POWER'),
('left', 'NOT'), # , 'NEGATION'
)
def p_error(self, p):
if p:
self._logger.error("Syntax error at '%s'", p.value)
raise NameError('Syntax Error')
else:
self._logger.error("Syntax Error at End Of File")
raise NameError("Syntax Error at End Of File" )
# start = 'program'
start = 'statement'
# def p_empty(self, p):
# 'empty :'
# pass
def p_statement(self, t):
'statement : expression'
print('statement', t[1])
# def p_program(self, p):
# '''program : statement
# | program statement
# | empty
# '''
# if len(p) == 3:
# statement = p[2]
# else:
# statement = p[1]
# if statement is not None:
# self._program.add(statement)
# def p_statement(self, p):
# '''statement : expression_statement
# '''
# p[0] = p[1]
# def p_expression_statement(self, p):
# '''expression_statement : assignation SEMICOLON
# | function SEMICOLON
# | SEMICOLON
# '''
# if len(p) == 3:
# p[0] = p[1]
# def p_statement_list(self, p):
# '''statement_list : statement
# | statement_list statement
# '''
# if len(p) == 3:
# p[1].add(p[2])
# p[0] = p[1]
# else:
# p[0] = StatementList(p[1])
# def p_expression_list(self, p):
# '''expression_list : expression
# | expression_list COMMA expression
# '''
# if len(p) == 3:
# p[1].add(p[2])
# p[0] = p[1]
# else:
# p[0] = StatementList(p[1])
# def p_function(self, p):
# '''function : NAME LEFT_PARENTHESIS expression_list RIGHT_PARENTHESIS
# | NAME LEFT_PARENTHESIS RIGHT_PARENTHESIS
# '''
# if len(p) == 5:
# p[0] = Function(p[1], p[3])
# else:
# p[0] = Function(p[1])
def p_variable(self, p):
'''variable : NAME
'''
p[0] = Variable(p[1])
# def p_assignation(self, p):
# 'assignation : variable SET expression'
# p[0] = Assignation(p[3], p[1]) # eval value first
# def p_interger(self, p):
# '''constant : INT
# '''
# p[0] = IntConstant(p[1])
def p_float(self, p):
'''constant : NUMBER
'''
if '.' in p[1]:
p[0] = FloatConstant(p[1])
else:
p[0] = IntConstant(p[1])
def p_value(self, p):
'''expression : variable
| constant
'''
p[0] = p[1]
def p_unnary_operation(self, p):
# OP ...
'''expression : MINUS expression
| NOT expression
'''
p[0] = OperatorMetaclass.get_unary(p[1])(p[2])
def p_binary_operation(self, p):
# ... OP ...
'''expression : expression POWER expression
| expression MULTIPLY expression
| expression DIVIDE expression
| expression MODULO expression
| expression INT_DIVIDE expression
| expression PLUS expression
| expression MINUS expression
| expression EQUAL expression
| expression NOT_EQUAL expression
| expression LESS expression
| expression GREATER expression
| expression LESS_EQUAL expression
| expression GREATER_EQUAL expression
| expression AND expression
| expression OR expression
'''
p[0] = OperatorMetaclass.get_binary(p[2])(p[1], p[3])
def p_if(self, p):
'''expression : expression IF expression COLON expression
'''
p[0] = If(p[1], p[3], p[5])
##############################################
def __init__(self):
self._build()
##############################################
def _build(self, **kwargs):
self._lexer = lex.lex(module=self, **kwargs)
self._parser = yacc.yacc(module=self, **kwargs)
##############################################
def _reset(self):
self._previous_newline_position = 0
# self._program = Program()
##############################################
def parse(self, text):
self._reset() # Fixme: after ?
self._parser.parse(text, lexer=self._lexer)
# return self._program
##############################################
def test_lexer(self, text):
self._reset()
self._lexer.input(text)
while True:
token = self._lexer.token()
if not token:
break
print(token)
PySpice-pr-191/PySpice/Spice/Expression/__init__.py 0000664 0000000 0000000 00000001627 13654121544 0022263 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
PySpice-pr-191/PySpice/Spice/HighLevelElement.py 0000664 0000000 0000000 00000073511 13654121544 0021547 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module implements high level elements built on top of Spice elements."""
####################################################################################################
from ..Math import rms_to_amplitude, amplitude_to_rms
from ..Tools.StringTools import join_list, join_dict, str_spice
from ..Unit import as_s, as_V, as_A, as_Hz
from .BasicElement import VoltageSource, CurrentSource
####################################################################################################
class SourceMixinAbc:
__as_unit__ = None
####################################################################################################
class VoltageSourceMixinAbc:
__as_unit__ = as_V
####################################################################################################
class CurrentSourceMixinAbc:
__as_unit__ = as_A
####################################################################################################
class SinusoidalMixin(SourceMixinAbc):
r"""This class implements a sinusoidal waveform.
+------+----------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+------+----------------+---------------+-------+
| Vo + offset + + V, A |
+------+----------------+---------------+-------+
| Va + amplitude + + V, A |
+------+----------------+---------------+-------+
| f + frequency + 1 / TStop + Hz |
+------+----------------+---------------+-------+
| Td + delay + 0.0 + sec |
+------+----------------+---------------+-------+
| Df + damping factor + 0.01 + 1/sec |
+------+----------------+---------------+-------+
The shape of the waveform is described by the following formula:
.. math::
V(t) = \begin{cases}
V_o & \text{if}\ 0 \leq t < T_d, \\
V_o + V_a e^{-D_f(t-T_d)} \sin\left(2\pi f (t-T_d)\right) & \text{if}\ T_d \leq t < T_{stop}.
\end{cases}
Spice syntax::
SIN ( Voffset Vamplitude Freq Tdelay DampingFactor )
Public Attributes:
:attr:`amplitude`
:attr:`damping_factor`
:attr:`dc_offset`
:attr:`delay`
:attr:`frequency`
:attr:`offset`
"""
##############################################
def __init__(self,
dc_offset=0,
offset=0, amplitude=1, frequency=50,
delay=0, damping_factor=0):
self.dc_offset = self.__as_unit__(dc_offset)
self.offset = self.__as_unit__(offset)
self.amplitude = self.__as_unit__(amplitude)
self.frequency = as_Hz(frequency) # Fixme: protect by setter?
self.delay = as_s(delay)
self.damping_factor = as_Hz(damping_factor)
##############################################
@property
def rms_voltage(self):
return amplitude_to_rms(self.amplitude)
##############################################
@property
def period(self):
return self.frequency.period
##############################################
def format_spice_parameters(self):
sin_part = join_list((self.offset, self.amplitude, self.frequency, self.delay, self.damping_factor))
return join_list((
'DC {}'.format(str_spice(self.dc_offset)),
'AC SIN({})'.format(sin_part),
))
####################################################################################################
class PulseMixin(SourceMixinAbc):
"""This class implements a pulse waveform.
Nomenclature:
+--------+---------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+--------+---------------+---------------+-------+
| V1 + initial value + + V, A |
+--------+---------------+---------------+-------+
| V2 + pulsed value + + V, A |
+--------+---------------+---------------+-------+
| Td + delay time + 0.0 + sec |
+--------+---------------+---------------+-------+
| Tr + rise time + Tstep + sec |
+--------+---------------+---------------+-------+
| Tf + fall time + Tstep + sec |
+--------+---------------+---------------+-------+
| Pw + pulse width + Tstop + sec |
+--------+---------------+---------------+-------+
| Period + period + Tstop + sec |
+--------+---------------+---------------+-------+
| Phase + phase + 0.0 + sec |
+--------+---------------+---------------+-------+
Phase is only possible when XSPICE is enabled
Spice Syntax::
PULSE ( V1 V2 Td Tr Tf Pw Period Phase )
A single pulse so specified is described by the following table:
+-------------+-------+
| Time | Value |
+-------------+-------+
| 0 | V1 |
+-------------+-------+
| Td | V1 |
+-------------+-------+
| Td+Tr | V2 |
+-------------+-------+
| Td+Tr+Pw | V2 |
+-------------+-------+
| Td+Tr+Pw+Tf | V1 |
+-------------+-------+
| Tstop | V1 |
+-------------+-------+
Note: default value in Spice for rise and fall time is the simulation transient step, pulse
width and period is the simulation stop time.
Public Attributes:
:attr:`delay_time`
:attr:`fall_time`
:attr:`initial_value`
:attr:`period`
:attr:`phase`
:attr:`pulse_width`
:attr:`pulsed_value`
:attr:`rise_time`
"""
##############################################
def __init__(self,
initial_value, pulsed_value,
pulse_width, period,
delay_time=0, rise_time=0, fall_time=0,
phase=None):
# Fixme: default
# rise_time, fall_time = Tstep
# pulse_width, period = Tstop
self.initial_value = self.__as_unit__(initial_value)
self.pulsed_value = self.__as_unit__(pulsed_value)
self.delay_time = as_s(delay_time)
self.rise_time = as_s(rise_time)
self.fall_time = as_s(fall_time)
self.pulse_width = as_s(pulse_width)
self.period = as_s(period) # Fixme: protect by setter?
# XSPICE
if phase is not None:
self.phase = as_s(phase)
else:
self.phase = None
# # Fixme: to func?
# # Check parameters
# found_none = False
# for parameter in ('rise_time', 'fall_time', 'pulse_width', 'period'):
# parameter_value = getattr(self, parameter)
# if found_none:
# if parameter_value is not None:
# raise ValueError("Parameter {} is set but some previous parameters was not set".format(parameter))
# else:
# found_none = parameter_value is None
##############################################
@property
def frequency(self):
return self.period.frequency
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('PULSE(' +
join_list((self.initial_value, self.pulsed_value, self.delay_time,
self.rise_time, self.fall_time, self.pulse_width, self.period,
self.phase)) +
')')
####################################################################################################
class ExponentialMixin(SourceMixinAbc):
r"""This class implements a Exponential waveform.
Nomenclature:
+------+--------------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+------+--------------------+---------------+-------+
| V1 + Initial value + + V, A |
+------+--------------------+---------------+-------+
| V2 + pulsed value + + V, A |
+------+--------------------+---------------+-------+
| Td1 + rise delay time + 0.0 + sec |
+------+--------------------+---------------+-------+
| tau1 + rise time constant + Tstep + sec |
+------+--------------------+---------------+-------+
| Td2 + fall delay time + Td1+Tstep + sec |
+------+--------------------+---------------+-------+
| tau2 + fall time constant + Tstep + sec |
+------+--------------------+---------------+-------+
Spice Syntax::
EXP ( V1 V2 TD1 TAU1 TD2 TAU2 )
The shape of the waveform is described by the following formula:
Let V21 = V2 - V1 and V12 = V1 - V2.
.. math::
V(t) = \begin{cases}
V_1 & \text{if}\ 0 \leq t < T_{d1}, \\
V_1 + V_{21} ( 1 − e^{-\frac{t-T_{d1}}{\tau_1}} )
& \text{if}\ T_{d1} \leq t < T_{d2}, \\
V_1 + V_{21} ( 1 − e^{-\frac{t-T_{d1}}{\tau_1}} ) + V_{12} ( 1 − e^{-\frac{t-T_{d2}}{\tau_2}} )
& \text{if}\ T_{d2} \leq t < T_{stop}
\end{cases}
"""
##############################################
def __init__(self,
initial_value, pulsed_value,
rise_delay_time=.0, rise_time_constant=None,
fall_delay_time=None, fall_time_constant=None):
# Fixme: default
self.initial_value = self.__as_unit__(initial_value)
self.pulsed_value = self.__as_unit__(pulsed_value)
self.rise_delay_time = as_s(rise_delay_time)
self.rise_time_constant = as_s(rise_time_constant)
self.fall_delay_time = as_s(fall_delay_time)
self.fall_time_constant = as_s(fall_time_constant)
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('EXP(' +
join_list((self.initial_value, self.pulsed_value,
self.rise_delay_time, self.rise_time_constant,
self.fall_delay_time, self.fall_time_constant,
)) +
')')
####################################################################################################
class PieceWiseLinearMixin(SourceMixinAbc):
r"""This class implements a Piece-Wise Linear waveform.
Spice Syntax::
PWL( T1 V1 )
Each pair of values (Ti , Vi) specifies that the value of the source is Vi (in Volts or Amps) at
time = Ti . The value of the source at intermediate values of time is determined by using linear
interpolation on the input values. The parameter r determines a repeat time point. If r is not
given, the whole sequence of values (Ti , Vi ) is issued once, then the output stays at its
final value. If r = 0, the whole sequence from time = 0 to time = Tn is repeated forever. If r =
10ns, the sequence between 10ns and 50ns is repeated forever. the r value has to be one of the
time points T1 to Tn of the PWL sequence. If td is given, the whole PWL sequence is delayed by a
delay time time = td. The current source still needs to be patched, td and r are not yet
available.
`values` should be given as a list of (`Time`, `Value`)-tuples, e.g.::
PieceWiseLinearVoltageSource(
circuit,
'pwl1', '1', '0',
values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_V), (20@u_ms, 5@u_V)],
)
"""
##############################################
def __init__(self, values, repeate_time=0, delay_time=.0):
# Fixme: default
self.values = sum(([as_s(t), self.__as_unit__(x)] for (t, x) in values), [])
self.repeate_time = as_s(repeate_time)
self.delay_time = as_s(delay_time)
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('PWL(' +
join_list(self.values) +
' ' +
join_dict({'r':self.repeate_time, 'td':self.delay_time}) + # OrderedDict(
')')
####################################################################################################
class SingleFrequencyFMMixin(SourceMixinAbc):
r"""This class implements a Single-Frequency FM waveform.
Spice Syntax::
SFFM (VO VA FC MDI FS )
+------+-------------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+------+-------------------+---------------+-------+
| Vo + offset + + V, A |
+------+-------------------+---------------+-------+
| Va + amplitude + + V, A |
+------+-------------------+---------------+-------+
| Fc + carrier frequency + 1 / Tstop + Hz |
+------+-------------------+---------------+-------+
| Mdi + modulation index + + |
+------+-------------------+---------------+-------+
| Fs + signal frequency + 1 / Tstop + Hz |
+------+-------------------+---------------+-------+
The shape of the waveform is described by the following equation:
.. math::
V(t) = V_o + V_a \sin (2\pi F_c\, t + M_{di} \sin (2\pi F_s\,t))
"""
##############################################
def __init__(self, offset, amplitude, carrier_frequency, modulation_index, signal_frequency):
self.offset = self.__as_unit__(offset)
self.amplitude = self.__as_unit__(amplitude)
self.carrier_frequency = as_Hz(carrier_frequency)
self.modulation_index = modulation_index
self.signal_frequency = as_Hz(signal_frequency)
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('SFFM(' +
join_list((self.offset, self.amplitude, self.carrier_frequency,
self.modulation_index, self.signal_frequency)) +
')')
####################################################################################################
class AmplitudeModulatedMixin(SourceMixinAbc):
r"""This class implements a Amplitude Modulated source.
+------+----------------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+------+----------------------+---------------+-------+
| Vo + offset + + V, A |
+------+----------------------+---------------+-------+
| Va + amplitude + + V, A |
+------+----------------------+---------------+-------+
| Mf + modulating frequency + + Hz |
+------+----------------------+---------------+-------+
| Fc + carrier frequency + 1 / Tstop + Hz |
+------+----------------------+---------------+-------+
| Td + signal delay + + s |
+------+----------------------+---------------+-------+
Spice Syntax::
AM(VA VO MF FC TD)
The shape of the waveform is described by the following equation:
.. math::
V(t) = V_a (V_o + \sin (2\pi M_f\,t)) \sin (2\pi F_c\,t)
"""
##############################################
def __init__(self, offset, amplitude, modulating_frequency, carrier_frequency, signal_delay):
# Fixme: default
self.offset = self.__as_unit__(offset)
self.amplitude = self.__as_unit__(amplitude)
self.carrier_frequency = as_Hz(carrier_frequency)
self.modulating_frequency = as_Hz(modulating_frequency)
self.signal_delay = as_s(signal_delay)
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('AM(' +
join_list((self.offset, self.amplitude, self.carrier_frequency,
self.modulating_frequency, self.signal_delay)) +
')')
####################################################################################################
class RandomMixin(SourceMixinAbc):
r"""This class implements a Random Voltage source.
The TRRANDOM option yields statistically distributed voltage values, derived from the ngspice
random number generator. These values may be used in the transient simulation directly within a
circuit, e.g. for generating a specific noise voltage, but especially they may be used in the
control of behavioral sources (B, E, G sources, voltage controllable A sources, capacitors,
inductors, or resistors) to simulate the circuit dependence on statistically varying device
parameters. A Monte-Carlo simulation may thus be handled in a single simulation run.
Spice Syntax::
TRRANDOM( TYPE TS
> >)
TYPE determines the random variates generated: 1 is uniformly distributed, 2 Gaussian, 3
exponential, 4 Poisson. TS is the duration of an individual voltage value. TD is a time delay
with 0 V output before the random voltage values start up. PARAM1 and PARAM2 depend on the type
selected.
+-------------+---------------+---------+-------------+---------+
| Type + Parameter 1 + Default + Parameter 2 + Default |
+-------------+---------------+---------+-------------+---------+
| uniform + range + 1 + offset + 0 |
+-------------+---------------+---------+-------------+---------+
| gaussian + standard dev. + 1 + mean + 0 |
+-------------+---------------+---------+-------------+---------+
| exponential + mean + 1 + offset + 0 |
+-------------+---------------+---------+-------------+---------+
| poisson + lambda + 1 + offset + 0 |
+-------------+---------------+---------+-------------+---------+
"""
##############################################
def __init__(self, random_type, duration=0, time_delay=0, parameter1=1, parameter2=0):
# Fixme: random_type and parameters
self.random_type = random_type
self.duration = as_s(duration)
self.time_delay = as_s(time_delay)
self.parameter1 = parameter1
self.parameter2 = parameter2
##############################################
def format_spice_parameters(self):
if self.random_type == 'uniform':
random_type = 1
elif self.random_type == 'exponential':
random_type = 2
elif self.random_type == 'gaussian':
random_type = 3
elif self.random_type == 'poisson':
random_type = 4
else:
raise ValueError("Wrong random type {}".format(self.random_type))
# Fixme: to func?
return ('TRRANDOM(' +
join_list((random_type, self.duration, self.time_delay,
self.parameter1, self.parameter2)) +
')')
####################################################################################################
class SinusoidalVoltageSource(VoltageSource, VoltageSourceMixinAbc, SinusoidalMixin):
r"""This class implements a sinusoidal waveform voltage source.
See :class:`SinusoidalMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
SinusoidalMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = SinusoidalMixin.format_spice_parameters
####################################################################################################
class SinusoidalCurrentSource(CurrentSource, CurrentSourceMixinAbc, SinusoidalMixin):
r"""This class implements a sinusoidal waveform current source.
See :class:`SinusoidalMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
SinusoidalMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = SinusoidalMixin.format_spice_parameters
####################################################################################################
class AcLine(SinusoidalVoltageSource):
##############################################
def __init__(self, netlist, name, node_plus, node_minus, rms_voltage=230, frequency=50):
super().__init__(netlist, name, node_plus, node_minus,
amplitude=rms_to_amplitude(rms_voltage),
frequency=frequency)
####################################################################################################
class PulseVoltageSource(VoltageSource, VoltageSourceMixinAbc, PulseMixin):
r"""This class implements a pulse waveform voltage source.
See :class:`PulseMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
PulseMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = PulseMixin.format_spice_parameters
####################################################################################################
class PulseCurrentSource(CurrentSource, CurrentSourceMixinAbc, PulseMixin):
r"""This class implements a pulse waveform current source.
See :class:`PulseMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
PulseMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = PulseMixin.format_spice_parameters
####################################################################################################
class ExponentialVoltageSource(VoltageSource, VoltageSourceMixinAbc, ExponentialMixin):
r"""This class implements a exponential waveform voltage source.
See :class:`ExponentialMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
ExponentialMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = ExponentialMixin.format_spice_parameters
####################################################################################################
class ExponentialCurrentSource(CurrentSource, CurrentSourceMixinAbc, ExponentialMixin):
r"""This class implements a exponential waveform current source.
See :class:`ExponentialMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
ExponentialMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = ExponentialMixin.format_spice_parameters
####################################################################################################
class PieceWiseLinearVoltageSource(VoltageSource, VoltageSourceMixinAbc, PieceWiseLinearMixin):
r"""This class implements a piece wise linear waveform voltage source.
See :class:`PieceWiseLinearMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
PieceWiseLinearMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = PieceWiseLinearMixin.format_spice_parameters
####################################################################################################
class PieceWiseLinearCurrentSource(CurrentSource, CurrentSourceMixinAbc, PieceWiseLinearMixin):
r"""This class implements a piece wise linear waveform current source.
See :class:`PieceWiseLinearMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
PieceWiseLinearMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = PieceWiseLinearMixin.format_spice_parameters
####################################################################################################
class SingleFrequencyFMVoltageSource(VoltageSource, VoltageSourceMixinAbc, SingleFrequencyFMMixin):
r"""This class implements a single frequency FM waveform voltage source.
See :class:`SingleFrequencyFMMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
SingleFrequencyFMMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = SingleFrequencyFMMixin.format_spice_parameters
####################################################################################################
class SingleFrequencyFMCurrentSource(CurrentSource, CurrentSourceMixinAbc, SingleFrequencyFMMixin):
r"""This class implements a single frequency FM waveform current source.
See :class:`SingleFrequencyFMMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
SingleFrequencyFMMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = SingleFrequencyFMMixin.format_spice_parameters
####################################################################################################
class AmplitudeModulatedVoltageSource(VoltageSource, VoltageSourceMixinAbc, AmplitudeModulatedMixin):
r"""This class implements a amplitude modulated waveform voltage source.
See :class:`AmplitudeModulatedMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
AmplitudeModulatedMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = AmplitudeModulatedMixin.format_spice_parameters
####################################################################################################
class AmplitudeModulatedCurrentSource(CurrentSource, CurrentSourceMixinAbc, AmplitudeModulatedMixin):
r"""This class implements a amplitude modulated waveform current source.
See :class:`AmplitudeModulatedMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
AmplitudeModulatedMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = AmplitudeModulatedMixin.format_spice_parameters
####################################################################################################
class RandomVoltageSource(VoltageSource, VoltageSourceMixinAbc, RandomMixin):
r"""This class implements a random waveform voltage source.
See :class:`RandomMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
VoltageSource.__init__(self, netlist, name, node_plus, node_minus)
RandomMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = RandomMixin.format_spice_parameters
####################################################################################################
class RandomCurrentSource(CurrentSource, CurrentSourceMixinAbc, RandomMixin):
r"""This class implements a random waveform current source.
See :class:`RandomMixin` for documentation.
"""
##############################################
def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs):
CurrentSource.__init__(self, netlist, name, node_plus, node_minus)
RandomMixin.__init__(self, *args, **kwargs)
##############################################
format_spice_parameters = RandomMixin.format_spice_parameters
PySpice-pr-191/PySpice/Spice/Library.py 0000664 0000000 0000000 00000010213 13654121544 0017760 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import logging
####################################################################################################
from ..Tools.File import Directory
from .Parser import SpiceParser
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class SpiceLibrary:
"""This class implements a Spice sub-circuits and models library.
A library is a directory which is recursively scanned for '.lib' file and parsed for sub-circuit
and models definitions.
Example of usage::
spice_library = SpiceLibrary('/some/path/')
If the directory hierarchy contains a file that define a 1N4148 sub-circuit then we can retrieve
the file path using::
spice_library['1N4148']
"""
_logger = _module_logger.getChild('Library')
EXTENSIONS = (
'.lib',
'.mod',
'.lib@xyce',
'.mod@xyce',
)
##############################################
def __init__(self, root_path):
self._directory = Directory(root_path).expand_vars_and_user()
self._subcircuits = {}
self._models = {}
for path in self._directory.iter_file():
extension = path.extension.lower()
if extension in self.EXTENSIONS:
self._logger.debug("Parse {}".format(path))
spice_parser = SpiceParser(path)
if spice_parser.is_only_subcircuit():
for subcircuit in spice_parser.subcircuits:
name = self._suffix_name(subcircuit.name, extension)
self._subcircuits[name] = path
elif spice_parser.is_only_model():
for model in spice_parser.models:
name = self._suffix_name(model.name, extension)
self._models[name] = path
##############################################
@staticmethod
def _suffix_name(name, extension):
if extension.endswith('@xyce'):
name += '@xyce'
return name
##############################################
def __getitem__(self, name):
if name in self._subcircuits:
return self._subcircuits[name]
elif name in self._models:
return self._models[name]
else:
# print('Library {} not found in {}'.format(name, self._directory))
# self._logger.warn('Library {} not found in {}'.format(name, self._directory))
raise KeyError(name)
##############################################
@property
def subcircuits(self):
""" Dictionary of sub-circuits """
return iter(self._subcircuits)
@property
def models(self):
""" Dictionary of models """
return iter(self._models)
# ##############################################
# def iter_on_subcircuits(self):
# return self._subcircuits.itervalues()
# ##############################################
# def iter_on_models(self):
# return self._models.itervalues()
PySpice-pr-191/PySpice/Spice/Netlist.py 0000664 0000000 0000000 00000113175 13654121544 0020011 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This modules implements circuit and subcircuit.
The definition of a netlist follows the same conventions as SPICE. For example this SPICE netlist
is translated to Python like this:
.. code-block:: spice
.title Voltage Divider
Vinput in 0 10V
R1 in out 9k
R2 out 0 1k
.end
.. code-block:: python3
circuit = Circuit('Voltage Divider')
circuit.V('input', 'in', circuit.gnd, 10)
circuit.R(1, 'in', 'out', kilo(9))
circuit.R(2, 'out', circuit.gnd, kilo(1))
or as a class definition:
.. code-block:: python3
class VoltageDivider(Circuit):
def __init__(self, **kwargs):
super().__init__(title='Voltage Divider', **kwargs)
self.V('input', 'in', self.gnd, '10V')
self.R(1, 'in', 'out', kilo(9))
self.R(2, 'out', self.gnd, kilo(1))
The circuit attribute :attr:`gnd` represents the ground of the circuit or subcircuit, usually set to
0.
We can get an element or a model using its name using these two possibilities::
circuit['R1'] # dictionary style
circuit.R1 # attribute style
The dictionary style always works, but the attribute only works if it complies with the Python
syntax, i.e. the element or model name is a valide attribute name (identifier), i.e. starting by a
letter and not a keyword like 'in', cf. `Python Language Reference
`_.
We can update an element parameter like this::
circuit.R1.resistance = kilo(1)
To simulate the circuit, we must create a simulator instance using the :meth:`Circuit.simulator`::
simulator = circuit.simulator()
"""
####################################################################################################
from collections import OrderedDict
from pathlib import Path
import keyword
import logging
import os
# import networkx
####################################################################################################
from ..Tools.StringTools import join_lines, join_list, join_dict
from .ElementParameter import (
ParameterDescriptor,
PositionalElementParameter,
FlagParameter, KeyValueParameter,
)
from .Simulation import CircuitSimulator
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class DeviceModel:
"""This class implements a device model.
Ngspice model types:
+------+-------------------------------+
| Code + Model Type |
+------+-------------------------------+
| R + Semiconductor resistor model |
+------+-------------------------------+
| C + Semiconductor capacitor model |
+------+-------------------------------+
| L + Inductor model |
+------+-------------------------------+
| SW + Voltage controlled switch |
+------+-------------------------------+
| CSW + Current controlled switch |
+------+-------------------------------+
| URC + Uniform distributed RC model |
+------+-------------------------------+
| LTRA + Lossy transmission line model |
+------+-------------------------------+
| D + Diode model |
+------+-------------------------------+
| NPN + NPN BJT model |
+------+-------------------------------+
| PNP + PNP BJT model |
+------+-------------------------------+
| NJF + N-channel JFET model |
+------+-------------------------------+
| PJF + P-channel JFET model |
+------+-------------------------------+
| NMOS + N-channel MOSFET model |
+------+-------------------------------+
| PMOS + P-channel MOSFET model |
+------+-------------------------------+
| NMF + N-channel MESFET model |
+------+-------------------------------+
| PMF + P-channel MESFET model |
+------+-------------------------------+
"""
##############################################
def __init__(self, name, modele_type, **parameters):
self._name = str(name)
self._model_type = str(modele_type)
self._parameters = {}
for key, value in parameters.items():
if key.endswith('_'):
key = key[:-1]
self._parameters[key] = value
##############################################
def clone(self):
# Fixme: clone parameters ???
return self.__class__(self._name, self._model_type, self._parameters)
##############################################
@property
def name(self):
return self._name
@property
def model_type(self):
return self._model_type
@property
def parameters(self):
return self._parameters.keys()
##############################################
def __getitem__(self, name):
return self._parameters[name]
##############################################
def __getattr__(self, name):
try:
return self._parameters[name]
except KeyError:
if name.endswith('_'):
return self._parameters[name[:-1]]
##############################################
def __repr__(self):
return str(self.__class__) + ' ' + self.name
##############################################
def __str__(self):
return ".model {0._name} {0._model_type} ({1})".format(self, join_dict(self._parameters))
####################################################################################################
class PinDefinition:
"""This class defines a pin of an element."""
##############################################
def __init__(self, position, name=None, alias=None, optional=False):
self._position = position
self._name = name
self._alias = alias
self._optional = optional
##############################################
def clone(self):
# Fixme: self.__class__ ???
return PinDefinition(self._position, self._name, self._alias, self._optional)
##############################################
@property
def position(self):
return self._position
@property
def name(self):
return self._name
@property
def alias(self):
return self._alias
@property
def optional(self):
return self._optional
####################################################################################################
class OptionalPin:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
####################################################################################################
class Pin(PinDefinition):
"""This class implements a pin of an element. It stores a reference to the element, the name of the
pin and the node.
"""
_logger = _module_logger.getChild('Pin')
##############################################
def __init__(self, element, pin_definition, node):
super().__init__(pin_definition.position, pin_definition.name, pin_definition.alias)
self._element = element
self._node = node
node.connect(self)
##############################################
@property
def element(self):
return self._element
@property
def node(self):
return self._node
##############################################
def __repr__(self):
return "Pin {} of {} on node {}".format(self._name, self._element.name, self._node)
##############################################
def disconnect(self):
self._node.disconnect(self)
self._node = None
##############################################
def add_current_probe(self, circuit):
"""Add a current probe between the node and the pin.
The ammeter is named *ElementName_PinName*.
"""
# Fixme: require a reference to circuit
# Fixme: add it to a list
node = self._node
self._node = '_'.join((self._element.name, self._name))
circuit.V(self._node, node, self._node, '0')
####################################################################################################
class ElementParameterMetaClass(type):
# Metaclass to implements the element node and parameter machinery.
"""Metaclass to customise the element classes when they are created and to register SPICE prefix.
Element classes are of type :class:`ElementParameterMetaClass` instead of :class:`type`
.. code-block:: none
class Resistor(metaclass=ElementParameterMetaClass):
<=>
Resistor = ElementParameterMetaClass('Foo', ...)
"""
#: Dictionary for SPICE prefix -> [cls,]
__classes__ = {}
_logger = _module_logger.getChild('ElementParameterMetaClass')
##############################################
def __new__(meta_cls, class_name, base_classes, namespace):
# __new__ is called for the creation of a class depending of this metaclass, i.e. at module loading
# It customises the namespace of the new class
# Collect positional and optional parameters from class attribute dict
positional_parameters = {}
parameters = {}
for attribute_name, obj in namespace.items():
if isinstance(obj, ParameterDescriptor):
obj.attribute_name = attribute_name
if isinstance(obj, PositionalElementParameter):
d = positional_parameters
elif isinstance(obj, (FlagParameter, KeyValueParameter)):
d = parameters
# else:
# raise NotImplementedError
d[attribute_name] = obj
# Dictionary for positional parameters : attribute_name -> parameter
namespace['__positional_parameters__'] = OrderedDict(
sorted(list(positional_parameters.items()), key=lambda t: t[1]))
# Dictionary for optional parameters
# order is not required for SPICE, but for unit test
namespace['__optional_parameters__'] = OrderedDict(
sorted(list(parameters.items()), key=lambda t: t[0]))
# Positional parameter array
namespace['__parameters_from_args__'] = [
parameter
for parameter in sorted(positional_parameters.values())
if not parameter.key_parameter]
# Implement alias for parameters: spice name -> parameter
namespace['__spice_to_parameters__'] = {
parameter.spice_name:parameter
for parameter in namespace['__optional_parameters__'].values()}
for parameter in namespace['__spice_to_parameters__'].values():
if (parameter.spice_name in namespace
and parameter.spice_name != parameter.attribute_name):
_module_logger.error("Spice parameter '{}' clash with namespace".format(parameter.spice_name))
# Initialise pins
def make_pin_getter(position):
def getter(self):
return self._pins[position]
return getter
def make_optional_pin_getter(position):
def getter(self):
return self._pins[position] if position < len(self._pins) else None
return getter
if '__pins__' in namespace and namespace['__pins__'] is not None:
number_of_optional_pins = 0
pins = []
for position, pin_definition in enumerate(namespace['__pins__']):
# ensure pin_definition is a tuple
if isinstance(pin_definition, OptionalPin):
optional = True
number_of_optional_pins += 1
pin_definition = (pin_definition.name,)
pin_getter = make_optional_pin_getter(position)
else:
optional = False
pin_getter = make_pin_getter(position)
if not isinstance(pin_definition, tuple):
pin_definition = (pin_definition,)
for name in pin_definition:
# Check for name clash
if name in namespace:
raise NameError("Pin {} of element {} clashes with another attribute".format(name, class_name))
# Add a pin getter in element class
namespace[name] = property(pin_getter)
# Add pin
pin = PinDefinition(position, *pin_definition, optional=optional)
pins.append(pin)
namespace['__pins__'] = pins
namespace['__number_of_optional_pins__'] = number_of_optional_pins
else:
_module_logger.debug("{} don't define a __pins__ attribute".format(class_name))
return type.__new__(meta_cls, class_name, base_classes, namespace)
##############################################
def __init__(meta_cls, class_name, base_classes, namespace):
# __init__ is called after the class is created (__new__)
type.__init__(meta_cls, class_name, base_classes, namespace)
# Collect basic element classes
if '__prefix__' in namespace:
prefix = namespace['__prefix__']
if prefix is not None:
classes = ElementParameterMetaClass.__classes__
if prefix in classes:
classes[prefix].append(meta_cls)
else:
classes[prefix] = [meta_cls]
##############################################
# Note: These properties are only available from the class object
# e.g. Resistor.number_of_pins or Resistor.__class__.number_of_pins
@property
def number_of_pins(cls):
#! Fixme: many pins ???
number_of_pins = len(cls.__pins__)
if cls.__number_of_optional_pins__:
return slice(number_of_pins - cls.__number_of_optional_pins__, number_of_pins +1)
else:
return number_of_pins
@property
def number_of_positional_parameters(cls):
return len(cls.__positional_parameters__)
@property
def positional_parameters(cls):
return cls.__positional_parameters__
@property
def optional_parameters(cls):
return cls.__optional_parameters__
@property
def parameters_from_args(cls):
return cls.__parameters_from_args__
@property
def spice_to_parameters(cls):
return cls.__spice_to_parameters__
####################################################################################################
class Element(metaclass=ElementParameterMetaClass):
"""This class implements a base class for an element.
It use a metaclass machinery for the declaration of the parameters.
"""
# These class attributes are defined in subclasses or via the metaclass.
__pins__ = None
__positional_parameters__ = None
__optional_parameters__ = None
__parameters_from_args__ = None
__spice_to_parameters__ = None
#: SPICE element prefix
__prefix__ = None
##############################################
def __init__(self, netlist, name, *args, **kwargs):
self._netlist = netlist
self._name = str(name)
self.raw_spice = ''
self.enabled = True
# Process remaining args
if len(self.__parameters_from_args__) < len(args):
raise NameError("Number of args mismatch")
for parameter, value in zip(self.__parameters_from_args__, args):
setattr(self, parameter.attribute_name, value)
# Process kwargs
for key, value in kwargs.items():
if key == 'raw_spice':
self.raw_spice = value
elif (key in self.__positional_parameters__ or
key in self.__optional_parameters__ or
key in self.__spice_to_parameters__):
setattr(self, key, value)
else:
raise ValueError('Unknown argument {}={}'.format(key, value))
self._pins = ()
netlist._add_element(self)
##############################################
def copy_to(self, element):
for parameter_dict in self.__positional_parameters__, self.__optional_parameters__:
for parameter in parameter_dict.values():
if hasattr(self, parameter.attribute_name):
value = getattr(self, parameter.attribute_name)
setattr(element, parameter.attribute_name, value)
if hasattr(self, 'raw_spice'):
element.raw_spice = self.raw_spice
##############################################
@property
def netlist(self):
return self._netlist
@property
def name(self):
return self.__prefix__ + self._name
@property
def pins(self):
return self._pins
##############################################
def detach(self):
for pin in self._pins:
pin.disconnect()
self._netlist._remove_element(self)
self._netlist = None
return self
##############################################
@property
def nodes(self):
return [pin.node for pin in self._pins]
@property
def node_names(self):
return [str(x) for x in self.nodes]
##############################################
def __repr__(self):
return self.__class__.__name__ + ' ' + self.name
##############################################
def __setattr__(self, name, value):
# Implement alias for parameters
if name in self.__spice_to_parameters__:
parameter = self.__spice_to_parameters__[name]
object.__setattr__(self, parameter.attribute_name, value)
else:
object.__setattr__(self, name, value)
##############################################
def __getattr__(self, name):
# Implement alias for parameters
if name in self.__spice_to_parameters__:
parameter = self.__spice_to_parameters__[name]
return object.__getattribute__(self, parameter.attribute_name)
else:
raise AttributeError(name)
##############################################
def format_node_names(self):
""" Return the formatted list of nodes. """
return join_list((self.name, join_list(self.nodes)))
##############################################
def parameter_iterator(self):
""" This iterator returns the parameter in the right order. """
# Fixme: .parameters ???
for parameter_dict in self.__positional_parameters__, self.__optional_parameters__:
for parameter in parameter_dict.values():
if parameter.nonzero(self):
yield parameter
##############################################
# @property
# def parameters(self):
# return self._parameters
##############################################
def format_spice_parameters(self):
""" Return the formatted list of parameters. """
return join_list([parameter.to_str(self) for parameter in self.parameter_iterator()])
##############################################
def __str__(self):
""" Return the SPICE element definition. """
return join_list((self.format_node_names(), self.format_spice_parameters(), self.raw_spice))
####################################################################################################
class AnyPinElement(Element):
__pins__ = ()
##############################################
def copy_to(self, netlist):
element = self.__class__(netlist, self._name)
super().copy_to(element)
return element
####################################################################################################
class FixedPinElement(Element):
##############################################
def __init__(self, netlist, name, *args, **kwargs):
# Get nodes
# Usage: if pins are passed using keywords then args must be empty
# optional pins are passed as keyword
pin_definition_nodes = []
number_of_args = len(args)
if number_of_args:
expected_number_of_pins = self.__class__.number_of_pins # Fixme:
if isinstance(expected_number_of_pins, slice):
expected_number_of_pins = expected_number_of_pins.start
if number_of_args < expected_number_of_pins:
raise NameError("Incomplete node list for element {}".format(self.name))
else:
nodes = args[:expected_number_of_pins]
args = args[expected_number_of_pins:]
pin_definition_nodes = zip(self.__pins__, nodes)
else:
for pin_definition in self.__pins__:
if pin_definition.name in kwargs:
node = kwargs[pin_definition.name]
del kwargs[pin_definition.name]
elif pin_definition.alias is not None and pin_definition.alias in kwargs:
node = kwargs[pin_definition.alias]
del kwargs[pin_definition.alias]
elif pin_definition.optional:
continue
else:
raise NameError("Node '{}' is missing for element {}".format(pin_definition.name, self.name))
pin_definition_nodes.append((pin_definition, node))
super().__init__(netlist, name, *args, **kwargs)
self._pins = [Pin(self, pin_definition, netlist.get_node(node, True))
for pin_definition, node in pin_definition_nodes]
##############################################
def copy_to(self, netlist):
element = self.__class__(netlist, self._name, *self.nodes)
super().copy_to(element)
return element
####################################################################################################
class NPinElement(Element):
__pins__ = '*'
##############################################
def __init__(self, netlist, name, nodes, *args, **kwargs):
super().__init__(netlist, name, *args, **kwargs)
self._pins = [Pin(self, PinDefinition(position), netlist.get_node(node, True))
for position, node in enumerate(nodes)]
##############################################
def copy_to(self, netlist):
nodes = [str(x) for x in self.nodes]
element = self.__class__(netlist, self._name, nodes)
super().copy_to(element)
return element
####################################################################################################
class Node:
"""This class implements a node in the circuit. It stores a reference to the pins connected to
the node.
"""
_logger = _module_logger.getChild('Node')
##############################################
def __init__(self, netlist, name):
if keyword.iskeyword(name):
self._logger.warning("Node name '{}' is a Python keyword".format(name))
self._netlist = netlist
self._name = str(name)
self._pins = set()
##############################################
def __repr__(self):
return 'Node {}'.format(self._name)
def __str__(self):
return self._name
##############################################
@property
def netlist(self):
return self._netlist
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._netlist._update_node_name(self, value) # update nodes dict
self._name = value
@property
def pins(self):
return self._pins
##############################################
@property
def is_ground_node(self):
return self._name in ('0', 'gnd')
##############################################
def __bool__(self):
return bool(self._pins)
##############################################
def __iter__(self):
return iter(self._pins)
##############################################
def connect(self, pin):
if pin not in self._pins:
self._pins.add(pin)
else:
raise ValueError("Pin {} is already connected to node {}".format(pin, self))
##############################################
def disconnect(self, pin):
self._pins.remove(pin)
####################################################################################################
class Netlist:
"""This class implements a base class for a netlist.
.. note:: This class is completed with element shortcuts when the module is loaded.
"""
_logger = _module_logger.getChild('Netlist')
##############################################
def __init__(self):
self._ground_name = 0
self._nodes = {}
self._ground_node = self._add_node(self._ground_name)
self._subcircuits = OrderedDict() # to keep the declaration order
self._elements = OrderedDict() # to keep the declaration order
self._models = {}
self.raw_spice = ''
# self._graph = networkx.Graph()
##############################################
def copy_to(self, netlist):
for subcircuit in self.subcircuits:
netlist.subcircuit(subcircuit)
for element in self.elements:
element.copy_to(netlist)
for name, model in self._models.items():
netlist._models[name] = model.clone()
netlist.raw_spice = str(self.raw_spice)
return netlist
##############################################
@property
def gnd(self):
return self._ground
@property
def nodes(self):
return self._nodes.values()
@property
def node_names(self):
return self._nodes.keys()
@property
def elements(self):
return self._elements.values()
@property
def element_names(self):
return self._elements.keys()
@property
def models(self):
return self._models.values()
@property
def model_names(self):
return self._models.keys()
@property
def subcircuits(self):
return self._subcircuits.values()
@property
def subcircuit_names(self):
return self._subcircuits.keys()
##############################################
def __getitem__(self, attribute_name):
if attribute_name in self._elements:
return self._elements[attribute_name]
elif attribute_name in self._models:
return self._models[attribute_name]
# Fixme: subcircuits
elif attribute_name in self._nodes:
return self._nodes[attribute_name]
else:
raise IndexError(attribute_name) # KeyError
##############################################
def __getattr__(self, attribute_name):
try:
return self.__getitem__(attribute_name)
except IndexError:
raise AttributeError(attribute_name)
##############################################
def _add_node(self, node_name):
node_name = str(node_name)
if node_name not in self._nodes:
node = Node(self, node_name)
self._nodes[node_name] = node
return node
else:
raise ValueError("Node {} is already defined".format(node_name))
##############################################
def _update_node_name(self, node, new_name):
if node.name not in self._nodes:
# should not happen
raise ValueError("Unknown node")
del self._nodes[node.name]
self._nodes[new_name] = node
##############################################
def get_node(self, node, create=False):
if isinstance(node, Node):
return node
else:
str_node = str(node)
if str_node in self._nodes:
return self._nodes[str_node]
elif create:
return self._add_node(str_node)
else:
raise KeyError("Node {} doesn't exists".format(node))
##############################################
def has_ground_node(self):
return bool(self._ground_node)
##############################################
def _add_element(self, element):
"""Add an element."""
if element.name not in self._elements:
self._elements[element.name] = element
else:
raise NameError("Element name {} is already defined".format(element.name))
##############################################
def _remove_element(self, element):
try:
del self._elements[element.name]
except KeyError:
raise NameError("Cannot remove undefined element {}".format(element))
##############################################
def model(self, name, modele_type, **parameters):
"""Add a model."""
model = DeviceModel(name, modele_type, **parameters)
if model.name not in self._models:
self._models[model.name] = model
else:
raise NameError("Model name {} is already defined".format(name))
return model
##############################################
def subcircuit(self, subcircuit):
"""Add a sub-circuit."""
# Fixme: subcircuit is a class
self._subcircuits[str(subcircuit.name)] = subcircuit
##############################################
def __str__(self):
""" Return the formatted list of element and model definitions. """
# Fixme: order ???
netlist = self._str_raw_spice()
netlist += self._str_subcircuits() # before elements
netlist += self._str_elements()
netlist += self._str_models()
return netlist
##############################################
def _str_elements(self):
elements = [element for element in self.elements if element.enabled]
return join_lines(elements) + os.linesep
##############################################
def _str_models(self):
if self._models:
return join_lines(self.models) + os.linesep
else:
return ''
##############################################
def _str_subcircuits(self):
if self._subcircuits:
return join_lines(self.subcircuits)
else:
return ''
##############################################
def _str_raw_spice(self):
netlist = self.raw_spice
if netlist and not netlist.endswith(os.linesep):
netlist += os.linesep
return netlist
####################################################################################################
class SubCircuit(Netlist):
"""This class implements a sub-cicuit netlist."""
##############################################
def __init__(self, name, *nodes, **kwargs):
if len(set(nodes)) != len(nodes):
raise ValueError("Duplicated nodes in {}".format(nodes))
super().__init__()
self._name = str(name)
self._external_nodes = nodes
# Fixme: ok ?
self._ground = kwargs.get('ground', 0)
if 'ground' in kwargs:
del kwargs['ground']
self._parameters = kwargs
##############################################
def clone(self, name=None):
if name is None:
name = self._name
# Fixme: clone parameters ???
kwargs = dict(self._parameters)
kwargs['ground'] = self._ground
subcircuit = self.__class__(name, list(self._external_nodes), **kwargs)
self.copy_to(subcircuit)
##############################################
@property
def name(self):
return self._name
@property
def external_nodes(self):
return self._external_nodes
@property
def parameters(self):
"""Parameters"""
return self._parameters
##############################################
def check_nodes(self):
"""Check for dangling nodes in the subcircuit."""
nodes = self._external_nodes
connected_nodes = set()
for element in self.elements:
connected_nodes.add(nodes & element.nodes)
not_connected_nodes = nodes - connected_nodes
if not_connected_nodes:
raise NameError("SubCircuit Nodes {} are not connected".format(not_connected_nodes))
##############################################
def __str__(self):
"""Return the formatted subcircuit definition."""
nodes = join_list(self._external_nodes)
parameters = join_list(['{}={}'.format(key, value)
for key, value in self._parameters.items()])
netlist = '.subckt ' + join_list((self._name, nodes, parameters)) + os.linesep
netlist += super().__str__()
netlist += '.ends ' + self._name + os.linesep
return netlist
####################################################################################################
class SubCircuitFactory(SubCircuit):
__name__ = None
__nodes__ = None
##############################################
def __init__(self, **kwargs):
super().__init__(self.__name__, *self.__nodes__, **kwargs)
####################################################################################################
class Circuit(Netlist):
"""This class implements a cicuit netlist.
To get the corresponding Spice netlist use::
circuit = Circuit()
...
str(circuit)
"""
_logger = _module_logger.getChild('Circuit')
##############################################
def __init__(self, title,
ground=0, # Fixme: gnd = 0
global_nodes=(),
):
super().__init__()
self.title = str(title)
self._ground = ground
self._global_nodes = set(global_nodes) # .global
self._includes = [] # .include
self._parameters = {} # .param
# Fixme: not implemented
# .csparam
# .func
# .if
# .lib
##############################################
def clone(self, title=None):
if title is None:
title = self.title
circuit = self.__class__(title, self._ground, set(self._global_nodes))
self.copy_to(circuit)
for include in self._includes:
circuit.include(include)
for name, value in self._parameters.items():
self.parameter(name, value)
return circuit
##############################################
def include(self, path):
"""Include a file."""
if path not in self._includes:
self._includes.append(path)
else:
self._logger.warn("Duplicated include")
##############################################
def parameter(self, name, expression):
"""Set a parameter."""
self._parameters[str(name)] = str(expression)
##############################################
def str(self, simulator=None):
"""Return the formatted desk."""
# if not self.has_ground_node():
# raise NameError("Circuit don't have ground node")
netlist = self._str_title()
netlist += self._str_includes(simulator)
netlist += self._str_globals()
netlist += self._str_parameters()
netlist += super().__str__()
return netlist
##############################################
def _str_title(self):
return '.title {}'.format(self.title) + os.linesep
##############################################
def _str_includes(self, simulator=None):
if self._includes:
# ngspice don't like // in path, thus ensure we write real paths
real_paths = []
for path in self._includes:
path = Path(str(path)).resolve()
if simulator:
path_flavour = Path(str(path) + '@' + simulator)
if path_flavour.exists():
path = path_flavour
real_paths.append(path)
return join_lines(real_paths, prefix='.include ') + os.linesep
else:
return ''
##############################################
def _str_globals(self):
if self._global_nodes:
return '.global ' + join_list(self._global_nodes) + os.linesep
else:
return ''
##############################################
def _str_parameters(self):
if self._parameters:
return join_lines(self._parameters, prefix='.param ') + os.linesep
else:
return ''
##############################################
def __str__(self):
return self.str(simulator=None)
##############################################
def str_end(self):
return str(self) + '.end' + os.linesep
##############################################
def simulator(self, *args, **kwargs):
return CircuitSimulator.factory(self, *args, **kwargs)
PySpice-pr-191/PySpice/Spice/NgSpice/ 0000775 0000000 0000000 00000000000 13654121544 0017335 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/NgSpice/RawFile.py 0000664 0000000 0000000 00000015306 13654121544 0021245 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import os
from ..RawFile import VariableAbc, RawFileAbc
####################################################################################################
"""This module provide tools to read the output of Ngspice.
Header
.. code::
Circuit: 230V Rectifier
Doing analysis at TEMP = 25.000000 and TNOM = 25.000000
Title: 230V Rectifier
Date: Thu Jun 4 23:40:58 2015
Plotname: Transient Analysis
Flags: real
No. Variables: 6
No. Points: 0
Variables:
No. of Data Columns : 6
0 time time
1 v(in) voltage
...
5 i(vinput) current
Binary:
Operating Point
Node voltages and source branch currents:
* v(node_name)
* i(vname)
Sensitivity Analysis
* v({element})
* v({element}_{parameter})
* v(v{source})
DC
* v(v-sweep)
* v({node})
* i(v{source})
AC
Frequency, node voltages and source branch currents:
* frequency
* v({node})
* i(v{name})
Transient Analysis
Time, node voltages and source branch currents:
* time
* v({node})
* i(v{source})
"""
# * v({element}:bv_max)
# * i(e.xdz1.ev1)
####################################################################################################
import logging
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
# Fixme: self._
class Variable(VariableAbc):
##############################################
def is_voltage_node(self):
return self.name.startswith('v(')
##############################################
def is_branch_current(self):
# source branch current
return self.name.startswith('i(')
##############################################
@property
def simplified_name(self):
if self.is_voltage_node() or self.is_branch_current():
return self.name[2:-1]
else:
return self.name
####################################################################################################
class RawFile(RawFileAbc):
""" This class parse the stdout of ngspice and the raw data output.
Public Attributes:
:attr:`circuit`
same as title
:attr:`data`
:attr:`date`
:attr:`flags`
'real' or 'complex'
:attr:`number_of_points`
:attr:`number_of_variables`
:attr:`plot_name`
AC Analysis, Operating Point, Sensitivity Analysis, DC transfer characteristic
:attr:`temperature`
:attr:`title`
:attr:`variables`
:attr:`warnings`
"""
_logger = _module_logger.getChild('RawFile')
__variable_cls__ = Variable
##############################################
def __init__(self, stdout, number_of_points):
self.number_of_points = number_of_points
raw_data = self._read_header(stdout)
self._read_variable_data(raw_data)
# self._to_analysis()
self._simulation = None
##############################################
def _read_header(self, stdout):
""" Parse the header """
binary_line = b'Binary:' + os.linesep.encode('ascii')
binary_location = stdout.find(binary_line)
if binary_location < 0:
raise NameError('Cannot locate binary data')
raw_data_start = binary_location + len(binary_line)
# self._logger.debug(os.linesep + stdout[:raw_data_start].decode('utf-8'))
header_lines = stdout[:binary_location].splitlines()
raw_data = stdout[raw_data_start:]
header_line_iterator = iter(header_lines)
self.circuit_name = self._read_header_field_line(header_line_iterator, 'Circuit')
self.temperature, self.nominal_temperature = self._read_temperature_line(header_line_iterator)
self.warnings = [self._read_header_field_line(header_line_iterator, 'Warning')
for i in range(stdout.count(b'Warning'))]
for warning in self.warnings:
self._logger.warn(warning)
self.title = self._read_header_field_line(header_line_iterator, 'Title')
self.date = self._read_header_field_line(header_line_iterator, 'Date')
self.plot_name = self._read_header_field_line(header_line_iterator, 'Plotname')
self.flags = self._read_header_field_line(header_line_iterator, 'Flags')
self.number_of_variables = int(self._read_header_field_line(header_line_iterator, 'No. Variables'))
self._read_header_field_line(header_line_iterator, 'No. Points')
self._read_header_field_line(header_line_iterator, 'Variables', has_value=False)
self._read_header_field_line(header_line_iterator, 'No. of Data Columns ')
self._read_header_variables(header_line_iterator)
return raw_data
##############################################
def fix_case(self):
""" Ngspice return lower case names. This method fixes the case of the variable names. """
circuit = self.circuit
element_translation = {element.lower():element for element in circuit.element_names}
node_translation = {node.lower():node for node in circuit.node_names}
for variable in self.variables.values():
variable.fix_case(element_translation, node_translation)
##############################################
def _to_dc_analysis(self):
if 'v(v-sweep)' in self.variables:
sweep_variable = self.variables['v(v-sweep)']
elif 'v(i-sweep)' in self.variables:
sweep_variable = self.variables['v(i-sweep)']
else:
#
raise NotImplementedError
return super()._to_dc_analysis(sweep_variable)
PySpice-pr-191/PySpice/Spice/NgSpice/Server.py 0000664 0000000 0000000 00000013234 13654121544 0021160 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module provides an interface to run ngspice in server mode and get back the simulation
output.
When ngspice runs in server mode, it writes on the standard output an header and then the simulation
output in binary format. At the end of the simulation, it writes on the standard error a line of
the form:
.. code::
@@@ \d+ \d+
where the second number is the number of points of the simulation. Due to the iterative and
adaptive nature of a transient simulation, the number of points is only known at the end.
Any line starting with "Error" in the standard output indicates an error in the simulation process.
The line "run simulation(s) aborted" in the standard error indicates the simulation aborted.
Any line starting with *Warning* in the standard error indicates non critical error in the
simulation process.
"""
####################################################################################################
import logging
import os
import re
import subprocess
####################################################################################################
from .RawFile import RawFile
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class SpiceServer:
"""This class wraps the execution of ngspice in server mode and convert the output to a Python data
structure.
Example of usage::
spice_server = SpiceServer(spice_command='/path/to/ngspice')
raw_file = spice_server(spice_input)
It returns a :obj:`PySpice.Spice.RawFile` instance.
"""
_logger = _module_logger.getChild('SpiceServer')
SPICE_COMMAND = 'ngspice'
##############################################
def __init__(self, **kwargs):
self._spice_command = kwargs.get('spice_command') or self.SPICE_COMMAND
##############################################
def _decode_number_of_points(self, line):
"""Decode the number of points in the given line."""
match = re.match(r'@@@ (\d+) (\d+)', line)
if match is not None:
return int(match.group(2))
else:
raise NameError("Cannot decode the number of points")
##############################################
def _parse_stdout(self, stdout):
"""Parse stdout for errors."""
# self._logger.debug(os.linesep + stdout)
error_found = False
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc0 in position 870: invalid start byte
# lines = stdout.decode('utf-8').splitlines()
lines = stdout.splitlines()
for line_index, line in enumerate(lines):
if line.startswith(b'Error '):
error_found = True
self._logger.error(os.linesep + line.decode('utf-8') + os.linesep + lines[line_index+1].decode('utf-8'))
if error_found:
raise NameError("Errors was found by Spice")
##############################################
def _parse_stderr(self, stderr):
"""Parse stderr for warnings and return the number of points."""
self._logger.debug(os.linesep + stderr)
stderr_lines = stderr.splitlines()
number_of_points = None
for line in stderr_lines:
if line.startswith('Warning:'):
self._logger.warning(line[len('Warning :'):])
elif line == 'run simulation(s) aborted':
raise NameError('Simulation aborted' + os.linesep + stderr)
elif line.startswith('@@@'):
number_of_points = self._decode_number_of_points(line)
return number_of_points
##############################################
def __call__(self, spice_input):
"""Run SPICE in server mode as a subprocess for the given input and return a
:obj:`PySpice.RawFile.RawFile` instance.
"""
self._logger.info("Start the spice subprocess")
process = subprocess.Popen((self._spice_command, '-s'),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
input_ = str(spice_input).encode('utf-8')
stdout, stderr = process.communicate(input_)
# stdout = stdout.decode('utf-8')
stderr = stderr.decode('utf-8')
self._parse_stdout(stdout)
number_of_points = self._parse_stderr(stderr)
if number_of_points is None:
raise NameError('The number of points was not found in the standard error buffer,'
' ngspice returned:' + os.linesep +
stderr)
return RawFile(stdout, number_of_points)
PySpice-pr-191/PySpice/Spice/NgSpice/Shared.py 0000664 0000000 0000000 00000120005 13654121544 0021113 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module provides a Python interface to the Ngspice shared library described in the *ngspice
as shared library or dynamic link library* section of the Ngspice user manual.
In comparison to the subprocess interface, it provides an interaction with the simulator through
commands and callbacks and it enables the usage of external voltage and current source in the
circuit.
.. This approach corresponds to the *standard way* to make an interface to a simulator code.
.. warning:: Since we don't simulate a circuit in a fresh environment on demand, this approach is
less safe than the subprocess approach. In case of bugs within Ngspice, we can expect some side
effects like memory leaks or worse unexpected things.
This interface use the CFFI module to interface with the shared library. It is thus suited to run
within the Pypy interpreter which implements JIT optimisation for Python.
It can also be used to experiment parallel simulation as explained in the Ngspice user manual. But
it seems the Ngspice source code was designed with global variables which imply to use one copy of
the shared library by worker as explained in the manual.
.. warning:: This interface can strongly slow down the simulation if the input or output callbacks
is used. If the simulation time goes wrong for you then you need to implement the callbacks at a
lower level than Python. You can have look to Pypy, Cython or a C extension module.
"""
####################################################################################################
import logging
import os
import platform
import re
# import time
import numpy as np
####################################################################################################
from cffi import FFI
ffi = FFI()
####################################################################################################
from PySpice.Unit import u_V, u_A, u_s, u_Hz, u_F
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
from PySpice.Config import ConfigInstall
from PySpice.Probe.WaveForm import (OperatingPoint, SensitivityAnalysis,
DcAnalysis, AcAnalysis, TransientAnalysis,
PoleZeroAnalysis, NoiseAnalysis, DistortionAnalysis, TransferFunctionAnalysis,
WaveForm)
from PySpice.Tools.EnumFactory import EnumFactory
from .SimulationType import SIMULATION_TYPE
####################################################################################################
def ffi_string_utf8(x):
return ffi.string(x).decode('utf8') # Fixme: ascii ?
####################################################################################################
class Vector:
""" This class implements a vector in a simulation output.
Public Attributes:
:attr:`data`
:attr:`name`
"""
_logger = _module_logger.getChild('Vector')
##############################################
def __init__(self, ngspice_shared, name, type_, data):
self._ngspice_shared = ngspice_shared
self._name = str(name)
self._type = type_
self._data = data
self._unit = ngspice_shared.type_to_unit(type_)
if self._unit is None:
self._logger.warning('Unit is None for {0._name} {0._type}'.format(self))
##############################################
def __repr__(self):
return 'variable: {0._name} {0._type}'.format(self)
##############################################
@property
def is_interval_parameter(self):
return self._name.startswith('@')
##############################################
@property
def is_voltage_node(self):
return self._type == self._ngspice_shared.simulation_type.voltage and not self.is_interval_parameter
##############################################
@property
def is_branch_current(self):
return self._type == self._ngspice_shared.simulation_type.current and not self.is_interval_parameter
##############################################
@property
def simplified_name(self):
if self.is_voltage_node and self._name.startswith('V('):
return self._name[2:-1]
elif self.is_branch_current:
# return self._name.replace('#branch', '')
return self._name[:-7]
else:
return self._name
##############################################
def to_waveform(self, abscissa=None, to_real=False, to_float=False):
""" Return a :obj:`PySpice.Probe.WaveForm` instance. """
data = self._data
if to_real:
data = data.real
# Fixme: else UnitValue instead of UnitValues
# if to_float:
# data = float(data[0])
if self._unit is not None:
return WaveForm.from_unit_values(self.simplified_name, self._unit(data), abscissa=abscissa)
else:
return WaveForm.from_array(self.simplified_name, data, abscissa=abscissa)
####################################################################################################
class Plot(dict):
""" This class implements a plot in a simulation output.
Public Attributes:
:attr:`plot_name`
"""
##############################################
def __init__(self, simulation, plot_name):
super().__init__()
self._simulation = simulation
self.plot_name = plot_name
##############################################
def nodes(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.values()
if variable.is_voltage_node]
##############################################
def branches(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.values()
if variable.is_branch_current]
##############################################
def internal_parameters(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.values()
if variable.is_interval_parameter]
##############################################
def elements(self, abscissa=None):
return [variable.to_waveform(abscissa, to_float=True)
for variable in self.values()]
##############################################
def to_analysis(self):
if self.plot_name.startswith('op'):
return self._to_operating_point_analysis()
elif self.plot_name.startswith('sens'):
return self._to_sensitivity_analysis()
elif self.plot_name.startswith('dc'):
return self._to_dc_analysis()
elif self.plot_name.startswith('ac'):
return self._to_ac_analysis()
elif self.plot_name.startswith('tran'):
return self._to_transient_analysis()
elif self.plot_name.startswith('pz'):
return self._to_polezero_analysis()
elif self.plot_name.startswith('noise'):
return self._to_noise_analysis()
elif self.plot_name.startswith('disto'):
return self._to_distortion_analysis()
elif self.plot_name.startswith('tf'):
return self._to_transfer_function_analysis()
else:
raise NotImplementedError("Unsupported plot name {}".format(self.plot_name))
##############################################
def _to_operating_point_analysis(self):
return OperatingPoint(
simulation=self._simulation,
nodes=self.nodes(to_float=True),
branches=self.branches(to_float=True),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_sensitivity_analysis(self):
# Fixme: separate v(vinput), analysis.R2.m
return SensitivityAnalysis(
simulation=self._simulation,
elements=self.elements(), # Fixme: internal parameters ???
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_dc_analysis(self):
# if 'v(v-sweep)' in self:
# sweep_variable = self['v(v-sweep)']
# elif 'v(i-sweep)' in self:
# sweep_variable = self['v(i-sweep)']
if 'v-sweep' in self:
sweep_variable = self['v-sweep']
elif 'i-sweep' in self:
sweep_variable = self['i-sweep']
else:
raise NotImplementedError(str(self))
sweep = sweep_variable.to_waveform()
return DcAnalysis(
simulation=self._simulation,
sweep=sweep,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_ac_analysis(self):
frequency = self['frequency'].to_waveform(to_real=True)
return AcAnalysis(
simulation=self._simulation,
frequency=frequency,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_transient_analysis(self):
time = self['time'].to_waveform(to_real=True)
return TransientAnalysis(
simulation=self._simulation,
time=time,
nodes=self.nodes(abscissa=time),
branches=self.branches(abscissa=time),
internal_parameters=self.internal_parameters(abscissa=time),
)
##############################################
def _to_polezero_analysis(self):
return PoleZeroAnalysis(
simulation=self._simulation,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_noise_analysis(self):
return NoiseAnalysis(
simulation=self._simulation,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_distortion_analysis(self):
frequency = self['frequency'].to_waveform(to_real=True)
return DistortionAnalysis(
simulation=self._simulation,
frequency=frequency,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_transfer_function_analysis(self):
return TransferFunctionAnalysis(
simulation=self._simulation,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
####################################################################################################
class NgSpiceShared:
_logger = _module_logger.getChild('NgSpiceShared')
NGSPICE_PATH = None
LIBRARY_PATH = None
__MAX_COMMAND_LENGTH__ = 1023
##############################################
_instances = {}
@classmethod
def new_instance(cls, ngspice_id=0, send_data=False):
# Fixme: send_data
if ngspice_id in cls._instances:
return cls._instances[ngspice_id]
else:
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
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.
"""
self._ngspice_id = ngspice_id
self._stdout = []
self._stderr = []
self._load_library()
self._init_ngspice(send_data)
self._is_running = False
##############################################
def _load_library(self):
if ConfigInstall.OS.on_windows:
# https://sourceforge.net/p/ngspice/discussion/133842/thread/1cece652/#4e32/5ab8/9027
# When environment variable SPICE_LIB_DIR is empty, ngspice looks in C:\Spice64\share\ngspice\scripts
# Else it tries %SPICE_LIB_DIR%\scripts\spinit
if 'SPICE_LIB_DIR' not in os.environ:
os.environ['SPICE_LIB_DIR'] = os.path.join(self.NGSPICE_PATH, 'share', 'ngspice')
api_path = os.path.join(os.path.dirname(__file__), 'api.h')
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)
# Note: cannot yet execute command
##############################################
def _init_ngspice(self, send_data):
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)
self._send_init_data_c = ffi.callback('int (pvecinfoall, int, void *)', self._send_init_data)
self._background_thread_running_c = ffi.callback('int (bool, int, void *)', self._background_thread_running)
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._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
rc = self._ngspice_shared.ngSpice_Init(self._send_char_c,
self._send_stat_c,
self._exit_c,
self._send_data_c,
self._send_init_data_c,
self._background_thread_running_c,
self_c)
if rc:
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
rc = self._ngspice_shared.ngSpice_Init_Sync(self._get_vsrc_data_c,
self._get_isrc_data_c,
ffi.NULL, # GetSyncData
ngspice_id_c,
self_c)
if rc:
raise NameError("Ngspice_Init_Sync returned {}".format(rc))
self._get_version()
try:
self._simulation_type = EnumFactory('SimulationType', SIMULATION_TYPE[self._ngspice_version])
except KeyError:
self._simulation_type = EnumFactory('SimulationType', SIMULATION_TYPE['last'])
self._logger.warning("Unsupported Ngspice version {}".format(self._ngspice_version))
self._type_to_unit = {
self._simulation_type.time: u_s,
self._simulation_type.voltage: u_V,
self._simulation_type.current: u_A,
self._simulation_type.frequency: u_Hz,
self._simulation_type.capacitance: u_F,
}
# Prevent paging output of commands (hangs)
self.set('nomoremode')
##############################################
@staticmethod
def _send_char(message_c, ngspice_id, user_data):
"""Callback for sending output from stdout, stderr to caller"""
self = ffi.from_handle(user_data)
message = ffi_string_utf8(message_c)
prefix, _, content = message.partition(' ')
if prefix == 'stderr':
self._stderr.append(content)
self._logger.error(content)
else:
self._stdout.append(content)
# Fixme: ???
return self.send_char(message, ngspice_id)
##############################################
@staticmethod
def _send_stat(message, ngspice_id, user_data):
"""Callback for simulation status to caller"""
self = ffi.from_handle(user_data)
return self.send_stat(ffi_string_utf8(message), ngspice_id)
##############################################
@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._logger.debug('ngspice_id-{} exit status={} immediate_unloding={} quit_exit={}'.format(
ngspice_id,
exit_status,
immediate_unloding,
quit_exit))
return exit_status
##############################################
@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._logger.debug('ngspice_id-{} send_data [{}]'.format(ngspice_id, data.vecindex))
actual_vector_values = {}
for i in range(int(number_of_vectors)):
actual_vector_value = data.vecsa[i]
vector_name = ffi_string_utf8(actual_vector_value.name)
value = complex(actual_vector_value.creal, actual_vector_value.cimag)
actual_vector_values[vector_name] = value
# self._logger.debug(' Vector: {} {}'.format(vector_name, value))
return self.send_data(actual_vector_values, number_of_vectors, ngspice_id)
##############################################
@staticmethod
def _send_init_data(data, ngspice_id, user_data):
"""Callback to send back initialization vector data"""
self = ffi.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
##############################################
@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._logger.debug('ngspice_id-{} background_thread_running {}'.format(ngspice_id, is_running))
self._is_running = is_running
##############################################
@staticmethod
def _get_vsrc_data(voltage, time, node, ngspice_id, user_data):
"""FFI Callback"""
self = ffi.from_handle(user_data)
return self.get_vsrc_data(voltage, time, ffi_string_utf8(node), ngspice_id)
##############################################
@staticmethod
def _get_isrc_data(current, time, node, ngspice_id, user_data):
"""FFI Callback"""
self = ffi.from_handle(user_data)
return self.get_isrc_data(current, time, ffi_string_utf8(node), ngspice_id)
##############################################
def send_char(self, message, ngspice_id):
""" Reimplement this callback in a subclass to process logging messages from the simulator. """
# self._logger.debug('ngspice-{} send_char {}'.format(ngspice_id, message))
return 0
##############################################
def send_stat(self, message, ngspice_id):
""" Reimplement this callback in a subclass to process statistic messages from the simulator. """
# self._logger.debug('ngspice-{} send_stat {}'.format(ngspice_id, message))
return 0
##############################################
def send_data(self, actual_vector_values, number_of_vectors, ngspice_id):
""" Reimplement this callback in a subclass to process the vector actual values. """
return 0
##############################################
def send_init_data(self, data, ngspice_id):
""" Reimplement this callback in a subclass to process the initial data. """
return 0
##############################################
def get_vsrc_data(self, voltage, time, node, ngspice_id):
""" Reimplement this callback in a subclass to provide external voltage source. """
self._logger.debug('ngspice_id-{} get_vsrc_data @{} node {}'.format(ngspice_id, time, node))
return 0
##############################################
def get_isrc_data(self, current, time, node, ngspice_id):
""" Reimplement this callback in a subclass to provide external current source. """
self._logger.debug('ngspice_id-{} get_isrc_data @{} node {}'.format(ngspice_id, time, node))
return 0
##############################################
@staticmethod
def _convert_string_array(array):
strings = []
i = 0
while (True):
if array[i] == ffi.NULL:
break
else:
strings.append(ffi_string_utf8(array[i]))
i += 1
return strings
##############################################
@staticmethod
def _to_python(value):
try:
return int(value)
except ValueError:
try:
# Fixme: return float(value.replace(',', '.'))
return float(value)
except ValueError:
return str(value)
##############################################
@staticmethod
def _lines_to_dicts(lines):
values = dict(description=lines[0])
values.update({parts[0]: NgSpiceShared._to_python(parts[1])
for parts in map(str.split, lines)})
return values
##############################################
@property
def is_running(self):
return self._is_running
##############################################
def clear_output(self):
self._stdout = []
self._stderr = []
##############################################
@property
def stdout(self):
return os.linesep.join(self._stdout)
@property
def stderr(self):
return os.linesep.join(self._stderr)
##############################################
def exec_command(self, command, join_lines=True):
""" Execute a command and return the output. """
if len(command) > self.__MAX_COMMAND_LENGTH__:
raise ValueError('Command must not exceed {} characters'.format(self.__MAX_COMMAND_LENGTH__))
self._logger.debug('Execute command: {}'.format(command))
self.clear_output()
rc = self._ngspice_shared.ngSpice_Command(command.encode('ascii'))
if rc:
raise NameError("ngSpice_Command '{}' returned {}".format(command, rc))
if join_lines:
return self.stdout
else:
return self._stdout
##############################################
def _get_version(self):
self._ngspice_version = None
self._has_xspice = False
self._has_cider = False
self._extensions = []
output = self.exec_command('version -f')
for line in output.split('\n'):
match = re.match('\*\* ngspice\-(\d+)', line)
if match is not None:
self._ngspice_version = int(match.group(1))
# if '** XSPICE extensions included' in line:
if '** XSPICE' in line:
self._has_xspice = True
self._extensions.append('XSPICE')
# if '** CIDER 1.b1 (CODECS simulator) included' in line:
if 'CIDER' in line:
self._has_cider = True
self._extensions.append('CIDER')
self._logger.debug('Ngspice version {} with extensions: {}'.format(
self._ngspice_version,
', '.join(self._extensions),
))
##############################################
@property
def ngspice_version(self):
return self._ngspice_version
@property
def has_xspice(self):
"""Return True if libngspice was compiled with XSpice support."""
return self._has_xspice
@property
def has_cider(self):
"""Return True if libngspice was compiled with CIDER support."""
return self._has_cider
##############################################
@property
def simulation_type(self):
return self._simulation_type
def type_to_unit(self, vector_type):
return self._type_to_unit.get(vector_type, None)
##############################################
def _alter(self, command, device, kwargs):
device_name = device.lower()
for key, value in kwargs.items():
if isinstance(value, (list, tuple)):
value = '[ ' + ' '.join(value) + ' ]'
self.exec_command('{} {} {} = {}'.format(command, device_name, key, value))
##############################################
def alter_device(self, device, **kwargs):
"""Alter device parameters"""
self._alter('alter', device, kwargs)
##############################################
def alter_model(self, model, **kwargs):
"""Alter model parameters"""
self._alter('altermod', model, kwargs)
##############################################
def delete(self, debug_number):
"""Remove a trace or breakpoint"""
self.exec_command('delete {}'.format(debug_number))
##############################################
def destroy(self, plot_name='all'):
"""Release the memory holding the output data (the given plot or all plots) for the specified runs."""
self.exec_command('destroy ' + plot_name)
##############################################
def device_help(self, device):
"""Shows the user information about the devices available in the simulator. """
return self.exec_command('devhelp ' + device.lower())
##############################################
def save(self, vector):
self.exec_command('save ' + vector)
##############################################
def show(self, device):
command = 'show ' + device.lower()
lines = self.exec_command(command, join_lines=False)
values = self._lines_to_dicts(lines)
return values
##############################################
def showmod(self, device):
command = 'showmod ' + device.lower()
lines = self.exec_command(command, join_lines=False)
values = self._lines_to_dicts(lines)
return values
##############################################
def source(self, file_path):
"""Read a ngspice input file"""
self.exec_command('source ' + file_path)
##############################################
def option(self, **kwargs):
"""Set any of the simulator variables."""
for key, value in kwargs.items():
self.exec_command('option {} = {}'.format(key, value))
##############################################
def quit(self):
self.set('noaskquit')
return self.exec_command('quit')
##############################################
def remove_circuit(self):
"""Removes the current circuit from the list of circuits sourced into ngspice."""
self.exec_command('remcirc')
##############################################
def reset(self):
"""Throw out any intermediate data in the circuit (e.g, after a breakpoint or after one or more
analyses have been done already), and re-parse the input file. The circuit can then be
re-run from it’s initial state, overriding the affect of any set or alter commands.
"""
self.exec_command('reset')
##############################################
def ressource_usage(self, *ressources):
"""Print resource usage statistics. If any resources are given, just print the usage of that resource.
Most resources require that a circuit be loaded. Currently valid resources are:
* decklineno Number of lines in deck
* netloadtime Nelist loading time
* netparsetime Netlist parsing time
* elapsed The amount of time elapsed since the last rusage elapsed call.
* faults Number of page faults and context switches (BSD only).
* space Data space used.
* time CPU time used so far.
* temp Operating temperature.
* tnom Temperature at which device parameters were measured.
* equations Circuit Equations
* time Total Analysis Time
* totiter Total iterations
* accept Accepted time-points
* rejected Rejected time-points
* loadtime Time spent loading the circuit matrix and RHS.
* reordertime Matrix reordering time
* lutime L-U decomposition time
* solvetime Matrix solve time
* trantime Transient analysis time
* tranpoints Transient time-points
* traniter Transient iterations
* trancuriters Transient iterations for the last time point*
* tranlutime Transient L-U decomposition time
* transolvetime Transient matrix solve time
* everything All of the above.
"""
if not ressources:
ressources = ['everything']
command = 'rusage ' + ' '.join(ressources)
lines = self.exec_command(command, join_lines=False)
values = {}
for line in lines:
if '=' in line:
parts = line.split(' = ')
else:
parts = line.split(': ')
values[parts[0]] = NgSpiceShared._to_python(parts[1])
return values
##############################################
def set(self, *args, **kwargs):
"""Set the value of variables"""
for key in args:
self.exec_command('set {}'.format(key))
for key, value in kwargs.items():
self.exec_command('option {} = {}'.format(key, value))
##############################################
def set_circuit(self, name):
"""Change the current circuit"""
self.exec_command('setcirc {}'.format(name))
##############################################
def status(self):
"""Display breakpoint information"""
return self.exec_command('status')
##############################################
def step(self, number_of_steps=None):
"""Run a fixed number of time-points"""
if number_of_steps is not None:
self.exec_command('step {}'.format(number_of_steps))
else:
self.exec_command('step')
##############################################
def stop(self, *args, **kwargs):
"""Set a breakpoint.
Examples::
ngspice.stop('v(out) > 1', 'v(1) > 10', after=10)
A when condition can use theses symbols: = <> > < >= <=.
"""
command = 'stop'
if 'after' in kwargs:
command += ' after {}'.format(kwargs['after'])
for condition in args:
command += ' when {}'.format(condition)
self.exec_command(command)
##############################################
def trace(self, *args):
"""Trace nodes"""
self.exec_command('trace ' + ' '.join(args))
##############################################
def unset(self, *args):
"""Unset variables"""
for key in args:
self.exec_command('unset {}'.format(key))
##############################################
def where(self):
"""Identify troublesome node or device"""
return self.exec_command('where')
##############################################
def load_circuit(self, circuit):
"""Load the given circuit string."""
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_array = ffi.new("char *[]", circuit_lines_keepalive)
rc = self._ngspice_shared.ngSpice_Circ(circuit_array)
if rc:
raise NameError("ngSpice_Circ returned {}".format(rc))
# for line in circuit_lines:
# rc = self._ngspice_shared.ngSpice_Command('circbyline ' + line)
# if rc:
# raise NameError("ngSpice_Command circbyline returned {}".format(rc))
##############################################
def run(self, background=False):
""" Run the simulation. """
# in the background thread and wait until the simulation is done
command = b'bg_run' if background else b'run'
rc = self._ngspice_shared.ngSpice_Command(command)
if rc:
raise NameError("ngSpice_Command run returned {}".format(rc))
if background:
self._is_running = True
else:
self._logger.debug("Simulation is done")
# time.sleep(.1) # required before to test if the simulation is running
# while (self._ngspice_shared.ngSpice_running()):
# time.sleep(.1)
# self._logger.debug("Simulation is done")
##############################################
def halt(self):
""" Halt the simulation in the background thread. """
rc = self._ngspice_shared.ngSpice_Command(b'bg_halt')
if rc:
raise NameError("ngSpice_Command bg_halt returned {}".format(rc))
##############################################
def resume(self, background=True):
""" Halt the simulation in the background thread. """
command = b'bg_resume' if background else b'resume'
rc = self._ngspice_shared.ngSpice_Command(command)
if rc:
raise NameError("ngSpice_Command bg_resume returned {}".format(rc))
##############################################
@property
def plot_names(self):
""" Return the list of plot names. """
return self._convert_string_array(self._ngspice_shared.ngSpice_AllPlots())
##############################################
@property
def last_plot(self):
""" Return the last plot name. """
return self.plot_names[0]
##############################################
@staticmethod
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.
# };
if flags & 1:
return 'real'
elif flags & 2:
return 'complex'
##############################################
@staticmethod
def _vector_is_real(flags):
return flags & 1
##############################################
@staticmethod
def _vector_is_complex(flags):
return flags & 2
##############################################
def plot(self, simulation, plot_name):
""" Return the corresponding plot. """
# plot_name is for example dc with an integer suffix which is increment for each run
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:
break
else:
vector_name = ffi_string_utf8(all_vectors_c[i])
name = '.'.join((plot_name, vector_name))
vector_info = self._ngspice_shared.ngGet_Vec_Info(name.encode('utf8'))
vector_type = self._simulation_type[vector_info.v_type]
length = vector_info.v_length
# self._logger.debug("vector[{}] {} type {} flags {} length {}".format(i,
# vector_name,
# vector_type,
# self._flags_to_str(vector_info.v_flags),
# length))
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
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)
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)
i += 1
return plot
####################################################################################################
#
# Platform setup
#
if ConfigInstall.OS.on_windows:
drive = os.getenv('SystemDrive') or 'C:'
root = drive + os.sep
ngspice_dirname = 'Spice'
if platform.architecture()[0] == '64bit':
ngspice_dirname += '64'
ngspice_dirname += '_dll'
ngspice_path = os.path.join(root, 'Program Files', ngspice_dirname)
NgSpiceShared.NGSPICE_PATH = ngspice_path
_path = os.path.join(ngspice_path, 'dll-vs', 'ngspice{}.dll')
elif ConfigInstall.OS.on_osx:
_path = 'libngspice{}.dylib'
elif ConfigInstall.OS.on_linux:
_path = 'libngspice{}.so'
else:
raise NotImplementedError
NgSpiceShared.LIBRARY_PATH= _path
PySpice-pr-191/PySpice/Spice/NgSpice/Simulation.py 0000664 0000000 0000000 00000010140 13654121544 0022027 0 ustar 00root root 0000000 0000000 ###################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This modules implements classes to perform simulations.
"""
####################################################################################################
import logging
####################################################################################################
from ..Simulation import CircuitSimulator
from .Server import SpiceServer
from .Shared import NgSpiceShared
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class NgSpiceCircuitSimulator(CircuitSimulator):
SIMULATOR = 'ngspice'
##############################################
def __init__(self, circuit, **kwargs):
super().__init__(circuit, **kwargs)
if kwargs.get('pipe', True):
self.options('NOINIT')
self.options(filetype='binary')
####################################################################################################
class NgSpiceSubprocessCircuitSimulator(NgSpiceCircuitSimulator):
_logger = _module_logger.getChild('NgSpiceSubprocessCircuitSimulator')
##############################################
def __init__(self, circuit, **kwargs):
super().__init__(circuit, pipe=True, **kwargs)
# Fixme: to func ?
server_kwargs = {x:kwargs[x] for x in ('spice_command',) if x in kwargs}
self._spice_server = SpiceServer(**server_kwargs)
##############################################
def _run(self, analysis_method, *args, **kwargs):
super()._run(analysis_method, *args, **kwargs)
raw_file = self._spice_server(spice_input=str(self))
self.reset_analysis()
raw_file.simulation = self
# for field in raw_file.variables:
# print field
return raw_file.to_analysis()
####################################################################################################
class NgSpiceSharedCircuitSimulator(NgSpiceCircuitSimulator):
_logger = _module_logger.getChild('NgSpiceSharedCircuitSimulator')
##############################################
def __init__(self, circuit, **kwargs):
super().__init__(circuit, pipe=False, **kwargs)
ngspice_shared = kwargs.get('ngspice_shared', None)
if ngspice_shared is None:
self._ngspice_shared = NgSpiceShared.new_instance()
else:
self._ngspice_shared = ngspice_shared
##############################################
@property
def ngspice(self):
return self._ngspice_shared
##############################################
def _run(self, analysis_method, *args, **kwargs):
super()._run(analysis_method, *args, **kwargs)
self._ngspice_shared.destroy()
self._ngspice_shared.load_circuit(str(self))
self._ngspice_shared.run()
self._logger.debug(str(self._ngspice_shared.plot_names))
self.reset_analysis()
plot_name = self._ngspice_shared.last_plot
if plot_name == 'const':
raise NameError('Invalid plot name')
return self._ngspice_shared.plot(self, plot_name).to_analysis()
PySpice-pr-191/PySpice/Spice/NgSpice/SimulationType.py 0000664 0000000 0000000 00000004214 13654121544 0022676 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
__all__ = [
'LAST_VERSION',
'SIMULATION_TYPE',
]
####################################################################################################
# cf. ngspice-xx/include/ngspice/sim.h
SIMULATION_TYPE = {}
SIMULATION_TYPE[26] = (
'no_type',
'time',
'frequency',
'voltage',
'current',
'output_n_dens',
'output_noise',
'input_n_dens',
'input_noise',
'pole',
'zero',
's_parameter',
'temperature',
'res',
'impedance',
'admittance',
'power',
'phase',
'db',
'capacitance',
'charge',
)
SIMULATION_TYPE[27] = (
'no_type',
'time',
'frequency',
'voltage',
'current',
'voltage_density',
'current_density',
'sqr_voltage_density',
'sqr_current_density',
'sqr_voltage',
'sqr_current',
'pole',
'zero',
's_parameter',
'temperature',
'res',
'impedance',
'admittance',
'power',
'phase',
'db',
'capacitance',
'charge',
)
LAST_VERSION = 30
for version in range(28, LAST_VERSION +1):
SIMULATION_TYPE[version] = SIMULATION_TYPE[27]
SIMULATION_TYPE['last'] = SIMULATION_TYPE[LAST_VERSION]
PySpice-pr-191/PySpice/Spice/NgSpice/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0021434 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/NgSpice/api.h 0000664 0000000 0000000 00000003433 13654121544 0020262 0 ustar 00root root 0000000 0000000 /* Simplified Ngspice API for CFFI parser */
typedef struct ngcomplex
{
double cx_real;
double cx_imag;
} ngcomplex_t;
typedef struct vector_info
{
char *v_name;
int v_type;
short v_flags;
double *v_realdata;
ngcomplex_t *v_compdata;
int v_length;
} vector_info, *pvector_info;
typedef struct vecvalues
{
char *name;
double creal;
double cimag;
bool is_scale;
bool is_complex;
} vecvalues, *pvecvalues;
typedef struct vecvaluesall
{
int veccount;
int vecindex;
pvecvalues *vecsa;
} vecvaluesall, *pvecvaluesall;
typedef struct vecinfo
{
int number;
char *vecname;
bool is_real;
void *pdvec;
void *pdvecscale;
} vecinfo, *pvecinfo;
typedef struct vecinfoall
{
char *name;
char *title;
char *date;
char *type;
int veccount;
pvecinfo *vecs;
} vecinfoall, *pvecinfoall;
typedef int (SendChar) (char *, int, void *);
typedef int (SendStat) (char *, int, void *);
typedef int (ControlledExit) (int, bool, bool, int, void *);
typedef int (SendData) (pvecvaluesall, int, int, void *);
typedef int (SendInitData) (pvecinfoall, int, void *);
typedef int (BGThreadRunning) (bool, int, void *);
typedef int (GetVSRCData) (double *, double, char *, int, void *);
typedef int (GetISRCData) (double *, double, char *, int, void *);
typedef int (GetSyncData) (double, double *, double, int, int, int, void *);
int ngSpice_Init (SendChar *, SendStat *, ControlledExit *, SendData *, SendInitData *, BGThreadRunning *, void *);
int ngSpice_Init_Sync (GetVSRCData *, GetISRCData *, GetSyncData *, int *, void *);
int ngSpice_Command (char *);
pvector_info ngGet_Vec_Info (char *);
int ngSpice_Circ (char **);
char *ngSpice_CurPlot (void);
char **ngSpice_AllPlots (void);
char **ngSpice_AllVecs (char *);
bool ngSpice_running (void);
bool ngSpice_SetBkpt (double);
/* End */
PySpice-pr-191/PySpice/Spice/Parser.py 0000664 0000000 0000000 00000076102 13654121544 0017621 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
"""This module implements a partial SPICE netlist parser.
See the :command:`cir2py` tool for an example of usage of the parser.
It would be difficult to implement a full parser for Ngspice since the syntax is mainly contextual.
"""
####################################################################################################
import logging
import os
####################################################################################################
from .BasicElement import SubCircuitElement
from .ElementParameter import FlagParameter
from .Netlist import ElementParameterMetaClass, Circuit, SubCircuit
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class ParseError(NameError):
pass
####################################################################################################
class PrefixData:
"""This class represents a device prefix."""
##############################################
def __init__(self, prefix, classes):
self.prefix = prefix
self.classes = classes
number_of_positionals_min = 1000
number_of_positionals_max = 0
has_optionals = False
for element_class in classes:
number_of_positionals = element_class.number_of_positional_parameters
number_of_positionals_min = min(number_of_positionals_min, number_of_positionals)
number_of_positionals_max = max(number_of_positionals_max, number_of_positionals)
has_optionals = max(has_optionals, bool(element_class.optional_parameters))
self.number_of_positionals_min = number_of_positionals_min
self.number_of_positionals_max = number_of_positionals_max
self.has_optionals = has_optionals
self.multi_devices = len(classes) > 1
self.has_variable_number_of_pins = prefix in ('Q', 'X') # NPinElement, Q has 3 to 4 pins
if self.has_variable_number_of_pins:
self.number_of_pins = None
else:
# Q and X are single
self.number_of_pins = classes[0].number_of_pins
self.has_flag = False
for element_class in classes:
for parameter in element_class.optional_parameters.values():
if isinstance(parameter, FlagParameter):
self.has_flag = True
##############################################
def __len__(self):
return len(self.classes)
##############################################
def __iter__(self):
return iter(self.classes)
##############################################
@property
def single(self):
if not self.multi_devices:
return self.classes[0]
else:
raise NameError()
####################################################################################################
_prefix_cache = {}
for prefix, classes in ElementParameterMetaClass.__classes__.items():
prefix_data = PrefixData(prefix, classes)
_prefix_cache[prefix] = prefix_data
_prefix_cache[prefix.lower()] = prefix_data
# for prefix_data in sorted(_prefix_cache.values(), key=lambda x: len(x)):
# print(prefix_data.prefix,
# len(prefix_data),
# prefix_data.number_of_positionals_min, prefix_data.number_of_positionals_max,
# prefix_data.has_optionals)
# Single:
# B 0 True
# D 1 True
# F 2 False
# G 1 False
# H 2 False
# I 1 False
# J 1 True
# K 3 False
# M 1 True
# S 2 False
# V 1 False
# W 3 False
# Z 1 True
# Two:
# E 0 1 False
# L 1 2 True
# Three:
# C 1 2 True
# R 1 2 True
# NPinElement:
# Q 1 1 True
# X 1 1 False
####################################################################################################
class Statement:
""" This class implements a statement, in fact a line in a Spice netlist. """
##############################################
def __init__(self, line, statement=None):
self._line = line
if statement is not None:
self._line.lower_case_statement(statement)
##############################################
def __repr__(self):
return '{} {}'.format(self.__class__.__name__, repr(self._line))
##############################################
def value_to_python(self, x):
if x:
if str(x)[0].isdigit():
return str(x)
else:
return "'{}'".format(x)
else:
return ''
##############################################
def values_to_python(self, values):
return [self.value_to_python(x) for x in values]
##############################################
def kwargs_to_python(self, kwargs):
return ['{}={}'.format(key, self.value_to_python(value))
for key, value in kwargs.items()]
##############################################
def join_args(self, args):
return ', '.join(args)
####################################################################################################
class Comment(Statement):
pass
####################################################################################################
class Title(Statement):
""" This class implements a title definition. """
##############################################
def __init__(self, line):
super().__init__(line, statement='title')
self._title = self._line.right_of('.title')
##############################################
def __str__(self):
return self._title
##############################################
def __repr__(self):
return 'Title {}'.format(self._title)
####################################################################################################
class Include(Statement):
""" This class implements a include definition. """
##############################################
def __init__(self, line):
super().__init__(line, statement='include')
self._include = self._line.right_of('.include')
##############################################
def __str__(self):
return self._include
##############################################
def __repr__(self):
return 'Include {}'.format(self._include)
##############################################
def to_python(self, netlist_name):
return '{}.include({})'.format(netlist_name, self._include) + os.linesep
####################################################################################################
class Model(Statement):
""" This class implements a model definition.
Spice syntax::
.model mname type (pname1=pval1 pname2=pval2)
"""
##############################################
def __init__(self, line):
super().__init__(line, statement='model')
text = line.right_of('.model')
kwarg_start = text.find('(')
kwarg_stop = text.find(')')
if kwarg_start == -1 or kwarg_stop == -1:
# raise ParseError("Bad model: {}".format(line))
parts, self._parameters = line.split_line('.model')
self._name, self._model_type = parts
else:
self._name, self._model_type = text[:kwarg_start].split()
self._parameters = Line.get_kwarg(text[kwarg_start+1:kwarg_stop])
##############################################
@property
def name(self):
""" Name of the model """
return self._name
##############################################
def __repr__(self):
return 'Model {} {} {}'.format(self._name, self._model_type, self._parameters)
##############################################
def to_python(self, netlist_name):
args = self.values_to_python((self._name, self._model_type))
kwargs = self.kwargs_to_python(self._parameters)
return '{}.model({})'.format(netlist_name, self.join_args(args + kwargs)) + os.linesep
##############################################
def build(self, circuit):
circuit.model(self._name, self._model_type, **self._parameters)
####################################################################################################
class SubCircuitStatement(Statement):
""" This class implements a sub-circuit definition.
Spice syntax::
.SUBCKT name node1 ... param1=value1 ...
"""
##############################################
def __init__(self, line):
super().__init__(line, statement='subckt')
# Fixme
parameters, dict_parameters = self._line.split_line('.subckt')
self._name, self._nodes = parameters[0], parameters[1:]
self._statements = []
##############################################
@property
def name(self):
""" Name of the sub-circuit. """
return self._name
@property
def nodes(self):
""" Nodes of the sub-circuit. """
return self._nodes
##############################################
def __repr__(self):
text = 'SubCircuit {} {}'.format(self._name, self._nodes) + os.linesep
text += os.linesep.join([' ' + repr(statement) for statement in self._statements])
return text
##############################################
def __iter__(self):
""" Return an iterator on the statements. """
return iter(self._statements)
##############################################
def append(self, statement):
""" Append a statement to the statement's list. """
self._statements.append(statement)
##############################################
def to_python(self, ground=0):
subcircuit_name = 'subcircuit_' + self._name
args = self.values_to_python([subcircuit_name] + self._nodes)
source_code = ''
source_code += '{} = SubCircuit({})'.format(subcircuit_name, self.join_args(args)) + os.linesep
source_code += SpiceParser.netlist_to_python(subcircuit_name, self, ground)
return source_code
##############################################
def build(self, ground=0):
subcircuit = SubCircuit(self._name, *self._nodes)
SpiceParser._build_circuit(subcircuit, self._statements, ground)
return subcircuit
####################################################################################################
class Element(Statement):
""" This class implements an element definition.
"{ expression }" are allowed in device line.
"""
_logger = _module_logger.getChild('Element')
##############################################
def __init__(self, line):
super().__init__(line)
line_str = str(line)
# self._logger.debug(os.linesep + line_str)
# Retrieve device prefix
self._prefix = line_str[0]
prefix_data = _prefix_cache[self._prefix]
# Retrieve device name
start_location = 1
stop_location = line_str.find(' ')
# Fixme: if stop_location == -1:
self._name = line_str[start_location:stop_location]
self._nodes = []
self._parameters = []
self._dict_parameters = {}
# Read nodes
if not prefix_data.has_variable_number_of_pins:
number_of_pins = prefix_data.number_of_pins
if number_of_pins:
self._nodes, stop_location = self._line.read_words(stop_location, number_of_pins)
else: # Q or X
if prefix_data.prefix == 'Q':
self._nodes, stop_location = self._line.read_words(stop_location, 3)
# Fixme: optional node
else: # X
args, stop_location = self._line.split_words(stop_location, until='=')
self._nodes = args[:-1]
self._parameters.append(args[-1]) # model name
# Read positionals
number_of_positionals = prefix_data.number_of_positionals_min
if number_of_positionals and stop_location is not None: # model is optional
self._parameters, stop_location = self._line.read_words(stop_location, number_of_positionals)
if prefix_data.multi_devices and stop_location is not None:
remaining, stop_location = self._line.split_words(stop_location, until='=')
self._parameters.extend(remaining)
if prefix_data.prefix in ('V', 'I') and stop_location is not None:
# merge remaining
self._parameters[-1] += line_str[stop_location:]
# Read optionals
if prefix_data.has_optionals and stop_location is not None:
kwargs, stop_location = self._line.split_words(stop_location)
for kwarg in kwargs:
try:
key, value = kwarg.split('=')
self._dict_parameters[key.lower()] = value
except ValueError:
if kwarg in ('off',) and prefix_data.has_flag:
self._dict_parameters['off'] = True
else:
self._logger.warn(line_str)
# raise NameError('Bad element line:', line_str)
if prefix_data.multi_devices:
for element_class in prefix_data:
if len(self._parameters) == element_class.number_of_positional_parameters:
break
else:
element_class = prefix_data.single
self.factory = element_class
# Move positionals passed as kwarg
to_delete = []
for parameter in element_class.positional_parameters.values():
if parameter.key_parameter:
i = parameter.position
self._dict_parameters[parameter.attribute_name] = self._parameters[i]
to_delete.append(i)
for i in to_delete:
del self._parameters[i]
# self._logger.debug(os.linesep + self.__repr__())
##############################################
@property
def name(self):
""" Name of the element """
return self._name
##############################################
def __repr__(self):
return 'Element {0._prefix} {0._name} {0._nodes} {0._parameters} {0._dict_parameters}'.format(self)
##############################################
def translate_ground_node(self, ground):
nodes = []
for node in self._nodes:
if str(node) == str(ground):
node = 0
nodes.append(node)
return nodes
##############################################
def to_python(self, netlist_name, ground=0):
nodes = self.translate_ground_node(ground)
args = [self._name]
if self._prefix != 'X':
args += nodes + self._parameters
else: # != Spice
args += self._parameters + nodes
args = self.values_to_python(args)
kwargs = self.kwargs_to_python(self._dict_parameters)
return '{}.{}({})'.format(netlist_name, self._prefix, self.join_args(args + kwargs)) + os.linesep
##############################################
def build(self, circuit, ground=0):
factory = getattr(circuit, self.factory.__alias__)
nodes = self.translate_ground_node(ground)
if self._prefix != 'X':
args = nodes + self._parameters
else: # != Spice
args = self._parameters + nodes
kwargs = self._dict_parameters
if self._logger.isEnabledFor(logging.DEBUG):
message = ' '.join([str(x) for x in (self._prefix, self._name, nodes,
self._parameters, self._dict_parameters)])
self._logger.debug(message)
factory(self._name, *args, **kwargs)
####################################################################################################
class Line:
""" This class implements a line in the netlist. """
_logger = _module_logger.getChild('Element')
##############################################
def __init__(self, line, line_range, end_of_line_comment):
self._end_of_line_comment = end_of_line_comment
text, comment, self._is_comment = self._split_comment(line)
self._text = text
self._comment = comment
self._line_range = line_range
##############################################
def __repr__(self):
return '{0._line_range}: {0._text} // {0._comment}'.format(self)
##############################################
def __str__(self):
return self._text
##############################################
@property
def comment(self):
return self._comment
@property
def is_comment(self):
return self._is_comment
##############################################
def _split_comment(self, line):
line = str(line)
if line.startswith('*'):
is_comment = True
text = ''
comment = line[1:].strip()
else:
is_comment = False
# remove end of line comment
location = -1
for marker in self._end_of_line_comment:
_location = line.find(marker)
if _location != -1:
if location == -1:
location = _location
else:
location = min(_location, location)
if location != -1:
text = line[:location].strip()
comment = line[location:].strip()
else:
text = line
comment = ''
return text, comment, is_comment
##############################################
def append(self, line):
text, comment, is_comment = self._split_comment(line)
if text:
if not self._text.endswith(' ') or text.startswith(' '):
self._text += ' '
self._text += text
if comment:
self._comment += ' // ' + comment
_slice = self._line_range
self._line_range = slice(_slice.start, _slice.stop + 1)
##############################################
def lower_case_statement(self, statement):
"""Lower case the statement"""
# statement without . prefix
if self._text:
lower_statement = statement.lower()
_slice = slice(1, len(statement) + 1)
_statement = self._text[_slice]
if _statement.lower() == lower_statement:
self._text = '.' + lower_statement + self._text[_slice.stop:]
##############################################
def right_of(self, text):
return self._text[len(text):].strip()
##############################################
def read_words(self, start_location, number_of_words):
"""Read a fixed number of words separated by space."""
words = []
stop_location = None
line_str = self._text
number_of_words_read = 0
while number_of_words_read < number_of_words: # and start_location < len(line_str)
stop_location = line_str.find(' ', start_location)
if stop_location == -1:
stop_location = None # read until end
word = line_str[start_location:stop_location].strip()
if word:
number_of_words_read += 1
words.append(word)
if stop_location is None: # we should stop
if number_of_words_read != number_of_words:
template = 'Bad element line, looking for word {}/{}:' + os.linesep
message = (template.format(number_of_words_read, number_of_words) +
line_str + os.linesep +
' '*start_location + '^')
self._logger.warning(message)
raise ParseError(message)
else:
if start_location < stop_location:
start_location = stop_location
else: # we have read a space
start_location += 1
return words, stop_location
##############################################
def split_words(self, start_location, until=None):
stop_location = None
line_str = self._text
if until is not None:
location = line_str.find(until, start_location)
if location != -1:
stop_location = location
location = line_str.rfind(' ', start_location, stop_location)
if location != -1:
stop_location = location
else:
raise NameError('Bad element line, missing key? ' + line_str)
line_str = line_str[start_location:stop_location]
words = [x for x in line_str.split(' ') if x]
return words, stop_location
##############################################
@staticmethod
def get_kwarg(text):
dict_parameters = {}
parts = []
for part in text.split():
if '=' in part and part != '=':
left, right = [x for x in part.split('=')]
parts.append(left)
parts.append('=')
if right:
parts.append(right)
else:
parts.append(part)
i = 0
i_stop = len(parts)
while i < i_stop:
if i + 1 < i_stop and parts[i + 1] == '=':
key, value = parts[i], parts[i + 2]
dict_parameters[key] = value
i += 3
else:
raise ParseError("Bad kwarg: {}".format(text))
return dict_parameters
##############################################
def split_line(self, keyword):
"""Split the line according to the following pattern::
keyword parameter1 parameter2 ... key1=value1 key2=value2 ...
Return the list of parameters and the dictionary.
"""
# Fixme: cf. get_kwarg
parameters = []
dict_parameters = {}
text = self.right_of(keyword)
parts = []
for part in text.split():
if '=' in part and part != '=':
left, right = [x for x in part.split('=')]
parts.append(left)
parts.append('=')
if right:
parts.append(right)
else:
parts.append(part)
i = 0
i_stop = len(parts)
while i < i_stop:
if i + 1 < i_stop and parts[i + 1] == '=':
key, value = parts[i], parts[i + 2]
dict_parameters[key] = value
i += 3
else:
parameters.append(parts[i])
i += 1
return parameters, dict_parameters
####################################################################################################
class SpiceParser:
""" This class parse a Spice netlist file and build a syntax tree.
Public Attributes:
:attr:`circuit`
:attr:`models`
:attr:`subcircuits`
"""
_logger = _module_logger.getChild('SpiceParser')
##############################################
def __init__(self, path=None, source=None, end_of_line_comment=('$', '//', ';')):
# Fixme: empty source
if path is not None:
with open(str(path), 'r') as f:
raw_lines = f.readlines()
elif source is not None:
raw_lines = source.split(os.linesep)
else:
raise ValueError
self._end_of_line_comment = end_of_line_comment
lines = self._merge_lines(raw_lines)
self._title = None
self._statements = self._parse(lines)
self._find_sections()
##############################################
def _merge_lines(self, raw_lines):
"""Merge broken lines and return a new list of lines.
A line starting with "+" continues the preceding line.
"""
lines = []
current_line = None
for line_index, line_string in enumerate(raw_lines):
if line_string.startswith('+'):
current_line.append(line_string[1:].strip('\r\n'))
else:
line_string = line_string.strip('\r\n')
if line_string:
_slice = slice(line_index, line_index +1)
line = Line(line_string, _slice, self._end_of_line_comment)
lines.append(line)
# handle case with comment before line continuation
if not line_string.startswith('*'):
current_line = line
return lines
##############################################
def _parse(self, lines):
""" Parse the lines and return a list of statements. """
# The first line in the input file must be the title, which is the only comment line that does
# not need any special character in the first place.
#
# The last line must be .end
if len(lines) <= 1:
raise NameError('Netlist is empty')
# if lines[-1] != '.end':
# raise NameError('".end" is expected at the end of the netlist')
title_statement = '.title '
self._title = str(lines[0])
if self._title.startswith(title_statement):
self._title = self._title[len(title_statement):]
statements = []
sub_circuit = None
scope = statements
for line in lines[1:]:
# print('>', repr(line))
text = str(line)
lower_case_text = text.lower() # !
if line.is_comment:
scope.append(Comment(line))
elif lower_case_text.startswith('.'):
lower_case_text = lower_case_text[1:]
if lower_case_text.startswith('subckt'):
sub_circuit = SubCircuitStatement(line)
statements.append(sub_circuit)
scope = sub_circuit
elif lower_case_text.startswith('ends'):
sub_circuit = None
scope = statements
elif lower_case_text.startswith('title'):
# override fist line
self._title = Title(line)
scope.append(self._title)
elif lower_case_text.startswith('end'):
pass
elif lower_case_text.startswith('model'):
model = Model(line)
scope.append(model)
elif lower_case_text.startswith('include'):
scope.append(Include(line))
else:
# options param ...
# .global
# .lib filename libname
# .param
# .func .csparam .temp .if
# { expr } are allowed in .model lines and in device lines.
self._logger.warn('Parser ignored: {}'.format(line))
else:
try:
element = Element(line)
scope.append(element)
except ParseError:
self._logger.warn('Parse error on:\n{}'.format(line))
return statements
##############################################
def _find_sections(self):
""" Look for model, sub-circuit and circuit definitions in the statement list. """
self.circuit = None
self.subcircuits = []
self.models = []
for statement in self._statements:
if isinstance(statement, Title):
if self.circuit is None:
self.circuit = statement
else:
raise NameError('More than one title')
elif isinstance(statement, SubCircuitStatement):
self.subcircuits.append(statement)
elif isinstance(statement, Model):
self.models.append(statement)
##############################################
def is_only_subcircuit(self):
return bool(not self.circuit and self.subcircuits)
##############################################
def is_only_model(self):
return bool(not self.circuit and not self.subcircuits and self.models)
##############################################
@staticmethod
def _build_circuit(circuit, statements, ground):
for statement in statements:
if isinstance(statement, Include):
circuit.include(str(statement))
for statement in statements:
if isinstance(statement, Element):
statement.build(circuit, ground)
elif isinstance(statement, Model):
statement.build(circuit)
elif isinstance(statement, SubCircuit):
subcircuit = statement.build(ground) # Fixme: ok ???
circuit.subcircuit(subcircuit)
##############################################
def build_circuit(self, ground=0):
"""Build a :class:`Circuit` instance.
Use the *ground* parameter to specify the node which must be translated to 0 (SPICE ground node).
"""
circuit = Circuit(str(self._title))
self._build_circuit(circuit, self._statements, ground)
return circuit
##############################################
@staticmethod
def netlist_to_python(netlist_name, statements, ground=0):
source_code = ''
for statement in statements:
if isinstance(statement, Element):
source_code += statement.to_python(netlist_name, ground)
elif isinstance(statement, Include):
pass
elif isinstance(statement, Model):
source_code += statement.to_python(netlist_name)
elif isinstance(statement, SubCircuitStatement):
source_code += statement.to_python(netlist_name)
elif isinstance(statement, Include):
source_code += statement.to_python(netlist_name)
return source_code
##############################################
def to_python_code(self, ground=0):
ground = str(ground)
source_code = ''
if self.circuit:
source_code += "circuit = Circuit('{}')".format(self._title) + os.linesep
source_code += self.netlist_to_python('circuit', self._statements, ground)
return source_code
PySpice-pr-191/PySpice/Spice/RawFile.py 0000664 0000000 0000000 00000032134 13654121544 0017713 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import os
####################################################################################################
"""This module provide tools to read raw output.
"""
####################################################################################################
from PySpice.Unit import u_Degree, u_V, u_A, u_s, u_Hz
####################################################################################################
import logging
import numpy as np
####################################################################################################
from PySpice.Probe.WaveForm import (OperatingPoint, SensitivityAnalysis,
DcAnalysis, AcAnalysis, TransientAnalysis,
WaveForm)
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class VariableAbc:
"""This class implements a variable or probe in a SPICE simulation output.
Public Attributes:
:attr:`index`
index in the array
:attr:`name`
:attr:`unit`
"""
##############################################
def __init__(self, index, name, unit):
# Fixme: self._ ?
self._index = int(index)
self.name = str(name)
self._unit = unit # could be guessed from name also for voltage node and branch current
self.data = None
##############################################
@property
def index(self):
return self._index
# @property
# def name(self):
# return self._name
# @name.setter
# def name(self, value):
# self._name = value
##############################################
def __repr__(self):
return 'variable[{0._index}]: {0.name} [{0._unit}]'.format(self)
##############################################
def is_voltage_node(self):
raise NotImplementedError
##############################################
def is_branch_current(self):
raise NotImplementedError
##############################################
@property
def is_interval_parameter(self):
return self.name.startswith('@') # Fixme: Xyce ???
##############################################
@staticmethod
def to_voltage_name(node):
return 'v({})'.format(node)
##############################################
@staticmethod
def to_branch_name(element):
return 'i({})'.format(element)
##############################################
def fix_case(self, element_translation, node_translation):
""" Update the name to the right case. """
if self.is_branch_current():
if self.simplified_name in element_translation:
self.name = self.to_branch_name(element_translation[self.simplified_name])
elif self.is_voltage_node():
if self.simplified_name in node_translation:
self.name = self.to_voltage_name(node_translation[self.simplified_name])
##############################################
@property
def simplified_name(self):
raise NotImplementedError
##############################################
def to_waveform(self, abscissa=None, to_real=False, to_float=False):
""" Return a :obj:`PySpice.Probe.WaveForm` instance. """
data = self.data
if to_real:
data = data.real
# Fixme: else UnitValue instead of UnitValues
# if to_float:
# data = float(data[0])
if self._unit is not None:
return WaveForm.from_unit_values(self.simplified_name, self._unit(data), abscissa=abscissa)
else:
return WaveForm.from_array(self.simplified_name, data, abscissa=abscissa)
####################################################################################################
class RawFileAbc:
""" This class parse the stdout of ngspice and the raw data output.
"""
_logger = _module_logger.getChild('RawFileAbc')
##############################################
@property
def simulation(self):
if self._simulation is not None:
return self._simulation
else:
raise NameError('Simulation is undefined')
@simulation.setter
def simulation(self, value):
self._simulation = value
##############################################
@property
def circuit(self):
return self._simulation.circuit
##############################################
__name_to_unit__ = {
'time': u_s,
'voltage': u_V,
'current': u_A,
'frequency': u_Hz,
}
##############################################
def _read_line(self, header_line_iterator):
""" Return the next line """
# Fixme: self._header_line_iterator, etc.
line = None
while not line:
line = next(header_line_iterator)
return line.decode('utf-8')
##############################################
def _read_header_line(self, header_line_iterator, head_line):
""" Read an header line and check it starts with *head_line*. """
line = self._read_line(header_line_iterator)
self._logger.debug(line)
if line.startswith(head_line):
return line
else:
raise NameError("Unexpected line: %s" % (line))
##############################################
def _read_header_field_line(self, header_line_iterator, expected_label, has_value=True):
""" Read an header line and check it starts with *expected_label*.
Return the values next to the label if the flag *has_value* is set.
"""
line = self._read_line(header_line_iterator)
self._logger.debug(line)
if has_value:
# a title can have ': ' after 'title: '
location = line.find(': ') # first occurence
label, value = line[:location], line[location+2:]
else:
label = line[:-1]
if label != expected_label:
raise NameError("Expected label %s instead of %s" % (expected_label, label))
if has_value:
return value.strip()
##############################################
def _read_temperature_line(self, header_line_iterator):
# Doing analysis at TEMP = 25.000000 and TNOM = 25.000000
line = self._read_header_line(header_line_iterator, 'Doing analysis at TEMP')
pattern1 = 'TEMP = '
pattern2 = ' and TNOM = '
pos1 = line.find(pattern1)
pos2 = line.find(pattern2)
if pos1 != -1 and pos2 != -1:
part1 = line[pos1+len(pattern1):pos2]
part2 = line[pos2+len(pattern2):].strip()
temperature = u_Degree(float(part1))
nominal_temperature = u_Degree(float(part2))
else:
temperature = None
nominal_temperature = None
return temperature, nominal_temperature
##############################################
def _read_header_variables(self, header_line_iterator):
self.variables = {}
for i in range(self.number_of_variables):
line = (next(header_line_iterator)).decode('utf-8')
self._logger.debug(line)
items = [x.strip() for x in line.split('\t') if x]
# 0 frequency frequency grid=3
index, name, unit = items[:3]
# unit = time, voltage, current
unit = self.__name_to_unit__[unit] # convert to Unit
self.variables[name] = self.__variable_cls__(index, name, unit)
# self._read_header_field_line(header_line_iterator, 'Binary', has_value=False)
##############################################
def _read_variable_data(self, raw_data):
""" Read the raw data and set the variable values. """
if self.flags == 'real':
number_of_columns = self.number_of_variables
elif self.flags == 'complex':
number_of_columns = 2*self.number_of_variables
else:
raise NotImplementedError
input_data = np.fromstring(raw_data, count=number_of_columns*self.number_of_points, dtype='f8')
input_data = input_data.reshape((self.number_of_points, number_of_columns))
input_data = input_data.transpose()
# np.savetxt('raw.txt', input_data)
if self.flags == 'complex':
raw_data = input_data
input_data = np.array(raw_data[0::2], dtype='complex64')
input_data.imag = raw_data[1::2]
for variable in self.variables.values():
variable.data = input_data[variable.index]
##############################################
def nodes(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.variables.values()
if variable.is_voltage_node()]
##############################################
def branches(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.variables.values()
if variable.is_branch_current()]
##############################################
def internal_parameters(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.variables.values()
if variable.is_interval_parameter]
##############################################
def elements(self, abscissa=None):
return [variable.to_waveform(abscissa, to_float=True)
for variable in self.variables.values()]
##############################################
def to_analysis(self):
self.fix_case()
if self.plot_name == 'Operating Point':
return self._to_operating_point_analysis()
elif self.plot_name == 'Sensitivity Analysis':
return self._to_sensitivity_analysis()
elif self.plot_name == 'DC transfer characteristic':
return self._to_dc_analysis()
elif self.plot_name == 'AC Analysis':
return self._to_ac_analysis()
elif self.plot_name == 'Transient Analysis':
return self._to_transient_analysis()
else:
raise NotImplementedError("Unsupported plot name {}".format(self.plot_name))
##############################################
def _to_operating_point_analysis(self):
return OperatingPoint(
simulation=self.simulation,
nodes=self.nodes(to_float=True),
branches=self.branches(to_float=True),
)
##############################################
def _to_sensitivity_analysis(self):
# Fixme: test .SENS I (VTEST)
# Fixme: separate v(vinput), analysis.R2.m
return SensitivityAnalysis(
simulation=self.simulation,
elements=self.elements(),
)
##############################################
def _to_dc_analysis(self, sweep_variable):
sweep = sweep_variable.to_waveform()
return DcAnalysis(
simulation=self.simulation,
sweep=sweep,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_ac_analysis(self):
frequency = self.variables['frequency'].to_waveform(to_real=True)
return AcAnalysis(
simulation=self.simulation,
frequency=frequency,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_transient_analysis(self):
time = self.variables['time'].to_waveform(to_real=True)
return TransientAnalysis(
simulation=self.simulation,
time=time,
nodes=self.nodes(abscissa=time),
branches=self.branches(abscissa=time),
internal_parameters=self.internal_parameters(),
)
PySpice-pr-191/PySpice/Spice/Simulation.py 0000664 0000000 0000000 00000100020 13654121544 0020474 0 ustar 00root root 0000000 0000000 ###################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This modules implements classes to perform simulations.
"""
####################################################################################################
import logging
import os
####################################################################################################
from ..Config import ConfigInstall
from ..Tools.StringTools import join_list, join_dict, str_spice
from ..Unit import Unit, as_V, as_A, as_s, as_Hz, as_Degree, u_Degree
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class AnalysisParameters:
"""Base class for analysis parameters"""
__analysis_name__ = None
##############################################
@property
def analysis_name(self):
return self.__analysis_name__
##############################################
def to_list(self):
return ()
##############################################
def __str__(self):
return '.{0.analysis_name} {1}'.format(self, join_list(self.to_list()))
####################################################################################################
class OperatingPointAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for operating point analysis."""
__analysis_name__ = 'op'
####################################################################################################
class DcSensitivityAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for DC sensitivity analysis."""
__analysis_name__ = 'sens'
##############################################
def __init__(self, output_variable):
self._output_variable = output_variable
##############################################
@property
def output_variable(self):
return self._output_variable
##############################################
def to_list(self):
return (
self._output_variable,
)
####################################################################################################
class AcSensitivityAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for AC sensitivity analysis."""
__analysis_name__ = 'sens'
##############################################
def __init__(self, output_variable,
variation, number_of_points, start_frequency, stop_frequency):
if variation not in ('dec', 'oct', 'lin'):
raise ValueError("Incorrect variation type")
self._output_variable = output_variable
self._variation = variation
self._number_of_points = number_of_points
self._start_frequency = as_Hz(start_frequency)
self._stop_frequency = as_Hz(stop_frequency)
##############################################
@property
def output_variable(self):
return self._output_variable
@property
def variation(self):
return self._variation
@property
def number_of_points(self):
return self._number_of_points
@property
def start_frequency(self):
return self._start_frequency
@property
def stop_frequencyr(self):
return self._stop_frequency
##############################################
def to_list(self):
return (
self._output_variable,
self._variation,
self._number_of_points,
self._start_frequency,
self._stop_frequency
)
####################################################################################################
class DCAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for DC analysis."""
__analysis_name__ = 'dc'
##############################################
def __init__(self, **kwargs):
self._parameters = []
for variable, value_slice in kwargs.items():
variable_lower = variable.lower()
if variable_lower[0] in ('v', 'i', 'r') or variable_lower == 'temp':
self._parameters += [variable, value_slice.start, value_slice.stop, value_slice.step]
else:
raise NameError('Sweep variable must be a voltage/current source, '
'a resistor or the circuit temperature')
##############################################
@property
def parameters(self):
return self._parameters
##############################################
def to_list(self):
return self._parameters
####################################################################################################
class ACAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for AC analysis."""
__analysis_name__ = 'ac'
##############################################
def __init__(self, variation, number_of_points, start_frequency, stop_frequency):
# Fixme: use mixin
if variation not in ('dec', 'oct', 'lin'):
raise ValueError("Incorrect variation type")
self._variation = variation
self._number_of_points = number_of_points
self._start_frequency = as_Hz(start_frequency)
self._stop_frequency = as_Hz(stop_frequency)
##############################################
@property
def variation(self):
return self._variation
@property
def number_of_points(self):
return self._number_of_points
@property
def start_frequency(self):
return self._start_frequency
@property
def stop_frequencyr(self):
return self._stop_frequency
##############################################
def to_list(self):
return (
self._variation,
self._number_of_points,
self._start_frequency,
self._stop_frequency
)
####################################################################################################
class TransientAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for transient analysis."""
__analysis_name__ = 'tran'
##############################################
def __init__(self, step_time, end_time, start_time=0, max_time=None,
use_initial_condition=False):
if use_initial_condition:
uic = 'uic'
else:
uic = None
self._step_time = as_s(step_time)
self._end_time = as_s(end_time)
self._start_time = as_s(start_time)
self._max_time = as_s(max_time, none=True)
self._use_initial_condition = uic
##############################################
@property
def step_time(self):
return self._step_time
@property
def end_time(self):
return self._end_time
@property
def start_time(self):
return self._start_time
@property
def max_time(self):
return self._max_time
@property
def use_initial_condition(self):
return self._use_initial_condition
##############################################
def to_list(self):
return (
self._step_time,
self._end_time,
self._start_time,
self._max_time,
self._use_initial_condition,
)
####################################################################################################
class PoleZeroAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for pole-zero analysis."""
__analysis_name__ = 'pz'
##############################################
def __init__(self, node1, node2, node3, node4, tftype, pztype):
self._nodes = [node1,node2,node3,node4]
self._tftype = tftype
self._pztype = pztype
##############################################
@property
def node1(self):
return self._nodes[0]
@property
def node2(self):
return self._nodes[1]
def node3(self):
return self._nodes[2]
@property
def node4(self):
return self._nodes[3]
@property
def tftype(self):
return self._tftype
@property
def pztype(self):
return self._pztype
##############################################
def to_list(self):
return (
self._nodes[0],
self._nodes[1],
self._nodes[2],
self._nodes[3],
self._tftype,
self._pztype,
)
class NoiseAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for noise analysis."""
__analysis_name__ = 'noise'
##############################################
def __init__(self, output, src, variation, pts, fstart, fstop, pts_per_summary):
self._output = output
self._src = src
self._variation = variation
self._pts = pts
self._fstart = fstart
self._fstop = fstop
self._pts_per_summary = pts_per_summary
##############################################
@property
def output(self):
return self._output
@property
def src(self):
return self._src
@property
def variation(self):
return self._variation
@property
def pts(self):
return self._pts
@property
def fstart(self):
return self._fstart
@property
def fstop(self):
return self._fstop
@property
def pts_per_summary(self):
return self._pts_per_summary
##############################################
def to_list(self):
if self._pts_per_summary:
return(
self._output,
self._src,
self._variation,
self._pts,
self._fstart,
self._fstop,
self._pts_per_summary,
)
else:
return(
self._output,
self._src,
self._variation,
self._pts,
self._fstart,
self._fstop,
)
class DistortionAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for distortion analysis."""
__analysis_name__ = 'disto'
##############################################
def __init__(self, variation, pts, fstart, fstop, f2overf1):
self._variation = variation
self._pts = pts
self._fstart = fstart
self._fstop = fstop
self._f2overf1 = f2overf1
##############################################
@property
def variation(self):
return self._variation
@property
def pts(self):
return self._pts
@property
def fstart(self):
return self._fstart
@property
def fstop(self):
return self._fstop
@property
def f2overf1(self):
return self._f2overf1
##############################################
def to_list(self):
if self._f2overf1:
return(
self._variation,
self._pts,
self._fstart,
self._fstop,
self._f2overf1,
)
else:
return(
self._variation,
self._pts,
self._fstart,
self._fstop,
)
class TransferFunctionAnalysisParameters(AnalysisParameters):
"""This class defines analysis parameters for transfer function (.tf) analysis."""
__analysis_name__ = 'tf'
##############################################
def __init__(self, outvar, insrc):
self._outvar = outvar
self._insrc = insrc
##############################################
@property
def outvar(self):
return self._outvar
@property
def insrc(self):
return self._insrc
##############################################
def to_list(self):
return(
self._outvar,
self._insrc,
)
####################################################################################################
class CircuitSimulation:
"""Define and generate the spice instruction to perform a circuit simulation.
.. warning:: In some cases NgSpice can perform several analyses one after the other. This case
is partially supported.
"""
_logger = _module_logger.getChild('CircuitSimulation')
##############################################
def __init__(self, circuit, **kwargs):
self._circuit = circuit
self._options = {} # .options
self._initial_condition = {} # .ic
self._saved_nodes = set()
self._analyses = {}
self.temperature = kwargs.get('temperature', u_Degree(27))
self.nominal_temperature = kwargs.get('nominal_temperature', u_Degree(27))
##############################################
@property
def circuit(self):
return self._circuit
##############################################
def options(self, *args, **kwargs):
for item in args:
self._options[str(item)] = None
for key, value in kwargs.items():
self._options[str(key)] = str_spice(value)
##############################################
@property
def temperature(self):
return self._options['TEMP']
@temperature.setter
def temperature(self, value):
self._options['TEMP'] = as_Degree(value)
##############################################
@property
def nominal_temperature(self):
return self._options['TNOM']
@nominal_temperature.setter
def nominal_temperature(self, value):
self._options['TNOM'] = as_Degree(value)
##############################################
def initial_condition(self, **kwargs):
""" Set initial condition for voltage nodes.
Usage::
simulator.initial_condition(node_name1=value, ...)
"""
for key, value in kwargs.items():
self._initial_condition['V({})'.format(str(key))] = str_spice(value)
# Fixme: .nodeset
##############################################
def save(self, *args):
# Fixme: pass Node for voltage node, Element for source branch current, ...
"""Set the list of saved vectors.
If no *.save* line is given, then the default set of vectors is saved (node voltages and
voltage source branch currents). If *.save* lines are given, only those vectors specified
are saved.
Node voltages may be saved by giving the node_name or *v(node_name)*. Currents through an
independent voltage source (including inductor) are given by *i(source_name)* or
*source_name#branch*. Internal device data are accepted as *@dev[param]*.
If you want to save internal data in addition to the default vector set, add the parameter
*all* to the additional vectors to be saved.
"""
self._saved_nodes |= set(*args)
##############################################
def save_internal_parameters(self, *args):
"""This method is similar to`save` but assume *all*.
"""
# Fixme: ok ???
self.save(list(args) + ['all'])
##############################################
@property
def save_currents(self):
""" Save all currents. """
return self._options.get('SAVECURRENTS', False)
@save_currents.setter
def save_currents(self, value):
if value:
self._options['SAVECURRENTS'] = True
else:
del self._options['SAVECURRENTS']
##############################################
def reset_analysis(self):
self._analyses.clear()
##############################################
def analysis_iter(self):
return self._analyses.values()
##############################################
def _add_analysis(self, analysis_parameters):
self._analyses[analysis_parameters.analysis_name] = analysis_parameters
##############################################
def operating_point(self):
"""Compute the operating point of the circuit with capacitors open and inductors shorted."""
self._add_analysis(OperatingPointAnalysisParameters())
##############################################
def dc_sensitivity(self, output_variable):
"""Compute the sensitivity of the DC operating point of a node voltage or voltage-source branch
current to all non-zero device parameters.
Examples of usage::
analysis = simulator.dc_sensitivity('v(out)')
Spice syntax:
.. code:: spice
.sens outvar
Examples:
.. code:: spice
.sens V(1, OUT)
.sens I(VTEST)
"""
self._add_analysis(DcSensitivityAnalysisParameters(output_variable))
##############################################
def ac_sensitivity(self, output_variable,
variation, number_of_points, start_frequency, stop_frequency):
"""Compute the sensitivity of the AC values of a node voltage or voltage-source branch
current to all non-zero device parameters.
Examples of usage::
analysis = simulator.ac_sensitivity(...)
Spice syntax:
.. code::
.sens outvar ac dec nd fstart fstop
.sens outvar ac oct no fstart fstop
.sens outvar ac lin np fstart fstop
Spice examples:
.. code::
.sens V(OUT) AC DEC 10 100 100 k
"""
self._add_analysis(
AcSensitivityAnalysisParameters(
output_variable,
variation, number_of_points, start_frequency, stop_frequency
))
##############################################
def dc(self, **kwargs):
"""Compute the DC transfer fonction of the circuit with capacitors open and inductors shorted.
Examples of usage::
analysis = simulator.dc(Vinput=slice(-2, 5, .01))
analysis = simulator.dc(Ibase=slice(0, 100e-6, 10e-6))
analysis = simulator.dc(Vcollector=slice(0, 5, .1), Ibase=slice(micro(10), micro(100), micro(10))) # broken ???
Spice syntax:
.. code:: spice
.dc src_name vstart vstop vincr [ src2 start2 stop2 incr2 ]
*src_name* is the name of an independent voltage or a current source, a resistor or the
circuit temperature.
*vstart*, *vstop*, and *vincr* are the starting, final, and incrementing values respectively.
A second source (*src2*) may optionally be specified with associated sweep parameters. In
this case, the first source is swept over its range for each value of the second source.
Spice examples:
.. code:: spice
.dc VIN 0 .2 5 5.0 0.25
.dc VDS 0 10 .5 VGS 0 5 1
.dc VCE 0 10 .2 5 IB 0 10U 1U
.dc RLoad 1k 2k 100
.dc TEMP -15 75 5
"""
self._add_analysis(DCAnalysisParameters(**kwargs))
##############################################
def ac(self, variation, number_of_points, start_frequency, stop_frequency):
# fixme: concise keyword ?
"""Perform a small-signal AC analysis of the circuit where all non-linear devices are linearized
around their actual DC operating point.
Examples of usage::
analysis = simulator.ac(start_frequency=10@u_kHz, stop_frequency=1@u_GHz, number_of_points=10, variation='dec')
Note that in order for this analysis to be meaningful, at least one independent source must
have been specified with an AC value. Typically it does not make much sense to specify more
than one AC source. If you do, the result will be a superposition of all sources, thus
difficult to interpret.
Spice examples:
.. code::
.ac dec nd fstart fstop
.ac oct no fstart fstop
.ac lin np fstart fstop
The parameter *variation* must be either `dec`, `oct` or `lin`.
"""
self._add_analysis(
ACAnalysisParameters(
variation, number_of_points, start_frequency, stop_frequency
))
##############################################
def transient(self, step_time, end_time, start_time=0, max_time=None,
use_initial_condition=False):
"""Perform a transient analysis of the circuit.
Examples of usage::
analysis = simulator.transient(step_time=1@u_us, end_time=500@u_us)
analysis = simulator.transient(step_time=source.period/200, end_time=source.period*2)
Spice syntax:
.. code:: spice
.tran tstep tstop >
"""
self._add_analysis(
TransientAnalysisParameters(
step_time, end_time, start_time, max_time,
use_initial_condition
))
##############################################
def polezero(self, node1, node2, node3, node4, tftype, pztype):
"""Perform a Pole-Zero analysis of the circuit.
node1, node2 - Input node pair.
node3, node4 - Output node pair
tftype - should be "cur" for current or "vol" for voltage
pztype - should be "pol" for pole, "zer" for zero, or "pz" for combined pole zero analysis.
See section 15.3.6 of ngspice manual.
Spice syntax:
.. code:: spice
.tran tstep tstop >
.pz node1 node2 node3 node4 cur pol
.pz node1 node2 node3 node4 cur zer
.pz node1 node2 node3 node4 cur pz
.pz node1 node2 node3 node4 vol pol
.pz node1 node2 NODE3 node4 vol zer
.pz node1 node2 node3 node4 vol pz
Examples:
.pz 1 0 3 0 cur pol
.pz 2 3 5 0 vol zer
.pz 4 1 4 1 cur pz
"""
# do some rudimentary parameter checking.
if not tftype in [ 'cur', 'vol']:
raise NameError("polezero type must be 'cur' or 'vol'")
if not pztype in ['pol', 'zer', 'pz']:
raise NameError("pztype must be 'pol' or 'zer' or 'pz'")
self._add_analysis(
PoleZeroAnalysisParameters(
node1, node2, node3, node4, tftype, pztype
))
##############################################
def noise(self, output_node, ref_node, src, variation, pts, fstart, fstop, pts_per_summary=None):
"""Perform a Pole-Zero analysis of the circuit.
output_node, ref_node - output node pair.
src - signal source, typically an ac voltage input.
variation - must be 'dec' or 'lin' or 'oct' for decade, linear, or octave.
pts, fstart, fstop - number of points, start and stop frequencies.
pts_per_summary - if specified, the noise contributions of each noise generator is produced every pts_per_summary frequency points.
See section 15.3.4 of ngspice manual.
Spice syntax:
General form:
.noise v(output <,ref >) src ( dec | lin | oct ) pts fstart fstop
Examples:
.noise v(5) VIN dec 10 1kHz 100 MEG
.noise v(5 ,3) V1 oct 8 1.0 1.0 e6 1
"""
# do some rudimentary parameter checking.
if not variation in [ 'dec', 'lin', 'oct' ]:
raise NameError("variation must be 'dec' or 'lin' or 'oct'")
self._add_analysis(
NoiseAnalysisParameters(
'V(' + str(output_node) + ',' + str(ref_node) + ')', src, variation, pts, fstart, fstop, pts_per_summary
))
##############################################
def transfer_function(self, outvar, insrc):
"""
The python arguments to this function should be two strings, outvar and insrc.
ngspice documentation as follows:
General form:
.tf outvar insrc
Examples:
.tf v(5, 3) VIN
.tf i(VLOAD) VIN
The .tf line defines the small-signal output and input for the dc small-signal analysis. outvar
is the small signal output variable and insrc is the small-signal input source. If this line is
included, ngspice computes the dc small-signal value of the transfer function (output/input),
input resistance, and output resistance. For the first example, ngspice would compute the ratio
of V(5, 3) to VIN, the small-signal input resistance at VIN, and the small signal output resistance
measured across nodes 5 and 3
"""
self._add_analysis(
TransferFunctionParameters(
outvar, insrc
))
##############################################
def distortion(self, variation, pts, fstart, fstop, f2overf1=None):
"""Perform a distortion analysis of the circuit.
variation, pts, fstart, fstop - typical ac range parameters.
if f2overf1 is specified, perform a spectral analysis, else perform a harmonic analysis.
See section 15.3.3 of ngspice manual.
- harmonic analysis,
The distof1 parameter of the AC input to the circuit must be specified.
Second harmonic magnitude and phase are calculated at each circuit node.
- Spectral analysis,
The distof2 parameter of the AC input to the circuit must be specified as well as distof1.
See the ngspice manual.
Spice syntax:
General form:
.disto dec nd fstart fstop
.disto oct no fstart fstop
.disto lin np fstart fstop
Examples:
.disto dec 10 1kHz 100 MEG
.disto dec 10 1kHz 100 MEG 0.9
"""
# do some rudimentary parameter checking.
if not variation in [ 'dec', 'lin', 'oct' ]:
raise NameError("variation must be 'dec' or 'lin' or 'oct'")
self._add_analysis(
DistortionAnalysisParameters(
variation, pts, fstart, fstop, f2overf1
))
##############################################
def str_options(self, unit=True):
# Fixme: use cls settings ???
if unit:
_str = str_spice
else:
_str = lambda x: str_spice(x, unit)
netlist = ''
if self.options:
for key, value in self._options.items():
if value is not None:
netlist += '.options {} = {}'.format(key, _str(value)) + os.linesep
else:
netlist += '.options {}'.format(key) + os.linesep
return netlist
##############################################
def __str__(self):
netlist = self._circuit.str(simulator=self.SIMULATOR)
netlist += self.str_options()
if self.initial_condition:
netlist += '.ic ' + join_dict(self._initial_condition) + os.linesep
if self._saved_nodes:
# Place 'all' first
saved_nodes = self._saved_nodes
if 'all' in saved_nodes:
all_str = 'all '
saved_nodes.remove('all')
else:
all_str = ''
netlist += '.save ' + all_str + join_list(saved_nodes) + os.linesep
for analysis_parameters in self._analyses.values():
netlist += str(analysis_parameters) + os.linesep
netlist += '.end' + os.linesep
return netlist
####################################################################################################
class CircuitSimulator(CircuitSimulation):
""" This class implements a circuit simulator. Each analysis mode is performed by a method that
return the measured probes.
For *ac* and *transient* analyses, the user must specify a list of nodes using the *probes* key
argument.
"""
_logger = _module_logger.getChild('CircuitSimulator')
if ConfigInstall.OS.on_windows:
DEFAULT_SIMULATOR = 'ngspice-shared'
else:
# DEFAULT_SIMULATOR = 'ngspice-subprocess'
DEFAULT_SIMULATOR = 'ngspice-shared'
# DEFAULT_SIMULATOR = 'xyce-serial'
# DEFAULT_SIMULATOR = 'xyce-parallel'
##############################################
@classmethod
def factory(cls, circuit, *args, **kwargs):
"""Return a :obj:`PySpice.Spice.Simulation.SubprocessCircuitSimulator` or
:obj:`PySpice.Spice.Simulation.NgSpiceSharedCircuitSimulator` instance depending of the
value of the *simulator* parameter: ``subprocess`` or ``shared``, respectively. If this
parameter is not specified then a subprocess simulator is returned.
"""
if 'simulator' in kwargs:
simulator = kwargs['simulator']
del kwargs['simulator']
else:
simulator = cls.DEFAULT_SIMULATOR
sub_cls = None
if simulator in ('ngspice-subprocess', 'ngspice-shared'):
if simulator == 'ngspice-subprocess':
from .NgSpice.Simulation import NgSpiceSubprocessCircuitSimulator
sub_cls = NgSpiceSubprocessCircuitSimulator
elif simulator == 'ngspice-shared':
from .NgSpice.Simulation import NgSpiceSharedCircuitSimulator
sub_cls = NgSpiceSharedCircuitSimulator
elif simulator in ('xyce-serial', 'xyce-parallel'):
from .Xyce.Simulation import XyceCircuitSimulator
sub_cls = XyceCircuitSimulator
if simulator == 'xyce-parallel':
kwargs['parallel'] = True
if sub_cls is not None:
return sub_cls(circuit, *args, **kwargs)
else:
raise ValueError('Unknown simulator type')
##############################################
def _run(self, analysis_method, *args, **kwargs):
self.reset_analysis()
if 'probes' in kwargs:
self.save(* kwargs.pop('probes'))
method = getattr(CircuitSimulation, analysis_method)
method(self, *args, **kwargs)
self._logger.debug('desk' + os.linesep + str(self))
##############################################
def operating_point(self, *args, **kwargs):
return self._run('operating_point', *args, **kwargs)
##############################################
def dc(self, *args, **kwargs):
return self._run('dc', *args, **kwargs)
##############################################
def dc_sensitivity(self, *args, **kwargs):
return self._run('dc_sensitivity', *args, **kwargs)
##############################################
def ac(self, *args, **kwargs):
return self._run('ac', *args, **kwargs)
##############################################
def transient(self, *args, **kwargs):
return self._run('transient', *args, **kwargs)
##############################################
def polezero(self, *args, **kwargs):
return self._run('polezero', *args, **kwargs)
##############################################
def noise(self, *args, **kwargs):
return self._run('noise', *args, **kwargs)
##############################################
def distortion(self, *args, **kwargs):
return self._run('distortion', *args, **kwargs)
##############################################
def tf(self, *args, **kwargs):
return self._run('transfer_function', *args, **kwargs)
##############################################
PySpice-pr-191/PySpice/Spice/Xyce/ 0000775 0000000 0000000 00000000000 13654121544 0016715 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/Xyce/RawFile.py 0000664 0000000 0000000 00000013065 13654121544 0020625 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import os
from ..RawFile import VariableAbc, RawFileAbc
####################################################################################################
"""This module provide tools to read the output of Xyce.
Header
"""
####################################################################################################
import logging
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class Variable(VariableAbc):
##############################################
def is_voltage_node(self):
name = self.name.lower()
return name.startswith('v(') or not self.is_branch_current()
##############################################
def is_branch_current(self):
return self.name.endswith('#branch')
##############################################
@staticmethod
def to_voltage_name(node):
return 'v({})'.format(node)
##############################################
@property
def simplified_name(self):
name = self.name
if len(name) > 1 and name[1] == '(':
return name[2:-1]
elif name.endswith('#branch'):
return name[:-7]
elif '#' in name:
# Xyce change name of type "output_plus" to "OUTPUT#PLUS"
return name.replace('#', '_')
else:
return self.name
####################################################################################################
class RawFile(RawFileAbc):
""" This class parse the stdout of ngspice and the raw data output.
Public Attributes:
:attr:`data`
:attr:`date`
:attr:`flags`
'real' or 'complex'
:attr:`number_of_points`
:attr:`number_of_variables`
:attr:`plot_name`
AC Analysis, Operating Point, Sensitivity Analysis, DC transfer characteristic
:attr:`title`
:attr:`variables`
"""
_logger = _module_logger.getChild('RawFile')
__variable_cls__ = Variable
##############################################
def __init__(self, output):
raw_data = self._read_header(output)
self._read_variable_data(raw_data)
# self._to_analysis()
self._simulation = None
##############################################
def _read_header(self, output):
""" Parse the header """
# see https://github.com/FabriceSalvaire/PySpice/issues/132
# Xyce open the file in binary mode and print using: os << "Binary:" << std::endl;
# endl is thus \n
binary_line = b'Binary:\n'
binary_location = output.find(binary_line)
if binary_location < 0:
raise NameError('Cannot locate binary data')
raw_data_start = binary_location + len(binary_line)
self._logger.debug(os.linesep + output[:raw_data_start].decode('utf-8'))
header_lines = output[:binary_location].splitlines()
raw_data = output[raw_data_start:]
header_line_iterator = iter(header_lines)
self.title = self._read_header_field_line(header_line_iterator, 'Title')
self.date = self._read_header_field_line(header_line_iterator, 'Date')
self.plot_name = self._read_header_field_line(header_line_iterator, 'Plotname')
self.flags = self._read_header_field_line(header_line_iterator, 'Flags')
self.number_of_variables = int(self._read_header_field_line(header_line_iterator, 'No. Variables'))
self.number_of_points = int(self._read_header_field_line(header_line_iterator, 'No. Points'))
self._read_header_field_line(header_line_iterator, 'Variables')
self._read_header_variables(header_line_iterator)
return raw_data
##############################################
def fix_case(self):
""" Ngspice return lower case names. This method fixes the case of the variable names. """
circuit = self.circuit
element_translation = {element.upper():element for element in circuit.element_names}
node_translation = {node.upper():node for node in circuit.node_names}
for variable in self.variables.values():
variable.fix_case(element_translation, node_translation)
##############################################
def _to_dc_analysis(self):
if 'sweep' in self.variables:
sweep_variable = self.variables['sweep']
else:
raise NotImplementedError
return super()._to_dc_analysis(sweep_variable)
PySpice-pr-191/PySpice/Spice/Xyce/Server.py 0000664 0000000 0000000 00000011022 13654121544 0020531 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This module provides an interface to run xyce and get back the simulation
output.
"""
####################################################################################################
import logging
import os
import shutil
import subprocess
import tempfile
from PySpice.Config import ConfigInstall
from .RawFile import RawFile
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class XyceServer:
"""This class wraps the execution of Xyce and convert the output to a Python data structure.
Example of usage::
spice_server = XyceServer(xyce_command='/path/to/Xyce')
raw_file = spice_server(spice_input)
It returns a :obj:`PySpice.Spice.RawFile` instance.
Default Xyce path is set in `XyceServer.XYCE_COMMAND`.
"""
if ConfigInstall.OS.on_linux:
XYCE_COMMAND = 'Xyce'
elif ConfigInstall.OS.on_osx:
XYCE_COMMAND = 'Xyce'
elif ConfigInstall.OS.on_windows:
XYCE_COMMAND = 'C:\\Program Files\\Xyce 6.10 OPENSOURCE\\bin\\Xyce.exe'
else:
raise NotImplementedError
_logger = _module_logger.getChild('XyceServer')
##############################################
def __init__(self, **kwargs):
self._xyce_command = kwargs.get('xyce_command') or self.XYCE_COMMAND
##############################################
def _parse_stdout(self, stdout):
"""Parse stdout for errors."""
# log Spice output
self._logger.info(os.linesep + stdout.decode('utf-8'))
error_found = False
simulation_failed = False
warning_found = False
lines = stdout.splitlines()
for line_index, line in enumerate(lines):
if line.startswith(b'Netlist warning'):
warning_found = True
# Fixme: highlight warnings
self._logger.warning(os.linesep + line.decode('utf-8'))
elif line.startswith(b'Netlist error'):
error_found = True
self._logger.error(os.linesep + line.decode('utf-8'))
elif b'Transient failure history' in line:
simulation_failed = True
self._logger.error(os.linesep + line.decode('utf-8'))
if error_found:
raise NameError("Errors was found by Xyce")
elif simulation_failed:
raise NameError("Xyce simulation failed")
##############################################
def __call__(self, spice_input):
"""Run SPICE in server mode as a subprocess for the given input and return a
:obj:`PySpice.RawFile.RawFile` instance.
"""
self._logger.debug('Start the xyce subprocess')
tmp_dir = tempfile.mkdtemp()
input_filename = os.path.join(tmp_dir, 'input.cir')
output_filename = os.path.join(tmp_dir, 'output.raw')
with open(input_filename, 'w') as f:
f.write(str(spice_input))
command = (self._xyce_command, '-r', output_filename, input_filename)
self._logger.info('Run {}'.format(' '.join(command)))
process = subprocess.Popen(
command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = process.communicate()
self._parse_stdout(stdout)
with open(output_filename, 'rb') as f:
output = f.read()
# self._logger.debug(output)
raw_file = RawFile(output)
shutil.rmtree(tmp_dir)
return raw_file
PySpice-pr-191/PySpice/Spice/Xyce/Simulation.py 0000664 0000000 0000000 00000004622 13654121544 0021417 0 ustar 00root root 0000000 0000000 ###################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
"""This modules implements classes to perform simulations.
"""
####################################################################################################
import logging
####################################################################################################
from ..Simulation import CircuitSimulator
from .Server import XyceServer
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class XyceCircuitSimulator(CircuitSimulator):
_logger = _module_logger.getChild('XyceCircuitSimulator')
SIMULATOR = 'xyce'
##############################################
def __init__(self, circuit, **kwargs):
super().__init__(circuit, **kwargs)
xyce_command = kwargs.get('xyce_command', None)
self._xyce_server = XyceServer(xyce_command=xyce_command)
##############################################
def str_options(self):
return super().str_options(unit=False)
##############################################
def _run(self, analysis_method, *args, **kwargs):
super()._run(analysis_method, *args, **kwargs)
raw_file = self._xyce_server(spice_input=str(self))
self.reset_analysis()
raw_file.simulation = self
# for field in raw_file.variables:
# print field
return raw_file.to_analysis()
PySpice-pr-191/PySpice/Spice/Xyce/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0021014 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Spice/__init__.py 0000664 0000000 0000000 00000005103 13654121544 0020115 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import logging
from . import BasicElement
from . import HighLevelElement
from .Netlist import Netlist, ElementParameterMetaClass
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
def _get_elements(module):
element_classes = []
for item in module.__dict__.values():
if (type(item) is ElementParameterMetaClass
and item.__prefix__ is not None
):
element_classes.append(item)
return element_classes
####################################################################################################
#
# Add a method to create elements to the Netlist class
#
spice_elements = _get_elements(BasicElement)
high_level_elements = _get_elements(HighLevelElement)
for element_class in spice_elements + high_level_elements:
def _make_function(element_class):
def function(self, *args, **kwargs):
return element_class(self, *args, **kwargs)
return function
func = _make_function(element_class)
def _set(name):
# _module_logger.debug("Add device shortcut {} for class {}".format(name, element_class))
setattr(Netlist, name, func)
_set(element_class.__name__)
if element_class in spice_elements:
if hasattr(element_class, '__alias__'):
_set(element_class.__alias__)
if hasattr(element_class, '__long_alias__'):
_set(element_class.__long_alias__)
PySpice-pr-191/PySpice/Tools/ 0000775 0000000 0000000 00000000000 13654121544 0016042 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Tools/EnumFactory.py 0000664 0000000 0000000 00000011677 13654121544 0020664 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
""" This module provides an implementation for enumerate.
The enumerate factory :func:`EnumFactory` builds a enumerate from a list of names and assigns to
these constants a value from 0 to N-1, where N is the number of constants. For example::
enum = EnumFactory('Enum1', ('cst1', 'cst2'))
builds a enumerate with *cst1* set to 0 and *cst2* set to 1.
We can get a constant's value using an integer context like::
int(enum.cst1)
and the constant's name using::
repr(enum.cst1)
We can test constant equality using::
enum1.cst == enum2.cst
or with something that understand the *int* protocol::
enum1.cst == obj
# equivalent to
int(enum1.cst) == int(obj)
The number of constants could be retrieved with::
len(enum)
The enumerate factory :func:`ExplicitEnumFactory` is a variant that permits to specify the values of
the constants::
enum2 = ExplicitEnumFactory('Enum2', {'cst1':1, 'cst2':3})
We can test if a value is in the enumerate using::
constant_value in enum2
"""
####################################################################################################
# __all__ = ['EnumFactory', 'ExplicitEnumFactory']
####################################################################################################
class ReadOnlyMetaClass(type):
""" This meta class implements a class where attributes are read only. """
##############################################
def __setattr__(self, name, value):
raise NotImplementedError
####################################################################################################
class EnumMetaClass(ReadOnlyMetaClass):
""" This meta class implements the :func:`len` protocol. """
##############################################
def __len__(self):
return self._size
##############################################
def __getitem__(self, i):
return self._index[i]
####################################################################################################
class ExplicitEnumMetaClass(ReadOnlyMetaClass):
""" This meta class implements the operator ``in``. """
##############################################
def __contains__(self, item):
return item in self.constants
####################################################################################################
class EnumConstant:
""" Define an Enum Constant """
##############################################
def __init__(self, name, value):
self._name = name
self._value = value
##############################################
def __eq__(self, other):
return self._value == int(other)
##############################################
def __int__(self):
return self._value
##############################################
def __hash__(self):
return self._value
##############################################
def __repr__(self):
return self._name
####################################################################################################
def EnumFactory(enum_name, enum_tuple):
""" Return an :class:`EnumMetaClass` instance, where *enum_name* is the class name and
*enum_tuple* is an iterable of constant's names.
"""
index = [EnumConstant(name, value) for value, name in enumerate(enum_tuple)]
obj_dict = {}
obj_dict['_size'] = len(enum_tuple)
obj_dict['_index'] = index
obj_dict.update({str(enum):enum for enum in index})
return EnumMetaClass(enum_name, (), obj_dict)
####################################################################################################
def ExplicitEnumFactory(enum_name, enum_dict):
""" Return an :class:`ExplicitEnumMetaClass` instance, where *enum_name* is the class name and
*enum_dict* is a dict of constant's names and their values.
"""
obj_dict = {}
obj_dict['constants'] = list(enum_dict.values())
for name, value in list(enum_dict.items()):
obj_dict[name] = EnumConstant(name, value)
return ExplicitEnumMetaClass(enum_name, (), obj_dict)
PySpice-pr-191/PySpice/Tools/File.py 0000664 0000000 0000000 00000016454 13654121544 0017305 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import os
import subprocess
####################################################################################################
def file_name_has_extension(file_name, extension):
return file_name.endswith(extension)
####################################################################################################
def file_extension(filename):
# index = filename.rfind(os.path.extsep)
# if index == -1:
# return None
# else:
# return filename[index:]
return os.path.splitext(filename)[1]
####################################################################################################
def run_shasum(filename, algorithm=1, text=False, binary=False, portable=False):
if algorithm not in (1, 224, 256, 384, 512, 512224, 512256):
raise ValueError
args = ['shasum', '--algorithm=' + str(algorithm)]
if text:
args.append('--text')
elif binary:
args.append('--binary')
elif portable:
args.append('--portable')
args.append(filename)
output = subprocess.check_output(args)
shasum = output[:output.find(' ')]
return shasum
####################################################################################################
class Path:
##############################################
def __init__(self, path):
self._path = str(path)
##############################################
def __bool__(self):
return os.path.exists(self._path)
##############################################
def __str__(self):
return self._path
##############################################
@property
def path(self):
return self._path
##############################################
def is_absolut(self):
return os.path.isabs(self._path)
##############################################
def absolut(self):
return self.clone_for_path(os.path.abspath(self._path))
##############################################
def normalise(self):
return self.clone_for_path(os.path.normpath(self._path))
##############################################
def normalise_case(self):
return self.clone_for_path(os.path.normcase(self._path))
##############################################
def expand_vars_and_user(self):
return self.clone_for_path(os.path.expandvars(os.path.expanduser(self._path)))
##############################################
def real_path(self):
return self.clone_for_path(os.path.realpath(self._path))
##############################################
def relative_to(self, directory):
return self.clone_for_path(os.path.relpath(self._path, str(directory)))
##############################################
def clone_for_path(self, path):
return self.__class__(path)
##############################################
def split(self):
return self._path.split(os.path.sep)
##############################################
def directory_part(self):
return Directory(os.path.dirname(self._path))
##############################################
def filename_part(self):
return os.path.basename(self._path)
##############################################
def is_directory(self):
return os.path.isdir(self._path)
##############################################
def is_file(self):
return os.path.isfile(self._path)
##############################################
@property
def inode(self):
return os.stat(self._path).st_ino
##############################################
@property
def creation_time(self):
return os.stat(self._path).st_ctime
####################################################################################################
class Directory(Path):
##############################################
def __bool__(self):
return super().__nonzero__() and self.is_directory()
##############################################
def join_directory(self, directory):
return self.__class__(os.path.join(self._path, str(directory)))
##############################################
def join_filename(self, filename):
return File(filename, self._path)
##############################################
def iter_file(self, followlinks=False):
for root, directories, files in os.walk(self._path, followlinks=followlinks):
for filename in files:
yield File(filename, root)
##############################################
def iter_directories(self, followlinks=False):
for root, directories, files in os.walk(self._path, followlinks=followlinks):
for directory in directories:
yield Path(os.path.join(root, directory))
####################################################################################################
class File(Path):
default_shasum_algorithm = 256
##############################################
def __init__(self, filename, path=''):
super().__init__(os.path.join(str(path), str(filename)))
self._filename = self.filename_part()
if not self._filename:
raise ValueError
self._directory = self.directory_part()
self._shasum = None # lazy computation
##############################################
def __bool__(self):
return super().__nonzero__() and os.path.isfile(self._path)
##############################################
@property
def directory(self):
return self._directory
##############################################
@property
def filename(self):
return self._filename
##############################################
@property
def extension(self):
return file_extension(self._filename)
##############################################
@property
def shasum(self):
if self._shasum is None:
return self.compute_shasum()
else:
return self._shasum
##############################################
def compute_shasum(self, algorithm=None):
if algorithm is None:
algorithm = self.default_shasum_algorithm
self._shasum = run_shasum(self._path, algorithm, portable=True)
return self._shasum
PySpice-pr-191/PySpice/Tools/Path.py 0000664 0000000 0000000 00000004001 13654121544 0017303 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import os
####################################################################################################
def to_absolute_path(path):
# Expand ~ . and Remove trailing '/'
return os.path.abspath(os.path.expanduser(path))
####################################################################################################
def parent_directory_of(file_name, step=1):
directory = file_name
for i in range(step):
directory = os.path.dirname(directory)
return directory
####################################################################################################
def find(file_name, directories):
if isinstance(directories, bytes):
directories = (directories,)
for directory in directories:
for directory_path, sub_directories, file_names in os.walk(directory):
if file_name in file_names:
return os.path.join(directory_path, file_name)
raise NameError("File %s not found in directories %s" % (file_name, str(directories)))
PySpice-pr-191/PySpice/Tools/StringTools.py 0000664 0000000 0000000 00000005005 13654121544 0020703 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
import os
####################################################################################################
from PySpice.Unit.Unit import UnitValue
####################################################################################################
def str_spice(obj, unit=True):
# Fixme: right place ???
"""Convert an object to a Spice compatible string."""
if isinstance(obj, UnitValue):
if unit:
return obj.str_spice()
else: # Fixme: ok ???
return obj.str(spice=False, space=False, unit=False)
else:
return str(obj)
####################################################################################################
def join_lines(items, prefix=''):
return os.linesep.join([prefix + str(item)
for item in items
if item is not None]) # Fixme: and item
####################################################################################################
def join_list(items):
# return ' '.join([str_spice(item)
# for item in items
# if item is not None and str_spice(item)])
values = []
for item in items:
if item is not None:
str_value = str_spice(item)
if str_value:
values.append(str_value)
return ' '.join(values)
####################################################################################################
def join_dict(d):
return ' '.join(["{}={}".format(key, str_spice(value))
for key, value in sorted(d.items())
if value is not None])
PySpice-pr-191/PySpice/Tools/__init__.py 0000664 0000000 0000000 00000000000 13654121544 0020141 0 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Unit/ 0000775 0000000 0000000 00000000000 13654121544 0015661 5 ustar 00root root 0000000 0000000 PySpice-pr-191/PySpice/Unit/SiUnits.py 0000664 0000000 0000000 00000017506 13654121544 0017642 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
"""This module defines SI prefixes and units.
"""
####################################################################################################
from .Unit import UnitPrefix, SiBaseUnit, Unit
####################################################################################################
# Define SI unit prefixes
class Yotta(UnitPrefix):
__power__ = 24
__prefix__ = 'Y'
__spice_prefix__ = None
class Zetta(UnitPrefix):
__power__ = 21
__prefix__ = 'Z'
__spice_prefix__ = None
class Exa(UnitPrefix):
__power__ = 18
__prefix__ = 'E'
__spice_prefix__ = None
class Peta(UnitPrefix):
__power__ = 15
__prefix__ = 'P'
__spice_prefix__ = None
class Tera(UnitPrefix):
__power__ = 12
__prefix__ = 'T'
class Giga(UnitPrefix):
__power__ = 9
__prefix__ = 'G'
class Mega(UnitPrefix):
__power__ = 6
__prefix__ = 'M'
__spice_prefix__ = 'Meg'
class Kilo(UnitPrefix):
__power__ = 3
__prefix__ = 'k'
class Hecto(UnitPrefix):
__power__ = 2
__prefix__ = 'h'
__spice_prefix__ = None
class Deca(UnitPrefix):
__power__ = 1
__prefix__ = 'da'
__spice_prefix__ = None
class Milli(UnitPrefix):
__power__ = -3
__prefix__ = 'm'
class Micro(UnitPrefix):
__power__ = -6
__prefix__ = 'μ'
__spice_prefix__ = 'u'
class Nano(UnitPrefix):
__power__ = -9
__prefix__ = 'n'
class Pico(UnitPrefix):
__power__ = -12
__prefix__ = 'p'
class Femto(UnitPrefix):
__power__ = -15
__prefix__ = 'f'
__spice_prefix__ = None
class Atto(UnitPrefix):
__power__ = -18
__prefix__ = 'a'
__spice_prefix__ = None
class Zepto(UnitPrefix):
__power__ = -21
__prefix__ = 'z'
__spice_prefix__ = None
class Yocto(UnitPrefix):
__power__ = -24
__prefix__ = 'y'
__spice_prefix__ = None
# Fixme: ngspice defines mil
####################################################################################################
# Define SI units
class Metre(SiBaseUnit):
__unit_name__ = 'metre'
__unit_suffix__ = 'm'
__quantity__ = 'length'
class Kilogram(SiBaseUnit):
__unit_name__ = 'kilogram'
__unit_suffix__ = 'kg'
__quantity__ = 'mass'
class Second(SiBaseUnit):
__unit_name__ = 'second'
__unit_suffix__ = 's'
__quantity__ = 'time'
__is_si__ = True
class Ampere(SiBaseUnit):
__unit_name__ = 'ampere'
__unit_suffix__ = 'A'
__quantity__ = 'electric current'
class Kelvin(SiBaseUnit):
__unit_name__ = 'kelvin'
__unit_suffix__ = 'K'
__quantity__ = 'thermodynamic temperature'
class Mole(SiBaseUnit):
__unit_name__ = 'mole'
__unit_suffix__ = 'mol'
__quantity__ = 'amount of substance'
class Candela(SiBaseUnit):
__unit_name__ = 'candela'
__unit_suffix__ = 'cd'
__quantity__ = 'luminosity intensity'
####################################################################################################
# Define Derived units
class Radian(Unit):
__unit_name__ = 'radian'
__unit_suffix__ = 'rad'
__quantity__ = 'angle'
__si_unit__ = 'm*m^-1'
__default_unit__ = True
class Steradian(Unit):
__unit_name__ = 'steradian'
__unit_suffix__ = 'sr'
__quantity__ = 'solid angle'
__si_unit__ = 'm^2*m^-2'
__default_unit__ = True
class Hertz(Unit):
__unit_name__ = 'frequency'
__unit_suffix__ = 'Hz'
__quantity__ = 'frequency'
__si_unit__ = 's^-1'
__default_unit__ = True
class Newton(Unit):
__unit_name__ = 'newton'
__unit_suffix__ = 'N'
__quantity__ = 'force'
__si_unit__ = 'kg*m*s^-2'
__default_unit__ = True
class Pascal(Unit):
__unit_name__ = 'pascal'
__unit_suffix__ = 'Pa'
__quantity__ = 'pressure'
__si_unit__ = 'kg*m^-1*s^-2'
__default_unit__ = True
# N/m^2
class Joule(Unit):
__unit_name__ = 'joule'
__unit_suffix__ = 'J'
__quantity__ = 'energy'
__si_unit__ = 'kg*m^2*s^-2'
__default_unit__ = True
# N*m
class Watt(Unit):
__unit_name__ = 'watt'
__unit_suffix__ = 'W'
__quantity__ = 'power'
__si_unit__ = 'kg*m^2*s^-3'
__default_unit__ = True
# J/s
class Coulomb(Unit):
__unit_name__ = 'coulomb'
__unit_suffix__ = 'C'
__quantity__ = 'electric charge'
__si_unit__ = 's*A'
__default_unit__ = True
class Volt(Unit):
__unit_name__ = 'volt'
__unit_suffix__ = 'V'
__quantity__ = 'voltage'
__si_unit__ = 'kg*m^2*s^-3*A^-1'
__default_unit__ = True
# W/A
class Farad(Unit):
__unit_name__ = 'farad'
__unit_suffix__ = 'F'
__quantity__ = 'capacitance'
__si_unit__ = 'kg^-1*m^-2*s^4*A^2'
__default_unit__ = True
# C/V
class Ohm(Unit):
__unit_name__ = 'ohm'
__unit_suffix__ = 'Ω'
__quantity__ = 'electric resistance, impedance, reactance'
__si_unit__ = 'kg*m^2*s^-3*A^-2'
__default_unit__ = True
# V/A
class Siemens(Unit):
__unit_name__ = 'siemens'
__unit_suffix__ = 'S'
__quantity__ = 'electrical conductance'
__si_unit__ = 'kg^-1*m^-2*s^3*A^2'
__default_unit__ = True
# A/V
class Weber(Unit):
__unit_name__ = 'weber'
__unit_suffix__ = 'Wb'
__quantity__ = 'magnetic flux'
__si_unit__ = 'kg*m^2*s^-2*A^-1'
__default_unit__ = True
# V*s
class Tesla(Unit):
__unit_name__ = 'tesla'
__unit_suffix__ = ''
__quantity__ = 'T'
__si_unit__ = 'kg*s^-2*A^-1'
__default_unit__ = True
# Wb/m2
class Henry(Unit):
__unit_name__ = 'henry'
__unit_suffix__ = 'H'
__quantity__ = 'inductance'
__si_unit__ = 'kg*m^2*s^-2*A^-2'
__default_unit__ = True
# Wb/A
class DegreeCelcius(Unit):
__unit_name__ = 'degree celcuis'
__unit_suffix__ = '°C'
__quantity__ = 'temperature relative to 273.15 K'
__si_unit__ = 'K'
class Lumen(Unit):
__unit_name__ = 'lumen'
__unit_suffix__ = 'lm'
__quantity__ = 'luminous flux'
__si_unit__ = 'cd'
# cd*sr
class Lux(Unit):
__unit_name__ = 'lux'
__unit_suffix__ = 'lx'
__quantity__ = 'illuminance'
__si_unit__ = 'm^-2*cd'
__default_unit__ = True
# lm/m2
class Becquerel(Unit):
__unit_name__ = 'becquerel'
__unit_suffix__ = 'Bq'
__quantity__ = 'radioactivity (decays per unit time)'
__si_unit__ = 's^-1' # same as Hertz
class Gray(Unit):
__unit_name__ = 'gray'
__unit_suffix__ = 'Gy'
__quantity__ = 'absorbed dose (of ionizing radiation)'
__si_unit__ = 'm^2*s^-2'
# J/kg
class Sievert(Unit):
__unit_name__ = 'sievert'
__unit_suffix__ = 'Sv'
__quantity__ = ' equivalent dose (of ionizing radiation)'
__si_unit__ = 'm^2*s^-2'
class Katal(Unit):
__unit_name__ = 'katal'
__unit_suffix__ = 'kat'
__quantity__ = 'catalytic activity'
__si_unit__ = 'mol*s^-1'
__default_unit__ = True
####################################################################################################
# class Mil(Unit):
# __scale__ = 25.4e-6 # mm
# __spice_suffix__ = 'mil'
PySpice-pr-191/PySpice/Unit/Unit.py 0000664 0000000 0000000 00000163575 13654121544 0017173 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
"""This module implements units.
A shortcut is defined for each unit prefix, e.g. :class:`pico`, :class:`nano`, :class:`micro`,
:class:`milli`, :class:`kilo`, :class:`mega`, :class:`tera`.
"""
####################################################################################################
import logging
import collections.abc as collections
import math
# import numbers
import numpy as np
####################################################################################################
from PySpice.Tools.EnumFactory import EnumFactory
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class UnitPrefixMetaclass(type):
"""Metaclass to register unit prefixes"""
__prefixes__ = {} # singletons
##############################################
def __new__(meta, class_name, base_classes, attributes):
cls = type.__new__(meta, class_name, base_classes, attributes)
if class_name != 'UnitPrefix':
meta.register_prefix(cls)
return cls
##############################################
@classmethod
def register_prefix(meta, cls):
power = cls.__power__
if power is None:
raise ValueError('Power is None for {}'.format(cls.__name__))
meta.__prefixes__[power] = cls()
##############################################
@classmethod
def prefix_iter(cls):
return cls.__prefixes__.values()
##############################################
@classmethod
def get(cls, power):
return cls.__prefixes__[power]
####################################################################################################
class UnitPrefix(metaclass=UnitPrefixMetaclass):
"""This class implements a unit prefix like kilo"""
__power__ = None
__prefix__ = ''
##############################################
def __repr__(self):
return '{}({}, {})'.format(self.__class__.__name__, self.__power__, self.__prefix__)
##############################################
def __int__(self):
return self.__power__
##############################################
def __str__(self):
return self.__prefix__
##############################################
@property
def power(self):
return self.__power__
@property
def prefix(self):
return self.__prefix__
@property
def is_unit(self):
return self.__power__ == 0
@property
def scale(self):
return 10**self.__power__
##############################################
@property
def spice_prefix(self):
if hasattr(self, '__spice_prefix__'):
return self.__spice_prefix__
else:
return self.__prefix__
##############################################
@property
def is_defined_in_spice(self):
return self.spice_prefix is not None
##############################################
def __eq__(self, other):
return self.__power__ == other.__power__
##############################################
def __ne__(self, other):
return self.__power__ != other.__power__
##############################################
def __lt__(self, other):
return self.__power__ < other.__power__
##############################################
def __gt__(self, other):
return self.__power__ > other.__power__
##############################################
def str(self, spice=False):
if spice:
return self.spice_prefix
else:
return self.__prefix__
####################################################################################################
class ZeroPower(UnitPrefix):
__power__ = 0
__prefix__ = ''
__spice_prefix__ = ''
_zero_power = UnitPrefixMetaclass.get(0)
####################################################################################################
class SiDerivedUnit:
"""This class implements a unit defined as powers of SI base units.
"""
# SI base units
__base_units__ = (
'm',
'kg',
's',
'A',
'K',
'mol',
'cd',
)
##############################################
def __init__(self, string=None, powers=None):
if powers is not None:
self._powers = self.new_powers()
self._powers.update(powers)
elif string is not None:
self._powers = self.parse_si(string)
else:
self._powers = self.new_powers()
self._hash = self.to_hash(self._powers)
self._string = self.to_string(self._powers)
##############################################
@property
def powers(self):
return self._powers
@property
def hash(self):
return self._hash
@property
def string(self):
return self._string
def __str__(self):
return self._string
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, self._string)
##############################################
@classmethod
def new_powers(cls):
return {unit: 0 for unit in cls.__base_units__}
##############################################
@classmethod
def parse_si(cls, string):
si_powers = cls.new_powers()
if string:
for prefixed_units in string.split('*'):
parts = prefixed_units.split('^')
unit = parts[0]
if len(parts) == 1:
powers = 1
else:
powers = int(parts[1])
si_powers[unit] += powers
return si_powers
##############################################
@classmethod
def to_hash(cls, powers):
hash_ = ''
for unit in cls.__base_units__:
hash_ += str(powers[unit])
return hash_
##############################################
@classmethod
def to_string(cls, si_powers):
units = []
for unit in cls.__base_units__:
powers = si_powers[unit]
if powers == 1:
units.append(unit)
elif powers > 1 or powers < 0:
units.append('{}^{}'.format(unit, powers))
return '*'.join(units)
##############################################
# @property
def is_base_unit(self):
count = 0
for powers in self._powers.values():
if powers == 1:
count += 1
elif powers != 0:
return False
return count == 1
##############################################
# @property
def is_unit_less(self):
return self._hash == '0'*len(self.__base_units__)
##############################################
def __bool__(self):
return not self.is_unit_less()
##############################################
def clone(self):
return self.__class__(powers=self._powers)
##############################################
def __eq__(self, other):
return self._hash == other.hash
##############################################
def __ne__(self, other):
return self._hash != other.hash
##############################################
def __mul__(self, other):
powers = {unit: self._powers[unit] + other._powers[unit]
for unit in self.__base_units__}
return self.__class__(powers=powers)
##############################################
def __imul__(self, other):
for unit in self.__base_units__:
self._powers[unit] += other.powers[unit]
self._hash = self.to_hash(self._powers)
self._string = self.to_string(self._powers)
return self
##############################################
def __truediv__(self, other):
powers = {unit: self._powers[unit] - other._powers[unit]
for unit in self.__base_units__}
return self.__class__(powers=powers)
##############################################
def __itruediv__(self, other):
for unit in self.__base_units__:
self._powers[unit] -= other.powers[unit]
self._hash = self.to_hash(self._powers)
self._string = self.to_string(self._powers)
return self
##############################################
def power(self, value):
powers = {unit: self._powers[unit] * value
for unit in self.__base_units__}
return self.__class__(powers=powers)
##############################################
def reciprocal(self):
return self.power(-1)
##############################################
def sqrt(self):
return self.power(1/2)
##############################################
def square(self):
return self.power(2)
##############################################
def cbrt(self):
return self.power(1/3)
####################################################################################################
class UnitMetaclass(type):
"""Metaclass to register units"""
__units__ = {}
__hash_map__ = {}
##############################################
def __new__(meta, class_name, base_classes, attributes):
cls = type.__new__(meta, class_name, base_classes, attributes)
meta.init_unit(cls)
meta.register_unit(cls)
return cls
##############################################
@classmethod
def init_unit(meta, cls):
si_unit = cls.__si_unit__
if not (isinstance(si_unit, SiDerivedUnit) and si_unit):
# si_unit is not defined
if cls.is_base_unit():
si_unit = SiDerivedUnit(cls.__unit_suffix__)
else: # str
si_unit = SiDerivedUnit(si_unit)
cls.__si_unit__ = si_unit
##############################################
@classmethod
def register_unit(meta, cls):
obj = cls()
meta.__units__[obj.unit_suffix] = obj
if obj.si_unit:
hash_ = obj.si_unit.hash
if hash_ in meta.__hash_map__:
meta.__hash_map__[hash_].append(obj)
else:
meta.__hash_map__[hash_] = [obj]
##############################################
@classmethod
def unit_iter(meta):
return meta.__units__.values()
##############################################
@classmethod
def from_prefix(meta, prefix):
return meta._units__.get(prefix, None)
##############################################
@classmethod
def from_hash(meta, hash_):
return meta.__hash_map__.get(hash_, None)
##############################################
@classmethod
def from_si_unit(meta, si_unit, unique=True):
# Fixme:
# - handle power of units
# unit -> numpy vector, divide and test for identical factor
# define unit, format as V^2
# - complex unit
units = meta.__hash_map__.get(si_unit.hash, None)
if unique and units is not None:
if len(units) > 1:
units = [unit for unit in units if unit.is_default_unit()]
if len(units) == 1:
return units[0]
else:
raise NameError("Unit clash", units)
else:
return units[0]
else:
return units
####################################################################################################
class UnitError(ValueError):
pass
####################################################################################################
class Unit(metaclass=UnitMetaclass):
"""This class implements a unit.
"""
__unit_name__ = ''
__unit_suffix__ = ''
__quantity__ = ''
__si_unit__ = SiDerivedUnit()
__default_unit__ = False
# __spice_suffix__ = ''
_logger = _module_logger.getChild('Unit')
##############################################
def __init__(self, si_unit=None):
self._unit_name = self.__unit_name__
self._unit_suffix = self.__unit_suffix__
self._quantity = self.__quantity__
if si_unit is None:
self._si_unit = self.__si_unit__
else:
self._si_unit = si_unit
##############################################
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, str(self))
##############################################
@property
def unit_name(self):
return self._unit_name
@property
def unit_suffix(self):
return self._unit_suffix
@property
def quantity(self):
return self._quantity
@property
def si_unit(self):
return self._si_unit
##############################################
@property
def is_unit_less(self):
return self._si_unit.is_unit_less()
##############################################
@classmethod
def is_default_unit(cls):
return cls.__default_unit__
@classmethod
def is_base_unit(cls):
return False
##############################################
def __eq__(self, other):
"""self == other"""
return self._si_unit == other.si_unit
##############################################
def __ne__(self, other):
"""self != other"""
# The default __ne__ doesn't negate __eq__ until 3.0.
return not (self == other)
##############################################
def _equivalent_prefixed_unit(self, si_unit):
equivalent_unit = PrefixedUnit.from_si_unit(si_unit)
if equivalent_unit is not None:
return equivalent_unit
else:
return PrefixedUnit(Unit(si_unit))
##############################################
def _equivalent_unit(self, si_unit):
equivalent_unit = UnitMetaclass.from_si_unit(si_unit)
if equivalent_unit is not None:
return equivalent_unit
else:
return Unit(si_unit)
##############################################
def _equivalent_unit_or_power(self, si_unit, prefixed_unit):
if prefixed_unit:
return self._equivalent_prefixed_unit(si_unit)
else:
return self._equivalent_unit(si_unit)
##############################################
def multiply(self, other, prefixed_unit=False):
si_unit = self._si_unit * other.si_unit
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def divide(self, other, prefixed_unit=False):
si_unit = self._si_unit / other.si_unit
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def power(self, exponent, prefixed_unit=False):
si_unit = self._si_unit.power(exponent)
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def reciprocal(self, prefixed_unit=False):
si_unit = self._si_unit.reciprocal()
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def sqrt(self, prefixed_unit=False):
si_unit = self._si_unit.sqrt()
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def square(self, prefixed_unit=False):
si_unit = self._si_unit.square()
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def cbrt(self, prefixed_unit=False):
si_unit = self._si_unit.cbrt()
return self._equivalent_unit_or_power(si_unit, prefixed_unit)
##############################################
def __str__(self):
if self._unit_suffix:
return self._unit_suffix
else:
return str(self._si_unit)
##############################################
def is_same_unit(self, value):
return value.unit == self
##############################################
def validate(self, value, none=False):
if none and value is None:
return None
if isinstance(value, UnitValue):
if self.is_same_unit(value):
return value
else:
raise UnitError
else:
prefixed_unit = PrefixedUnit.from_prefixed_unit(self)
return prefixed_unit.new_value(value)
####################################################################################################
class SiBaseUnit(Unit):
"""This class implements an SI base unit."""
##############################################
@classmethod
def is_base_unit(cls):
return True
##############################################
@classmethod
def is_default_unit(cls):
return True
####################################################################################################
class PrefixedUnit:
"""This class implements a prefixed unit.
"""
__unit_map__ = {} # Prefixed unit singletons
__prefixed_unit_map__ = {}
__value_ctor__ = None
__values_ctor__ = None
##############################################
@classmethod
def register(cls, prefixed_unit):
unit = prefixed_unit.unit
unit_prefix = prefixed_unit.power
if unit_prefix.is_unit and unit.is_default_unit():
key = unit.si_unit.hash
# print('Register', key, prefixed_unit)
cls.__unit_map__[key] = prefixed_unit
if unit.unit_suffix:
unit_key = str(unit)
else:
unit_key = '_'
power_key = unit_prefix.power
# print('Register', unit_key, power_key, prefixed_unit)
if unit_key not in cls.__prefixed_unit_map__:
cls.__prefixed_unit_map__[unit_key] = {}
cls.__prefixed_unit_map__[unit_key][power_key] = prefixed_unit
##############################################
@classmethod
def from_si_unit(cls, si_unit):
return cls.__unit_map__.get(si_unit.hash, None)
##############################################
@classmethod
def from_prefixed_unit(cls, unit, power=0):
if unit.unit_suffix:
unit_key = str(unit)
else:
if power == 0:
return _simple_prefixed_unit
unit_key = '_'
try:
return cls.__prefixed_unit_map__[unit_key][power]
except KeyError:
return None
##############################################
def __init__(self, unit=None, power=None, value_ctor=None, values_ctor=None):
if unit is None:
self._unit = Unit()
else:
self._unit = unit
if power is None:
self._power = _zero_power
else:
self._power = power
if value_ctor is None:
self._value_ctor = self.__value_ctor__
else:
self._value_ctor = value_ctor
if values_ctor is None:
self._values_ctor = self.__values_ctor__
else:
self._values_ctor = values_ctor
##############################################
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, str(self))
##############################################
@property
def unit(self):
return self._unit
@property
def power(self):
return self._power
@property
def scale(self):
return self._power.scale
##############################################
@property
def is_unit_less(self):
return self._unit.is_unit_less
##############################################
def clone(self):
return self.__class__(self._unit, self._power)
##############################################
def is_same_unit(self, other):
return self._unit == other.unit
##############################################
def check_unit(self, other):
if not self.is_same_unit(other):
raise UnitError('{} versus {}'.format(self, other))
##############################################
def is_same_power(self, other):
return self._power == other.power
##############################################
def __eq__(self, other):
"""self == other"""
return self.is_same_unit(other) and self.is_same_power(other)
##############################################
def __ne__(self, other):
"""self != other"""
# The default __ne__ doesn't negate __eq__ until 3.0.
return not (self == other)
##############################################
def str(self, spice=False, unit=True):
string = self._power.str(spice)
if unit:
string += str(self._unit)
if spice:
# F is interpreted as f = femto
if string == 'F':
string = ''
else:
# Ngspice don't support utf-8
string = string.replace('Ω', 'Ohm') # utf-8 cea0
string = string.replace('μ', 'u') # utf-8 cebc
return string
##############################################
def str_spice(self):
# Ngspice User Manual Section 2.3.1 Some naming conventions
#
# Letters immediately following a number that are not scale factors are ignored, and letters
# im- mediately following a scale factor are ignored. Hence, 10, 10V, 10Volts, and 10Hz all
# represent the same number, and M, MA, MSec, and MMhos all represent the same scale
# factor. Note that 1000, 1000.0, 1000Hz, 1e3, 1.0e3, 1kHz, and 1k all represent the same
# number. Note that M or m denote ’milli’, i.e. 10−3 . Suffix meg has to be used for 106 .
# Fixme: unit clash, e.g. mm ???
return self.str(spice=True, unit=True)
##############################################
def __str__(self):
return self.str(spice=False, unit=True)
##############################################
def new_value(self, value):
if isinstance(value, np.ndarray):
return self._values_ctor.from_ndarray(value, self)
elif isinstance(value, collections.Iterable):
return [self._value_ctor(self, x) for x in value]
else:
return self._value_ctor(self, value)
####################################################################################################
class UnitValue: # numbers.Real
"""This class implements a value with a unit and a power (prefix).
The value is not converted to float if the value is an int.
"""
_logger = _module_logger.getChild('UnitValue')
##############################################
@classmethod
def simple_value(cls, value):
return cls(_simple_prefixed_unit, value)
##############################################
def __init__(self, prefixed_unit, value):
self._prefixed_unit = prefixed_unit
if isinstance(value, UnitValue):
# Fixme: anonymous ???
if not self.is_same_unit(value):
raise UnitError
if self.is_same_power(value):
self._value = value.value
else:
self._value = self._convert_scalar_value(value)
elif isinstance(value, int):
self._value = value # to keep as int
else:
self._value = float(value)
##############################################
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, str(self))
##############################################
@property
def prefixed_unit(self):
return self._prefixed_unit
@property
def unit(self):
return self._prefixed_unit.unit
@property
def power(self):
return self._prefixed_unit.power
@property
def scale(self):
return self._prefixed_unit.power.scale
@property
def value(self):
return self._value
##############################################
def clone(self):
return self.__class__(self._prefixed_unit, self._value)
##############################################
def clone_prefixed_unit(self, value):
return self.__class__(self._prefixed_unit, value)
##############################################
# def to_unit_values(self):
# return self._prefixed_unit.new_value(self._value)
##############################################
# def clone_unit(self, value, power):
# return self.__class__(PrefixedUnit(self.unit, power), value)
##############################################
def is_same_unit(self, other):
return self._prefixed_unit.is_same_unit(other.prefixed_unit)
##############################################
def _check_unit(self, other):
if not self.is_same_unit(other):
raise UnitError
##############################################
def is_same_power(self, other):
return self._prefixed_unit.is_same_power(other.prefixed_unit)
##############################################
def __eq__(self, other):
"""self == other"""
if isinstance(other, UnitValue):
return self.is_same_unit(other) and float(self) == float(other)
else:
return float(self) == float(other)
##############################################
def __ne__(self, other):
"""self != other"""
# The default __ne__ doesn't negate __eq__ until 3.0.
return not (self == other)
##############################################
def _convert_value(self, other):
"""Convert the value of other to the power of self."""
self._check_unit(other)
if self.is_same_power(other):
return other.value
else:
return other.value * (other.scale / self.scale) # for numerical precision
##############################################
def _convert_scalar_value(self, value):
return float(value) / self.scale
##############################################
def __int__(self):
return int(self._value * self.scale)
##############################################
def __float__(self):
return float(self._value * self.scale)
##############################################
def str(self, spice=False, space=False, unit=True):
string = str(self._value)
if space:
string += ' '
string += self._prefixed_unit.str(spice, unit)
return string
##############################################
def str_space(self):
return self.str(space=True)
##############################################
def str_spice(self):
return self.str(spice=True, space=False, unit=True)
##############################################
def __str__(self):
return self.str(spice=False, space=True, unit=True)
##############################################
def __bool__(self):
"""True if self != 0. Called for bool(self)."""
return self._value != 0
##############################################
def __add__(self, other):
"""self + other"""
if (isinstance(other, UnitValue)):
self._check_unit(other)
new_obj = self.clone()
new_obj._value += self._convert_value(other)
return new_obj
else:
return float(self) + other
##############################################
def __iadd__(self, other):
"""self += other"""
self._check_unit(other)
self._value += self._convert_value(other)
return self
##############################################
def __radd__(self, other):
"""other + self"""
return float(self) + other
##############################################
def __neg__(self):
"""-self"""
return self.clone_prefixed_unit(-self._value)
##############################################
def __pos__(self):
"""+self"""
return self.clone()
##############################################
def __sub__(self, other):
"""self - other"""
if (isinstance(other, UnitValue)):
self._check_unit(other)
new_obj = self.clone()
new_obj._value -= self._convert_value(other)
return new_obj
else:
return float(self) - other
##############################################
def __isub__(self, other):
"""self -= other"""
self._check_unit(other)
self._value -= self._convert_value(other)
return self
##############################################
def __rsub__(self, other):
"""other - self"""
return other - float(self)
##############################################
def __mul__(self, other):
"""self * other"""
if (isinstance(other, UnitValue)):
equivalent_unit = self.unit.multiply(other.unit, True)
value = float(self) * float(other)
return equivalent_unit.new_value(value)
else:
try: # scale value
scalar = float(other)
new_obj = self.clone()
new_obj._value *= scalar
return new_obj
except (ValueError, TypeError): # Numpy raises TypeError
return float(self) * other
##############################################
def __imul__(self, other):
"""self *= other"""
if (isinstance(other, UnitValue)):
raise UnitError
else: # scale value
# Fixme: right ?
self._value *= self._convert_value(other)
return self
##############################################
def __rmul__(self, other):
"""other * self"""
if (isinstance(other, UnitValue)):
raise NotImplementedError # Fixme: when ???
else: # scale value
return self.__mul__(other)
##############################################
def __floordiv__(self, other):
"""self // other """
if (isinstance(other, UnitValue)):
equivalent_unit = self.unit.divide(other.unit, True)
value = float(self) // float(other)
return equivalent_unit.new_value(value)
else:
try: # scale value
scalar = float(other)
new_obj = self.clone()
new_obj._value //= scalar
return new_obj
except (ValueError, TypeError): # Numpy raises TypeError
return float(self) // other
##############################################
def __ifloordiv__(self, other):
"""self //= other """
if (isinstance(other, UnitValue)):
raise NotImplementedError
else: # scale value
self._value //= float(other)
return self
##############################################
def __rfloordiv__(self, other):
"""other // self"""
if (isinstance(other, UnitValue)):
raise NotImplementedError # Fixme: when ???
else: # scale value
return other // float(self)
##############################################
def __truediv__(self, other):
"""self / other"""
if (isinstance(other, UnitValue)):
equivalent_unit = self.unit.divide(other.unit, True)
value = float(self) / float(other)
return equivalent_unit.new_value(value)
else:
try: # scale value
scalar = float(other)
new_obj = self.clone()
new_obj._value /= scalar
return new_obj
except (ValueError, TypeError): # Numpy raises TypeError
return float(self) / other
##############################################
def __itruediv__(self, other):
"""self /= other"""
if (isinstance(other, UnitValue)):
raise NotImplementedError
else: # scale value
self._value /= float(other)
return self
##############################################
def __rtruediv__(self, other):
"""other / self"""
if (isinstance(other, UnitValue)):
raise NotImplementedError # Fixme: when ???
else: # scale value
return other / float(self)
##############################################
def __pow__(self, exponent):
"""self**exponent; should promote to float or complex when necessary."""
new_obj = self.clone()
new_obj._value **= float(exponent)
return new_obj
##############################################
def __ipow__(self, exponent):
self._value **= float(exponent)
return self
##############################################
def __rpow__(self, base):
"""base ** self"""
raise NotImplementedError
##############################################
def __abs__(self):
"""Returns the Real distance from 0. Called for abs(self)."""
return self.clone_prefixed_unit(abs(self._value))
##############################################
def __trunc__(self):
"""trunc(self): Truncates self to an Integral.
Returns an Integral i such that:
* i>0 iff self>0;
* abs(i) <= abs(self);
* for any Integral j satisfying the first two conditions,
abs(i) >= abs(j) [i.e. i has "maximal" abs among those].
i.e. "truncate towards 0".
"""
raise NotImplementedError
##############################################
def __divmod__(self, other):
"""divmod(self, other): The pair (self // other, self % other).
Sometimes this can be computed faster than the pair of
operations.
"""
return (self // other, self % other)
##############################################
def __rdivmod__(self, other):
"""divmod(other, self): The pair (self // other, self % other).
Sometimes this can be computed faster than the pair of
operations.
"""
return (other // self, other % self)
##############################################
def __mod__(self, other):
"""self % other"""
raise NotImplementedError
##############################################
def __rmod__(self, other):
"""other % self"""
raise NotImplementedError
##############################################
def __lt__(self, other):
"""self < other
< on Reals defines a total ordering, except perhaps for NaN."""
return float(self) < float(other)
##############################################
def __le__(self, other):
"""self <= other"""
return float(self) <= float(other)
##############################################
def __ceil__(self):
return math.ceil(float(self))
##############################################
def __floor__(self):
return math.floor(float(self))
##############################################
def __round__(self):
return round(float(self))
##############################################
def reciprocal(self):
equivalent_unit = self.unit.reciprocal(prefixed_unit=True)
reciprocal_value = 1. / float(self)
return equivalent_unit.new_value(reciprocal_value)
##############################################
def get_prefixed_unit(self, power=0):
prefixed_unit = PrefixedUnit.from_prefixed_unit(self.unit, power)
if prefixed_unit is not None:
return prefixed_unit
else:
raise NameError("Prefixed unit not found for {} and power {}".format(self, power))
##############################################
def convert(self, prefixed_unit):
"""Convert the value to another power."""
self._prefixed_unit.check_unit(prefixed_unit)
if self._prefixed_unit.is_same_power(prefixed_unit):
return self
else:
value = float(self) / prefixed_unit.scale
return prefixed_unit.new_value(value)
##############################################
def convert_to_power(self, power=0):
"""Convert the value to another power."""
if power == 0:
value = float(self)
else:
value = float(self) / 10**power
return self.get_prefixed_unit(power).new_value(value)
##############################################
def canonise(self):
# log10(10**n) = n log10(1) = 0 log10(10**-n) = -n log10(0) = -oo
try:
abs_value = abs(float(self))
log = math.log(abs_value)/math.log(1000)
# if abs_value >= 1:
# power = 3 * int(log)
# else:
# if log - int(log): # frac
# power = 3 * (int(log) -1)
# else:
# power = 3 * int(log)
power = int(log)
if abs_value < 1 and (log - int(log)):
power -= 1
power *= 3
# print('Unit.canonise', self, self._value, int(self._power), '->', float(self), power)
if power == int(self.power):
# print('Unit.canonise noting to do for', self)
return self
else:
# print('Unit.canonise convert', self, 'to', power)
# print('Unit.canonise convert', self, 'to', Unit)
return self.convert_to_power(power)
except Exception as e: # Fixme: fallback
self._logger.warning(e)
return self
####################################################################################################
class UnitValues(np.ndarray):
"""This class implements a Numpy array with a unit and a power (prefix).
"""
_logger = _module_logger.getChild('UnitValues')
CONVERSION = EnumFactory('ConversionType', (
'NOT_IMPLEMENTED',
'NO_CONVERSION',
'FLOAT',
'UNIT_MATCH',
'UNIT_MATCH_NO_OUT_CAST',
'NEW_UNIT'
))
# Reference_documentation:
# https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.ndarray.html
# https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html
# https://docs.scipy.org/doc/numpy-1.13.0/reference/ufuncs.html
UFUNC_MAP = {
# Math operations
# --------------------------------------------------
np.add: CONVERSION.UNIT_MATCH,
np.subtract: CONVERSION.UNIT_MATCH,
np.multiply: CONVERSION.NEW_UNIT,
np.divide: CONVERSION.NEW_UNIT,
np.logaddexp: CONVERSION.FLOAT,
np.logaddexp2: CONVERSION.FLOAT,
np.true_divide: CONVERSION.NEW_UNIT,
np.floor_divide: CONVERSION.NEW_UNIT,
np.negative: CONVERSION.NO_CONVERSION,
np.positive: CONVERSION.NO_CONVERSION,
np.power: CONVERSION.NEW_UNIT,
np.remainder: CONVERSION.UNIT_MATCH,
np.mod: CONVERSION.UNIT_MATCH,
np.fmod: CONVERSION.UNIT_MATCH,
np.divmod: CONVERSION.UNIT_MATCH,
np.absolute: CONVERSION.NO_CONVERSION,
np.fabs: CONVERSION.NO_CONVERSION,
np.rint: CONVERSION.NO_CONVERSION,
np.sign: CONVERSION.NO_CONVERSION,
np.heaviside: CONVERSION.NOT_IMPLEMENTED, # !
np.conj: CONVERSION.NOT_IMPLEMENTED, # !
np.exp: CONVERSION.FLOAT,
np.exp2: CONVERSION.FLOAT,
np.log: CONVERSION.FLOAT,
np.log2: CONVERSION.FLOAT,
np.log10: CONVERSION.FLOAT,
np.expm1: CONVERSION.FLOAT,
np.log1p: CONVERSION.FLOAT,
np.sqrt: CONVERSION.NEW_UNIT,
np.square: CONVERSION.NEW_UNIT,
np.cbrt: CONVERSION.NEW_UNIT,
np.reciprocal: CONVERSION.NEW_UNIT,
# Trigonometric functions
# --------------------------------------------------
np.sin: CONVERSION.FLOAT,
np.cos: CONVERSION.FLOAT,
np.tan: CONVERSION.FLOAT,
np.arcsin: CONVERSION.FLOAT,
np.arccos: CONVERSION.FLOAT,
np.arctan: CONVERSION.FLOAT,
np.arctan2: CONVERSION.FLOAT,
np.hypot: CONVERSION.FLOAT,
np.sinh: CONVERSION.FLOAT,
np.cosh: CONVERSION.FLOAT,
np.tanh: CONVERSION.FLOAT,
np.arcsinh: CONVERSION.FLOAT,
np.arccosh: CONVERSION.FLOAT,
np.arctanh: CONVERSION.FLOAT,
np.deg2rad: CONVERSION.FLOAT,
np.rad2deg: CONVERSION.FLOAT,
# Bit-twiddling functions
# --------------------------------------------------
np.bitwise_and: CONVERSION.NOT_IMPLEMENTED, # Nonsense
np.bitwise_or: CONVERSION.NOT_IMPLEMENTED, # Nonsense
np.bitwise_xor: CONVERSION.NOT_IMPLEMENTED, # Nonsense
np.invert: CONVERSION.NOT_IMPLEMENTED, # Nonsense
np.left_shift: CONVERSION.NOT_IMPLEMENTED, # Nonsense
np.right_shift: CONVERSION.NOT_IMPLEMENTED, # Nonsense
# Comparison functions
# --------------------------------------------------
np.greater: CONVERSION.UNIT_MATCH_NO_OUT_CAST,
np.greater_equal: CONVERSION.UNIT_MATCH_NO_OUT_CAST,
np.less: CONVERSION.UNIT_MATCH_NO_OUT_CAST,
np.less_equal: CONVERSION.UNIT_MATCH_NO_OUT_CAST,
np.not_equal: CONVERSION.UNIT_MATCH_NO_OUT_CAST,
np.equal: CONVERSION.UNIT_MATCH_NO_OUT_CAST,
np.logical_and: CONVERSION.UNIT_MATCH,
np.logical_or: CONVERSION.UNIT_MATCH,
np.logical_xor: CONVERSION.UNIT_MATCH,
np.logical_not: CONVERSION.UNIT_MATCH,
np.maximum: CONVERSION.UNIT_MATCH,
np.minimum: CONVERSION.UNIT_MATCH,
np.fmax: CONVERSION.UNIT_MATCH,
np.fmin: CONVERSION.UNIT_MATCH,
# Floating functions
# --------------------------------------------------
np.isfinite: CONVERSION.NOT_IMPLEMENTED, # ! _T
np.isinf: CONVERSION.NOT_IMPLEMENTED, # ! _T
np.isnan: CONVERSION.NOT_IMPLEMENTED, # ! _T
np.fabs: CONVERSION.NOT_IMPLEMENTED, # ! _
np.signbit: CONVERSION.NOT_IMPLEMENTED, # ! _T
np.copysign: CONVERSION.NOT_IMPLEMENTED, # !
np.nextafter: CONVERSION.NOT_IMPLEMENTED, # !
np.spacing: CONVERSION.NOT_IMPLEMENTED, # !
np.modf: CONVERSION.NOT_IMPLEMENTED, # !
np.ldexp: CONVERSION.NOT_IMPLEMENTED, # !
np.frexp: CONVERSION.NOT_IMPLEMENTED, # !
np.fmod: CONVERSION.NOT_IMPLEMENTED, # !
np.floor: CONVERSION.NOT_IMPLEMENTED, # !
np.ceil: CONVERSION.NO_CONVERSION,
np.trunc: CONVERSION.NO_CONVERSION,
}
##############################################
@classmethod
def from_ndarray(cls, array, prefixed_unit):
# cls._logger.info('UnitValues.__new__ ' + str((cls, array, prefixed_unit)))
# obj = cls(prefixed_unit, array.shape, array.dtype) # Fixme: buffer ???
# obj[...] = array[...]
obj = array.view(UnitValues)
obj._prefixed_unit = prefixed_unit
if isinstance(array, UnitValues):
return array.convert(prefixed_unit)
return obj
##############################################
def __new__(cls,
prefixed_unit,
shape, dtype=float, buffer=None, offset=0, strides=None, order=None):
# Called for explicit constructor
# obj = UnitValues(prefixed_unit, shape)
# cls._logger.info('UnitValues.__new__ ' + str((cls, prefixed_unit, shape, dtype, buffer, offset, strides, order)))
obj = super(UnitValues, cls).__new__(cls, shape, dtype, buffer, offset, strides, order)
# obj = np.asarray(input_array).view(cls)
obj._prefixed_unit = prefixed_unit
return obj
##############################################
def __array_finalize__(self, obj):
# self._logger.info('UnitValues.__new__ ' + '\n {}'.format(obj))
# self is a new object resulting from ndarray.__new__(UnitValues, ...)
# therefore it only has attributes that the ndarray.__new__ constructor gave it
# i.e. those of a standard ndarray.
# We could have got to the ndarray.__new__ call in 3 ways:
# From an explicit constructor - e.g. UnitValues():
# obj is None
# we are in the middle of the UnitValues.__new__ constructor
if obj is None:
return
# From view casting - e.g arr.view(UnitValues):
# obj is arr
# type(obj) can be UnitValues
# From new-from-template - e.g infoarr[:3]
# type(obj) is UnitValues
self._prefixed_unit = getattr(obj, '_prefixed_unit', None) # Fixme: None
##############################################
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
# - "ufunc" is the ufunc object that was called
# - "method" is a string indicating how the ufunc was called, either
# "__call__" to indicate it was called directly,
# or one of its "ufuncs.methods": "reduce", "accumulate", "reduceat", "outer", or "at".
# - "inputs" is a tuple of the input arguments to the ufunc
# - "kwargs" contains any optional or keyword arguments passed to the function.
# This includes any *out* arguments, which are always contained in a tuple.
# ufunc.reduce(a[, axis, dtype, out, keepdims]) Reduces a‘s dimension by one, by applying ufunc along one axis.
# ufunc.accumulate(array[, axis, dtype, out, ...]) Accumulate the result of applying the operator to all elements.
# ufunc.reduceat(a, indices[, axis, dtype, out]) Performs a (local) reduce with specified slices over a single axis.
# ufunc.outer(A, B, **kwargs) Apply the ufunc op to all pairs (a, b) with a in A and b in B.
# ufunc.at(a, indices[, b]) Performs unbuffered in place operation on operand ‘a’ for elements specified by ‘indices’.
# self._logger.info(
# '\n self={}\n ufunc={}\n method={}\n inputs={}\n kwargs={}'
# .format(self, ufunc, method, inputs, kwargs))
# ufunc=
# method=__call__
# inputs=(UnitValues(mV, [0 1 2 3 4 5 6 7 8 9]), 2)
# ufunc=
# method=__call__
# inputs=(UnitValues(mV, [0 1 2 3 4 5 6 7 8 9]),)
# kwargs={}
# ufunc=
# method=__call__
# inputs=(UnitValues(mV, [0 1 2 3 4 5 6 7 8 9]), UnitValues(mV, [0 1 2 3 4 5 6 7 8 9]))
prefixed_unit = self._prefixed_unit
conversion = self.UFUNC_MAP[ufunc]
self._logger.info("Conversion for {} is {}".format(ufunc, conversion))
# Cast inputs to ndarray
args = []
if conversion == self.CONVERSION.NO_CONVERSION:
# should be 1 arg
args = [( input_.as_ndarray(False) if isinstance(input_, UnitValues) else input_ )
for input_ in inputs]
#
elif conversion == self.CONVERSION.FLOAT:
if not prefixed_unit.is_unit_less:
# raise ValueError("Must be unit less")
self._logger.warning("Should be unit less")
args = [( input_.as_ndarray(True) if isinstance(input_, UnitValues) else input_ )
for input_ in inputs]
#
elif conversion in (self.CONVERSION.UNIT_MATCH, self.CONVERSION.UNIT_MATCH_NO_OUT_CAST):
# len(inputs) == 2
other = inputs[1]
if isinstance(other, (UnitValues, UnitValue)):
self._check_unit(other)
args.append(self.as_ndarray())
nd_other = self._convert_value(other)
if isinstance(other, UnitValues):
nd_other = nd_other.as_ndarray()
elif isinstance(other, UnitValue):
nd_other = float(nd_other)
args.append(nd_other)
else:
raise ValueError
#
elif conversion == self.CONVERSION.NEW_UNIT:
if len(inputs) == 1:
#! Fixme: power
if ufunc == np.sqrt:
prefixed_unit = self.unit.sqrt(True)
elif ufunc == np.square:
prefixed_unit = self.unit.square(True)
elif ufunc == np.cbrt:
prefixed_unit = self.unit.cbrt(True)
elif ufunc == np.reciprocal:
prefixed_unit = self.unit.reciprocal(True)
else:
raise NotImplementedError
args.append(self.as_ndarray(True))
elif len(inputs) == 2:
other = inputs[1]
if isinstance(other, (UnitValues, UnitValue)):
if ufunc == np.multiply:
prefixed_unit = self.unit.multiply(other.unit, True)
elif ufunc in (np.divide, np.true_divide, np.floor_divide):
prefixed_unit = self.unit.divide(other.unit, True)
else:
raise NotImplementedError
args.append(self.as_ndarray(True))
if isinstance(other, UnitValue):
args.append(float(other))
else:
args.append(other.as_ndarray(True))
elif ufunc in (np.multiply, np.divide, np.true_divide, np.floor_divide, np.power):
if ufunc == np.power:
prefixed_unit = self.unit.power(other, True)
args.append(self.as_ndarray())
args.append(other)
else:
raise NotImplementedError
else:
raise NotImplementedError
#
else: # self.CONVERSION.NOT_IMPLEMENTED
raise NotImplementedError
# self._logger.info("Output unit is {}".format(prefixed_unit))
# Cast outputs to ndarray
outputs = kwargs.pop('out', None)
if outputs:
out_args = []
for output in outputs:
if isinstance(output, UnitValues):
out_args.append(output.as_ndarray())
else:
out_args.append(output)
kwargs['out'] = tuple(out_args)
else:
outputs = (None,) * ufunc.nout
# Call ufunc
results = super(UnitValues, self).__array_ufunc__(ufunc, method, *args, **kwargs)
if results is NotImplemented:
return NotImplemented
# ensure results is a tuple
if ufunc.nout == 1:
results = (results,)
# Cast results
if conversion in (self.CONVERSION.FLOAT, self.CONVERSION.UNIT_MATCH_NO_OUT_CAST):
# Fixme: ok ???
results = tuple(( result if output is None else output )
for result, output in zip(results, outputs))
else:
results = tuple(( UnitValues.from_ndarray(np.asarray(result), prefixed_unit) if output is None else output )
for result, output in zip(results, outputs))
# list or scalar
return results[0] if len(results) == 1 else results
##############################################
# def __array_wrap__(self, out_array, context=None):
#
# self._logger.info('\n self={}\n out_array={}\n context={}'.format(self, out_array, context))
#
# return super(UnitValues, self).__array_wrap__(out_array, context)
##############################################
def as_ndarray(self, scale=False):
array = self.view(np.ndarray)
if scale:
return array * self.scale
else:
return array
##############################################
def __getitem__(self, _slice):
value = super(UnitValues, self).__getitem__(_slice)
if isinstance(value, UnitValue): # slice
return value
else:
return self._prefixed_unit.new_value(value)
##############################################
def __setitem__(self, _slice, value):
if isinstance(value, UnitValue):
self._check_unit(value)
value = self._convert_value(value).value
elif isinstance(value, UnitValues):
self._check_unit(value)
value = self._convert_value(value)
super(UnitValues, self).__setitem__(_slice, value)
##############################################
def __contains__(self, value):
raise NotImplementedError
##############################################
def __repr__(self):
# return repr(self.as_ndarray())
return '{}({})'.format(self.__class__.__name__, str(self))
##############################################
@property
def prefixed_unit(self):
return self._prefixed_unit
@property
def unit(self):
return self._prefixed_unit.unit
@property
def power(self):
return self._prefixed_unit.power
@property
def scale(self):
return self._prefixed_unit.power.scale
##############################################
def is_same_unit(self, other):
return self._prefixed_unit.is_same_unit(other.prefixed_unit)
##############################################
def _check_unit(self, other):
if not self.is_same_unit(other):
raise UnitError
##############################################
def is_same_power(self, other):
return self._prefixed_unit.is_same_power(other.prefixed_unit)
##############################################
def __eq__(self, other):
"""self == other"""
if isinstance(other, UnitValues):
return self.is_same_unit(other) and self.as_ndarray() == other.as_ndarray()
else:
raise ValueError
##############################################
def _convert_value(self, other):
"""Convert the value of other to the power of self."""
self._check_unit(other)
if self.is_same_power(other):
return other
else:
return other * (other.scale / self.scale) # for numerical precision
##############################################
def __str__(self):
return str(self.as_ndarray()) + '@' + str(self._prefixed_unit)
##############################################
def reciprocal(self):
equivalent_unit = self.unit.reciprocal(prefixed_unit=True)
reciprocal_value = 1. / np.as_ndarray(True)
return self.from_ndarray(reciprocal_value, equivalent_unit)
##############################################
def get_prefixed_unit(self, power=0):
prefixed_unit = PrefixedUnit.from_prefixed_unit(self.unit, power)
if prefixed_unit is not None:
return prefixed_unit
else:
raise NameError("Prefixed unit not found for {} and power {}".format(self, power))
##############################################
def convert(self, prefixed_unit):
"""Convert the value to another power."""
self._prefixed_unit.check_unit(prefixed_unit)
if self._prefixed_unit.is_same_power(prefixed_unit):
return self
else:
value = self.as_ndarray(True) / prefixed_unit.scale
return prefixed_unit.new_value(value)
##############################################
def convert_to_power(self, power=0):
"""Convert the value to another power."""
value = self.as_ndarray(True)
if power != 0:
value /= 10**power
return self.get_prefixed_unit(power).new_value(value)
####################################################################################################
# Reset
PrefixedUnit.__value_ctor__ = UnitValue
_simple_prefixed_unit = PrefixedUnit()
####################################################################################################
class FrequencyMixin:
""" This class implements a frequency mixin. """
##############################################
@property
def period(self):
r""" Return the period :math:`T = \frac{1}{f}`. """
return self.reciprocal()
##############################################
@property
def pulsation(self):
r""" Return the pulsation :math:`\omega = 2\pi f`. """
# Fixme: UnitValues
return float(self * 2 * math.pi)
####################################################################################################
class PeriodMixin:
""" This class implements a period mixin. """
##############################################
@property
def frequency(self):
r""" Return the period :math:`f = \frac{1}{T}`. """
return self.reciprocal()
##############################################
@property
def pulsation(self):
r""" Return the pulsation :math:`\omega = \frac{2\pi}{T}`. """
return self.frequency.pulsation
PySpice-pr-191/PySpice/Unit/__init__.py 0000664 0000000 0000000 00000017666 13654121544 0020012 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
# Note: This module should be outsourced, only code specific to SPICE must remain.
"""This module implements units.
Shortcuts are defined to build unit values easily :
* for each unit prefix, e.g. :func:`pico`, :func:`nano`, :func:`micro`, :func:`milli`, :func:`kilo`,
:func:`mega`, :func:`tera`. These shortcuts return unit less values.
* for each unit and prefix as the concatenation of *u_*, the unit prefix and the
unit suffix, e.g. :func:`u_pV`, :func:`u_nV`, :func:`u_uV` :func:`u_mV`, :func:`u_V`,
:func:`u_kV`, :func:`u_MV`, :func:`u_TV`.
Theses unit value constructors accept int, float, object that can be converted to float,
:class:`UnitValue` instance and an iterable on these types.
A shortcut is defined to check an unit value match a particular unit, e.g. :func:`as_V`. Theses
shortcuts return the value if the unit match else it raises the exception *UnitError*.
A shortcut is defined to access each unit, e.g. :func:`U_V`, :func:`U_A`, :func:`U_s`, :func:`U_Hz`,
:func:`U_Ω`, :func:`U_F`, :func:`U_H.`, as well as for prefixes e.g. :func:`U_mV`.
Some shortcuts have Unicode and ASCII variants:
* For micro, we have the prefix *μ* and *u*.
* For Ohm, we have :func:`u_Ω` and :func:`u_Ohm`.
Some examples of usage:
.. code-block:: python3
foo = kilo(1) # unit less
resistance_unit = U_Ω
resistance1 = u_kΩ(1)
resistance1 = u_kOhm(1) # ASCII variant
resistance1 = 1@u_kΩ # using Python 3.5 syntax
resistance1 = 1 @u_kΩ # space doesn't matter
resistance1 = 1 @ u_kΩ #
resistance2 = as_Ω(resistance1) # check unit
resistances = u_kΩ(range(1, 11)) # same as [u_kΩ(x) for x in range(1, 11)]
resistances = range(1, 11)@u_kΩ # using Python 3.5 syntax
capacitance = u_uF(200)
inductance = u_mH(1)
temperature = u_Degree(25)
voltage = resistance1 * u_mA(1) # compute unit
frequency = u_ms(20).frequency
period = u_Hz(50).period
pulsation = frequency.pulsation
pulsation = period.pulsation
.. warning::
According to the Python `operator precedence
`_, division operators
have a higher priority than the matrix multiplication operator. In consequence you must had
parenthesis to perform something like :code:`(10@u_s) / (2@_us)`.
"""
####################################################################################################
import logging
import sys
from . import Unit as _Unit
from . import SiUnits as _SiUnits
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
_version_info = sys.version_info
_has_matmul = _version_info.major * 10 + _version_info.minor >= 35
if not _has_matmul:
_module_logger.warning("Your Python version doesn't implement @ operator")
####################################################################################################
class UnitValueShorcut:
##############################################
def __init__(self, prefixed_unit):
self._prefixed_unit = prefixed_unit
##############################################
def _new_value(self, other):
return self._prefixed_unit.new_value(other)
##############################################
def __call__(self, other):
"""self(other)"""
return self._new_value(other)
##############################################
def __rmatmul__(self, other):
"""other @ self"""
return self._new_value(other)
####################################################################################################
def _to_ascii(name):
ascii_name = name
for args in (
('μ', 'u'),
('Ω', 'Ohm'),
('°C', 'Degree'),
):
ascii_name = ascii_name.replace(*args)
return ascii_name
def define_shortcut(name, shortcut) :
# ° is illegal in Python 3.5
# see https://docs.python.org/3/reference/lexical_analysis.html#identifiers
# https://www.python.org/dev/peps/pep-3131/
if '°' not in name:
globals()[name] = shortcut
ascii_name = _to_ascii(name)
if ascii_name != name:
globals()[ascii_name] = shortcut
####################################################################################################
# Define shortcuts for unit prefixes : ..., micro, milli, kilo, mega, ...
def _build_prefix_shortcut(unit_prefix):
unit_cls_name = unit_prefix.__class__.__name__
name = unit_cls_name.lower()
prefixed_unit = _Unit.PrefixedUnit(power=unit_prefix)
_Unit.PrefixedUnit.register(prefixed_unit)
shortcut = lambda value: _Unit.UnitValue(prefixed_unit, value)
define_shortcut(name, shortcut)
for unit_prefix in _Unit.UnitPrefixMetaclass.prefix_iter():
if unit_prefix.__class__ != _Unit.ZeroPower:
_build_prefix_shortcut(unit_prefix) # capture unit_prefix
####################################################################################################
# Fixme: better ???
class FrequencyValue(_Unit.UnitValue, _Unit.FrequencyMixin):
pass
# Fixme:
class FrequencyValues(_Unit.UnitValues): # , _Unit.FrequencyMixin
pass
class PeriodValue(_Unit.UnitValue, _Unit.PeriodMixin):
pass
class PeriodValues(_Unit.UnitValues): # , _Unit.PeriodMixin
pass
####################################################################################################
# Define unit shortcuts
def _build_unit_type_shortcut(unit):
name = 'U_' + unit.unit_suffix
define_shortcut(name, unit)
def _build_as_unit_shortcut(unit):
name = 'as_' + unit.unit_suffix
shortcut = unit.validate
define_shortcut(name, shortcut)
def _exec_body(ns, unit_prefix):
ns['__power__'] = unit_prefix
def _build_unit_prefix_shortcut(unit, unit_prefix):
name = 'u_' + str(unit_prefix) + unit.unit_suffix
if unit.__class__ == _SiUnits.Hertz:
value_ctor = FrequencyValue
values_ctor = FrequencyValues
elif unit.__class__ == _SiUnits.Second:
value_ctor = PeriodValue
values_ctor = PeriodValues
else:
value_ctor = _Unit.UnitValue
values_ctor = _Unit.UnitValues
prefixed_unit = _Unit.PrefixedUnit(unit, unit_prefix, value_ctor, values_ctor)
_Unit.PrefixedUnit.register(prefixed_unit)
define_shortcut('U' + name[1:], prefixed_unit)
shortcut = UnitValueShorcut(prefixed_unit)
define_shortcut(name, shortcut)
def _build_unit_shortcut(unit):
_build_as_unit_shortcut(unit)
_build_unit_type_shortcut(unit)
for unit_prefix in _Unit.UnitPrefixMetaclass.prefix_iter():
if unit_prefix.is_defined_in_spice:
_build_unit_prefix_shortcut(unit, unit_prefix)
for unit in _Unit.UnitMetaclass.unit_iter():
if unit.unit_suffix and unit.__class__ not in (_SiUnits.Kilogram,):
# Fixme: kilogram
_build_unit_shortcut(unit)
####################################################################################################
unit_value = _Unit.UnitValue.simple_value
Frequency = u_Hz
Period = u_s
PySpice-pr-191/PySpice/__init__.py.in 0000664 0000000 0000000 00000002225 13654121544 0017461 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
__version__ = '@VERSION@'
__git_tag__ = '@GIT_TAG@'
__git_sha__ = '@GIT_SHA@'
def show_version():
print('PySpice Version {}'.format(__version__))
PySpice-pr-191/README.html 0000664 0000000 0000000 00000040402 13654121544 0015211 0 ustar 00root root 0000000 0000000
PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators
PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators
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')
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
PySpice-pr-191/README.rst 0000664 0000000 0000000 00000017200 13654121544 0015055 0 ustar 00root root 0000000 0000000 .. -*- Mode: rst -*-
.. -*- Mode: rst -*-
..
|PySpiceUrl|
|PySpiceHomePage|_
|PySpiceDoc|_
|PySpice@github|_
|PySpice@readthedocs|_
|PySpice@readthedocs-badge|
|PySpice@pypi|_
.. |PySpiceUrl| replace:: https://pyspice.fabrice-salvaire.fr
.. |PySpiceHomePage| replace:: PySpice Home Page
.. _PySpiceHomePage: https://pyspice.fabrice-salvaire.fr
.. .. |PySpice@readthedocs-badge| image:: https://readthedocs.org/projects/pyspice/badge/?version=latest
.. :target: http://pyspice.readthedocs.org/en/latest
.. |PySpice@github| replace:: https://github.com/FabriceSalvaire/PySpice
.. .. _PySpice@github: https://github.com/FabriceSalvaire/PySpice
.. |PySpice@pypi| replace:: https://pypi.python.org/pypi/PySpice
.. .. _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
.. |Pypi License| image:: https://img.shields.io/pypi/l/PySpice.svg
:target: https://pypi.python.org/pypi/PySpice
: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
.. |Build Status| image:: https://travis-ci.org/FabriceSalvaire/PySpice.svg?branch=master
:target: https://travis-ci.org/FabriceSalvaire/PySpice
:alt: PySpice build status @travis-ci.org
.. |ohloh| image:: https://www.openhub.net/accounts/230426/widgets/account_tiny.gif
:target: https://www.openhub.net/accounts/fabricesalvaire
:alt: Fabrice Salvaire's Ohloh profile
: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 -*-
.. _CFFI: http://cffi.readthedocs.org/en/latest/
.. _Circuit_macros: http://ece.uwaterloo.ca/~aplevich/Circuit_macros
.. _IPython: http://ipython.org
.. _Kicad: http://www.kicad-pcb.org
.. _Matplotlib: http://matplotlib.org
.. _Modelica: http://www.modelica.org
.. _Ngspice: http://ngspice.sourceforge.net
.. _Numpy: http://www.numpy.org
.. _PyPI: https://pypi.python.org/pypi
.. _Pyterate: https://github.com/FabriceSalvaire/Pyterate
.. _Python: http://python.org
.. _Sphinx: http://sphinx-doc.org
.. _Tikz: http://www.texample.net/tikz
.. _Xyce: https://xyce.sandia.gov
.. |CFFI| replace:: CFFI
.. |Circuit_macros| replace:: Circuit_macros
.. |IPython| replace:: IPython
.. |Kicad| replace:: Kicad
.. |Matplotlib| replace:: Matplotlib
.. |Modelica| replace:: Modelica
.. |Ngspice| replace:: Ngspice
.. |Numpy| replace:: Numpy
.. |PyPI| replace:: PyPI
.. |Pyterate| replace:: Pyterate
.. |Python| replace:: Python
.. |Sphinx| replace:: Sphinx
.. |Tikz| replace:: Tikz
.. |Xyce| replace:: Xyce
=====================================================================================
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
====
.. -*- Mode: rst -*-
.. no title here
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. :code:`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. :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`
* 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
.. :ref:`user-faq-page`
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 :code:`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 :code:`u_mV` or compute the value of :code:`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
.. End
.. End
PySpice-pr-191/README.txt 0000664 0000000 0000000 00000003301 13654121544 0015061 0 ustar 00root root 0000000 0000000 .. -*- 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
PySpice-pr-191/bin/ 0000775 0000000 0000000 00000000000 13654121544 0014136 5 ustar 00root root 0000000 0000000 PySpice-pr-191/bin/cir2py 0000775 0000000 0000000 00000004610 13654121544 0015275 0 ustar 00root root 0000000 0000000 #! /usr/bin/env python3
####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
####################################################################################################
####################################################################################################
import argparse
####################################################################################################
import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()
####################################################################################################
from PySpice.Spice.Parser import SpiceParser
####################################################################################################
parser = argparse.ArgumentParser(description='Convert a circuit file to PySpice')
parser.add_argument('circuit_file', # metavar='circuit_file',
help='.cir file')
parser.add_argument('-o', '--output',
default=None,
help='Output file')
parser.add_argument('--ground',
type=int,
default=0,
help='Ground node')
parser.add_argument('--build',
default=False, action='store_true',
help='Build circuit')
args = parser.parse_args()
####################################################################################################
parser = SpiceParser(path=args.circuit_file)
if args.build:
parser.build_circuit()
circuit = parser.to_python_code(ground=args.ground)
if args.output is not None:
with open(args.output, 'w') as f:
f.write(circuit)
else:
print(circuit)
PySpice-pr-191/bower/ 0000775 0000000 0000000 00000000000 13654121544 0014504 5 ustar 00root root 0000000 0000000 PySpice-pr-191/bower/bower.json 0000664 0000000 0000000 00000000716 13654121544 0016521 0 ustar 00root root 0000000 0000000 {
"name": "PySpice",
"version": "0.1.0",
"homepage": "https://github.com/FabriceSalvaire/PySpice",
"authors": [
"Fabrice Salvaire "
],
"description": "A Spice Package for Python",
"main": "",
"keywords": [
""
],
"license": "GPLv3",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"MathJax": "~2.5.3",
"jquery": "~2.1.4"
}
}
PySpice-pr-191/doc/ 0000775 0000000 0000000 00000000000 13654121544 0014133 5 ustar 00root root 0000000 0000000 PySpice-pr-191/doc/datasheets/ 0000775 0000000 0000000 00000000000 13654121544 0016260 5 ustar 00root root 0000000 0000000 PySpice-pr-191/doc/datasheets/1N4148_1N4448.pdf 0000664 0000000 0000000 00000363213 13654121544 0020444 0 ustar 00root root 0000000 0000000 %PDF-1.3
%
1260 0 obj <>
endobj
xref
1260 50
0000000016 00000 n
0000002124 00000 n
0000002258 00000 n
0000002502 00000 n
0000002530 00000 n
0000002575 00000 n
0000002610 00000 n
0000002818 00000 n
0000003012 00000 n
0000003525 00000 n
0000003563 00000 n
0000003641 00000 n
0000003718 00000 n
0000003910 00000 n
0000004631 00000 n
0000005242 00000 n
0000005809 00000 n
0000006408 00000 n
0000006977 00000 n
0000007113 00000 n
0000007557 00000 n
0000007937 00000 n
0000008158 00000 n
0000008496 00000 n
0000008723 00000 n
0000008950 00000 n
0000009545 00000 n
0000010054 00000 n
0000010245 00000 n
0000011066 00000 n
0000011869 00000 n
0000012602 00000 n
0000020597 00000 n
0000020830 00000 n
0000021060 00000 n
0000023731 00000 n
0000023791 00000 n
0000023872 00000 n
0000023961 00000 n
0000024070 00000 n
0000024177 00000 n
0000024278 00000 n
0000024388 00000 n
0000024507 00000 n
0000024615 00000 n
0000024727 00000 n
0000024827 00000 n
0000024924 00000 n
0000001917 00000 n
0000001342 00000 n
trailer
<<0879F2361EC44440AC282DF15550B83E>]>>
startxref
0
%%EOF
1309 0 obj<>stream
<ዼ ^C1ˏ$A>
n髀b+®S2;Tހ#ǛAN9\vڜ3L-uHW*wi3FLyoӤ20Gj*-7p/j7km(
k칈.CԶi"9+0y#<9dDVJn-A4`SzU7ѾFG~2K0^8y"
omL?!0KiESO@_+[ĸ[ƮhA"clpܞ)رũdqY8J&f"3j.FS31$7FL71E[e
I̠b
5IZ#iJx܇OBo0TK@kh[ӆygh 3?_+Aw/.PKGrٰ
endstream
endobj
1308 0 obj<>/Size 1260/Type/XRef>>stream
x1 0ð4t\GbG&`'MF[!<ăx= 2
endstream
endobj
1261 0 obj<>
endobj
1262 0 obj<>/PageMode/UseOutlines/Names 1265 0 R/Outlines 1295 0 R/Metadata 106 0 R/Pages 105 0 R/OpenAction[1266 0 R/XYZ null null null]/Threads 1263 0 R/StructTreeRoot 108 0 R/Type/Catalog/PageLabels 103 0 R>>
endobj
1263 0 obj[1264 0 R]
endobj
1264 0 obj<>>>
endobj
1265 0 obj<>
endobj
1266 0 obj<>
endobj
1267 0 obj<>/Font<>/ProcSet[/PDF/Text]/ExtGState<>>>
endobj
1268 0 obj<>
endobj
1269 0 obj[/ICCBased 1294 0 R]
endobj
1270 0 obj<>
endobj
1271 0 obj<>
endobj
1272 0 obj<>
endobj
1273 0 obj<>stream
>/f3z"@xϩٖ1m}W{:m=snN6v3r p"rAx5ڠ6m߰nO Cg-DV\7ݘdžH}^.yKws7+3+U'V>!S%B_ָQ :6Mus%'N֦ѦP"*k§D/bŹi|x_K[EmM.8rƐ'wL/,bL
pȶ <Ij.vrHy
<ڭy :r}5YBg?9xva%`P-CAF,[9leϖA"a >. =:iM|2}Ӥ|ڑD#N7C)P>*%Ҍ}LNFuIɞV efڡwjaʊ6#?]#
S2kėJV.eG{;ԝn䷿l9irgUGY*%hQ -
endstream
endobj
1274 0 obj<>stream
?#YXYm7:K1<9آMrIBj2ImI+$!'T{z".J