From 95844b62d397dcef0839e23981771e832ee3e127 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 01:12:55 +0000 Subject: [PATCH] chore(third-party-sync): update pyspice to master@fe08718d5a1a --- .../PySpice-org/PySpice/.gitattributes | 9 + .../PySpice/.github/CODE_OF_CONDUCT.md | 46 + .../PySpice/.github/CONTRIBUTING.md | 3 + .../PySpice/.github/ISSUE_TEMPLATE.md | 7 + .../PySpice/.github/PULL_REQUEST_TEMPLATE.md | 4 + .../PySpice-org/PySpice/.github/SUPPORT.md | 3 + .../.github/workflows/codeql-analysis.yml | 71 + .../.github/workflows/pyspice-test.yml | 43 + third_party/PySpice-org/PySpice/.gitignore | 130 ++ third_party/PySpice-org/PySpice/.gitmodules | 6 + third_party/PySpice-org/PySpice/.travis.yml | 88 + .../PySpice-org/PySpice/CONTRIBUTORS.md | 9 + third_party/PySpice-org/PySpice/GPL-V3.0.txt | 674 ++++++ third_party/PySpice-org/PySpice/LICENSE.txt | 674 ++++++ third_party/PySpice-org/PySpice/MANIFEST.in | 34 + .../PySpice/PySpice/Config/ConfigInstall.py | 65 + .../PySpice/PySpice/Config/__init__.py | 0 .../PySpice/PySpice/Config/logging.yml | 44 + .../PySpice/PySpice/DeviceLibrary/__init__.py | 106 + .../PySpice/PySpice/Doc/ExampleTools.py | 48 + .../PySpice/PySpice/Doc/__init__.py | 0 .../PySpice/PySpice/Logging/Logging.py | 69 + .../PySpice/PySpice/Logging/__init__.py | 0 .../PySpice/PySpice/Math/Calculus.py | 164 ++ .../PySpice/PySpice/Math/__init__.py | 46 + .../PySpice/Physics/MaterialProperties.py | 40 + .../PySpice/Physics/PhysicalConstants.py | 64 + .../PySpice/PySpice/Physics/Resistor.py | 30 + .../PySpice/PySpice/Physics/SemiConductor.py | 66 + .../PySpice/PySpice/Physics/__init__.py | 0 .../PySpice/PySpice/Plot/BodeDiagram.py | 60 + .../PySpice/PySpice/Plot/__init__.py | 0 .../PySpice-org/PySpice/PySpice/Probe/Plot.py | 43 + .../PySpice/PySpice/Probe/WaveForm.py | 477 ++++ .../PySpice/PySpice/Probe/__init__.py | 0 .../PySpice/PySpice/Scripts/__init__.py | 0 .../PySpice/PySpice/Scripts/cir2py.py | 70 + .../Scripts/pyspice_post_installation.py | 470 ++++ .../PySpice/PySpice/Spice/BasicElement.py | 1715 +++++++++++++++ .../PySpice/PySpice/Spice/ElementParameter.py | 395 ++++ .../PySpice/PySpice/Spice/Expression/Ast.py | 407 ++++ .../PySpice/Spice/Expression/Parser.py | 346 +++ .../PySpice/Spice/Expression/__init__.py | 19 + .../PySpice/PySpice/Spice/HighLevelElement.py | 865 ++++++++ .../PySpice/PySpice/Spice/Library.py | 150 ++ .../PySpice/PySpice/Spice/Netlist.py | 1292 +++++++++++ .../PySpice/PySpice/Spice/NgSpice/RawFile.py | 234 ++ .../PySpice/PySpice/Spice/NgSpice/Server.py | 162 ++ .../PySpice/PySpice/Spice/NgSpice/Shared.py | 1324 +++++++++++ .../PySpice/Spice/NgSpice/Simulation.py | 127 ++ .../PySpice/Spice/NgSpice/SimulationType.py | 90 + .../PySpice/PySpice/Spice/NgSpice/__init__.py | 23 + .../PySpice/PySpice/Spice/NgSpice/api.h | 76 + .../PySpice/PySpice/Spice/Parser.py | 1057 +++++++++ .../PySpice/PySpice/Spice/Parser_jmgc.py | 1486 +++++++++++++ .../PySpice/PySpice/Spice/RawFile.py | 417 ++++ .../PySpice/PySpice/Spice/Simulation.py | 1236 +++++++++++ .../PySpice/PySpice/Spice/Xyce/RawFile.py | 175 ++ .../PySpice/PySpice/Spice/Xyce/Server.py | 140 ++ .../PySpice/PySpice/Spice/Xyce/Simulation.py | 73 + .../PySpice/PySpice/Spice/Xyce/__init__.py | 0 .../PySpice/PySpice/Spice/__init__.py | 75 + .../PySpice/PySpice/Tools/EnumFactory.py | 176 ++ .../PySpice-org/PySpice/PySpice/Tools/File.py | 292 +++ .../PySpice-org/PySpice/PySpice/Tools/Path.py | 53 + .../PySpice/PySpice/Tools/StringTools.py | 84 + .../PySpice/PySpice/Tools/__init__.py | 0 .../PySpice/PySpice/Unit/SiUnits.py | 330 +++ .../PySpice-org/PySpice/PySpice/Unit/Unit.py | 1944 +++++++++++++++++ .../PySpice/PySpice/Unit/__init__.py | 244 +++ .../PySpice-org/PySpice/PySpice/__init__.py | 27 + third_party/PySpice-org/PySpice/README.html | 896 ++++++++ third_party/PySpice-org/PySpice/README.rst | 374 ++++ third_party/PySpice-org/PySpice/README.txt | 114 + .../PySpice/anaconda-recipe/README.md | 3 + .../PySpice/anaconda-recipe/bld.bat | 0 .../PySpice/anaconda-recipe/build.sh | 10 + .../PySpice/anaconda-recipe/meta.yaml | 80 + third_party/PySpice-org/PySpice/bin/cir2py | 24 + .../PySpice/bin/pyspice-post-installation | 24 + .../PySpice/doc/datasheets/1N4148_1N4448.pdf | Bin 0 -> 124555 bytes .../PySpice/doc/datasheets/1N5919B.pdf | Bin 0 -> 108074 bytes .../PySpice/doc/datasheets/2N2222A.pdf | Bin 0 -> 69501 bytes .../PySpice/doc/datasheets/BAV20.pdf | Bin 0 -> 210933 bytes .../PySpice/doc/logo/make-logo-png | 12 + .../PySpice/doc/logo/svg/logo-v1.svg | 257 +++ .../PySpice/doc/logo/svg/logo-v2.svg | 268 +++ .../doc/pelican/content/pages/legal_notice.md | 21 + .../doc/pelican/content/pages/page-404.md | 9 + .../PySpice/doc/pelican/pelicanconf.py | 828 +++++++ .../PySpice/doc/pelican/publishconf.py | 44 + .../PySpice/doc/pelican/src/contact.html | 10 + .../doc/pelican/src/documentation.html | 49 + .../PySpice/doc/pelican/src/get-help.html | 33 + .../PySpice-org/PySpice/doc/pelican/tasks.py | 152 ++ .../PySpice/doc/pelican/theme/.eslintrc.json | 233 ++ .../PySpice/doc/pelican/theme/browserslist | 15 + .../PySpice/doc/pelican/theme/gulpfile.js | 213 ++ .../PySpice/doc/pelican/theme/package.json | 37 + .../doc/pelican/theme/scss/addons/_all.scss | 18 + .../pelican/theme/scss/addons/_browser.scss | 76 + .../PySpice/doc/pelican/theme/scss/base.scss | 37 + .../doc/pelican/theme/scss/layout/_all.scss | 36 + .../theme/scss/layout/_breadcrumb.scss | 124 ++ .../doc/pelican/theme/scss/layout/_codes.scss | 11 + .../pelican/theme/scss/layout/_content.scss | 49 + .../pelican/theme/scss/layout/_footer.scss | 34 + .../pelican/theme/scss/layout/_gabriela.scss | 6 + .../pelican/theme/scss/layout/_header.scss | 78 + .../pelican/theme/scss/layout/_images.scss | 30 + .../pelican/theme/scss/layout/_roboto.scss | 282 +++ .../doc/pelican/theme/scss/libs/_all.scss | 22 + .../doc/pelican/theme/scss/mixins/_all.scss | 22 + .../pelican/theme/scss/mixins/_colours.scss | 11 + .../pelican/theme/scss/mixins/_functions.scss | 12 + .../theme/scss/mixins/_material_design.scss | 6 + .../doc/pelican/theme/scss/mixins/_other.scss | 25 + .../pelican/theme/scss/mixins/_zindex.scss | 31 + .../doc/pelican/theme/scss/settings/_all.scss | 24 + .../theme/scss/settings/_bootstrap4.scss | 21 + .../pelican/theme/scss/settings/_custom.scss | 34 + .../theme/scss/settings/_fontawesome_v5.scss | 2 + .../theme/scss/settings/_pre_bootstrap.scss | 8 + .../doc/pelican/theme/scss/sites/_all.scss | 50 + .../pelican/theme/static/webfonts/setup.sh | 1 + .../doc/pelican/theme/templates/404.html | 8 + .../doc/pelican/theme/templates/archives.html | 15 + .../doc/pelican/theme/templates/article.html | 69 + .../doc/pelican/theme/templates/articles.html | 41 + .../doc/pelican/theme/templates/author.html | 9 + .../doc/pelican/theme/templates/authors.html | 13 + .../doc/pelican/theme/templates/base.html | 109 + .../pelican/theme/templates/base_root.html | 131 ++ .../pelican/theme/templates/categories.html | 13 + .../doc/pelican/theme/templates/category.html | 9 + .../pelican/theme/templates/gosquared.html | 15 + .../doc/pelican/theme/templates/index.html | 6 + .../doc/pelican/theme/templates/page.html | 29 + .../pelican/theme/templates/pagination.html | 12 + .../theme/templates/period_archives.html | 15 + .../doc/pelican/theme/templates/tag.html | 8 + .../doc/pelican/theme/templates/tags.html | 13 + .../pelican/theme/templates/translations.html | 18 + .../PySpice/doc/sphinx/Makefile.sphinx | 139 ++ .../PySpice-org/PySpice/doc/sphinx/make-html | 5 + .../doc/sphinx/source/abbreviation.txt | 31 + .../doc/sphinx/source/bibliography.rst | 33 + .../PySpice/doc/sphinx/source/conf.py | 348 +++ .../doc/sphinx/source/design-notes.rst | 7 + .../PySpice/doc/sphinx/source/development.rst | 32 + .../PySpice/doc/sphinx/source/donate.rst | 27 + .../sphinx/source/example-introduction.rst | 175 ++ .../doc/sphinx/source/example-whish-list.rst | 97 + .../PySpice/doc/sphinx/source/faq.rst | 163 ++ .../doc/sphinx/source/how-to-refer.rst | 30 + .../PySpice/doc/sphinx/source/index.rst | 171 ++ .../doc/sphinx/source/installation.rst | 333 +++ .../PySpice/doc/sphinx/source/news.rst | 7 + .../PySpice/doc/sphinx/source/news.txt | 191 ++ .../PySpice/doc/sphinx/source/on-the-web.rst | 24 + .../PySpice/doc/sphinx/source/overview.rst | 204 ++ .../doc/sphinx/source/project-links.txt | 43 + .../doc/sphinx/source/reference-manual.rst | 31 + .../doc/sphinx/source/related-projects.rst | 62 + .../PySpice/doc/sphinx/source/roadmap.rst | 46 + .../PySpice/doc/sphinx/source/simulators.rst | 76 + .../source/themes/PySpiceRtd/layout.html | 55 + .../themes/PySpiceRtd/static/css/PySpice.css | 119 + .../themes/PySpiceRtd/static/css/theme.css | 5 + .../themes/PySpiceRtd/static/getthecode.js | 27 + .../themes/PySpiceRtd/static/jquery.min.js | 5 + .../source/themes/PySpiceRtd/theme.conf | 15 + .../PySpice/examples/.travis/travis.py | 3 + .../PySpice-org/PySpice/examples/Settings.py | 12 + .../examples/advanced-usages/index.rst | 5 + .../internal-device-parameters.py | 89 + .../PySpice/examples/analyses/analyses.py | 196 ++ .../examples/analyses/pole_zero_analysis.py | 59 + .../examples/analyses/test_pole_zero.cir | 11 + .../PySpice/examples/basic-usages/index.rst | 7 + .../basic-usages/netlist-manipulations.py | 70 + .../examples/basic-usages/raw-spice.py | 36 + .../examples/basic-usages/subcircuit.py | 62 + .../PySpice/examples/basic-usages/unit.py | 86 + .../c-examples/ngspice-shared/Makefile | 11 + .../ngspice-shared/sharedspice-essential.h | 72 + .../ngspice-shared/test-external-source.c | 291 +++ .../c-examples/ngspice-shared/test-module.py | 68 + .../examples/c-examples/ngspice-shared/test.c | 269 +++ .../c-examples/ngspice-shared/test.py | 225 ++ .../c-examples/ngspice_cb/compile-linux.txt | 31 + .../ngspice_cb/examples/adder_mos.cir | 66 + .../ngspice_cb/include/sharedspice.h | 328 +++ .../ngspice_cb/ng_shared_test/compile.txt | 1 + .../ngspice_cb/ng_shared_test/main.c | 613 ++++++ .../ngspice_cb/ng_shared_test_sl/main.c | 362 +++ .../ngspice_cb/ngspice_cb_contents.txt | 23 + .../PySpice/examples/data-analysis/fft.py | 103 + .../PySpice/examples/diode/RingModulator.py | 41 + .../diode/diode-characteristic-curve.py | 203 ++ .../examples/diode/diode-recovery-time.py | 158 ++ .../PySpice/examples/diode/index.rst | 7 + .../diode-characteristic-curve-circuit-ac.m4 | 15 + ...iode-characteristic-curve-circuit-pulse.m4 | 15 + .../m4/diode-characteristic-curve-circuit.m4 | 15 + .../diode/m4/full-wave-rectification.m4 | 38 + .../diode/m4/half-wave-rectification.m4 | 16 + .../diode/m4/voltage-multiplier-circuit.m4 | 23 + ...ener-diode-characteristic-curve-circuit.m4 | 15 + .../examples/diode/notes/rectification1.png | Bin 0 -> 34063 bytes .../PySpice/examples/diode/notes/schema1.png | Bin 0 -> 78789 bytes .../PySpice/examples/diode/notes/schema2.png | Bin 0 -> 80698 bytes .../PySpice/examples/diode/rectification.py | 153 ++ .../PySpice/examples/diode/ring-modulator.py | 93 + .../PySpice/examples/diode/tex/diode.tex | 63 + .../examples/diode/voltage-multiplier.py | 71 + .../diode/zener-characteristic-curve.py | 79 + .../examples/electricity/three-phase.py | 99 + .../examples/filter/low-pass-rc-filter.py | 58 + .../examples/filter/m4/low-pass-rc-filter.m4 | 13 + .../examples/filter/m4/low-pass-rlc-filter.m4 | 15 + .../filter/m4/pass-band-rlc-filter.m4 | 14 + .../PySpice/examples/filter/rlc-filter.py | 123 ++ .../examples/fundamental-laws/index.rst | 61 + .../fundamental-laws/m4/current-divider.m4 | 21 + .../m4/millman-theorem-with-current-source.m4 | 33 + .../fundamental-laws/m4/millman-theorem.m4 | 24 + .../m4/thevenin-norton-theorem.m4 | 27 + .../fundamental-laws/m4/voltage-divider.m4 | 12 + .../fundamental-laws/millman-theorem.py | 80 + .../thevenin-norton-theorem.py | 90 + .../voltage-current-divider.py | 78 + .../PySpice-org/PySpice/examples/index.rst | 11 + .../libraries/diode/general-purpose/BAV21.lib | 38 + .../diode/general-purpose/BAV21.lib@xyce | 38 + .../libraries/diode/schottky/1N5822.lib | 23 + .../libraries/diode/switching/1N4148.lib | 48 + .../libraries/diode/zener/1N5919B.lib | 43 + .../examples/libraries/mosfet/irf150.lib | 64 + .../operational-amplifier/LMV981.MOD | 228 ++ .../examples/libraries/transistor/2N2222A.lib | 29 + .../transistor/ptm_65nm_nmos_bulk.mod | 93 + .../transistor/ptm_65nm_pmos_bulk.mod | 93 + .../ngspice-shared/external-source.py | 100 + .../PySpice/examples/ngspice-shared/index.rst | 12 + .../ngspice-shared/ngspice-interpreter.py | 87 + .../OperationalAmplifier-api-brainstorming.py | 57 + .../OperationalAmplifier.py | 59 + .../examples/operational-amplifier/astable.py | 56 + .../operational-amplifier.py | 50 + .../examples/passive/capacitor-inductor.py | 115 + .../passive/m4/capacitor_and_inductor.m4 | 28 + .../examples/power-supplies/HP54501A.py | 28 + ...tive-half-wave-rectification-post-zener.py | 75 + ...itive-half-wave-rectification-pre-zener.py | 81 + .../examples/power-supplies/hp54501a-cem.py | 69 + ...f-wave-rectification-post-zener-circuit.m4 | 26 + ...lf-wave-rectification-pre-zener-circuit.m4 | 39 + .../pyterate-templates/document.jinja2 | 6 + .../PySpice/examples/relay/relay.py | 72 + .../examples/resistor/m4/resistor-bridge.m4 | 27 + .../examples/resistor/m4/voltage-divider.m4 | 13 + .../examples/resistor/resistor-bridge.py | 31 + .../examples/resistor/voltage-divider.py | 36 + .../PySpice-org/PySpice/examples/run-examples | 43 + .../spice-examples/ac-coupled-amplifier.cir | 18 + .../ac-coupled-transistor-amplifier.cir | 25 + .../examples/spice-examples/astable.cir | 33 + .../examples/spice-examples/diode-ac.cir | 11 + .../PySpice/examples/spice-examples/diode.cir | 18 + .../spice-examples/low-pass-rc-filter.cir | 12 + .../operational-amplifier-model-1.cir | 31 + .../operational-amplifier-model-2.cir | 56 + .../spice-examples/resistor-bridge.cir | 15 + .../small-signal-amplifier-with-diodes.cir | 26 + .../spice-examples/small-signal-amplifier.cir | 16 + .../transform-less-power-supply.cir | 48 + .../examples/spice-examples/transistor.cir | 14 + .../examples/spice-examples/transistor2.cir | 9 + .../spice-examples/voltage-divider.cir | 7 + .../spice-parser/bootstrap-example.py | 58 + .../PySpice/examples/spice-parser/index.rst | 3 + .../examples/spice-parser/kicad-example.py | 151 ++ .../kicad-pyspice-example-cache.lib | 125 ++ .../kicad-pyspice-example-rescue.lib | 26 + .../kicad-pyspice-example.cir | 17 + .../kicad-pyspice-example.pro | 56 + .../kicad-pyspice-example.sch | 229 ++ .../kicad-pyspice-example.sch.svg | 1565 +++++++++++++ .../kicad-spice-example/README.txt | 3 + .../kicad-spice-example-cache.lib | 126 ++ .../kicad-spice-example-rescue.lib | 38 + .../kicad-spice-example.pro | 74 + .../kicad-spice-example.sch | 251 +++ .../netlist/kicad-spice-example.default.cir | 28 + .../kicad-spice-example.number-node.cir | 28 + .../netlist/kicad-spice-example.using-X.cir | 28 + .../kicad-spice-example/spice/LMV981.MOD | 228 ++ .../kicad-spice-example/spice/components.cir | 31 + .../kicad-spice-example/spice/example.cir | 25 + .../kicad-spice-example/spice/run.sh | 4 + .../switched-power-supplies/buck-converter.py | 118 + .../examples/transformer/Transformer.py | 54 + .../transformer/transformer-example.py | 55 + .../transistor/ac-coupled-amplifier.py | 67 + .../transistor/m4/ac-coupled-amplifier.m4 | 33 + .../examples/transistor/m4/transistor.m4 | 22 + .../examples/transistor/nmos-transistor.py | 64 + .../PySpice/examples/transistor/transistor.py | 146 ++ .../examples/transmission-lines/time-delay.py | 43 + .../PySpice-org/PySpice/gh-pages/README.rst | 7 + .../PySpice/gh-pages/html-rsync-filter.txt | 7 + .../PySpice/gh-pages/init-gh-pages | 27 + .../PySpice/gh-pages/update-gh-pages | 55 + third_party/PySpice-org/PySpice/invoke.yaml | 4 + .../PySpice-org/PySpice/issues/issue-107.py | 67 + .../PySpice-org/PySpice/issues/issue-11.py | 28 + .../PySpice-org/PySpice/issues/issue-130.py | 17 + .../PySpice-org/PySpice/issues/issue-133.py | 41 + .../PySpice-org/PySpice/issues/issue-142.py | 55 + .../PySpice-org/PySpice/issues/issue-150.py | 25 + .../PySpice-org/PySpice/issues/issue-157-2.py | 40 + .../PySpice-org/PySpice/issues/issue-157.py | 42 + .../PySpice-org/PySpice/issues/issue-164.py | 73 + .../PySpice-org/PySpice/issues/issue-167.lib | 22 + .../PySpice-org/PySpice/issues/issue-167.py | 70 + .../PySpice-org/PySpice/issues/issue-169.py | 16 + .../PySpice-org/PySpice/issues/issue-172.cir | 9 + .../PySpice-org/PySpice/issues/issue-172.py | 36 + .../PySpice/issues/issue-182-lf411c.mod | 43 + .../PySpice-org/PySpice/issues/issue-6.py | 31 + .../PySpice/issues/two-stage-amp.cir | 19 + .../PySpice-org/PySpice/requirements-dev.txt | 30 + .../PySpice-org/PySpice/requirements.txt | 6 + third_party/PySpice-org/PySpice/setup.cfg | 90 + third_party/PySpice-org/PySpice/setup.py | 50 + third_party/PySpice-org/PySpice/setup_data.py | 77 + .../PySpice-org/PySpice/tasks/__init__.py | 51 + .../PySpice-org/PySpice/tasks/anaconda.py | 54 + .../PySpice/tasks/cir2py-libraries | 1 + .../PySpice-org/PySpice/tasks/clean.py | 65 + third_party/PySpice-org/PySpice/tasks/doc.py | 157 ++ .../PySpice-org/PySpice/tasks/fix-dunder.py | 108 + third_party/PySpice-org/PySpice/tasks/git.py | 105 + .../PySpice-org/PySpice/tasks/github.py | 48 + .../PySpice-org/PySpice/tasks/ngspice.py | 225 ++ .../PySpice-org/PySpice/tasks/release.py | 195 ++ third_party/PySpice-org/PySpice/tasks/test.py | 226 ++ third_party/PySpice-org/PySpice/tox.ini | 11 + .../PySpice/unit-test-todo/test_file.py | 10 + .../PySpice/unit-test-todo/test_netlist.py | 44 + .../PySpice/unit-test-todo/test_pipe.py | 39 + .../PySpice/unit-test/Math/test_Calculus.py | 178 ++ .../PySpice/unit-test/Pickle/test_Pickle.py | 71 + .../PySpice/unit-test/Probe/test_WaveForm.py | 144 ++ .../unit-test/Spice/test_BasicElement.py | 67 + .../unit-test/Spice/test_Expression.py | 86 + .../unit-test/Spice/test_HighLevelElement.py | 77 + .../PySpice/unit-test/Spice/test_Netlist.py | 213 ++ .../unit-test/SpiceParser/hsada4077.cir | 155 ++ .../PySpice/unit-test/SpiceParser/hsop77.cir | 191 ++ .../unit-test/SpiceParser/mosdriver.lib | 14 + .../unit-test/SpiceParser/test_SpiceParser.py | 80 + .../PySpice/unit-test/Unit/test_Units.py | 323 +++ tools/third_party_sync/repos.lock.json | 10 +- 365 files changed, 41126 insertions(+), 1 deletion(-) create mode 100644 third_party/PySpice-org/PySpice/.gitattributes create mode 100644 third_party/PySpice-org/PySpice/.github/CODE_OF_CONDUCT.md create mode 100644 third_party/PySpice-org/PySpice/.github/CONTRIBUTING.md create mode 100644 third_party/PySpice-org/PySpice/.github/ISSUE_TEMPLATE.md create mode 100644 third_party/PySpice-org/PySpice/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 third_party/PySpice-org/PySpice/.github/SUPPORT.md create mode 100644 third_party/PySpice-org/PySpice/.github/workflows/codeql-analysis.yml create mode 100644 third_party/PySpice-org/PySpice/.github/workflows/pyspice-test.yml create mode 100644 third_party/PySpice-org/PySpice/.gitignore create mode 100644 third_party/PySpice-org/PySpice/.gitmodules create mode 100644 third_party/PySpice-org/PySpice/.travis.yml create mode 100644 third_party/PySpice-org/PySpice/CONTRIBUTORS.md create mode 100644 third_party/PySpice-org/PySpice/GPL-V3.0.txt create mode 100644 third_party/PySpice-org/PySpice/LICENSE.txt create mode 100644 third_party/PySpice-org/PySpice/MANIFEST.in create mode 100644 third_party/PySpice-org/PySpice/PySpice/Config/ConfigInstall.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Config/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Config/logging.yml create mode 100644 third_party/PySpice-org/PySpice/PySpice/DeviceLibrary/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Doc/ExampleTools.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Doc/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Logging/Logging.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Logging/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Math/Calculus.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Math/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Physics/MaterialProperties.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Physics/PhysicalConstants.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Physics/Resistor.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Physics/SemiConductor.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Physics/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Plot/BodeDiagram.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Plot/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Probe/Plot.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Probe/WaveForm.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Probe/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Scripts/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Scripts/cir2py.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Scripts/pyspice_post_installation.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/BasicElement.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/ElementParameter.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Ast.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Parser.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Expression/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/HighLevelElement.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Library.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Netlist.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/RawFile.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Server.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Shared.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Simulation.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/SimulationType.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/api.h create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Parser.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Parser_jmgc.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/RawFile.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Simulation.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/RawFile.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Server.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Simulation.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Spice/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Tools/EnumFactory.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Tools/File.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Tools/Path.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Tools/StringTools.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Tools/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Unit/SiUnits.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Unit/Unit.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/Unit/__init__.py create mode 100644 third_party/PySpice-org/PySpice/PySpice/__init__.py create mode 100644 third_party/PySpice-org/PySpice/README.html create mode 100644 third_party/PySpice-org/PySpice/README.rst create mode 100644 third_party/PySpice-org/PySpice/README.txt create mode 100644 third_party/PySpice-org/PySpice/anaconda-recipe/README.md create mode 100644 third_party/PySpice-org/PySpice/anaconda-recipe/bld.bat create mode 100644 third_party/PySpice-org/PySpice/anaconda-recipe/build.sh create mode 100644 third_party/PySpice-org/PySpice/anaconda-recipe/meta.yaml create mode 100644 third_party/PySpice-org/PySpice/bin/cir2py create mode 100644 third_party/PySpice-org/PySpice/bin/pyspice-post-installation create mode 100644 third_party/PySpice-org/PySpice/doc/datasheets/1N4148_1N4448.pdf create mode 100644 third_party/PySpice-org/PySpice/doc/datasheets/1N5919B.pdf create mode 100644 third_party/PySpice-org/PySpice/doc/datasheets/2N2222A.pdf create mode 100644 third_party/PySpice-org/PySpice/doc/datasheets/BAV20.pdf create mode 100644 third_party/PySpice-org/PySpice/doc/logo/make-logo-png create mode 100644 third_party/PySpice-org/PySpice/doc/logo/svg/logo-v1.svg create mode 100644 third_party/PySpice-org/PySpice/doc/logo/svg/logo-v2.svg create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/content/pages/legal_notice.md create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/content/pages/page-404.md create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/pelicanconf.py create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/publishconf.py create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/src/contact.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/src/documentation.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/src/get-help.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/tasks.py create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/.eslintrc.json create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/browserslist create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/gulpfile.js create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/package.json create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_all.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_browser.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/base.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_all.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_breadcrumb.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_codes.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_content.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_footer.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_gabriela.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_header.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_images.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_roboto.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/libs/_all.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_all.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_colours.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_functions.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_material_design.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_other.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_zindex.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_all.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_bootstrap4.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_custom.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_fontawesome_v5.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_pre_bootstrap.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/scss/sites/_all.scss create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/static/webfonts/setup.sh create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/404.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/archives.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/article.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/articles.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/author.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/authors.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base_root.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/categories.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/category.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/gosquared.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/index.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/page.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/pagination.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/period_archives.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tag.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tags.html create mode 100644 third_party/PySpice-org/PySpice/doc/pelican/theme/templates/translations.html create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/Makefile.sphinx create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/make-html create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/abbreviation.txt create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/bibliography.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/conf.py create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/design-notes.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/development.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/donate.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/example-introduction.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/example-whish-list.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/faq.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/how-to-refer.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/index.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/installation.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/news.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/news.txt create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/on-the-web.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/overview.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/project-links.txt create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/reference-manual.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/related-projects.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/roadmap.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/simulators.rst create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/layout.html create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/PySpice.css create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/theme.css create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/getthecode.js create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/jquery.min.js create mode 100644 third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/theme.conf create mode 100644 third_party/PySpice-org/PySpice/examples/.travis/travis.py create mode 100644 third_party/PySpice-org/PySpice/examples/Settings.py create mode 100644 third_party/PySpice-org/PySpice/examples/advanced-usages/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/advanced-usages/internal-device-parameters.py create mode 100644 third_party/PySpice-org/PySpice/examples/analyses/analyses.py create mode 100644 third_party/PySpice-org/PySpice/examples/analyses/pole_zero_analysis.py create mode 100644 third_party/PySpice-org/PySpice/examples/analyses/test_pole_zero.cir create mode 100644 third_party/PySpice-org/PySpice/examples/basic-usages/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/basic-usages/netlist-manipulations.py create mode 100644 third_party/PySpice-org/PySpice/examples/basic-usages/raw-spice.py create mode 100644 third_party/PySpice-org/PySpice/examples/basic-usages/subcircuit.py create mode 100644 third_party/PySpice-org/PySpice/examples/basic-usages/unit.py create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice-shared/Makefile create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice-shared/sharedspice-essential.h create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice-shared/test-external-source.c create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice-shared/test-module.py create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice-shared/test.c create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice-shared/test.py create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/compile-linux.txt create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/examples/adder_mos.cir create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/include/sharedspice.h create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/ng_shared_test/compile.txt create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/ng_shared_test/main.c create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/ng_shared_test_sl/main.c create mode 100644 third_party/PySpice-org/PySpice/examples/c-examples/ngspice_cb/ngspice_cb_contents.txt create mode 100644 third_party/PySpice-org/PySpice/examples/data-analysis/fft.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/RingModulator.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/diode-characteristic-curve.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/diode-recovery-time.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/diode-characteristic-curve-circuit-ac.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/diode-characteristic-curve-circuit-pulse.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/diode-characteristic-curve-circuit.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/full-wave-rectification.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/half-wave-rectification.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/voltage-multiplier-circuit.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/m4/zener-diode-characteristic-curve-circuit.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/diode/notes/rectification1.png create mode 100644 third_party/PySpice-org/PySpice/examples/diode/notes/schema1.png create mode 100644 third_party/PySpice-org/PySpice/examples/diode/notes/schema2.png create mode 100644 third_party/PySpice-org/PySpice/examples/diode/rectification.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/ring-modulator.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/tex/diode.tex create mode 100644 third_party/PySpice-org/PySpice/examples/diode/voltage-multiplier.py create mode 100644 third_party/PySpice-org/PySpice/examples/diode/zener-characteristic-curve.py create mode 100644 third_party/PySpice-org/PySpice/examples/electricity/three-phase.py create mode 100644 third_party/PySpice-org/PySpice/examples/filter/low-pass-rc-filter.py create mode 100644 third_party/PySpice-org/PySpice/examples/filter/m4/low-pass-rc-filter.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/filter/m4/low-pass-rlc-filter.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/filter/m4/pass-band-rlc-filter.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/filter/rlc-filter.py create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/m4/current-divider.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/m4/millman-theorem-with-current-source.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/m4/millman-theorem.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/m4/thevenin-norton-theorem.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/m4/voltage-divider.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/millman-theorem.py create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/thevenin-norton-theorem.py create mode 100644 third_party/PySpice-org/PySpice/examples/fundamental-laws/voltage-current-divider.py create mode 100644 third_party/PySpice-org/PySpice/examples/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/diode/general-purpose/BAV21.lib create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/diode/general-purpose/BAV21.lib@xyce create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/diode/schottky/1N5822.lib create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/diode/switching/1N4148.lib create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/diode/zener/1N5919B.lib create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/mosfet/irf150.lib create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/operational-amplifier/LMV981.MOD create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/transistor/2N2222A.lib create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/transistor/ptm_65nm_nmos_bulk.mod create mode 100644 third_party/PySpice-org/PySpice/examples/libraries/transistor/ptm_65nm_pmos_bulk.mod create mode 100644 third_party/PySpice-org/PySpice/examples/ngspice-shared/external-source.py create mode 100644 third_party/PySpice-org/PySpice/examples/ngspice-shared/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/ngspice-shared/ngspice-interpreter.py create mode 100644 third_party/PySpice-org/PySpice/examples/operational-amplifier/OperationalAmplifier-api-brainstorming.py create mode 100644 third_party/PySpice-org/PySpice/examples/operational-amplifier/OperationalAmplifier.py create mode 100644 third_party/PySpice-org/PySpice/examples/operational-amplifier/astable.py create mode 100644 third_party/PySpice-org/PySpice/examples/operational-amplifier/operational-amplifier.py create mode 100644 third_party/PySpice-org/PySpice/examples/passive/capacitor-inductor.py create mode 100644 third_party/PySpice-org/PySpice/examples/passive/m4/capacitor_and_inductor.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/power-supplies/HP54501A.py create mode 100644 third_party/PySpice-org/PySpice/examples/power-supplies/capacitive-half-wave-rectification-post-zener.py create mode 100644 third_party/PySpice-org/PySpice/examples/power-supplies/capacitive-half-wave-rectification-pre-zener.py create mode 100644 third_party/PySpice-org/PySpice/examples/power-supplies/hp54501a-cem.py create mode 100644 third_party/PySpice-org/PySpice/examples/power-supplies/m4/capacitive-half-wave-rectification-post-zener-circuit.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/power-supplies/m4/capacitive-half-wave-rectification-pre-zener-circuit.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/pyterate-templates/document.jinja2 create mode 100644 third_party/PySpice-org/PySpice/examples/relay/relay.py create mode 100644 third_party/PySpice-org/PySpice/examples/resistor/m4/resistor-bridge.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/resistor/m4/voltage-divider.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/resistor/resistor-bridge.py create mode 100644 third_party/PySpice-org/PySpice/examples/resistor/voltage-divider.py create mode 100644 third_party/PySpice-org/PySpice/examples/run-examples create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/ac-coupled-amplifier.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/ac-coupled-transistor-amplifier.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/astable.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/diode-ac.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/diode.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/low-pass-rc-filter.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/operational-amplifier-model-1.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/operational-amplifier-model-2.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/resistor-bridge.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/small-signal-amplifier-with-diodes.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/small-signal-amplifier.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/transform-less-power-supply.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/transistor.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/transistor2.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-examples/voltage-divider.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/bootstrap-example.py create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/index.rst create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-example.py create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example-cache.lib create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example-rescue.lib create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example.pro create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example.sch create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-pyspice-example/kicad-pyspice-example.sch.svg create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/README.txt create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/kicad-spice-example-cache.lib create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/kicad-spice-example-rescue.lib create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/kicad-spice-example.pro create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/kicad-spice-example.sch create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/netlist/kicad-spice-example.default.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/netlist/kicad-spice-example.number-node.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/netlist/kicad-spice-example.using-X.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/spice/LMV981.MOD create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/spice/components.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/spice/example.cir create mode 100644 third_party/PySpice-org/PySpice/examples/spice-parser/kicad-spice-example/spice/run.sh create mode 100644 third_party/PySpice-org/PySpice/examples/switched-power-supplies/buck-converter.py create mode 100644 third_party/PySpice-org/PySpice/examples/transformer/Transformer.py create mode 100644 third_party/PySpice-org/PySpice/examples/transformer/transformer-example.py create mode 100644 third_party/PySpice-org/PySpice/examples/transistor/ac-coupled-amplifier.py create mode 100644 third_party/PySpice-org/PySpice/examples/transistor/m4/ac-coupled-amplifier.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/transistor/m4/transistor.m4 create mode 100644 third_party/PySpice-org/PySpice/examples/transistor/nmos-transistor.py create mode 100644 third_party/PySpice-org/PySpice/examples/transistor/transistor.py create mode 100644 third_party/PySpice-org/PySpice/examples/transmission-lines/time-delay.py create mode 100644 third_party/PySpice-org/PySpice/gh-pages/README.rst create mode 100644 third_party/PySpice-org/PySpice/gh-pages/html-rsync-filter.txt create mode 100644 third_party/PySpice-org/PySpice/gh-pages/init-gh-pages create mode 100644 third_party/PySpice-org/PySpice/gh-pages/update-gh-pages create mode 100644 third_party/PySpice-org/PySpice/invoke.yaml create mode 100644 third_party/PySpice-org/PySpice/issues/issue-107.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-11.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-130.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-133.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-142.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-150.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-157-2.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-157.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-164.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-167.lib create mode 100644 third_party/PySpice-org/PySpice/issues/issue-167.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-169.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-172.cir create mode 100644 third_party/PySpice-org/PySpice/issues/issue-172.py create mode 100644 third_party/PySpice-org/PySpice/issues/issue-182-lf411c.mod create mode 100644 third_party/PySpice-org/PySpice/issues/issue-6.py create mode 100644 third_party/PySpice-org/PySpice/issues/two-stage-amp.cir create mode 100644 third_party/PySpice-org/PySpice/requirements-dev.txt create mode 100644 third_party/PySpice-org/PySpice/requirements.txt create mode 100644 third_party/PySpice-org/PySpice/setup.cfg create mode 100644 third_party/PySpice-org/PySpice/setup.py create mode 100644 third_party/PySpice-org/PySpice/setup_data.py create mode 100644 third_party/PySpice-org/PySpice/tasks/__init__.py create mode 100644 third_party/PySpice-org/PySpice/tasks/anaconda.py create mode 100644 third_party/PySpice-org/PySpice/tasks/cir2py-libraries create mode 100644 third_party/PySpice-org/PySpice/tasks/clean.py create mode 100644 third_party/PySpice-org/PySpice/tasks/doc.py create mode 100644 third_party/PySpice-org/PySpice/tasks/fix-dunder.py create mode 100644 third_party/PySpice-org/PySpice/tasks/git.py create mode 100644 third_party/PySpice-org/PySpice/tasks/github.py create mode 100644 third_party/PySpice-org/PySpice/tasks/ngspice.py create mode 100644 third_party/PySpice-org/PySpice/tasks/release.py create mode 100644 third_party/PySpice-org/PySpice/tasks/test.py create mode 100644 third_party/PySpice-org/PySpice/tox.ini create mode 100644 third_party/PySpice-org/PySpice/unit-test-todo/test_file.py create mode 100644 third_party/PySpice-org/PySpice/unit-test-todo/test_netlist.py create mode 100644 third_party/PySpice-org/PySpice/unit-test-todo/test_pipe.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Math/test_Calculus.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Pickle/test_Pickle.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Probe/test_WaveForm.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Spice/test_BasicElement.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Spice/test_Expression.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Spice/test_HighLevelElement.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Spice/test_Netlist.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/SpiceParser/hsada4077.cir create mode 100644 third_party/PySpice-org/PySpice/unit-test/SpiceParser/hsop77.cir create mode 100644 third_party/PySpice-org/PySpice/unit-test/SpiceParser/mosdriver.lib create mode 100644 third_party/PySpice-org/PySpice/unit-test/SpiceParser/test_SpiceParser.py create mode 100644 third_party/PySpice-org/PySpice/unit-test/Unit/test_Units.py diff --git a/third_party/PySpice-org/PySpice/.gitattributes b/third_party/PySpice-org/PySpice/.gitattributes new file mode 100644 index 00000000..6b249517 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.gitattributes @@ -0,0 +1,9 @@ +# 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 diff --git a/third_party/PySpice-org/PySpice/.github/CODE_OF_CONDUCT.md b/third_party/PySpice-org/PySpice/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..cbe07d36 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# 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/ diff --git a/third_party/PySpice-org/PySpice/.github/CONTRIBUTING.md b/third_party/PySpice-org/PySpice/.github/CONTRIBUTING.md new file mode 100644 index 00000000..b0c61130 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/CONTRIBUTING.md @@ -0,0 +1,3 @@ +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 diff --git a/third_party/PySpice-org/PySpice/.github/ISSUE_TEMPLATE.md b/third_party/PySpice-org/PySpice/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..d643b265 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +### Environment (OS, Python version, PySpice version, simulator) + +### Expected Behaviour + +### Actual Behaviour + +### Steps to reproduce the behaviour diff --git a/third_party/PySpice-org/PySpice/.github/PULL_REQUEST_TEMPLATE.md b/third_party/PySpice-org/PySpice/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..45d914cc --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +To make it easier to merge your pull request, you should divide your PR into smaller and easier-to-verify units. + +Please do not make a pull requests with a lot of modifications which are difficult to check. If I merge +pull requests blindly then there is a high risk this software will become a mess quickly for everybody. diff --git a/third_party/PySpice-org/PySpice/.github/SUPPORT.md b/third_party/PySpice-org/PySpice/.github/SUPPORT.md new file mode 100644 index 00000000..d279d178 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/SUPPORT.md @@ -0,0 +1,3 @@ +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. diff --git a/third_party/PySpice-org/PySpice/.github/workflows/codeql-analysis.yml b/third_party/PySpice-org/PySpice/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..dbec8e46 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '45 23 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/third_party/PySpice-org/PySpice/.github/workflows/pyspice-test.yml b/third_party/PySpice-org/PySpice/.github/workflows/pyspice-test.yml new file mode 100644 index 00000000..2067ddf7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.github/workflows/pyspice-test.yml @@ -0,0 +1,43 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Pyspice Test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + - name: Install PySpice + run: python setup.py install + # - name: Run Tox + # run: tox -e py + - name: Run Unit-Tests + run: pytest unit-test + # - name: Run examples + # run: pyterate --only-run + # - name: Make examples RST + # run: | + # # --skip-rst --skip-external-figure --skip-notebook + # pyterate --skip-external-figure --skip-notebook diff --git a/third_party/PySpice-org/PySpice/.gitignore b/third_party/PySpice-org/PySpice/.gitignore new file mode 100644 index 00000000..d60b61d0 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.gitignore @@ -0,0 +1,130 @@ +# -*- mode: Conf -*- + +#################################################################################################### + +*.stderr +*.stdout +*/tex/auto +*~ +__pycache__ +du.log +parser.out +parsetab.py + +#################################################################################################### +# +# Secrets +# + +doc/pelican/SECRET_CONFIG.py +tasks/anaconda-login.txt +tasks/SECRET_CONFIG.py + +#################################################################################################### + +MANIFEST + +#################################################################################################### + +.cache/ +.pytest_cache/ +.tox/ +PySpice.egg-info/ +build/ +dist/ + +doc/logo/logo-v1.jpg +doc/logo/logo-v1.png +doc/logo/logo-v2.jpg +doc/logo/logo-v2.png +doc/logo/logo.png + +doc/pelican/output +doc/pelican/pelican-plugins +doc/pelican/pelican-themes +doc/pelican/theme/.yarnclean +doc/pelican/theme/node_modules +doc/pelican/theme/static/css/base.css +doc/pelican/theme/static/css/libs/bootstrap/bootstrap-grid.css +doc/pelican/theme/static/css/libs/bootstrap/bootstrap-reboot.css +doc/pelican/theme/static/css/libs/bootstrap/bootstrap.css +doc/pelican/theme/static/css/libs/fontawesome/brands.css +doc/pelican/theme/static/css/libs/fontawesome/fontawesome.css +doc/pelican/theme/static/css/libs/fontawesome/regular.css +doc/pelican/theme/static/css/libs/fontawesome/solid.css +doc/pelican/theme/static/css/libs/fontawesome/v4-shims.css +doc/pelican/theme/yarn-error.log +doc/pelican/theme/yarn.lock + +doc/sphinx/build/ +doc/sphinx/source/api/ +doc/sphinx/source/examples/ + +#################################################################################################### + +examples/spice-examples/eg6.dat +examples/spice-examples/gnucap.cir +examples/spice-examples/mosfet-characterization.cir +examples/spice-examples/ring-modulator.cir +examples/spice-examples/xyce-error.cir +examples/spice-examples/xyce-error.cir.FD.prn +examples/spice-examples/xyce-raw.data +examples/spice-examples/xyce-test.cir +examples/spice-examples/xyce-test2.cir +examples/spice-examples/xyce-test2.csv + +#################################################################################################### + +no-git + +PySpice/Spice/notes.txt +PySpice/Spice/rf.diff +PySpice/Unit/numpy-notes.txt + +__TRASH__/ + +doc/extern/ +doc/logo/logo-square.png +doc/sphinx/source/-to-be-removed.txt +doc/sphinx/source/_static/stargazer.png + +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/ + +examples/c-examples/ngspice-shared/log +examples/c-examples/ngspice-shared/sharedspice.h + +resources + +unit-test/test.py + +anaconda-recipe/conda-bld +doc/pelican/BUGS.md + +examples/c-examples/ngspice_cb/ng_shared_test/ng_shared_test.cbp +examples/c-examples/ngspice_cb/ng_shared_test/ng_shared_test.depend +examples/c-examples/ngspice_cb/ng_shared_test/ng_shared_test.layout +examples/c-examples/ngspice_cb/ng_shared_test_sl/libngspice.dll.a +examples/c-examples/ngspice_cb/ng_shared_test_sl/ng_shared_test_sl.cbp +examples/c-examples/ngspice_cb/ng_shared_test_sl/ng_shared_test_sl.depend +examples/c-examples/ngspice_cb/ng_shared_test_sl/ng_shared_test_sl.layout +examples/c-examples/ngspice_cb/ng_shared_test_sl_v/ +examples/c-examples/ngspice_cb/ng_shared_test_v/ + +Spice64_dll/ \ No newline at end of file diff --git a/third_party/PySpice-org/PySpice/.gitmodules b/third_party/PySpice-org/PySpice/.gitmodules new file mode 100644 index 00000000..2de491d9 --- /dev/null +++ b/third_party/PySpice-org/PySpice/.gitmodules @@ -0,0 +1,6 @@ +[submodule "pyspice-feedstock"] + path = pyspice-feedstock + url = https://github.com/conda-forge/pyspice-feedstock +[submodule "ngspice-feedstock"] + path = ngspice-feedstock + url = https://github.com/conda-forge/ngspice-feedstock.git diff --git a/third_party/PySpice-org/PySpice/.travis.yml b/third_party/PySpice-org/PySpice/.travis.yml new file mode 100644 index 00000000..42354dea --- /dev/null +++ b/third_party/PySpice-org/PySpice/.travis.yml @@ -0,0 +1,88 @@ +#################################################################################################### +# +# Windows: shell is Git BASH +# +# if [ "$TRAVIS_OS_NAME" = "linux" ]; then ... ; fi +# if [ "$TRAVIS_OS_NAME" = "osx" ]; then ... ; fi +# if [ "$TRAVIS_OS_NAME" = "windows" ]; then ... ; fi +# +#################################################################################################### + +# https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-on-standard-infrastructure +dist: bionic # Ubuntu + +# language: c +cache: + ccache: true + pip: true + directories: + - ngspice-34 + +language: python # this works for Linux but is an error on macOS or Windows + +# python: +# - 3.7 +# - 3.8 + +jobs: + include: + - name: "Python 3.8.0 on Bionic Ubuntu Linux" + python: 3.8 + before_install: + - sudo apt-get -y install ngspice + - sudo apt-get -y install gcc libreadline-dev + # - sudo apt-get -y install libngspice0 # from eoan (19.10) + - name: "Python 3.7.4 on macOS" + os: osx + osx_image: xcode11.2 # Python 3.7.4 running on macOS 10.14.4 Mojave + language: shell + addons: + homebrew: + packages: + - libngspice + - name: "Python 3.8.0 on Windows" + os: windows # Windows 10.0.17134 N/A Build 17134 + language: shell + before_install: + - choco install python --version 3.8.0 + - python -m pip install --upgrade pip + env: PATH=/c/Python38:/c/Python38/Scripts:$PATH + +# 'python' points to Python 2.7 on macOS but points to Python 3.8 on Linux and Windows +# 'python3' is a 'command not found' error on Windows but 'py' works on Windows only + +install: + # all three OSes agree about 'pip3' + - pip3 install --upgrade pip + - pip3 install -r requirements.txt + - pip3 install beautifulsoup4 invoke requests + - pip3 install pytest + - pip3 install pyterate + - pip3 install pint + - pip3 install . + # - python3 setup.py -q install + - echo $TRAVIS_BUILD_DIR + # /home/travis/build/FabriceSalvaire/PySpice + - ls $TRAVIS_BUILD_DIR/ + - ls $TRAVIS_BUILD_DIR/ngspice-34/ + # Note: /bin/mkdir: cannot create directory ‘/usr/local/include/ngspice’: Permission denied + - if [ "$TRAVIS_OS_NAME" = "linux" -a ! -e "ngspice-34/lib" ]; then invoke ngspice.install ; fi + +script: + # - echo $TRAVIS_BUILD_DIR # /home/travis/build/FabriceSalvaire/PySpice + # - echo $PWD # same + - pytest unit-test + - if [ "$TRAVIS_OS_NAME" = "windows" ]; then pyspice-post-installation --install-ngspice-dll; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/ngspice-34/lib ; fi + - pyspice-post-installation --check-install + - if [ "$TRAVIS_OS_NAME" = "windows" ]; then export PYTHONIOENCODING="utf_8"; fi + - invoke test.run-examples + + # - setx PySpiceLibraryPath %SRC_DIR%\examples\libraries # [win] + # - pyterate --config=%SRC_DIR%\examples\Settings.py --only-run # [win] + # run on OSX but zmq.error.ZMQError: Too many open files + # run on Windows but + # circuit.R('load', 'output', circuit.gnd, 50@u_Ω) + # SyntaxError: invalid character in identifier + # and ... + # - PySpiceLogLevel=DEBUG PySpiceLibraryPath=$PWD/examples/libraries pyterate --config=./examples/Settings.py --only-run diff --git a/third_party/PySpice-org/PySpice/CONTRIBUTORS.md b/third_party/PySpice-org/PySpice/CONTRIBUTORS.md new file mode 100644 index 00000000..3fefede7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/CONTRIBUTORS.md @@ -0,0 +1,9 @@ +# Contributors + +In addition to https://github.com/FabriceSalvaire/PySpice/graphs/contributors, some contributors are lost due to git/github folks ... + +* **Peter Garrone** https://github.com/PeterGarrone + + Pole-zero, noise, distorsion, transfer-function analyses + + https://github.com/FabriceSalvaire/PySpice/pull/191 merged in 7aea62b (author lost in github) diff --git a/third_party/PySpice-org/PySpice/GPL-V3.0.txt b/third_party/PySpice-org/PySpice/GPL-V3.0.txt new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/third_party/PySpice-org/PySpice/GPL-V3.0.txt @@ -0,0 +1,674 @@ + 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 +. diff --git a/third_party/PySpice-org/PySpice/LICENSE.txt b/third_party/PySpice-org/PySpice/LICENSE.txt new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/third_party/PySpice-org/PySpice/LICENSE.txt @@ -0,0 +1,674 @@ + 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 +. diff --git a/third_party/PySpice-org/PySpice/MANIFEST.in b/third_party/PySpice-org/PySpice/MANIFEST.in new file mode 100644 index 00000000..4956c94c --- /dev/null +++ b/third_party/PySpice-org/PySpice/MANIFEST.in @@ -0,0 +1,34 @@ +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 bower/bower.json +include invoke.yaml +include make-release.sh +include requirements.txt requirements-dev.txt +include setenv.sh start.sh +include setup_data.py +include tox.ini +recursive-include PySpice *.py *.yml *.h +recursive-include doc * +recursive-include doc/sphinx/source/_static * +recursive-include examples * +recursive-include gh-pages * +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 +prune doc/pelican \ No newline at end of file diff --git a/third_party/PySpice-org/PySpice/PySpice/Config/ConfigInstall.py b/third_party/PySpice-org/PySpice/PySpice/Config/ConfigInstall.py new file mode 100644 index 00000000..b56f8aff --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Config/ConfigInstall.py @@ -0,0 +1,65 @@ +#################################################################################################### + +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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Config/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Config/logging.yml b/third_party/PySpice-org/PySpice/PySpice/Config/logging.yml new file mode 100644 index 00000000..467b4424 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Config/logging.yml @@ -0,0 +1,44 @@ +#################################################################################################### + +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: INFO + # level: WARNING + # level: DEBUG + handlers: [console] + +#################################################################################################### + +# loggers: + +# PySpice: +# level: DEBUG +# #level: INFO +# #level: WARNING +# handlers: [console] diff --git a/third_party/PySpice-org/PySpice/PySpice/DeviceLibrary/__init__.py b/third_party/PySpice-org/PySpice/PySpice/DeviceLibrary/__init__.py new file mode 100644 index 00000000..071405c7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/DeviceLibrary/__init__.py @@ -0,0 +1,106 @@ +#################################################################################################### +# +# 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 . +# +#################################################################################################### + +#################################################################################################### + +class Manufacturer: + + ############################################## + + def __init__(self, + name, + url=None, + ): + + self._name = name + self._url = url + + ############################################## + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @property + def url(self): + return self._url + + @url.setter + def url(self, value): + self._url = value + +#################################################################################################### + +class Footprint: + + ############################################## + + def __init__(self, + name, + ): + + self._name = name + + ############################################## + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + +#################################################################################################### + +class Device: + + ############################################## + + def __init__(self, + name, + manufacturer, + datasheet_url=None, + model_url=None + ): + + # part + # part_number + # footprint + # description + # device_category x/y + # pins + # features / parameters + + self._name = name + + ############################################## + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value diff --git a/third_party/PySpice-org/PySpice/PySpice/Doc/ExampleTools.py b/third_party/PySpice-org/PySpice/PySpice/Doc/ExampleTools.py new file mode 100644 index 00000000..eb0deb19 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Doc/ExampleTools.py @@ -0,0 +1,48 @@ +#################################################################################################### +# +# 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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Doc/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Doc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Logging/Logging.py b/third_party/PySpice-org/PySpice/PySpice/Logging/Logging.py new file mode 100644 index 00000000..c408f552 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Logging/Logging.py @@ -0,0 +1,69 @@ +#################################################################################################### +# +# 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'), Loader=yaml.SafeLoader) + + 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 or ConfigInstall.OS.on_osx: + 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) + if 'PySpiceLogLevel' in os.environ: # used by tools/make-examples + level = getattr(logging, os.environ['PySpiceLogLevel'].upper(), None) + logger.setLevel(level) # level can be int or string + + return logger diff --git a/third_party/PySpice-org/PySpice/PySpice/Logging/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Logging/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Math/Calculus.py b/third_party/PySpice-org/PySpice/PySpice/Math/Calculus.py new file mode 100644 index 00000000..979fe761 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Math/Calculus.py @@ -0,0 +1,164 @@ +#################################################################################################### +# +# 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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Math/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Math/__init__.py new file mode 100644 index 00000000..45367217 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Math/__init__.py @@ -0,0 +1,46 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Physics/MaterialProperties.py b/third_party/PySpice-org/PySpice/PySpice/Physics/MaterialProperties.py new file mode 100644 index 00000000..1f9791f1 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Physics/MaterialProperties.py @@ -0,0 +1,40 @@ +# -*- 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Physics/PhysicalConstants.py b/third_party/PySpice-org/PySpice/PySpice/Physics/PhysicalConstants.py new file mode 100644 index 00000000..406459bf --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Physics/PhysicalConstants.py @@ -0,0 +1,64 @@ +# -*- 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Physics/Resistor.py b/third_party/PySpice-org/PySpice/PySpice/Physics/Resistor.py new file mode 100644 index 00000000..fd5d1617 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Physics/Resistor.py @@ -0,0 +1,30 @@ +#################################################################################################### +# +# 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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Physics/SemiConductor.py b/third_party/PySpice-org/PySpice/PySpice/Physics/SemiConductor.py new file mode 100644 index 00000000..afe8b025 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Physics/SemiConductor.py @@ -0,0 +1,66 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Physics/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Physics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Plot/BodeDiagram.py b/third_party/PySpice-org/PySpice/PySpice/Plot/BodeDiagram.py new file mode 100644 index 00000000..5d13f566 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Plot/BodeDiagram.py @@ -0,0 +1,60 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Plot/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Plot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Probe/Plot.py b/third_party/PySpice-org/PySpice/PySpice/Probe/Plot.py new file mode 100644 index 00000000..42bc4aa3 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Probe/Plot.py @@ -0,0 +1,43 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Probe/WaveForm.py b/third_party/PySpice-org/PySpice/PySpice/Probe/WaveForm.py new file mode 100644 index 00000000..ca92d15f --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Probe/WaveForm.py @@ -0,0 +1,477 @@ +#################################################################################################### +# +# 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. + +""" + +# https://numpy.org/doc/stable/user/basics.subclassing.html#basics-subclassing + +#################################################################################################### + +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, + ): + # Called first + # cls._logger.info(str((cls, prefixed_unit, shape, dtype, buffer, offset, strides, order))) + + # call UnitValues.__new__(...) + obj = super(WaveForm, cls).__new__(cls, prefixed_unit, shape, dtype, buffer, offset, strides, order) + # obj = np.asarray(data).view(cls) + + # extra attributes + obj._name = str(name) + obj._title = title # str(title) + obj._abscissa = abscissa # Numpy array + + return obj + + ############################################## + + def __array_finalize__(self, obj): + # Called after __new__ + # self._logger.info('') + + # Fixme: ??? else _prefixed_unit is not set + super().__array_finalize__(obj) + + # if obj is None: + # return + + # extra attributes + self._name = getattr(obj, 'name', None) + self._title = getattr(obj, 'title', None) + self._abscissa = getattr(obj, 'abscissa', None) + + ############################################## + + # def __init__(self, name, prefixed_unit, shape, + # dtype=float, buffer=None, offset=0, strides=None, order=None, + # title=None, abscissa=None): + # # Called last + # self._logger.info('') + + ############################################## + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + 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): + if value is not None: + self._title = str(value) + else: + self._title = None + + ############################################## + + 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Probe/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Probe/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Scripts/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Scripts/cir2py.py b/third_party/PySpice-org/PySpice/PySpice/Scripts/cir2py.py new file mode 100644 index 00000000..765b8a95 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Scripts/cir2py.py @@ -0,0 +1,70 @@ +#################################################################################################### +# +# 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 + +#################################################################################################### + +def main(): + + 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Scripts/pyspice_post_installation.py b/third_party/PySpice-org/PySpice/PySpice/Scripts/pyspice_post_installation.py new file mode 100644 index 00000000..d9184b02 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Scripts/pyspice_post_installation.py @@ -0,0 +1,470 @@ +#################################################################################################### +# +# PySpice - A Spice Package for Python +# Copyright (C) 2020 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 . +# +#################################################################################################### + +"""Tool to perform post installation. + +""" + +#################################################################################################### + +from pathlib import Path +from zipfile import ZipFile +import argparse +import os +import shutil +import sys +import tempfile + +import requests + +#################################################################################################### + +# Archive: resources/ngspice-31_dll_64.zip +# Spice64_dll/dll-mingw/ +# Spice64_dll/dll-mingw/libgcc_s_seh-1.dll +# Spice64_dll/dll-mingw/libgomp-1.dll +# Spice64_dll/dll-mingw/libwinpthread-1.dll +# Spice64_dll/dll-mingw/msys-ngspice-0.dll +# Spice64_dll/dll-vs/ +# Spice64_dll/dll-vs/ngspice.dll +# Spice64_dll/dll-vs/vcomp140.dll +# Spice64_dll/include/ +# Spice64_dll/include/ngspice/ +# Spice64_dll/include/ngspice/sharedspice.h +# Spice64_dll/lib/ +# Spice64_dll/lib/about-libs.txt +# Spice64_dll/lib/lib-mingw/ +# Spice64_dll/lib/lib-mingw/libngspice.dll.a +# Spice64_dll/lib/lib-mingw/libngspice.la +# Spice64_dll/lib/lib-vs/ +# Spice64_dll/lib/lib-vs/ngspice.exp +# Spice64_dll/lib/lib-vs/ngspice.lib +# Spice64_dll/lib/ngspice/ +# Spice64_dll/lib/ngspice/analog.cm +# Spice64_dll/lib/ngspice/digital.cm +# Spice64_dll/lib/ngspice/spice2poly.cm +# Spice64_dll/lib/ngspice/table.cm +# Spice64_dll/lib/ngspice/xtradev.cm +# Spice64_dll/lib/ngspice/xtraevt.cm +# Spice64_dll/share/ +# Spice64_dll/share/ngspice/ +# Spice64_dll/share/ngspice/scripts/ +# Spice64_dll/share/ngspice/scripts/spinit + +#################################################################################################### + +class CircuitTest: + + ############################################## + + def test_spinit(self): + + from PySpice.Spice.Netlist import Circuit + import PySpice.Unit as U + + circuit = Circuit('Test') + + # Fixme: On Windows + # Supplies reduced to 2.5749% Supplies reduced to 1.7100% Warning: source stepping failed + # doAnalyses: Too many iterations without convergence + source = circuit.V('cc', 'vcc', circuit.gnd, 15@U.u_V) + circuit.R(1, 'output', 'comparator', 1@U.u_kΩ) + circuit.C(1, 'comparator', circuit.gnd, 100@U.u_nF) + circuit.R(2, 'output', 'reference', 100@U.u_kΩ) + circuit.R(3, 'vcc', 'reference', 100@U.u_kΩ) + circuit.R(4, 'reference', circuit.gnd, 100@U.u_kΩ) + # circuit.NonLinearVoltageSource(1, 'output', circuit.gnd, + # expression='V(reference, comparator)', + # table=((-U.micro(1), 0), + # (U.micro(1), source.dc_value)) + # ) + + simulator = circuit.simulator(temperature=25, nominal_temperature=25) + simulator.initial_condition(comparator=0) # Fixme: simulator.nodes.comparator == 0 + analysis = simulator.transient(step_time=1@U.u_us, end_time=500@U.u_us) + + if (len(analysis.output)) < 500: + raise NameError('Simualtion failed') + +#################################################################################################### + +class PySpicePostInstallation: + + GITHUB_URL = 'https://github.com/FabriceSalvaire/PySpice' + + NGSPICE_BASE_URL = 'https://sourceforge.net/projects/ngspice/files' + NGSPICE_RELEASE_URL = NGSPICE_BASE_URL + '/ng-spice-rework' + NGSPICE_WINDOWS_DLL_URL = NGSPICE_RELEASE_URL + '/{0}/ngspice-{0}_dll_64.zip' + NGSPICE_WINDOWS_DLL_OLD_URL = NGSPICE_RELEASE_URL + '/old-releases/{0}/ngspice-{0}_dll_64.zip' + NGSPICE_MANUAL_URL = NGSPICE_RELEASE_URL + '/{0}/ngspice-{0}-manual.pdf/download' + NGSPICE_MANUAL_OLD_URL = NGSPICE_RELEASE_URL + '/old-releases/{0}/ngspice-{0}-manual.pdf/download' + + ############################################## + + def run(self): + + parser = argparse.ArgumentParser(description='Tool to perform PySpice Post Installation.') + + parser.add_argument( + '--ngspice-version', + type=int, default=None, + help='NgSpice version to install', + ) + + parser.add_argument( + '--install-ngspice-dll', + action='store_true', + help='install Windows DLL', + ) + + parser.add_argument( + '--force-install-ngspice-dll', + action='store_true', + help='force DLL installation (for debug only)', + ) + + parser.add_argument( + '--download-ngspice-manual', + action='store_true', + help='download Ngspice manual', + ) + + parser.add_argument( + '--check-install', + action='store_true', + help='check installation', + ) + + parser.add_argument( + '--download-example', + action='store_true', + help='download examples', + ) + + self._args = parser.parse_args() + + count = 0 + if self._args.install_ngspice_dll or self._args.force_install_ngspice_dll: + self.install_ngspice_dll() + count += 1 + + if self._args.check_install: + self.check_installation() + count += 1 + + if self._args.download_example: + self.download_example() + count += 1 + + if self._args.download_ngspice_manual: + self.download_ngspice_manual() + count += 1 + + if not count: + parser.print_help() + + ############################################## + + def _download_file(self, url, dst_path): + print('Get {} ... -> {}'.format(url, dst_path)) + response = requests.get(url, allow_redirects=True) + if response.status_code != requests.codes.ok: + response.raise_for_status() + with open(dst_path, mode='wb') as fh: + fh.write(response.content) + + ############################################## + + @property + def ngspice_version(self): + if not hasattr(self, '_ngspice_version'): + version = self._args.ngspice_version + if version is None: + from PySpice.Spice.NgSpice import NGSPICE_SUPPORTED_VERSION + version = NGSPICE_SUPPORTED_VERSION + self._ngspice_version = version + return self._ngspice_version + + ############################################## + + def install_ngspice_dll(self): + + if not(os.name == 'nt' or self._args.force_install_ngspice_dll): + return + + from PySpice.Spice import NgSpice + + with tempfile.TemporaryDirectory() as tmp_directory: + tmp_directory = Path(tmp_directory) + url = self.NGSPICE_WINDOWS_DLL_URL.format(self.ngspice_version) + zip_path = tmp_directory.joinpath('ngspice-{}_dll_64.zip'.format(self.ngspice_version)) + dst_path = Path(NgSpice.__file__).parent + try: + self._download_file(url, zip_path) + except requests.exceptions.HTTPError: + print('Download failed, trying another URL...') + url = self.NGSPICE_WINDOWS_DLL_OLD_URL.format(self.ngspice_version) + self._download_file(url, zip_path) + with ZipFile(zip_path) as zip_file: + zip_file.extractall(path=dst_path) + print('Extracted {} in {}'.format(zip_path, dst_path.joinpath('Spice64_dll'))) + + spice64_path = dst_path.joinpath('Spice64_dll') + dll_path = spice64_path.joinpath('dll-vs') + # src = dll_path.joinpath('ngspice-{}.dll'.format(self.ngspice_version)) + src = 'ngspice-{}.dll'.format(self.ngspice_version) + target = dll_path.joinpath('ngspice.dll') + # For ngspice version <=31 DLL naming did not contain a version number + if dll_path.joinpath(src).exists(): + if target.exists(): + target.unlink() + try: + target.symlink_to(src) + except OSError: + # OSError: symbolic link privilege not held + # Windows: If User Account Control (UAC) is on, any user with the "Create Symbolic + # Links" privilege that is not in the Administrators group can simply create a + # symbolic link. For users within the Administrators group and with UAC on, the user + # must "Run as Administrator". + shutil.copy(target.parent.joinpath(src), target) + + spinit_path = spice64_path.joinpath('share', 'ngspice', 'scripts', 'spinit') + with open(spinit_path) as fh: + content = fh.read() + rule = '='*80 + print(rule) + print(content) + print(rule) + cm_path = spice64_path.joinpath('lib', 'ngspice') + content = content.replace('../lib/ngspice/', str(cm_path) + '/') + print(rule) + print(content) + print(rule) + with open(spinit_path, 'w') as fh: + fh.write(content) + + ############################################## + + def download_ngspice_manual(self): + url = self.NGSPICE_MANUAL_URL.format(self.ngspice_version) + try: + self._download_file(url, 'ngspice-manual-{}.pdf'.format(self.ngspice_version)) + except requests.exceptions.HTTPError: + print('Download failed, trying another URL...') + url = self.NGSPICE_MANUAL_OLD_URL.format(self.ngspice_version) + self._download_file(url, 'ngspice-manual-{}.pdf'.format(self.ngspice_version)) + + ############################################## + + def check_installation(self): + + """Tool to check PySpice is correctly installed. + + """ + + import ctypes.util + + print('OS:', sys.platform) + print() + + print('Environments:') + for _ in ( + 'PATH', + 'LD_LIBRARY_PATH', + 'PYTHONPATH', + + 'NGSPICE_LIBRARY_PATH', + 'SPICE_LIB_DIR', + 'SPICE_EXEC_DIR', + 'SPICE_ASCIIRAWFILE', + 'SPICE_SCRIPTS', + 'NGSPICE_MEAS_PRECISION', + 'SPICE_NO_DATASEG_CHECK', + 'NGSPICE_INPUT_DIR', + ): + print(_, os.environ.get(_, 'undefined')) + print() + + if 'VIRTUAL_ENV' in os.environ: + print('On Virtual Environment:') + for _ in ( + 'VIRTUAL_ENV', + ): + print(_, os.environ.get(_, 'undefined')) + print() + + if 'CONDA_PREFIX' in os.environ: + print('On Anaconda:') + for _ in ( + # not specific + 'CONDA_EXE', + 'CONDA_PYTHON_EXE', + # 'CONDA_SHLVL', # shell level, 1 in conda else 0 + # '_CE_CONDA', # empty + # specific + 'CONDA_DEFAULT_ENV', + 'CONDA_PREFIX', + # 'CONDA_PROMPT_MODIFIER', + ): + print(_, os.environ.get(_, 'undefined')) + print() + + try: + print('Load PySpice module') + import PySpice + print('loaded {} version {}'.format(PySpice.__file__, PySpice.__version__)) + print() + except ModuleNotFoundError: + print('PySpice module not found') + return + + import PySpice.Logging.Logging as Logging + logger = Logging.setup_logging(logging_level='INFO') + + from PySpice.Config import ConfigInstall + from PySpice.Spice import NgSpice + from PySpice.Spice.NgSpice import NGSPICE_SUPPORTED_VERSION + from PySpice.Spice.NgSpice.Shared import NgSpiceShared + + print('ngspice supported version:', NGSPICE_SUPPORTED_VERSION) + print() + + ############################################## + + message = os.linesep.join(( + 'NgSpiceShared configuration is', + ' NgSpiceShared.NGSPICE_PATH = {0.NGSPICE_PATH}', + ' NgSpiceShared.LIBRARY_PATH = {0.LIBRARY_PATH}', + )) + print(message.format(NgSpiceShared)) + print() + + ############################################## + + cwd = Path(os.curdir).resolve() + print('Working directory:', cwd) + print() + + locale_ngspice = cwd.joinpath('ngspice-{}'.format(NGSPICE_SUPPORTED_VERSION)) + if locale_ngspice.exists() and locale_ngspice.is_dir(): + print('Found local ngspice:') + for root, _, filenames in os.walk(locale_ngspice, followlinks=True): + for filename in filenames: + print(root, filename) + print() + + + ngspice_module_path = Path(NgSpice.__file__).parent + print('NgSpice:', ngspice_module_path) + for root, _, filenames in os.walk(ngspice_module_path): + for filename in filenames: + print(root, filename) + print() + + ############################################## + + if ConfigInstall.OS.on_windows: + print('OS is Windows') + library = NgSpiceShared.LIBRARY_PATH + elif ConfigInstall.OS.on_osx: + print('OS is OSX') + library = 'ngspice' + elif ConfigInstall.OS.on_linux: + print('OS is Linux') + library = 'ngspice' + else: + raise NotImplementedError + + library_path = ctypes.util.find_library(library) + print('Found in library search path: {}'.format(library_path)) + + ############################################## + + print('\nLoad NgSpiceShared') + ngspice = NgSpiceShared.new_instance(verbose=True) + + if ConfigInstall.OS.on_linux: + # For Linux see DLOPEN(3) + # Apparently there is no simple way to get the path of the loaded library ... + # But we can look in the process maps + pid = os.getpid() + maps_path = '/proc/{}/maps'.format(pid) + with open(maps_path) as fh: + for line in fh: + if '.so' in line and 'ngspice' in line: + parts = [x for x in line.split() if x] + path = parts[-1] + print('loaded {}'.format(path)) + break + print() + + if ngspice.spinit_not_found: + print('WARNING: spinit was not found') + print() + + message = os.linesep.join(( + 'Ngspice version is {0.ngspice_version}', + ' has xspice: {0.has_xspice}', + ' has cider {0.has_cider}', + )) + print(message.format(ngspice)) + print() + + command = 'version -f' + print('> ' + command) + print(ngspice.exec_command(command)) + print() + + circuit_test = CircuitTest() + circuit_test.test_spinit() + + print('PySpice should work as expected') + + ############################################## + + def download_example(self): + + import PySpice + version = PySpice.__version__ + + RELEASE_URL = self.GITHUB_URL + '/archive/v{}.zip'.format(version) + + zip_path = 'examples.zip' + + dst_path = input("Enter the path where you want to extract examples: ") + dst_path = Path(dst_path).resolve() + dst_parent = dst_path.parent + if not dst_parent.exists(): + print("Directory {} doesn't exists".format(dst_parent)) + return + + with tempfile.TemporaryDirectory() as tmp_directory: + self._download_file(RELEASE_URL, zip_path) + with ZipFile(zip_path) as zip_file: + zip_file.extractall(path=tmp_directory) + examples_path = Path(tmp_directory).joinpath('PySpice-{}'.format(version), 'examples') + shutil.copytree(examples_path, dst_path) + print('Extracted examples in {}'.format(dst_path)) + +#################################################################################################### + +def main(): + _ = PySpicePostInstallation() + return _.run() diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/BasicElement.py b/third_party/PySpice-org/PySpice/PySpice/Spice/BasicElement.py new file mode 100644 index 00000000..f4f943a6 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/BasicElement.py @@ -0,0 +1,1715 @@ +#################################################################################################### +# +# 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) | ++--------------+------------------------------------------------------+ + +""" + +#################################################################################################### + +import logging + +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, + ) + +#################################################################################################### + +_module_logger = logging.getLogger(__name__) + +#################################################################################################### + +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) + + _logger = _module_logger.getChild('CoupledInductor') + + ############################################## + + def __init__(self, name, *args, **kwargs): + + super().__init__(name, *args, **kwargs) + + self._inductors = [] + for inductor in (self.inductor1, self.inductor2): + try: + self.netlist.element(inductor) + except KeyError: + try: + inductor = 'L' + inductor + self.netlist.element(inductor) + self._logger.info('Prefixed element {}'.format(inductor)) + except KeyError: + raise ValueError('Element with name {} not found'.format(inductor)) + # Fixme: str or Element instance ? + self._inductors.append(inductor) + self.inductor1, self.inductor2 = self._inductors + +#################################################################################################### + +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' + + # Fixme: + VALID_KWARGS = ('expression', 'table') + + ############################################## + + 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:`nfin` + only for Xyce + + :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:`nfin` + only for Xyce + + :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) + + # only for Xyce + nfin = IntKeyParameter('nfin') + +#################################################################################################### +# +# 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` + is the characteristic impedance + + :attr:`time_delay` + alias:`TD` + is the transmission delay + + :attr:`frequency` + alias:`F` + + :attr:`normalized_length` + alias:`NL` + + Attributes: + + :attr:`impedance` + + :attr:`time_delay` + + :attr:`frequency` + + :attr:`normalized_length` + + The transmission delay, `td`, may be specified directly (as `td=10ns`, for example). + Alternatively, a frequency `f` may be given, together with `nl`, the normalized electrical + length of the transmission line with respect to the wavelength in the line at the frequency + `f`. If a frequency is specified but `nl` is omitted, 0.25 is assumed (that is, the frequency is + assumed to be the quarter-wave frequency). Note that although both forms for expressing the line + length are indicated as optional, one of the two must be specified. + + 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): + + super().__init__(name, *args, **kwargs) + + if not (self.has_parameter('time_delay') or + (self.has_parameter('frequency') and self.has_parameter('normalized_length'))): + raise NameError('Either TD or F, NL must be specified') + +#################################################################################################### + +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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/ElementParameter.py b/third_party/PySpice-org/PySpice/PySpice/Spice/ElementParameter.py new file mode 100644 index 00000000..19e70d57 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/ElementParameter.py @@ -0,0 +1,395 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Ast.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Ast.py new file mode 100644 index 00000000..548815d8 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Ast.py @@ -0,0 +1,407 @@ +#################################################################################################### +# +# 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 + ')' diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Parser.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Parser.py new file mode 100644 index 00000000..3e141c38 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/Parser.py @@ -0,0 +1,346 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/__init__.py new file mode 100644 index 00000000..3930a3f5 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Expression/__init__.py @@ -0,0 +1,19 @@ +#################################################################################################### +# +# 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 . +# +#################################################################################################### diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/HighLevelElement.py b/third_party/PySpice-org/PySpice/PySpice/Spice/HighLevelElement.py new file mode 100644 index 00000000..dbce6238 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/HighLevelElement.py @@ -0,0 +1,865 @@ +# -*- 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.""" + +# Fixme: check NgSpice for discrepancies + +#################################################################################################### + +from ..Math import rms_to_amplitude, amplitude_to_rms +from ..Tools.StringTools import join_list, join_dict, str_spice, str_spice_list +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:`ac_magnitude` + + :attr:`amplitude` + + :attr:`damping_factor` + + :attr:`dc_offset` + + :attr:`delay` + + :attr:`frequency` + + :attr:`offset` + + """ + + ############################################## + + def __init__(self, + dc_offset=0, + ac_magnitude=1, + offset=0, amplitude=1, frequency=50, + delay=0, damping_factor=0): + + self.dc_offset = self.AS_UNIT(dc_offset) + self.ac_magnitude = self.AS_UNIT(ac_magnitude) + 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): + # Fixme: ok ??? + return amplitude_to_rms(self.amplitude * self.ac_magnitude) + + ############################################## + + @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 {} AC {}'.format(*str_spice_list(self.dc_offset, self.ac_magnitude)), + '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, + dc_offset=0): + + # Fixme: default + # rise_time, fall_time = Tstep + # pulse_width, period = Tstop + + self.dc_offset = self.AS_UNIT(dc_offset) # Fixme: -> SourceMixinAbc + 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): + + # if DC is not provided, ngspice complains + # Warning: vpulse: no DC value, transient time 0 value used + + # Fixme: to func? + return join_list(( + 'DC {}'.format(str_spice(self.dc_offset)), + '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, repeat_time=None, delay_time=None, dc=None): + self.values = sum(([as_s(t), self.AS_UNIT(x)] for (t, x) in values), []) + self.repeat_time = as_s(repeat_time, none=True) + self.delay_time = as_s(delay_time, none=True) + self.dc = self.AS_UNIT(dc, none=True) + + ############################################## + + def format_spice_parameters(self): + + # Fixme: to func? + + d = {} + if self.repeat_time is not None: + d["r"] = self.repeat_time + if self.delay_time is not None: + d["td"] = self.delay_time + + _ = "" + if self.dc is not None: + _ += "DC {} ".format(str_spice(self.dc)) + _ += "PWL(" + join_list(self.values) + if d: + _ += " " + join_dict(d) # OrderedDict( + _ += ")" + + return _ + +#################################################################################################### + +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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Library.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Library.py new file mode 100644 index 00000000..4518d30b --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Library.py @@ -0,0 +1,150 @@ +#################################################################################################### +# +# 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 +import re + +#################################################################################################### + +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 = ( + '.spice', + '.lib', + '.mod', + '.lib@xyce', + '.mod@xyce', + ) + + ############################################## + + def __init__(self, root_path, recurse=False, section=None): + + 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)) + try: + spice_parser = SpiceParser(path=path, recurse=recurse, section=section) + for lib in spice_parser.incl_libs: + self._subcircuits.update(lib._subcircuits) + self._models.update(lib._models) + except Exception as e: + # Parse problem with this file, so skip it and keep going. + self._logger.warn("Problem parsing {path} - {e}".format(**locals())) + continue + 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() + + # ############################################## + + def search(self, s): + """ Return dict of all models/subcircuits with names matching regex s. """ + matches = {} + models_subcircuits = {**self._models, **self._subcircuits} + for name, mdl_subckt in models_subcircuits.items(): + if re.search(s, name): + matches[name] = mdl_subckt + return matches diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Netlist.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Netlist.py new file mode 100644 index 00000000..96fbc733 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Netlist.py @@ -0,0 +1,1292 @@ +#################################################################################################### +# +# 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]] + # Fixme: else + + ############################################## + + 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) + elif hasattr(self, 'VALID_KWARGS') and key in self.VALID_KWARGS: + pass # cf. NonLinearVoltageSource + else: + raise ValueError('Unknown argument {}={}'.format(key, value)) + + self._pins = () + netlist._add_element(self) + + ############################################## + + def has_parameter(self, name): + return hasattr(self, '_' + name) + + ############################################## + + 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 element(self, name): + return self._elements[name] + + def model(self, name): + return self._models[name] + + def node(self, name): + return self._nodes[name] + + ############################################## + + def __getitem__(self, attribute_name): + + if attribute_name in self._elements: + return self.element(attribute_name) + elif attribute_name in self._models: + return self.model(attribute_name) + # Fixme: subcircuits + elif attribute_name in self._nodes: + return self.node(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._libs = [] # .lib, contains a (name, section) tuple + self._parameters = {} # .param + + # Fixme: not implemented + # .csparam + # .func + # .if + + ############################################## + + 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 lib(self, name, section=None): + """Load a library.""" + v = (name, section) + if v not in self._libs: + self._libs.append(v) + else: + self._logger.warn(f"Duplicated lib {v}") + + ############################################## + + 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_libs(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_libs(self, simulator=None): + if self._libs: + libs = [] + for lib, section in self._libs: + lib = Path(str(lib)).resolve() + if simulator: + lib_flavour = Path(f"{lib}@{simulator}") + if lib_flavour.exists(): + lib = lib_flavour + s = f".lib {lib}" + if section: + s += f" {section}" + libs.append(s) + return os.linesep.join(libs) + 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([f'.param {key}={value}' + os.linesep + for key, value in self._parameters.items()]) + 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/RawFile.py b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/RawFile.py new file mode 100644 index 00000000..90ae7f6b --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/RawFile.py @@ -0,0 +1,234 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Server.py b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Server.py new file mode 100644 index 00000000..83a1f493 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Server.py @@ -0,0 +1,162 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Shared.py b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Shared.py new file mode 100644 index 00000000..183c08a4 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Shared.py @@ -0,0 +1,1324 @@ +#################################################################################################### +# +# 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. + +""" + +#################################################################################################### + +# 16.7 Environmental variables +# 16.7.1 Ngspice specific variables +# +# SPICE_LIB_DIR +# default: /usr/local/share/ngspice (Linux, CYGWIN), C:\Spice\share\ngspice (Windows) +# SPICE_EXEC_DIR +# default: /usr/local/bin (Linux, CYGWIN), C:\Spice\bin (Windows) +# SPICE_ASCIIRAWFILE +# default: 0 +# Format of the rawfile. 0 for binary, and 1 for ascii. +# SPICE_SCRIPTS +# default: $SPICE_LIB_DIR/scripts +# In this directory the spinit file will be searched. +# NGSPICE_MEAS_PRECISION +# default: 5 +# Sets the number of digits if output values are printed by the meas(ure) command. +# SPICE_NO_DATASEG_CHECK +# default: undefined +# If defined, will suppress memory resource info (probably obsolete, not used on Windows +# or where the /proc information system is available.) +# NGSPICE_INPUT_DIR +# default: undefined +# If defined, using a valid directory name, will add the given directory to the search path +# when looking for input files (*.cir, *.inc, *.lib). + +#################################################################################################### + +__all__ = [ + 'NgSpiceCircuitError', + 'NgSpiceCommandError', + 'NgSpiceShared', +] + +#################################################################################################### + +from pathlib import Path +import logging +import os +import platform +import re + +import numpy as np + +from cffi import FFI + +#################################################################################################### + +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 PySpice.Unit import u_V, u_A, u_s, u_Hz, u_F, u_Degree + +from .SimulationType import SIMULATION_TYPE + +#################################################################################################### + +ffi = FFI() + +#################################################################################################### + +_module_logger = logging.getLogger(__name__) + +#################################################################################################### + +class NgSpiceCircuitError(NameError): + pass + +class NgSpiceCommandError(NameError): + pass + +#################################################################################################### + +def ffi_string_utf8(_): + _ = ffi.string(_) + try: + return _.decode('utf8') + except UnicodeDecodeError: + return _ + +#################################################################################################### + +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('disto'): + return self._to_distortion_analysis() + elif self.plot_name.startswith('noise'): + return self._to_noise_analysis() + elif self.plot_name.startswith('pz'): + return self._to_polezero_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): + for name in ('v-sweep', 'i-sweep', 'temp-sweep'): + if name in self: + sweep_variable = self[name] + break + 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 + NUMBER_OF_EXEC_CALLS_TO_RELEASE_MEMORY = 10_000 + + ############################################## + + @classmethod + def setup_platform(cls): + + if ConfigInstall.OS.on_windows: + if platform.architecture()[0] != '64bit': + raise NameError('Windows 32bit is no longer supported by NgSpice') + + _ = os.environ.get('NGSPICE_LIBRARY_PATH', None) + if _ is not None: + cls.LIBRARY_PATH = _ + else: + if ConfigInstall.OS.on_windows: + ngspice_path = Path(__file__).parent.joinpath('Spice64_dll') + cls.NGSPICE_PATH = ngspice_path + # path = ngspice_path.joinpath('dll-vs', 'ngspice-{version}{id}.dll') + path = ngspice_path.joinpath('dll-vs', 'ngspice{}.dll') + + elif ConfigInstall.OS.on_osx: + path = 'libngspice{}.dylib' + + elif ConfigInstall.OS.on_linux: + path = 'libngspice{}.so' + + else: + raise NotImplementedError + + cls.LIBRARY_PATH = str(path) + + ############################################## + + _instances = {} + + @classmethod + def new_instance(cls, ngspice_id=0, send_data=False, verbose=False): + """Create a NgSpiceShared instance""" + + # Fixme: send_data + + if ngspice_id in cls._instances: + return cls._instances[ngspice_id] + else: + cls._logger.debug("New instance for id {}".format(ngspice_id)) + instance = cls(ngspice_id=ngspice_id, send_data=send_data, verbose=verbose) + cls._instances[ngspice_id] = instance + return instance + + ############################################## + + def __init__(self, ngspice_id=0, send_data=False, verbose=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._spinit_not_found = False + + self._number_of_exec_calls = 0 + + self._stdout = [] + self._stderr = [] + self._error_in_stdout = None + self._error_in_stderr = None + + self._has_cider = None + self._has_xspice = None + self._ngspice_version = None + self._extensions = [] + + self._library_path = None + self._load_library(verbose) + self._init_ngspice(send_data) + + self._is_running = False + + ############################################## + + @property + def spinit_not_found(self): + return self._spinit_not_found + + ############################################## + + @property + def library_path(self): + if self._library_path is None: + if not self._ngspice_id: + library_prefix = '' + else: + library_prefix = '{}'.format(self._ngspice_id) # id = + library_path = self.LIBRARY_PATH.format(library_prefix) + self._library_path = library_path + return self._library_path + + ############################################## + + def _load_library(self, verbose): + + 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: + _ = str(Path(self.NGSPICE_PATH).joinpath('share', 'ngspice')) + os.environ['SPICE_LIB_DIR'] = _ + # self._logger.warning('Set SPICE_LIB_DIR = %s', _) + + # Fixme: not compatible with supra + # if 'CONDA_PREFIX' in os.environ: + # _ = str(Path(os.environ['CONDA_PREFIX']).joinpath('share', 'ngspice')) + # os.environ['SPICE_LIB_DIR'] = _ + # self._logger.warning('Set SPICE_LIB_DIR = %s', _) + + # https://sourceforge.net/p/ngspice/bugs/490 + # ngspice and Kicad do setlocale(LC_NUMERIC, "C"); + if ConfigInstall.OS.on_windows: + self._logger.debug('locale LC_NUMERIC is not forced to C') + elif ConfigInstall.OS.on_linux or ConfigInstall.OS.on_osx: + self._logger.debug('Set locale LC_NUMERIC to C') + import locale + locale.setlocale(locale.LC_NUMERIC, 'C') + + api_path = Path(__file__).parent.joinpath('api.h') + with open(api_path) as fh: + ffi.cdef(fh.read()) + + message = 'Load library {}'.format(self.library_path) + self._logger.debug(message) + if verbose: + print(message) + self._ngspice_shared = ffi.dlopen(self.library_path) + + # Note: cannot yet execute command + + ############################################## + + def _init_ngspice(self, send_data): + + # Ngspice API: ngSpice_Init ngSpice_Init_Sync + + 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: + # See SimulationType.py + 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, + self._simulation_type.temperature: u_Degree, + } + + # 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) + _module_logger.debug(str(ffi.string(message_c))) + message = ffi_string_utf8(message_c) + + # split message in "" + prefix, _, content = message.partition(' ') + if prefix == 'stderr': + self._stderr.append(content) + if content.startswith('Warning:'): + func = self._logger.warning + # elif content.startswith('Warning:'): + else: + self._error_in_stderr = True + func = self._logger.error + if content.strip() == "Note: can't find init file.": + self._spinit_not_found = True + self._logger.warning('spinit was not found') + func(content) + else: + self._stdout.append(content) + # Fixme: Ngspice writes error on stdout and stderr ... + if 'error' in content.lower(): + self._error_in_stdout = True + # if self._error_in_stdout: + # self._logger.warning(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 + 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): + if lines: + values = dict(description=lines[0]) + values.update({ + parts[0]: NgSpiceShared._to_python(parts[1]) + for parts in map(str.split, lines) + }) + return values + else: + raise ValueError + + ############################################## + + @property + def is_running(self): + return self._is_running + + ############################################## + + def clear_output(self): + self._stdout = [] + self._stderr = [] + self._error_in_stdout = False + self._error_in_stderr = False + + ############################################## + + @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. """ + + # Ngspice API: ngSpice_Command + + # Prevent memory leaks by periodically freeing ngspice history of past commands + # Each command sent to ngspice is stored in the control structures + if self._number_of_exec_calls > self.NUMBER_OF_EXEC_CALLS_TO_RELEASE_MEMORY: + # Clear the internal control structures + self._ngspice_shared.ngSpice_Command(FFI.NULL) + self._number_of_exec_calls = 0 + self._number_of_exec_calls += 1 + + 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() + + encoded_command = command.encode('ascii') + rc = self._ngspice_shared.ngSpice_Command(encoded_command) + + if rc: # Fixme: when not 0 ??? + raise NameError("ngSpice_Command '{}' returned {}".format(command, rc)) + + if self._error_in_stdout or self._error_in_stderr: + raise NgSpiceCommandError("Command '{}' failed".format(command)) + + 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(r'\*\* 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 %s with extensions: %s', + 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): + # Performance optimization: dispatch multiple alter commands jointly + device_name = device.lower() + commands = [] + commands_str_len = 0 + for key, value in kwargs.items(): + if isinstance(value, (list, tuple)): + value = '[ ' + ' '.join(value) + ' ]' + cmd = '{} {} {} = {}'.format(command, device_name, key, value) + # performance optimization: collect multiple alter commands and + # dispatch them jointly + commands.append(cmd) + commands_str_len += len(cmd) + if commands_str_len + len(commands) > self.MAX_COMMAND_LENGTH: + self.exec_command(';'.join(commands[:-1])) + commands = commands[-1:] + commands_str_len = len(commands[0]) + if commands: + self.exec_command(';'.join(commands)) + + ############################################## + + 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, command): + lines = self.exec_command(command, join_lines=False) + if lines: + values = self._lines_to_dicts(lines) + return values + else: + return '' + + ############################################## + + def show(self, device): + return self._show('show ' + device.lower()) + + ############################################## + + def showmod(self, device): + return self._show('showmod ' + device.lower()) + + ############################################## + + 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.""" + + # Ngspice API: ngSpice_Circ + circuit_lines = [line for line in str(circuit).splitlines() if line] + self._logger.debug('ngSpice_Circ\n' + str(circuit)) + + # ngspice 33 requires an empty line at the end + circuit_lines.append("") + + 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) + self.clear_output() + rc = self._ngspice_shared.ngSpice_Circ(circuit_array) + + if rc: # Fixme: when not 0 ??? + raise NameError("ngSpice_Circ returned {}".format(rc)) + + # Fixme: when Ngspice found an error in the circuit, it reports the error in stdout + # Fixme: https://sourceforge.net/p/ngspice/bugs/496/ + if self._error_in_stdout: + self._logger.error('\n' + self.stdout) + raise NgSpiceCircuitError('') + + # for line in circuit_lines: + # rc = self._ngspice_shared.ngSpice_Command(('circbyline ' + line).encode('utf8')) + # if rc: + # raise NameError("ngSpice_Command circbyline returned {}".format(rc)) + + ############################################## + + def listing(self): + command = 'listing' + return self.exec_command(command) + + ############################################## + + def run(self, background=False): + + """ Run the simulation. """ + + # in the background thread and wait until the simulation is done + + command = 'bg_run' if background else 'run' + self.exec_command(command) + + 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. """ + self.exec_command('bg_halt') + + ############################################## + + def resume(self, background=True): + """ Halt the simulation in the background thread. """ + command = 'bg_resume' if background else 'resume' + self.exec_command(command) + + ############################################## + + @property + def plot_names(self): + """ Return the list of plot names. """ + # Ngspice API: ngSpice_AllPlots + 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' + else: + raise NotImplementedError + + ############################################## + + @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. """ + + # Ngspice API: ngSpice_AllVecs ngGet_Vec_Info + + # 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 + + 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 + # template = 'vector[{}] {} type {} flags {} length {}' + # self._logger.debug(template.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 range(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 + # import json + # with open(name + '.json', 'w') as fh: + # json.dump(list(array), fh) + else: + # for k in range(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.complex128) + array.imag = tmp_array[1::2] + plot[vector_name] = Vector(self, vector_name, vector_type, array) + + i += 1 + + return plot + +#################################################################################################### +# +# Platform setup +# + +NgSpiceShared.setup_platform() diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Simulation.py b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Simulation.py new file mode 100644 index 00000000..92b62ec2 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/Simulation.py @@ -0,0 +1,127 @@ +################################################################################################### +# +# 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() + # load circuit and simulation + # Fixme: Error: circuit not parsed. + 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('Simulation failed') + + return self._ngspice_shared.plot(self, plot_name).to_analysis() diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/SimulationType.py b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/SimulationType.py new file mode 100644 index 00000000..fe07f41a --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/SimulationType.py @@ -0,0 +1,90 @@ +#################################################################################################### +# +# 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', +] + +#################################################################################################### + +# For a new ngspice relase, we just have to check this file hasn't changed +# ngspice-xx/src/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 = 34 # released on January 31st, 2021 + +for version in range(28, LAST_VERSION +1): + SIMULATION_TYPE[version] = SIMULATION_TYPE[27] + +SIMULATION_TYPE['last'] = SIMULATION_TYPE[LAST_VERSION] diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/__init__.py new file mode 100644 index 00000000..80519135 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/__init__.py @@ -0,0 +1,23 @@ +#################################################################################################### +# +# PySpice - A Spice Package for Python +# Copyright (C) 2020 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__ = ['NGSPICE_SUPPORTED_VERSION'] + +from .SimulationType import LAST_VERSION as NGSPICE_SUPPORTED_VERSION diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/api.h b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/api.h new file mode 100644 index 00000000..4a02e3af --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/NgSpice/api.h @@ -0,0 +1,76 @@ +/* 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 */ diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Parser.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Parser.py new file mode 100644 index 00000000..3fcfda71 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Parser.py @@ -0,0 +1,1057 @@ +#################################################################################################### +# +# 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 Lib(Statement): + + """ This class implements a library definition. """ + + ############################################## + + def __init__(self, line): + + super().__init__(line, statement='lib') + self._lib = self._line.right_of('.lib') + + ############################################## + + def __str__(self): + return self._lib + + ############################################## + + def __repr__(self): + return 'Lib {}'.format(self._lib) + + ############################################## + + def to_python(self, netlist_name): + + return '{}.lib({})'.format(netlist_name, self._lib) + os.linesep + +#################################################################################################### + +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').strip('"') + + ############################################## + + 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').strip() + import re + mtch = re.match('\s*([^ \t]+)\s*([^ \t(]+)(.*)', text) + self._name = mtch[1] + self._model_type = mtch[2] + params = mtch[3] + params = params.strip('() ') + self._parameters = Line.get_kwarg(params) + + ############################################## + + @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: + # Fixme: warning -> debug due to spam ... + self._logger.debug(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` + + :attr:`incl_libs` + + """ + + _logger = _module_logger.getChild('SpiceParser') + + ############################################## + + def __init__(self, path=None, source=None, end_of_line_comment=('$', '//', ';'), recurse=False, section=None): + + # Fixme: empty source + + self._path = path # For use by _parse() when recursing through files. + + 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=lines, recurse=recurse, section=section) + 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): + line_string = line_string.lstrip(' ') + 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, recurse=False, section=None): + + """ 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: + self._logger.warning('Empty Spice file: {self._path}'.format(**locals())) + # 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):] + + # SUBCKT and MODEL files often start with their commands as the + # first line so they'll parse incorrectly if that line is removed. + # For everything else, assume the first line is a TITLE line and + # remove it. + if str(lines[0]).startswith(('.model', '.subckt')): + start_index = 0 + else: + start_index = 1 + + statements = [] + skip_lines = [False] # True on top of stack means skip lines. + sub_circuit = None + scope = statements + self.incl_libs = [] # Libraries found during recursive descent into includes. + for line in lines[start_index:]: + # print('>', repr(line)) + text = str(line) + lower_case_text = text.lower() # ! + if skip_lines[-1]: + if lower_case_text.startswith('.endl'): + skip_lines.pop() + elif 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 first 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'): + incl = Include(line) + scope.append(incl) + if recurse: + from .Library import SpiceLibrary + incl_path = os.path.join(str(self._path.directory_part()), str(incl)) + self.incl_libs.append(SpiceLibrary(root_path=incl_path, recurse=recurse)) + elif lower_case_text.startswith('lib'): + lib = Lib(line) + if section and str(lib) != section.lower(): + # If the .lib statement is only followed by the name of a section, + # then skip any lines in a library section whose name does not match + # the library section argument. + skip_lines.append(True) + else: + scope.append(lib) + else: + # options param ... + # .global + # .lib filename libname + # .param + # .func .csparam .temp .if + # { expr } are allowed in .model lines and in device lines. + # self._logger.warning('Parser ignored: {}'.format(line)) + pass + else: + try: + element = Element(line) + scope.append(element) + except ParseError: + self._logger.warning('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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Parser_jmgc.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Parser_jmgc.py new file mode 100644 index 00000000..c28e04d7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Parser_jmgc.py @@ -0,0 +1,1486 @@ +#################################################################################################### +# +# PySpice - A Spice Package for Python +# Copyright (C) 2020 jmgc / 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. + +SPICE is case insensitive. + +""" + +#################################################################################################### + +from collections import OrderedDict +import logging +import os +import regex + +#################################################################################################### + +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 base 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') + + base, self._parameters = line.split_keyword('.model') + self._name, self._model_type = base + self._name = self._name.lower() + + ############################################## + + @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): + return circuit.model(self._name, self._model_type, **self._parameters) + +#################################################################################################### + +class Parameter(Statement): + + """This class implements a parameter definition. + + Spice syntax:: + + .param name=expr + + """ + + ############################################## + + def __init__(self, line): + super().__init__(line, statement='param') + + text = line.right_of('.param').strip().lower() # Fixme: lower ??? + idx = text.find('=') + self._name = text[:idx].strip() + self._value = text[idx + 1:].strip() + + ############################################## + + @property + def name(self): + """Name of the model""" + return self._name + + ############################################## + + def __repr__(self): + return 'Param {}={}'.format(self._name, self._value) + + ############################################## + + def to_python(self, netlist_name): + args = self.values_to_python((self._name, self._value)) + # Fixme: linesep here ??? + return '{}.param({})'.format(netlist_name, self.join_args(args)) + os.linesep + + ############################################## + + def build(self, circuit): + circuit.parameter(self._name, self._value) + +#################################################################################################### + +# Review: HERE + +class CircuitStatement(Statement): + + # Review: jmgc + + """This class implements a circuit definition. + + Spice syntax:: + + Title ... + + """ + + ############################################## + + def __init__(self, title): + + super().__init__(title, statement='title') + + # Review: Title + title_statement = '.title ' + self._title = str(title) + if self._title.startswith(title_statement): + self._title = self._title[len(title_statement):] + + self._statements = [] + self._subcircuits = [] + self._models = [] + self._required_subcircuits = set() + self._required_models = set() + self._params = [] + + ############################################## + + @property + def title(self): + """Title of the circuit.""" + return self._title + + @property + def name(self): + """Name of the circuit.""" + return self._title + + @property + def models(self): + """Models of the circuit.""" + return self._models + + @property + def subcircuits(self): + """Subcircuits of the circuit.""" + return self._subcircuits + + @property + def params(self): + """Parameters of the circuit.""" + return self._params + + ############################################## + + def __repr__(self): + text = 'Circuit {}'.format(self._title) + os.linesep + text += os.linesep.join([repr(model) for model in self._models]) + os.linesep + text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + 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._models + self._subcircuits + self._statements) + + ############################################## + + def append(self, statement): + """Append a statement to the statement's list.""" + self._statements.append(statement) + + ############################################## + + def append_model(self, statement): + """Append a model to the statement's list.""" + self._models.append(statement) + + ############################################## + + def append_param(self, statement): + """Append a param to the statement's list.""" + self._params.append(statement) + + ############################################## + + def append_subcircuit(self, statement): + """Append a subcircuit to the statement's list.""" + self._subcircuits.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): + circuit = Circuit(self._title) + for statement in self._params: + statement.build(circuit) + for statement in self._models: + model = statement.build(circuit) + for statement in self._subcircuits: + subckt = statement.build(ground) # Fixme: ok ??? + circuit.subcircuit(subckt) + for statement in self._statements: + if isinstance(statement, Element): + statement.build(circuit, ground) + return circuit + +#################################################################################################### + +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_keyword('.subckt') + # Review: syntax ??? + if parameters[-1].lower() == 'params:': + parameters = parameters[:-1] + self._name, self._nodes = parameters[0], parameters[1:] + self._name = self._name.lower() + self._parameters = dict_parameters + + self._statements = [] + self._subcircuits = [] + self._models = [] + self._required_subcircuits = set() + self._required_models = set() + self._params = [] + + ############################################## + + @property + def name(self): + """Name of the sub-circuit.""" + return self._name + + @property + def nodes(self): + """Nodes of the sub-circuit.""" + return self._nodes + + @property + def models(self): + """Models of the sub-circuit.""" + return self._models + + @property + def params(self): + """Params of the sub-circuit.""" + return self._params + + @property + def subcircuits(self): + """Subcircuits of the sub-circuit.""" + return self._subcircuits + + ############################################## + + def __repr__(self): + if self._parameters: + text = 'SubCircuit {} {} Parameters: {}'.format(self._name, self._nodes, self._parameters) + os.linesep + else: + text = 'SubCircuit {} {}'.format(self._name, self._nodes) + os.linesep + text += os.linesep.join([repr(model) for model in self._models]) + os.linesep + text += os.linesep.join([repr(subcircuit) for subcircuit in self._subcircuits]) + 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._models + self._subcircuits + self._statements) + + ############################################## + + def append(self, statement): + """Append a statement to the statement's list.""" + self._statements.append(statement) + + ############################################## + + def append_model(self, statement): + """Append a model to the statement's list.""" + self._models.append(statement) + + ############################################## + + def append_param(self, statement): + """Append a param to the statement's list.""" + self._params.append(statement) + + ############################################## + + def append_subcircuit(self, statement): + """Append a model to the statement's list.""" + self._subcircuits.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, parent=None): + subcircuit = SubCircuit(self._name, *self._nodes, **self._parameters) + subcircuit.parent = parent + for statement in self._params: + statement.build(subcircuit) + for statement in self._models: + model = statement.build(subcircuit) + for statement in self._subcircuits: + subckt = statement.build(ground, parent=subcircuit) # Fixme: ok ??? + subcircuit.subcircuit(subckt) + for statement in self._statements: + if isinstance(statement, Element): + statement.build(subcircuit, 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 + prefix = line_str[0] + if prefix.isalpha(): + self._prefix = prefix + else: + raise ParseError("Not an element prefix: " + prefix) + prefix_data = _prefix_cache[self._prefix] + + # Retrieve device name + args, kwargs = line.split_element(prefix) + self._name = args.pop(0) + + 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 = args[:number_of_pins] + args = args[number_of_pins:] + else: # Q or X + if prefix_data.prefix == 'Q': + self._nodes = args[:3] + args = args[3:] + # Fixme: optional node + else: # X + if args[-1].lower() == 'params:': + args.pop() + self._parameters.append(args.pop()) + self._nodes = args + args = [] + + # Read positionals + number_of_positionals = prefix_data.number_of_positionals_min + if number_of_positionals and (len(args) > 0) and (prefix_data.prefix != 'X'): # model is optional + self._parameters = args[:number_of_positionals] + args = args[number_of_positionals:] + if prefix_data.multi_devices and (len(args) > 0): + remaining = args + args = [] + self._parameters.extend(remaining) + + if prefix_data.prefix in ('V', 'I') and (len(args) > 0): + # merge remaining + self._parameters[-1] += " " + " ".join(args) + self._dict_parameters = kwargs + + # Read optionals + if (prefix_data.has_optionals or (prefix_data.prefix == 'X')) and (len(kwargs) > 0): + for key in kwargs: + self._dict_parameters[key] = kwargs[key] + + 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: + idx = parameter.position + if idx < len(self._parameters): + self._dict_parameters[parameter.attribute_name] = self._parameters[idx] + to_delete.append(idx - len(to_delete)) + for idx in to_delete: + self._parameters.pop(idx) + + # 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 _check_params(self, elements=1): + params = [] + for param in self._parameters: + values = param.replace(',', ' ') + if values[0] == '(' and values[-1] == ')': + values = values[1: -1].split() + if len(values) > elements: + raise IndexError('Incorrect number of elements for (%r): %s' % (self, param)) + params.extend(values) + else: + params.extend(values.split()) + self._parameters = params + + ############################################## + + def _voltage_controlled_nodes(self, poly_arg): + result = ['v(%s,%s)' % nodes + for nodes in zip(self._parameters[:(2 * poly_arg):2], + self._parameters[1:(2 * poly_arg):2])] + result += self._parameters[2 * poly_arg:] + return ' '.join(result) + + ############################################## + + def _current_controlled_nodes(self, poly_arg): + result = ['i(%s)' % node + for node in self._parameters[:poly_arg]] + result += self._parameters[poly_arg:] + return ' '.join(result) + + ############################################## + + def _manage_controlled_sources(self, nodes): + try: + idx = self._nodes.index('POLY') + if idx == 2: + poly_arg = self._nodes[3] + if poly_arg[0] == '(' and poly_arg[-1] == ')': + poly_arg = poly_arg[1:-1] + try: + poly_arg = int(poly_arg) + except TypeError as te: + raise TypeError('Not valid poly argument: %s' % poly_arg, te) + self._nodes = self._nodes[:2] + nodes = nodes[:2] + if self._prefix in 'EG': + self._check_params(2) + values = self._voltage_controlled_nodes(poly_arg) + if self._prefix == 'E': + key = 'v' + else: + key = 'i' + else: + self._check_params(1) + values = self._current_controlled_nodes(poly_arg) + if self._prefix == 'F': + key = 'v' + else: + key = 'i' + poly_str = '{ POLY (%d) %s }' % (poly_arg, values) + + self._dict_parameters[key] = poly_str + self._parameters.clear() + self._name = self._prefix + self._name + self._prefix = 'B' + prefix_data = _prefix_cache[self._prefix] + self.factory = prefix_data.single + return nodes + raise IndexError('Incorrect position of POLY: %r' % self) + except ValueError: + pass + _correction = [] + correction = [] + for _node, node in zip(self._nodes, nodes): + _values = _node.replace(',', ' ') + try: + values = node.replace(',', ' ') + except AttributeError: + values = str(node) + if _values[0] == '(' and _values[-1] == ')': + _values = _values[1: -1] + if values[0] == '(' and values[-1] == ')': + values = values[1: -1] + _correction.extend(_values.split()) + correction.extend(values.split()) + self._parameters = correction[len(self._nodes):] + self._parameters + self._nodes = _correction[:len(self._nodes)] + parameters = self._parameters + correction = correction[:len(self._nodes)] + if self._prefix in 'EG': + if len(correction) + len(parameters) == 5: + parameters = correction[2:] + parameters + self._nodes = _correction[:2] + value = '{v(%s, %s) * %s}' % tuple(parameters) + if self._prefix == 'E': + key = 'v' + else: + key = 'i' + self._dict_parameters[key] = value + self._parameters.clear() + self._name = self._prefix + self._name + self._prefix = 'B' + prefix_data = _prefix_cache[self._prefix] + self.factory = prefix_data.single + else: + if len(correction) + len(parameters) == 4: + parameters = correction[2:] + parameters + self._nodes = _correction[:2] + value = '{i(%s) * %s}' % tuple(parameters) + if self._prefix == 'F': + key = 'v' + else: + key = 'i' + self._dict_parameters[key] = value + self._parameters.clear() + self._name = self._prefix + self._name + self._prefix = 'B' + prefix_data = _prefix_cache[self._prefix] + self.factory = prefix_data.single + return correction[:len(self._nodes)] + + ############################################## + + def build(self, circuit, ground=0): + + nodes = self.translate_ground_node(ground) + if self._prefix != 'X': + if self._prefix in ('EFGH'): + nodes = self._manage_controlled_sources(nodes) + args = nodes + self._parameters + else: # != Spice + args = self._parameters + nodes + factory = getattr(circuit, self.factory.__alias__) + kwargs = self._dict_parameters + message = ' '.join([str(x) for x in (self._prefix, self._name, args, + self._dict_parameters)]) + self._logger.debug(message) + return factory(self._name, *args, **kwargs) + + +#################################################################################################### + +class Line: + + """This class implements a line in the netlist.""" + + _logger = _module_logger.getChild('Line') + + ############################################## + + 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) + if line_str[start_location] == '{': + stop_location = line_str.find('}', start_location) + if stop_location > start_location: + stop_location += 1 + else: + 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] + result = [] + expression = 0 + begin_idx = 0 + for idx, word in enumerate(words): + if expression == 0: + begin_idx = idx + expression += word.count('{') - word.count('}') + if expression == 0: + if begin_idx < idx: + result.append(' '.join(words[begin_idx:idx + 1])) + else: + result.append(word) + return result, 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 + + ############################################## + + @staticmethod + def _partition(text): + parts = [] + values = text.replace(',', ' ') + for part in values.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) + return parts + + ############################################## + + @staticmethod + def _partition_parentheses(text): + p = regex.compile(r'\(([^\(\)]|(?R))*?\)') + parts = [] + previous_start = 0 + for m in regex.finditer(p, text): + parts.extend(Line._partition(text[previous_start:m.start()])) + parts.append(m.group()) + previous_start = m.end() + parts.extend(Line._partition(text[previous_start:])) + return parts + + ############################################## + + @staticmethod + def _partition_braces(text): + p = regex.compile(r'\{([^\{\}]|(?R))*?\}') + parts = [] + previous_start = 0 + for m in regex.finditer(p, text): + parts.extend(Line._partition_parentheses(text[previous_start:m.start()])) + parts.append(m.group()) + previous_start = m.end() + parts.extend(Line._partition_parentheses(text[previous_start:])) + return parts + + ############################################## + + @staticmethod + def _check_parameters(parts): + parameters = [] + dict_parameters = {} + + 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 + + ############################################## + + def split_keyword(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. + The parenthesis can be omitted. + + """ + + text = self.right_of(keyword) + + p = regex.compile(r'\(([^\(\)]|(?R))*?\)') + b = regex.compile(r'\{([^\{\}]|(?R))*?\}') + parts = [] + + mp = regex.search(p, text) + mb = regex.search(b, text) + if mb is not None: + if mp is not None: + if (mb.start() > mp.start()) and (mb.end() < mp.end()): + parts.extend(Line._partition(text[:mp.start()])) + parts.extend(Line._partition_braces(mp.group()[1:-1])) + elif (mb.start() < mp.start()) and (mb.end() > mp.end()): + parts.extend(Line._partition_braces(text)) + else: + raise ValueError("Incorrect format {}".format(text)) + else: + parts.extend(Line._partition_braces(text)) + else: + if mp is not None: + parts.extend(Line._partition(text[:mp.start()])) + parts.extend(Line._partition(mp.group()[1:-1])) + else: + parts.extend(Line._partition(text)) + return Line._check_parameters(parts) + + ############################################## + + def split_element(self, prefix): + + """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(prefix) + + parts = Line._partition_braces(text) + + return Line._check_parameters(parts) + +#################################################################################################### + +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 fh: + raw_lines = fh.readlines() # Fixme: cf. jmgc + 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) + + ############################################## + + 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(' \t\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 + + ############################################## + + @staticmethod + def _check_models(circuit, available_models=set()): + p_available_models = available_models.copy() + p_available_models.update([model.name for model in circuit._models]) + for subcircuit in circuit._subcircuits: + SpiceParser._check_models(subcircuit, p_available_models) + for model in circuit._required_models: + if model not in p_available_models: + raise ValueError("model (%s) not available in (%s)" % (model, circuit.name)) + + ############################################## + + @staticmethod + def _sort_subcircuits(circuit, available_subcircuits=set()): + p_available_subcircuits = available_subcircuits.copy() + names = [subcircuit.name for subcircuit in circuit._subcircuits] + p_available_subcircuits.update(names) + dependencies = dict() + for subcircuit in circuit._subcircuits: + required = SpiceParser._sort_subcircuits(subcircuit, p_available_subcircuits) + dependencies[subcircuit] = required + for subcircuit in circuit._required_subcircuits: + if subcircuit not in p_available_subcircuits: + raise ValueError("subcircuit (%s) not available in (%s)" % (subcircuit, circuit.name)) + items = sorted(dependencies.items(), key=lambda item: len(item[1])) + result = list() + result_names = list() + previous = len(items) + 1 + while 0 < len(items) < previous: + previous = len(items) + remove = list() + for item in items: + subckt, depends = item + for name in depends: + if name not in result_names: + break + else: + result.append(subckt) + result_names.append(subckt.name) + remove.append(item) + for item in remove: + items.remove(item) + if len(items) > 0: + raise ValueError("Crossed dependencies (%s)" % [(key.name, value) for key, value in items]) + circuit._subcircuits = result + return circuit._required_subcircuits - set(names) + + ############################################## + + 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') + + circuit = CircuitStatement(lines[0]) + stack = [] + scope = circuit + 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'): + stack.append(scope) + scope = SubCircuitStatement(line) + elif lower_case_text.startswith('ends'): + parent = stack.pop() + parent.append_subcircuit(scope) + scope = parent + 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(model) + elif lower_case_text.startswith('include'): + include = Include(line) + scope.append(include) + elif lower_case_text.startswith('param'): + param = Parameter(line) + scope.append_param(param) + else: + # options param ... + # .global + # .lib filename libname + # .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) + if hasattr(element, '_prefix') and (element._prefix == "X"): + name = element._parameters[0].lower() + scope._required_subcircuits.add(name) + elif hasattr(element, '_dict_parameters') and 'model' in element._dict_parameters: + name = element._dict_parameters['model'].lower() + scope._required_models.add(name) + except ParseError: + pass + SpiceParser._check_models(circuit) + SpiceParser._sort_subcircuits(circuit) + return circuit + + ############################################## + + @property + def circuit(self): + """Circuit statements.""" + return self._statements + + @property + def models(self): + """Models of the sub-circuit.""" + return self._statements.models + + @property + def subcircuits(self): + """Subcircuits of the sub-circuit.""" + return self._statements.subcircuits + + ############################################## + + 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)) + circuit = self.circuit.build(str(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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/RawFile.py b/third_party/PySpice-org/PySpice/PySpice/Spice/RawFile.py new file mode 100644 index 00000000..eb7d04c6 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/RawFile.py @@ -0,0 +1,417 @@ +#################################################################################################### +# +# 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='complex128') + 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(), + ) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Simulation.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Simulation.py new file mode 100644 index 00000000..54488bc5 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Simulation.py @@ -0,0 +1,1236 @@ +################################################################################################### +# +# 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_frequency(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_frequency(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): + + 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 = use_initial_condition + + ############################################## + + @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, + 'uic' if self._use_initial_condition else None, + ) + +#################################################################################################### + +class MeasureParameters(AnalysisParameters): + + """This class defines measurements on analysis. + + """ + + ANALYSIS_NAME = 'meas' + + ############################################## + + def __init__(self, analysis_type, name, *args): + + _analysis_type = str(analysis_type).upper() + if _analysis_type not in ('AC', 'DC', 'OP', 'TRAN', 'TF', 'NOISE'): + raise ValueError('Incorrect analysis type {}'.format(analysis_type)) + + self._parameters = [_analysis_type, name, *args] + + ############################################## + + @property + def parameters(self): + return self._parameters + + ############################################## + + def to_list(self): + return self._parameters + +#################################################################################################### + +class PoleZeroAnalysisParameters(AnalysisParameters): + + """This class defines analysis parameters for pole-zero analysis.""" + + ANALYSIS_NAME = 'pz' + + ############################################## + + def __init__(self, node1, node2, node3, node4, tf_type, pz_type): + + self._nodes = (node1, node2, node3, node4) + self._tf_type = tf_type # transfert_function + self._pz_type = pz_type # pole_zero + + ############################################## + + @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 tf_type(self): + return self._tf_type + + @property + def pz_type(self): + return self._pz_type + + ############################################## + + def to_list(self): + return list(self._nodes) + [self._tf_type, self._pz_type] + +#################################################################################################### + +class NoiseAnalysisParameters(AnalysisParameters): + + """This class defines analysis parameters for noise analysis.""" + + ANALYSIS_NAME = 'noise' + + ############################################## + + def __init__(self, output, src, variation, points, start_frequency, stop_frequency, points_per_summary): + + self._output = output + self._src = src + self._variation = variation + self._points = points + self._start_frequency = start_frequency + self._stop_frequency = stop_frequency + self._points_per_summary = points_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 points(self): + return self._points + + # Fixme: mixin + @property + def start_frequency(self): + return self._start_frequency + + @property + def stop_frequency(self): + return self._stop_frequency + + @property + def points_per_summary(self): + return self._points_per_summary + + ############################################## + + def to_list(self): + + parameters = [ + self._output, + self._src, + self._variation, + self._points, + self._start_frequency, + self._stop_frequency, + ] + + if self._points_per_summary: + parameters.append(self._points_per_summary) + + return parameters + +#################################################################################################### + +class DistortionAnalysisParameters(AnalysisParameters): + + """This class defines analysis parameters for distortion analysis.""" + + ANALYSIS_NAME = 'disto' + + ############################################## + + def __init__(self, variation, points, start_frequency, stop_frequency, f2overf1): + + self._variation = variation + self._points = points + self._start_frequency = start_frequency + self._stop_frequency = stop_frequency + self._f2overf1 = f2overf1 + + ############################################## + + @property + def variation(self): + return self._variation + + @property + def points(self): + return self._points + + @property + def start_frequency(self): + return self._start_frequency + + @property + def stop_frequency(self): + return self._stop_frequency + + @property + def f2overf1(self): + return self._f2overf1 + + ############################################## + + def to_list(self): + + parameters = [ + self._variation, + self._points, + self._start_frequency, + self._stop_frequency, + ] + + if self._f2overf1: + parameters.append(self._f2overf1) + + return parameters + +#################################################################################################### + +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._measures = [] # .measure + self._initial_condition = {} # .ic + self._node_set = {} # .nodeset + 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) + + ############################################## + + @staticmethod + def _make_initial_condition_dict(kwargs): + return {f"V({key})": str_spice(value) for key, value in kwargs.items()} + + ############################################## + + def initial_condition(self, **kwargs): + """Set initial condition for voltage nodes. + + Usage:: + + simulator.initial_condition(node_name=value, ...) + + General form:: + + .ic v(node_name)=value ... + + The `.ic` line is for setting transient initial conditions. It has two different + interpretations, depending on whether the uic parameter is specified on the `.tran` control + line, or not. One should not confuse this line with the `.nodeset` line. The `.nodeset` + line is only to help DC convergence, and does not affect the final bias solution (except for + multi-stable circuits). The two indicated interpretations of this line are as follows: + + 1. When the uic parameter is specified on the `.tran` line, the node voltages specified on + the `.ic` control line are used to compute the capacitor, diode, BJT, JFET, and MOSFET + initial conditions. This is equivalent to specifying the `ic=...` parameter on each + device line, but is much more convenient. The `ic=...` parameter can still be specified + and takes precedence over the `.ic` values. Since no dc bias (initial transient) + solution is computed before the transient analysis, one should take care to specify all + dc source voltages on the `.ic` control line if they are to be used to compute device + initial conditions. + + 2. When the uic parameter is not specified on the `.tran` control line, the DC bias (initial + transient) solution is computed before the transient analysis. In this case, the node + voltages specified on the `.ic` control lines are forced to the desired initial values + during the bias solution. During transient analysis, the constraint on these node + voltages is removed. This is the preferred method since it allows Ngspice to compute a + consistent dc solution. + + """ + d = self._make_initial_condition_dict(kwargs) + self._initial_condition.update(d) + + ############################################## + + def node_set(self, **kwargs): + """Specify initial node voltage guesses. + + Usage:: + + simulator.node_set(node_name=value, ...) + + General form:: + + .nodeset v(node_name)=value ... + .nodeset all=val + + The `.nodeset` line helps the program find the DC or initial transient solution by making a + preliminary pass with the specified nodes held to the given voltages. The restrictions are + then released and the iteration continues to the true solution. The `.nodeset` line may be + necessary for convergence on bistable or astable circuits. `.nodeset all=val` sets all + starting node voltages (except for the ground node) to the same value. In general, the + `.nodeset` line should not be necessary. + + """ + d = self._make_initial_condition_dict(kwargs) + self._node_set.update(d) + + ############################################## + + 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 _add_measure(self, measure_parameters): + self._measures.append(measure_parameters) + + ############################################## + + def operating_point(self): + """Compute the operating point of the circuit with capacitors open and inductors shorted.""" + 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 measure(self, analysis_type, name, *args): + + """Add a measure in the circuit. + + Examples of usage:: + + simulator.measure('TRAN', 'tdiff', 'TRIG AT=10m', 'TARG v(n1) VAL=75.0 CROSS=1') + simulator.measure('tran', 'tdiff', 'TRIG AT=0m', f"TARG par('v(n1)-v(derate)') VAL=0 CROSS=1") + + Note: can be used with the .options AUTOSTOP to stop the simulation at Trigger. + + Spice syntax: + + .. code:: spice + + .meas tran tdiff TRIG AT=0m TARG v(n1) VAL=75.0 CROSS=1 + + """ + + self._add_measure(MeasureParameters(analysis_type, name, *args)) + + ############################################## + + def transient(self, step_time, end_time, start_time=0, max_time=None, use_initial_condition=False): + + """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, tf_type, pz_type): + + """Perform a Pole-Zero analysis of the circuit. + + node1, node2 - Input node pair. + node3, node4 - Output node pair + tf_type - should be `cur` for current or `vol` for voltage + pz_type - 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: + + .. code:: spice + + .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 tf_type not in ('cur', 'vol'): + raise NameError("polezero type must be 'cur' or 'vol'") + if pz_type not in ('pol', 'zer', 'pz'): + raise NameError("pz_type must be 'pol' or 'zer' or 'pz'") + + self._add_analysis( + PoleZeroAnalysisParameters(node1, node2, node3, node4, tf_type, pz_type) + ) + + ############################################## + + def noise(self, output_node, ref_node, src, variation, points, start_frequency, stop_frequency, points_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. + points, start_frequency, stop_frequency - number of points, start and stop frequencies. + points_per_summary - if specified, the noise contributions of each noise generator is produced every points_per_summary frequency points. + + See section 15.3.4 of ngspice manual. + + Spice syntax: + + General form: + + .. code:: spice + + .noise v(output <,ref >) src ( dec | lin | oct ) pts fstart fstop + + Examples: + + .. code:: spice + + .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. + # Fixme: mixin + if variation not in ('dec', 'lin', 'oct'): + raise NameError("variation must be 'dec' or 'lin' or 'oct'") + + output = 'V({},{})'.format(output_node, ref_node) + + self._add_analysis( + NoiseAnalysisParameters(output, src, variation, points, start_frequency, stop_frequency, points_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: + + .. code:: spice + + .tf outvar insrc + + Examples: + + .. code:: spice + + .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( + TransferFunctionAnalysisParameters(outvar, insrc) + ) + + ############################################## + + def distortion(self, variation, points, start_frequency, stop_frequency, f2overf1=None): + + """Perform a distortion analysis of the circuit. + + variation, points, start_frequency, stop_frequency - 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: + + .. code:: spice + + .disto dec nd fstart fstop + .disto oct no fstart fstop + .disto lin np fstart fstop + + Examples: + + .. code:: spice + + .disto dec 10 1kHz 100 MEG + .disto dec 10 1kHz 100 MEG 0.9 + + """ + + # do some rudimentary parameter checking. + if variation not in ('dec', 'lin', 'oct'): + raise NameError("variation must be 'dec' or 'lin' or 'oct'") + + self._add_analysis( + DistortionAnalysisParameters(variation, points, start_frequency, stop_frequency, 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._node_set: + netlist += '.nodeset ' + join_dict(self._node_set) + 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 measure_parameters in self._measures: + netlist += str(measure_parameters) + os.linesep + for analysis_parameters in self._analyses.values(): + netlist += str(analysis_parameters) + os.linesep + netlist += '.end' + os.linesep + 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')) + + _kwargs = dict(kwargs) + _kwargs.pop('log_desk', None) + + method = getattr(CircuitSimulation, analysis_method) + method(self, *args, **_kwargs) + + message = 'desk' + os.linesep + str(self) + if kwargs.get('log_desk', False): + self._logger.info(message) + else: + self._logger.debug(message) + + ############################################## + + 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 transfer_function(self, *args, **kwargs): + return self._run('transfer_function', *args, **kwargs) + + tf = transfer_function # shorcut diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/RawFile.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/RawFile.py new file mode 100644 index 00000000..8abf5ffb --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/RawFile.py @@ -0,0 +1,175 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Server.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Server.py new file mode 100644 index 00000000..c05325c4 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Server.py @@ -0,0 +1,140 @@ +#################################################################################################### +# +# 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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Simulation.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Simulation.py new file mode 100644 index 00000000..70058ce7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/Simulation.py @@ -0,0 +1,73 @@ +################################################################################################### +# +# 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() diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Spice/Xyce/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Spice/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Spice/__init__.py new file mode 100644 index 00000000..81e67035 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Spice/__init__.py @@ -0,0 +1,75 @@ +#################################################################################################### +# +# 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) + # Preserve docstrings for element shortcuts + function.__doc__ = element_class.__doc__ + 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) + + diff --git a/third_party/PySpice-org/PySpice/PySpice/Tools/EnumFactory.py b/third_party/PySpice-org/PySpice/PySpice/Tools/EnumFactory.py new file mode 100644 index 00000000..658d11ce --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Tools/EnumFactory.py @@ -0,0 +1,176 @@ +#################################################################################################### +# +# 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) diff --git a/third_party/PySpice-org/PySpice/PySpice/Tools/File.py b/third_party/PySpice-org/PySpice/PySpice/Tools/File.py new file mode 100644 index 00000000..ee693263 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Tools/File.py @@ -0,0 +1,292 @@ +#################################################################################################### +# +# 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 . +# +#################################################################################################### + +# Used by Spice/Library.py + +#################################################################################################### + +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): + + if self.is_file(): + yield File(self.filename_part(), self.directory_part()) + else: + 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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Tools/Path.py b/third_party/PySpice-org/PySpice/PySpice/Tools/Path.py new file mode 100644 index 00000000..6038a948 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Tools/Path.py @@ -0,0 +1,53 @@ +#################################################################################################### +# +# 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))) diff --git a/third_party/PySpice-org/PySpice/PySpice/Tools/StringTools.py b/third_party/PySpice-org/PySpice/PySpice/Tools/StringTools.py new file mode 100644 index 00000000..05a5c992 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Tools/StringTools.py @@ -0,0 +1,84 @@ +#################################################################################################### +# +# 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__ = [ + 'join_dict', + 'join_lines' + 'join_list', + 'str_spice', + 'str_spice_list', +] + +#################################################################################################### + +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 str_spice_list(*args): + return [str_spice(x) for x in args] + +#################################################################################################### + +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]) diff --git a/third_party/PySpice-org/PySpice/PySpice/Tools/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/PySpice/Unit/SiUnits.py b/third_party/PySpice-org/PySpice/PySpice/Unit/SiUnits.py new file mode 100644 index 00000000..34296151 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Unit/SiUnits.py @@ -0,0 +1,330 @@ +#################################################################################################### +# +# 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' diff --git a/third_party/PySpice-org/PySpice/PySpice/Unit/Unit.py b/third_party/PySpice-org/PySpice/PySpice/Unit/Unit.py new file mode 100644 index 00000000..8eae5175 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Unit/Unit.py @@ -0,0 +1,1944 @@ +#################################################################################################### +# +# 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`. + +""" + +# https://numpy.org/doc/stable/user/basics.subclassing.html#basics-subclassing + +#################################################################################################### + +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 not None: + self._value_ctor = value_ctor + + if values_ctor is not None: + 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): + + # Ngspice User Manual Section 2.3.1 Some naming conventions + # + # Letters immediately following a number that are not scale factors are ignored, and + # letters immediately 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. + + # >>> WARNING <<< + # Note that M or m denote ’milli’, i.e. 10−3 . Suffix meg has to be used for 106. + # see SPICE_PREFIX in SiUnits + + # Fixme: unit clash, e.g. mm ??? + + 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 + # degree symbole can be encoded str(176) in Extended ASCII + string = string.replace('°', '') # U+00B0 + string = string.replace('℃', '') # U+2103 + # U+2109 ℉ + string = string.replace('Ω', 'Ohm') # U+CEA0 + string = string.replace('μ', 'u') # U+CEBC + + return string + + ############################################## + + def str_spice(self): + 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, + + # Statistic functions + # -------------------------------------------------- + np.mean: CONVERSION.NO_CONVERSION, + } + + ############################################## + + @classmethod + def from_ndarray(cls, array, prefixed_unit): + + # cls._logger.debug('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.debug('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.debug('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.debug( + # '\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])) + + # ufunc= + # method=reduce + # inputs=(WaveForm [10 12 14 16 18 20 22 24 26 28]@mV,) + + prefixed_unit = self._prefixed_unit + + conversion = self.UFUNC_MAP[ufunc] + self._logger.debug("Conversion for {} is {}".format(ufunc, conversion)) + + # e.g. np.mean do an internal call to reduce + if method != '__call__': + conversion = self.CONVERSION.NO_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.debug("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.debug('\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 __getstate__(self): + # # https://docs.python.org/3/library/pickle.html#object.__getstate__ + # return { + # 'data': super(UnitValues, self).__getstate__(), + # 'prefixed_unit': self._prefixed_unit, + # } + + ############################################## + + def __reduce__(self): + # https://docs.python.org/3/library/pickle.html#object.__reduce__ + np_state = super(UnitValues, self).__reduce__() + # ( , + # (, (0,), b'b'), + # (1, (1, 1), dtype('float64'), False, b'\x00\x00\x80?\x00\x00\x80?') ) + obj_state = (self._prefixed_unit,) + np_state[2] + return np_state[:2] + (obj_state,) + np_state[3:] + + ############################################## + + def __setstate__(self, state): + # https://docs.python.org/3/library/pickle.html#object.__setstate__ + super(UnitValues, self).__setstate__(state[1:]) + self._prefixed_unit = state[0] + + ############################################## + + 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 +PrefixedUnit._values_ctor = UnitValues + +_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 diff --git a/third_party/PySpice-org/PySpice/PySpice/Unit/__init__.py b/third_party/PySpice-org/PySpice/PySpice/Unit/__init__.py new file mode 100644 index 00000000..b714c0c9 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/Unit/__init__.py @@ -0,0 +1,244 @@ +#################################################################################################### +# +# 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 diff --git a/third_party/PySpice-org/PySpice/PySpice/__init__.py b/third_party/PySpice-org/PySpice/PySpice/__init__.py new file mode 100644 index 00000000..f1b3b5b3 --- /dev/null +++ b/third_party/PySpice-org/PySpice/PySpice/__init__.py @@ -0,0 +1,27 @@ +#################################################################################################### +# +# 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__ = '1.5' +GIT_TAG = 'v1.5' + +def show_version(): + print('PySpice Version {}'.format(__version__)) diff --git a/third_party/PySpice-org/PySpice/README.html b/third_party/PySpice-org/PySpice/README.html new file mode 100644 index 00000000..4f53b4c8 --- /dev/null +++ b/third_party/PySpice-org/PySpice/README.html @@ -0,0 +1,896 @@ + + + + + + +PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators + + + + +
+

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

+ + + + +

PySpice license +PySpice python version

+

PySpice last version

+

Anaconda last version +Anaconda donwloads

+

PySpice build status @travis-ci.org

+

Quick Links

+ +
+

2024 Update

+

Disclaimer: PySpice is developed on my free time actually, so I could be busy with other tasks and less reactive.

+

The free Discourse forum was closed some time ago due to a lack of activity. +A HTML backup is stored in the directory pyspice-discourse-backup.

+

On Devel HEAD

+
    +
  • fixed the ngspice library loading for recent cffi

  • +
  • fixed simulation aborting due to a message from newer ngspice

  • +
  • fixes for Spice parser

  • +
  • added support for Pint unit library

  • +
  • implemented SpiceLibrary

  • +
  • code cleanup but must check for typo...

  • +
+ +

An issue was found with NgSpice Shared, we must setlocale(LC_NUMERIC, "C"); see https://sourceforge.net/p/ngspice/bugs/490/

+
+
+

Overview

+
+

What is PySpice ?

+

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

+
+
+

Where is the Documentation ?

+

The documentation is available on the PySpice Home Page.

+

Note: This site is hosted on my own infrastructure, if the site seems done, please create an issue to notify me.

+
+
+

Where to get help or talk about PySpice ?

+

Thanks to Discourse, PySpice now has a Forum hosted at https://pyspice.discourse.group

+
+
+

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.

+
+
+
+

Pull Request Recommendation

+

To make it easier to merge your pull request, you should divide your PR into smaller and easier-to-verify units.

+

Please do not make a pull requests with a lot of modifications which are difficult to check. If I merge +pull requests blindly then there is a high risk this software will become a mess quickly for everybody.

+
+
+

Credits

+

Authors: Fabrice Salvaire and contributors

+
+
+

News

+ + +
+

V1.6.0 (development release)

+
    +
  • KiCadTools a proof of concept module to read KiCad 6 +.kicad_sch schema file and compute the netlist. This module can +be used to perform any kind of processings on a KiCad schema. It is +actually hosted in the source but could become a standalone +project. For PySpice, it provides a very flexible way to draft a +circuit with the help of KiCad and then generate the netlist without +using the netlist export feature of KiCad. And thus leverage the +writing of fastidious cicruit.

  • +
+
+
+

V1.5.0 (production release) 2021-05-15

+
    +
  • Support Ngspice up to version 34

  • +
  • Renamed custom dunders "__dunder__" to "CONSTANT" or "_private" class attributes

  • +
  • Fixed typo in documentation (thanks to endolith and brollb)

  • +
  • Add DC temperature sweep support #272 (thanks to Fatsie)

  • +
  • PWL support improvements #271 (thanks to Fatsie)

  • +
  • Assign units on creation of temperature-sweep vectors #263 (thanks to ARF1)

  • +
  • Prevent memory leaks by freeing ngspice command log #260 thanks to ARF1)

  • +
  • Performance optimization: dispatch multiple alter commands jointly #259 (thanks to ARF1)

  • +
  • Added spice library support #258 (thanks to Fatsie)

  • +
  • Allow to specify DC value for PWL #257 (thanks to Fatsie)

  • +
  • Support for .nodeset type initial condition #256 (thanks to Fatsie)

  • +
  • Fix accuracy problems #254 (thanks to sotw1957)

  • +
  • Changes to make it easier to use PySpice with a large archive of SPICE models medium diff #249 (thanks to xesscorp)

  • +
  • Netlist.py: Fix wrong method when joining parameters during netlist parse #245 (thanks to cyber-g)

  • +
  • Unit: add Pickle support

  • +
  • Add Parser code from #136 (thanks to jmgc) but not yet merged

  • +
  • Unit: add np.mean

  • +
+
+
+

V1.4.3 2020-07-04

+

A huge effort, thanks to @stuarteberg Stuart Berg, has been made to make Ngspice and PySpice +available on Anaconda (conda-forge) for the Window, OSX and Linux platforms. Thanks to the +conda-forge continuous integration platform, we can now run unit tests and the examples on theses +platforms automatically. Hope this will make the software more robust and easier to run !

+
    +
  • PySpice is now available on Anaconda(conda-forge) as well as a wheel on PyPI

  • +
  • Added a post installation tool to download the Ngspice DLL on Windows and to check the installation. +It should now simplify considerably the PySpice installation on Windows.

  • +
  • This tool can also download the examples and the Ngspice PDF manual.

  • +
  • On Linux and OSX, a Ngspice package is now available on Anaconda(conda-forge). +Note that theses two platforms do not download a binary from Ngspice since a compiler can easily be installed on theses platforms.

  • +
  • Updated installation documentation for Linux, the main distributions now provide a ngspice shared package.

  • +
  • Added a front-end web site so as to keep older releases documentation available on the web.

  • +
  • fixed and rebuilt all examples (but mistakes could happen ...)

  • +
  • examples are now available as Python files and Jupyter notebooks +(but some issues must be fixed, e.g. due to the way Jupyter handles Matplotlib plots)

  • +
  • support NgSpice 32 API (no change)

  • +
  • removed @substitution@ in PySpice/__init__.py, beacause it breaks pip install from git

  • +
  • fixed some logging spams

  • +
  • fixed NonLinearVoltageSource

  • +
  • fixed Unicode issue with °C (° is Extended ASCII)

  • +
  • fixed ffi_string_utf8 for UnicodeDecodeError

  • +
  • fixed logging formater for OSX (removed ANSI codes)

  • +
  • reworded "Invalid plot name" exception

  • +
  • removed diacritics in example filenames

  • +
  • cir2py has been converted to an entry point so as to work on all platforms

  • +
  • updated Matplotlib subplots in examples

  • +
  • added a unit example

  • +
  • added a NMOS example (thanks to cyber-g) cf. #221

  • +
+
+
+

V1.4.0 2020-05-05

+

This release is yanked due to broken Windows support.

+
    +
  • fixed nasty issue with NgSpice shared for setlocale(LC_NUMERIC, "C"); cf. #172

  • +
  • fixed AC AC_MAG AC_PASAE SIN for new NgSpice syntax

  • +
  • fixed initial_state for VoltageControlledSwitch

  • +
  • fixed LosslessTransmissionLine #169

  • +
  • fixed docstrings for element shortcut methods (thanks to Kyle Dunn) #178

  • +
  • fixed parser for leading whitespace (thanks to Matt Huszagh) #182

  • +
  • fix for PyYAML newer API

  • +
  • support NgSpice 31 API (no change)

  • +
  • added check for CoupledInductor #157

  • +
  • added check-installation tool to help to fix broken installation

  • +
  • added pole-zero, noise, distorsion, transfer-function analyses (thanks to Peter Garrone) #191

  • +
  • added .measure support (thanks to ceprio) #160

  • +
  • added log_desk parameter to CircuitSimulator

  • +
  • added listing command method to NgSpiceShared

  • +
  • added Xyce Mosfet nfin #177

  • +
+
+
+

V1.3.2 2019-03-11

+
    +
  • support Ngspice 30 and Xyce 6.10

  • +
  • fixed NgSpice and Xyce support on Windows 10

  • +
  • bug fixes

  • +
+
+
+

V1.2.0 2018-06-07

+
    +
  • Initial support of the Xyce simulator. Xyce is an open source, SPICE-compatible, +high-performance analog circuit simulator, capable of solving extremely large circuit problems +developed at Sandia National Laboratories. Xyce will make PySpice suitable for industry and +research use.

  • +
  • Fixed OSX support

  • +
  • Splitted G device

  • +
  • Implemented partially A XSPICE device

  • +
  • Implemented missing transmission line devices

  • +
  • Implemented high level current sources +Notice: Some classes were renamed !

  • +
  • Implemented node kwarg e.g. circuit.Q(1, base=1, collector=2, emitter=3, model='npn')

  • +
  • Implemented raw spice pass through (see User FAQ)

  • +
  • Implemented access to internal parameters (cf. save @device[parameter])

  • +
  • Implemented check for missing ground node

  • +
  • Implemented a way to disable an element and clone netlist

  • +
  • Improved SPICE parser

  • +
  • Improved unit support:

    +
      +
    • Implemented unit prefix cast U_μV(U_mV(1)) to easily convert values

    • +
    • Added U_mV, ... shortcuts

    • +
    • Added Numpy array support to unit, see UnitValues Notice: this new feature could be buggy !!!

    • +
    • Rebased WaveForm to UnitValues

    • +
    +
  • +
  • Fixed node order so as to not confuse users Now PySpice matches SPICE order for two ports elements !

  • +
  • Fixed device shortcuts in Netlist class

  • +
  • Fixed model kwarg for BJT Notice: it must be passed exclusively as kwarg !

  • +
  • Fixed subcircuit nesting

  • +
  • Outsourced documentation generator to Pyterate

  • +
  • Updated setup.py for wheel

  • +
+ +
+
+

V1.1.0 2017-09-06

+
    +
  • Enhanced shared mode

  • +
  • Shared mode is now set as default on Linux

  • +
+
+
+

V1.0.0 2017-09-06

+
    +
  • Bump version to v1.0.0 since it just works!

  • +
  • Support Windows platform using Ngspice shared mode

  • +
  • Fixed shared mode

  • +
  • Fixed and completed Spice parser : tested on example's libraries

  • +
+
+
+

V0.4.2

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

  • +
+
+
+

V0.4.0 2017-07-31

+
    +
  • Git repository cleanup: filtered generated doc and useless files so as to shrink the repository size.

  • +
  • Improved documentation generator: Implemented format for RST content and Tikz figure.

  • +
  • Improved unit support: It implements now the International System of Units. +And we can now use unit helper like u_mV or compute the value of 1.2@u_kΩ / 2@u_mA. +The relevant documentation is on this page.

  • +
  • Added the Simulation instance to the Analysis class.

  • +
  • Refactored simulation parameters as classes.

  • +
+
+
+

V0.3.2 2017-02-22

+
    +
  • fixed CCCS and CCVS

  • +
+
+
+

V0.3.1 2017-02-22

+
    +
  • fixed ngspice shared

  • +
+
+
+

V0.3.0 2015-12-08

+
    +
  • Added an example to show how to use the NgSpice Shared Simulation Mode.

  • +
  • Completed the Spice netlist parser and added examples, we could now use a schematic editor +to define the circuit. The program cir2py translates a circuit file to Python.

  • +
+
+
+

V0 2014-03-21

+

Started project

+ + +
+
+
+ + diff --git a/third_party/PySpice-org/PySpice/README.rst b/third_party/PySpice-org/PySpice/README.rst new file mode 100644 index 00000000..d3a3102f --- /dev/null +++ b/third_party/PySpice-org/PySpice/README.rst @@ -0,0 +1,374 @@ +.. -*- Mode: rst -*- + +.. -*- Mode: rst -*- + +.. |PySpiceUrl| replace:: https://pyspice.fabrice-salvaire.fr + +.. |PySpiceHomePage| replace:: PySpice Home Page +.. _PySpiceHomePage: https://pyspice.fabrice-salvaire.fr + + +.. |PySpice@github| replace:: https://github.com/FabriceSalvaire/PySpice + + +.. |PySpice@pypi| replace:: https://pypi.python.org/pypi/PySpice + + +.. |PySpice@anaconda| replace:: https://anaconda.org/conda-forge/pyspice + +.. |PySpice@fs-anaconda| replace:: https://anaconda.org/fabricesalvaire/pyspice + +.. |Anaconda Version| image:: https://anaconda.org/conda-forge/pyspice/badges/version.svg + :target: https://anaconda.org/conda-forge/pyspice/badges/version.svg + :alt: Anaconda last version + +.. |Anaconda Downloads| image:: https://anaconda.org/conda-forge/pyspice/badges/downloads.svg + :target: https://anaconda.org/conda-forge/pyspice/badges/downloads.svg + :alt: Anaconda donwloads + + +.. |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 + + +.. |Tavis CI master| image:: https://travis-ci.com/FabriceSalvaire/PySpice.svg?branch=master + :target: https://travis-ci.com/FabriceSalvaire/PySpice + :alt: PySpice build status @travis-ci.org +.. -*- 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| + +|Anaconda Version| +|Anaconda Downloads| + +|Tavis CI master| + +**Quick Links** + +* `Production Branch `_ +* `Devel Branch `_ +* `Travis CI `_ +* `pyspice@conda-forge `_ +* `conda-forge/pyspice `_ +* `ngspice@conda-forge `_ +* `Ngspice `_ +* `Ngspice Bug Tracker `_ +* `Xyce of Sandia National Laboratories `_ + +2024 Update +=========== + +**Disclaimer: PySpice is developed on my free time actually, so I could be busy with other tasks and less reactive.** + +The free Discourse forum was closed some time ago due to a lack of activity. +A HTML backup is stored in the directory `pyspice-discourse-backup`. + +**On Devel HEAD** + +* fixed the ngspice library loading for recent cffi +* fixed simulation aborting due to a message from newer ngspice +* fixes for Spice parser +* added support for Pint unit library +* implemented SpiceLibrary +* code cleanup but must check for typo... + +.. + Brief Notes + =========== + +An issue was found with NgSpice Shared, we must `setlocale(LC_NUMERIC, "C");` see https://sourceforge.net/p/ngspice/bugs/490/ + +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|_. + +*Note: This site is hosted on my own infrastructure, if the site seems done, please create an issue to notify me.* + +Where to get help or talk about PySpice ? +----------------------------------------- + +Thanks to `Discourse `_, PySpice now has a **Forum** hosted at https://pyspice.discourse.group + +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. + +Pull Request Recommendation +=========================== + +To make it easier to merge your pull request, you should divide your PR into smaller and easier-to-verify units. + +Please do not make a pull requests with a lot of modifications which are difficult to check. **If I merge +pull requests blindly then there is a high risk this software will become a mess quickly for everybody.** + +Credits +======= + +Authors: `Fabrice Salvaire `_ and `contributors `_ + +News +==== + +.. -*- Mode: rst -*- + + +.. no title here + +V1.6.0 (development release) +---------------------------- + +* **KiCadTools** a proof of concept module to read KiCad 6 + `.kicad_sch` schema file and compute the netlist. *This module can + be used to perform any kind of processings on a KiCad schema. It is + actually hosted in the source but could become a standalone + project.* For PySpice, it provides a very flexible way to draft a + circuit with the help of KiCad and then generate the netlist without + using the netlist export feature of KiCad. And thus leverage the + writing of fastidious cicruit. + +V1.5.0 (production release) 2021-05-15 +-------------------------------------- + +* Support Ngspice up to version 34 +* Renamed custom dunders "__dunder__" to "CONSTANT" or "_private" class attributes +* Fixed typo in documentation (thanks to endolith and brollb) +* Add DC temperature sweep support #272 (thanks to Fatsie) +* PWL support improvements #271 (thanks to Fatsie) +* Assign units on creation of temperature-sweep vectors #263 (thanks to ARF1) +* Prevent memory leaks by freeing ngspice command log #260 thanks to ARF1) +* Performance optimization: dispatch multiple alter commands jointly #259 (thanks to ARF1) +* Added spice library support #258 (thanks to Fatsie) +* Allow to specify DC value for PWL #257 (thanks to Fatsie) +* Support for `.nodeset` type initial condition #256 (thanks to Fatsie) +* Fix accuracy problems #254 (thanks to sotw1957) +* Changes to make it easier to use PySpice with a large archive of SPICE models medium diff #249 (thanks to xesscorp) +* `Netlist.py`: Fix wrong method when joining parameters during netlist parse #245 (thanks to cyber-g) +* Unit: add Pickle support +* Add Parser code from #136 (thanks to jmgc) but not yet merged +* Unit: add np.mean + +V1.4.3 2020-07-04 +----------------- + +A huge effort, thanks to @stuarteberg Stuart Berg, has been made to make Ngspice and PySpice +available on Anaconda (conda-forge) for the Window, OSX and Linux platforms. Thanks to the +conda-forge continuous integration platform, we can now run unit tests and the examples on theses +platforms automatically. Hope this will make the software more robust and easier to run ! + +* PySpice is now available on Anaconda(conda-forge) as well as a wheel on PyPI +* Added a post installation tool to download the Ngspice DLL on Windows and to check the installation. + It should now simplify considerably the PySpice installation on Windows. +* This tool can also download the examples and the Ngspice PDF manual. +* On Linux and OSX, a Ngspice package is now available on Anaconda(conda-forge). + Note that theses two platforms do not download a binary from Ngspice since a compiler can easily be installed on theses platforms. +* Updated installation documentation for Linux, the main distributions now provide a ngspice shared package. + +* Added a front-end web site so as to keep older releases documentation available on the web. +* fixed and rebuilt all examples (but mistakes could happen ...) +* examples are now available as Python files and Jupyter notebooks + (but some issues must be fixed, e.g. due to the way Jupyter handles Matplotlib plots) + +* support NgSpice 32 API (no change) +* removed @substitution@ in PySpice/__init__.py, beacause it breaks pip install from git +* fixed some logging spams +* fixed NonLinearVoltageSource +* fixed Unicode issue with °C (° is Extended ASCII) +* fixed ffi_string_utf8 for UnicodeDecodeError +* fixed logging formater for OSX (removed ANSI codes) +* reworded "Invalid plot name" exception +* removed diacritics in example filenames +* cir2py has been converted to an entry point so as to work on all platforms +* updated Matplotlib subplots in examples +* added a unit example +* added a NMOS example (thanks to cyber-g) cf. #221 + +V1.4.0 2020-05-05 +----------------- + +This release is yanked due to broken Windows support. + +* fixed nasty issue with NgSpice shared for `setlocale(LC_NUMERIC, "C");` cf. #172 +* fixed `AC AC_MAG AC_PASAE SIN` for new NgSpice syntax +* fixed `initial_state` for `VoltageControlledSwitch` +* fixed `LosslessTransmissionLine` #169 +* fixed docstrings for element shortcut methods (thanks to Kyle Dunn) #178 +* fixed parser for leading whitespace (thanks to Matt Huszagh) #182 +* fix for PyYAML newer API +* support NgSpice 31 API (no change) +* added check for `CoupledInductor` #157 +* added `check-installation` tool to help to fix broken installation +* added pole-zero, noise, distorsion, transfer-function analyses (thanks to Peter Garrone) #191 +* added `.measure` support (thanks to ceprio) #160 +* added `log_desk` parameter to `CircuitSimulator` +* added `listing` command method to `NgSpiceShared` +* added Xyce Mosfet nfin #177 + +V1.3.2 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 diff --git a/third_party/PySpice-org/PySpice/README.txt b/third_party/PySpice-org/PySpice/README.txt new file mode 100644 index 00000000..82305f15 --- /dev/null +++ b/third_party/PySpice-org/PySpice/README.txt @@ -0,0 +1,114 @@ +.. -*- 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| + +|Anaconda Version| +|Anaconda Downloads| + +|Tavis CI master| + +**Quick Links** + +* `Production Branch `_ +* `Devel Branch `_ +* `Travis CI `_ +* `pyspice@conda-forge `_ +* `conda-forge/pyspice `_ +* `ngspice@conda-forge `_ +* `Ngspice `_ +* `Ngspice Bug Tracker `_ +* `Xyce of Sandia National Laboratories `_ + +2024 Update +=========== + +**Disclaimer: PySpice is developed on my free time actually, so I could be busy with other tasks and less reactive.** + +The free Discourse forum was closed some time ago due to a lack of activity. +A HTML backup is stored in the directory `pyspice-discourse-backup`. + +**On Devel HEAD** + +* fixed the ngspice library loading for recent cffi +* fixed simulation aborting due to a message from newer ngspice +* fixes for Spice parser +* added support for Pint unit library +* implemented SpiceLibrary +* code cleanup but must check for typo... + +.. + Brief Notes + =========== + +An issue was found with NgSpice Shared, we must `setlocale(LC_NUMERIC, "C");` see https://sourceforge.net/p/ngspice/bugs/490/ + +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|_. + +*Note: This site is hosted on my own infrastructure, if the site seems done, please create an issue to notify me.* + +Where to get help or talk about PySpice ? +----------------------------------------- + +Thanks to `Discourse `_, PySpice now has a **Forum** hosted at https://pyspice.discourse.group + +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. + +Pull Request Recommendation +=========================== + +To make it easier to merge your pull request, you should divide your PR into smaller and easier-to-verify units. + +Please do not make a pull requests with a lot of modifications which are difficult to check. **If I merge +pull requests blindly then there is a high risk this software will become a mess quickly for everybody.** + +Credits +======= + +Authors: `Fabrice Salvaire `_ and `contributors `_ + +News +==== + +.. include:: news.txt + +.. End diff --git a/third_party/PySpice-org/PySpice/anaconda-recipe/README.md b/third_party/PySpice-org/PySpice/anaconda-recipe/README.md new file mode 100644 index 00000000..7be999dd --- /dev/null +++ b/third_party/PySpice-org/PySpice/anaconda-recipe/README.md @@ -0,0 +1,3 @@ +# Anaconda Recipe + +This recipe is used for test. diff --git a/third_party/PySpice-org/PySpice/anaconda-recipe/bld.bat b/third_party/PySpice-org/PySpice/anaconda-recipe/bld.bat new file mode 100644 index 00000000..e69de29b diff --git a/third_party/PySpice-org/PySpice/anaconda-recipe/build.sh b/third_party/PySpice-org/PySpice/anaconda-recipe/build.sh new file mode 100644 index 00000000..7c9dcf9b --- /dev/null +++ b/third_party/PySpice-org/PySpice/anaconda-recipe/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# https://github.com/AnacondaRecipes/conda-feedstock/blob/master/recipe/build.sh + +# --record filename in which to record list of +# installed files +# --single-version-externally-managed used by system package builders to +# create 'flat' eggs + +invoke release.update-git-sha +$PYTHON setup.py install --single-version-externally-managed --record record.txt diff --git a/third_party/PySpice-org/PySpice/anaconda-recipe/meta.yaml b/third_party/PySpice-org/PySpice/anaconda-recipe/meta.yaml new file mode 100644 index 00000000..a65a73ff --- /dev/null +++ b/third_party/PySpice-org/PySpice/anaconda-recipe/meta.yaml @@ -0,0 +1,80 @@ +# https://jakevdp.github.io/blog/2016/08/25/conda-myths-and-misconceptions + +# https://docs.anaconda.com/anaconda-cloud/user-guide/tasks/work-with-packages/#working-with-packages +# https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html +# https://github.com/AnacondaRecipes/conda-feedstock + +{% set name = "PySpice" %} +{% set version = "1.4.2" %} + +package: + name: {{ name|lower }} + version: {{ version }} + +source: + # path: ../.. + # + # url: "https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz" + # sha256: 18f39249072958de6ead18ab54a14442e9e9e32b5579185b907ad7a794908be9 + # + git_url: https://github.com/FabriceSalvaire/PySpice + git_rev: "v{{ version }}" + git_depth: 1 # (Defaults to -1/not shallow) + +build: + noarch: python + number: 0 + # script: python -m pip install --no-deps --ignore-installed . + # script: "{{ PYTHON }} -m pip install . -vv" + # entry_points: + # - foo = foo.bar:main_foo + has_prefix_files: + always_include_files: + # - bin/cir2py + # - bin/ngspice-check-installation + # - bin/ngspice-post-installation + # - PySpice/Spice/NgSpice/api.h + # - PySpice/Config/logging.yml + +requirements: + host: + - pip + - python + - invoke >=1.3 + - numpy >=1.18 + run: + - python + - PyYAML >=5.3 + - cffi >=1.14 + - matplotlib >=3.1 + - numpy >=1.18 + - ply >=3.11 + - scipy >=1.4 + - requests >=2.23 +# build: +# - wheel + +# test: +# imports: + +# outputs: +# - type: wheel + +about: + home: https://github.com/FabriceSalvaire/PySpice + license: "GNU General Public License v3.0" + license_family: GPL + license_file: LICENSE.txt + # https://www.gnu.org/licenses/gpl-3.0.en.html + summary: "Simulate electronic circuit using Python and the Ngspice / Xyce simulators" + # description: > + # ... + dev_url: https://github.com/FabriceSalvaire/PySpice + doc_url: https://pyspice.fabrice-salvaire.fr + # doc_source_url: + +extra: + maintainers: + - FabriceSalvaire + recipe-maintainers: + - FabriceSalvaire diff --git a/third_party/PySpice-org/PySpice/bin/cir2py b/third_party/PySpice-org/PySpice/bin/cir2py new file mode 100644 index 00000000..a30c30b7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/bin/cir2py @@ -0,0 +1,24 @@ +#! /usr/bin/env python3 + +#################################################################################################### +# +# PySpice - A Spice Package for Python +# Copyright (C) 2020 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 . +# +#################################################################################################### + +from PySpice.Scripts.cir2py import main +main() diff --git a/third_party/PySpice-org/PySpice/bin/pyspice-post-installation b/third_party/PySpice-org/PySpice/bin/pyspice-post-installation new file mode 100644 index 00000000..2e248aab --- /dev/null +++ b/third_party/PySpice-org/PySpice/bin/pyspice-post-installation @@ -0,0 +1,24 @@ +#! /usr/bin/env python3 + +#################################################################################################### +# +# PySpice - A Spice Package for Python +# Copyright (C) 2020 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 . +# +#################################################################################################### + +from PySpice.Scripts.pyspice_post_installation import main +main() \ No newline at end of file diff --git a/third_party/PySpice-org/PySpice/doc/datasheets/1N4148_1N4448.pdf b/third_party/PySpice-org/PySpice/doc/datasheets/1N4148_1N4448.pdf new file mode 100644 index 0000000000000000000000000000000000000000..36c7c908cf4e8b1e6839cc0e3ebcd82b0fd8d20a GIT binary patch literal 124555 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 literal 0 HcmV?d00001 diff --git a/third_party/PySpice-org/PySpice/doc/datasheets/1N5919B.pdf b/third_party/PySpice-org/PySpice/doc/datasheets/1N5919B.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b2ec4d7f883c2509ad0b95d4a010f01608951952 GIT binary patch literal 108074 zcmeFZ2UJtr*Do4CK&7cjldd2lHKBx#fb^#HUJMbCP69{?sHmtkrHTls^xnHPDM5;W z^p5l{EtCM^?&$Fx&pH45-uK4$?j7Tfad*aMuf6J=bImo^n!mL+oVRW&@qzh8DLAKR zx_T+jg2mYc*<9}2W0R5+(6D!gTf5u)z-`#T0vc>!L5L7USU{T%0v1qY6NQQiLj<(g zga!0~6tI{$R6vEzlnp8($_5dGn#;&gz@2S?iYVCraZ#MT?+(BFqa}zC#aY2WF0d%u zj}V*luQ;%vu+YEaz>t5(35)zYPFxUR1b_WiA4CKK`BxlN6#B0?v46KKA}lQU&wL^x z(0|8?3;l}^5%GWL69tR^t6fnD^q=&B!QvwSii3)Z{gYpau(;sA;-Dh`s!vo9EG+U5 zK188l;eXO13KfF>lMhj-h=|xf$_fhN`}@J}7bt2o@*7HJ32Bq*R{?}&uE3n)2SBjGpUFc%xRfGQhA zNEpz32v|%&mklf=E+d0Ly2GuV039B=V`XIp+@97}aH}a?BU}9g5aA850SPTh*cozi zSz4l0vWiRU1`_pi(Zr>udKQZ6(sjs$nP)Cl8IhA;Ls#y3kR?{DIFdh96BK-wni}f` z(oau~Hbg$4eg7DX(?cn$rfe(^q`N-(HUc<2HriGLcXj6wdwN0J;Z3Q}@<)Pp>@ z^^V4Uqk0u;xZDWnJW&;Bn0(8_ZYg;&i4L?+Y z7d>xRxPStX>gZzoBSpjd4%`t)7RD$4WdI<5iCe+l#Z}(rzA0V>M8rk-fdL6v5p$rl zJKP!ht6~KgXC&~7V1xW@NXG>U^a-0FK#q;Q^?xd_14p=cxWnKGfMz4@JNMu)BoI<{ z5@Ztsh7VrDoWN{CzzF#fgs=(yL85?yi=&IXuB$Z+4&+zR6=Z|p?Vk=11pg6~kr7Y= z=mC=T^nm1_)#>R0$v>;p(}Ms8?`I*1C6MEXq<~Vl++AS0aHOdKU||LH;P;UL5rFs< z%mozhBb9aWECH1&v7O|BT1S2?6i;a=-H9!}xgM zX)Yjdjez3?EFkA@Z|%q@@8W2qsRvjL_dld71QAel226&%vn?QHXE|qt{h#lD_Reqp zh!^#LtsfyjG*#fQegw?u-}+HV1g|}UYom zC97Zk3C!^#e+vFrxBlvczjY}v=i=k>4gA-;^k*4-C*hg+_4|`Zpw3_6-^=A zDn5j7?N=`R6X;ZY3_cZL{znQB#?y|E!{7fqf5P}Sg@1Mu5W>%|fBb|*MSfE;1#LwQ zWlf$Rn)csPGQ8&ep=3z7lL4S1KP;J+wG&{r|IrAb+q%HahJ@J(Xt}sMSv&s73QUP` zz*zpY!g&2RgxlNNA=v;{@bjcC0x+)vc%y?i-h2=+_(v&uyoKfy6%}FQ69P;dFt-W< z3jjf27|H$2DK04TvwCYMdq?kUKRD$EW)fGGpVdQw>DK6vun1V>rzQNmeG^axrX72j zoU^SXFfsf#vhai9C)3)uG=XKS_AR!%){Y3cfQ#!Nfgj`XPZKPlEB8mdt|prxzaZqd zy6}Aetn1e@K?DN*59@@#Rs|~0a||62jO8@PUo0Okaa|WN1ku*7`;2JO=oHSqnl(yU z(^}QA<;+Xbj7gEtTdk<8U9hmS6rS^hY~H{5Xz$^n*d77*x{_Md9pup+{^oJ#o#Q*m zk47r*o2ETEnx~bfx@?cm2PfQ$kU3G1b$T-JN@G+1sf&`LLGaDy+p9LbwAnpDg;gbS zFY4&1MsD7*K1DZ1~?E;RW zRK<{9`yFnUEI-3osCLTYj`j8--LsFNt&U!|q;Y-srM!KNv@-U|WlQ;~ z**!@9HD>j~m;JS>9Pa`o3q|0R1Dlc@ z{cs2u$L8ge8Z+sPyIYK-XrZ?)46iQ2dXvobLN8Y`ARskUe8{HOZ4>G-fH(iTI76R_PsSd zvm@{xbf=m4Wn2m$i2W|H;a&fl1HF=aCOPSGoPpwm4ciA$DQ(D1%QBVkm!T8OL>NlN zY@sqb;Kf4d8&ucxJrSZ9_0uRT3zstCYqZjA+Z&gAj1-l4qAE*j+sVg5U}}6-y7@wN z*b(iL)AE%uswweIvW(XuZYAlqwf8qmuO>Ur>Ih1#%22%KdW{vfIZ=br>c8Ut{6xyB z?Xn)T2`VL8UXW>7e4B^VJFjWpfPTF`j2Y88m9>BIOx(Nn8Xq$v^)8h<$q@$@yK85h zf~lF|&)=tNq2ESR!RRVy?IOhD!wkQN-~N2fFzF)Il?Zv`N0i@lJjXBeH9zrcH~MfU z?Rafr1r(?`niD+9EzEh<^2(^4BG4wXhux=WV-Mn_oEI8-sdV*GW4a1Z@#j~ zZ>Tfrh8lKr-McHR(f54v9wqhhy^~Wt@rN|8I`xOG5#E9EU&OVCCgyw9ZO&<~7ppWA z`yUWz_;Jb{@iyy(gv@7%bBxp9BiI>i?u&az4%bY*n33IsYILF2>Z3k-sCG24HG1Wh z6bsiSa8TjCP3XDjobjymDmD|7a`DH#YynSqH|y`-8DRWEll1Vi#LRq_w({4kT0h^G z_KX?U#r)P@qeIIJtF#rjbSZtX()x%~S?BXlUxaa zGMj*?qg$Eho(x_3a4J&OVmmw2L+-r4WUvIpx0~AK3-k6ZXm#+{VDp( z0wPeFh#7Au`R__m>>ifqG(y|5{V;lnDFhFKKhm%{hh)Qbo1t9_&ru~&(kr`Y9F*In4o z44T`P%u#-Q;V@`CaoQ98@Cs3lV|>dy$2^NtkKv_Vo3gjxo|g|#VVaVODTQ6waZhWz zjI}lQ=-<@PQj_byIb|uGAgBxCkKDPtm19P;!W@&FaBXlu01*x)UOUH%zN2qI(<)+- zKRt8%ej;<$?&EQH8CP4T#vd9jhlt#_Mf-k8{=Kg-$QIQZz|1 zVP{KTtyj3k*i$i6)I2HhkQ%H)LS^m?pMsQM4+#9uf~!9JQSKtTg0w~^E^K7ye(CcO z#3egEl3X3@$HdO$mAB%opWOB*zfFXxOBu0!!Qa1DIc>ag0_y+Dl7u68_u&(B4Lro@ z)>Rmh_5M2cTBO9BB{il~X+T3M3jR8zGY=_=b&BetjY^}fd3=-CPBkX=hJHvwTP26q zCW-1xh3H|8%SzDfp3Pyk7sGdVNX|yNv=FxEYTSwq8(g9vbzYU)M&|9t$cfOm(0g-> z_|<)WKgwoF&Fdr;4CD0|-qGz}4M3Q+OEAh_;VIqg=TbaODG5ItLeg=W_x^n^8`qF6 zFkhL6x9LQ;x*o5^-Oq&)1DQ~|w~SU-+COdIYdx@NgGEo(xcQuy|H9tKKp4#-^Z1&6 z;KEr!*ew(D(;|wTjgQXBXW&ZCecB@0RKL#%4TFBG0eQx16$Ef|>T4)Ix#T?=|TPBI^uKu-Pii*as9I--rM@=+u>7wzWvOD_tt)f z@ZKXn^vnChyZAp-e*gQk{=fd>eLH;mFGuzFym;sLhxhlJy#If4-SHIz(l!-PRZzgg z0ybWr6vBbJ%|1bAQ*~A28W3WLD%im+Pj2-XFlMygd?e3+}Gq;^gY$ zjCVf8enawpVR%1a5yGIX*IqLc&nE8rlT2OtoL;A9l^ z7({;h^yxFF$^uz>IXM*#JvB8AH8nlmg^R#-fsT%miG_)gj)Q}Xi-SW1 zP)ZTWKQ96bGBOHEigT2d=PpuGQC_@w?)>?47nv_!q`7#Jne!4Ia9!eLyTZc4a)s^M zHBQcJ*B}r<2;|T5k|*_`^QS>>pmrjH%OJw@1VrZvPMQd00s4puep~>l_=^ZcKzNFn zg!J^8vt$JSOe6pi68%a%2O>B{Ktyn~{1pil35^qI6&Nr{P=0j~jbE zX10~MqEeoeL^t)#?xdDB_s?Mr?s`6rNh@m^nBRpe8`^pW#io~|2N(9vg9r%$YES*3 ziIjv`9^@Fph-o&eITBA>nj zL?qMMDC&zM;nRPT@IR-3>n{>cMnL35KiWJGk_BP(B2+oKIU?yu9+_EDs7goZEIkLQ zc2lY?NCXYkdqGpd$;zmj&9%VvDM=1bf<%o!p4~Il(Go8;Rz@qfyBy2yoE#2AVJDMi0o;@p@`Np zy;y!>zsbgOK013qAlu<##v*Kq()UTB{=sVJp@WYnY)@BT%Oz!#I4rjd;yAeY`oRU= zuf3S0cV;)7ot*e*ym?PR*#vfjP6j11>7{E@;wk6%o-A0}6=mH+cimMK8PuP_ycjR; zubj*AI^`c$uEt5(Tskm^tY&>YAU=^aJSE%n&UehgIw+oLzO?$08j3<<*2in$<488D zvak9IP-Sjh=iPR)gyFueMTrajeRt+&=DNp192lLnFASEna&tpeIU=Gm<~ww{2#ss?NXE?~g4SIz3LzdKxN zfjimlIM`6kwyZTiZD6tIE>)B*$)VY)I*rQp5U(&$`H(fEdFXI=u(baC1+^ttT-KeZ z1GsEhRW#-SX?E%z&~)MZnOL>H`lbDGS%!-JwWsBVV#^C+TYJ^jFZ{;F{3JrlKMMQX zt!qCxttQVFVMp;<<uK2e`Drb>IQ%*u^>bVM`Nt3ze4K((>Y@b+7SH zX+GZ#uKIT?zoZCn&D5U0f7#AWy@`UcVBZ$ZZlfeSAyJbVbXp>?)mVJeK*B&bv{Y~! z?!X|1{(3)&xr+4-8jWr5yC<7E>bib<#D6z;B4J=tsOBBk%PVv2DBOde#fwt+cC>m) z$(_#63!nIJSKxL$sQCJ?Est~cGMs?&W^}EJ={O5of^q_}xUQ1BnjKiC6Y=J|0LK)WO%Qz29JGTHu z-{6cfip1bi{+(UdrK7LwG%>{kJGl3ueQrGM&7Rm*>M6PPLCPi`o-UdEW4o-xj~fcv zja>mt&?@aLt(ez6#SH{L7u3vc%1=Nvo&n!T0u~l>;SrNZ`okxn#{j`ei4h4lfW47Q zH_9BhToFN0Umxu6U3oVvku_%IjBFY27N*Xj-nt$>K>4J{RYHAx6S9_nuI6c*v@V<$ z+DEVrL5|J5pEJW^6gJ|+F+t4V(>nP2ZAR5Ettd&qi5TCI&bv1i2q~&^ZE0-oEc@7= z>q+xrPdX>B+Za1aKS+XtED-Eer=(XbSsMDV7-}a=3S1xEoVf8J*&Z&kj!<;6y4#!S zpfZ^Bq8Q^{hMcI2@6N0ooYK|hPmzk1_%J1_TqPEEYI8KAf{ec^L@gmcJSo_KQo*Uc zbgx1xrUV?`lGjpVbZM8AaGfMLqhUoDNtJz&WGeAWbRMHjRV=dRz+0Gm>eeSl8D|+) z?wwOH%Kf~nVaXT-ku3M<_s9CB4Gg$&*RbI@ zy5%SPFsAk zgPntNP*-Q>(mo3;k4i=!!QGxshO+?5PV&|{Cxq5r( zlAUb>FXVj8teXs%hv;p@Vu4dpI<3@w@wBs;@NDUu82;eUm)oLGv7RR&^Tgi0tqQ8V z*PUoAIpIv+cIxTbpl9hJ& z`}D8^SWj-|#L%FgVu$iGB30z!4|PEVL^mXf7XoIK^X7Lb^Si5Js6VtFyY8}n*rhg@ zmu$`Jdgn35Qr&xfSxoEA8p}Lu3OYOm?fp1vw7s>ZrKS7|cjsMcO@Y!bEc-iE&ih;g z{?+lA_WVI58r#)x6G56ycF*j=6%SLYBd6%(a3cOyZ^wdAmP-Tv*$Q>$h3V?NF8kug z176$ve07_;7@<}AW@fS0mmcrON2^8ZR4y zog6cUwKPK*C^Jj+wIns;O=V`7gJb0$qM$Hv{{@qt@-g=Ma(N~xJ$i^r+!>Z}WA`S| zX;!PAdhAf|?hDp~)W=lMWY>(;bFt{ht04lnA6d$cvs~GZ9&V|?x;CR#l_Wmm$u9|LD*dJ_5DGk_Qd*?>-}L@ZdLGkGliX<{Df`eY2s_O zNA*9mW|EzmN}m<~c>5tb;)%~gzJO=$aqyRLw0oT@k(XsAM$HGM^W7s1OB1GjNXHr- z?Qh<)GG2)e2T2h+zKwU!^VnnXts|H^7wwOD8)JW~mww1=lP^&_BJA|0x>*onv|hBz z)NDODgt055ckf>HyGqM!`mkc|;hVnP#JMv3vSW05wBe0xu*a>qxWd`_BW<=AAJ%u* z4r(26f&p`zC!lj(ZF8wOKJjH%O#UJE%L#~Hc5D9x^vrh7z2^i(`xZlm9Xvj^w5w_R z?1H<#3QYEZ>FDY*Y5$ulb4q;Xh*0xeIC4X>yzdhPON3pKHmM58+&S2cnrrB#knhiW zt3srMkVNvWO$PuC&3#gH2NoQ4eG4e-nKt&36@54byS?`U#ki#OMATCD!)R}rOj0@nZ()LOOe-R zn}gq=l^1=QvS}SZmZ^vLA=$UPUZ|J1FVQpjjNY0Ekggp>ebbfIukx0VsF#9Lts58b z8{9%&g|Nahse1VjPe{bu>cg-bn|@ z21i3f8kTFK3hbcgdiFoXl74!2+1Q2(13NN892qo(U3(RfDlk=cmCvZ8?8QxRp!WA` z%QT~!IaHZzzEqUPI;$Fu{4Vw4Y%*2S)Csoj70e;#w(rXc_TsoE#fR1taPqJ>3 zALW@?PIo{5sd~%X*B0~cmrHzv!5)*2F>p{3VtCbDua$N-rd0)wu$b$b5Iue=6MikT zqR8|F^rBjJBT#mGd!XoVYN@JK)bMF}xE$rOYMV3eDCZpQ5hw4kYZh<(h3uOARO6EH8{*m?Am=Qs^`aFI0dVOZL-bAy`BAgcY;MwZHVK3-?O)j>NNYh5}hLr~Uk z#!wmf=<1~_pKXx9tc`-EnSQNp?;{f`Nb=w-D1ARn?(H#f8z#9H*ZVkX8RfWKc`TV) zy{l_#t5YI2Q+VW*S(}@8EK-NH5;CoFhZ6BEc%U)P7kCXEi zuM1OTsZHUmj|Nn=EdI72`A=gASm*I(E=)nMW=l)w)O3!F!p2A+Z{?*t{=jkV@guo{ z?KpZ**ua2T&|M2@rh6+d+lJep$v(dhICQTbK3^(Ke3xzqHQDHvyRe`d`Ju;`C}`F= zj+wcuC|%@mC0kChXdaO{Z#(@?EQGh-eDU-8nOjNmy#6)8b~)1dxm|>+7X~WF2)~Od zxfs^v2Z!afcBO9)V{$uh7Q55=JcPD4vHr3n?y635oz||@#Ovgz4ji)5Uk&=$XYCFx z_;^eR=CLueY)qVhs)#V|`OyPaGUom6V7QT0Ic#XkWOwM>j*~Ub+ITu?mDJ8r zZzw{bsv{f1vTD`xG-g;~Z%p0n1_d8MRpRMQ-EyAgwU%UO8CXur$9~&k_ww*tv~}Z4 zT@@D_uPm?dL-oVsG^;xw_D|22I|LIu^ATn+zUh(S=BC!x{sfCd?LOUkv4$aOX?3=u z>=S89C~j4vLx%XRl8~`_9E&Fthf&x$be57`QXjj46=9J~OTMy1zGeW78WEZDp740}j3J(#&7!A0rWOE*5GWfDd!*)(jco{YaM~%a*^y{5 zH5l^Mw2H89vbzzJxV+Ay>%N)&8D=)JZs$w%!Lvd|f_m7RX)?QIV9c%P*kAywm}K*L z%%PzDjdpt8$NM}+#{sFZ9t&xGcv+Rv$dJtE2OXsHz%(oajWTazv$k{;I0ZdFHFChG%Mdrz%ILy3@9r zRBuYeYxJA&6lq;scOvcjfSU6_0s#TX5=Ux;R)m@xu0&=Yi48W}oq790$zg?`lD>H| z71DWQGwf>taVRE`qAZ{@sa%XqeX`i)-D#Ih$r@G@-=+ipVZINSg*Q~S$q>0>_hZJR zm+lO#elH4n?@imsB-6>CyDC&#JKmx)ZD{>9uLu_(>`{7&w;TP?DG@PRz+zohAX-ONyZ?W|<7D{kf7oyN0s8+x;>Az^ZU1?c_rH%93;rL87d!u7 zy!d||UVQThUL3(ez){Z>6_pYz-G?imxiuJ;WI3`cW_VW)z=;>=9|1t*z6cC;$7|p& za>YHblh4D`2}M8A)_L`EO9ikR2eqmBIzX#4DzQ3!kBj+KDDsL<dz}OMud;~=5UhyS)fOjR|L$iFfM(UB<>uJW_biu~Jj%RkqJvm(1(ZNFAW&IuB zvF`-~UbeIvl|8(+<>v2N7H?$Jab+2Y20o91aCirPd`$9Cz(R10y35+ zB6p7M1%sSL`Sg`N3yAibvL%hr6UvY!v|HAub52-fJ<{vFC>o&W6D>(uAWC}dn z+7xMm&oa90dkL}ou?5_(q19Gh^0o>IG091sd*r{F^|=k357+NfX%&tU8|v^yXKUdU zo-VnvEsI}>XIwGjAkXp)ThHk_zEBP>+T6;b?cUhEA2vxN%3@1vIX`=5t?-cEee1yW z1SC$)%97;JWHbC~ccpOq`XS$eTy86^NSR=d5rqTz4YF`os$1sYF9 zmbl-G%nXxXi_nQN23&42!0YD%5G&%0qAnaa8h2jyV4oxXHh@=Yu^KFotM5jmSW^tp z@S4Qjaucd_Q?UVYDc*owTGYMd6mttZbTQ2rtKzF4rWDLJL~ARpT_xY4U0%oz_=SW8 z4T2@+MB-OkbB8>8D@#j;R8Y={$@&NWbFCQdTxL_*cQ(k?w>cWYD`g{PeTULsmFXV* z*;Xw1)pm}jrN8P9?kJ;NtJChh2$R+;DvKF+LrhxDRS|BHI(e9&sIup0CtvkeC1_^L zQaNv1cKez)j&3f@SNI9!9&0mFr%v60Thn6Sy*aiGdXsf`TR&!`x8UQU^~ieflOB}m z7kF~kvC}f?>d-(!h|`l4LvsoWKk+ItI;t8@X&<$G^{)I(4iBVfNxcDvo+jhaqwAA%w9;j7oUYq?$F7f1rX z8yHGrwq^|bS1ytJ3>Y+SBuwu0MIGc08hhkd^;WerS!_?MM%1V;$Evt!EYSoBlZQOu z`?wcNHu|5%yzyBNiqNgb$6z*IArrHjEsorlp$FUrv+uP_<|+G>YFx)3+&S`QBGl z<+-0Ay4vBJn>@Cx663`d)|PU?^I=YtSG3Bf8%j6rWux|7$X{N)O={%VSguc@;bjg0 z39mc_#i132-_KnqId#9fQ~3juU1=<*P;{%Ui(*^GLE#7HC7HBdI@viOF1u>tm&DC< zWwLYWy%kF$XWN2!;~Pt_da<0IEBUd3-Ut9-^-=H%Qt{|;oVD&Oef8O8p?tkxAbVW* z3K##bjilRs%q7Xw-4Dvg25M5nE({W$-jd*BVn}{N=r$A~f+)6*VR-94210K7nKEAP zFndvC4~FwoS#R4%ufF!?ZEs4yUSsTgY4-$Vr?%Nqg(9zBJOL@fhH(zhEaEU%x4rs1 z>>>9A@@EI>bqcGqfgQ)`w*lXP#ctB0K4V(|I+_8%+@zzU3%VJ=YBnv_cyZs{A>FjM zIKGZ$ar`_R;D@}l%Az^ez*e6&f7k7QXnsm6??h#Aulq)`xef}n@$FP#g$9b*AR@Cb zKEjQ8*BEotjk*lgQ?2@VIO4L(80FypRW=5YCs&O>KbPjBr2%7tW(9J5MeiHjwdy!ufX|o974BA=>)BJLYR-{S>6gwHy-pRAs8P8|epdvkoLJ-G zO_lR5EMsY^@G+{L4{2%QI!*&GQL!630r9W#BpCVakc)@JVwInH(|NsY%_Gis2z%R54kM#d#R#@pZn(Omc%-@0~W*y-Ux_Jh zb>FNj4-!X~N0x-meyZp>_whzRQQd>toq!l^=gdfrK^}hHvc16OoStC-h7=$w=L8g5yp?eyYvbI7~pw zEOK3ws z*(abDVAnd)q-4NW>jV@dQFjDqJ($bZ#K*!RgK8YC1Vni4%H&!?_U8GEXHtOJk8L*6MSxb2)sQth*_G#f#itc>&10jxtK=fn%g#~McE_WcLn*S^Tl#F&hC+z%o~ zduY9S_0SqgHxs#5;JM|#vbn|GYVV;<1!{cP!AkgIzO=ry{+u`It%Q!Pii6jo_Y~${ zD)V*P%+1;v$qK74Jm;_DT3*nqY3{VoMDLMPRV-!)bV_;SUhZ!^DCXSwV#R#QMl$fo z+~`O*_VQda^-%cKHjhFo(sz4;Wy+z2Rdqpi>)s-`Hg9L-w3~ftV|7P(^^*^l`Zq{v z3tTxKTs*4#)cO4G<)B6MZcH6`)09L^jt;AH{7|p2&A~}OG{=hIohN~&@Uz6Rw!6lr zxWK^V*K9b&9OAQ-+VQi@E;u7#gCV>amyvgf*F|7$0qCM3L?$0)j`~t`q^4OGu|P-9 zk$99{17)P54T62s<4!BDaAa~`*C|A%ef>=pOKE>!nVgDR=FM^b*b$y%oN!uazK-(E zcaJU63gdI#69!rH?h@KY4cOrTi~6*WV}7fixBbNn^kK}#yOpW+r45`C6)4L!g^X9~ zafhON-EvoUo>DlI%rc{cEP9-rodq2M(A1kL4zN*oZdH?!)oBv(25qREHCLFDAWwH z52DuZ7Yk1rNW7ridu)sS9E?Gvg&aMVSn_pn(!eTtAZjV>II_(ZvZt+fe5Wc_PleDr|QSW4oZ0S+ahyD zGTK|4snTik{RW+cu%bhrOYg%j=%)5a;plD&Pg+x>5v+p~RHRHe;?VYK7l^noC0!^zpq<$g2MIsg>Q5_{@H#5& zKLO2L19pY@OzgPq@y~0u55Pz8@yIfcvv4( z5*yFE=ebI={-Wc>RxxwLCv>$#jvIt5B$Bj&$*-v+nL$3Me`lCM@W#zHp{jwZW%DNs z3AZbpvAuDY0|K{8jL2M9BMTq2pzTNZ^Y+#%0+v>Qvp)QinV3N5yPj0W&4+jCDGzC) z4EawTQ-5S7wtXRu9%=aGOPg<$E|i93qKpxGswo~D!JqMZr>5X@WJ;V#DaxW{sm6zdm%4d1TD0_yNz{jPV4A^yRp^Vh}F9&Ps3NPFm9*s zq&uFFQSfA$2g*stX`e>YRmdTGE!sn^ATYNUz-iSN8RJbA`ANQXl^^ajcr?eKCFtE~Kq7tnyLB*jXanIM_+7b~Af7D{V3 zvn|FuW%)1Y@ND?Rub03uK1L%g>~&0IT7%v8!{3j39IL&)`}*k|$H#|_=PgH1!v|wu zVsoI*ltrH$)8grGjEPDxeMQb*sY^ks8z)#O8>Ee3gI7>nyQI{nMqdRyKX6z|z7(vlOoD%qbk7Rrp6E} zlbV=do*~E9%P!>O$y0J+2D?z5rHimZl`rVaX(A7>IfHiL6RIlMzu684t`tipWxeHr z9+*?kunLtmrE^qyg~{h1CPfFZD^b7_JN4>=1db?lukCtx1=rBzIIBDIWp9H-;m+`v zo;@G}l)zE4VUVNFc!^-|ntr$`)K|H(xdm2rUB$=cL+Pf@lPFz#lD3g8u`z<9EPF0? zsv+Rpfo?+_ETB@L@Y@OKUH%cU_;!b=a<{J3CSIkcl-3)B@AZc9dpNn~m~4F7zbu@< zRn?k*<@#zFGF<1)1SCHNL!b%oauR`bcYhs^pW9fI70yeNT(yLiui=u0MUzYiLgNFX zs}| z;2$v=CaCjSH4c8f8+4W@2&7#2ku~DWGV8XsKkFzCU2S(rmsz}MUv9f~RVwC_n!>J> z><+NNd_B3YzG@V($a4Y$*NuflsKLzj^)8o*JXlt^L9_wLGU^2MKY^Hk`*8M;uk8PJ zBpC|)$BF+Q#4H5-uauvkx`Khz$v-|w{XgtTvMp%s{~k&HUw0%~BO+EK-qj2^`GsBU zsj~Ob^ahTei0GdpI!fQ5hF@zk3x!xW#0pn{Z_LKp1-*Y6^<-Xra)v5*_5#?{;)> zYZo6e!rC85KK-EPFJq@Af0@IS6uQCtvFyxzjFF0b4#>p^MJR9j2=ESDILrEbvOHWo zYzOUPF7z^*PgTlh9vF+FY(6=x7k2U^OS{}iht`|YwZ+2Z!pd{~hQtG&XhXSV9Bt0i z?;kTsBvR20aB#BiS*T-*dAO5|w%@epI4A;GYX!lYi`Xap7ca@Q$;NO z)BX39R_g3Q6#6T(dA8ql8`@y=VRGz|fnN#gjA|xQIU;l`voyIc6i7@CvP6DW&l+=w z1nr-lYQULR0EcKV&)PxmG)4+Hfp2_KPEn|1=c{+O_IA0AJsyO7z9?Sdao>P*5?Z~_ zTi~;&hYXC4(RQn8Jd8$kqV1YG2hk@Wy6ufk+xGBS)&bD~t0PO8_(dX?WUfY69ZLH? z1{QTRx<4StnX$GZphNCREwI495c%CZorgtatb0Rd_x)a`M>AXXG4W&IFgbbZsP^H) zFb5~Qw+!iEKn}eY!C*)XrSbhpoNEw&mU9%^hXRxMh`xqJL! zLHuEp{!#_W%DV?8fmLaHI|$UM^woKAFBY9#dRVxJ+siC*o~IrS>N68!jO)!S7U1ju zXc>Y%79CbsForw(zM0>Ycw&Q0{78l^&oo{R8|k@uNKD}{y0rdm%xtaqJiGj57dhOX zt&+Eu)u~wurDS(fOw=`WiCneMorlq}N}N=!t%arfMzJ+5pwnIKx%^i_7qlC^EeB3O zIr+;6wk$#CA1XYvI^LtS2zcc0n9Ssof1_bzUEXir#aZUHvRULY0H<7tUEvzks7+g# zu@)a1lA;$pBn=L?p3OpPXEB>*KXhQ+_0z?%G+dBc_{-ZaQpIx_cM-PsI@} z(Juq+p;@U;K-ZNI)__C#_9viCbmIxA?G?^9VCPMdY;(G1D1x8sqe$IYEKW2*2sdxJ z2R)43_s(C&QMe$cn2S)AvcbTSB_==FqfFKX;C!@d>Io=Jh83q5zC8*Y{qN5|a+0t; z?xG;F2KHO%Shs*Js+q}yOEFY?0N0-kn`PIa0&_vQ;QkDwjh0xk2LsPJ7C~n| zn^6r;=H&TyDK+|u)P0?hOxg*U$enEkqn{lUb#0lYX}L(b&V0p0RVhiUvF6HJ@UI3{!egQC7G|(H^;! za3%ZLsvVIvZq2nbEE-wjXMRMuS|HoprIz|`e)O(>LZfzC*UL*d?u{5@s{R}D((Q(? z8(XcSw~t!1Tx90YNl;Nym41<(k-Uzq&y!jO4w?03ivgm9GB=Uu&=safnl{#vlH@)` zDPc=`k1tkF?3CW}#8@n98%Q;dHpL2hy5O=(P9BRMKN_;tp@AduXh6|T_tg%U$ z_fOuJR&9xet8E^fG3y$j%BZXn&->iKC3NoIJbLMpR6SNrJYLt*&fKGxsWm(eNlOr89yi8ntzz ztW!0Tbo?Q046lH1P(`1^MjOsvNk?v|KNVp|>v)O=To&SiJB_qFE%78I^t`4<7*Y|W zNneR`AQb{fvasHdHx9y`~KfG2YQTtXZti1-|2^MV9jm zD3asu%%wc7cee9oo6Tvom8Pipv$=gQPD;yYH22*$es6Z_>+614QkV3dRhfjaF-M7b zP0iH_oXy_#4qnTH1&l3jL1^6zrzr4S*o*-jn#x+bK*iIk63J_ zq5hth?NN*NCdV2_VZcIqf70RGuFA;+8i#ZY?p5EKhmkl%Z3`6fhTxo7!VMKIE;%;; zY6-RjX?6xz?I<;2UF(@y(eJa-dgQu>S%E zN*zS%?gj2@QavmWt6NRTSRGFkUPp{S zx_V|=u0Vb%Iz(RP>}%uFrbMZusVC>}18w+Q&)hGr9R=)5iYE4*I2&%Q&m+7G9|dP0 z-Eq$;M%z5t?!5do;Ot!-RJgy729*%$qF*a!C-*dL;) zFwbJ);B2Zd{ZHd>5MjN~*2R!T0{G(nGQ|Rh>z=^Y2%o2fPYk{1Q)yr}832z3;^it zTA%^+$R7?4KG%?X&jM|Bt~^_G3JNTZ+eb-yl<8z(?D7*S#nLNAfwQ&3#LIGAw_z ze7~8(2-Z+Abmq3pTG+*O`QmKr0f5!K+X0oY;YsVlTU=+V1$Wa#Vg2F>)G#7!k?l93 z*-HrHFbC|f)1{MpPXp~{!O4DbMEB9Xt`9i=|5QWZ+y1|)Va_XB&Ii-ZTMC2lx#<^8 ztxxsMTL>j^g&YSyFh>UXuNaw+GVTrkn`>_Xx^}&L@c(k{tus|SFGaw4i6nZW*C}}c z`u|v>ZxCR+CY zZqHp$MBln>TMWbTgxpO=lkS!@I89a5?C)IAL$EvD0^vHYWLa|IvbKFbByvWR-cf5g zBtBs$tERexV^Fz6z$n)S(TBkb5e>;bcKSOc8=54A)d{d?_J1l1FqIOYL-uy!Z^u`A z1QV<)I{Ct50j#%C)0i)~KweDwo4~#}AfNsAxs;O{M06K4H!X+gC6zpt8c!cBD|ho5 z-&RwW<<>=2`sUKk{3|b+aLVPF{{=%@#_f>e{_wL#_WZ;fBZ89N;a&vy;`EE8k8px? z_!HV=)p7TH>5PM4>8sq-KqW<#(aD9Cv!~F-d-GYHNVmHtQ#)a)PK%?B(i-t&0@){^ z=O^^yH%OIu!P+xVzZ_(B8gBZgN6caNS?i$PA=4-*w1H^DL&DVbz3XeWJoy0!vMz^& zzmKcgqNDrGYrvDuCvYu+>gcZb0DZpm2HY*&i2i!&5q;8AvM$;m-=?TBixs@3_N~a? z=HH50eF(`ufeJ~T_~OZS0P48bH;4c*ytV^|*B^avEnj}Xs-%OSF2^v6E5pyx&(L>Z zDGboO|M~{mi+Ah!Ums`q1b%+<2G6I;{k|diIs#%T0)E&+#XV&d7WiP4DR-9`26^Z+ zEdLRv_JwWpDV;*{!~#U7tNbf2pTa4?y$zp3sh-;bpL6XtU*bj1e>o?7A?_eeEXV%3W4^!JiHTEhu7Kc-UZid?z#-Ej z4C=bQ36+nZ7>52qQMkA1>KfwvR`pa5+;|+mu>CNNk*la&gvw&m;O&H~dWtw#l|yyb zlYyp!d2eRldT)*_E}r?KE0N7>e?LDD#pS{rwC-XJ|!a zvjY#|o-?MBVa|MhHD-=l2OMv_Z1J1cU-oJ36M-?Wb2~uq`(fu@;{yy#H+-&uo)BNJ zVh5Pne%^UkMFGaUI$};!g~;b^ngt))e`k5+^0N2m$`BAMeKKF=(T-zI2i=-V0(R|i zO(s~@xpLkZGJJ-K1RMf186C26|CPpjqo{r{=#S;KyxA00qc`u>^}5D!F=#sxmFcPW zy%QV1(i83WNdM(?CV<{?WOLx32C5b90eLM zE*xFq_tx_yTZX9674k-lvUUk|PviK!5Hru%X(0!?r6R^={N1{QqRiTRk04+S7;V#4*u zHS^O#-VG7vit=06vOC55Rn-QzNoRRs%R#I8!ks2BBXF8D2L71{m{=KhrSpG0KRm$Q z03{E*g*iXFMtmKwue}v1=7{N3HD$wR2%rMc9{<_2d1F?ztpf>Qh0{Ic(X< zLxg&W)yck!Bzz)2_+Bu9@yE`!_%}#KkHXe9<`OW6 z0!%g>n+@;}oyY>bD^J)J$v1D_@8chy#z5xC_Dl~CU(=)kPDG8(Z|uwOTtoex-Wjfy zpR{8(#dF73{E#haSy4g4^}i~uR9Mt_hxr$)g-d-RWpHuM93u_x`64a}vkZ6VY7Vu% zeSrPL4m3UgJnrf2!prn$#*ZoXGY*toxiu!OiBhlk4tjy79Ri zJw+3n3?+Dm0d{`rK+Db4lU( z7*1J1K?LwY3j6Kj+^C%q8AAu+0$(_VId4A}607+Xjezi66Hd~zJYLGXls4lci{(7n zDcagujE_zIczAvMl4u#?;sO4oF~M{|x4Z_^T5kscz4h`roxQcQ`&OdxRu!Vi(0hTc zfp9s&SQYaQz)Xr~<_cmACup4xAzbbOB4eIvND5)bx-a?g4>tE{=}($|KERcS&xJ*J zcRF7z^m0}$-%iU{7hF~9mfXqJ7z5I^JsofvW~Sl&Bt;lLJ(ah{`t;_yVjO@-SkQ|b z`h0>hAYqA#S=}u&eEX)YI%dd;mn`p7s?P)ej;2 zQ@QIY{rS7!YvQ^69ZxOF{8MhKLu_?pog?V?eYw=eO!xa2^QzvQIT(K-dpzE2_2)ho z*%Mbu%LOGWw?5}4Z{a7jGo+ipRJJZrr`Yu;qa@G>dVHehgar6E=aRPGo(KG6dr0s@{?)Q0X) zKb7NORVbW}cgj_5G)wYyBx)+#gO_h|Z5-51Um5J6uk~2T{mP$IT&qV$Mu==8qCa`? zc7phZ7QM?pb?g|=x-4*e;HukOkQ36+)HQhF-l(i~F%E`1%X`Q&=R40Rq^Y)?DtgYl z#3{Zq^M(cnzIu*pihC@!@ZpQufdJiRf#~&4y&(TkTmR5Nna6Ux5V`Nj?Oclb9qas{ zM40hSE#%321Z(BFyKUjC%+X%BJw|&FyvtEXga4#$r_Wgj9{GHlw|VQC0tO%pSKei?E8}IfNYYP#q>q&2mHuD$ z4F4zZ?Em13We2j{{_mXfa>Oi*g8#uZZw({>2eO1Yn;9Da5f}QOTtpIKnaOU-N-mfq zJ~m_7u}QT9hKO3ewNSCYE&s+5Kq$gU!hoU)NvuK&ia`Dl6$va)`n7E3C?+qO#ZYZn zg|zH-J`euHbbiXwh`AD-g~cJBC1+Z^a6@zh-~_nb+-&W10kR%Ia;b{So0gd1L86v( zXQU#BFfo1@6vBdl!x4j_g8Xu;VxZ?Hf!>WDmaf&@g#C!f)`gBW>mrpI?`69E4x?W= zLj)1P;ee)2Y_iYfwyQ2@Kb3jL$#g49q6}6LDS8ZT8m_}KhWp*U_1DsV>GAxOhGvW9Hh&POHf z{{RLu>bS(M{f2~>hzPY%M84E6)}^Up-Me{2m`cs697#cQQIQB0z$8prlX!-aTBuo& zV1@^p+VhafQM_*88W$j%*ViZHLo~-7^i~73oyFVJ3kj2L*@7SNHdg#6YP@j1u9!7b zZ!OL9^dONn5WV-uS{W1XAWV1N1l=#wUN6ONH0;=pf4jS%o}T#3i#A7;XW8F!ka`4e zI%h^4H#6?_?Z$tl)s6e6om19Sm$RMMX=ce(4+ioR?CTM{Jh-`<(o7_}7h%SX4wT4L zsq9@X|6GJ}YQj{q_*O#qF;8xZ}6|NS_oB z{pBo%%=7R#( zFUT4SJw{A$=nGc+V^olH7KFDHnm(7n(T}i}&=PDwpBo#zqtDwGB{CqU5B65vme3Qn z+D}U#O(q~jABqREh8Tua5Fr#^Ll7yJ_7L(_*nk=>JmgOvT`9;A-8m3D7gLN_^2eLV&oEBe?k*U+zTmI3u(L zDdWUP;S)pc6%jD<_x_=!M~u(mHey_fhRo@l{(WS1hs+W(om~O;NF?uy!jMV8M!@_O z;?0X-l30~|3AGKjjbf5@l_E!b6#-=qOckLcRz=AO>l&;Y1Rp#YlpgFD)IbX}61^uC zjh&bL74Nqwx-PUX{ub$&#wzPBR~$z$`gI6lPog1xO^h55h~=lyque3u$*-lZPGpke z5*0Knd{i8!9-+pfSx8hXP}$~{(~ExlSKIrl!tIk!4T$BjA`fiE(iUu-;TBw4@t zm$Oj(kMR`0ok~ZDF(2>j;*@AUW}zpa|1T4^b>u%Wl7U|Jk<}@Vp_lYKAluThA?0tS z)TXqkR06gR%jZD{nCk2m5jzy2jFoTYU<7| z?yz>|y?C3H-A%q}9=F=O>hAqRjKK=~3}@ZJvRSZcu_?Y)r)yqQrJK?TyPdbKd82rPC)Ca)rcSj~u%}xV7uC z3~17e)l1q*-JwSnPoz6XAwc&!xjAD55Z(3Q_QFWO{DbE}yS2(y%l&1mwVM;rDrhEH z;V%dENyb60PSnZ&A{Z(yAvx+n!am4j7u>4$;YLlu%GOB#crc6gx9@LoZNOh2WUOGd z;2YSU9)VhcHhs&!EtnD*jlt;9F$ubQ^83%xF`x`;EdE<$pPQZwKh2s081NYUGnf;n+#P#QDP<2(KYLo=e+02C6Q8WBh!;W zGFm$JB23B5LgqmJz^IX5nOxbhwt2aK$-5@+VBqj(Z++B$g6klE%={33?|i~L5i@Z! zlcRmu@oz2v0dg7g+@CC{u)niEE2tsj0Lw;`N9Tv~qKczen7-sdyn}RAiYonQVX6il zEuXRw^%&!t(tQA(4%^QRpa^!Wagn8rvQH5G%*Ox^haQJ9?zz|15Yg~i^HK>kFE|%; zQ-0NdC2JDlt^8^xEi>>KY8ZbUN2c|u9IxTZC9#lV6>q|~DtdNo@^TV>qR2|AmS)#L zS8XUW5zEQn(SToKDC0HQ-Q!onio7098ZK)eZI5d|d2)LS!CggM4~CD*jD2t`JnT29MRTRwR@LcO{z#2!?MaBP+YOe5s71)~ zS8KK9mWZD1PV>{?)B4j2yNK25(#*BZc~1wk)xjR9Wv<8WyX;WLG$Is|^^VMz7UZUGWN2va{Tg`3a|>NinB`X%GoNB zs@7`y>bx4vnuuDETBq9cI{mukdg=Q929Ad6MvBJFCiJH8X3%E0=G&HkExWB6t#fTs zZT;=s?M)pF9VMM4of%yiT`}Eo-GSZTJ?=eEy|%rVeWrZ}{d)Zy1L^~dgNlRGL()T| z!(zh&BZ4D6qr9V?V_aix;~e8H6KoUBlfNgMrhZQ~O|wlm&#=$5&T`JS&+*K4&-2gs zEr=`(FG?;>EXgg+EvqcAu4u39t{SbLu34_#tvj#3Z}@IPZia86Y$b2wZ|CpO?9}YC z?RM>n>`m?~?Qa|y9$Xwc9DW=H9wQznpAemto&GxQJQF*cJ=Z!vy0E=?zYM-Yy~@0% zzHYn`xS70FzdgFMzx%$Ae871qd1QU;e^Pkbeztmk2ZX=iyp+CjypFu7y`8+fd_aGs ze$srle@O%LdQ3%q2*|2d6=zYwG*VSRH&n|CmW40&iLRaZZSD)+ldK)i8&CugebiA14wO2s z*$t(gyDO$~9B=`$0Nj6aaUL>Z-~udAN02P5j@Md6f^H!03b;xW5@^Jt!&{Q2oj4$1 zk^5o|8%irFAh|4l^RRPwWr~c*gyW#SEtnwAx(Er~MGw38o*;xj^S>$$6@u%7?(so_ zQK_ql1~3ch>Jgj-&*&aK2V9W?qN`$-L2~CnizF@G;nbFus^NU0OMQIFXV)gv5Yrk! zHk%*>^5Z7<;AeSeozzt5PVQ4Teb*5 zHFVYQj2e>H8IvLsB_0J0>5oR_;tQJV`X|MLF!>W33ia+Xlj6}q>0yF=lAO8V8?-r7 zqH=hyDx<)@_=4|)qQlHiNOqFkm+^NR1bK`M1%Pw&c)yx>ra!X^)yNdOqlVY+Pk>ak znwSB`8AUI`M%eG!E37ZhRX3$-`x7FtsK@678`c;7T5#uGu2KQUI)enA8HZenh2)NPPp0USlj1e@ z-4B5FzCjl`vzZo;)RKumm64R(u~dTdtTS@E7`+gZ;ee3+O`#k|lwtn>-;_oqR1(>Z zw51%F!yKgyhk<6}sW@AJXTH5q{WH$kD=)XW&t>XV+GEScKceLvKYYeo_rQ&~c`wju z#=~r31F>1}uN`$Re*gqhR0JWArn2j|o?AuyzV6mD_tQ_xJ+caDBc*6%%dE=8LoiD` zr>`n&Sm?|pe~*v*Fu9PdFI|Xa?+c)Ny(#SOelc`EJWl>KSG7`Di9`zk6?LWiv9~{N ztB!zKL7gCQ-#m3Y+J2TTVIS}_j(U1iy#A^Cre#lSgmm4H23f_To z<2Q!~b^ob%v>~b(Xs#ztB}@S5l-XnEiRn)}1X*;%w}MFrb4}xp2YfuWL({T#zMXBe zMETJB@?J$iqyjC~Ih#`&qtzDR{4(y*iz}u5E3??R-j0z&bd+C; zO?ly~5umzA9(@poLEZk*nwY2jn>B6ucDU^I4Kt$E2WCSfz0<+02$z^gA0eMh<1l}=`u%y0$nRm z4%GOO_IcvthXRONuQhZCfs~G%Qe@o-Lk==1!x{wc6uW;lUP#8cq38(AAhq~t^FQ&o zdTbt~9CQkSO%9%F91T3QlcXiSBwJTU(1-+qWxt7IJmF9FHdip>3N%P&d_w8i_FFU3 z1d>LLQ*$_SCAv}gLm!fSev5hY%OcuMlJ^zw^xz`sKWu%bM@t?EZ1OXhZ}{Bo9(&rQFg>37eD>KN(=nm2i`R6r5MWW|V=A1l#dJkiMG_j_MB_$5h zF?Jh^o5yqGwSR<<)J86hlm2PSvsy|8=;^#S{J@po4>)bgk1Rkf9^)$*Le#pH{s=Ro z@miWc-}9n87mkL;DM!sioBTtc;hV{xbO5a3in7m!`qtQEsSZQv_ub zFc1}i{%6GTFYjLuE#COb`p-L|1XaO3J8C+t0-^w{WPv?DnW0;)O*_n+(CzlBa#K&h z^cK(f`^<{`BNg^Yn1wnA=|-pNy>I(c!~^!H$Cy+C9w~K^xRDK)UfrXiiLo=yZeh`hAy~&U96j6 zJ7h*@1e!r&xZS*(s|Pi|a&~e1zrEk~o?=sEZk9pTTM#*fkJ>u7VaqnFPLQ?V*MnJO z*%XpCbCjX1hq(04nzx^flI)XIDoreNmgHSRU;KW$d^|BlB*AuzypB5jI?~&|&g1lO zLv4V;*PrTlIhki091a@={-?7j}d8O5h zMUC2Xn#MB68UMRX#F3^Mqn^iRZvDrsgzD1XnvbFp-uP2Qh@wb(ToxX+$dX1x$9U{! z@}HfSZqK)O&&G$t@w->|jzjmvi5vfnVYXdoF+k4`66F{q5^}yo-v&m|7L0oXf)g?} z6y)|_B|`i)+?em5kKkH_=`E^7qUbiu7w-66jvgfmv42fO0b_6mo|-t<;>K315hg8pOe_7q2YmRfFMoG}3_z8HpS03L@61T)awWG3J1r_;sI6nUgjFjE79vb= zQSnEv@7;@MT`_ z7f+3HM4TVw%NZoCpy|9 z{*F{!w?DfJO32rk#>ksE8l{pta2*16h-Il{7LGp}whxXXB&&1@Ht9&2sQBG`AoQ69 znC#P6&GF)#mBh3h_!kgitCJJZ?kL|Bm1fp3W=jgwq!LJ#w=S86n#8I>w1cQ;)li!PQR;r z@1xpM`hq~b7T0sQ^J%IsT;%TuQGVu0uVr%&d*C`=3?d}^-{(+`lFsGB+xgO>K&6!b z=rxcN@>7SEg0aVjFo22zRlGg$U}|_iovvN|o#eDeNxF|vQ>FIR?olS>bS7$JRQ-f$ zI0L}?jr6^r%9{kpOcXGnYrbqREpaOO0B!!OaB+_w)U~mp2Ak2Xv1xw;=RgE)b2PO& zRZ7d1n=qSgWJIO7?K4Bg%=2Nk5s%ZvTlPL@-3Z<$b%?Q9o@Z#sLy_%O=#q9d=xzX_ z5#-D@Sr;(}o!-b|Sw%^8kx19_beyV*3<)MKr=hAQmDI|c)0tB;WTs*_Ua<0y9182u zN+mOo!nT6r8M5t!O%#Kk+Hz1g4+%37LBLf_iT?2@O(7$Chm~5X`%~E5&cY)3a|uYn z&|GyB9$5|{4>`Cldtb;vdtKt@uN?`2&~T@by5|{KNDcCB$^@uo`anA|^@Gas7MYFx z2jd?c3`NfO`S|w*r~e442iD&Hdj*f=YcxuM-*wGLwm_~>4UZ^g!L`8Z=e~Y3uTV>E z-6dnTI(^zFQ_-blbXVHR+s5)VPF#A8l?{zT$Z1nQZK2A}oOnp5RyJ#)ieDBHkF_~U z24Mb8Jf4Cv%4}s6ko!y2AZa8#$_-JTc~Sz5dyWgYu8VdAgQx ze$uV3yU>AbSkm*On674^j7ig{ZfLm0qXD zm3)^;fN57kg{2{_OnQndziV35eHiUMdyUUZ)OlAxnruNUjKhnF41`({T_50KO=!P^ z`lCOwH-lZZEIRSL=}?=a!J_N!HN15xowy{_hANk{jhKr2xMOq7nCR2z_I?9mAi75x zgB7w7WXfK~A$bJXhwjW%xcU4Sf0wv|8<2ee*5k=vSUr+S&$*OvN9Sc5%p;t^@@0Gi zQi7FSW@5G&ic5?zVq8r2NAhMi1Nlzn<=H#l@3VE?RNcgcwo4}SSZNhaW%`ZPmNie^ z&krN=Pqv}@MoBxJJ8V>|pIZgm)XJ%;JqziC)v3wp_)PbytS0FE_?Eicx?(YH{^bi@ zwT<8-7&Rq45%n5seo@~8o))uD(JnXxq`xC6u`x7y=G61vkTZ0o5xWgDYm%iE=^pSa>N^KAxw2gR z!6z3W6@!3PYt5|Bf%5$JZJ}0meb$V-tva^l5?GsIcAx=66Y5h#j5!dWk7?mYh72>X zBMKo#{pb3mr%4*i!Lbq^qVn!WiW@H`U5zS~W39n$1h09xX;T$u1=TW%!vN>zXJOxq ztqBk12Pg%5^JM@5)30`qTo?Zlmv)P5gap}E*_6qZds zxjMH6tyG{_uoS3c!;E)+aWs|Clyy;ol&wh0zfNcugZ|vHVvyjNw**U+jbU?*4y^u5 zV&FD5_IFGVhzebe1T+O6F)}z+VGO%X9Y$~=Rp!r^w*r>jIT*`pl5;ACM1u>NL<`aqd zI@F$vTahQ6%x+%!t;Ro7A{;^5Br+IV0)tv53|l7@a?jDQ?PJ4z!)k?2I$nbyZj%b7 z;9=p7E4W_k$ka*vX2DSSX%+9}!5x*Y0z92a2ox-q1zvu!_`+7iT@4@{Ps}YblJoSg z$ou{p1tFh~UUs%ub*gQuZFH(Fax2$Vpk8KRwV);FzWnR>^?)ZKxkp`$WnmcZV7$e8 zR930Itd{-^adaH6cIY4s18MPO4bv)3KQUJFl#?1Vl68?R@GWIdSb8>?c1MSVap6km z;;|u9XMU#qhaYu`y>hM1Hiy$fI^HMyiGut(UmBqvYST=2| zL#MBpwC3>j-NP~}&5DZ6bYmMN3+0HRqo{%JC#a-Im>%Fa4MV<4xljAs(>OFLW>Q{u z)=@`^If@}LxVS@kxjBa<`MK8FOQoV2r*g;Cn(uhW0ouv<=Jon>#%KHFZ{ujE*3hS?aI0@&d#vmEO0Ju|e8rEs04_k-g00r$e5!IGBOlATEVG5a;Yx4r zIY8Fw0Un91hUodC$_1J?%g1cvsu|r2Dk`o0b-Kt+@%I5TL#fvCLZbVDiQDm913T0f z+_!;+`*Ku2x5F?Pv0fCO0H}g_qR6_KbL9t%Yp@eQN!?eH=>QcZq9Xad807^gXR=j{fq5>|AJJKWrl?OPS_q;G)0jvkR2>F7R_(OQymI@G+tMx`?R#8c#&r)! z(=BFlzt%0*3&@7#g%t-_)GchFAeUtb+$w~MzCVqK8;(89k9?bcm^GNszgSV-I5teL0*_}B>(&U4W$X~f+TXA)X6{cfbXuxQBD~*HDl&IXHs!NV-<+tA^Q~?)sti-y-f6Ck$LBBoukVWkG{?Y8181Y}WR`Oz;TnWG zANK2)CB?c4PF^-PO=Ft<4j{a}^qyEM5(wDfII zGYV2?FXrv-inx-Ow-E*$m(y6s~J9At~A7tC-+U587)>BzeMG*v2qEbMc6& zyuocy9}{C9MYIvgekrs0>HnlPIkE)b7{y&nxnyI10NG9ox#!>u$GSD(i(%Z?YMtgk z`rb`>W&B|9`|*|E5{g;ZPbn5=X3)J#QXNVw)ouUhweQfzeY$ac(dez^)#dnyLo(-v zw!Zg}=c?!OjOf-Y3BJF}UK}*zJ;r7qa-VxLHR&pibNHJZ?vFbev8-`(yWd*=QI#?E zv#gnjBX_e524%}c4Oy31Y?-F}>XSifmI5Kam5}c!>BshkTN$DitxHDlg_MXHjID|V z{t4&{kDZ8rwDb?l_p^V1r4KQ@sJ<9t>zcm1VmkHN#pQGKjkgFBFpf~zhH`>@+`Lp3 z@e#(@%xkBHg5U9LgP9(bMFij8=u_}abO&EJ#;6TgsFR!Yx=RegwSsw^sXnThr0vV( zy|AnPElpl>fqw)j}leBdpL^`xey+7S@Kadh{ z(Vz$bnTi;*FvS^jkchBcZZryU{P#qiVPpQ)vuvFi8;KunagI{d4Vh=3b~FgpzUsrj z{T(*Fc8Q5#xEGLkG;{cu)h1Zv##NP(<;8Lw0A@7dRnPt=9QFY&<7-$uQt7*4*-#>z z&N0tNdIT672tky^{pG4a`q>D$NkOrRxOgA5F))1lH>cVJIOxhJrAOWS%PZ``v)rX6 zv9g=n3-Zn2ZIU45gMzwGpcyQ^WT?{~x+E4O$Ml?@0dp-5!=OX)N6ZvQ$LV!&aXQ{P z-t+jI{E8s{IV2uCpHkRfsPlYQxr;(aDZ^H(IpNmnOf$P&53rZU=NW*EOuA`K0l9># z8()2X`iH=M*dHxsAW81{9Q6on z%fB-uULk3A(pk620i8lqV{U@8I8_T>q0VTtpmj%rM&+)GDJn2CKK6DRS@!qM(jqao zTCu7ao99X^IKMLnE&Vvv!)cfE%`DTGQxmeDGxyc~n}P2Zii53%OqCdWvbGUa-Ev&5 zm=dX_@6;>LM2p|uZf-^ajv6w-6A6OVD}PW&j>`|DRE{~FD#=sIe)$z+s^OYLT$w-@cT-=qtkZ%v^+^PDj#T<{x z2UkKIFQ_fOY**eT^k)ks?u4_EAHwifS2IJBM9E06B+X%T=#^>EO2jxRaB}v^Z+}q6 zVe`64Xu6|jNtA64d?~DbL8}BF$ERFQ)Bx%QFuJZbE?nemneIf%*+v zy>nE|0RyXUe-dVs&|Y|Bl->yigm8P!K{qcD4|4 z?=4+_l7MevF3F9AwCmy@vLvad<^VKGustEJDtZ}+LdozTO2^hd^@PZ(-pJxr#bUVt zt0H8gw6fxb@&IRuv9p$E&J%lw0c`UP{;5sI9bJo_WnGV|E`X#dd5ge-Uo1Xibj)XP zpcpy*7At>w)Jx)dP3u$J>sgzdF-4g)*-^HQ1H8MW_2;H~mRdNaaS}F7yV|@Gg33g$ zHo66!&{%FMXEeVSqP8EMVnLCcmPDM}n=XMXBwuk$23QCO7;sGB*> zh5rduMvbkoiz|7|HP4yHg!-vcRrW(_d$wHg%)eW1AXs&4DabQRF6->PUy91de%J^j zaZo%)YHS{%ZQQrFKU-xKZts39`;Me2?7M-ixg_2tIHdFox7*TM^j94z6YfrZxI+Gw zSlV`|6__Dw|1m62Z*x@Q4RMRKUNa;4mkb?oc&K8wHAP+YC{~))Mj_+KD(M0Bc6j|R z_NEO6ryA!DXazU`MO@vQX{X=LgOE*$Z`^z4fyFAi@ox^eGnsiA|KuCuA^}N?Lba(I z;=8f?5vU)ocSRg*#+xL*X-&&E>@dzGTML(sEqxI_OUUXF=QXJ*dYff}h_1}#GM7S? z69-}?RYCZ}4JL2h;K32TDb)p5-H!UW3SCS-0h=34oS4j_ynZ$x{!Cnn-RIDCdl-^s z{O^%vk$2vVxBez(cEZdDERlA;nKM%4&<1)`6@f3SsOX5w_b$$jrqrP*j@O$!y*-+o?c2^&3=Ri;d~z4TZjMYXW+g)F&ZUE zVB&d}ay<05V{uMP0G&L#QW!(NUn%i1`V1O6Cp$+?-hL7@SzXzFK;2)b*^oxrp~{Y6 zGIRc|%rd}myxaUGdg*N}rJ+=}#N>RkiI3$NO&Cs&il>^yrp3(!7e%qb7sSTk+Wj@J@zsiTf-)P)mpFE=hvZd*k>TefA9 zbfsh@YB`F`5k zVW))?7t+c@>!C=l)&%(Y8e7Ob1z|eYom?q~cWy}Xae%kGG?Wqy*gH;Y zW(IVPEqb1vPo5$IkB_H`S8CJD%sBh$G0uY)`NP&P)UG%?x|7=p>WBL`RbDl_h-^&}I#+j91>*cz=y z$SRo!kta$(z!(n|7xUH!%?7z4@x~GjQJ|g7P|#x)$RL&T3uA6TFqM^^W?*b z;t~8(jty!U;~nHkV?v!~7ghTmSp~|_WZs(q#rc)cvG%WMhscuE8U@S0J&Nbu+c6z! zdP_VRO36I<4u#+AY(XE?*o(;e?JyR5W3|}XIFyk`o8VqjFIH+%< zzoRl!8J6kz;hvk*WhZ31FgnFB`>>>Sc&z$Fx>yG|+~n!LF~qk-cA}E&f&C?41NKyc z7zcDJarE3Fok!s-vqay(T$d0zlLi2ShF3d+Qm=)^V%%3xgwBK*EX>4UVyHi5I?LH{ z*_o}6kgQl-b}dX$O|Tc3=kdc49@ranjFRLo7TzSBdc18i&7X5JVMjOgBddhK}s+2l=98(3c%yc(Va zHYzzZatwhvaQ;&6;h@&?B44dMnYZEGR9dG!(tVy^PR;6$TB(z-QW`eqH!~#c2kUK< zh;(S9lCKd&S1kDk)fkrY)Z)HwvdrE<2vWH~H3>JZ3f9Jc&>9*xp8So7KLI;y?0Fft%zx9%}Eb6ZW)D6KtE zwC!X*P2ia_Lu&1^4jDOw@7F~z0FL~(mD%r2Yn4&3`A@3+jJ~DM^CR>X&#xq8oxp>Y z(;H~FLBw4H{1P}1mjNH69aB(%3-=wx(M}K9S`>+=T*t09IhGj~$}d7A1hhmJYO2?- zM?lyc5w%zU7qhB<@2hD(pQ7C6%9qinS!GlR5pj??A$_k$bh{jb^X+E+N$*pY;jxQ8)LzLEV6ZbtVZ&uqLKW{v6i>Oi&? z`+ZI8%#@DfH^VP}zqSXoIl<1C9v~Z~VG<1LvuL+4<{iP>7+`Fx6s-E5?$rqQ4n12K z@^Gc};A)E{414lk;k;YQ8?E)8WCB@X=d6Ej>c^tn&3N46=jH|8p#(*8km;G9 zI2It0b2IzRrT6`QAeLs6jM(Ua(3aIne}}gpytXsr&2gtSu=D-3df!d)R(}EQ&C)gI zqR0BRak9{D=Xvb?JUBq-SbZ<{?-XH3z+oc&CLP3nZ=Zj~?!DR=~<#E#mxZaM$ z+OaVpig1TbOL_VilXj!`x(&SSy*~BWOrl=75ni36U5!`b0cNTmmG0qc{o^BdAg8RF znVc{!5+EJUWiNmGOru)h>8*eae{w2%ll&h3eQO%a5Y&^VvrzAFA?$ETXR-dx{aI*n zn4+%ag~Mff+xkIA%TjqTsU4A%xVC)F*prXLpiiC>3Wm>*w}1|3+}y&E4aB@~7CUybvJ3 zZTD;v*B$La6p#qEY0&Id8C49q*76V}-csnzdH z)OCsAtA?nPw1ecEnV~NfFUsRS&~5KOh`7E42!1xkIlsSyMYeoQ)tz2ls`GfA7Hlym zSHY*x)nZe6X$t8jw1U^^{C<_i%J}_=4F%Ie)D)mPg%0SIpi9f#(&xtpUl9%ai7_0* zpq60E2%ob0nlDe@QkX!%7;3bCG1us0?X?cQQe<2)UvJ!Wc3C8A)?;h8zCr7S3ut@o zz7z0M7T5-jy!0!&1f;&wD2~FK@y&rIM5f`Z)KmTB>F#456DS=dmug0*_{}iq+C7Ce zYD~i>da3@vZ?ZB^aj9ugJ;jdgn(QT_3vMW2K(zM~>lKPzNxRyi@%K|7CDPA99}asB z1D|NOb&>%czC)zkkI2zqkO`{TH%!OBsUGkOD6Zvs1|QxI0p8eZ-Sla$ii%*ZiLzd8 zx$1P;h(K(g%j7DX`1i-l=ri6@tm*ByV9`iE#{^DPWWp@gK8QdgBxQ{Z&7fobgVfPS zW+H9(Jq4TV1>pd7)F0%0v4>z_gWZP0l0Z}tq>WwUwo^_pI@!;rq)_OcuIG;zay-X* zrrO&vv4Q(GeTLrK@ptK$uV+7y3xc>;kh6`(*oM=Ky2pR+5DC(egtW8-^7i$rg7g0a z&B`UhUc;u1uu;2g9X0zECR^RVe^`FoLri6yf32)MgB!_=#h$G)L+p>^i0Gbr&iSlF z-gbx286Ju^1Mm(7UR#if_lF<$bwxH^n{Px0qe{BaQQ5PT*+iHmpT})zF@HMIca+lV z|Mfa8;(Po<00s2gc|$jGm_@K0895c^`l1-Lj5U)_5q) z;xjdnRpun_3nCbK`g}N-#TW92dC1ZFy*`c%n)Q|zJPiud?)_f?J3z$0SCW`WuOJSY z`&!~)ufqG{j!iht689`tM(JFB*06Hh@lWNdBgqi{%W+!yn=5IcapkYbd39Jh?>MBq zRh^WP^M)r$;YM9TI^tNVoSGEU11LBiSN3r?tbE7Otn6}#g6?#|<``0{McLqKSJvWs zLC01_jp3TZir=wWX-unD@N&5`MM7!n`g9g$3H$OF^Jg8xP=k( z8kHG&mgrAOVHV!lM8o>{HWJ?I(m&f+o}{_!+x49CCaT-RB;3}duk?nKrql+#&KuTE zt>1_YQFP71m1eWh87S8c z#VSI)wj z!aK!_<|5mYDJt66;4Z;#JyT>wwynI0czb$dy`(7g&0zW;!w*?Ky}ZNI)*E}=4`m@; z-4cxT5E9m_HimWo`u50>$%Nc4eoOkQY+hboD47~hXD&tNDxN7x(?Xut$n(tRjPT*- z-<$Dk_*N3iX`l_;yM%}J(isb!u;wRy%|Qx3sOk&|Q0``9eYlEjEfZp^iq zH8V4#U)1^emRj>=Dj6CXRr77NbitgLep%%Gn)Op}<8yZ#t#`dIG~R6_{zfZ~yNxi^ zX0Na3EUTej?^Du><@2TG!VdV(EV;DoOpTPFh1aitaW!rE&Ge-vje|MwqLqcDge)Y> zsY%TpSTIn`cF4v5p}SguqpHsEIp@B1?_>Aeec#<>caxY6aWU^FF3m+`Y8fL!Ng*jK zTC}yq0x=Owq|(@dAo7w94^arVFa-)_2#JIw6{bZPnAzGk)|p}lu@jJSTx2>K3Su_> z&ke!pYwzB3{(H`y^S{6Uzo8K8Je&c;69^cAkk@GhOB1d^(uM5!8KjafQ{jS!!QoIK zkUyENM`68)bam$|;X$$sCBR~(WY_WB!<8a?_QJChj z>N2Alrj4Z&(DxMTEK2-@o+&K}%vfp8#pQ*OL==_BN~8FJI8ep+0i_XV6#02?6zeij zZW~bdPnrysHwT6(1w~O*k_UqWt5NI|V4#XC3SUexpb0V16j`9E5dvVY9^}Q$7t~rKlXEZiF^f269?$)n9!B_U|fQ?Srn93Gs zHa781yZ`pyss8h4@q2A=ZftGawW;;XR?&X@pLo-rkJ!dJe5UQq4Q*@#jI}B|hdc>> zqG*~a7dWAUx+EK=eYpHM>T=j%Hg&1X@dV;17vnhYu)cePW!%x5%(xIHbx8<|6pj z)&^3V++y+>eN)l z!%LhE%c7tlL}MQ8IVD2=?ic&;Hd7P!BU5)*Aj4R`VLT1%M@EN1;L%jFwm9kX*TNqn z)|%N=T7pvUYM&oe5T&YWzbN_qQoKNv3aYB(W>ME8Zu~mu`N5i^)$P-k{dnr^Sv4!# z$~yQXn`#PsA6~qzV#=ob9hK`EX0O{gt6_7I!CIx~QHGz&zXRD?V@Aq&Ds%L5J;$4)6P#_qn-Ag5 z*_I66dVtLbvP*%_q3k96Gn7U$daPFlu=_Qb;BhmKxg;T2#z`a-4jzzLBTb!+7NKQm zHQEbkvR8R~CwO!uH8MfvW0v(vQzCtF7YF>6X{x^~O`* z1-{d9A6Y_v1o%3|G?tU)9I40kz%mb!ps*ZXF!<#gnP$T&dKDGdXTZiS5G<8B-sz+e z_x1KNEA_+B3DR>Y-wY5YY;s)0qln)N`+Ipy%A2VagPd`p`qSyO)r^jPN@^j3IP~q4 zh#R}q?WrYwW0%aBr*;R23D?CPKkS0NiM;<{-lipY+n zYz0wOWg}a=cjVV+_J}cqh9SMXNlbZn>@xDf?Sk70jsEv|v6HM*);Z5R1iPdJ$V~U6 z-dVH%16%NJ#ky@ zW=>v3i5(+5g?hLps<$Fm6dkY{Lnat*OXS>ZG z=y-PQVm6!o{gIs~@b2u6FK*yx@sjI5T+3qRV{qUd@STi~nlX;m=w^O3*-YNB@rQYA zN1{MDyMR@KFUXekGSdlRhNtjxWT7MwpP9Pl(6HrDhviVPvS~1PP58v}Pf!(1=n8}h zOOyyWCYS;d45c3@@ftLq8Z8O1%iTf-53E~2b#)A!SZ9zzsYFhmE{akmWMMhE(=&Z& z-j2T)xA8B1V^#Dw-@LGh>6V08mO!@%K6f{k>^ju~ZnxN=u~5==i@0vuDFrIx5h5%^ z{p^KEgzZMc4%msPO!7z{88B5M`~9&f)d`730S$^jNV74GiZe{oIyRmuW5G-AF;rbH zVlg!BPMu8fxoj#fH))7?*cv;#8;)kFq9~-n&#H`N#s9^P#WUl_xLM51st1J!#R1`z zI3S&~oexXjQXAEIj%M{~N4u-t{igfl!Eb}v z4&DkzWNH(|3*n$A91hyTK|l*zFwAKY+DG;spRHrP4+nag2FNmp6BAW#^$&ryS*+s$ z(t%=#;u=%cd+W#|vW%=IJn1L*L07f;*m$tBPAS3Qh1H!T^h>E>eWL7iI*JkwfSPfn zMH3lNgHky4d}Cw2Hz!^V9@K~|JTosEI2f{Z{1U+5GmgoyUP0@QWCiF zuHzIVU^4p$!U(faO{nmI3-e^nsX8OFTk2PWI^+3GqtrEKU1HtuLyB-eb3P)o~L~; zXe&Kywe_A?L;E#Zh;dfjD5~b*B@BEqbBhTI4B!ANL>jK_QB^M==qLM-L7p`WKuQ6` z)LgBLV#{JA7GRMa`>E6#x3DRWQJlsJNIY_a?Zme{5a`1--Nq2^$2Cw$PMgZtXf>q| zZ#^~vSe6$VW{v!d?rH&w>N>;coO}0u_ujkjWr5``%TpAnu;4OOZ$=wKl|V;@kaSCp z1g&TVF{vd|4I)NG#c55AF&3N}#R-~0L=-b>OEk4*`p`Hrsgrif(A0J_O-sp418e_t zb{DbJ&TzSR_CC(z|Gw}4zMN20x2DG^IH6C{dPd6$RD}WpTX9nj*oc_J1kEa*xs2D4 z7Ewq4Bk-eD11X0awx2uETCm)1H8l@)tp84@eZcqN?ABWi^>uAskx%ble~5RwU*G*s z+qNG2ak8~_O6122d^N z7ccxTvZOptmPd36QAY_JVG9je-~d?|+pW*d(Lfau6F#PSxCz|WW3&OT*ue90z!&7O z>u7RY^{(Uf4Q&H`oz>5CQZhpu7$mi-g&Pvaf(-hCq{j`mk9a+1&Aj=c2E z$mDNt9_s%Cb#3KPIR|K7GMa-cVyc%S;S^kpIh>f~Rn6FJhE|oRN%h*zMlVL`GHqha z!O32iOe^SOn&)B;&K0v;qjyH-_u`cdh2@bDt>nCh1iVD2I+i3XQJ%G~R2sNCU7dct zwNBZhe=%v7z9Z?b{*Ke4Q|K0FMmeEfu+UjnnhDG^5#O$* zg#^(v#ODltc7bX|7t)($R!GQtGO!d@@%M|=k;q+hjkI!J98{f)T(z!EuI(<~<)rsq zPCA@xh@_p*iJCNEVtA&AF~ws_2WyN~jM9gyT@8ke#H<;FCMcGbHYk58vdG0$>acxQ zNx3i2yXTfQR6VikdGf@SwFA@N`%U{FBV%vw`Lyr;baBOjr#Juf#LF#b_!Z*%{PO&T zpZ$K#w@^4!Y%~Q7vJ{ac zK_!`xNO9vtFW61V&C#1wl)3m*+zfoVG>1SRcd#!y75p_+`lf@)nS-tL(fZH>o+{t(7Uoo`{9p?S6Us-P0tf|11{v^di zPl}R9PxF)vN*`~wA|S6D2{4WBTs&*1o39$MVTi0%<(y_oRe(DZMv^QMX$T)0obPUa zhr=A)?@w!X%tRW8>L8A;ugpZ63E=)jRPw^HwT0N?Xw!-@hUr{ra}#Q+slgc;fx?)b zi#q`Xzu4@v*fN_sczxrB@vGM!G;G|xKQejmUS#sn^BoQAcI~NO`)Wy9*NRt8_qA<5 z#U*ARS>OH9C*Acw&YnB+>XnBG<7)@6g<6lekI7H9yIte~_LFm-a%h?7Mt>b9_wT3FJtxGi&YOj%w@Oc5(YP898^5@Ti~ z%`&74Ogl{~HPLieMsh;)nTX718w1s*@RO;E-hXc9u1qSnuR4!A`|^Kh_$rICeb1B?aHVm570@ZT*g6U0!6kw-d4NjQk@>4GHsc` zl0~%;OIOa#*>00UDLlJ(#$^|UnsBHl)+rZ~Kw&0TTR0;Clhq;<+9)xDjDumRvCp|} z^@`TYB3v}Qac~OjZX681-17N}w?87kJh^3SfA6-|9=t+s`R?-VA2tf^ss^n8@DnT_ zjr=L{kI0`R=g(f{3f~wUIo1i13z)eC@u!2&07cWG1>hqzNUJjtZ7I){Vl8hZrQmB2 zQD856^gTylMZgNGpO6Mv9T**KfdU5y$EDHHQLbimbn4XTD13V}?%_*#E&KLTHB+O- zyv~5qIh@WBT{_Qe(?QaILVisMxvD`P15VIaKTGv`Oddyh<*1OQsfZ@6&_(*6Hat82&d!zM=a-^8s3ls?ir}HSd!Zr_YxjFc*kl6U*F7d{3r5 zS+=xlrPz`!I@7aphG9-}Ms~nc6kMEM<*rG7&R3OPRaR5=g8PN^`s|lHEyxRfqRgxfho`9t3xy}9#~`4DQC>J40!EC%zAOrj0i0rej0Vkg3Lh#)R;os>;sP#;o8lsC zppc~bQ+eXBTloSdt++s|<^Mx^wMRv9o$E2oTjzWRstbB*f7#?J9G7oJ_Q9ThdrjBV?i zyWW``8C{0#8{R}|zdLVMUHi&x+|#;z(T6Y8T-p4G^S3V`C(@6;*K*Xn`1kj-BS~n& z+TRqfI$F7A%cBi{f%xC+qbRN%`XzyKpsIt&;Z#XuabXJDC}{QiL4GolB!+N6l%PZAV{`@D()r9Q>#%y-0!r^HM9M`Jyr+|5AtqxLg`di@CF#@BE7NPG zRT94_@D#UHs#RX(UR3yqPh$L$Y|R&xB#&Fm&K@v;N(pM0_B=1*U*}1zc`wERKP5A4OTlm0Wc-{B<01h+WeYqK)O3VNPsiU=R`HBa z@Ut+v(@uhHO?W)HVaWJ*saSN>EK79bBL+%yBDpEOe%o4#0)fIRKA{v8svt#kTm=va zyE59H=)rh zMvNO@va-=UkCx017gvm&vU$DP!gZB*E||SLKXRz9YX6*EX0ppyS^D^s1HNq%Y$Kj1 znfz=n6auR9-lyEt+y@Yq4-U+y7YAu%iBqz_&y1honNH=|=Tl3mWx+aXRj`5D%I#&| zuy-=;_CMQCQkR3bf-Z+U=n4jzY(C4Coua3XwNLX-Pnedpkb5Hd3->zrR^~Ow)|6(n zi*9zEa(E~&6;QoufQ8Jw_)b;<5gP-u3REYB*kq4pW0EyiQp3)1R9FX95J*i7>mm|u z*cvfOn?H;2WE6-J91YiahvOp7gb2i{!J$!9gAxhhf#cj6`M`EU7?)Mdhq$n^?cJlz zKVG?Rp4qk!6?cDx9vO8wzkAc3f6iL+$xA!`LDRWke0v1_{BKv#)cqeH9oY2h4)cqR zzc;VGd+Of0_8|1)^VokOmpi!lSz{QJEQq|wiLA)-N&bMJ<`o(8K!$SR^ZGnK zhEHY^L&)ubi(gC$A)o9DQQ*aBG#ic;D25T5=u7lLgGd8DGedd9EPz7y7-~fS-dnM} zd|B8lbaa{?)3G>VoJ#U)kp1FA~!*a)5-Tlco*&6J6 z2Qb5D;75hpY$WiUCW?YUF)VhZENKcQ3K$(h)jdR*!i>{p-A>B^J1hMk>1rD_(*jW3 zX{97#W2Lt*M<2}7+`+Kxgi!q4!DifTWTJOZF{`+)7PIJW)82x!2*e1x8ulPjuNYCX zhYe8v`?mpG_^nRs3QY%;hp^jFh~c()5}N;GzC3Eyedgb1|7EMq#cK0G+nbpScdyW` zeP!70ku80dV8ar~{Z7dJOyn^F$=>7ydQJq*7Cp$#WMohwcOsohX*5Yr9ov8qpQt$) z=({9@!ja4jonc^KkvTL&TP;bD{F&dS za;X~oI=&w;9>_80yN0%q++)6k6AK>z^kZy#a!Md2DTU!}VKp-$oEjE0*>HNM-yRH6 zKBp%H4|_ej0C#CzW(cJyfNZY|E}9eyQ5g(eDEx!7TLX1cz|9==3j z5DoOf3p^22fPNP5S8<*es0bTSq z>if8+wiC@hdm7cYS9Sbq@Y1@{NvnReW_yp>TQ_$&azQ+H0i&dmOgP&~*}>}pPr?wE z(ImM^J}J{ON7IT38LSHel)TT0zhQFIP)OBNN!9DX-3_UHh*Z8S~!40Qor#V@0u;@8;MxOj==Tf2hOtpf!Nh1Hyr6qaQ%taRMF){ZSB z6Ks%Tu>+Y{MH{aeJ{&P6rhAa?F+63SIUd%7!nmIIQ{uJ$pneqRl=1F^{X4m?#;UCy zVf6?pcs*K#I}NL01-^%kcuCJOYM-!l{^N_Yy33EOJaQa0`J10Fu3gUDyqnbV{^E<6 z7eV!LlQE)^X=qH^@B&d98Igzb!{o8@bmk@I3?n=(pJUEJ5yC7%N@Etco_(2ni@hdt zGK+??r&(IUpqAXBA&ibMAR5|i1#Z0B2KQooWbqjM59QSY+thW2?>YBrU*BgOKV!#^ z?Q1fVl7x_OQIM`ynvFLqO&|~pq=;%-%SPy^8YV=xmghQcWl{iL`<;87d++%l-~avWJTYR2@cIYd z(kMJLms_g%qS2+0x<0>8@r%45q(pyE6k!JGB!Oy|{C-ZP0->_1aEhO&D&d4Ny2LY@ zMe>SxTAUECi-Nd9!E0)>Oj4l2Ps=csTv?8F0D5QV&sLS6(VF947gvs38=*&YGe9tYWus`}dE-ZK(?^a(b(H4?DUUbnn3 z0Lj#?HpSG^48@3V1ZcT}X}(MneEYu7qY)ZC7as zx`G#U(ah&^0BhaC{ZB96m<;SJ7C3AiF#1WFz@KalU!>QFJbjFQeTJL9eGAgRkzSqt z$-SfW=08h9IckK@K-7rbb}v9EFrdgODQt=8tDYfK{-I0%?FZzN^nhHtSuEH5a*C#F z;q`tGY5MQi;SonUPBP$4OnT!CEgMw<4qyza=$gf<8;}D4u+T@|JVXH-mqjDM`#3@s zpQ>|;kNQ=K<*r#t+)qYX;+pVsH_ujfa;LhJd*z)hfOq^Y8-PM4Cd_x=ov@%`C=|-x z;^Jjbd-XEoBZ-YX8wECsY*aAXGKeS>6;Lp-8tY7cvi``=Mj77#&7`2g>Jd3238egX zo52P#z;i?gNv41{!;RzJ7+^QKKzleFY}Mmw74;%j&ChPj5!}nv0*G~17COtDDc+C0 zLOC4c-d>IxN|44C;n~3cz$GwcV0mD?y0Y&+7i8^cX)Z4=bnmS7phjhORe@6tbNv0ZnTb6@W_MapQ5~5lR)s=U0LEb=^ci zZMBDNYLC-XoJLyS5L3z+Y4Q94pFiaV_Nqi3hfV54g%vtRRL^HXAR0SNVvft8UAxFFmo4T@Crv~t}I@>xEHcN0BiGD_G>eD&gg!;3Q3I9_eayNK0obh zV*oo`y|shEb4_rysgka}I|qu8qfoVaA^b@w+}=UbP@qYMSVu{gY;F!k7m+PQyin@- zX=$T4e)r>-KELj}{Jl@QgtzZ560YCHWIPT`Uylly>^WnrRh^+k0wLPXV<(wLq)@2B zvI2CgP?_fypFpWkRs=pJNn*9$inEeNEN2pk5HH5UxtLN(N?oUJRd=XEs;DZUrkSYG z;9(CZtAz)3&8)1RQ_Ks+0#h;rQ~#r6Y!@V`>mCr8hpI%&&s;wb{Hmm2;24>rs01IJ zRlF`2wm&i2js9@T%P^WktLPE!fbT3fR=?8P>U8+{whZfi@;rtp?XPi#Z{8{uQr zj3#;BPO83ICk0Ipstk03Tvbf~ZZSTVQFoyA0ZcJMkkKt^Mm@2i1W6bU`cRrf9nre} zYOezw!kVhG#0hH-fI5l;T(MXjs4=;8tAMR3)9f84x3HD7+NhIh;QGly(*8E-`qA0a z=r7+Xji0(i5?B8~;)DNq>35~8^fK8_zIUrMWV=+1wly)Ibs#h6_i^y`JqTe%!ZWR=Bs){V1_i zjKE(kqJ7#H@WBo?I=76mI=03fV8%hiRQp$^xuKPTylxFADcB~Rwg9&;YIE{VCVhf7 z9$EeLk=~C=zb+jlU;WvM;;NPxN{7U8-QG63{X%Ja`o}zZac|%AwHijwNyzcn!3q(s zf!y=bwyG1my>3(elgjpb!Dq6{!A6;lSroFk4-Nf|AW#Kr71(1le;u)7ZE(Id(vWYn zaGuDwnU%9qIm7z3k%UuThu@o(Gp>8f0p#=*bt_Wq)xNszbvu2}>4U~T|3Twr?KNY} z_`Ck0VM4V_Sw_&Z49f`k>^M!wLVn4%Of4Wre7;aP7EOkcN}_1=aF|OsFn>lOhM_A- zM?WrMXjgi!va49L)xg>=33n_NQ`sHap)8+mh&-&1rT@!E85D+Vs>%?Yyn42^ir$ET zn_0oA___dVo$dlCsKg4p2lPh@A}B<3e_`&2tMnz$%6g>=TQ+ z7Vwh|hwjz6CdfRqp&pJ|!ofPx8Vc10B{>uhg=;eWBNW0Z!$J#VE0aD+55M`F-Iw26 zJ+FJ!%+$s1FKuW_ulx%+xo>#Q%ik|Gi{ooA4IaOe$Ys~;Dh-g97hYVf%G0~}g&l*R z`@(+g9QtNH6#gZ?$2HShFW=Ai3(xRh5rkZR5%1PL$}g8!B_69^mhH-~=X>S8#D;l? zYV-`$4MsiLD#%rVQw8}d$S_xwe_RH+DsZYGkNsX3qIsH=rCC0oYd6|5%W{t`*p%wY zbmyK{zoKN;K>8B}*`yNx~OuIw|p{rq9|kapPkrnxWseC~)gYz)^XD@v^??bvZA z=5rdMA>$fjw$R$*aN8icw5GxMp*W4_LRwQYpCdUjBqDb(WhN$@e92^pXPj5CY_|wU zUwy@j+?8W|b;C>VX z=ht*3>B4ff<(+KaaTc|;w=aaK49gBszEm3whlDT-QfO7PPTzTL%cVVE{^9y{egCDp zYG9i<&+vWU-FLn_-<|Iq|D25z+aYoM10)cK#KhnO5fXj^p(-O8a~rBq{@OqaV{C1= zpsM_ot{`pI+Ep9tkg67x@e>jIK^tHm#rPU7M&syl%^Hx;Fk{m%9J8U29g<)^3hA4(#~$#ckVf(n_s?-f#w3(Z=jE z6@tqk;0(l_aN-c&)^FJsgiiAzgsWM^!g>H%)Idd%-J%C!f*Zar+Lr4@k{BzJLcSoN z={E|4Z$9c^Ae^91MzS|385F30W=&WR*M;3-9hc-wxmWU2h|<@0|1Lf4+tB^X;j}L1 zvMEIz?OeNXLEt>J3_PHrl3b&(h+87;aJ)mlbl7AD>qGeLDPD9%a8aM(NqvSjdSFah zFksn>Y{-K`o;DBe^lbI?dRUJV2}wvQ%0bDZL#arIQn6B`Njj9c2}@C+Lurx@rL3FE zvxjt;$lh+%pzm9OD5wFe+a;JRs?{e{KpjXSm&XHzqH~?+yYdhI^hy5y*7MDMFMc{m z&WxS7njbs-8d7euE5=TKF!0oQpfgnt(uYRmu z{aCN9g_vicP-oUmF)OMGiZfBRQmU3~bPja3crFIdX>(a3p*C?Vaxy97)dzpow0F1}KC^;`iM)GHL2N zplMLw0TU{?4i`9!lZ*p0@$ZwO6VVBb8Hl?E>yH|cDBL#NG9b+OxrUAoOO%1^=m?|Q zf}f~Gcz*8pm(Ytdg5}fEtG~;g2UYr6@7A3=x!C+R0fhW3W=hGQa-X03+&zJxnTlJY{bP+sbOoIEbCad(j} zd9!;v*)Bitb`Uppu1*1n%G`#_?KYgUD3-aMuypFO!x7SSm!|1X zQ5Fn0ap)T03XVirLvuJpQ4lbk$#6P#oe}(AuV0%lqLoaDkzsVhWikY<91MjJLO$OR z+JB-T46=S-YYyyW&hN_^%bV6MyfLW>iguFf0r-7-@dE48`lz}5{|S(^N7aUhVMq@b zSC0;mYzHJ;r#0b}48U)p0xDrM{;&cj466z|{jy2ShC)$1mxY2TDHKGB6Q+_{Fqx4m z-iFZ6^Ur_yw+erQguMT}v|?&t`iG3j&Ll?3pSDGMV=KU8MsRfNp_bc*?E>1St<`abe9cb z1YkwHZ08ZsKe7*DpD9X`$YS6ObqE`#D2tDoQm=%iAv9nrcDo#6*vD4j0~TZlOc5%` zRaA?n-D(N)L>st1(HO8XsHo?|6vm1F7JTVw7TRWExY8+zSZ`K!Q#QvM6(B#z(=$jv@=vwA&&}C5j@bKmjfeeCY`v6@VR+T{x^i zgtv|xb06J8;kKp)PoTi{+-dwQ+nQhW!VB9DqGMzIxgSxC$DmUlx3VOkaH^Ea9EzT7 zLCr#o$V!4;EJ9k98C5~{G8xcV#sgY&=}c~T0;!3k9K;wQPLw!7k|L$yWK=@;pv{C3 z2RP)Us6CKGbf6jThnb_9E}^^d5mIL3v0W;Yfy~dM0XP>2;h)0TY6KGqb%~s@BfDQk z4B=NYK81E$z*_4lpb7K}sPNIAv^JuRF|^gvE3KyxjR&Pq;8ehos!hPBT*@P8K zTpms#BbNNotF5lqy2pq zT?M^CBQY(WOmV4ci@BC*-w-+yPYW9pJLEm`1^J$GFQL~bRm5r46-n=`u*-Ndwk3vR zfoe6Q9#9Xd<0?6%9#g+kSyiS5u2MNkYhI-fc552HN|kB1S9ym6Mm4~Chwza><8@ac zU}NY_eoGKFRY|h~tUb0?Tg&hkNGih>RG&=9|62upQo&K&!0QqC@z!G0iUKVDO0^fP ztOr#Tl~%102Y{934)?y~zGnKNb$(q!;6#hjDrM&cP z^XWHwD~GaHJs4(bR*aYBP~ zq6+!;r07ZK5}$ugwa!Q)b2~SpQ|C8ve^0>@q%1o?A#5pSD82;KQ%OazC6Vi`|GH!q z70I@$dDzm-9=F@&@lK7fyiLWh1i=J1vFWZ~Z#wpV^R||{%~#f=+NQlf_-^TOqx+Bh z_8w`|L~r@~0q;}8TfQ~x*^L{187X~f)uN*>FWRcjXa7=j?UETg z?>u;UZUed+4QSEU>XyzmE9QO&Dtb3mbPyOpV@gr4`4%Fwqk`0tCPFg7<3St@mIrDB z3j$k%2ZQ{alC&r7U*=in&k9+k-I4V?;ol@Yqik?=d%FGSf|unh-YdTACAYk{eE%r@ zdvHAH3z2F^wX23?941-jXd_RPE2aP8?rXC4Kd!4jIEwO&zwhqeZFcwecK2>?H+R|O z@^vI|S3Yu=@G%)#L_#%u1VX?vMh_81XiF}I41OS?R=|o%ozbzPO&E}(MF|?UrnIDm z;SURwID#`pEzC5us1wLoOev70&+}d)sWW%C@4oxaz571D=l6SlPslHTuH-pTdgPGf zS8_{x6{;()l2%$3A?hMp3d>O?myOY?B6u(uU^%2F3Q;%eeJP%kVK~GT(|~pJ^tUq7rb+&_fk) zA)S;Im+{a-K_Wnh1*t-R@Y~MjBljQ9xWnK4@#uOk)$ps9L$7aYIppaY`u6$7&wuo@ z;VZ+RzD8d<*6_^fzxJLy4Z2!4a*6K;y^7G4qdq$2->Okfp)RQ-O@t>xz){3pM^LCg zB#X#w)b#*_*c~Z z`u>P7M-9vCxQF?5o=v_cwN*Xfdrx{_e$VH#eA|8h;CO%WecA?XtHx^-B*uMYD#4dZ z!)xs(?c@R(fHpwW6mshw9eDLpe-6{9;ygHCsS=Mu)dpAQGJsr$&JP(T5r)Z~PN=j; zq)~CU$ccVjB+9r*Wb!Ib%S)XY?d1TKEcqLoYwJsDnU|vIrTUw%#`~Mu*@5iF%qeSgdHdY(f11C1`mN8Shr?S}?tF9qFV;Or zpRzybq4`ukM7eE;_vWqt@$pYS{mah@FtP?PI}eOBfRPREewh>0n3_`WP(77qrL%%t zCNDMXorkywJZq%YX4>hFp7ERwei6PHyl7srzYbqy%(J4=cm(NM8$s@hletoLvNemV zRBO3;YK?iPvqE03KBQjs{>!>fuljWw;{A%Q0V5UBAV5i8$xTucGqjkl_Zn0;TqA9? z8bD3Vi7Y{l05Zp5MGL8Fcrk+vMp%P2sTgs?j}eFa%Z#N4wv@LcI>yF8=`pcKJTH!j z0z$M{*jQN-S@jXa1*y7mOl!<6>>nCWx@sLgSs!aj@q3GB&=qBh zDp89;jI)3N=csv$XZ(ycC$^s5wC>E4>6fOwJ5+dRQ_JfIAKScl`)_vL*w;q+UoM%$ z`LEY-fm0v-`Q*7%ClIq*P|*V5SqLzzckL)~Lf}aoJ&jU>vW8#p*&wY^aza=I*|A~Z zF2zy4gTG~gzj>~kgArj`U{-jVGbgYxGRIjGxF@{SSrzz6WR4wb5rDBQgpn~=h*I|s{blt!(jUMjk!lTu?y6^=v7VMt{O<}i#bh8G|; zUK1PR3y1m^=^1d(8N6SJN;m`+vX7Cgv3e*|&0HqJMH^WOrJ3e2is>X-K@3wYvJfv? zG=UXS{@$*{up4d)@A18=zMcg+46c8`Y}mfs@KM@DipU_9$yiS^ zMg!KJfNAd6`}-TStqJ($e&|7`O`ZFQYwCtq61_R#kaQ`KM+~o)N8>a;Y1=j$2c9%1 zjgrb_WomjA&;(=+RV|bZl^E}|wFM(jwk)_OZ$`z^J9>KfS9WFAr)pLNUXyFm_wTy- zATZ+g;U)Z6z=#4eiEePyisCU7O3YlK%rm`GemFm&OfVBAGnFdyE~Um?F5a!IRj$k5 zhWwLD5@olQ+*Y=rY>Y~*X(}~u^xw1<|mrJgc z8MfsO{hmA0S?&ZyR!Q_iGL_YlR?G)HhfS z)HG}e$vO? z$4(tTc^o^-9fa_gplX?P)rZ7_#1Gji9@`dXuVX?bKacNH1vWLy4yWuK!)KVhhZ4>4 zh^8X@Vv?JzN{vXgTcQ@LF_w!kNagI;Lgw3EIa*P zzZmm-eR)*P0bUXyW_%j~Qqy=c>k=(1WRw^TMsLX2(fLGo%iFb`o7UGoTMd@*n-?1Q zzdCduw|B>5_0Me?`U7xsCjbSH!1ambw0o~qg(p}n?Uvf54yju@FAYc{k)l$Q)GD=& zPF;{jBsnU9#}WmOm%RKFlz2T}LH3F<4-wjgcA-P)7A^?hZec*+h)^i>!kr*wy)xGz zd>1!?#Z8cLSAi*;Fs5t*nsAJnfFzPJhQgwp??3qm7C|ydI;}w?@Ec+)PH*hy}abZxAbB!@jtq& zH8zUt3eTOn^PXML>~m(<>m7Svj+YQ)mN<6YcuF9q5KI!12;<#=kV%WdE#{q4T9QEA zHWhJc5oy&nkXX=?A7JAmgA&!MAXR{Lra{O)zYNcp?ThGmA)u5Ez^pkmEXZBCtRVAjWtg z(?K>fAi)_+b6u=#dAg)h(ZaNlXsXiDQnnzqb@EGKYchfjJ^b`E-lm@-44ZK7%5sD#^WFk8B@nk3Z-TBG=-{Vi* zcmo}o+%(a_M!!ATPV@44a;XO{UOL7D5qT;X>hH_In3ktbzXN! z1<{eGCJCmX(J?nU%CzF!IZ8>IPUmR~-Dq8Ynx4$_gZzolA1d?;AxX4B4E^Ca<03Sp z+e<@*fH3H<&O+6dN#IdDDqjqKWb^Oy<2Do03oz`=SOSeOhK1u_0+bZ_=k3VdQc7iw&tD(Fwl(d%;Z6OTI0EUMy`; z`|A#LZrU6D?&)70IvUG9u=b~eOFABYwh<@yv@Tn*6_Tg4s6-- z$j^T~@wcM3xJ>dTfVMeao)f(6uk|7Q68BHj+>w-Owbl8s0tNSg?dv}s6Ya>3Z9@R45~r}RYC4Q%LA%{s!*xw zfO4pUa8VKYQ(H|+x)4km+{ zx#n3aD@h6g%2am*Z-cn!)s5$t?|(#B25VN&TYnHI_a2_Rc46JNiS_IY-|YJ8?z0nb z5QD|cnYw~g1VtBPq1DIyCjBN}!bBih8KvZU8d>lUkEmE$i!e`mQfQODF07KIjNa&J z49qg;>I*yz0&~qQpOqiebDmt_F|&*Bk~{P+PgkJB+>HFPz`NSH$N9&Vc5Mx}l3%H; z(Uf4kyRxv$*DonfG+0F@?^_1*4~7DNiA5fB?^zJ=a&*&b*nq@BKyxrGc^m>iFe$;CA>>p| zM8-V2H6rq<76D6;%x_Im8l_)`ZW*&13tJw3Q&>A%a4&6u+dNjeUkrc(D}-8ejy+7w zLaCmb+D^?e^X>Dkx7t-8zxDvqh9yc#e^;hA(Rwg;tLyew$1q;XJRnx<$_W^K5PYR7 z-tre;gBO4K18G%vxf?iMya-G~1B<&o!PMv`tTkHP?G13NWecN@xI9g)@@H~6rFF

uHt^ge^=47*EVDHe_pVxApn8IglUTrvc~1Y+U*OJr2KHc-45 zfFROVT8g9>Rbw{E6S+|9ksGR(&%x&*DRbNf83i&TAvz&SI*LuUXkCk@46r-8aER8# zp7iALf%WQZ=vkbLU3aDJq40FbUq(xpzs11k`+(|e5})twB)UlVx6NHRI=*ejOTf9; z@j>eIbjBBSWg#~t>NXJu*2MbM=d*iklmd*|O`tpDZ!Ei>0BeallJ<_^3quabQu~(( zBDH5JiL$!Rk#SYb?``T$Wz=jgF-b{efK~(U4w8h~JU08%@K<5y{OY$B-nIQDturdD>nGstNSr=^38@^%bpFx0! zyI$4p7Xs|xom#T|5po#6O-to!`@oY@9G}pmtgZlw=orcJM^>&a6GJOLNBe)q$u zCj8~AwZ)(qXn9gRgUZ_AF=IoJLoIPrZRD&V1Ho6uq!O>8%dS1lwro>vbng71S69x< z7PcQTJ;7)8#OamXevi9yJiHo`tgo2OD4U}zLPSvI;GUo#K>Zeq2zi)TCSVjwo0}p6 zX@Ny4Fc-#>C>4{p1ctp{U_r|&>ahS-=||R3k|d>tGN_29@I}FT#qQMfYps2A)$Z8V z&8&O)`uUxqN~koiE{-yp))P}e1j7)pFH~xPs7@t|#zl^9dOWP#BOtndFExuA?_@N1 zuKeX*8d$|Ky2qO0C2F&BUX&lO9X^D&oD?&>bkpD*_sj}NIr&BDe&8ynw5lW*5bppm z4wr4@)i|~|Kdt>2b>qv2L74tc$hf7_ZZh+a{B6D?M$uolBXH>EDK0%SL}ibdF}Kqik1Pzj{LO z_Y0lyw(n0qe^D#7(pR2obQ=10(P+OOEG?`}T47TkPej(1y_x8wvuC=$$S4%6C}qcM zUUEp~MT;Q>;#5@7$s?|s=sPFt|FH=%044Rt@`RBPs^#ehQqd0?Mn`6{>FNrZjuK#Zw~8xA`E zoBcHwXJ?)5doq*S+pC^J|9K1fJs#a9+x3dW*0lZmt93M4^SIcA98#UFO0|D}T}&R! zTR2yoWK}(H=kMYY_^l8|zltf1!M=JQ5X12fnR50>VVqD)Q6ZJA6mJI1AKQBu!dfr6UdDo~Z zB=Esd(^F6`Srsl}9OopC5^pAxy;9{3y*toEsuZME@+!Jlp?kh8^@8<3w@t5c7!HEK;ap+h9+E2$B1Az~eI^ZIYdKmX9^ zbu&s?k{K37xdgLVTBew*{5*Etc5{~E>jI-FT$8D8X<8!KUlPF1=zk4omp3zmBF%^( z(8g>(FfaZxERIM^OP|97qqsX0rVU7{?^jPf+xrm!G}gDXdM}@)T2Hpnqnd|m4ZJ0= zHW}G~>=tKp#MTCXIp{U|`-cY__jI85(DWGMu;%;a`zLA^eKq~W<%tc8>21~Pg#7}l zZGcbI*PwX@mJMGwq)`JFh+!$_l|k+q%?zOG*H^wo!B zio3(Rlj#T73e zN088_*9P&$z_sGX!pR;Qq#XU9W6n$ ze9>T0l~a|p$ryPCCxN7eDR1T_S%L9|Q92k!rPdw^RVOf8PR`KgfbHwv^F9AI3Btpy zG!1Xu(CWp9!Mj}oi^kq&Y6g3MyvE#S6n}8qTHVQhqfKje$2-i#V!vJlak(8VVO+7| zv%yn}S|h*3K7c(hej?y1&vx2Yp+Co;KRRUPL#s9=)T^#CNq=?S8q7Y7 zU`BOR_>+&3^4-=ZJhs+$17^Lq(M{GR`V>NoJcud-+{45&mSVBWcFzg!ZO56P3&1j7 zRub=F`?S2lP!9$lAkVy)X4-;qoUuQIJItCp4x5S@G9JsOMH#A3EH4wiUPYXY4iJ@I zWDq(B$usSO7{2|0+cCI$=*%6)93dx0vp#eq`-DPlo#M@!XM=NnAm`9Gn0jdC9^M2U z_RD{di!;=2$ktU#QhClZKc5)14UA2}7`L>0QuXJ!GDvNBUS;@jW!=-P*XI^MWA*&) z8l$RPzCeYTO_dT(o~$DbYx%=uK3kL5Maa1I9N=+`|U$9Mx1t+l(E} zfMcGw&M&>sG#N+Nj_naNGvE~3#CrSudIHehEk9LsS#0P*T)F(g1MOSMdiLy2I?C#O zrF}DX)k&gT;2tpDE{(0rz11AZ$q-zq4*n$&y9K^^GAIf}O~f`qND3r5LrFlYM5)LC zE!%FFE9H7kU&{lL{<(VoZr+=vQV(AMKaWQ&-|UMNo~K@n(+-y)XV|ZIVF4_7_Y-_S z7QU}}7WmCWgI{Wpi+`^K#5@&ay;`_^k3=b?rr3 z=D(HP`ipl`%qFNBKG2b0vhR3R;&~4+od<+0y5?`Lo!|`)uP|DL!mM9wUblBYR#POO zTxamV`I_`fJ7};$wIn+VsGk6BY8!ZP5y2To2t-2c1W5y6AcaUTl%9tfD2BOYT9yID zO#}0})f{tnf#wBpBpJR-KZg!~+@J7}{)U_75K<KF?WR#~WVL8?Q$jPGz4R6Bk9i zur0~ulfqJYSSixDXzr?^zG&p+e6Ze;(+3q0CDyRsS{(ob-h%F1IW;UQhN2}}1|{;6 zjD73%-ZZOrY~#ZoOg=CH4RU7hB76IhZ1{(xPlfNBU8Tsyv^BVQw=hC^JO1?)@i@GP z6ofJq{+?_OFLL70oA*vrAB+y6p1XG;zmR-$JC*l_%JxvhZAn{R-WSrEL;$v_xofjt zaX9x1zN#Ea#?4VNk{q}}B&W}4e@n(_@ROHoJD?nL({o>Ed4(5Uig`Gt=VnIGt@=fW%7IeE%mnSv5k^$tLBG$F2+bS zFxaHatWk7V)dv}Fcn$M;fd!(99s~79o>enE*s0N^;f$LNgY_3SOXxWwJ|lgI%yYXv<2p6b*MwwNiM80hq>*IS zMf+jO!3A1_%kJZ5XI_~5_(lAqA4K;1%+8QVr;y!^dW&H-z!SY zuwOhsHox<-k@fPDwehm;es*`){NcpDqN%eJ&0n19mFGl*uueuk%(63vbsa@j>tnVsZw1;PM?+Y3LSufLa1iO$3CI0TZ8G57ccX zLnkfLL$HZ4nN7lL+?jwOqplm%nl~GE(^8w1DEAB*QgD;rgodnU;cCY&=NAq*M zgUc?B-Kf^Kc6NB_^en=~I{m8l!KTgTRPHQ`(E`j{WE;`WM1)7}`!fxaT z)f{W5)Ejg-%bwa{LGC}Trv~rv%~Yo#SRbfNEs7Z_2(kUjerb937Lpo8;&$moOswuF zkF&rbVtV`yUAzl3D~M9)bhRn?dRBcld%= z*Mi#p-un&%atPS~C+Pq%JbTOlXz&tOP7zkO{D`msN6`QUJ^)Rr0I4O(9(!;X%-21C z?IQt=cRQGU0T`f<6CJ)TCIPa#0VlqY^r!Fv9H)Q}WPse=fDiD2DS9}eyMg5!_=7;d z0VpuRC~QC|`q62{wUQ5tY981D##?-&r5YN~Z2Zmu{n9uA8qI}uFnh3Y-bbndma^n> zb5U~;mu)=3ath&c79nbGKT+5|;q>nj1OT2b#~u9$*6`m*1<y|V29dlB2wt8WLju?yfF4IOU4)-l(w5^R4cr-Y zZfJfm3|_Lbwpngh9UrftL861uXmkN{b$5eZr1G^iti5AtYbq|~%RF^xOROwac9y}g zmDuc!yl*Q<;MG3WR@cAOy`v@Qed{5=9_M-m3G9gSMJ(YDdXUt?B5M%wf*4?WWH~^q z`!w8{Q!|!s@`CZxa$o~yU}L$lMya1W=t=o|y|h2Up1V`PEpO}%HR&07O~JH#mKLDo z6s}8|fO&KZ$G8%(l{KOr1rp7Anon%|V05@rM^eLzXybD+vmbhP8e~!4RgE$=?EqhH=z@V%G)o>-cSG?i((R?u*ASG% zSa-b@L_l2iSN$~+z41gQk;5tn;^4gJ!d*a8iT6D-;cwBHo`UT^8LF_9mPpITR>-&* zU6x>9f|;1oB9aN!G^+8&!L-|zJkkly;vEPx5?8+eFvU%%uGBdZEMQv0j}9LuRT8## zbyxET>o~gj8t3pMMFhRSANXfI33-?>pn3Z#BBn~)UR`67@G5w?;74ig+@qrF*Vf2a zot?&}_vvUEDYR0ez>2#5;VQ$nT&gOYt`7SX$ook9Y(=@q*h-3Y0r3b%yy@tB>=PMF zoLgnjf)@6N>%cx6M|cr-($R>4?0Ag!(NWmUEEnhEJVuyzb_Q8Xq4MTCV`5Pv1xp>e ze5UK8y+kLvoSl*dX8?`x>64}VwEo*)4>GiQ@wz_`so5FKQyGz8hq}!qp7F0Pvx`_h zy|2gq${nrHpiAI*PRsEwwa2~0=g_rySDOdZ7HNQgKiTbc*!Nd405AZ8al;f8ER>0@ zv6Hi-iGdC5k7Q?P3Cqk*z(DXL@bJ(}o7kE;n-j2cGIG$1Sy(%pIMRz*8#tSYm>Ag^ zoBWSrMgm3#PBsQUzW)IOCGmGd4N_T24XaDTUxW?Wc_`P@KM#no!!L)1ju6Bf4nZ0y zuQ5u}J;t1E%gb6cNJMnTvJ|>XIYCvqfo{;mvj`3k7o7^FIV@^@gk!Ny`CJv$`6^Ih zW+~O_C?l2GY>WH3err?+8WPI!q2!LU&6fzp$ zj}L>?sVCY7Bg_XI2=6ul-eZOWz+>CQJA4PhodMmG!$IP8loDQNWmF^x0EFqC6gy+` zY4NTMR59zU65XrUJxx^d39$-SkLS{CU! z@%?rO`%?PQ^DY$ucnW=BQvm?1NXCiC-sMk+&ZH&!mMxkSZ8SH&(l3vU5Btb@Cz>N; z_B_M^v~VgcS-K7dx0mvfd@J;1c36u!WABL8?!B=+BB>}jW8oUNmkj4B0TzNl0vRl& zC{)W)p0MCj5FLGDvoSD3J{SRtJ9o$!fep);F%otAX%T_y!cids*vk49nI)VO!V&Ly ze?QCg$fU@gKCcw4ZEnmlR49`))l>riepta-;@xvA{Xpum`~x4?QK6JpKWHdTY%wOj7%@k^<9AC!ka9IBogJbo6W&(9rBIiOTN>9 zBHHRtcafVPVrFv-`*z3l(5d-%rSI2d!Zy3_bBni{%YHzQRy}iP31*Pq@tZU5s5g#s zHfLqnI%?VTl=BPQAszpDHm2*-pA>fbv++YMY(=t-4(#)8UZY2xZkMCFAxKGTGZyPG zuJuzi&o)9&+VsG(F zDC^+fhn+_Odr^AghDRM?MFV$ZJ?(p^-@cL^GHfu=7Y;lx+4JtLO1oNaZ*~ga9()1W znbGV=qtY#*+78q*F{%MWIE(zhPDLr8pi>g6mNzX{x>}e@?+gQ{Cr|8~XETiJRj zd^zlK@|eC(GJn)AuCt@bwthCu=O|yIYu4hki7ke->9pEmt6uXtaM3HO%;l*xUd0bI zLu#R4XFnO*Sy*|g2><2ByEftLv?Qm_f0|*Rdt`+F#b##9bu6d-UDIX%L+FwZVsArd zZZ?KY?tKUeX%)Fud~FsN(aWN*3qJq`BJT{~0`iGlJSlw)8w3{FQjbSE!+Lzo9+rER z+0vA~+i<&BcW6CML5I`5(Aey0yFpLv_$I$u%$bpslV)Ki-i}ni&djXL^xPyhE`M>* zoxZdEQv7-pt5~XUC@i3Lm7SbN?X5$|HX9yrqr2=#rE4Y+mO9dxZI&^2U$S4JVOuY! zXdmNpGdtH+^|f*_0*+lzX2d3KxI&lxyhIG^G3Et?|DA{kl|CpG0~S98!`fusCLoT) zKr3g*rXUtjkR!B}8JAy!SVsZll^7R+frp5Ri^&V>RJZjV9Unt#2+1p!4-ExX@H!GB z6s#CHi4@FL6Uj8oR!1QnTV$|Q6YEO3fG)@yOPk2xK`XOF#?XpGiXAxk`0`hp;V{L# zfglS2)f88>7XkxHNg8ZUj1a1mhsoJgDJhC=wSs|Pn`~eM!>cPavnuv4gPoW?DkRw{ z1u+%xnrWhk0UodTZXQ`34VzVxHN(-kIfr4k?laWDte9)N4=26%I1iWpizzCn#udP+ z&Z;aLG>dW;RvYKJU9&e_XAR1@NZTk$tI7o zL};HWM06v8 z>j*X5h{Q$LhRPEOeyUY%FA9mpJNs4MshGWYK1QwIYiVrBX0zK`Bvkk=U7O2+TqF3; zhd08_X~{=;yfK|#flJ?722&N;XecVy<7n2IFBRbr=QLj5BmIzg&CD)R z+P`8DP2-1fwkr5A01YjS!U~)bvI-XVN3hrw8}{{mi5ghjJ3et|WPnx9iUFo2XZ$J13FpZ;C}^DZ^qwSiyo5l>!2ZAQJ{SQh=dJqG!f*{$LtBdcNh!4&dS( zv15ZBX`)UoOgjSGC)glFA$f3wh$0i|g=Y;weqaz{A7^6r>QsRlt)i5Qd_a zmAFKxib<9y5FiARFRo8a1agYS*|!=lp%fKsnpQx=#dQYjhA=j1T4IWT@2(b@!3i`c!}Q8^Dy7;$)a*;!Vvpr{~Ek2y6_PB1Rdjlf@=e!}92 zoY1z7P@sHmKtN`KHovjmRDv*fx?Tzr!C4W+x`3*dHbZsc1rcR?jYs0Y%?4a{0zD>f z-FDw=cJDIN(kjd4p55ErWozim$QRz|t?sWmv~_sIiM$XXYVdY$%gt=mN$&3y2~xT6 zsBt@|J6Go=nT6GA!LD8u-ZCzZBwfZT z8Uj~AMX!LP@Pb;`&9ri0P@ccSpmlIOh;7x1Itwm6aUsJ^Tg@i3d&A@>;3cV&>U1{c z5k`t=;Qa$S-BnU8|1xXUwX<^M^!s_!o)^I!YYC&`PG>oE0+(X^O^QpqT`cebOB z(G8aQjk?Q`iqs~v-=C!Y15|<7(X8$;LdoPG@K4dcP8c{8v!`T1cRUwz{ABox<8uI} zk5@x|yO{aag;tAp>#@<7N(U7Wt%M)=dqr&S32HjFzQ1XTe$Pm@K6CejYp#wTU*nhJ z!AVI3n?r!1iueQUzF-62k|HncH_kq=rQ~_RvFgmC;HM*?Wp|KWxP3;kXoL9|K7u`# zuRWQ9f4ZAa?0-0Fn8emz<|DNu%I+Gtw4$?TjH%mZC*M8tm|WpbTdYcwYn&8G&pd0*l3OU%gCVY0== zllIb$C>cbS+ByGNoA2{QeyJ#g&5cFZtE!oGxmiS}&4KNvye*L(YBFGg{^x0vP5PU5 zON_#6Eca}Y_90B~I`)~JD}(qU7Z*yQ#NIYyVo`|+TXWhLuT}nLQlqQFj(m-@aWh3c z>2}W54w69aekC2@Y()$k!wCoh%if#R;WP$GZ1 zp{$r5fTY;A``n~zZ+97gx>rvC(sgImZW-M_7Dr}PYK`qRh(_A0N$}kOcoXu&rWb8? zTl4=>`t*-Dh9p&lJS?2KzLOs;(f=JXLwKjfB5Rx$MOUiw%y*a^hc{Bk z{(R}9w@=S^q!#$sG|_!r8XnyD_35fBpa(_O50u7F^Tf}|7|Ffg;}+{r57HZ9NA#zM zJ=9b`uKgeR9DNZ*L4n)FoZx)v06J&wj3d_ z9Kvh^d(T1_zJjK^$Usc$QRUZuy!LQkK>R%v8M8=fGmrEG)jfdJQV<0_7A^hj_vyU= zP&*U}La;mpNe2iPW^w~kwSphumKuJj>cRl3+4?JBq#Rci+C^j+Ip!oqp}r{eedY~a zFbc~(pxWo0h@tq9x#H?|M~xW7Nrdpgv!)GrgK)F%LOeJ_u`)NoFao1FK%aiW=D}ga zKlhSr`((u4y=(U>LugjQD4=pNWP60~05v45V#u8lROT(Ww9Y6WapX5&D^Fm&@Gd8G<}6m{bUVgtV3xXNcl*jt%*WLe{rS$mfNoBm5!D%_@ap^ ztV4T}3b}rmyI9gj?i?gJh$A>=dME(i(*_yWhaKn*iGi|gphG9cU!@kN6z3~f=<;8; zhc86M>m!cf#A6&AAwl5`gH4q~L(sePLK55?v2~17j&(W!vLKf`b+(i-Q=&f;CO*N! zlDoIytjt+_;QIuv%;-mSeosj4@HYXQ|pKp!Qw=WcPl(y9vd8nbZUk&&l) zCHU=o3>cYCCXhj~7TUX~%7}kR=*R;$D-FUtupg-l+lxyIq)_SKNHIFHDJIeY{Y2=d z`6MfZ>K&<|8U9rnXf+e@Vk->Y;io1Mg_cc-DZWLnI&#gk7g2}unINI~H|-!!=%6(~ zIWL2}8KDpi@lCA${tbJwp0=X#~ zwVui~YtbPgz2C#Bb_0wqo4Y4-=d4xi3XT5Q<^?Cgu`7;$yjkjjbSUl=sjxtbYF2W| zVt%#l=uFQr0_i7 z9zQ0Glw&AfZ>VHot9~!0AadqpCRx!!fa4x5jpenxF_l0wY`#Sdc$GJ0u&*F)@GWnJ{85q?+c>0&s2+!fP3E&(>V_ z9Wuul^QBEUQgw?`qsV0>ig^26#f=q{o1aMKvLv}}@Gm%I-)C4-BlcyLj2`*JdtyB2 zx*iLueU7`czqg%M>#>oyhd-ULpO->!u=tSqNPHEa3|xNg(a#*SJj77LkMF<=i-g9} z_Mz6foo>us58@yB3YGnN222`D;t4=k+xYs(um|P~zUKc3afY^q@cr}ovLJE7eXO+M zwcSHL3FuCcu=g8k&&hahbAA0DRn1M!O#FRb?$X?9b8GIx+L48gM?+7oUB;_V)<(;! zNxi(BCJ{A#oIQ118k;>K2?aY`S(HuUWeG~=cO}1+hE9T{%UYF=ES(jj^1u>BUPNA* zZzp`d!e(UE^UohiEWLYMGVf-2)_{o4jw72wx7cbsU*yq9?=Hh#vbW}+x@hO$0>_11 zIuxS4w!;u-%@MB@#0qC-pYui*z6&9rCgG8~lj$s6=>%3ZVoeyFs--ffk|yx8n7MP? ziKqg!gJtTCNB8wqlf%FC4J-NC#)5$kL09QuaIG@n%EY35mj-&9x z;vP2xqgymtFn%gdf!vnl8!KZ+;=(aAqU~~y;iN1NQ+2=kAI64erg!c_TUDwuUN%Q4 z(G|_3Q+T;>bSzmO+mHnK=vTDIR{%8 zF+jl@#mqPZ)g#|R34QtA-L!<&g5?u)2Wp1z?c7tQA}7{LCL7S~l}kg%%3N&%{vMa?&7zfnVm7{}3DOBsY zcXKb4fpCmAoH}Xpz4h=S%!HMvY*(r)_!zy%v}mdFi#?5s?RB^fqx1#I$#E4A!qR^? z(BSNc6E(tysEkj##4T?E8fNq8&r_kW4^UaCY@vX!=5iv`?kmvz7=O#do1~|xn z;a$OVe>n;?b&(tO}o&7p_ReC0WNCg2lfAq;>B3neBB?F_Xe3=WcYhju_T(&5Qcx<&fKY4-w~ zfDPA!EHOE!Q<8Kg&}ki{Gbb}6uNLy^h9RYo@T%EwL7`^pF8xJMah+&2du*rP4_Okw zD!fRUWBOWb_$I%mGm zY8;^2e5#u$EyygH(tN~MycpwGRS(*d&*!ALSV=lglgwt4DNQIyP37?CaW(+EvbOax5#hlPasW#9I}1^_?e{^_jvA1Vsu* zzaO1oo0NUdUKiYy&<|8{3seKcq&rRhXtJQx!<> zRY>Qnjk6S(CZ&dQjV%)=*b22-%gI~;58Z>{S-r zlleD$=kLc05S01pxmm|!AOQ99E$u=aBz!9?*T>s#Z9ul^fT zo^^hu^ubF|_8K2b(aynZYGAgXV7j>sO%LHR>uwk^uO6qf9LIOG&MSY5v6FczTIgIo7o(|h%sCRYPIOFh0 z@aKftEaMOpd^6Bf?AyI28YH++1NDu_{pIALOx}8+G)(g{whLtuf zITvGk56QclTGT#%e*eK9nGVBttw-+_ifla9w*ArO&a>B8$R%|NJSsklUYq;{6z#hu z8kkyRgdWQ5c(RF2{7*JB5P!hUIeasttY))Uc?F5QSqu^phKaBcO3)dRs1!npA~qLI zl;Aw?a!;JqjPo|@&n*YRYOB$~*|tqxQ~L5juw^!$oV`8ZX27q%=V-i{ zGG}0Qh_jNN*&Jr5YOHr^M^0}tJxfi=A>N;kl*!B?9z`DApC47Y=KJoM+Kk$nxW8Q( z5I7kZh3JKm7D9^JVYddN{7lAB1#*B821~gdkC~1EA0U-ifr6P5&_#@aB|`S!OVvk( z5{Sjg`OQl(4oZ_Hkt_LTpG?UB0&L=lA+>86=5LrKfMu9X7)XkM#c(2|A*Qb&o)d*b zfD5t^59cflsjh}F(_u(JXGj4YM=8Mm!rTJ1c?8EIhixuLDoiJgw8EyB^GUFHD&Xbh zclC45lZ2!pamEAC5RRIGm5Kr4_vHpqw^er+C@OF#4GS~<%=# zC1Q8idUx~v?dys&&Dr9wZL2;84#5CGuMT(m-!f|)|HZ5+Ih)w15-`w<+1WbF8rYc7 z3(JYhh)Yr{dDs}*S<5K@TcGsA%Q+jF)63d9+89{>oAtxOnJ^LjOVKI*o1tc6VP@`3 zz{2o@Wo=;QM8HBXV&Y_EV(Uym%f!g}Zyg~aJ9jOHpEMH#0V5+L^ABOiz^X$p_@8S3 zB>sbKU}ItJLGhmdmj6%}*C&R+ogOXm+#LVJ{lJuYu z{Gl~XDEav4Mb!S8`TsJT|2b+FhW{G%{~$e?7=N_#f8#v=SC&&*p3%0S0cPV7DhifK z!eM^$CXNm-4XtrrC)Qj|KnAjb=jAyTgj^6jZYMT`TQO_xIY}Gjb#TiW!@%Ac)2zEj zux0NA0uEK(Y;P&inhaNf%&~b2u{a3~-i7wS4Qnb^&HePfxpU3`!#MoHTzaIo?SrI1 zOX#H^k}@Em2rxV_rWhV~%2OyN=mq4Rl|Z?W-+FCp52rzJiG^rEs&x5CH|FB|HPZ!m zqT2L!qe_{0?kU(zx9fDbpJrmzkh2406b)y0pS?GvB1w1y1Os46#?t)1Gh4?0QjM^q zo&A4oiGhHXla-E%iGYKJNrztE!114DU}XDuEroy9@h9m-!1QlY(a!mYGX7a3853g* zga59%qKT88i=&Z=)6c4^%Nbgl7&-q)k~Rzk%>RLqv|%J*X8spV(&oqfS^g1!7F5{I z+RjnQ-oVJ@r@pWf0|C=N#`jOjKeqIfr>y*w_a6*pWkv#~|6nL9GZ8TT$L@rg^cV>K z6@MDZJK7m3nK*0F%ZrH7E1S4G|BS)F%tTm+Uew)LTFo@I#{g? zA1B(Cr1Jan@bksNyEo%Ur4@0u_mzTCwy>1MfcJtRol3X# zkZJ2qBPb2`d8gHiXmnjC@b~Hs3gW&#pTR9h4)qq}^UH3w$Ns3xv&-qndC%uN<@)51 z_qW$n-mBjg*^6D-8x`7F%L0LkHXsirriMx)dF`&y8Z1RijcMdXjhb zkZ4+MXbq^13s9gs{>xQyJh~b(eVu0J0?GxuF~yr9?lWn82+{zlAS8a}_A-=v_gCN| zB|XY${Xua)1Qw>iyC5;eu$hX9?ApP*SGuamXmM1%elgyRc7L;C$pC1%6fhMAaio=M%nYDV{nvLzp`dhh;3I8f=2N zDNw4P>=;}BgmLoV%R4iu5pn`f{F0fU=p`wB4CvnMPAUB`V*<4h<`giD^XB{3Eb$WP zp3d1LBt*Ck!z80dt0*y+X?b^v%k>^mylvC_NYSB$;XG8gFk%(Ev$q1TRYv-KF<1qmvbL zA+WMuQTewDT`a?7`2X^7y$;K~fTz>R@Gxxi@5^Ww!;y#%?;}nB@wIk}0L_|9P(Q-F zo?UAeFNkhMC|0y~`1s@HO-Ik$-l5+i7&t}VBqJ;vmW2NtrHU(S0Y+Z%@_T7sx95F-PK#)GJGbxvI`(&;Cl_wRStBg4k1)Mwaa~mTY zonb4R)->mWP(r|xsIc}E4qBidnB#&78w=GC0oKpgwk@&B=0ZFU0t|l>CBi&km@G+++PXSKukozC@k5R5w}Ur zB)DiMg@oBH+JGkPc`Bv8aaO8GrNa-#|Yb7Od< zISgc;pkug4Q4Uww9K8~+uHT;zg5SU1+_x=ixH$6XXEwH|>@ zj2TeWEy4%^nk3;Yz!=0}{{dohv)3Z9x~xGloQ0EgnW!P8Of3)29oZ1feX5OxksQ@z zhbXl1<{=?<+Pvv{V6I51z?F59S%NEWMYAQDOk^jvs0y}GQH?5S72=OOqAJwUkh-+y zm{h@d1`FzoE@&mu3YpK4-H&BS{-+L!Q%G}R_AX1Z@uhuQ$gV5LBxx#l#?h?+N8G2` zF0DzpQwc*9wFIV3r&|K?nmnzNo$C~{6=*VEXU?Tvb9jAtrM@0NT^BhhQx`cVYFG`+ z76>xya_EE3I*qO_bW=yvI0&raxZA@N`q2vM=9h?FmjS*n!UeJxfQ&?f(a<0a-y+F;QMcTG4K3cV!0fy2fgJ~X%GKr%+T1p{=2NR>T z;rN?Hb89#=o>947CvSYf)WkJ?zcT5m`%_ou_>zV)8xlyWaWUV&96dKR`yH ze`0rR03*q!KP@vzAw4j(612px)d3_t%^uYh|J)!-ijztINMK^<9AG~WR3vq=m>!@X zpA|?1Q}n<481%pPum2zS{{L>#>Hq)V`~SEBKl}XuXE)%-1OCV7{MQZmKSk#MzdV9} z6!X7%1gxwaKW_T}qo?^tZQYi*713u|4Z*__R5ktC6kHM^a>Htzcs#g`H$JixBA}Ht z{1$9Ql1ZS8&tpH?{J-NAafJHq6k`(6S-m`t3nn-PfZq}FYb!s%P+->XLLiSM!pL6 zdHa>9c~@e#=McAkiUA|yg{{Z24n%6p*24$pw%nD-qHvD3#SX;iUl(U*HtIaEem~70I+K18On4xye;$P$oolW3pRF~vF>@RP z%USBrr9Wmjvp=&NcJOP%U2HiPYD4%Bt7F73@xojO1JkirY**2T9Lmi<=GmdzWnqEk z(REm7Mss(+uP4jZO!4zWhgYdDgGFaUv%9`LWUG$Ie#mNnzbDJ(aU*L=MfV3=y%3a6ZRE#@fI zbn8Pze^br2A8^4PI_5aG@fwRDW;>o@m;dvBu=kBIq6FWUqc@&;W4*C$+qP}nwr$(C zZQHhObLY34Y<9E%Y_i`s>FzJL?@hWYRY{#b_Z(&dBPPXky9S-oW!5_^@yJ#`hffAa zorsW}!dFrh@uyR}5@hRhGe8{3%*b1dK=FC7_jcP@kMW*fqt?G@mysi?kW4F>GSqIY zq$oWp7nqsst1Q?|$TF~PKY-(eUx+*0pY^o!jl)njW=c?cNbI_4go+k@_q+s-Wz?) z3T3-?jfdghcA6p4945OL!*-~O3%TPGRky~u#d{Zfr})z?c(r!|4#dL{n>y9)$0DZH zB&EIJ%@PZd2hXtPcT`BcTbFezvQY;^p54K?&v?7bR4Jz+$8~Dek`LZx8BdH+wp6+3 zTN+0Z=+Q)jTto9iXwIntXaa~F(8}~~3=KxQl}Ghk z%=DR!t88D7S`JT_up@egn&aKBa+=eyRFaKz6Jj}9)Tg@w_^1$rY^a)#&VpF7;$V=1 z&dLijjSEP46k)U~l3|fqF&0btk`w2JQ(=mXfxbslSezpAKfB1{;r8Mh?OF)Eo87sL z25AE2{V_sbt+uJV7Ba<+!o}A?-4)-<)dClIOEQT!-jW*=X&d^K4 zmY>`h&$Od^QFRDs8_>Tl!$nQk&EsI(U>x9oQM_iDqFqpbZ+4w7jkS$SC8vfr8ELwv zOM(0?5F99xo%LHFyr*&>!H&+QAxcDb605vDoLF^EtLqLN3dU(uk4%^hJG|wg^#5XiH>T(wc#8%q^VFa3qsLevc|Ag zGM45{IP{KIiP!rI3*&l994CXM#%Tk9SkS z)HB|<(=)MqL1gp@yn~uLpnt6qPWg0bkqlU;8>s1ad1evFQmhMJP0aIOo>$zHE5@0o z6cp3r>9SQdWPw40^D1%R29%YMR>+XSYqBns4GBaBie!nzJ3<(yqMu2Akq5jgFpUqp zLYb(ITb|g%Y0i(@WQ+&=3k`+&9nH~k4GlDic6lC^sK{Fn6@CYcW&7x$;dbr}mXA6_ z7WpRisXO7N?}my)Jt@*3JpSjT?B{8)U@de-V%I_t4qX%Mn}+Do;h#aUF;M+{Flzmr z%s=b`dsrB=9JpCSj=Z#&cP^XvJ7T>7KptTofNI$3Owrp^0*S}1q>|aoH(1p&?`=5{ zj{~)iQ{X=~DdO@5>d&-k7SI0*2bm_T(?7mIKg{4bfjt{gL(9mVC5|vKBaL@E%=3*0 z<}w^Uz5WJRJju)inW)b$RMp&N49W774mNi2L9oJRY<#h<_+srm*ghFfNy^I){RMav zc26#}l;~Q7Xb*WWw<8QHmARkbH(;NaAaFi>$;^Wlf@qt^s3MDRZS2<#;>PW51`Lvx z5Kus0lO<4%m=HWuGn00YIOiRAD`M}w)MHgrp3Q8PNqIZTUtnb)W*04ohbIrA0qT^+ zdQVdePP>BM7)~QlJ{$E<9KJ=$wi`M~nMgn!cX(0s4yQWM-|#ptpc~MaQW4XGKuO;A z2_i~WtvKCEUIk~EaIrB)H(n-fj?xy%>}n>SbVjz@v$Dh3aMRwn^NzVB?7d=^PdpAH!5_zE~4V zx~|X%Ax`RZB{scOOeQ~K z0>+{w1Vll2qiP?dL~Xto)(kjB`hE4RmwT8XJ7LqXqi$WYY0-GGl|LJ?`jo-%YF=@Y zL{mDXB!)*>pVZesrlWgEO*Q#1o^=nYt0r`H-L1iNdYY42)rg$^;m-|2=qMPB300No z%8hAPlNoQ*p2!S397i9jlK3kns3wJC7D)enllj1ardUTVp~a6(sGr2AgFx!6&&wr^ z0AeIZLcroGhY~{|uz)4H1Fw<40d|}?Di0u!p<0G2Wy!TRa5I)mSd1Q2{~J%}GNO$% z(HgXh<FU#juBqvKd>L~_&qV9R;5QJKLcrhyLDw9Zho6q${Zh&E`$lw%xPV`8CAHo?*(YT zGv6wpd>uO~#;)NY*j}Qv>n~lw$!N0g>+b5xGP`5PYebo$_vP#*M((bwb-9dF_XGUs z7XMcMhS-EBE!M%2WOQ#g;uKOs$42cMVrV}p*G`lmisF``eq=RI&F9n%n*Ic(#}$5O zy~<0@yn)j^@P~QOS;i+A>(u>NozJKG7Hk_I!#}J`!n40}XO=aN@#aUhrum4~n&7=# zhc1bPjVQhq##fA}ES`)#xe`jMdJA_#LBbd>$iFE~Xm7tiTD`Cod>_FeRWt26xla-){;z1P*^rM+e|xHd&V^0;?tpP zRoU=d<-CjD^g9PXYH!NbBDk?dJIO4))LAviq&GD@P()^p!Qv*f@!E*hM(Wc@rABh* zDi%cQiuqEg1a#3NVZ$n%V!`IngJnjddLhg8-sbg2a^C5&?c5$zG^Q;oM~2Fw%TYf z`l}#gxo$}UQ9NHN(Gag#X9mVWeC>ohvX8!BNvwp)f;c2n$X112}>3Q=zi!giMaBt)5ssx8E@9Nd2zR3KAjqiF=D^i?R^=UpD(Cs?uskCaq9 z4+wbnxN$|0nS8I{)i>i)sf?^MQrX}wp`)Zhcx5u7bX`)KX@7yomZLo#qeC&WlEJYI z#Gal{3LJFV>i_cUXp-v7g@tl^O z$?DmdH|!-%-o%!OJKJsK>{&!wQ-Fhha1d~g!4oDGr2=S8o6*rHMbryi9HDJ6e0Gar zHsoTPzo89UWGS25F)wegNsPwJ;yT9`m;&jx z7x#U7-SyDAs4`XrfO^SpKpaeQLp4t_a1Fz6UO^g^D|^kcxB&wg3GaPD8@RdG8KVzT>~@k4(PvqXq+@TGH3 zZuQ<(qtf^Uz>gRMvgJPY%^TBq?<%AqGlf{jZLv*Js-+s+Uj-Z#ipBz^g5GXUhhUn) z0#?i-qy&OUPrd3gBrGz^7V(V7^bPTn-#=S@79DNS9vr0tOO!l@#7t>RW|I>(_HjW<#{I*zZbC-!}`=p7QWZ z4aQ%rXFaZ&>ofVXU>jYMVLsRjmeFa9_g2)Hjt9?;2D8mURD8oqkaQz=MEGEB0yaMglfLWIy`eeMC&xxiTF zct+XeSKX^Q5=q2R9nn;Ig+|-blhD+})r-|^T8LgMaU~@ahu8YQprSD}^NHZA1`nd- z!bJm$KK+nG`qTMTxtD*ELE_>&3>r;9+T+|3_YPVnOvru|9Xpwj=vjqB)%a4~_?9== zWb+W(y4%osjT`geST)@^^qmAacMyK0#?v*{+?LB*XEXoKj4$;?My_v$xct?cQ>ymc zc>aA%t@vcI>0lx>nCDRXe30g((sTEj);-@NFE@P<0_2VN?V7a57xUABr zHK==U9`<6hH@$w34Sdu{yMPR}zKN;NY)nV)Mj8#Rz5o6&J>NS|IiIa6DHO<4{%PWA zkZUJgOLO6=j@>v*`eyNg^|;C_^Hr6)3S-IP-no*QkTZAI4jAp%rqy(0W*1_26pUu< zeugh&%78d9MMCoqM1C=>4F<5JV^*xTV1NX19;#t>P)$feL5xX4;=O4ZS5BB_oUY)b z)zZa~mgM@^)zU5tMi3*SxK*xOpJWMGb8SPG;3zL|=%a88E-AAqo+o+O;dh5_K8aD!~V{aSV!Bc1iiqNNEQOTFN*p3 z(D3(T3NU;lLBhfrH2yhR>5JB($OdHcE}O8giwm-a6JiF`F8oE4^4+V3A*Wl(eZ6FY zhNfGt-|Ns8tLR=9igwV(+S6aue0YqPP%jqh?Xl9J4k^#b93**gwpA8=r<9sY2R%v~ zCC-g&?`5m9%G`rvX;)Rq_#++#!``%C!c%G2YeJvY8~~!Nw7M3)8+V3Sg{~$$ANA5W zxiv$33ZfrwXnAmQFMm5f?s~s1lScA$gKtIy$_(e8dZd*TI%J~`K}97w{6B*UYj_KYOHhjuh)(U zj?>*;+g0=o1rZmIH-U%umHmtQppR3b8^Td)7%3RgpnNwN7e7VJBbSHHoWD;_gj%+m zS+yg3Nw>VtD-}6@cPu=$qgd(&?%x20=oOd0DnFg-THN5sYTg`$1Qze6Jw7laqS!)s z$LrW96f}`+^On@)+cwtDEX?Ubm=&GseXnf{o60T_IfDQTb?;LGBvMIeIe1K^bdbcGu?>f#Vj!j=OENi2OHQUwb_R<(i^u0@RmwO%8=M#{DhH)Wat9$%$_Fx#KHa-9 z7(NQZ3pYJwXDZ-T*LP(LDRo{J`R9^5?p0YBJT@3XJ|!1-t`CY@JDkcNH`OvLA9uFw z+tDLk>pkpU+?(W1E2IvHpa6HrQnyR7_4+ zx^A>S(A`%`mm$ll=r7G~_MYqIzC|Mg4=G@lFH2{+&orTctAxw*cTJ+wWodAw2n~-4Al@2c)&>J)NA_ zH9aZ^E+GpJpV_c2uFxwi9|J$R25xrE0a7ohj+c@ZTl>adm_d1D-(2w{WQYtDN6Ry&^J zjK5N;pBL*vX;M?51UlRiR*&A}X}Mb1=bt1<>}p3>O~$W=p$#n}#~MG`h#8D99(%cM z(_k$xEExVpcD2}|L+waVSu7IZ*YKCtWWQiv0eu`Gv#&ypG^Mxn2veCk!?=h4tzfi$ zfRs`(%p}1@B7<;;jDGhoUu{>}n`E3W8?jqaN}@dQRN*$bp*|gN2&>_JL z@}n9E7Ekcy&km8so!Y08+R{n?hDqtQqLF^X;lE*_-)#JaBU8Ckq702_)IZdqk&-C{ z!o?SOz>CAKVW3hmu2KEkD|pcMTd8q}TC}1II`fHPDbW_SM^9lx1(^#KT<5D_R3){f z$~G@7{}$SFyJg?MFpKtROudV@zjK#3f=Yui*D%Tzxd;4?Z)6_1Y*{}WA^H;brw7*= z?z52IoF7aJ=@-S(-Ibj{?$H5_EXM{>@7+K?Q(mXnIKtoJ$& z=iDzlo0Ux0j)2#`oHpdE{z3B}PYS*?WgL&Xi%?g?9D>lVOUb|QTTT^@(kRFnU}m0? zlRZBs>K0|Ul5&XB8Lp9DEPSRpJ%MPt%^{GgEouIDU0kR;;N3X((FbqF<0>JGsbt@# z-D$yX&^O!Jy&il#EgWU`yn)r-a*l_%i8M7*f&n@Qfn!yTHbG65?&|GQT6YV%I;*$y z?_O4JXteIl0pX0@onGH{2_istESFF9BS#JV5?%Mi^|-x;at!%_fi!`*ZBoJyV!U)f zh^1f(=IyF@Lr2J^_m@HrqR^I2D?NA9i>- z>pQY<$OYj3;qUFjCF8uM*mq)=0{Et6rcP%lvWU9}a)c_~NoY{LA|9u?dU{zX;v`^$ zJ_-ePzoh}bG;PTcaszAM=ODQ;{P~NWw-^4(MObir$hc5-r-QxM8oMC%@|?qDrRt$S z5pY#Qgr*73d)rl5W(e5SnH75WAw8!lxixX>;JdCM!n)R7$Wzi$%}(Wgdc(U5xxrclc#qa$pJ^Wcfwtk;6Q_E$(W50EM6(RqaM2<7{5$DaMCq90Q8! zHz5q<;-qK8LxNq{o>57kld(rNU^)gw7FpBI*j^@e_msjzeku?NtL}-`3_d$|9CUu< z#b?H(%e~1CtLXjwXp<6w^#alUp_(?$!2Q~$)~7RKS2|hJl+mUt2<4>M@{aMiD*bZZ z8`|aU?YWhOj>xpo2%TlTR9jI<1X?yYFq#0ete^urq&q5E0D6Yw%a&(iSf^%|UjqCakwIHRoi4L?gMf$o} z|Mn$k^Xe$YANpHqrIlt?Tkai?j|&R@wpdI{H)b7%d)STrY0$J4^`k$C#W(7gn;}7M zi$0z=q)$JRp@rbqscRub^P;92YWpKS>l?9Z&3?__Y1^KIQJmB- z@?t6YomX;GaW%?Cb(SK0_Odn_Ebv(f{h z>P+lQkOoz<`w8#gVfdUJvuhqeKcnm!@~%&jc-@>ppBmOSjSn#R7X-Nb#~wMhZ8Qn& zIV34U(`_Us?|1Q>7!nGMvoh4B54iY=RbxgENrIBJ+1s3^DO1~Pp??iH+QmVcRQ;g| zbDOUV4I7$m>86Kgn0QkQX5&FTCzcyPX_M9RDx|V6UT$2-qcjc{U6oNmW>c`T7_1eV zCP{Kti6OTyUN95LVi26xAV*+ZfGcVpRXmjB&=IJhviE-d-Mgm6jyb31>gA_ay65_a zZQ`&b5f+pQew|SnBx+?t3*9GE3U8&xSsR;~hU&NE!-*_h49yec?8A$O2OJD#y&mpu z{ZY-UM^qjMcEDJ(g~jB9#I#h7LumRwIabb1Z`Czu-flOHfA(virVtB_AAdrBJj_#x zEl|QLZK`K&Lr8_mwy~>5w-*JZj~3%p3Y~WACFotUE$r8z5~8_F(hCX;(QK2y%$>>m zdcSFo=4n~o144bN!@W@)%nsC|7S^U6&O**xX@7eE8gc))`T$0RUt4~=*=x_M+Y3Ej zE7WA2Ts8$w4jUAHnpSB>ZRxKKzrnSYo3(p)JhY=vF85<%Y!1R@5T;(~hU;3WOEtI+ z)%oAAXGyWusz@VfwE@BU9~c-esu6$qUe363CVXUa6S&WMq!X??Xk0V8*@sy6pHCn| zd=@|*!hUar6Vkv-Fg^UJ5Jm`PGguCNQwU`T>?ICVqq;S$QKPsm$=3_jSvXWDyV=+k z7&5H_T^3(d)JaMKp$4UwMLgi6>S1!EEs6i+b%{G4u*RQu{c45+%l5`jda;cs;?5Jy z#kRxfc*3hE3I-JF#@G7z?01D$mM53hnd|}rDhr<=Y(z-KuhUJs(URv~qw+d2k?l>5 zJ0|2$%Y(9kJ67^O5j7E@UjDnvxXifHxYW4XxZJqn*hbH3(nd1|Ta=>Bg5=8B{L$X1 zfg@H9zWPQQ`t?hLKkx~-n>_ppIZ%BJNL=d9(j{8=Kt2}UeA*0z$t!6nQ>Wo-oMwef7-vl)U@&LkQABOeW{yndoN@E zO3L7Dh(aAr-kZMB`O9O(^LeP{IxaITpH77X`}Nqx+K=_+>edYG=I6u1$$=sw)#1j~ zg#xmWVY>>o#a3dlbIb01oMuK{LmgOTa2hMNjsW=8o@+r++qHU~t)uVO%m~V30Vwmn zan|7axPaqJPcIQEC*n^io^F*+^lad~L<9OIWaqKAXTuS_ zst^%E^r#7Mn@Q8i6;P1d%L2i%m00c!RN0&R-1>dw@#x_3_09bK?)v>s&)vs|Noz-{ z++HjqScLWA`fIDCBIIoUdf@5%>tG2i=xITJcn}u(s$CMnM@u%1$BkwLJ;kS_F#hM^ zk;Ax+dQ8u{U2amamy%4N)-N`Gbi5y#Aw7`o-N)}#+n7EQ5+cOw;8B6!pJLY9FC2Sd z!W*$OS`lF$2&+sVa~34ov@M?_O^QGyVWBOg7rM+&m;6%P9xEWF+uRYfnyu6c)>;3O z{AOGLO_sn91D{UbzqU^)4sqUG_f`B%@p+fro3bx0UaOhVCV$)|#!|sE5FnrH6=_7j z=>+6rO14~`8cU|HGgmML_ILvwKiVfS6bY}LJ!)*^bTraafL$&lIr8zy_Mc-t-&k~s zJ-swd%#J1Z1Wh0gXTCuy7Y-Xz__S^L)i$``$Om=5Vhcl5x2fL9ef@sR`k%6tlLcz( zl9U#_t=*di{Paf)X_|^dZn;{eM6qq(*0wpneX~cK&_|xJUmeY{{mnM!ZV&Ib^CBU! zcvgT(#)(bq_>+k?6-C7~4S1}y2MNjZmr!kMo*hNT(J4#b0q6M_NJ2{mNM6_skG2yu z?lxuT*{mzcX-4bCX7}1rdnCN1U8GvnBSLHp$|Uj^{X>iXZaRE@rRXSmaQBGHwQk~> zR}?^Yx-5LX9CiZ+GecvNkU-tj(YZpe$H~D{@stul4Zny?=`|SU&U7+95K;MQi)eH` zbLb;oJb8J2U*hrn2@Gjt{%J8r+2r6}BsF|0csqLGCTtCLOYnXDigd?IT?A9MOZk+; zHLTPed0k50JxPnfd`S7N!ZBhaANgenA_(4{rz?6E(=533Rl zLR?I=9ycs?uFlp2-?>d%f3TBC=cl8Zbn{US z_X1v>`Z2Dnlph%2b>C{6Gc<`7I-0n%K%!*CP-`|aQdve`v)d~NK*{_Ncf9Z0tD7-* zLf0LU=617}8R~r!pR8h1nC_y7yo#kS{^7uZd-gy*p513%vAk0xN00t$2qO@3HEB{(Oy zwOLc~1vb~pVpBb7wAH=)2Tl184cKWnybn@o8%X29_XYzesOy)DvJ2CW*er|(D72|a z`Pa=;#iO)?muc$0RTcKA>`PX!0IDL)hThqUS-74PAk-!sVb-DB?0$@W2gdw%HrjCy z4Q6Mf$Ih0h*v}3MZ= zXcs&xD8#r)4cP`(EU@e(-g>v56~(ri!o0GQl<(knm*N=@Y)>vAyx#kQE}O{mb%m7E0LXbfb7of zGu$5uNx-5|%(Pv}^gUOMwX7_1e6k^cd}~;yAmcbWSD#`#PHgu?EuS|tkklNMDr^k< z3hdrBIW*}lX*!*cQ6~x<3giQvHr5N47SWoOFDPTplBL8%gXIdRc!@D2%ol}UAdN}# zSL9K%+z6LTG7?o?nxrd%^NYLbEGp8aN0R;-AZ2kkl_X|l?oO2Mi93Q2L8|pCOY_B9 zE5J{+BPlGxt96dvD5=(Rnr5AN8_HEC=Eyn#7h-)s*rn0dHuT@&D)O^l7nkV<&VFQx z$@;Sp&~%&mLjIC8u^?N&r{g3)tO3##A6qG%qA+Hs+Ow*^^`NYnMa5UWsMW~`^-jBA z4@Sf5LS~0?ecFTU9QMBrD2Ktdeh+20Od1laDGwNOs@_t5D}6}-t?=}-h2xH`W5*Um z?S<=u;#uod7N%B)LT9`6E$e7)o{g`1w>A%imaRChZwA_LAh3(h?ETw|Vu9)9`d#pm zq5_Mimt`kfvLbPTof7ghwe`B|+4(JNX&URT$Nh=pD$ZTEnFz+YrNdadBCIer)HGw$R@dFD!p;^B#A(|XDY`<`8*rJK9{^IyWUB7gJ{x@K*~1!seN& z&E{s)k`;1(V#cR!;TCOw>W#RR|A__Jb{nB}ig!Utf%FCnA^u^S>j49(&=b)3FTm&j zL-d!9o|XBxTlfFKO8=+oFFhMQ1M~md54))Brl~N3l1(Y7yoQXoYqU^10|Qh~Fj~wD z*N@){r|%1ZM+bpFp%5R^3Wc95i!7K}q^MY{F2@@ogyiWz+Mv4x2~;g>22UYjqq=_X zQS)`v)&0xM&G4AcWN1C_}FfE<~qAI)SD>#NDdiGP zIR>cvznld`~%k@f`EpZJSXr#2e1zhP!4;`bh4@RC2rE&+* zkn5O5sET@1slglfo%(&*lYi9t1x|J#WY2IIf&N}-bU{QOa~PapqLLQeFLJCd@b>KMyFx%3b188xm3(x{4Q_wI2@OzJ2JsTyV?C9?8D#V;9ZbVPe zlp!9VW;L7^Xc@yYu{Y!3h(}OGOALz_Rek@|)K<=Q_dHS_ZIq=|Y|M%;I~1~|RW7o4 z(1Oe=|DRUq{Mlf0Gb0P5f6psBzNM!xKbRzc!1KRWeRiTy!+N`* zdFGg!2gfT}3*nn<%^Yl=VOgm&u)iWdM)pFTyK}(98>riE!8shY7F6QNAZ&G8z=xl#Ws6{&5v!<2LEp#KXU_6~mwRSm~?IM%qkQcf0CJp)N z;QtN^!U_OaUf1H=sTe!^y5d; zf`lQO22sl5+2%qzc;p_cLn{HN?V{9<9Pk@P8lFx$nE;ohLX;XVc#Jq-AM$9xx=21Y zEM4o~v>PD}K{`?r&%MGaOoCCdF>O&@^t=s$8d3OrzU{`nbiu-@orUTiX1463yC_-^ z<2&02!? zQ_?}jqw$oBR5HF?gxz+?)xS$;X1NX7<3*E=cRuJLa5g&5|WGA_r!7)Sx5wwo`>U1sPR||p}{ID zk_;%a*LJ+CdD1N!w~0&-6?f}0mYtg^&HQo`*X6X9W+{(okG{_@`m6(d1vbZ3faP3I@zukZC*2udp@lmDIo0T5zm?y!#!@cb{cYXunUv& zvH7p-BcDwe-?sr-C&Jks+`Ao@!V=D)RrqlBA29@4S_73sq0S%$58E6dfbuF6LK?Js zHq*L2oAS$nTNziRL&}vCu~U5i#eZ+Wuj?n~eAGa4v?qN99k^d_aqJQqQ(g=c}XgIr?&0*@hl!?|;P=PGY?h3{;DsdGKGC zdJg(FR9xq+D%Ic34HND;fG<`TEJdmi-5}OI>8Q`s{A5vo!SLU-a4un4?aDuS&q^be z*aQD6XHtJKB5lVd4VfoZJ6piY-;K?qxniM&BZBV`xtJ+Dk;=B( z*j6eY(>bxd7_WU4<5Cq^pOX(DEW38))F?cXb%YETCp;a27YdSfWYr@3q_zp2;;HU2 z&RBQb0aj>{x&8TgL;QglhKR>Jh8btN#$l_}dwNHh-sOwGA8iLWpR;S*kd_B?F=|?U zy6DD&WixcoTYWG-oPFidxVjo3uROaH1CDl(us9+pYrL8LiWU;@Tx}OaP6zF<`>fWe zu}9EaA5vI=_X?zqF9ft&hedWwH2n1aZw$O!g49Rnszq2A#^=-R2IDf z846~d@&;_0<=!b%& zFVhVF!M6;n{a&Mo4OvywJOMi#NDTkl8}a7A(!6&nsX-7%`0N6)ZSG2q4scTI*k2v< zN}QDlJ+!^d;T!u8bMUxvx`c2LQSr);=39@!Q+9gt<*9xsO~}>#Z5RYKCB|!qT%NNYd~a$tiSA2?4R&P z61vZg+#^B-&;z2;rr3~25TO3{MRn1TX=DLmHLlCdQ1krD0t+Qn9ySHt8wr+1Hl5*bZYZa^y9S;U&8UA5{;*KfaXy>R|NeH)(^y)?EWTUsVSek z(207d__;*8qOak*tjXCfskn;56T5Sg?{ygUk1AvLh0x;;-&BblX6heD{9*@{B1*j5 zhd2KH4${y5#M}&)RTYSm(ycO&uBG>#$E2MZp@Sr4a!(T&~~Ka6Xr%4 zV9jcj=9-y6C0oQANlm2Il-MM=Q-vRiP(hFh_bwZ=_(%&2@<1Xuh$B{r!u*uv>=R~n zDsV9Es4C8Stou=iCUedIDCQu1R4>81x1XpT1PiUvgDXVHeUw~1OgsUu&CVR!bSTJb z5!Yd|r-&dpJdM{KF;bIS5$e~-A8h(nLm!8GuhuV~d!1KzE~I8EXCAVZphY1SyKD6t`n}O@ z&Q4vPyMAIK$l=?T_8X=AFz$Y!b_&h)KdeB8STm(;{TcU;PNUFs#RG9%nq}o9{Br$> zfOC~&JU)P9g@&m5mXH9Ufu(NL$6NBw4jq&rAX)0L^zKUy0E8>UrF>OTOF%%- zgTPEd9@s{;$U=yFet`r(<_9<}Ea8qEN#DsR_{~|#bof4q`VD|;lELL($}HgaphPwP z_WP>{-~97+d%%pYCeFOyx&=)tD(W7EY@IraDP)E$4*M#+a|HHh%cIRKc(#4;?@V|L zOel@lgLq0(JFy)5)||4S`r!Nyv{63l0Qz%9k%>}Do z(o@m`;u~B4D$xR9qb7^Oj0#KRw&C|Wkh}dzfI}(G)HiG&Fg1dQi5KdLBUj*9S|0U{HKC=s>G|>Lx#;sEb1~wLR(Y#RCy@G(*|!^;UZS7^^zBKi zGHN9lhQpG2`!yZ2b*MxQQth}I7(BGXtr-*~ZT{H5yL_B+z9aw6{#E`6cjWsiKxRM# z8zw09+wR2}cy$+)_~#-McP)0Dkbp+U%WGl-Var$8Ka>TOpy<(|x)a6rz-hcWZJ{|- zAI0fo$soS8R2g10TN6Dg%mEXmZ%O=BrEMY6coH2E)hS&k^RO?a)#1d8j0vTZ=pkNP z`H|$oznJT_9+tpI@o_pns=tC*G+Sx@s{xB3ry=5l$8Asn@>N|WAX;XbgWP{9VJpdE z(r9w?!X!7Ns)gm88bUTkHcxaM@sZem#}R|~z1N{Skl!B7aFXeP>h`hQq}w@xjG-3# z+XEx7=_gc>?~8#7+zu{C8x<;70IEY5s)j@7ZH^pO#zZbGK#c^WZY8Ea|Hu#;cC5Gl zE-o*H0sQn485P5o)j>M<#7CaO?qEOoq6d_mhSJ?uEShbI}@Bd7Ow_vm7iG6 zSpTS09_TuJef9@XT_||Bzdh%YKv7%uEP3bOu--cr)Jwfb^BGAY5Q>cR0csP^b*a=?7Bmn-l?{tX8|u;NLaz zuBR~m#p4;>MA#0dh72VznArh}5{XsVAmtv_lf2-F_VEySwew!e+uSFPloD*JFF6pb zyI+77`!ZBvZTfTev@UV0T6V!1`$FIV(`tdsbrBbRAr->z5f0%=1$P4F>LK0-s4=`c za+3^8dgWOm_t40Cf{bLRW@43@K=>%9$}rP~E^IS1|NgP%s|>`WjW%Au)TGw3@7g5Q z3E{F0^dY_B2T;vR%~kr&^~C#u6)UVmZ|?iExq1a>#Qi5Z;jn^n&)hAKmrY?iAKda5 za|jprpL$);+L$|;W}j%~KDwb%3*#X~e7GM!X@ELwcz_*J1KA&Xjb?tELodJA_Wx+H1P_O9TL-ebT z%~@K<8H3(;VMU?!*`PDM?5qGRbzrbVnw|&~wU8FK$eY1ct{64-ah3=k=}iyx+G8;h zj{af$YIX8zMU`?j?poW3+c8}%N4#y2_GLKAvX2>hBu364>${kJe_A6UL3L^Uf|)ZT zXAmieoL)UD2fkJXwWSY?K2d58U^=IKd5z{KhxH*GnNzq%nCq}AqjniTvcCoiTq#=~ z`Z$wL9w5WZgKcnG2WcQGOcCSHIc3-KJBt^|z!F`OV3}s`{c&Es98F%f0y|E+cD1gy z_N^Y7$0Xo{O-zf(AN>@=yF+zRvR+WmTU&OaLFKP1Q+>@?qsbYtO!SDT3&p!bn_5Ru zJ-Dz7$i1SkkR0G5?uI_mq`n=Q{d6%XjFK#YH%8M}O)gPdZUM0S2V8-0hM?TYaz;?^ zs5W$=DJoml@xALI!PDpc|4*3{vw*=9!>*x^s=1G86^bq)S&EDV3yO=lHu%fg?4TH@6l zUti)i71SLVJ^A=d>7SPAPx%B=AEbWGxE%rS9DcsTJoi7qm$LJVy76__4wKl0a{zkX zdDf8d;>6y`ZKH(%+xKY&nI2X^h1t{T6L(Gb0$e%8t_j}J4@-r(z(cRtg}f}%n3GAX zA}-Z?RZ*tqtR!IuMEoG@&|5`qn}^aK(Y> z?j5HonHimBk6LZRk{wcvL~KQ{)ZQK%okOOAK`Qh z(+TaQdgX-%jKrHn};5v9h3oks{ zbgyuJEn?Ph9e_>?+FeZPgZsC@4T{iXqyaQ-=tQ;~6<6gt!D|+0{Ln@h2s3~S~-VAUO_05*U~PBrAf75=2lz$zc;jKtxfpNK_C+MHB>7k`fgO5`@=1WVdH? z&b|A7_r34!`DnU(^{Q2?SFNtD`e(ZT0a+P7&$AyeC@!Ly0KvvFK5F7_ia&S!^dUw0 zw3K9JM(GFI3aU!4V+yVATlE_t)tChwvB#{CV-Z~O6D_#6~;yHvn|Le6$SfBmz1zGGwus2 zX-oyV(Z2>Cq!b)u6p2K(TGgp5pjz?Nlqq4f`yaazCiA!ut~s29uL{&qgjqLXFDyA7odv4*NspLU$+apl9?d%0k8^a=>U`)Rh*V5Q1$rhe1*_Z z_k84x9ewug!Y7>`9duIL1MW-FD(+P0WH58z|4|8YT=PZP`);0>j2wiFt4e{d*XF z^Mv|HciqF5)|>||CZ|SxMt2}aN0krT)+G_+NZ)tNC^-Hs){E7(Q)~!j&M4Ge<6M$1RLZ>;0M#Bu7EKBqtG@#vRnEE(G6C3AQQnJt@J~UU;W| zcMK+tpl+w!(7`xumzRH5F_EBn>cYv0PS%%pJCqI&DIPaaHSbQz{_>8ftYlD=EA%ps zHsIRH16h2s3QRN}tOq_emd|u(H}4Ix6F5lEM+HT z1}D3-Dftte%XGSpdc0?{)ZR<8TNN~U)71=4y_K5UHY3*9c)#G~1)qsF)%4^!h5@YR z=_vNZ_m4*nChA6?`EPtMd-5<7W;_Dlz(Fcu3PA!d$)R+7Fi^r-37x3I4|DY~c-Tj% zm`G`#icN14j+J{8apLJzrmVWaxcioxcU99>gV<2=k-B8ti>e%QDNGS#T~Rq#DkJ+c)IYZ|0jE88REv1ta%!eLcE%nynhny z;bogc<~*PDU*O!f5qMN;&%@~GySMJiiO}8A!a)$ZO6Jj&X3+r8F2>&6VUY>nYy#8I z=aD&cdr}9^NT-T%h%lQfz;j8n^*6+Pg}9AcfbOgGvDn=gFC-r6({kCfFK?*LKJVo5p9l6na=WDX&1?7I%vTMvOnH!RmWPh7Ux>Vxy<06iKWyp27tuxal?8E9h-aVYr^>{)zXr(EA+iydUh(+sAECG*%uuQ$2jh6 zA2n_$DUKLr+HS zoZg6Q7%Q!3b{^Iu`uwNjM|`7Eg1CK&Utfs9Af_m$^VGid7eyNT6v{n)CFmmx#^}=b z*8b?ua&~^r+sjCJYAs>Hq~G8TO|1@h=5vF znhV&28Y%9;r29%^08UUnu#xkU^Ud0R$B$FPef&djhjL6CfVoC@@HL8B1523?SIzj9 ze$`+C{-UW|Mr6xF&)PpiNS+0al&BfwL7ZDC<+XSZJ5ml;Gk8;AKJ;!(ve9Hkt_b?m znCIjd%XuN!@kdX~&|#y5SqiHq0?nc3{7r~7C`bkp^cag$vyx=Dru~)z0sC#6nc>>E7FwD?cIr19E{@%U#XI{KAx0zGAsqex|PYwvXwT z>Ao253WRQC+hbOrZN zYsd6mqb#3{FLAle;SzF8JPN^YXWVzyd08Av>fI}lS9bz<;ZhYP{QGd?Fua$vpK=;I zb7j22!D+btwQ^_G=#8t`bKT020Lvn&l1&NrVW)TWVLK%KuLiMJe>+(2GgcvFIa>OH z+3O(#(ui;B#BMKRKI8B@gTku;^4EO#n>0rUM>9VpU(Amd`G(Hhh74h0Chn3?onq_l zuff^utPuWwgdNp(SJx8B60x)NWr&uy;pI8eVaAfVqdmgcZqdCfqcz30m`67~dpjdY z8$Ici^R97PsLPUl=FDSfF&k3iYd#A{o!r@6icxC@UJYNhk+KD$Q-}`r z@LsF+SdaL`%QsQ6r6Dc3>RDRVL%bFd75|CN4*V2dR=TT zh9$c)T@;<&d2H`P#;M{i>r-u)Yr)wL(W^>+GHmdle)YYyb);qG1$MLlA=HMzLzmiRd zH|;i0fnS|Ad<&J#*Sx9TX!id2#Lmq_qW7-tHV*jI@_A(I)OYOJAmepJ808&jy_wTNXFgF+NTWdcbJ~v*xypWP!c9m+4y7`un&x3g=b}rOq+Ijj>y($#o8!Qr7rm~)BX@V+*RNQ9+UMe2%hYt| zA?!2nJo}Vb!qbD@Lzg-ku=R7kyB=TaWSyJaU3p_tuqobH;%oF5)zTS*Ph~!H?by)9 z_l+IyQRaO|JFiK19~30%dq`wXCfD@OsFo1$P}ocBR-{5l&Zygs$z%G}Ir)M_^(eRN z{6)oHU8Z+*z3jDG4vO~~@s=vWd^g9SoV|y*-8fPM3S2n1t7iMRAHTy-voRR z+u{-SHkyrm$YrC#37R5LS~v7usco9<{W9O&*gdtgS0qy0X;)NDd`?Ntcwd|dM_W8a=;;s2 zx8vT!t#vWhmlc`>dW%nAp__9s$DN)WjAyBMKj}WCs-> zP?y&Tmp3Z^`+1-K_vStk|P9{H5Q>;$3 zr7Wc$=@smJ*LT#S!Zvd!9e=aI;rMRYhMgPVRmKRt+rqw~`Yan)lG2vkF7=G<>5=Cz zkGOY0-#_PnI8*l|~?m&gK5YfupS2HT$Uq$6aN& zQOx67M}=p;ezEb4=VQY6GaWS3bSsp(ZvTy(bH+&p!Eh9QDcnIuF;p34b#Ho8fxy$$ zOZ518f}7Yr^Oi2Uthw^z$7aHcX@hAPXBb+x4sP-`Q~fw-FDWD39uX-yVpBfvcNTS@AEjmM!aZuekQXRifuUZIj_wyivwM@n6xX25- zLE3xf#(u8nZ63ov>`mD8F5Mq0|F{Yu!#R7*k|kl|(AoNq(8TD?MSH(vj?st|)%wcr zfMpzbn{(bbsDyKj^cH&Fm#KBT*wc6nDjEv*}k{oVJ^coe@+<=%7>bK%Pg z$f>hD3G|<7XwTYXs+9ign!Nb2|H;V!XlC}ef##lA<<@&v{vEUv8e`!nkTe| zAk8N=9hjQEwJ93U717u2l3li>N+tKdn|Uw%Aj~d8TSWQ+Uvshcca5wVJ0XKio+y$0 zUj9TWsj5x0CFguon@H8n?NKMNHy*I%{ABJv0^t|k$E|C7c{U(o;HA5()-zLsk|O6HOM7ptuG1?h3=?-bi&6nQg`xQTF9T3yfD zZpS+JdTdf%m76!LBJ>`O=DDrT0$E!vkVllx`AYO2)Y%yHWis&9SsnR-z(~&o-LUkp z@jSb34)j!(o1CAhVP`w>oc+7diH29_i{IQubp6O1y?VIZY_1A^d$#;K>_yb5*R^8d zJ4SBoCp+#a*7A9@M<;0X8J`pWq?Z_TbMI^aZ85?8GX<#iL_M06{ia6X^iUWZ-BR6|vtJ=;5AYhtaZe}VIdrWKmju!kY`242>m&5p^XsIb!g z$y-}{q;JP(tPs2~CSff zGy2~Z3|8Kjn{(pk)ssnm%I5n`I#nvBf1G6O`1F#L166+U{(-UQNquCqgE`}{%^Tex zPqq`tu_gm_1Jx5y*Ig$(QRg&ceMBS`<*m>0cr+ziwCCTZ1Zdcaxuir5Jd>8^+M{XH z*{1#0^3?q|Vr<@lpH-DR+f6_ExLxw)DlXNUw^fvxq#V{~+&<80JkmeRo*4gvE%C#) zlBYT;6dAje_<%Rx$Qf|vO94zS$shI{DA~?&vR<`3BDS*1qw+}82(V|_b{ zW7t3K5mi?U{lLQ4M=x6^U7k2fF6Vc;n*KqC&S;X%Blf{v^c4Qp)0gmvr|aGf8Igux9VYrn`kc~#fqwnsXP6Uq zH}=|=iwdXHlPT$jLv+=wuPow!OU*HEl9!Z!3yzuFgEU0s#ukf59Vt)Uz3Ar{{-kLV^3#Mk(B z^Kv&HXgY8WJ-~DJV%$zqmJgi8TB7$Es(M}0ozD0dF{h2Y)Sk0ArH}kJK5}W8^L%38 zxr{S=CH9Vwd}N9XFa4bC8+hKul>%#3SJ2BbIrIkjotNt_mJq(7o`)$d*3U*qr7m^5 z-*)2PG2oHN{hZO`os!9ohzK6>AG4Dw<45QA+t{Pjba;n&n*virDh}>6l$1X({GcfF zyJ)DgsF=**N9BCuO*X0YaQpCk+sit4PEV#}jfsw>x!&G?TL$?GKGT&~Imth$tN$p> zC^+CzmSg7Djb5w;UE$Ru>H6LAl|<&g=vEpS@4f7+cC07FTzd(Ng4rMmqW`Al;x|_!$@}Q-!rOkUE{k}n`DwQpn={ec* zo@5~dBOUz$z5FA3KK8k>!S_5hC67it)H!9s(WYVB*mJwO!^(PM^Gu=Lv(xUXSwW-6 zo?GQ}N?e_-(T`AM#x@A)L4x_4pJeKd+RlX@$yRz0u9Nhs;5M0b^3=X3)`I)d zM|sS=1#X>nHhl2&;?MpNhufDwO?FJCUD3>MIgu}{RMH+Fdcqc4&D^NnMm)RIHa~b* zrA9l&r~N3g-9+TAcseJ;an#kbk`f9K-!MT*hP-V8v<&iLGSV>S!>@Mp(f)jqzF8jO z(C0xq&n#hf?vlmmaNWrJPO-_DnaSes1`ffYcEPu8(HY{O@J?@s_waK0v|eq07I+*j4oP&w)A#OKwkp_Q-+xwYEq zyH`Qe`)&<@V-5AVjN)B7lbiU;xXAfwhu_EdP}3fvY<3nY%cEPdvncG2Tr;_rb8|x* z#re#b_&LRuLx^Y3u#7-RcS&U4w-N8ROfU`8Qlk00_5Cs}`Y7~+nxSWmA3sW9P>YO- zDa42KGD()Xr58h-toDnie3h5^u=z&v&RqCTKhm)p%>V}m0{ei>MxM<~wBirHX8Iql z=kw#Rple`s4?0gO<2IALTkllbYdB5Cp9mI^gsNT~Vex6Q^tBQ?0Ut*4_nI;+>uw*N3QOyGPni!%9wF znXqzEY5$N-)(zWtJ&EkX`N+kGMM$uss^Nj!z4p+VA)I|p)xGwhb*P4nH>@H%Ptc7xA}B8eA=WuRe(2_Ml2mN8p4Ikuz92OaZD zJ?yG`8!mID9p*DoEIN$K+>qkGUxbdi-_knaI8RuvlK2%~{|C6|aR+!){f~L&=ktt4 zo|9!7IhuJcF!KQ0jYp6VDvuv9Af2;6g?}oPrB{BGnby#wW-9D`pV&NO?X(Rl;q~d& zyPr9sX9}#pv-h@+ggy~0%DytT7<-oJ zQ}iPC-s7`=^R$%~kI&LLzfu}sG75ZQ#61)S5}3nJ@V?$lzv)T3VISu;8L_i29hZ}MY%(6B z`TfPS4K-eHKklTnznL95|52f|=vcB1{TR2#ym9p2&$kle;&S#Yetn&A-XmY31#?b* zV?XBWmviV8*BlHpbc;rlRoIEU07C|CUGlWSgX=Ph)=aODwr(rrj;a4Sa_-zozo{%9)%~WA&d=VjfH3W7dADVDFSCx}{Qh<=w&!ddgE|)E zG^vZWj|>wNE!7e9M_aFHdqv!i3FY;&Atk+1j(lo6MGE$AHFHr#H09W8-Y?iXYj-5W ziZf8wq%}(6MZUZ4X<^y6YuAz-PbR4f*6BhGdu+A4$Xt6%b(M22*yvqPe#WEUt`L#y zC3NAolCJ3WliEj=y2McQsY9o1GGkI5S$8$v(5qMOiR05@a)qdYz$3|^4$vr0Mb z8=d_^mUc>Tn3f%rRxWDw((fR}e>?iK(ey|P(a;wgxWWF}=65;)6^3;BE;l@O+~aA= zgIhMg_OKm$CpDk*#7I)Tk#t7)!SPIWA2j>-tTyfAPt~f!36@t4J>aJBhc2u%H9FEE=w#3%Ax(d*de@WH{K3Vhc zaP`5d=BZqLf*1@MB8UW7#t;Bs9Y|V-S*Gj5uVw3_p5(%xN8=5#7@RR0hBd@su*PsC9A}8dV*jVP{HV>m=Cw}< zB+zz$dXj1NvrF0VXXXsW#lQnJ1`}~UC%TcY(qixUoU&m@>rQLB*bC9b_ z%51IiQVV~T_%v>+EHao($5{nc=I5k{629HTj(Dqu@1ELkwxKV|>GkifHTVio-2yWu z9En-Uc503ZNRjL`XUP@cA13|iCCebL}C|gEv7cWye9~VmQouITTX2_48FjK zDYw8x`J3FvtjcZtf0WxG(Z|}{)+W0We{*#}sKL;Ls+dz;q;1T}0NEcJs%vZMVnZ>+ zqG6E#%>{wQ0Yr#M2nvgWpwSrWg2UkZh4l0LWwm!&f6t*JHu zJD`oF1p{1Hcr2P)7g*oY0$kb<@E8`bPxWK59&j6!;{ZEUT|f@%F6k|o1N8to9)+T= zm*k)iOa4=R0L~Ybfxd%wSFb@iCDP@V~C9-cws0%!pMB=>Exa=`wop~mW-(U!c}U%*yI7`nEOl8zMHDSs?<+0SX*|!iA8~Wy?T8Q9vjRFv(iF5{P7RNdlQ< zVeSHv0jP*=?d^$V2v!Itgof||nPEvfP2m>>jjZS|)-DPKU0UTL_54BWzsT2f-QC=2 zIYw(-2Mz$P!`8YE2!*A|M1bd323W*_!6#M_h8IMjwBbl$I8GQ2KL*1J!(gJ&Uo=;O zCc1otMAjye?8P;-sD#mIAxnFE$VnoZ0tOtQ4qjzrQS%ogs=y%K%HubVAmG9X#A-)Y zY5rf~m6!UKos* zA1dkUVnZVHfulYE%U&2U^8Krs13z?Wdd2szrdLKw^D85Mekd?K_*3l&ICToKP`frb zaOPVX_42PwvE9i;EBYP4oE#7QU;aQaXfzTH0gr$mVBCfR095b;aa@w&;AkKUYh=Kg zgAn^#861K7lO6^S1E%(C^{@y423jk_Aut$VPQOMEkH)|NVEz{w2>tvj!(;!Xhk*m= z$S-;r6b?WP*4o2h0VL!X85W28Q&}wVim*TF;Zc9`1qXxwA_Jgc41feJ*Wp4ow*_X; z^gBdFp<1?{z;`)#ppkX}17WhuLL0&182TMYj>xLZ(eD7QfnYB^pshl5w05xp5K{~e zx*Ff0LNWgc0lx0H6YR?G7*9jLI5BK0fqt)J%kVvi@+jq0w6jLpcrr{90v^R zg<&{h7;5Pv{r?RN0nVEWswfjZPLs%%6mbn1IRS`_q_(7%f~>5z00h`yy{jlK0FhQu z)DVDZ$*L+!tEr-QtIl;fOTL?MO|J1BB=>fbW(N!#98)lu?PM8W}puL5s2gPZ-Y2%HCH-* zQ2?M@0D{&BaemSK#mFjX1_6A#BM3(TfD(u%p%7FM2fu(O0Y+A7{%T`2FteVO)w+Lo zJN!Qd;w&dFa20@Q3ei&3mW6mjbai!wR_C=4kp6##wEU04Jiv|I&DN4=36b)E{OS~+ zoTjYo%5@EqQP)*dQJ0jVo(O=Of)LX0k*W2;A+SpUuqjI-g$hmr*Yw8gYAZ^ws{;1r>ng43>etm?xf86b`nwy&x`o!%0YuUB;9>DG@#B>40wZK^+ zEa50YycL`Pptxv)pall_hei?zNCMm(gCG(C$dqb#RoTD17cE5ZAG8(dZ~hyfm8Sb| z7+J6AKb5Q(p*6?<3D=r%the(|xYmo%n&bb3YfU)T+xaJ4>qThI@&EO3{hMej0{j8= zBHFbCXhEnDm?o7ERmI$bXitGqe`vHI0d^KRW2qpRH1MMydy+Nuq%H9@k*onAoku=d}> zi`R#Um%tdl|Lee_wSY7LbRqy)7>PoFfHZyqkp9o`Z2;8-Cw+X&Q?$hy|Gz!`@4A%q+k{F>&D2pPfx7#ra;&`II5GP5%<(8=O6 zGti0PGcvJo(9_A_v(PF1dBM!YPA879iO4jMtSlL+wh1rCKMVXlxSwuMm>DifB1w`2yIhc958JR`dm>2}bTEH2*Np%OD$oy_n# z{y~k)KiK4;Q?Rvl!e^%c#|MgkWP{I6Pp9N=XH2K2U~EFCs>y)Qh|lmx0FF)$#`@N< zP-fc(21W>adIox&K*Sip{vZQr_?6Q{fuJ7uSX_h{tIXK6?tX_!#F;3u z0O3gJV5B5qqIm=+Fi{X9{HOsiFm%cI%z+52ois3=Fi~C81lXdO+E`gCrnN5ETyuG< zX=}*w?p8|`=;v?NjU&&>MQKzx>eSOh-HUtj*|G5)h*`e(!Z&*mQkwCRK#Z0!VX-8BCi z!OFmh&&I*1O(*@w-1Yy<(?5RkXRZHBoPx2Vt+Runu_HdqKbcXmb^7BE`2RA%zgYV> zn*SojI&Yu%4K-i2gM5siZy`Yc=w~FdlcHP~Gzb^Xx^m zvxD+K>h^qkq`*dXkMcNlY3>e) z3?yo8)U{yOCV}Q^3voSR^IiXO<=l=0BYlX*?Hz%0f^L;#lq|9z1(|l;@@s)<*!Kj9 zwx{Mw1(P!O|q%_zM(eHL+Z zUBvv#d4fF`wZ**K?((@ZeadopPraVEZZQ1RSfN0nN1#c@#HChZpxL->>mCtXZRUbp z5)D;xdO1dqw0-DU&$4_!^Mw7jIkD1`ADP2D6Dn zZ*XV2NvHTaq|x}owf-OmSU&&e;G-8tZ77^*u zc%=AA2v)teF?5MGb>p#;5Ci$cXQHIW5$<4j7kC$QeUDQSTiozx;uRVU(E3`_&U=V zI&zFZWp`spf7EgW?kzm7G)`ePX@)l1to%j;G3zaJPXG+4U5m|$@tqiXHOj5K2wNv| zrfUpRDk#;Jspk20ffob!Lt@;z3t}$mS8@I)5So|+wcsDhTS+_7+QU!E53)S#+7iS<9zQ&z1dkoBRWl^uN0~c7V!%h^u3+o>UTrm zd`obw3LZ7{wlAFZp>bZRP@>Bun`!PgC)v$1?~3bEF(&e49%D5h`YqD4J`|NWejo~3 z+<Y3@SByuzh)z0tG%n~N_o z)HeNBcJUcT4;RxlD?otl#gZEWfLR?lkRO~s-|t8*v#rmCYhLCrrY@Y$jqRQp7q*)O zopBuY$Au(|eW5m_tVFt--*PA;JaiF+YcHWSysK^1yDSJJQVpr$((s?O3(3{bI;fSz zc)t*mejzM&^_B{DUqE>e^96Wlz`%d*VG_K>VD^>v)G~DJmq;4JDciwrox4~-9bTMt za4KYGqqyl_!~whi7A3=+p!xaAm@gE)oXvgiQnoVRE?w!d{*R}nnvgMs3HGMKHP z@*5~+C(_Yw48lEgsV$9qyx~@?gf-C2E&$vfM9dzD%pSZO9NjJq@-~MSJ<%TOahIgq zR8&Yyn8GbC@5d~Q`6nucCn;!<5T7t#IoXS@B&PS{I!rgYL4pww05q z66$EVzIVnqG1AS~*gU7xff}sGuD1k;#e3N=oFm|oc_wx{@Uajg#LYRGceNAH_$UOI()c&adCE9f~%R79-jdW zFZ+%&{vepX7m5+PY#F)9?y32-FibYqQ~L^j@x=Ik5%$0N5%Azsg=(OuZ*+)6S1~F< zLn}i=Ej=kQO;<)eE;U0=Au~zk5@L_SoWOz#Lqj9w%wNQj)(8@x0;V^w)yF`>N1;s6 zTHi{{(xTSKM90xnhTlV8N6aE3+s8mA253+sC~skJA^$@T;amY<&&bMDZB(v*wXe6L z)@|T<1jdS=j)9(mp6Qgqh)GJ1`RHI{-8;;FC|2bd)qqa}{?pv}y*uzo?Ba%UIq}k0 zt4Hh`zyJZ6xWDvP$m!uFU@FQ}fcW7>mQN*e+nLYSeDB(3jmXq~`e1wO;pBRZB=S{6 z^z~ijii5zl)ADBfV4mz*^ljo{$jkKeE7bnxg>RDYvHJ$a+tgW1Xlwjup_t5P^U^zR zo2#o$PwL5gcelyId-l$Z)%$d7y8X=YP5pcK%FSC%ZTjm-A0B3bwPdEMX%);?{vsBJ z@!tC9#(mlY=)}z?6Fl~MSE;G?j8_as^Yq7g|NeZC@cUT##wRV^=X8g~L3+my=sqcz z<43qN$|uf;_D#$7iyr>sJ6wrBch^oIKLpA*4t0zAWyh4OI({SIT#vEu1^_`8Sop-9 z^5nZY=Y{JfO!J8rd~453_{S+aC$EOshkXZZK2v5qeJ8s_`4!qv-DgKV=I?d5+2yBfmjFg}LI&;K! zfKa$gt$&M);RP&HW^@Y^)lahN0}2&+xN9}Os~^Y;O1v`{wND3p6ef>UL@;chMmNxt z8txROb{su!l;I^6V=~r9A@siHT@V-zHWi*S?pG^pN1@*=Mb5puV|A^Ylla&&cZv0O zcRB*GG@N&FsVzuuOuO34tc&)b+_XAR0Gqx_i)<6l81ODNT-MtrOX5|pbHSQjm2-cj-HmH5sf9_@j~mrJy-k*47OD+% z=8m`5*&@3rd~)$&ZiMlMxZ4-VM3|M>3t>3z)}wP}rPe{B@>IxlA-zy}rL8{M(5|L@ ztKT)cLcjI~;)}Of9$qilhY_4)kS#Y(hV2Unzb#_9W-+w3Zqf<_lR79@>}{w)cH_V( z*-d6fOb$HY13mHA|zv$Wyd9d2m0 z9CSxM!)1GfGAFf0AL3h3XAx!2T%uRoHGSt_H?wq3e?RdM*;RS5z;ldJ46a3SYmcp; zm5!sm2yzg+QAK?n>2+5*NKHh!kJ$HS?7$TY(D10!a<&Jthj^x_lC-9sF4XM*5}N4! z&G_C4uig}fXB!e|(NP6Dhw>sJu}mPC2{{&$I59ADlA24qhuKssaCYhCz#2@7ixSB|dZ(=6&+2yy_T{g@58@Lto zF$0-4D=IL&`qR}8{npQHqXUXzn!&1)amPweneX`G#2`_qsp4TMb#9kB3Q<)5Lx(*) zb6oAMPQ$L}bd=pWlQIoHv{aEd== zQ$(U``A-WaRgJTGoU*O*^Po}#pX&Osn0mn`?oTQPNSSw+AV9BM`6u6BpJ~sgCaMpA zU-g?;iOO?3jMp8v`7gkf<$Ae6YG$6nHM1`8#_}$KHMbzMu=kvh8loP7(2~g%+`wmWaa+^o>6-k?=lfdNp|VV%{0%ijt^2e&9HSOS7|d}nifNt}@|v8gHmBLCa!rDl9_~l1Boage znRz58Njef_kXYDFOPS@x9gMU~)VaO9&mZHU5ilLQq~PZKTqS}}SaSs~5n+GSvYU^} z!79dhaNu=r0-Y3z3z_-E_v|!`7_@1xI6J59K;`qJvOQ?p_M*5}8V=zeuLsaRXL>w! zW%yF?1-;MWR;fs-Ti_w#CvKY38U{b)nd$V4bB>QyA(Y3{moJf0;=-W8>wAGYDdNN+ z@jx6V(F9jvvy?!eh9sHaYj1l;0g9CrESQ%REJ4f7(FfT97X|}vhf*$Khf-STNG?U5 zVknhwvnxsH9LQzEXbUL+PH~bBO|~-aS+!+dQtaZZ9eUW4yRvW@KzrBj+UB{*jm7#MQd5_- z%hn~RV)gUQP3v20>Ebh{OW(M`c^)E| z!LO(^36!kLL*m`Kb*zkv8hk=XkjJ__)8IK{>{$Bt9A^uc%6t)}O4Z5UeTU^^At&|9 zo!{2W^G#TaSb2_IE@vZnJzmq%9bLgMjMq`y3)7&=4{Oqdf8bIcTRL0aGrJPnl1?GZ zV_qmiu`4`z+?7#D`zc9Q1(Z5(3+5el& z-zt7acEJu{NEO=>k}=*$v`6=hjlp9MgFuuB9N8C9365MJJphsqTt6z>suq?j&k{#l zbNE+sUl3d~Of7vsJj(!9gg`j@D>MueT}~ZIN2~ku{e42Jm(D$I$->DQJq&QHWd#+U zlD6mkmB-?R#aXMOijH&FvW?~WbTK;f{Ue9#aLpLm@y7v^g-@M^oeDbz<=h#=q@0@9 znZCNax`@iS?eXEWFS+`pT{DH)#KO^uR*nsPy{;LQ=^g&iHs2>JGE54b&wG!T%*c0rUeyXEMxFB6>n(GIdZguJ05k9s| z<}Vyh4>HrPQ7*1DJyi%^{90zSq;o^(BCC`tW{lkswGbn94>tgX$KQ)ogEICGjKt#< z_$7nNpw!S8AFN}gpvGur^ZF)uzwoYLJjcH57Wv>;Qc_)z$-A*Q*g?*Ra$Dsk%Tt>k zNRxHhu;+HTH4imoX_i5-s%y|~4sG&o9#q3zx}axJf~XF`>IHP{i}#xA<8p)?&u-zH zKsu*ye=p@pERDdi^(4G&a*eyX)f!f=@ng*#YNjjVE1tH^JttM!Flkclb+xnUuE!bA zJV??2iNaPCYm}RdKSoxGhrv+Q&Yr=|qTIiJ@=i;(!Vuw=s2(r~%2@rx+nWT!Q<9UD zxOKGSU}a>DCM-NiL_#>gw$aD_Ttj}dmSQpj6Pb%-2j1z^wD!9}x`dmJ;)7TJ+F~9O z&gUi?A|@UhPKtjKp0}lc*X-gkie2H=^RJFMBMvSjet0G4DkY>WY9*|Spy)UIpy~wx z_=ZmTJ461r3z5IUYX5bFWuj+b{%mixqY;O26T?|G8LaXQ=cdUNK)lP`DX1qzfZ1u{r*Z~Zil?WG5^i-MC% zH(NQ0O6GfD&#AbcA33A&HyI&rW$^w`i3rQ{6=m&t>TdbcNp4tct!S+jIZUC_nh270 zKC$GNZX~GSBZLYRXpb7H8V>32Ps+aKqX2Z!031R6X#UoFi9U}#rip@n9d&0T_W*qP zRU!l{VKWda5W+q{fhIkEdV*GxO%f@jZFQ9vD>L`S`?jNR4TeC8+Qe0o50OsjZ}mov zMu9~F4^?I{EBtLzMo21#W{0iu_FB_^n-Fi@FC{k%H{({W4~1I|VlNCxF@a43T8>c3 z7joAN9nzmJs+Zd$SBzK9S0qtpNkawFM!#Z4-sm7c0;SKLLPlslKGUXDrCPgR~5dv;y{a#`ZBc(?M? zC~Yo-ruoUtt4_d-C4CRxP~GcwEzuL*E+ z&b|02CHu(L11!tM<0rEv*iBh`RgKKA4>vbyc+Ny4JnrlpH3^ID>AHRxX8n1px&SFfDD0BNbvZDd-@ zatq_o&Fm6Ki6-Qx>evZ3t5n5Nu%nhzQdK1&>RrC>+TLwUt#B7ckpqP(8I$3w#i7^{9Sox>p`gtP6t%Q(7d4?M#-m$9Q?y%%+ zn4?|klo8jC!}VQAN(z(8+gloESL~If>tL5m*Z7c0F+GL4gh=RFAw|p=w#t0zk6&MY zYW$j5+hiS^%Q8wcGED1MgR7-idYn!o%RrpE>KdbUn8ET1KS?qhutB3X_gJ0g`0niw z%Xtk$w%elGf0XAx&h zOh~Boh~SBzW(hWBEc@IufiAQ6+~gXXyzgAKNz1}s$Srj^UX7W;BWEIZhJKt7XZ_AF zb=eJVCix`r^q%%ux)~=Bts2PeWqjd#>%REDKJTBRiQGrqk`3HLg>vR7HGR@57W^q89f8JDSl2er(>P*m zzqXp9lDH!KO4a2-cCc89xYpCBF>h*z9x9B_bX24;x9%oEnn=M>!y$#odyUC|gflBM ztw2=5tBG#dacc9Rj;b=PyeyN{UfJ$ZF7aNwGxPg%;90^02~l)AX?z#LQOim9)UG}TPV$D{dB`oiW7Nq)dD+Xu zkUnOT1$AAJSYx6~Tx{Bh?4o^<+xZ$-wLjTDS-4n5v9M%E#vEtTxLByY9KIYLXTf{4 z+}m)p+))*JKTh!gn=7Yi_g8E58hHBYW#RnFX<_;EHn+;UCQ|DkY!VdpMo@r64IISn zwhi?=h8p;E*OxRmlnG-sum`}T)~z<`;|KV-n878*2g2xx9^e=0miakmfd?fwCzs($`6T%q?#SLDlp9zT`jazj6Q{aqW}9aiCgqF$ zsT_McNp=Wzx4*7XfQ1)nUh2DA?q%5jrD+=@7$@%-p=47wV>|<{qw`@E2HA zono;!?9c4WA>NB*rW@p)eU=TScUfWtND77s@5e}R&*Ox(14j{P{45}FF-0vsORceCmjIgRz7k+!h&Qg_TdJIE(^Sh`I|{yYJ8|G?4C$?(7_ck5 zSbQY@Xy2D1)Glc_;nv=`5#$&u*v|W~$5V@J4>|?;q#)aNhVoMPp}B#4z1()DT%Ev` zy?iYou_4C>DaSgiKsS9}vt6hz1Fx|8H+T;(zd0R{b=*M_ukksdgo5D7sA7)l+aNiG z-6B6(e1Q^YnJV03yE#`mSEt^#?_HsKYEGmkd`*1J`=Jj(0=; z4dt<`iY3&#JCJF19V)g@l4OTmCNS^~yMC(*V~c)B&jD(_uJ$J0UfAQ8{DM_-EsrjI za*j2vP&%`m%i|3uvR?j{(N0-X(fx+v{Lxv|mPTRYlJhu^Q&X}`+FX96wF@^?DI6c7 zDT%;4s;2b*{8Qq76k4BqUY; z!*_rC)pvl8zKB)^p}_d@Q0ir5L4nnKJ93)acdbE24(6TA+uAkc#@4U$Uy|3y(u~OS zN;ezK4qMMdc1UA~Yz6!+Yv!gna1FLWn5=J3aKq)v>G8!rk+#1BK-SzEnx@z7n!lKN zm`alKsuEUXaPkI|k%V{+pJCQ-H26~^HRvtC z>Xb0ofB7+>prKAkZlcC0MAk@bq`4jo6T#?uirS}Gl3S8<*)pplVxvS@OtMHfp<7fp zSvPf`+b-v{E9^0;K~@TErwKZ;d%^hBnvb!1arnZyce_+o^^8ioMc&gH&9B`%I5TKg z@v7tJ$3z)ZlVeym9VL~!bpNjCry=78o%(UNXnEes%#aw5R=6g46Ke@y)bOKlh^fg} zRNszsJL7ta%xIhUlbq+&)}=!|wxjKxk;U7~&JbL2vy4mCg>^Z8yQWi|oNEGXJKv_2 zlop;I7&viS-Q!u?c^H5zZZEsr_;>3~pqMb$(mNwkdQlX{5$)R%`->ZECd>5JyZ?S{US#+q z_S;AcI|ArQ-2!{q?nzy)T`g4f^sXO&Vb8uD)%b-pJMn9Wo%1biGj6D6%6vO*(Z><{ zDj*-=24Dmr0i1GQHU{<%K!|YMf$k8YArQf)?`DV)uiFpc568YqaCW*~GWy?BC!-66 zU$fC{?&wmYY&yKJD<3TP-yj$O?Ud)9e|I+ctJLFvJmfO6v;7B%WVWp&KVcI{kI?x} z^_*4#9!Mb_M_m+Ah7`)HG{1?{AXj8SEd=-B-Nh=s1pMsL$2P{D+Xm!;@5`ZT}#3$?V6|-mx)x3tQQr z+#zxxs1Ot^`+n?cyD9hy1&a{BRX>D>bvEv-7SI-jdQ42WJs+Np=}c$F)e#c91MI*8 z=qdeT=ffSixBw}d$X6QnuU#+*)mYen8MfH4u5`G+I+G5C1Cdmt z9z8hCHj?q79BEO&HkY+%$Kt(fR8?##^L(r2%uJ7`NAFf6iV_Cl64?DDEen_VRDGao4*k1<(ewAbqxf9?>4RtmBwx;@+(Fl zZnSaC;}O(V{9uWn&Cy?S!i$?P9ME8|%B-dX7HIyPv{F`(cTNEgI}7xR9sCeGBq9gc z95>*E*ex+Xpn=^xKM3jQ5Bio2cu;NV6v0PRyMWHQ1SGn$jQnV@CL}81Ma?)0!%z^2 zVP~Pyol-ZzKjtqcLxIJpbeKfn7ll(yjvbIV2N4C)sGgivSU~RE?0R*yc0RTL^bw?` z5_U8(WKl;wqwqG(QD%zr!ZiS#jbm#qA@cwP0PWZTvzW2!y1s7L>l zr3@j|vVuB~=@QPsZa2#F7Ab+A!EELj9lfU|x2DJV7|)dSMb<$&hfZE?O;VWfvi%8^QY6K&p2S+s?Yjj2dSh}W6a{5;xCvm zQMM*~FobmZx#^B4%4p0}f|UnGD$&JO@l8 z-;62mKur<;?Y%?$BvNXQlUJ(YH+D2VIX&I|Vp5vE9{^b>{$qkTwTRkr9I+vX%A=y; ztWOZyBxpwyTT4rJrd1NLLa`FePB}sZ6J$Yu;2>XfuxP8h_Rqpvxg^s-ZSGxHi z>|7nUEbBH&=G#PT4OznwPc>djWyW#CQhUyQbtDgSnYfwSIkpvoqVT^70&W z1(qdmoM?mFZB`HXwaDFlOzsfV&7X(&934|+Po-Q5^I`rf%a)-oNFVzGi4 zcWNhn`rt==9rE>bWE$eCfXw&OB79Wv9c_^j<^gZjB0luHd_K+&joQ?frlO)#rMVj# z$q1-tO*dfJ<$lbeVCx-?QuYOeo12oQM&~^o*|@LJ_5|iH`%}QOzx5V1G1mBG=ECmu z;AJHT-@e)@mezU05??$1N4w?9~`$w1!rx9GY- zhyHec0}~rMvrf5vN&ouh;+-80D1xd2A$rtz<)I%KA5|%-KEJi$3ocC(Lp_t2uBz9a zL-9aT+}t2Qry!VXHzZ?jPBO_lCoT{`3od}%aXuvoiAmTArzUP_&$I*KnHRAIP}ar7 zs%=|>`T{xw8!;NoYv5OoK{DVzPXC(eqxzv#6nke+o~|Mu&Jp(B8N3u3N7zUZ`w})N z6#>XtiUE*fu#FKl&$%&Vkn(F;*$fGQ=Eq7w1Y16)%g96{rt0x!-k%SE&zHxXoM$*QVYd!OI z8||+zAASS6sb93vPPK~ghPCim;+&)PjU%(r*iaj2Mtrqp8U`SIoy|*cE@l8}6nLDg z7wmZ23P~#fh(_Go3Lv%@6C3k6rD(bU^L?_IJPGJ!%8? z2Q&K++2NYZT0B9lfn@fk{a$JBwS<5HyVc-|(C!dyX$IzA z^_<*4(J+{LF|~DcVZ>BF`~5B5-8njChS=NUv{9(AK+*Q*X6FExnn%sPZr)bzHTHjf zcg?-q&C=^QZ(m|II*Fb{)1Wo~^r|y0& zD8&)4eps0L7;yFX`$GD2*=S<@{&z9&zpwZG7lzEv!u0Pc%w>wSEeZon$jt+l14vQy zzSkN@GZH#nvYcu!4kYm+A;F(4QDAsA+7)BQvP8`Hly-~O6prmG>h5RRQwj{4`OE8g*UQ|Ih$00q5@w#YmQkti+MxLDLxbnEJYLx$Fj29-eFc+lj->Ol zB(pLaT*M;RAWfj7YJm1280b>px%blyDvGYoAGNX)6Tx%~I=S(nX;eTp%lypZY#zD} zy-&%G$eR(T{1JS9J#sG26Q0NgI)6cQ@BMCw7PGwBe?MtpRLydJdu0vMaQY|B8-g%|#W1gGfonw6 zKmZBUeQGEL#S(cW2I74(T3nrnfg2F&NiJSj^|TPE3?`brfEe&N<+IDbM9B-ls)k}H z2&blx71wEqV~z9I33Pb}>$1m^$&k*9N97y$OVf5HFJ0qux|4P*R-qrcy3hm~a<}o< zp3`(emU%Us(e~v@CVM3HNU-}00B@4 zbpX&}sEGp@zAof4j}nG(8T&e$muTK%!KAVMNSdEFP%m(bi(KNHiR0}(gj@1MW!AS4 z2cHg}bt@jjx|+SO;HLJFhf&rt1uH!G2O=rDVKCfhSPt;Ltv2uVw$!(a^D54#52DOc z-X4TDFYYw2&9mv62Pe`mrgyXRU+&DbnzocazO}Epq|f?P3y%%UHKY^W-5XO_(boIp z&(Mf9fKThsnW;WtaHQ8k*~>&elQ;CglFkF=qC5celV>X$H-tXEKY10-fP9o8eL*n* zigRdx{GDt5uPPMTnf|kDstD8^lEZARP^h(_1V#5fGaSR{W-Q$dZTc?EkSZn2^Yv0| zkQ9I9^dW3;D~v4mCx?%}RMO6>Bgw6b+G=vU{{W&Ff>TAVI-s8~EMEfSCpHMu>W2iz zgLj0X`ZvDQcIp%5y+G9POLd^ykFGF@hp#vEKCWSI#R|`edS3+A8x9|2`EHT4_bwY- z1J&iq*et>9T;YYRe%P%!U(!+805?%C_e|OTLmr^gV=}>qL9o6#IX(o(>~wtYz!(nR zu;#$$cmd`9rlD4o zmS=#>gch#J_~ENqG_PC}y$k|S-p4^{n@~KSOI#o1H4Li4rT6E|Hxd%l86orI=aGE1 z@rGL|5~Mj~_*l0t+a9&w>MgqLAIvR1ioeUPeLKSmX_W4z3|uqjfZ{Rb0&BdF(pil_ zXO>@ngnI3tmHr&;3s&Lh$>+2OhB-Ak#ZtV)9 zGo7?;W}y{^mp`e1!h}^0aM;z3Auk7J9_2kmLx&`wAL@6L4MUpFqoshu8FZgmlv->> zXMTn0VmpM-(TRb}-gh|c3PSJ$*_)0$7bxl^7CqUB$?`1p)@ zeaCs=dyS#+{ez?w@=@Raog4nIN;x^`>HpIXsVoqKO#st5L&Yv;0W!R5Hw9ntfloz} zIz{l^Q)DG%>uRZsAVL&PjumB}yd{RCJ|UHSUq;i_1BU??|D!Py%cO)84-A!QG;5;w zgZ^=!A;=u-trM&0EuQyKKMzM}(XjTK=;wnzhEGuCCT#T$hT$8cK^xZjDwoL%QPbR( z++wWU7-;hAqTC6iQh$7gX?%zNZJS}EEbRIlqg-Y#W{??H3Ha4KUPygl^R+TMu#o$Ug%vLpYT5@W;a>)V53}1TNs7!*V=1(E(p3~>dWAL9HpUWt{#_z?Mx$5ZG z3;RvYpWKtO?VnP|zv@4$GkQ|4T2jf!6&B=KT#ZU`r{tCZl|o9`E>Xo(kiR4!OO4_I zsR?$kqc`sKvW~bB%1s5eW7hc#h(&7-<1ss7(^z6yg&8*m<$AesQm@GB9*k%EK^6Fw zAXV_0YlFu8Td+mXK*(|UA27-Z?V2L*rlEx8xjPm8Es|t$n3C)36=>HKppC#sGZA0cyN0yee1~=xg{#cn*1brNNLZDO3wvtTt6gs( z(npqO3o8lY?(=A{RQ1hOv}tI?n#bdwqtcrY{s&5T+QuFSDD z&ZTIuN_wZa%>@R+^p`b`1G^S9d~Nr^^z|#rhMhcCB<#Xp)x}Jpv^jt0$^WbBVh)yn z`_rO$xK#?z{2@C;$iodvOv^a!9Z}qTTi^1g9$7Zz@z+?l-$qK$b(*1&95$#z4{T3-PXNb6>mFm%`F`j!36b ztob<)IwM_q@?xXn$t^y7M~Vkn7pa--^|rVV;Ad9kP!e@(9BJM>du!(=*;C^&;$-jp z2qDP~fA@j4arqN?BV%#ckT{PF_#pp56My25UitC)Ks9Ru^WyP@il5B}C+iYK=1Fnv z+3Q(U9xWYZ82QxZLT3JnSE3vlL5+9~lKYLQkg>h~#`rC-{8UE-=097Lx}II;tBFjp z^?pai&?TdP1Q1r-A=y8GkOEK?gC0^pmX&^1@$+<2SEv=_k=HbMby)L#y}rF%+=yyw zajh@PO^^0)^5UVN&Zw*)cG%xNs}hy*U(*p~oB%MQQu5Km*}cIQz4o&HUZO{8+aWJ| zOQ0lKKc#YM1kAF4lV0RiLAeUoDw&pbBXIv2np(b$CX8rC2|qtb;4Si!he?M_JVsu+Y)dYBl77C~4=*DB+wABh;F2WhpRXnO}Jr@w5W<_TmUA&4K3eoRph-LJnLZ=;Ngpc^~34!H30{ zTdF+9Q8C}}zKKOuKk05r{T*F9@I;N<`WzK1cJ%qub;YXGy%(nSz&~7x z8n-{hmeUHQW0h5KX8o-E%q~YK(AXaDsyG}letT`*$~th-yy$-2W%VRwb_7l zqJ#uwPVBjCE>>sg1E%k}2LKp=b)De*?;^nes7)zM3P zi8?to%ue73D-%tm>|YdaDPn&W*Brl9s**e%CsY7uty|Hp^gTi6eUf#<_xd){;;5?Fy_w2y%}Sa48ta15w`s!V!iaig0)ecc{u0e~u$g6TnRqM_3FI(Hi%f zVER)<68AD~UE)fM#=F@`_bh%MUZiN0Mng>bl<@7P8ad&FxOWsUu0PQ%j}2q6%ezZ} z?~A^}#nE4@_4R%3S2(!kdWeWcIj^E4$gc*Lv!o&!3QL})%llRB#s{~BS5^@+u;twg zO0fj(!}Vi#C@wFbXO@a?ITD%djQaDUoZ%|T(w0S08YNeRHmH)l zy1yB7;=5r!3GE=Zpg#zIgUCtxqk_KL8WWoF)A6A7p;T=VUO@lK1Vy=5~jsE!f36qG5X zl0u+PYT9D~{qp!SxQVsCu_dw)#rq3W$v|k_EE0xq)~EiQ(7n#0#|8av-?F@ovcf{9 z?z}Y=5Ek>?=qZp{l#FQ_k}&r=lC)gd*9hO=RAW9FQenpwY925Q1ulAw&hUOBQ%V(D|)^6gfL7v*7~>H!Q3shuNXyER;+k(wDnlqQ?+! z>Bu>0wM}$Gv^-Zd+^|1GRA%#T39n@m!PQ(Emqb*oFp_=5LH}C0xUDYzDJ+=XEJQb1 zFtowCEj*)9FQP-pRvIL0WVNJ+bE<$jO#`Jy7URAWj;dMldc^RW@rUUCxLOGI#FvX* zpz$ZkkZ8)}G0m?bp`n6-FKRFixfQHbi|O&&;Rf4N9l~cNBilte7PfW1FQlE(U%#m1 zC0oAY+@-9%3KF-9Y*-Ix-eRG=yCza4xSUwC>0PpgCmU<)ipQ5J@Kyds5Dp4!g$V}-rCh;j2Hxm-!d~W^j=yq4VgB^T zz7BDy#X`-q7H!+5d@YfhT?6022Hug_bFD>`iX_u2Y_y}bhNy&S@@JSsv34pyWY0p` zEpS^;E=K;&U&8oNsO*<*0SafTN0bY0s)I@vNkkM3GK@+}s2Vm#XgDdauK06`A~YiX z(G5v5N*1a&`z5*>c~Mxi{+gL~uZ3UW&3E%+2gHgsI|pMhA+@wn^}9oQOJ6z<8rtPG zbLIf)mgGC2QCh@uDoOpqj3YGnJ+-s(4$(vF7Mdz5S2C0g{T6hjh(n}glCn7(fG&ym zt*X)`Sfgf2xjUd|Uf#VVrK-C8l$vaus$P<1F}*nE>DTQqhl`1TQx)C`ZbD*#FXgWc zrQ77#=68(#KjD}&e5Y~aaVtr{R+V-47Rd_nr|$)sLGH@Q%hMaMTMQOW$>`p`()W{G zEgJ%a+h-8SM}Y28ZbCoh;sl3b3OQO+#@l1i_NDzgMaP11!)|-0elTh1W>)BvSHAA@ zp^{n+)wUNPU%AJ7K!B1KyVV=TU4t&^|6%Q`qas0d}CdjwAGt{SXAAW<%Vb!+1`!q*dr;y#-c6YPURyM=&Oqf@yn z?ZTlySUB=<9@us~6Ng%eYu4%t3djTFzvf(`NdCj?qM7^S$lL9bxRnAl=}5@!^4;a0 z4OVw`G$Js?m``mTNwtMp?f0Qb`~;8e4MpJxnjS!yJ6H98db|FIW4)lclcSumgOIJY zovqDZU=$pGp|sdKDB9^88q*6KyO|^QU0)Qy+i6vtUF!~AjEFY@? zApkryG&Bq}JPZsxG8`-%GCC4GJQ6we|@TV?+tN@@vKmY(fz@1XO1li{iYdbnTjOrX^8priE#(P=G zQ8`=8)xUcywo|rzmq7UeU{}PwN_ZRX=&rrvoEaz3^j#_V0C2SDz8(;sj}CGDBX^oz0}Ej zO1AKk*>~zfgHv)4w-U$Z3i`>}$;Hp&%?btQMnTi$9en^a@MhP1@{K#$Nn7809;&md z5dn=OT>jtD{)4{ZolLROu3-)h0pE$>n5Dd?I<-!GKm{?vgpi$_o#5w3&cRQP(wM(0 zI{>&Dc)zpHWv^YJVUtGZx=N=N6gcu?@7CgFI8&R&J^Xwer4Ok%BdC8VH))h6)IH=5XASQFy-K2o9?R#r51O>#qUVz*scmK zDtbHxPqt=!E~S|4RQ|R@^>r^R9KJLBWXPm)!F#w`8Z74SWPrtM35LvVXVedXIqgfx zL!Ne2k~rkEt{fNW5%5BJG>Idv4S~LZUWsnp28c=zX7xVD^26x~GyI%q!L zK`Up8Wx8>p1GM&~&`Ch4KGUqP zg;wqT&|QgVilOOsS?e%5JZpDzhOvPGNrymE=>qBZ`$9I1dQ^(A!s&oDggp5?<`9SD zu~L=1;#q|6D)P0WK|M!}@~+)-4ZJ;{fLE*fdv(o8&uiLSi;uGEY0M4QCeU?HD^HFZ z!opi)ila+dX%~IZ6X~vp7NzUVtkMatowpw$^;swF%+>I7OrLziei$kA=k0_?%o$k9d zA<%VS*{98yG3w3LM`RQoG*`)$vl`JngvB2zRp&E4YDnd)YRsP~@K?`w8vcJ&B8jff zwP4Q1jzNnJ>D;W1Em1UyL8(8vW?`3Y-`DV(*75_8+VEU*O{eJ+{sG`;PV;@$^rg~F z_GP>DdyR;+{~_^a5%W0ouDnXtx>?Eb9->(5cispUlO1I3t@;6oAHG|EQkJdO9XIsV z-i&hy;x9!9ZNDt%{JKP*?_9Qra4>9aU+*W(8`T%^5IqkSE&(Zp9)8KnY56RwW*yk{`eUod9sHZs- z(tZ*=r(&A_XY$i~vt(3Zzsq_8J@ivVR76xj)YMgx?x5J&9BFp&Em(aM^AQ=kn6Y@% zWKQzE!+nz6YJT8`0i$pAN4p)ho*^5UT(GfJy`QZ0D38CD{KZTWe&;1ir;;$hXd%L6@dZo+!8NU&uEKTAM)GqsN}VR6dRx zyc8u@WokiyvxU2_miM|N(l#GG@NXvS-WydriZ2PbHs-E*H+R|?l>B61-L;_>WSD$t zUn6X3o>@2NKWR{M(e{)VJ2>!t_LZuT?Tzhw*=xkluc+|v9b&JBzSNf~U<)x%QZ(a_uIYKcVuHVS#C!m-Hb=Wa+#gi=_P^IW0)3Pl?)YfdU8uhI zufD3vwdGWQ0A{ZK?j{r^7`d;)Nx(PFg4th{aaDnxuaB>oKlp{skt~LR~by zTzky~-Nw{Ohq4l_$uk7&g z<1T^UIZnJg#X$L+FK22N&w0+Q7J_s1^Rl(SJWVd@W1)0N`AX zhD|g1Of8t(^8&L%4m$3}z1`kp!anB%{x;CRc$Ey-_8u?FksOAMNx%GK0dI-5fde4? zS|j%}^zT7PPLut-;BS@JW<~Jq^iZpto+N$eMb|GQejk@;+=W)8SAMis+kR_ax8>E7y1J!OxC`uBaWAsnNxr=6S zapG*gq=Axz{8#?JND2=PH;~`YsXcJqY;h;8I=za@D^gW@s*&ygf~b_c}9Y zzJ{+Ubi5~3`9NhyDS{0FxlhuPijwBi|FHd8LE&QCB!U^8V(0duk>iI{T*(5^G>R4A!QT}|bF01!2Nw3EqYC8mbrpT}1Cc(S+O zPU0Bz4trTX<;7B*WJp+IvNv3$*qk%z+UUJ+x&5Y9Rme@Xk@q%qw3)V7HOixrC;gPBAQ63;j2{ZezrhPW>E6TK-RW8A3QEE19w5-%{$J0vs&5mdV?`B0R-FEb6 z;Er!(F89e0z2!GT*N4wV?L?%_y_(PEg^20GIK~@!*9QR8*NG<4sZ+x@y{L+pk>Zki zl`-tBQZ;O^3K$7xtX~Nt@!u`|tKvLHGfJC6GHoMcG{$ z*R^ZFPET}W^ne0be$!oK z!yKV91r$u<_`z4*X>6jq_5%=AarNBPMW3q&e>i)iS>**6)-F`>pHrjH>{c48T zGQT?WTuialU`3{MfTDKDB4L<*=_nJ?<(NLxHo$^-$RA4-1}U%=*ut(z`a`CyrNQK_ z-;}#Vw#&rZ=!8eC*Fc?VC{9gvJ^&f(aHQqoL6-%3)*`Dy5gUt+P=in>x{I2;4Nmg&=XtGDY~4K) z)pRp)m9gJdcC&|K@wQ;LP*E~tS-HqJdI7DTjW{=dv}84&hv!$_n>*HHBCURx;H2mB zOb&Qbac7!!Ya7P|H{)!Ooy5G@o?=$r|2TgUt9s#fEnLs8DZ1a(J(l?Z2(308U0%YC zb>}u3dwc(Kac#Tk9wY4N%Byk9;W>4Db)FOAt<_ii1<`Eqp3KRuvo))t3-qLIDe_kK z5l$hE+yQh;0`3cV)QsZA|7r)Oby zZe_okTM+sMchEc_k>pSD3Cnlir|UE6Qlif;>rW1NhmMh3MI#h*O^uh|>3HjQyMgVM zo{ZX361-CiQ!bkTmq43 zy6L0m;aNvKkEAq2T)gW(tUj@_lN6cr{vg*^!#CsS-y$4@syDQ^4!Du4ToI*Ca8~w? z!mXmQ%P}Yo-L<#lpAz+6;Bpa@nyOpW6>r-+g6j>bo50}=ncxFPo<--~^&?T>3>)^a zMbcQ(QY@XS0l}`nvZ|%|Dt`cmRM=&Q$Ln|2DqYkai;9PI8{FIyxOk+Q=}%i8`x{W{ zsYlJ(wP?O^EhvKw=GQtwtrXl5gc$lp!qu(oH_g8_H&}KjL|8U;#H0oBUFlm#kDY2} z@8Z)Cg;5NK=Ij6~)@QGLYVU~I_%NY)n_J`AFNhm%lzD5sHBR}oPfxPsm#2K?2A9l{ z_$JlfhHZ94e}Op#mEHB6Y%b#Pmh2vQ8;q={%vrtg&{}rMDpW{`-x0oy6Z!2G-66r> z>Q>+jomRQFJv+cayJ=6s*1 zo#&+qmPO^M@>I2{=VKMI#q5G(XHjF8NOo1rss?hgVuE|%aidMlq{0bSeoT$*$a7Mf zj*jC+Q$O;!BZ6&+$2eVARN45moGV?(i4FatC2u1i%_)J+4|>aWEY|u%g%>z!sZ;%7 zOPud`ZJSrNnqs;qg=#=q+0kj6xwR>bK>E_8MyHx8WJ7Hp#%s}Y&I65tJD28|Qa%kp zT_L9aSl2Y4xchxY78quM?mO*s5|1KlYf?uk%dXK`-F8cOx(1wD-w}y`U*G6p!Kq<) zH84g+pyuRoWyC}gEW%so(AtU<*S8M!pvkB6_Oe2^flI!iJZHtpAUG~Th2Qz^(zkO$ zxNh}<-`7~NMt;$;y++0sn3?|k<6B;SNieR~iNL~y4MWSu<-Q<0kn7JBFcU1q5|Xk8 z>TGwt%5EYjPEymw4ln7gF4{QuHXVJcXc=J#XXo7rX%9!*JW=%wwF6`|iQ}FqfZ-(Vc+g~TyiEhLq_4v)=KCXx3 zy`lPOFB7fwilNCTYfy~rbU$Cc#W`dqufeV3YvaH5zZ{n9a*{;mMKcQ&<`tQYz9@NZ zoOi#vIn=B3eY;e3s%v|APh6moBuOijWI!M+YIo{<4*Y{6UlKRZP!gIHX;D!HCI&d)r&Phg$rdNlRy2b|4ZE#=)v=`zyt8CRf$^#?bAJ z7q{+N^6EmrO;f32{)YY-_PzqDg(SH^T=6Vry*9x*8;W3WHun;pOk1)5Q&Oq(tvJ6w z%c?a3dU6sGB5YREg1?S;$46^hg?D_g{P0<z2 za5kIAG_Dk1A=YSC-_aUh8|Ex_K`E*JeEL;)hM2T;F+gfQbdSe^3j0<( z;6-aSS*)j0wIfwRT3}pK_ps;R+2FU*_13R4$l-_pRf894q^E5!G%rRl67-};iMl+xM_l+X&L3@g(~+i zT=#D6$&c1$FYSZ})GZ5Ts|%MK*|FcY4LQ*qWCTAUBJGNu0~W=_F|7ruIay}~%~a&` zM#HVbsSXNe<;sY#;rC({CkJxdqMX1ZeSJL|pa<$GKnww-eM3h6w0~lMePNf8heYFn zw708b3C1dw!;n;(sMUM*=EgI}pGGIkqxxE!?6-=|p7iK^^?L2W#N!Dx2nLbGjaykj zUK47H7rb{XSBHBEn;0-6mCwKvb%A+z=UE~%k`6!h1K`F9RhsuwF;lOH@4QXqL#%k& zAvcBvN{HmgAI%Vvfh-L?YL_Lr>QY(ik;i)6dm%d8Jh!f{$q->^uJ!3QukL%eLVIu> zAE@?XiWJ}_<#Asv9|-z+MeFa84On*8)CNi?Aw;H^BYIu)<{x3HNQ{=BIM9uEpCEGe zmKrGF?FE5?^={9@^xw-6Q3@Hr&CNqlm9dcoF`ZF_3}E>=!2BNiChPJId{LkLc=tLh zWTwbeMrZh|F}^C)pgRTQbXN)~<-K6`)wBu(1&!9kdoTc_F5H@;m~o@)IF3tO!O~B? z?%2P|gP`{8W{zGSbP9e_19bY+M&&&4tfuYK z^3kEZNE&^QKjP|ru;Yw3-j%<$_6i>bbZSxV<5sIN!|&%nQRkT$h2a$Xsig7w;fDHb zc4%>PgX+I^Re~<8G?AW83vT@8aRjGFDuifMH*EZA{V*qJ{mieyUkqhVOn8&c2?>uh z4eCbJ>Z74EQmxPrr$*8b$CxdzGmV%CQ-pMcVKDr*hY%1PdC0~xSE`b8w_t%#Wu<

AK*f7^mkYJ8NetJgb7uhocMu{9Yb;bU%$&ebL2|r)%y+oN z_H-Yj7>_Smx`m-f!X$GaVl@pR_d}Ls`d`9+Cl2gfY2!YoI=}34BMm>a1eCZ=QaSD6 zOw4HQA$*2nYhl{5cYNJ;t_w!8^Bn+jV3;iNd}C}g4aJ-rA>Z5o9vQ+E$>ih$gaxHx zoyuAF3o5I$G~O4&rV5nT!Qb#D!mr+$jiF4Fx;-2Tr-6g$J9Jr16gBksrq~_MoxOKv zdSdt*Yg)F)pE}K3zB0oit(SSl5cht?uv(l=C!ObaQA7Z(sHS^jB%2B*k7E(&820Ry zINi>q;zGNhAK1Lb?TGOVeNeLaMCoT$X^>xcjbqp2&-c;hq76!UToAHT8Z&Lx#vHppjS>LCg?v2g z3%^PTDBGrd!3&zxLz|GThu&3xlDr^Gwm=Bgsi_rw!Hq%D!uzY*HhgmW?=3e!niuj4 z&=*S!3-&AH^*KIvkveAUd7!2J#SP5JPOuPh!FR9(A3(KRKiqaiS_Mzu^WXfiVk&o! zKL9pj*PgSXcqlQ4RAWVg=5tFHC%!Jn>1<$9sY+zjVH=SIc!^-fl(d|-q7T)sPPV5r zT^6DbsyFMqF5Q~eC(B*#;mcju(74|s?K(@~RVxVSx{i|JOPv*ppTVfJOBMv>O26e> z8v6?6&LDs!!d(nJAe%e-B_Sbu2t-RMBgG*GhTb9tFEty?s8?R1fGdQbyhWv>DXlmO z@7%}%Y=$p6vmh}BB~m-U{S?#wNTT5p`#l??qm@@90SEiNfNnTt4!@M8TQ`9HkY70n zMIpb2oe!`SOW1lC$$`dRL%~?J>nX7%psal6RS=G%vYvNxWc6n+J+vSrG~cTf{+#$j zAE{%8IB5o^GoAa6!F1W5k13|nGk=k2r+w4r`K?^w&w-5z_9JeV$?hYkfTqy*%a}Ed zthlP3!A>U9S~2y$ZQ;zo!^;uuF(G`NwQ*uKnp4Dd-QKaJI=|l{SVKW6Y21+^ORmBu z<_;SEC%$ve2PILyLI#IY3VMM6B3RK6rwI#QcE+lGp%rG`P%1vraLHO>!W;9N5Q;K% zL4$dvw}hR}9`A7B(5~~ZeMeS4ooLQ_VZ_zKlb{0OBT4yVNl!&hqN(4=?|DNko> zXMgnqtkDS1`CA!tk&SFhAH&_m>zqfEXQz7n1Z`V;&gME#hsNjj+-0`GT4Czd!!?+g z>tH#iPd<0=KMQmB*=KWkpZM09t8c;vUY#p_ZOSq@rBekq=n`5YWrPSMHcHyoJB*c^R=7Z$#!ty z2GZPUvaGl!#zOg+1*`J0$Az0EO(NZJUJB6&ZvTwID(%FxCOh#%)2SI3svj`n!wC2t z=R#zFoN=aSPs9%ol($5{x*~V_m5M~0&lorItM0y}#i7LRR#qyIl@?2@vgiB7h|{}q zx7zu^>7WBv@G`e-sX#lYVK{~FlY1Ng`%{?5zceSU7s3PLZx;!Yk@2WouT&6O$v?N#vqDbKeMCGSa zG5>EUH2eJ?u%8KlDjg3T5M;A%evIe=1792%!;^*K9~l_eG8bv2ZthD)5VqPMtj-4Y zDd_ow{W{rt5kb?|8Y57xrqN*I<{#@&6+i0X22*a>=#2s_-9V&nZv=Oz-O}z+ooS3K zetSK%dXgB#>KXGD<9fAHf$Tk6khuqC?Lt5=JsuK_$kE1*m2*Y=E0^Xu8 z6+U`8iBNTj@C{-!Elfi~1!ye3U7llVNFusH?s5}lQmS82ZjT%)KTA2&7<|x!PFT0E z#l;xP(i6vKXXjV#|1xFZ0AqUfUq3laS*get&8;#z!Ox?K8Bb=mN6kJ*>99)_|w~knBeaKDNCysm(mrzsZMOp3BmHI`T=4av$Ms81gp5sXJ*XgPk5zOyt(@pow!q2WwF{^hCu9l#_b4r*MzZokH z?FrvUCQ@P^MDE*LwKnLsjT+VBZx%|1&*Li>_a+8UP5E@Q`F3TPT}T>j8vFqS`zv|F z3bl|_d)rPNK1B$TrkxC#n$FHODE9TI5S>*+d3KG}t@I$T?LiM9NVp8DbOcLOPT#Fa zIT=%zEcG;&xoc(l?_rlOP$B&-|FGMP2TX;ZcnXayJ?BAmd+8y2{k2vjsrTnD-~qiA zovuA2OtR47L%exbwa4JJSB9016I$cQ}@aVjkQ!BkHin4#NZ-j zRKhBVpJ`|=%l@;yfc-0suPYRV1d}P~cZh)__jZwP!@Zj?Q6^8J;CHY)Mhk52WMJvZ z(3}9eO9iI#OoiVwR#GIwwf@^{ET|_sp_SgBeN~H?sZLp1TEoK^ocu6Z?NAfdGC#`X zqY$%Qr*2+086-pyL7MJqBideDRP%&Etzlo-pvH71eBgE)_sXif#)UKdj@BcLEH0rpYVmr^wP@b z=k>vbGjf(<`^5V3(c1jhvS*^qlcFjjBFFJf0|J>q>gia&^RW~&ZSHrSpOWL?RE+DF zYpXuaMh%F{i5RliBjCcs1v8!~QNP5x>-?t5))&rsaHn8s>9n_O&~^vEJD;o87t!KP z4A-&ArGCs8AzAV}W;hbc7n&;6ci&H#ljOAC;s4=p{Qo@;@n0!{{>xj7k)8dogh2ne zXnlHU{crhzgrj<4{Xc<UELyrXxiGDjtW)PkBqm|>k^+ONzVIPn-H~V+L|!$_V<>D%a25f@D(n^l$5Vle(8b*tn{N7$4AogDp3Zi6cjvROd2@;sE!i>F zQb%Vlk(#R=ZCho-Rr@njhNDPpntdMdSffxE_~%zW9%s%Sh5FpVPWt_K+cqDCIX=(9 zglD87_f{Iu9^-X6Q?T#JJ9QnU##oE!jfk3#aJ9HD^zNqmK;QrQg=kG#G2wb9f+t^&A1iH;o);!fPj$okmx5ND45@-?#GGU&6BO~%g(|5 zGURpl`)fr1#AWy6&fwT;e~*^$+pBIi&kJyMQ|l@YE=T*iE?KQN;#^I`7Dpqpc4u=5 z>U=wYo%SwOax|oV&=5K=>wfn+0n=l6eR$YTnhH4Mk+9gq_4;h$bUAmJ)0L^)R&2N~ z_+2S-OT!UcyF=!5G}vt~870|L==;o{0o~m@fy=4n`U4l*4+Ax3-YVc}!&l(6dr$`_ zBFW;FC}b1HUD|nfn?3&F%VX1)m*uj4>2HTw6iYEAIE}+Y7H-iCQystLawY zqvwY+6Up$BR7=m=IFEoGwx1XTvQhYK!Ytv11l0DxuY>c{KNcJ4-%4uH)iHZUo_b}D zN84&mn||jehVQR_@dYEycd1-w{tR8Bs-nU4#5HVOKmVcT=wlbU!hZ0 z`-~hT#&?06<1CW7xQr#B9QmT;?HzXr0+j)$^ z`${^6uIo|4$a*Ie+mZ+L`w|Pl;&!_>ibLd|hkd3FWvIhLa?`bP^6_v~j&YmV4SxJW zg8_y0gvAg}B-3djobdAq)RA8G$P_}=fN};F-VY>c$}9*1%q{lOQ8NZPlgM|uRz&hn z1(!{*(X)1in@HvISEWH{G_>qAG+;JC>&pzRN7hv+R*dHP^V5X#o(?A-Tr{rQN@PkC z@=qQU+r&k~!9>Z5z$2))3b)O~X^>9K!J1t8UY(|jKvwk#?~Bm#@Rm`$(Q;L6Lu~e! z$QiAcnebigb9(%(pWF;5U-e+qy}TTiIkL5ThV4k4hB@?37&0?yd_+kRDK-M=A(>XV zVo|j#05CF{UNOr;fpcdBRt3RH^g?4lb`*nEu%^gv)?1zalr!Sj<~6e&q!KH);NpZd zSa?R0KjVYqZo?}u?UqgEMTUqhDi7-!NMqUt&S>G%Ur|Jx44dLrBXyBRZ~utnL9+bC zE7Q5Bpuw4+%cl;%#zl3-tBwfWX@Ag*Zpv7Rr98e)f(m1km}^xh-(L>VhE|In{av6% z`4ByoP<1IKC=OGly?Ka8?1=>9f;iGKK0{H&v0AGWoX#=f09Q=2yObuw@E7Avt5s6U zPnqeJd_h2l;}U7l5@?M6BRV`LouW~x-<7HfRZs*c&+2?@VG99yT&PBP*XT1b!(R@ z0$pf^NcGwd$y1O|+4Ke}zJUc(V_7s;FY&i_jc)VVtvd>A<*7mERYHH^9Kn*2CS5TE zYm5R5+3cqY?o$s(K~@Fl0pTXD!K3OV@#G`SLeBdQ$OK(uB3`Q(T4U1Bq!aJuus$am zp5h*{N9G=Z+2R~2g4qS{g5O;-)6+^Prb7r!xDU3|+TtF#8f8Re=NO<;!x@mk4dB=b z7s~t@Mv6xVzXg5@zXcD0u;sQ72yu1rqOta8*Wzfu5{R~W?JU5>#coxG`GgRDbPr*F zirB`rv2#n%b#&?v^2=ahw@M&Kw0_<9%(JdQZPZ_cUsVxf%kUCvHV$Tkzz1U&!Ukg( zNdvLNy9BWdXGFAv9Y(Ydr;4^8okkrc7MC;+;S7Ku=HwT(MV*!3P0N*uIR`sMBOYtqGhx76XZ-hIV_Eda53s3 z$>!sEg?pyqCAq9A{w&Zj;~*zQuY8;#lHjE#ssf7+b%PW;jTVvIf)s5Rf{z{_cV#0pG;aDEReiXhCxkSjfTflCk5x-(c zrbw-~HWC-fA8cl=>{G6?7NaRp@2BZe7{ST3EEO3Y6lL1d;K*3cwLD`JqSr!gh3^^Z zR%;B_Te8SI^wV!fMi{grd3{El8tgKs3dg=M}MRrDH@Co-S!+E%XDb>%yAc-o#X`w^x0$Z$geX?=<5;7wM z6hjL^M%lP$kh#Cd8v=>aNZ;Y$9GCIK6D_^RUYlmJLT$HJOSjB4;QSWofHR&)ZybFv zjorCUFiAWRE5gBmNXy;Q5r8LShTFYK+DmfCZ&_W2}R6$W8iC&Ky#dvJR%kanHo-G^)){M|LHA_Ool z+ak|yz2x1(gf+ubnb|?5_PwhL4+UR>vwloH{Z2j+{G}_U2wpmnCuYi6{T8nt$*9c~ zk*8zS7rQ~9K4=$)drfM>*!1W$w^S;mQ;(P9Q)A`J>{&!YM=9G0nO*A%El9mj^D?)q z>HfrUUlGEFBKpOC=x40&eW%he-^~<>@*_gtGT6JiFnl|)fv3UCHzxl=f1TP`lBO2L zkFzo65BSgmCJ+72#TSzuPZYS_2OwdO+ubX6F^}#h@2z4efd%v~Is?c_k@xHCLWFMT zW66|_t*%$^rg7-zS053whHyUens4VR$5~GitM(n;Z|j(kXphGYQ@RA*udgkgutyT> z)j7V;phAC&qJCki4wP^z>~@{b9s{=YEf$DDz2?hkIut{;n!caS55fl@>f5=T>_t7h zwG>{ORIj^y_j-MPY3Xshx>LRUTx(wZIk|iJT!Xy5SJ?J&dOcMppW1GA`MwReTw5M= zkp)&plE5P3M{xmbOGtKiAf2Vxpqe*NmSSmK#9p*+ImEi$CA{t@YDR=iWt6csyAOe>Q8GaYdt zRw}5(J_OVF52}Etlqd9 zSD=WJ+86c0nSzv?f-(|_8gyJGupngm4syvM?;-qCKWCi`d}pm&~_% z==Rqi{Nsg378?m+#CJ>S;=~-Qk}e}(1k>>ne?awC_k*+6mj@!O!tC zKr_)4`{_X#s8ITCfmd7?QW2HAo`HrgCCg&7Csrp#-XG)8y4qj3G1-7J*#MSjW|N$Z4<+7rC{R*pv)i8EeM>vl`_ zT1@)pSK9imWaZ0z>Bf%osN8q0M8;lHvZpxh%Unt30mSowCNQa$Z^?W14D zXzQe9ckvK%E?HogJqjAEX#u!=si`#2Wzfh%d(c>qjZ$p_9MWWn2SQu*W!c6kP(u!5 z>fnenmZ&iO3kU=FnnwxEe)LAQrB-%!t->xaK)sL&PN67Wmb3Yv2q+BB&>ocJ5LU?4 zsHvsOtfXi0Duc{Zc+e)ITj+Mnp&n~gM-at~5Z2MyeOjUEK{8D7{XapLs8vPg8fJ$u zli97qVf4J|E`C~rLG~)|>}+?3`Fj&bzz#>wB|br@`fjD+OE<)o%wV1o2$C`Cnh>r+ z;qLPH#qn8Ee_I?M>K(!;H=?PO#=f=wx*hs!eA>})mZ>;$7AttBU_pGA+BMM5!ijZ& z*(ZT4=pMl!hJw;gz)E_VYgM%OI;BRZUZctcfe$V-KE*{1f^0R2(| zq+skh{$P36A*|r(x^wt~NIm!hGCg>(Qj`QWY;p;cY^F$qZbo=iwdO2+w1X)9`C-Oj zeGADoZ1OQkrr=&-L|Zw1?=RBCIM_XGSClqdZIm|7 zv#P5Lh+d759gs@NrMJ~ukswg8z7EcDnh`;izLi9Ys|S2m(9xffgDo7}%!&+^CEzpu zm5I$IW(HmvHBWCByI+SCN01(JJ$2}(i(C|Ol|4d}!kU3mKn2N^bF;juWi0}7{e@LP zX|9YBDFb&aWQKV@PBz)BG^={?V%ZSUVwouVU6=(d#sys+mCo$5-`aUoN-j&->Hy~=nI;R#k~U(`H_qWlc@E2N#+b;nZa4+ ze;|B+r9n2}wC+ZlqZCVhnM{M68-K<&O%H^i8YqxXD7St>EfzRzY06}b;wTtpDa16! z`N7CkE-z_cja;G2D1w9=U^%{|THUMlV4prCG_Zxk!-WMrQtxdYO6zSEl?5MSm4q5% z6^3QBPm+iAUUn6w4BwZe#7V@>qx3)$+-79v%q!Ki49r*Lf))v|AVq^PH$;1Nh2t(r zwKU5_aU#YlgMZj`8}v6I#TWx;FkxK7pZ7oQO9*vE>oE1BSCJI;DN@gF`6e@?uh4xcLzLn5M>g5# z9rdK{M7?P!i9{CvFl8Y%QYyaJB9lc5q)AVgVz3ZL&fZGrUet#u_%{bOw-@5q^kN!Y&s+;7X5d4?dwT zoRwx7E4W`MTP6H=#wQvbBj+NZ|N#Pi1}VtkV3;#zeL(p9i|X@}lls{71oNyjK|DPH?4 zO1@$S=1M`v-byFjk7|>z_^lvxV~N&yE;6er2hCt@SG%SnaI0<^0F)%WpFS zizEcQ z!^7xW&nsn1Pt3I}?!Lxe?jOg~&9Z&kuc1n($ly$ra7aoKOfm+HT1LTi=XE#!@{rtm zr%wX1YV?x8I>lzs@ymM;`38cF<@LIXb#GD^cAe+y^>e;NwbW5$&TSi&iv5nalKH52 zB)H5e!vpJ-%-o(#s0~W#T9%akA{4UB$S?>tx{c~R+Y~a~$!cgC9k-Oz#_COf78RmerZ(pPS-2FM` zksk`KO<`jLD@ah&zj7p@JvCajYnPoQ*YZrBI(aa6j^1F1k6#@jjQqc*&0`rm+fUh&MId zIz0v7%|$>Md;5sR*GoE#r8p*ll@7}aW*>vo^yVqcuVpGNBLiO5#q>1w!}#&&;4{0t z`&Yht0-g2FPWn%CuX!Jta~yYg?x%)Z&YBp9>3A1I2%Utub;Ul8DdZgGZ|$rQEB9h_ z@%9<@?vst_r_JIyoM|92CiV_~r%HmXnw4wn&F%#41IN((P=YF<7M8a7t%JYaM3la% z=MjvK7j5z7u2zY=afKv2gy#(6)6y;3MBF5qfP~4~`m=&+p{!~l(2Up6BHpYk(}?HW ziSIcpF|~(+kk!Ph$Bz*+OIBiuM8q1H8Bnnrm@8_y8ko9q=n9ybQHsxdkH$4H$iQ(a zL0F^-9hCe#Of^V}*M1RZl1?(=U0iyYZ5WRl=)8)9J0j{a51vqyw2M2U@d5mOqGUAv zeIi#B;^YMBP?0FMlMpSUiA3BQDB|$XUzD3#ngi$*+CJmsj&40T#WB6Tt07^I3vq=OxW=~qM5b;4XkE7QoCEuP;I*=(FAb#3%X zdETWJ5Ey+R)Ic#h+RXqsl@?flV+%4h$??xFHH)!ET>ATYM`oXL1A z#_T%&HLh%XKO%o`67WfjuFsQGTs6)^DPW{A#^8&tfMS+y7VL%-50p)Q(>bmoy(=oF z!Te9X9@`F2*6D#Jk(%TKm3S1p-w@2^Gox};-&|m)($V{>8fVc{3cnBI1m6E;8 zin&F2`gN@YXqd6MQW;%Iu{h7=GbMmZn zO9NVJ=*g`f)q(xlHxZR|^-o_PSpzDRIX$YeIp$;(^5-iu5seiI$;gAO0;~ldaRn3* z9$^LP54(5;r*sMG#ulpkbY?}kt%Bw%ym=6{OpQff(=Er-_JY@R9K^?yu7a~?z7%9RFcf4wF5JY&PPoKTuY?q2+Ef9t-}3QG z$X)N=rVD?uPD;ZP?2ha)`J>L({TTkb&O0R1JO+vSxluh6d%`g!urUoSo_+M&D}Il~ z;i_0>*P#8vwSo`oa<1W0>~k5 z6kMi_i%FA~y%fZ~mc57!1s;`Qj^g0g!S$sf#kDBwZ+;G=Xe9P$D; zU6>-~4nz8M55k;3?=eQb!0Vl?NbzOmug-XoLtIz3p=(|kDP1IJ99Mc%E#QwD1MIcj zla*H_v`+!|^Wmv0!ra+K#*K=7zd1)7YFt*z1VX3PhP_BlvznJb2X{%?(NSeU2Mei^ zJ4K}$puX^egU1G^F!`|{e@w?PO|$)fv35>DvPEmUE!#GB*|v>cwr$(CYnN@?wr$&0 zyKL*$xqUkN#EsK^U-}^*){2an85tRoWBqe};)3$iUbNGEN9K#Go=4`pg~Zy<0N7(Ohc9ICA_x9uF!%e7 z+UBU+yB;R&o1)sjt8(0XDn1WVIkJGhZF?qhp`iS=tf0&qV zi6egUPxo8yyz233dv4`-it1o$#T??i(~nb;34KFjxm&M*X&Z68o-gx2vfZFF5&m~P z*3e@^$quKU4M;$alBG7cb5(^7^RJ$a*@ZZP62tzW0Ciglw}bojlBXUXhdgNmfK((% z2Qqlh@f?#v#>O_!AEob%p_fz0mTqdZqknag|Ng#!Kl*KY+;~D9zQBHCbrQ}X8;|Tb zU#CF~eK}C}>tcb1#ofx>Q*QJlPd@TXmgn=18m!~T}ma{y(zmoN^3FKFFkMB=@1E-Xrpkz&VCrB*-Fm z6bF1^k-|Pv?+yYrCcR5Eg%jCvvYf&vW5q4J6|s|?4FadhFs`n+7E$ecN&UWeu)qRT z*JaeTPLmi`2HP48k-4xmG94(17tPS(H3dygK4O*hP|D0wI) z`HY4?ky*vW$RbY#T9Z6PL=oaRzf(+tj^yO<^K(f+T{nvqgs~UxpafhnCd|n{ND!m6 zFZxZ)65E^e-4QC~nvgVSWxK*EwLKP;v%!{D}4kYBY^U|6-Aca)Sm zrB<-F5-&^rd0idxk2@#@X7iWvb^sRP@q)|EOQNS5UG&}=mv<*h#&TR@l$B9~#W`Ft z`3+*0EaT#5pm5*AkZ7>iY0e_{hFYh(*eR7P*UGQN2owOf5f;4lfkmB>P|=seb{*_f z0$=rxUT%QVS09sc(ys$K8!ck(_(!Evu@Dj+i^tT4L}g4K^AAhoL_uPro`N&(Yernh zM7dyW#mNVK0Y*VR17o1>lIbZ$x#}^8v1x%6*G%cf@nThv`GiEaQqBW{iVnQvVw^6h z9Z@WK5nRDVwOSQiCH9U^C)~qegr5-8f{OOLpEJ=1_!UVm`5_+cfYa}n&$NcA)_9MI zsN59cqWiC!8-*aESEa7lBO>6g+5?M5O``3CMD?1vuMqE3SJS}MNpkO zJo6Ks@?|npvSTna!gJirlfNCzl@H_$6di#K6a@tj6h-BN#bR;8o-O@hlIXK_3}%Z~ z)?E4fJs2#OaN(aoV;~YNHmioP$*x&-S{~8!*}AJ+qn6}=SY$5@fxYs4Gf^R=bVyul z{(=#~*nobf9HV$u^&T-T#Gi-+?(}3%kpzrOXZ|7)bv)^I*^@b9LPB<}^+aqP#@zLO z6zOt^)44($zF1PgYUdbImdrb|`UyF8_A-@SKj})6VQ{bv%|G(Yt42$Il6k1C49ezs zomK~pb)Ws>?UgftC5Kfkv1`rik_L{40mqT841O~91IMDz?xE6F8}uUyE}PD)58hU_ z;og}m^nJx@{rbJ6236KuU^YmIsJF1}moVp}=9;~6$QcCW#TX91Rt-uEM#HrUecB1h ze$~)SkEZ=~d-l(y5YNvdEV0SJWmjyBA_0{dSG2?!H8;oLa>`d%kztV=t3+5(ea>Qidy}3u4E0)w-Gnyu8wE%!Ux(# z`^e3kb$_DS0K5pE1WB{cWecST377TU0_FfX_{mbG!9?*N`S+2%4nS=nL{ z&z2mZP5VyR&p`b^qzeRo>36oo%hlcq-y#3lL6H9kNj6McHbRPro1~V- z5u=vH?aW|N7HfQ=dvz@Q6%XlE4)FV>FXP4av1CxPPjBzl{nHlYl3l;R0lIGg$qn*#b%wL5iPoO(O80S$KwYZ*oO0B83I z3oR-*uw;ldoWoR?jg3<-yaoOJnk(|7*`Jl! zeq6Gx03{AvwA*wN4dxnm*qP)-1tW3_I40ix3N1T0m}9P;bC!D$#rmUco2nLuXp8Rj zfy~&@jGyfp&-dfcmK|K3E}!oK*P0#Q$4jPl|U!6IW7O^Z7!M1gM}QksOp(~BwD#5CEC29KAw*F|R&6@`s855znGkQI^8 z&K4Bxk#SNWA6pC9tK+lwQ#6S4E#2Jf84cq~(ENoj=;tqxM|?z?n`D;3Ez)RHcp2`L z{f|jtmgY+$_-U!X195H%f}wjSx+kw~=X(pD0c)^_)QsvyM2<)kW=lK$4v-g=!P&2( zXfbzNZ1%EG*vfXg7qj_~8D($1zIm=4` zUQcRa9&0Fe{Y_GasE~w`2SI0!$qgJp8?19%ipt)om0meJ;{&mi&7$ z*b;K}Hxwm>5mlZ>>S10y(9{&DuO127YWRF#K#jD5nR3ZeE6@)u~Q`sa(0?F3AOYNczyMuVk)UOQXy-FW4&)`lKN@DZ)2^#WjEh4 zh_Ol%3gvN}8O{ zEUC)4kZTKcR#uKMh+kBi_l`cB@FRE;Nf;IY>GUw5&jqfN$5$*d6;!gvgm6}2Dcn&b z$=A$`WQR?MFlZ{Vz@$B9X^rVgT_d>KGB62zbB>glvFW0@8mv}1E(*5PfiO)5mRHcq z4lc5k&!jGk!YOV`9!|UU8@p0KX_@uNH4K$cz_MV$rxwzy13{boq(pnnC$Taiq&eoN zuSh%+#aT@a4Q)(?)L(zd_b6AUqJ^2G;2}E}lC+FY>RgR65vI|ZXjLQG82*s%qkbfk z^3YKmA65{j8E#Nuoh$~7Hz($xWmS<;=gD||i(Es} zQhjq$X{;+RgXX7EnGb1M;Z&V+0xY&9M7EDU1yJZi0bTiPjXg-$NS6ATFqNm?SPNS) zfR!=y0g0UzTx9)%NUNr;rm_JRwNt*IJ~pN$1kDpc%95es)Trj3(Z&d^4ONM?%k=z5 z9O|I=~YVLEu2hF9uKd z`=dwtq8k2g}s(yR3@m3BNhO$RqkwjC;E!$8E&$S-w*C{i?wWcgLHwC~E6u zd1lqeW4PtgG$h}auX3%jBAyhyThFZTFDwJ7x63e8ra4|v2L`N&=&LZrU_QhtUZYk? znnCbwDg{l-V%j_yt75sukE@Ks9%2gcp-7xfBC|lHWF|2<99gG4@T#GS;ixP6RIoPL zxeXyJ0>>3AEd$QgRtIxrO-i2C6zTagj z|Icr?S;0i=S^bwH^iG;vBWx}cYi;V(9ZHxdPvlPJDgR)Xa)f)*nR?7z1I@j0Ar`>Z z-}3yzjV-_*cP~vEi3N-Z^oI*;i0sUR~023CWu3{Yy%vjx24qCw&VNYUy zi5esJ6!-KLfAe^bUOU?)Zj~wAdt~YraAlog-~G4D*Q2bH#p*SA&C$ykQ7%h|@I0A? zM#^HyDPx`${G3J4+ysc8R5uPDm7oSz^O1r8Qv( z!31W$8sW7@q@#vdzMk*74NCLwj%w|zh^`(^ucx~lUsumo_pg~5i&h_>`?X7^pyt=N z*9mOOPT#k~m_cmJ4`RpxM0!S+ze)#7ccHnQl~uYDAmYTsg&J%{+j9GlA->C#K=J1v z{J4lrMh^^SF(C*|QZggH zTU?!KKL_awURRuq6-5%+JzRfPf7?9?!t(PIul{`&`RpqjzIwv!|9g&JV1IEW!BQ^L@@|JPjmkH@GXwv!5TSIDjDhTx zCq7(UGEv!A{P_({4`8BZR`G8RtN)AeT)@i7*uh5M=|Ay@)#yy7~=o~Fw$0G zrToH(h=3x0a{T}c^295{3<$|f_29)3JA=T$#DK2eElUej#3zL$@Hsum^cDcG(3$?Qf|<} zAV%}BD90OZO@8vi2v2ixKT~mS%!=VT%*`|850Sy3*dQpTmP~P&ZKN8LMCv5!t){&d zV`v&2Km>6!G<&5RVn$f%AUvEC;;jRKU12L7`!O_{T|LOYP+MW4$$gvsEl@k{?^qiP z>#1B%2gU?;ApbnRgrw2KhErOKu`($gR)9#;FKSGC>(l&Sn&}#v8d&L=X+H9$-|a7K zBv+Tex}aV*nZlNzLoaJS6wUzjCL=5w&fzsDeE>L{fe2i`;WLg20fw>(6*E8t)1-h9qv}uei{7*j_%)O;otNZvKpC|apaFV z04(DW;#;{uhbO##`3QhW?sA)tJMYJV@Syt{=*66M?9Vx%pYPRn;V*I1(eF4XNElM4 zkgu4Lmiv~Fvwi@?fT*LusJVbn{)F_vD!pjzKy0-@FaCN6aLYhwVc=ta@Qebqt1wXf zh^wGa0g`gSX97fKfMopO?69`h<+a{)dFh;?RtK zlLTN!@NELTas()FB0}~E@JNDfvuLFdl);pF8l{MDXq=$he$|4&vpi-f=>a~$+XFut z)v{W~N?7axm{rK9kib0_JDg0glit@GnO30cz}UXa8_*X{FK|5noPh=i{xAqwc_A1? zu>F7+LPT-uelb>pU~vwG&_)7naXJTROo8G!S8|w$fcyd&`DSuiC5VV%Ng-=~)B;L* zG5O0>Z9iHuw%m=`lv&r=gTHJTFd-pxeQNsL z411{{lbliv#{k8sj(ALdm;;jrb!zUFfXi^_p~eQFt3Ve%7Yr917X&Ui?`UF!W&4wM zSFI2@@F}5GBP@H<`(XCSt?r#DI?%R(&jSs&YcEJ%Jl)W~z%M6t-@~NZX(1;OX16d0KZaN!_@@1WjY0MNV{m!eTa}E zwM@VrLo@<(Cu@uIiu*|YPzm^@PrOIcm%K_YPclc#Kz=KTL{~;~O5CQxC|0j^S9l0J zW=_$W*qj)NqG%DQl(a}ureCIR9?@9VnB!FMROgiNpnNKUSsB$Fl>wtks(V~JDU!wj zB{-McDyNo6ljt!{GR}@Fw3O!|k6bFUXj}TZh^dabW?Y|FuU@ZQZ)zlF1|gksEb==) zX;x-7#OykSoX%dOU%lQ?Vj{ke#L*$bA;tlxO|VU+jmzQs==9F!F8r?Ji0craA{XTW z6$ph96^>F%AyuW2a-Fgs1sYYBd|Ndj=^+UyX_*37iuSi_KG_`AU!Dg{77`X&mOPCrjh^++_85;akL;iR zJ0jQsFd-t90;vK`Ls3QL1yv^12KD=HG@Zyi)S4#Q3RO!PO))Mh&kUE)r^1-bm=u{9 znTBa0?fU9Kn{=D3D{G!8JRv&yYrk!2Z?zpkKAj%P?+`#MJ@Z}`c0Kx9l&(b&3S0B7 zd3~YyyafFa{f50##HrB{@|n_szrl6I_CdEc2Ra9`q-dlPq>rTVQpQrMsmEz9XPMThgjrE6`&1y30<{BcJ%9^lD zr7*st3+5SC1&`+EuB^$dKpDuaJ*!yjr0c9}GPEn|)9OEa43jcjG3Kh4YzDF| zvCVu&eEYe_y-uZ$Zu_Nsqb^oF{Zh;2maCd@$|xTw{k0c|IG`4yo)b~dc!=+OwU%!7KVg^ zx`^^Xy2{Z?{2~x$6GmUirNqe0*vQ~SJIPYXY-N;mTmD#49ZD7CIKV|zi0NnZCFX+Z8DTg=aU?akK9oExGrTr*8J-d08b%kJbKGu6)kcTRjckE*jP!{VOH4}~ zOwvSp5MPiWdu!wt-kFV(lvtVQuN1f#uo%aj(b#3qk`YH7Y=^U&X zT!8t6sf=mMB*Y|ZB4K)4|5pE#9yO9ZY(4Snf@_Lts^(Iqado`<>rwL9gq;h^fZfh! zVKuqK-aTfxFgF!66`tWc$}~nb<=I7I^;@;u;j8$gt_n{x1DY7>1$1D;s%6Pa%=*OU zdPl$8@H<8}_@duZgjD261V2K3Xr-I+vwXiIIIE89{WtY*xU2q){Y$L%%i4FdGADDH z(^b43o=fA0`u_SA&w?i%8eJ8ghS*x!R@=hI`hmzcQen`wVVDj8?jHJ2uD+lSA%1&fl3>CtQoj^FGVzAk%O z(`{?_WhW(cgW4G9F=sw@_De$>Jb*l(DV=oMZ5KYT1~rP;ZB#C*GP+G|9%onw8Y9jV z=a=4S-qw6-AG6E0YdbyzXQC(4=Na0-6X4TuoG?1AM;_;Q)-@X|*Oi@2^eBu@uIvtO zhqUikhh2r9bl0YvIli^0gm3zLL}g+}u}OFqY4R&`-DCo?cu{r3+24s@ z3!%3kC5I(P$mPh<2_^|8C7UHSyqcd@AMR)OucrYyMZVQOdGGrjx;Vb48R{%dU-91# zdn@~@cQrAcI#2DM%f3&y8Ha55j={D^J9yqBZY%G+7oUS($KV&C5wRZl9lY+o9<6^m zYi}}s%=gbh_y4Y9_Fq~7{!Od)f5b;w{?ql(K+pU`oeck7dX$k-nO?58Zq9XjW{eJs zR$NL3)IMSgB`VqvNx;SBts0f&s*fa`gaGncPY9F=xHL%9M+Z`F5|-Qu25H`vNr9dz zur>;Hk~()BDB><5Q1N&EBe2%ygOCE1q`~C{z)uF2{R^M<-*7bhkEhDkzyg+=`@h^( z9RF+Xs((syEiT`AF26hgP|7syL+n!9Fkin5a3t?**|GY3vV{rhp z?}=iD?)9tN+-^--&ds zwj-@q9#OE~Ktw~szF1-boz!BQz$%FDv$t(>yj`X*0K*PoWuT*8Al%+MkAi#Z>!NZ; zWZU2R7NLKA5brzdX&ScmadPq{ARl`($Tu8YFZSq2@gQunSN6xg>s(EbTDpzYRNemo z#r=9#VE-m-x~M1=4=C7dIaP=}3yQQqih_bM16vj|a{*HLycdsS@F!=aR_ORg zc^<8^fHks`U)5u;aX_2wd>mD75izarAOf7C$!~j?gClagG~u!Gr&RRUV)b|g<*?!x zxVkb_cK^j}Y&Ri0x3UPd1spRNgvekcvZK%uC97HqFgOguzJoIBfef*p2-mbO^V%O^ zQZdM&6=f&%dI3HAA@uANVOzIc)stVc;S7ksumF*f7+lw1%WK(!XOsOGfFU985_1uq zfRIEDsZ=%Cc1hW3ClVVXj3$ke7lV^32NxG%k`^rG#w@Rm!r@o5(-2sZ$n$9UeLS*u zQSy2(BZF~BPMH@MpjPxZ9#^6-0)aU}^gMJ9RsNU+S9t7DLB+**?Xj6}JB{%4{b1^Z z*qjspz#+rDyG7RJbY^dE0{_^;_5Fx?!^iU(0lqWl^J%WS^~T(HZ-7z3h||a+nEV>tzXvc9V7)i>Xnva(~XI3=FhJfS(J={;DzUp&R?;c?!}`#m4fy za~5l# z>0H_7FmJ+cGxCrb2U`e}o_dMS1dx{=(n!BDm_mA+*aS2CNPt0RGIkafv=Wcd6vPrQ z5g6CNBZ$+Eux$hfnvd~HfCKt8Jn9ofK9xl`Lb; zH;||}7&)dB?H+U_=^B>6T97LLW?SQ^18~?up2#uOmT@?K)5<+0alO2j9l~|e%7mI( zAxA#GE5HmhUP}+KkdI+c9Db1v8i_66bA5~rvx(9|Y@0x@nuNa0!SFuXkdbVBwO3AR z5~={s{oIeqFg##~Q?35d4r_EgqGz4J8+BbXHds@wu7vV$=tBL?F4Wpo5IJk{d28`v*mcB7b+=<5 ziD@qUxsel&f2eB-Ivq^^&Q{Rj6SBqm@4SK3Mc1~f@Pi=Fd=;x?H|UJ5&%LFJt^$sS zYlC;%;726YJtycl_S^;bTr%$E`)Wh(*VrJ+G0WoB*OW2g9XpU~R@P&;&>J3g zOt)HZHxNQTkGGkDl6%wD_pOE<2DdFwI2-OGJl#ugW+GLZ_yG#lCVJ)x=*Vg>WwWB6Zc8(q;F0zzs-khF}FZHg@x0kEq z2M?mLwnw0N!AojgoZ0iK_yz;qgUUJ_^c9-`v&(Jr)mE#{PqXCC<)rY4)@fJUrc}_p z*P0*dd32}zcY`+u`v37C`~Sfn4#xkPdKCYIdaB6n;b|qPB|z)90%1`nVf^<0K|MCF z?j-ySY`K3J#Km`}#QP>;`WaQ|8~kb~Q6{DHu>L#Jf!xFF{Wqk;@NcZ0|3J8ZwRST6 z?`b>#0apJr`uyL;{{QU5z{JS@ukdr>sp*I z+FmI>GFfSgr-y*V01SlV*C_hV^puI;xs#&4rM(3pqW)lmlf&bX`uDf9vWdzz)%~gK z3F&Jsmxqh}!$Z+;DTmvy(N*FR-z zo`bQWomd&#nEG+D9o)1-PpQY}K@)fV_}K8V1l6%v-+{suw-%|Guke+6(S2+$1!9pT zQZp$PY&Z?vEc{RS{*}=R(mLZb7Sfh*s)qFwP<_c78(Z6M?>DE0ZJO$o zH7`eLH;dPN8Lg}O{$soxlWvevqC%)WYS4IX>%Y|c+3#w!_GqO9Kyg472`|o)3?cgE&eS*XJO zm96AeG9G>55f4*rV6)>mvbq{u(Pd)}3V1!k$WQ2!-T_8N;Mw>&ImE1VvBt>snN(wT znvU$EoPa;0N51bp;y8U-T@_AWir(aK%ezTWeib(@=Iy3lBzqEnT34Ujl!k;suZl=@ zMJlU8b$E^5^UKroL8An7?UgiXS)D8$t;|m3513^LJ%^k}MIQrWx472_%$xW+^qvvN z6&~NOhqe=YD$@qX6TJQn>vJF5+zz^MnJNf4Qj~@^S zocLG#$zYw8IYAj{j$Oc?SVXA@L|1U+#rMG(uZYRzTAt8monD8T0G#OZp}Y~#Glmm= z0e_@-HJ;hWM);9^x&QQ==%aiEgn7W$);dbJHtYsK34sFZ1mqFo1&Lov&cMF{vUNA= zSK9#>Ke=yhAL)B-PyJFYo+EQdo^}8B+)Y6BnM~+0&TJJCx}_Z7xqJE`>oe}8F|dmV zlI)`V4lGdMYozJX*J`B+jY6^tsnJWMOzp`RgB-7=79qPcbp{bR>S_2T*0k=7n`gB?>Ol{%CynPw9yma8|tN;RYH|CVp1zXd6VA5?6-}G^GDWTpYIDJ!P%Nu zo?Oz$K^RtVtx==Qho)xHrdR`hq>HvBc;}_K2eSc^`qc|Vtw^#lgxI-{bOElp8Iu|l z+RFv(U0&FT2W*^l)2qj2N$oTPQ7JisB0B3<>(K|e3(XD_V#Ahwza2pbv;v~o*J_G) zQ2iJV4Nos1u~XH>=5g)q%9ldo@4@DWpw|FN^eTK+1y4VbZU>HS)$)8ZT4%Ykm5_{7 z(M5)#cr!k1C@TBLa5?#Nx#C&jScP=Kfm0xBWMWysaETm4QE$O)@=}P8T*Kn!(AC1( zcH-w>A`5{>D+z8eLOG#G-uv>)(wUZ5F*;5r0KV3eMmW?Lpg7?9#PWt?F*|Q2@c^?+ z$}^k|@t^j;^YDfk^A&ml)k;6R$#udUJkH&sw#d=F!i7d$V} z9z-jYA#1P?XjDK~Hgf85Z8_OibcM3k>ZgZ~8io^P>W{4Ju^gQd18_pdhmHw9t&4Py zm&==09&!a?+6SdP2!6S$SyVcQdBsgP@1EGD0y3OjBboc}4M#*!crfoLvxQqdbSv%eq^^U1NuOP!t88T@4as6c?NW z{wno>R$^D70|?_C@ktYJ7J9%W45m+98QInb(%4Y+G>z^(Ijc}dr1Z3Le55Y%ML#$g zi|CilK{R|Q5;hyF7Ac)@VVU3j*ZH?1aGwoqEy@WLU^MX=%haL~KpG+6@LE9xTyuB5 z$dqyGmdr}h-w2DflfVa7z#j?qNK6~ikNMZ3^z-0E(rFG6tQb#dZ?-et38;4%=01&V zMdvf^1CV*)%rff{VPmc zb)j^HFmRDegZz_Ra^Ow4`obg4m`rP5{Hd*A*~LRLF&}!QP33!qXT9>khs{6R2^(LhoD3CxY zU1i0$Y@bpKeMp|7mkbHouufGN_a>z%h>fc%{xM!bf{n(forTe%26lgbdD-J*#b#KW zr2;z058`lk;X{LG>Efw9S3K>@Ck(Nd{^SN2u70xu6YXujVM%g7S;5K8j4a+?{ds;a z9jgvuO-oZM4FF=qiDUsD2ZmD^Y9tYi0vj*D^Dxs=h8UhzFc+64_23pgkpKA@C~*7n z`X8D^hpqTuNZ3n*V<1`W+IH*J?9n+pz+b9UEPp~H!Y?!uoy;%UNd!W$4(1l=K}pC* zgc2hT()`g>RJChUa@a@)D2ZkM|sDh*vdqeQL;g(eExTm&TrQQA-^lGzoCATqG& ziC?59J%E1b_L?(uK(SRLhG)aO#~Lt+ys)xI7<3mfWbNZwqi~CP6h=`ShPqRZ4YJDk z|2|Ch1qLWy5m1z9xUyZWp*QZ7pS29((qiV0!A(o)$LTHepP9zdoi2>=%+&GRG!hzO z1%>9a3`wsVyB_In!0KJ`d$EouQ2jGK zd`SrFV?wSdNQ)^iEcev@1RZ^5Sd5h$`M2Ceqqa_6id{6Oxf+f$od97FuMYXwv_D{2 zSIH^|POGM8_3=S*2Nv;bWTqI9y?D6ZpA(R0&-56N;*TWmshSL{R>A-fHLBQxDGJ9E z>HD@%49~Vc)t!(d z7?}%eJL8Tf*+Y`7#j|U=H}1btkBc4u%n+^CcPFo>E`D;L@LWc6YGs=wayHOsEPZ-_ zL2a?>BGC$4eBtrjvnWPO1-2M$5xwxicR$P3*tgz4THI9>uLrjyzNI@=yM@SklPVtD$LyCVa7*68+HAFoy7J z&GKL__DJ@V>G|$jYdquBWN@7F9=2$N9d+ZQ#gm4 zq(bvF6uQtflhjiW9MV3vQf~$+W_%3vaus|mR?v)kC!)?XHb5CuOgQiLbMt3!-d4Q* zs_GplZvD=7M(L7jEIa}MAFw+Y?%JhIbfoyYv}X%6-D`U#`G%J{5w@g9AKXV4M!;k{VrBc+1G{ z_W24$o_kazt?g7*j;WcMmeI}np>eJqLrPUcGr$a0j@Gp&08V$;i*gwy%<8ksWh(US z+F4gGBSn@0#hq;NIdaljc(}L8`ECnz+sH7mD@9tZuUF%u!V@MUN-%W^gw&A*5S{hl}h;zK-& zrxD!4k^##rct>4;wD+FS{F^zU0=#T<4b^x!-cj!FA*641@9?Zv zo#)RnIM?srLfk$}#-ZGH*R3;?tWhG`!1#5dJts9F^>`VWr|s6_TY;z1|YWV`qHxQ>~!xNn`Pm)I||Fr%p?W{ zLz9E(Y=6vJs4zFuSt#aU=li_5yWTd9$CEm5y_$&y&95~{j0b>ZyMV5!u>uBQVk*>x zh+Je5ofh#xA;r681eX)nAmD!|#+Cfddblc6&a)h{AV`^v*Ct zBYC5@6+CIf7E1QY>Ez_#{odOei#$B)V5^~@sykGVKOpX98&l-hG9l%0zul5xu3+Fa zMU`fLc7H$qcnD|^Q{?87(6QTBEvcznz6~;ECIH;l`q(3Wy$U_JmJw8K zSrQtKss2NCSB`^V5R<~#*U>Cw6d>|vE6da!2<$jCq0Fq#|DO++!21|ZLFr_EP;f@|!j%e9b8ZU>BlMGh3piI3(o=c*)cKmpE z5j=wo*Gf>aHg{5@LCfh%T9wBs)y&Jn`_fkG|~;Cjf2wVd|kFmsP_8u;!0X-5S7k)rshOPtW|mj`6(pt^BWcxb8)-pY3T92tDtjMd>eTbN zB4%*jv#7T0bSJ#EF^nM$siQ($c@NQ-VfU6tL(VXnc9O>}`8(fLG8Ws*n&|U2)jCQ5 zlF_qG_&n!?szPHG{HpdvzD&7VF4$gck^^)W)GpJEoRQ5$P5H}bS0y0QS%FBShyssi z_X(a+iIEDEaB~=4B&=wSp&g)+X0;y~-AQmYS8WxSJZ6ArngNkof|GzX&+|2_#u7Y> z3)_n;%ANvQ)cVh9&;ZX-lKPk2QW_-&%M|1`_CTmyJkaLH*w45M)q-+#R?bFUI#FO< zx)9_g0QlCwnbYml zaRDaV6-bIB_5$Np43qg@2D=QwV*HpNafVrK)Sj?)oqeqN?|f!z zqk(w|W+ZP@;e`M^fqn-#Cdm^(N@e9UXVJ)@g9rxcpfycr5P5G~{bh8@Ho%r%D$2t42M_vc-LIWss`$GR@-sM}soQiuFS(N6JP6o?`pRzi8( zBLxozbl%4iXSe2$fh;Vb@1S3n8N40R4k5~JZh`t4^Z2~J?FoFTe8(KUI$v&T8S2Uk z%V6**`ztUJOl$rV3vlF#S_XN)YWkgZv&TA`ITO5=(ey2XEwu8~neBr~V}akGrt)!A z%+B-$G=FaCD&%3YIGn^J8@XwWFCQ{O^3BOarGrS(_|UT#>h)>qJe7od1_g4E6A~V` zC@fm+K~l}E6#*c_S;Kt?Fi}z{0AC@qKpF zB-M<1!3l-~8WUZa9)%X&!NW2j@V$s$zS|dhWd$-b`)@DQS)Cu|TwYs3Dg&a1z9860 zch>R0+FiR#oFwX^>=VLj=`n-^)a>KwTkl!fao-nas>@NL775n~+80M-@ zZBD}c^t=6S-tnk~=}HneMV%^?4RG+Y51ogzlds-0tsz8u_*+NNrz2G^#cmc`uT0IZ z-&xwpblx>N0yixc41<^~w+ko(MH`_A0EnFnM8FoV$S+48hQ@{polb90*Mjsd(?8mj zoB6(Ak+tB?_6X&6<*oAFR^!UZ_xgY98J+{P<>UN1w_yQ%@B4A-_no=r&k=YG$jKZ~9&nxbSMeEe)KwW3KeDTWs`$ zpXuJkS!N2LHIf{Tql^Ur+rkYE32rsyR^fF14Jq{_H`eKdaA?3qYYkN?Yi3jS1b)9- zABe7w&9qsK2W_U=DphAQ;)kmlcS^E+$}b1DR`T{-^2YF*(&~`M2Wsx4CsT@7r^35z z;=P4`fAaG_3$|F@iO4EZC54ky=lu)dY#mI$E}EWK0kp9p-axXDH^DwZw+%gN|cinBvpOAQSGRpIX<(h&Xf5spZ~Je8L*^fO(e4gE$(UTh%~E(1Sp)b zS7jy{eBK|TkQkaRRkPlo$%`3kDA|PL>mNQGRvXmf&F3?0x>uL#ix7pc2`)=~Ce^6m z>=(?`84EQUuT}T`_DR>*a;h4`k1?kh3`dPpk zk@q6YahhOY2AR+{)G|k~cI6YIGtI4BSsF>+4tZf#U9iC|bqj*<8J27oA$0m)^#Bd` zv{xXi(~R~|%Z|9b?eE_9AC6A%4Cn` z_AO>%X6!pfw(L8@42ChojD3mhdzPXUB9dKY%@VR?7lpDU*(KQ}-}B-YD9eD8DL-_NoYCt#Uz zEikwYX~TYIO+U4JAX#-2S8lF84l>w2VU#o695(gdVB849lKjlHK5YzZmmIb5a^p-j z)r2|_h^O-!t+}FN{aUASEU>GM1oj2{>7w{f@7hP=XC|apoUFI5fxI&6Eni~>qShCK z1rbvc8k*o$%`+JJBmr|_cdf~;EX|(ov<2?OwK>cF)tMX!W6!Qc+oXQxiwJz@Ql^`e z(NNg6bzQc*$YZvvF10kt6^2pH(A@Knro!?~kB`z01xFcgnZGvQ%@XuUy9IsL-cIAC z8@BQ^q&Kf2aOnxfv)ulh$EoIZ`+^<^me=q+_)IBHHRm(EtPp?H>;+c5xZ|UM-j!}R zJpJfpvc$zSE(lXjlKI`Z#bc|BnSN)+0&YG66W8Hl>K^ONKFM)=WVWyx!eJTmHi|@D zpC5#Bq%+8GzIC;ruli-Ay`hBvjWG-y9#vTPNi=th??UX8xEJ^wT+#yFqTz>A!QxYz z9uFt5UzVuKZ@+Go=$Q3oL&~ z7v)Sd*|Fxmkz32$vXkp4d9#P5I+fbgb2#gvdmh!?*76-{YF7=z#4#E*O+WrPzgwOc zQVu=F4M?8Pu;_sgYUy7}5`aairp+hb?ATn%_rUS0XJ$VH37j8=GnwWT^CSg0?UHWW z25ALpD+k^knL+e8n4X%hGZ;U#>3-i7<8Kje!rqV}U*aCOAf`*{zJq9%G8sfTUYlntMY{{p%szUaNOjX!~pl(&Mn6C!)__Bxe1w7Rjh8xvoQG)azB|S?&dYg?Re7=J+mho9%h2ge) zMiek zJpJw9Rh{5wEQ#hy^F*jAGi5i~#LY5}D)xjSb_b>RkAe0}nX%D%>pRg@)EiHN*Dc~! z3++V$<$90NJP3&YtfG`8EE7zceAVg6=~LLMx3QWer_iy_sw38bBg@Zp>{33LnuT;| z1w@aO-2j!{s!MmCMz%4l=P9})p2i$`26ZDeIx?{yn1C`oVC97rcouwor)%{k;B)xL zHx`kd5D!kSN`~i$F5gpYw@mC_GI;2OC*%}kVb#q{^Az>fW0)r|rfl!iBnx;othF%- zSXezC9yJFx-g&vLTWKP6z_=!W&0@i%!(2NZHKg3p2=|dYW5Sk|47t;;7Q@V4sTSmQ zG=?$+Pk3n39hAw{x>gJub|zFrJ)WIrtelz;?b8XSr4lSZ)04c7muX0^&K6$Fx|#W+ zsJi>@Q}am|Jt0k}{LU`xI$v1?f$FwJVet8_9emZzV%v@d^GZ5Vr{p-nGLEx01%9Op zv)3SQLmr)KxQK$0#e+_KO;;GtesT&vDraAnR{UQ0N%#0%E68Z=n#p!Twz>P2(MW^I z`GH5m#UH`h6&Q~|0p%;rUu^aqgT*Id`A|G&JUOH(v5A8a{irtUtTx z7}@=Hir!&C?3dWcZbH;4 z4PjVowl$Z-GxW8Q3Az9n`DpI6Zs6xmQVZEvmpC4>W{}^=PMy@@&2R~BDJ#{Zrf8&b zK3ICUped!J)R2L+T=R-|mm1xz@Np<)xNt>@RJ5_=A-!ApCq9OQH7%Pb!ZF2m3tns+ znZy~NRUSGGRJDK3Hm+$I)8`nfHXplNs=dTG zTa=XCa0zH~=8N<-e(5(zaFE{YlaHgCVNOLXiwgr<<@W<=8g$d(4QG`EUYAF<jWF{ya@Qe6O6{XGx4jbGJ}0=2_6 zY^Z(jiJF@lf!C5}V~S$t?0Sddg9af+1Fv~1BxWN$XnYkkSp69EGF_idD_+xjr=VAI z0X6}u%fTV;l?sv;Wyw}|$idyb(h1gi`3XnKpU>aMp9`dG5<*nc>Ub_++QPB}`agy` zzgCzKi~zDigk=j@lC|VwRHzKw^JwA@X)#AHDv`Bm7BVyNxD8vr3)HXUjJIDe@7OJ? zRJXqkt&lT`(5$;D+J4N4=f+~m_3Uzn!|j;NnewWuaYIksdgDdAMY}SoVWN`C*P~pbWzy!}O22T#$2?N@&vm$B z#gGuJFA>%*3Aa_4SR!L##70O*>sy+i?HTRrqI}SqmNl+X*_}I<>s85Qz^G+ib)C5nZQvbto>?MsFqB!94OWC9_vuv=KCCm6>*w+mjxhQMXz#sKv@JsjQO? z9aA!_uT(a)_77aGHS>rx&@Kv0tL-ng^Jsi4AJdH_IbK7zwK8tQv@(8?dNWF5e3=e2 z!9<^Tk+d*aGZS_lXwa19|CM4%yVkNoUTB z=1U462zk-|!6g}yXZ*%JeWT2&8=D*HI5^E6W1B5q_^BSqtmSrOE@!vl5aUYRS)T55 z4&JNi2H%I*&bG%cwXeO;8C}7*0ZejwNhgL^`rn*5v=zvat$8XI^VuoS)qCWs>z5lyH(f|;8nL4r3+2TKhoK308tRmGQKsh_Slr+V0Pk$J|P;l6@3S;`@=mbeOK z&!R&(@-?{u-nDJ!F%#ybe!vpIfsrTZZt2)ASv+o5CL|+8JTSuQZWc4=Fr~+x2y)I@ z?mTXX@N|PHxyl=inhlP$eCP)jz8nP{hOuTLg@;RT@-GJ0Cs16JgHz9@rpO9L6qV$p z@J4BGK7tOzF4t}!ogOPC46Aij-{GyTGiy=g&Nv0x6ryUmHR}Dch{i`ob;C_Kw}%K)3WJ2S?KKx&0m0n^8S60_6pZ=B`^HL;&H`A*ea?Lb#MIBP7vzw zqTZ*lQnnYpCu}7=?+(D%FC}q%++C+qYr7eh@=8JSTB>{Fs)6k2;xVm+kdqH~q`(PD zwpODQ9O|X|@i9WCCvMy(Bri3$-MI^)etS}^?-sd97mvDWfdP&4*?0V57F}3n-6&0K zS8o9o`5roSx9_bHD%IK==Qr0^#1*+Q9j(Wuy@BGLFIEnm@{fO7==rhJ5i;~7<(jsx z)J*P&M9jrN@8rS4aKF@4`<~?OR>(DG1@$NFJnE<|J$*NG4sDYx1a}W%%f@;leWg!px4wsUdpVDRV}YP0V5#&@kMLZBZ7-pMt5m zM)Xd5ms_+Wc^x6V0yVmCZ0aJDpKEist#y$*yV^-X%>-|KwYBmp)F93f5TH6$$#z zw(~}+h9&TF^8gDCP z++u(2*>HZB_p+0Y5$M5%JRU8K&B{SJ40+Sqrm?ZF>+BCitwYot~sWL{cZTYJR=gxyg%alNt85R9|o z88)CywIHr=rg^3mMV%Q-e`mUHtjLYf<#|n$UK`hkC&wt58r4MO;3ME z+?R!QR&DXBG$F&FYYMt%Gq;SUqyqe_h8jsfE@fER)K#6nO#&nOIN_=mInyG{@z-2zjshiCe)oRQU}e#B%xU}|&!Oz(7?NsRXZRPv?GSErD5j!%o$XtMUf ze4eIV{*a)3D@z=pwQtHjSvl7@Tgm$fi&JuqW47YC5R8 zps@J-v;zxGxC^yA`FS!^qY1_H=g&WgBVq3#jgE^8=Hki#9zGADH$5VzX(ma*+v0wb z7i}xiKVUOhW(QfkvNL33kT$3KWw?r{1MZVnyZLg{FI{MSnZ}EPf?`-uDQsi+O?{6* z3iD`}Lscny<_*uaJo9RTWwk;QXHAn1o5hKN!qvgDp|{pXm2LNF{e1|hKkoW?Qj9zq zHhVgJKYml<%4QVD*i9)5DwgQhnKw0to|gmL$q4KgL%LgDZ~UX`43WST;YXdX>-~fL z!*P>gA9ouL2T)ea(N)HoGdtt>uNFiFm(1Qcwmx-}V`AQMQe(V};;IW%cw_Uo0cFLnkwwgcQ$h1) zoxEr1HQ90=`JD4%I@mt}cbB0{RB#x>>>>DE@b5)%%N74k3-9C?`X zUVp*O;Q)KUg?EOHwX-~wmK2YJ;P<7S!|3ad+}^oyR8f{pXGp zKSc#;2kSM_pHU+k)z;8>|@~pSQO$Pu9!T%a^2eI+j_i0r9-5uwE6!Dr1^V{qlf4)1&t6 zv!Rg}59>EV^%4rZ8m@V5C7+ZEb61pvzj{XfDlMqa_Ob1!kb6fDpS^y!jGg1&;z?O$ zXdBd;`3~dFBgu& zRZSxqrD<80eE9r|V{@x>ybpsJ8SndB)?RpB>kVJmn(uh=zJ=R1*Tsa$#ggaW0vSa% zemHdaNuQmL9`({4&RH3Ot_Vds;LP&K=E$R?M`i}p-H)Dp;lT znL76&PcgKz8BSk9L1o(8eFG3}88#_w2F)Yfuutreq`%EmtGUZ6GA#-Bxq<8%50w09 znaLHvc)e+yjbu)nG8VaM9WJOsQKN4emEqD>ecF&f7lYCN~i-J$hKlfAN1)Z zVEUkCiTq)hb0EdLn$uTNGhjWk_Lp7-ypmU7)3C`oS@II7V}A+kFm~ptEXn!qBcTt1 zSTk4GgRM92)3>Ef?=Xy~$c~3w0{RDn`Q;9-d>Nvu)N`p_qD*%oe%DV#aCPu%{8Y<{ zkmw}-rdI;gPF_VRiF~%{hc43kf|p>xx66{5gj>UzwYP@Vy((%C(I1EkaV8z9%fzLk zY)wD=ztV!ORj>}*+uT$cw$;2I7i_Xr!?ot0^{YdgrN&^7}VEwLToSK=zk`NSBiYOPc3&Yyc^F<@Dg z@Jj49nm4&ceaR`JM@YXr)dJMcbxV;~p^1#2AdYQKRy zbUx@}M75L7xmg}2KmK{&rnyzH%xfcLX!K@Sw9G4zOQYdie-UrdXX1+YY&9;x+k7${ zqiEK4Cf3K3pN~44%yB#f&sR9PQ~ zvwDwjia$nnuenm3F=OdAUq@@NBgq@>D6zweCvzgF?g(t1;;}m8gfa}-WwIi%e~7#N zqy0krzK8qst;rCPf4VDLBdrifD#l>v6TYJ09lU>cae}v(_Z~7{r zT?yJKyduuY8HYt-T>)am9p@MvUe_66^P6 zd3elKQs#DOx!iG#A_th><`%(L(Uk*|dA09LQ&-nk^XA(`IIs8&>3D6mY~l2f!smIR zf}G6wlGipKy{|3il-Uz|cx?3Q7fK6UR%kw_FwaAUpY*b)58uDcy}P3M==qm1Mtqxn z{la|{gP2BnWaEGF^%twSHm}Jv9Gmn4ieweT3{vJ|qZnL@IZO*o)*ic)W(rM7@>O174f$u2VLl;3S2dx?vB{e<4WFEXTbG&k&ujc*n>ImCbLPP zBQlg!F`C(E{@a!BOStRXUc@*Vx^l~r1UHIW_RAdi zbxJj34`&vMJkDO`J%H!YSgsFaL3$wkh^qf9gf{^3)@x_&$PZK4bhOt1OI~B3lBDTaBcl~9k zKqSj@siQygtbGDtKe#%-Z9^3K-Cv+!FmWRy^k?!5n1o^M1c>@ph{T z62as-xy%r{o%Nwy{T0DOC+`cXvrIZ|16u(#CkwM^h{C zq32-VUIG`8qF8dd;?l> z7J}eJTM}g80if-yfU)N0UrG;SdF&Cuz!!8>1l!&DjRp|1#l}Xyq zpFOwunmn{5kbD{xLLv2p)xwIF^Lhg#NBA3FsRp)t4@uk-!U1AqPgp-~A5f7F?|3&v zKMm<8NcQqECU@1Bl6@}nm=#eWOLnTRsN{RbarcGIe7#gYy=}2pBG;wsh>r*Ld)!CDj`H*wxVwZ&kR(*wQ^Vpp{daCD+x;-f2xxL>1`GHxC8q-zBt5O*3Os$iYNlN>`tCN@C z2nSIw`aad_BULtc&hVXD<2E`Nt<)Oyj`rq)%!n{`ZD~x?X^~m|!8el?x3a2VzGG1a zo8(VxP?)zZ-*N4(yE0Rr>vI)`eIGqo`^8r6LAtgmn*!DH8U04xErFexH^)yIKuBY$ zvecC{a>I$rFmRT$V=k@}^3#OV@I`;$Lm(1?dB5jB0(76w$WJd<;x+m`*c9CpOM(0h zPY8@CmI8w5f=cy>k|~>lT|%*xtF%W0try<~C^T#fyStN+Qj*;Hc<6_>q`P1YGO(XkF=H0QBwKd9PxkB9QLlR&JsYNhlhu-hlnr^ZwCa6i;Dw65Fi91 zL@Xgh@WQ$xJcY0X&TmG3v!jF}*x=F5u4o(MF6cG~%MLY`Oio@&Sa2Oc{JPv_0Kof`!odu|3ZG@$O ze=YJyn_p{d;E-rrFD0VFqYM!f5g{;G2rQ-thDks{5~3g>5L^NT`etNb$-mh6+5^*?>3}>dw}c%=qvhr=k}NUe+3-U=6Bqj-SC*N!-KQ|qA(~Y;&T##h#mOP*dc8s zY;kxegsTj~*%^bjLF^3=&>3kf1^naCzB46ex}se%sNXvHZQ#CklPI3~=iJ}=k3sMG zj|2vRwUg%X6hfkG5pEb)4jG6hg!q#e#-rRJ-w?o{|7_t~?w<{R?bu)YAVut#%{LeL zi-kP^bx|&V&i$ijGAb(PO!O50srf(G{@VOsn)zJCQ$+9Whfv z8;?V}*`V+;@-}#!HNq93geJJ6F&Gpc00s#Y{{Z+5(O4wTgTODfH>`hD_;0%&@eKn zjNX0_%0m7l0W28k9|f>rhTmmBfn9WB(yY3f+^z|EVCUZ-+Duk+TSS^Ejc9%ASz8 zeKG5(!-Zyf7kY|0#+w0``)De1zxauW`HZbg8FvRThZIbIP?&J?@icQ}cf|8}o@8S> ziE}@pclT=h3tL)2qed9%`Afz3HiBEmc8)d#o@$k0UJ2fKTuc9|%A+Yi?E{+3ap&j` zNX(kLk9A>d^vW?=UEbiDZID<|k3^ALTWuL%$DSzJiXn{fqf_70?fNdI^-b_74u%nRP;ssR(LMLk0qUZ?Q2^rFf!^M7x{(gb7HDV+20#E{^FR0o`!Q>45;C3^ zRwyM8W#}b91CEr7^`pGffw*dPJ05@}h;wkxp8-V+-<%W&?l;SpE@ zkzl~#|A{d9zn-tz^AaLqg#3e${By+qzei_jYHJb^BXVds?0<;P4?6N+bHHDe(*Gc( z1hush0l^UBLSPh9L`c*I3=qI|3lW7$oIJsT=9DZ z_>T#=eFgqbpYJ;;p}Tj3c5^1OineGX$*YY=V_l_y|4`x|=gV(S{EHpiA2$5w>3<3| z{&Q|b#G7?+>q{(|c#!2CG-7hFHa5y?QwX%Z;Q43z^^Ol#D(g;@QzPOVsFhJ3@|5a=F9<_;i|Jb$OE#J<0c^E*{V+%)nR pstUQ4-_=yl2WV-kuYXEPFz^!NJ}?T(j_1%B_)k0(%m85AoZPt zdcF60pXYtv?|I?3*|XQ4S+izlt^cgGWI^m98m( zi;G#(#L_^=*2Kj?9{^&O1b{(6P8Jqs82}gzW@Q!zfFT?pFtaoO1Y}kOfY{hrf$Yp; z01W_$1q=bOvaxFM@L(BO>cf~}0si>GLU*z?F#ORIgcA!L_~Qq}2DmN(EPv9#Ku*@* zXkaksZ!`!i`)@Q>Fz|O81U5MM&!75Wbq9tX>BeXsyoK!0z`!U}=>PU8Uo zMq^|9O+FU(f9Qkr_whp5*naDm70muyAFLdZ-(+Tku>Mw$9SHu7A3NyxIb~-9{$7uh zL00iQIDIfevNP|m25G2ITE(8!DBU!P7N2J?>) z6lgU+eZIfgc(bj>vSXuJQqc(0iU01$9{B~=7eRGJZ-=oe_SsDc2=X5=YR3(tpVe8R zK3c((iRu0t`Q~-_P-K$4$9^{@Z&bwz2{%|Y^(zOajGxj_a3j6``&@p;m53m#cggyk zw1m!UlXp+tA43{;O5gTpw;f4i3_9WIf~lU*(N$SXn%!1g;C@5y8v&@!$hZ(ysD9i_ zhdMKj=+-zcyn{&HZYh+Ve4D68ft4-dNZZceV2su|GQh${Kd>$-_@=cX2H^Gn@$Bd}w5 z#&dHaC3Og8{OOx~&6ggK?8d}eq%IP`VBfw&;-hdwdw1l;Wj!?9mIV&n!TLTFA+@&V zHdOp_zuN-auZQhPlM9}Dw(VHndBHIizv>Xr0(J_=poxnvR$5P4GQ^Co&nCI4EMsrM zfauK3|Fx~+ly0{5uKn)2mIU?6 zA!AVDU6b)BrHzso_6fRjCazc~bXe%H?e532{@D0nN&~ayzbXwY=%4gNMFRwY{c6F~ zUEjb-1E%j_*v`trrp5eSkz{pjE$m>LCun7EZ((T%V1e6{zKI^x$`$|u^YHvMG!O@~ zqO-NZwT1msGNwK+M)sNK-ph6IG8Jn&35*3b|)t@&=Ww%{Rbl=QhrR`Q`;j z883tz3qiR4|2Ud!ll-d*0Dq1~0jguEuVbsvEc38}^a;r#>*kL9r9V^gr$rH0#U@1D zBDwL$yH8i$iP6sJm}LQs5D2q80L-lPka5re5tX8E(5!-<+uh*wMhqbmHDN!cf`iib z)+(f_j*eLw0Q%)j{6LEDQb_68n!y1AHZ$-d%vFKf+8e;8LsrMgKniYvN_GY^_E2+} zFJs3ntz%(e2ZtKiCJn=qpL7_7^*cq%0IH*}1J!}!&38D0^Ra_HvVR978EXSeem$s( zl_i|bcSuq+hFP-yHDB1~3gfE)lR*!vXlr0_4Q>%DO&xdIb0H0b9hr2 z(gWnRe)?iR2;W(Y0KQ8s4x2wk6R5et!zMZ&*w3FT!zbp490qn!I{^DNyT4TVrQ(9N zR@MSmP8vWa*zCbBR!&yfUk(TuE+tHb4J@I*=2Fng5(*>O!DM8)76Ue=EZ5xOh3f@@ z7p_MRE5JP~08C?Go6^q$%vbnMxn3MC*iHh4Auj;PECn+Soohj0HDE%(MS#6Fu(Pta z)%&gja4`R|B=9x<%Yc3XJ@M6v1#YfACXe zfj?YJ2z$c{f4Hs>dtiq@d{-k85UdRTUP}bt9CAHMxZc`Y=_wdMHJD+pIkTdH6I6>? z*a<2MGcG7x1W|?SmfyLEDqQz`t*C!nH8|_G1bIRGrM1%a@`{CSu!1i#>WKRX!KEo@Zqch?t; z26Hw6KkLKq@at!r|LOjd51hx(iwy({esCFn%6}~bY^-n^Ty{9$Ki}bWI1PUNd93g2_RlOXC}<={|8+`3U1)Ajpr9su)x{>Z@IzYn)#oY{lY&5sDTA+ z2Y|qK8rUe|82;Ntnd=zA@Rtn?VrPTxmv(w^oM8mR0#bMj0XPsag4lsD63lo(K$suS z$p*s%{_hppVSA>ajEu3SN50BvE6aN)wb>eEAN!8irUJ|IUU$FNnp3BiTY0y|5)C5S z5^&lNe&PYTyd+84;A$I5v}}5ShbAiWjlTda4Vk=Q;mxn;tG!1*lXE-Yr%kn8g~74* znH_E^$K&Lnrm5$XiK;Sf<)6zdOl+0%%`@+Kuh3cmzjpg-bk%xy+_e0(8F^95X=H`@ z<}oMMYwgvTwug?Qed)GM3d93J#3uG}(V{gG0s1ZU{t?>M;Q7#(m4!5c zqO-oyyE5cnmC{SR>ZM=i>eOhC6{5QJ2W_({n6`xa2dpRLv!LW|QIMekx}}!$<_omB zlbX>olX%Va*8k~_DCH?I7vNuV2^oA1Kay^ppasAC7O&OWEO>naJG8AnsJ9~W(RgqNgU?g^}V|Ec)#u;>;V_-&HO<7wBQar8^ z7_D9)jpFWTpgmQ1doRL5S9a$Cam?*!Tb%|ro}-(*e#HeL;z&$I!OlWqnLOu*j=4=_j%T%Orlq+(n7_})_EX##cz*CpbKA?j)KeV3;4MDcQ}qMa#e@}i zqem#Y=4d$z!h?=%V8hv$Ej3gsD(TF4cUQGOCW*Tnv_F$9683QVs;T)!onY2+TD;t6 zn;aw*f`=j(CO({+UGd4|e4||arH(?DyB84_D7&qR*- zF=CX2p-#O+@^hvHr_^jW2kP@cLVzuRjw?BC%Hh2cGSa>vPdUGJ&LBiYRf^ zFJ5X?JRyTDR>;+RG{Vf!8zf}>M$7`Wov*yiVz>qBZX~^kcqsodZ4T=ZrkA+BY8Za@ zyJN}+&sA(H+SIHTiU&X3;a}dx*>!p{QU$Ha2WKd2>hT40eVEiJeJ3qQCvv#SJg5Wdti%z#WjH&YttJY8EDpz=oCvgQ%bXZ|u3 z=y~WnoiDHcS;c<2oj+9!2>G`v_K-;IYRMsI3Ipx^ixO0`4_uld;1PuP)wJFQ12ztC z`)1xhvHr}V#ys5G75%}^SZi%C1i>3`>RH39s}G{vPNg=d_O~pS&O^)!(;4eun-Qu` zXEpjOfT1U$Sj$3<1ong$48VYw4rF>sCHO-#_FGF?$VnePKk#Murv|8V%GHL*DtzI! zXXs@n>yZkpXOkj+sDEdUKOBv$ubEA-}ZLt*A1xdm1D;As)ZCHo_AbmO_?EE&R#g~ikNUz6T>~Iar^Ozo>E1D z_{997)Y5I~A;!&KZ(k0_1cH)$?UGN?nns5OPjW-#*YwX{kkXR}J-|q)B5OO+35hH2 zCXLp$RWcNHE=xNTwCTR<704-f8q8?7=-09Y#uD%LrST~M(HcD>gDPd)ES~d9yg-~^~jhb|W@ymmM1|1oTfVt;?*~bN7zi%)v@KJGgwP(>W0|u?c_PWvjK&MRbGfMMVZik zlKad*WswEM^_0D*2B3&{v0%%l_ATC65p$YBhdgnfdf&K)c*5-KMdgz?L4@{T#foptLqpCQ3XiR>11zcY-c8T2lelH3

w4we>(OoEF2jd_c1{UQ6XxEno>k+?wO$c zTLS(I(?|*Lvy@*mw_M0mPcQCJ<`IhGqpE->1`m{LU3Zv65{dmUzE$?&>{hi!10tho z)klcE5yx#8Zu%EHj3!n(;U#lC-kWHeoT)Pte#Hf$P&G-ire%d4iTp!K=Q`B;TW|&Q zV*kNWga>xlJ~#8P_==75&+!%f?XTaTkq|6q{V(FFpE1dIF5kKTjCFp-Qh&rlusGyr z90k0Nh~QDl|2B&HHG=P@ACV1!6?WG0vkaH&k1{-hgkA9W@H@P}pYML&ucI&c`=9M# zWw@l*JmGv{*Z=Mp-qZi41?)WLPq7)lt%;7gl;VFAlwGF<{-!vvs2unsWYO2Lgu>$$ zctV8rd(!4lp`oaltmsn)1!oIgE4UbnR!Wv8@C-9t$G*q#e?)}h`mp%N1nSHv_A7e% zOKkEZIE2L}e`E*#wE}<75dBi%za|L&^@RRZ!=DQZzyG!NFLnP;|4#${?}`8qrTsY-w{`ft z*Y<8Kvf9In(S=i7pstn=S1ba=bzl63|Z%O)z zi|(zErQxrr(($b)AA==p*|#r?Sipv@H+a82Sg)Z!z!z(H*+52)XDTkm^w7v|l%E*`Fl#B~wX4 zbNcDm^!sCcQNARO2k;eFhH~}BJ8`SrPn)g4$qkNqGx3jZ0_0Z)=tl;N~CaJkIhVt|OybK9;2O*?ip6Pn_CA2#R!E)2187gD!S$ zyh-FoE7~pz=pMRl>t7vY+llixnxC#WQNJoNVX+*|V|nV7B3)VCub}_>ORJ+VCGx2| zTK!ZH)LcxSEIXazXlYPvHqvnpN3!b-SfIm2x2gUUlpj}@&c_*iTIL;{l zIRkj#R{LuINGzCrO`x=sBK&E4zNqv)irjn1gxLa;1v269!#OF{W1MAPm8l~MhIiyn zIA$bZO%7Y8OuEXa1ajCRBrZ$t`Q)xpolHVNb{L^~@3-H)a_?f+4n=hs?cNKP%>QgO zqLRlr>k@chB(O$m5Rq6Rx6Qy*2KqC{+)Z@he>YV^ja<;~L2KW2Rr;80atI1bpvhVP*ZpnZEW|VEZkI6ZC&} zT=R!t_ow3;_+AbBD2MfrGnGHP1#rje=Q#|#G2C;9Z~E{?e|Hgn*83wqfjJ5A^54&E zehLA>1!p>P?-_&ukz&z0aVaO^dl7TR1Ab>(8^1R6%V7?;J4RMtl9vcNs1S-9aDYhBzn%c$NG z^ZV*5xWF# zT_Hl}?$QP?_$l($YY+|l^j1%7UQEXtC;^&~M-4Lw%-f6(n_WJ(_cwHBw2n%NC!@tl zHq5eRB3d8v(dChQ%bW3ktOyOlYwV9BmU}W^eA_s)S{3|!J&r_u?`Ba$AI&?)vIW7_ zJhN4(cE#=SS048MW1oS%G={spE1Y;))TU(?$c82I5%(2(hB+u&y3Vr5p3MhQ--M9( zxNKPnJ&>%#>OyTmn>(1snY`;fUqC;%nF(!j9UEq}&7!5D5`;EcO(_IkF+VS=v@ulL zenwTIxoX~4(N}H$=Af;7ANMdq#)^G0SG%2-Ge#iwLGi?+cV)n1Ws>z_%jObYS8Knj z{&6YK+|qMtbfkd#N5pphW{T+NJ!-_YHs%6M$;D20p38EkZf=dDCOF4@TdX;Tiod?n zeO4@JrHdFt!CBO}r}5e2&PS4?91XYaw4?U1B$re=c!Bs#&xv|R<2_Y_J#~f`_lt4Dti*fCpLBbX9cz^D$qU`ffO`s95cORsw(Hb1Y!diWNP1z`#SQaO{{F zzyUOha-rHRAYC)alAB0URJRM8Q~Tx_@nNU8UUL$W<50{rT$-Gmv;OT`{`@(zGr=tu zf3n-Y`8V^!u{GJky6dWqvTxr&AkYNX_Lq9`j31D=(e;-&4XH{@7DkuW>NHs8Y78+Q zXQoA{W~ce6nGLb6nY?Yaux|4AA!1rK*^dJyu8`)deat9XVVL9?8(R*`iG|P~g4#G3zvJz3o1{-h0pM;}klhW}&J0z0LSH>MA@wqSVJ-e5D2K z&Rzz)I464qhs-4^h+%d^zVS5*}n(ZVu3BD2VF~?v{?(ef>Nz zVlMR|M$%=-H1?6ilFS|c@?6Iu%JFY06Qc$PVq;nkG1C(b3Wi#u)7w+B6>}%b?G?*! zc;zpiD#-7lr|&O2(Q7I3y)R~P2t0Kntiv6;^X2G-H>{>G(&CHfdD}+pno|rn8M=nL zjWh#)MNMJ@ z1=&}jD^vTFAQG%~1DlU5Ly}YY8|7<@vUG2rnh&Rx|LiUON`d?^9X8l^nE%cPx4iqQ z+FwMcyAr&f%CY9ATZLTOK5(+RjwWgkP-FMtuAvR#t(n`q|5dQph3S4NyB=ZzYT`|3 z0z-wT_L}Q@^(+|jK;w*AorO|n@39!&gX*v08MBH<4QCzxLrt#Fyqf$Z#2j9WpvK-j zN=?5RClM~Lr$O>c8ll$xP7aVWvvZL&0!IOHeFcQfK7A2%K7frI6DY@~Fj@&Wf+|1mZ$67*@@C z)MyZ>t;Va@#nNNks<*r1h?;b1_Uf@nc4FedJaQ-^n-axDIl)~7ZUsQBDX#WZgSXVo zjigc(d3=*3(sBH6**FqNF8JtJ1cC^IPu|^Zd+j}VW2p;;8K6hb^yMn= z4s3j4r1^)9+tJ%k~OnyM$Ao5&iE5&xDb*A=K{H&syGS=JLR)q{D9^x!jeU9c*>~&jLW0Hkp zWfa5fw}!D)z@(C*H>zkl6az$-S*#<3w*^eFuvZjO=4`-jpqr|w z+oB83_I&O$M-naRDgu;570HL~ARCO%e&)G)G-}DmO;Wmf9eAD#Zyk%zR74_izwNku zGrrYmoG}vbFx}<6i0fhdOgKCi;U+Jh85K7QCUVm2(0iXdBRv06v9G_+`HL$BW@U$^ zdwz>9VgGf(!psl<*4PsESr{yf%bj4v2dFdY136p>@(k;Q=MV8Ib zO04qmQQM1dZ>-*31~HNSsPPtr!EK+ z0KY--)Qx9cz8#zyx$5XNJn6zh~x;oI>W7F5_Koco#FF*>(N+7 ze@lzPXM;IV`2ujIax8xQh{6LW4MH;aDrIUrCX{=c{~oO~cLRyV-A4;8-)b9$IRs|6 zF~wz&(ndq++K?-$=dVsLUu$p`9O2I1~9gIoPCOHYGL9)F6L5>TvP{;af;&7uEt z(LJ|cI5CR9+V}%Uj{h1n&E3cW+m;FcpwXs-oy_V1>0<+*kuF-wp?8C}F5WSKG0pkN zPu<*_e|IN7iwR$-4q;nQMdEl^)4wnur6CE|K3W0dU+ysap2Qbm!wBZ zAj;;)ZHEYz_aFB+Nry{L3%T82SdKYhKPXVAs;=Ifxu{Cg?JvdvMj|QM6^x zu-Ks&0IYk;m$Zg(yjOR|AFatBctb`GuaWh3{s-HW=M;rnncP|mNjLe7Qsmw2jO8F-U6xmtLUA>@*`?8@ zYpt@01SOy4C3*zXD1F@*z{HSW!owFj@nD7ex)%44Jg#ma!KdK$spPdrQyDo zys#`OO(3s1kK@!gkc0ly`{4r03lume-3_-Zn6rgnu=Ex@|H!=;hZ7(&!;*;-@1;qN z-j-yx#)ea3=cOKwg_eLd^L3oa2V{3877S2u2z?a;5gN1>QW@}X+I+}&+I7{R=irln zmh>6bDdw&>R#7FTE#k?IP=$VOL=Ic^ew3wqTxO)E6~@ydk87d|2E($mjpnhGoy0Rj zois&;YfuLq6hfW*Jqd)4Uq_+v->cMJCQRX-oOPGTC*SFN#9wwIixBZLZzF0+KfO;Q zKxP6Ur*=*oM!|8DM*KC2#|dzKrV!=c&ZsXad|#fj9}!pSePpHQxrvk8bHZ`n5YyKl zSTa$0p~>=xZb_YG^uGChhL!i-K*ypDVO;~T?I3Upo%(RMfG=xjgYYwP3Q^L&#Y%I3 z3kf<}%+<{PNdA3yT3G@^un#BJZIm=zWFjhHI?c0pPaokeL!6Eda+BIFWJ;~e%ni&a zu^8_4-pa~hT2bP13M3Rhn!Y8L7aT`MkB1Jl(8hf%LvK(v?2|dY}AjW{Z1 z%H{<#@8GwOf2u|!(bzpS?wpfqTk^*0Q#L_53PU1VHFx%|qn;eCOn(VLP(uuMS6{fx zr5i?A9d+bSu;8oRRbSY4e_Egezt+A%3u>x47=`cIZe+0K0swzH&p!i?Vf73W7Q0@q z&c{h08ecCCK&;GyJ6PT{{ZXBBBfr6fZ>SCC#k$If=|KL+``Z4&h3NV0>`g|lj+0A61TPy{27*0Wi4?r% zS@^`P=r`Ll-!%@$s05VX9Cpt>SdPB`skhFF14$%PMxsu1LX0| zh|RXu-kpae<5NtQpOufh#w?4MZbf~LP@8~4h!y^XUUiuex>p&8@mofwcyB%>^A7?q{BE9W`oujswMFR2B%NI@ zH7mWT@cu@R(AKU|*XP#N1qF%hGlBz9FCBSkF`;g?n^S~fbD*lu3pxO$BW?IZgS7*YP?=a791*ru*^T_)~9Xz<4 z)cHA^ohGBvZ4u{SaXEr=CF&NTj)Y<5Tk#Pif+tP!9GHnW)<>d01_cq@-*_?lo{c7> zS%GVsC*>$#`K>Mv#YXKn=VGU*Ft1zrOqWM1&L{>`_>U_9+D8>V+c9EeWvp}A&n`TA z)W1cY*GVO;Fc%ow5v>bZNzR&W+&+K5TBJg$6s~owJPxeGN?BV=u=aR>l&q&~O(Yjm zYTjXt&UypXu_yNahSqcynR@Y4%Gm+e#rkBHeUyK8H-CL%{WAr@0r^LF^ZRSqzbC;? zm;UsxYsk8zyxe(oo1d^rF>D8{<|>${5-%b!Qnvh7AW-7;i;|8>9CsmA+@cJg-E)1_ z^vgPA@D1LW-NaBA%uIr*yeSo9nt+N<0<#VT|veLWkV6KPFxNBxn zLEQR^3RjSJOk#IAG&xo08WX=^+JQTl*$0CJRMR>+Yb9S@;>XM8@Nu%#vxH19@sv39 zKTOW13_@7hXkLg@cr6j$SHve5hUDaBO15{ZSW3_}r)6cGVMinPg1GcY{B>QR1(rf3 z1ziOuMQ4;K60$oTY0kF)dF4}*cD(#7 zj3=t2+YTxeA``aWBM-N0$DYc?Mjvj*%~Z|}794fIen2Vn8WAwI#j+vZk{!Js!I9@A zG2a*FOe4y2Pc(2imQlAmWni^Ybgu40(t}0bc@;vwsr{|BvMxSi{>`9TUuVVp6&2!) z2|5wBKvs|5T|dB+7j4#*+jc6-IzbG=ncA5an?&-1n5~n{G9+%&Wasx@pin`qnl4A@ zX$QSNVR_%%Rewy;aKn#gEFs-K+rURKOu2{skhcE0kKT*dU@wW4f){h$>6*fkpISbb z4E9qqk(N+fg!B|vY`SJRJg{i1SZcP!w?$yx zHLE7=Tu&l<#_3?wrW!9L&b#I7bPhKC`jp&h1Gnjo=6z`3zwWV zW0FilnXMA;pGtX?7s~Z;25jayJ!zj-;W{YLvm`i6mum}u1g;=*w()<^*yRs?GCSxp z|KQ*)Rt()84w9F)759CPvF`Mq;3r12SR)5jEdjrjt!>%#dbhAOCRBYGmtSHQiM!XJ);$;$pXy!JL4VZ9sVKBi;b2q>^>vO8r%{!SUo;k!p z$FF7kA0fqidSeYQ9^UZ*SiLT$BwNPs>T9-Ke+-;!?f$GyL9P?&j@u+n?Sysoh10;{ zp*b_ky^qVq3gnp26&+?@hl6FJQZU;Q=0#m3vV^7~VGQMO)&vXGqHTAL1IOpM-1Xx=|XQ{5RI4@9Id&oNIY6weUN0^{1kfSkDc*kESGXS&0_>UcpCg>qe?8zHUIAXTAE4QIqT+?{V`o=tRo=jAc5+9=pQ4UO zc^fru@4b5ZVU&Kh?yw^nbp)7dndbA&RZOO`^7$8f&@Anrb0_RTkQ=g=F+Q3H|2$~3kN`8(DO`FqH# zbyW-Vg?Mk!=n{v7=00ST&-NOmj^J1m>&X=ieKbkBDP(zv#MIydPrp{Uf7D<{kb#OQ zah$Jx>$*jNF&+gqrUBMjSTM#U!b*+XVR*+LYCVa@i#moU z+Ry>R#{N)NSlKcqJlSI6ZtX@ySj?$I{>n4r zkq%k_z(uue zR-l0(BgFoht_4g(NVtIGuNvZC5t~9ixp3P2k^3O8RaHY)qvKxZJAVb;^xd^wj_Hrc zuB_B;tL)7j+*cp&hgfr>)rMN6w!?bm z3(<3WKZPV*vKk=jy9!Wy**XFwC#CA4n0vX|!DylFfJ>I-T>s?JLwZ5!wDGPptaoab zltgvN=)u{FBDbky092>9geQ&8jqp??6-j!>a#oS)OwqHFGKJ6uOg%9`MiaL(G&PD- zb#)t&iV@uPwqgIIcy+HE{lVrkMb>6_jL4|;eMz9`>*F1)9Y2kA&rMUW)OXxr#8t9j zLLm?3da04e$`4yv%U0fWP^;~$aYRtK4G?9{I25KG|@`bi=f^YJ>0^Z zPq4cfI`!g)O7e4NZpO$rm4=;;U(U!hRa25?dcWm5*?W@qxXHjTApXrsA(L)J$1M!b z&c`|_O7tj4DIfW+mZn!$GM7Wu3T}}q(eulg=2_aOFaESe*de8&Z?aK?Px z|B(*kNOk-Lfs;e1WND4ITx!R;MTgo7ok=Xze)jF_l@T-*Ok|U13|?!u?{^ShCf^e~ z+!R#iwtO_kOKC2sNQFkRA#dSM)??L6?dvgdxp0J%7As(#z$tJUTduY)bZ-cj=3k{S z)7Kbw164*YTtPMM8>SFiGd7)OI9}6hipqbCXMr~B0f#&+%N1rZnb&+ATGSlZ3TNZN zl^@E<V?Ld+h3g)0b`k-TBTNh1^)G z`K#1psKGjq?rYop?pQ(oZJAj`xH(f_)mYobYLn zzLk#nomFvirR5F|n~Uv`*69Xy`lDw3sD~eXdM1GCd2ghluRtEHCMsgGZcg!95 z^YFGTd($vV{PQl}%+XLLdlJSPcOn{;ao@D{Ou`3i2Hr$HBQ_Ncl!nF;?8;1^JTmI5 zEM0mw6lC-&A#Zk6mx9b*t&q6xO%u^`9c&H@(F?tgz}e~j{0=goPBNvsp)+$iRqc=E z;glKUxMW|fM8XQ9=04vz*ati<^Os{-Eh)`OP*TS))%Ie4!g?-zF7rUZ#BV28bIxr|1QOu7YpYMFycZ#Ha@NOb zq|l>AZf-)R?F38~lp|K8ah|S$?9>zwNeG|P4_6tdyM`4uI~=WF)O)2WqNh+7Pu(d% zPHIrKne_Rr5|tCL-M6%h`O5uy@B(p>~3 zzY7@~#1aII6=`T^UcfeKG*7(hYx{a&-Q=8?T7qsCH;uBMMabda*Jhr{!Vqog2%3XB z=9HOx?H#X>VuCfNQj^)dMlJb74^LjVvUj`dbYf$?@GZJ^dD~?MWVvJ-eQ2*FeTJ^f zt*D;663aNq$QG`9*E%4qgR>b!Y4wVBzG$O9=>7sknkK(Q^Hy^bl8Q^WT*C4UGxcT~ zhcv31U54yqDZUk#WViB*FVWM*4bNCWM(QWzl1DeqL(jaLn#Cf?X9o8v+qkgU+hwww z`g7HuPY?vfq3s)zte${Mg=g`rCDJj?@<)>hLhvn;;hXzYGzbFvOcMBV#5cvIbHo4 zCU{hZJ8d5V2vDD0@h~VyroUa=6p>mwv91F02MR!$uy6JDG~$XnBl(I}l3QKIv+x%j z*b?~B8#TvSsTjo%eWa)vW=&49uG&ElkqfHTM**)_#|?f+t7nr*tgTg4&mJ6EKc^Pp zx9v#ua$BPjM7$Rcx)XKOJq)R|<-#h>{6MN`P2++;jUV^z!p>s6m4odq$qYe7RgwuE z!!7i@0-->QT5P0mu}uXAF5xECXtEqx_dq2r5OU#1jVQwO0z@9EsaQc1rSVx;153NO zw{D-$sdCi2Zwiw)ZzJd3t3u{p((pu@7$)m#oLRzePg8y-oGjC;^~zWiC*x^9mbiYq zvP!D`?JgyT2T|zp91dmm=Rz+R^-czftuNQf%3lp&>~@mp=O83DnV%iD?mWiIm5pO3 zXVjE;4naFzS@`&&!)Fy-;pnD+de?)7`SJ6XqR+f?NEJe%i|al9T_ybq6;#YO74Bb9 z#xpHGq*M@++4gdHoi#+}v}a?4(JOm5pY(%VW^5Zbo;MDt-Y=2Q$gCu_eWddFp0 z6cQwg?BO(X&{OCjduz$!cwnUU^QS_?By2zBfk;iR+xB8gQQMw@J@tNC6;SVRlcKPv zM&(>c=xxK!`J1y``<)sZ z0HrVLZy(kef8~u<^-?$PRB6NWBMZ*G@q*cOdvqwv1wALdSc6}gdu^ff6x|{7WR=LQ zS-w_wflmV4USB9)xYA(mh31nPMk4v0heihrn*qaMfW^GPS@5w0)b@P009B?Dx&FZ_ zG_Wv=CG9i2eyGi^ewDI5E2nSXGFxtnes2K|1LLWTZ~dM8R@1f!DQ6}C!kI`1sQT-N zDRbV*H+0xuiLqVC>%+FlwlAiu037Tg`O!_EXuZqDTdm3IZC~0^@h_9S$`8KXgpeMA z+=M!fx+$hu@@);%RE${k{s_A&m-)Vj?Z^XbYg8$&BfdCCPx~2ax-)3swtSAd%x(FA z8;_bLW%zGjUP?#p`)f%}q00^v?<1z4Oe{Tar)X6xO>bLQv)Q&0(MM2hf2MX9NnIe4 z*@LmjiMe%T0(ktiTaRyx2(9BGa;}fs`n@4$mAqBT*L=k5}-63Oy@ zB&)NFYKejcMkR>$htDC7lr8SEIM&VCH%JVR?!H-~)^$~}E_xkFapQJ^t;Wb_g_t!9 zvi`)8{jNt(%$0|{yz|s3PmrZLwEc~?<^~8p<=2y~D)r5z=Mz34-b)_Ik=>46%Y8yy z_h>f(zv!d?2xyZug!2G-PxUtx%R(x zya}p|!DU2!p26A{hf=Eh`Bk2*LS;DxQ483je+1n@R0xfFftqKjkjM0GY^9#EiS?XC z5IVtRE1Iw3Y)`}!O7#TVTjyCXmTalxZd@^Od2{v)xgkr#z8nqsgq^m?FCgn3Ec?Fux=;aJ=U$a!#7oR~^qS17Sre zk833saG4-}ullATONQ3vwGcgN6Ia($>+*V)R}vf5#+pxA_cB|-FWj*dEBMYL6)ink z&u|-ii+$RtjL-=UdM{{RADk~vXmvr)nLmPs5wX=Ix+O!^+A1A&U6@S}xlg|E2q$fs zbQ1eXh6>1Yxo&KUak-=rxHlLGU*ru{?=PI>TCeS@^UViRYTdwX!LMFGy|GbMnI64C zpo6u( z*4uO}(VUjdu>tkBQ>nM0x5n~TKFf+VBKWV;Yd&K%HeW^m;>~em<1h};NaWbUcdO~6^@Hg0CGUV)e8uCAaQ#VEt2|77 zTBCeAo-ilr1kl-T6#vq|=S64R(^D>v`hq&1=S|i1GXWs3!&CRALo`+lGnMDEpXvG{ z!qkSMj9+rvRr7K}kHQ`Wyd@`66z~XVi!R}MxTXgT2{3dTQ>>?;@hj+GCx>{OPG3Fz z1WaR{duV@H-s}4O9hN8F-p2c+jfQMmKC!PIUfnCvee}svpB6JEcAKJ>g@aAQPdds{ zE26j6f>zvuhq^^}F`l2Dt@`4ni46PfW-10i$u`?`ZK=9;>hn?@(yrvgw7W)-%cByV zNImJ_eKi8bulFS@dfJv}x|0oR*gj^p5q^u&AB4c~j&zaxYL9W&h-=H|O>$R|qjrdB zR>ZS;VY+DauW@=F5S%L-dZF>OxKOa*GL3 z(e?{jof(Mx#n#oPs!pfZn|4i*eeW02A=ZSfZ!eI6y|O5RVE}u9iw&=%8?(HR6~-Q4 z@Cb@`k2SL|v2QG-YXJBvnw>axuJ9`F_-O@8>suA6K-j_%lq;z2jy{|ZxZ@o3D*d8^ z{T50eCwcyKypNjf;U?35{ztkNAEb<8-I`CFEhL9Q*_1SNk;Q>;tLeBH2dFg^UxX)m zv|e@UtRuWD*b%Y+z*1HDRdf~OcKCgt#hyveVB(Rn9Bxz>vkWc1LdBw|4sN2+AAxgj zRz(;ZD|JnC=b?$&XUmGsCxD$}2J~l;rTYgCxEW?|kQ=;5!V3lvs1qzH+lg*4)hz%!+RJF<_CynV-Iq@tCq36ab!d*$g? zmv=Kl-GX=&51TB%EPew z=wA?3K7Lih6Ou6|N>@_Vg22uu&I=r-VOpu170>1;ZBn$Y(NY+wx}%D{LUn>o z#P+)}Fp8f>NMVdI;B9ryJ00xJlm_e=w?P!w?X={k#|Mh6!hGf0w`4wIrn?(!m=&3V zQ010K;$B@Cia<`Ba_$yhMLbh{&qu98w_*Ox>r&lsFJC?k)3_O(G*2F#_Umz_ozT;c z;SW7zOx1$wYz{$J$TF@>cWNENXrEm@eLL3(l`hlrjMEaoSTDkjOIT$i*Yg&pcrsP@ zk!?4xB>HxW!b^`A??ObUjo2s8Ltkv>n0z_*)C$raB9AQz~AoU8(%7^GN&9PI-4#>7TqS1PcQchp~VgZHt&Q@2L~SN zBo#MsksQ_Pn3b$}5-&KKZPzNzp)y{t=fNhU;14sk8Gl*9XV1-k8CO|WBl0tsr$^&Z zh&65PRs_S6B#b&7Rs~tY4l^k(WyegMR^y_dRIN~v{LBNiL%`$j@fJ@-6zAev$fmIj zt1k?p3fkC0+ORIv{D?>v@T36*I?br0z&9Pox2Drf;E!Zp9<}R1_jp^O{9I9TBx-h{ zAXknX3gt0W^z!grZtTDbPA_1cr;h0~8~17B6R)$DRy;3zQY8u$V0n0*p59~kQWFYj zEk^ouu0r#b{BYSvfX1BVsQM*F&m<;%JuaZ!;m)o-Fz7CmJX7U@_P$_zTlH`N5ZY|u zxUctAz(Sz&Ypq<8TDpazRnk^@Q};tXeL}mlNu!D%-U?SgKzw-Ynq?Hup1!%h`}&pb znsRh&qx*A*>TRqpPm2D6KZ@u@4T`<=-|f8@pU@3b~jz}Iex&z3f%Hdy&bmn>(#|tb}*I>nN5}^PF>8D zAu7=TBz53;DvBEy$7;H;85DS@-B|oy;G@zrVw@A^Q;v*oku0q%r^VUEtv2w?4ul{DoPSns}wT0vr-d zGkp2nhFVidT84?3)W_U)mwbCRh6Da8jetH;u(>O(^5F%DCMAUpqelYfo>{`OcxT*SBo&~W&ow|&DaDnjiW#59XSigt{`u(p@7~ORWsCjIY5&v8_z!m8_o__&pI#z{-{$+DSN#`< zFa5t7eBbe~@7Mf-k^Lf(y&HRJ-s!sU?nQ?GwCMc&@UQk>7kUrrhu42~Yrey_f71MZ z4)wor^#0-q{ADis3k1j~!vFKzKZkku>HOO`Kd1BC?*9i#_qV&Dq(-Oq4wBQLVN_$F zXVzd~R(pq0YA{o)F)%S|yw69Cj)g_zy-q-lo`FR}gX&#O&e`_eD*YFvQ_kMX%1qz< zC!dX)O3Fr0-{u`@_t_*WxSc0$9skQaqS4U!9ii&{StrW3M*5vaNyV@9%i#HE+W%@k z`i1TMJ-uHJte+G3MN)j{q}u;vO;Z0dzyJL6FXP<5efN*)?k_Us`&B>fow8Jt@7%@r z?=b%U^nRc1K960_sM|;^e)$WNIhyAq0+Hmx{vP2UE7UKI-_iVi#9w2$9WXQ;U9hWv zLFNZ|N1*=XA-_xh#ftnz-}LNo*sW{Vx0eLt`}dLm@Erd>@?Ww3p5M>+&F-_D4Z1w! z!jzQ{HtrvppMH~f-sLYDX?%5cP>i@>!YNJ0v=Gkkk30Xa_Q#v@->Ll$`j^^{hgFyT zAD!Q5%HKM-WJIyVM#jYg_Zz4w*OT5Nl*XJ*&Os{ zZS3zfa}kKA+9VqPo5-*0{`rag$9?-9?=KN~+o2jNr#~XUf7!pMB1D>TX-ky?f4-b6 zzKwZ@*L~yHrlY|B?`nSvYW|xk{T25wHE@E>Yy@@nKXSi+dB5dAJ*w{IFqx7fKDawi zbvK#BEiemGzhCRuu5R=1ntxWZ|4#GwG=6Co4-@cd@cohe&4>6cnG@rJgNZHwwD}gT zHz`j0?<#+6-v6D-@92N2$Vvo_%NPDp`TaBcts;Xlk%9AYh-o>?_U{TmFZv&!6yxt; ze<}2ai)ifT|EI!Vzxm$^-9zx;9Y0F^ZJG+w|3%`@v-y95)bgtRni%dcaok@sdFSo^ z0{s5I6Yu!m_r>Nn$rkswJNJK$q5Zv7{aVof#-RProAF-h|Lawkey2nKeC1`|7g*eP zr2Bh2hIfi6&HH~H^6!htZ^rLCC;2xm_s^pA=ebG#4*s3I{2v3oH(0=Bd}q@9^vV9$ zk$z44&p>}3&wE6F59#MfKV$ki5+gnR-(&h2_@9yfEW7`AaNU1l=u!#3Q)&MS{{Mug z`}3Lpelh>5lA~i_`6tx-AM-`#p?8shT(0g-q(s*i$kuLvvp4k42fkUm4on~YXY<{j zunr85izH(q3XU6U!^RgB5tLN1l*29sS~#7#tAJAZ&t!e`@oY5^r}jaf$^3)nR@d{A z3hOF`um&`vW`;pweY7W9*adTNfhC1&Z8kdK{B#qAb`LR|&h<{>W0VXo+ceUfLyu~tzPNw~D!6y1c>%gt-1XhhelB$%;L;%zymHuxqy`2bKi=t+ZCx9Qw zV{UtRE9u>mG`rRVVfN{1E=kX;u6$@h+cJl;r%B$6SAO{>^b5wJFJ_T`AzC;YQxWRkIq;boH zCM;x?l{;OZ5|EZh70sJM<(C>gC$h=$eY*=PzISP^k3iu;WpIc&2_&-Y2W|5Y*B`!# zYnv@2Z$Bi5cdqM=M?E2=3NV8fqL;hGMXIg<9C@K7(;tPaz%aCleDzS4MEpKM9Z61F;v75Q%n= z(X2P{430jJ=-^t5>TmMAm9h{#lOZ8KB=)_+xOyG&OKZ}w0S^Jl(0$)Y`s{)vaj1Mm zRReZEXivV(Vk-INxRVEG!|EIld&W#}$Ye~U?r95RoPdU?iD_^}J-klpZdi)ObEgcz zEb2!vUt$m_{k4HS6`rVD7#z_|-u4%I#4D42IOem-p0_vLXOyH;hf9HrD!oa#@lg+KYa(DX zlN^F#Yy^Wev#m6fF;=gTr0El2eTHK2jcvK9uB;@k;cgwM&JB>vuu_~7)0D1yeQ z?6YU@z}X#?$EzFSsvwJ}5!X3n7k+ddj=fnP z10G6vl&O=xXK8e7pr{bR8PSA`#HE#c8iMllEbo`V-rCDEU(1_H`6A%90KBzL?RRFq z(mvtRFnvs+Q^ip>CTd^ZH#;<`Z&L9_^1<-@$C?YvfhEj~Ox9V2ZCne##S~6u9&H9$ z{}|2b*|^(B3i-AErv#u1PE~FffKogZy=u?$A#<3(YVm4tB&hFkBohpNsf?-i`2(Zn z6&Mt4({s+sPfO1MnZoDh;^$Fexp2~c^rKhjsoK09yDz0NUS73IuubTX=l#MSk%>do zuL7y)x2v?Au=Wxu#{@>M*%eivpAo_6Lvbd7C4Th4MTZ&lp@{TwVG9TNb+T`0gFbcT z*wRW-Qx?3;Pj#w10Qd&OfAaJIycjSz{2dOBsT(=`ujR46cgJtx(4>yfEwrD;K1tGVGfM z8YM5`6*R1JD9#ah{04oRt;!i{opZZoUFR zkUfz)&bH_Vqqs&OV)=*}VT_~H-=A)}5Z?y$NsGe12SgKV!qnVQOytjI_Vi)4 zF_{{U11txWayyw6W`1ndeTc|mwf>TP=$-{q$$B12Tg%IwqaD2dEmA8~hR{?Rh6wY- zAkq!Y3r(ywLWRy_Zeej)rsBe+tgVq8fxyzCa~>){ zn+Qrr8Qkat3a-v&zP|fNfqg=3rI?RrY<;G(d$^mj(4b;g-S|sfr?iQpUriwyS)Q}h z!?9jsQhVXDOwfop($HRPHENb?<9aBO{g^e8!ZZ-|>fNg1J~P9i#Bo+nxw%@Mlfago zPi1$+g(MtQ(0K^|*SoVPv1>Kfw3BU z!>*#Tp->`qV2FFZOr>5*721wr;581TW#*F^SI^LfSOEICD3<5##ag9KaSmi492J#2 z&hZ2VRa!%hVT=KWG{tPWUPj&8nSp_9Q=H0h~k ze6DKTw?JppujI?Ob}~bOkEYQ*AOT;zVr3wXsS!GnzakbLGxnr^P14sPkP=Il?we^6 zOGzBS30@2Q1m{}8o!Q3dvN}~TDi+S>#t{n;5R@JxalGXO@0nDXxc7}8m6TL8~ zR$RM08}P51R<|F=6DhS{leSKE2q&Y~yawzb=9MK@r_XkR+yCFr(BC|N22Digb&9tk?Q+BBNV3!zi)xPUYv2)TH`2T7)529}UfRa_{NONa+; zxrXPx<%e~KZiXtnJ(VDqjtUk%MZYd?)-yx$KqPuBb37|aDz!;l>fKW?e3!M03ST4m z&iu_h>_`z=P9aH!B(~gzY*^sihUqYrY=l&)*qiGZCxPA`Se5ksxscwMwq_gufx(G^ z2A8c7$dWwu^cErAe$+QIoVK(ov!!{|Afw_rrTwEzjk+j8$JurZhCMK&G|4t3kCHQw zEd+%g7B8F%9s6Wer8gx4!bKb8C!KkB2XKa(NtV1ja0bz%-uaLkqjHVBa-X(Y0D}E) zrRv;f_(Q(}CnwE=s(#^wo7dMGn>0|pwDFlU)$+IJV3$YFBuy4BOY={K<86!-($*vr z%E6$MDW?hW^SX>EDisRp96b@A-n2V~QGsd_?kROv&6YtL>m{XaF6V_$8z@tI5Zg0U zZq1PsD??2O(YrdBEOZ1;cbm*P3U7mN?1+J1Yq$0lW)(tW^{-DnJd4*i33DfR>|D(a zdSLh5rkGRgKB<>+=0K_6d{RKwY+^M9cPkz5Q#Yt6*XGbcWOMq_XH4&E?rn+MoPWtl zNqUtRSF8p`88A513>_a_dJ$VhKammqoL9tau0yo&n4on@alV(80l5^}TxIBKti_tD zPc|rf@9kMV_Y4szp58C%Ix{~?f@sRPMd!6Isa=D_1eW{3jHUqAd#Vq!xjJ%Ze8rB6 z1C1AP+G6U;R9;Y0FUKk6B^P}gsst0NncP36JBa+Eg%Hw36!1u0UaS`mcgUOW1&Juw z0q7Gb;M^-)3|wBWdr12TfG~yS3>MdM2TI=fVQl$ksM zwU#ltos-G%6>hmfH(Ahh`pa=IIlern)=+2lG43$s>L9~OR)w3df#jmLr3+b!Uzw##2vwNJmpyP=t^81e!jF52-uSHun*b(+hBfnHAOE zQe1HLbu-@=#i4oBOyTsC;^0Q^xw)h;W*y!wVC9`Jyd)R6d45 z7)N^g9RQDbo9Yuq<^6VKZHceB-AKX3-biP$yKx&$RlGz*%}00Hu2scKn~yeFSTbDt zjwG`SHR+zMYb)Dj6J8GdtBR}=L?HF|Dv2Mzx(I_xh*?GwO#54!V-)j)l!OTZRt1 zMyhYqwL0t1y>rh2+t#eHH2J270I(HBv}G90`kQkzM7o(kDvkjYY{KWbyVEh4&(T}` zP3}yqE(>w`Yh&9^mehU>+Wp|l=`jbSIoaU`S)a${pzd4g^pGkvvi*l0=s(+e|Knx^ zkA;QhI}Y|Y^b7Z|1~|W4#k_ZA`I%P#*RDFeYQNL-zwc7e{@JDYz2)`4-lh0!=kVvY z^S|#>{Mjb_Kg;C*-#Xy@Ytx%Q+lhar>9i~i|8trSJD47?!o$sMH7U8YNre=MmyuBS zpfeaB-T3BieGe3RcFh_-AJGB99kmIPx6jLQ0-;E5!Y1M=XtH|$AYSH%Zx@1jp#XX{ zhDh^y?Aw8gK%`lt?af#7ry2tP2B^ex*F<{~EwK}mml`yEMw4zy724s+Vw#qI6VZFZ z+1|n=djXe_N2b9qSVakf@`FWSX~46{-V+9rC@_QY(=lAdeQ223bS>fp@~=9_LGk3* z;Ekj*tz;8NmIcW0upxxjhklo44RQd0f4O;lVQ6`&N!t6vL4P2R>3+mxJheMLK z+Y^dDorykz_-0aZ=0nhm%TccwwL_N4Z-+aYw@v$m1fScKzC1>Xib zdkJ!jG<7Ai&S|r~kQ0>C>}6rV$!EH5b7Q5t_SGD}filH#P$mBK*)K95 zQI##%!SX{By4R>xIaM&ykwUe7l72gJV$l+Cw~}0>Thi2_;NqMb>GG znFBsK{y2M>$eCj!!a(3`0}>omex#nZrN7FrRPZQ)bVv{7S`tpnb-h+a-T`5oOuIpH+shKY9@-Pim+Y!n$e@I7VaQ+9#buZ}jE68bdYoMz3`*m^`9 zK{V4O>u5{+l_JGlYrM7Fwvg!pqEux2&^r8-Il)cOr#`Y@OkJ2+`*PvQ6qEJdxYu7; z=J)O`f7okyt5-YGUGY)#yqXF7wnLbZx$de4 z&_07}nl4#A`RdGkK9%v~=)TWubl? z!B_BpiN;oS6I^u2j0}g*E2>1|wKhCw*p@&t)l=^4Keh~!!lDG17Zq)w-5G^O^zdel zqoT7Lyag?GlN|UB3!VU;Y#pp**z~_s>Ya-S8bX|5g9OVaP0=L2jX*xtmh*>2oiBW& z0ofi}%~#9KAxkNcqo<^wOBc}7)@1H9s4_aN&*9}3VUx6P_574`SL=sFy8a{=u9j@% zS@!WAwHCu#7qcA*98<08?Q?$P{AG5y0_0`9hJ8Dk#N=d*r?pl$azQpr*fSrI7X;%d$SshUC&$*E9uw+vf#@Yd9=)lm-&RI|a^%+-x1@S_fZ zr`>sbUfo*eVQFnoQXN*=;~Oc0%0jDPM*7Z>ZK;ft#MHcs^xZs-29QZc{X`V_7I;pW zx&~NQZ5OOs^o~{RSZNe2Lm>i#TZqz3NA6q>K(GA|r5W{Ldz!AYROAQul`P3$`?&Vh zO6#}FqdSnvOVGpt-Ir$BZ61&Npr7aJv2ED;0~U(FDNF~Sw0x(cI$yx~%1KvHfrAcS zkZW?;`;mUs`7AFJ`qh=*ISl8O6$I4_KTE*g4xPIX3@wYT&3@d853`3LEwJ1KcYnYm zo3z20=y>}AW<_&jJleE{_BLVVVSXI4FtC@J21l93#XhUPoS~Hq3ROya4Xy}bL#lKK z(ugBw2mdmyVImfih&Y=+H}^v*&KiH*qaix7V&ySM==zH{s60q~du;Zt5Fx^tjNwt% zE0;CVIGPWlnUr?D0J}(`zV>vr7e$rh@E5jkqn>;)B+7E$ z*SWr!%2v~sRx5IwPz9vH>>-2kw(31!q+>3L6f^Y-DxpFnq-%kQYCM1NA=p7p@Rh(s z5))=oy|4T3txKWSW0xnicME!pT^~S^6kAgjmSq($0GAavXuY|_zWRl$?#R|`@DDZx zb_AKA6N+FJ>}-FyHhQ2I7Vr94y5(-$-F^;lnoXjs5wLrT1qYUc!J7KsdXKWrFavaF zotIe7IaFt_DQq)c3=(;auoF01qbHF~ZHJ6sqOyH?1e#^?O@$E|dQTIZOUlywh@A<~ zef8LgdM2H%B#gV#Fll5wg3MEv@H2#Mgm+x2hga^(qi#QC-g%IqQ zIXmIx1mE1m#5`?po+rxuyyw*7KqTG^FC_OK7SFE1FG)0YW=PkJf($6+>QvKiF=9O5 zF!AxC)6x?lT&Gs}$r)F(*>$J&AMz6!FmOf@1c{n42J9b-vEvGW@FemlHg=#(NAE;3 z@)W%jXoBs$7G;D!Aj?H>8?6F%e|_rrgmlnh$$vB~T37i({(XRSe z5Tu1Ujr-WX+#C}7^)SQex#F1TtS1JDUpiO5U7 zOfpb<5EjMh_o_+oX;1>`^|=%f09k+>-WJWC$~DUdwB2TX&PMHQFiN_(t@+o zei4pM1%;$d!loMi5!+LDq2KF_K>FB=IJc%JF1qI__3(00;uE+0!w|KXgY!<3=!H$c zMl3U^GKPGpuw5oC;|TnQc3LV1>A^Bw^K93do>6Yp%1R?IOPRPouM4GTs8>8TB1|^z z=zFt({$ktE%D3wf`r~749VFfFDpA8C`TQYmpw^?QeEMcylu*`Cs{~($x6fi7^16fJ zUw3Z<2xneZDw$~qi|s;0S0Doo?|Io_D{Oo|Wm8}l_ zt6lbx1WTgl6g9zfp606cj;Kj>MUqsKX=%L27<1+t34^gzo$z~f6^tI-$&d3>wM`Qcl`6D_8*RQ zfBnl}^ZCDOEc*XHWBN6(znyGS)BOW(?$60)D~Vj^H<87Jb(_~p@gWXkw7!5WBW~vV zvBjpu(OIplg5>>@`UV$wUJBJAA1Sv0{_E(2?o&3#X^I9NWQvC3X94-q2k}*l#WNYq z5*wjPnDejYtYo-|bplT)UhzMe}w*zsxjwii=Gj8((}mAN{2@a0P$N_{2;3HUhnnbuo>Lzc)dXVzoJSY6upt zmt}jrGrdiK`oaH{d=(*Vr?fhly$M9IBSC#Z)ezv30FP$3Q5{#H?KyrWT{9bUs#1K)4iH2Dw{nS~jNf25yUYo4k@iAQ zg8fdW>AndjCe%|WRTRUr1`t3pY1_BZ+~$irw__LeCaP<0N%APZ?M@uU| ztM61Z(6u(Ph|z^=e{iCUSdmgVZcaApLOW4(cOTGfl!&qdb=m~{xYRkoiXY6++iIZG z9K1Zw%EYM1(#^ES3i5Thpmzv8jxe1JwpSA))G)Y9)i+8K{Bu|h zmFc{aE-KeRME^0!l?Cw`ti&_VZDLFN?215u&~HgarNk1OA%rz1Ims(_9Y~QMiYq7V%_q=G6f{*;&FsEDozjab(GVdJFYAg*Jvq-}Q_ zG3r-5`dBiXvabsr3>Lf1esYPsJn5XWc3O$wD%_Z`X07uFjO4Kv(e%5WX}MbZ`_s-` zo`8GVa@!j)pcuKwG$0myO7UhGP4K00QS`*Szf1U&mMqYVdC zV);7dxDJIX4q7=c`}7jzz|v+wrw*i%p{hOL8gUz6aLaQ~gi?$LK2pAEoJ0kotjS+ay3uhei`%Ca6r9>}lqzXzK(Hh9TSW; zM6+iWv`pxqmXho@dOTUA6~u9^KE*hA;D?UuM}ZMa&}j_SjNW&n3NA&eRrZ@_a{sMO!Q~d%SHX^O7w$m;<1q6fwtBlq#SM<#9ls>dEQ4Yv;sVKPbXyA# z_Bo3dt-4H7!;cmCX2ofjoSR7`OBj$9ITD2Y*G3(AdsXKb?JnOKS%6-YM6JnjPkG|F=mTVc)aC0o$l-pC|M+3>-F z?KDSGKH)K3w97%6WN@s{P{RC$u;K+h8_86BC~hwK0z*c*^KG*kiRT@OW^An0)Y`U$ z&>X88OhJ}^XxxlGSzYNG(k{g=i4MC=RE4Ko-ns{DcPDy22fw_WzR`|>ZOBmUc12MB zY^SQ<;>0*Ik0tbV-PnHWaknLKG2cKGt+zFc$CuX{ZmdnM#~@IOCVr7QxA<4AGJ8U{ zgnd<85iYW)PrK$oET7FUh&g~W!6v61AH>?vv4%S@y9p+aqvy@!n{j2_%OhC!+GHJH zwydGuE~Os;!|P|@ooB%FFBpjdJ|*r*>CBG5rg#h-o7RpitZ~EXSD!#S+b=SIDiV4w z$X(I*x50^lpp)M5w&O}ia{f*~0b^Wxj#_?%D!X4C)f11%z42CZ;b^RHGi|2=Qz%mD zOMTtU;I5qDK{IZ4r5fc!3JcpuqwnBBF3rEBk-h3d4(AqfUWQiO4Go6Tp%4Vcsmi!s ztqBrrlgFutof&#&mfr|7P03D@bnIO|qvN2ty`4bLwL)?h{*yEr(>CGD)N z2<_nTS-*>*79z}evMNjjGH39W{-Oz|s|t13PEms>?pr6-2Qy0mTLFiQ_4Kt5J#?a0 z$q7B*J4KpUEab@L`6k?(i1f@o`e&Q1A5?~81SKOn`>Kr3ImId;QrQ%1Ow@s`>?jQw zYYMMfpTCpxj!bVK)FO@{xnY#zYCVrH2ftf-l1_Y~cX`pJmWor|| z*rMhOV86yaW^!*ED7Hn~uhyMxpHD-{g|Bgkh~-;*?jfg%2~+C;LD&SM@v#V>xyVNZBQaf-SC}lQ7G$nf5-PrWHAx2tHumS%=Q}iFM-{;MHHt*>}j95OMA3 zk<($C=SAMvhGvs{r$nkSSux1Fnq(e~zn3NH>tZH%Vh`2;f6m`8Z+{7^xfZQ^nW~v* zgJUgwetE0`*}RyE+=a)3fEFA5YDZFUaMe)RX1|C$b<0 zHjSrk+|jYpeu)Eh{sxdo#bJN2fTx`O0|F_@`x#95Ck%F_m%>+97t}rlq$l#kxmv8u ztvpqC&3R+k6+ux1ot~ReI3nhQ4bG7YIe)`RUC=Ckn!(Ij-VZOjzN;C}&2RDs6<`AZ4&<2vrc ztajIK3N5_#a9g+$^tLjaWQ$DqH|(@pi_~_hI{hfB4kcLOD(qMq%JLXX_zoU2r=SVl zBtLDPZh{7b=-5Y!yl0P{C%2AeGVG+ayX+{wSc^S-C>$c9UQPQ5PN4f0K$(`K z?Mbx?7-Jby@}TqpM1gH_&GM<$xJ{#zuCvwGCYT_$)<|AaWS!|0YV-;N|C zjam<3!GB7r|5OD3k16$EW!^M2OutVH-;Wi46+h$t>VEou?)bMFXu5yNmw%Tv|L^nV zUjzSRzWk@^`2XyLk?J37g{cJT->a8@=I#G)`81V~+)on_-LKt+e?5+T?}hXqTNi(w zM>0|~{Lf9&efM()<64|!z?|-~xDf$LcE45ySC&0Xn+@BJUcStRU2OEw1ctWy=VQ66 zA%8J9ZDC;q&N>mv$iOAnMV81<@7HHFfz3^6H%*=4H81es-((txBXcP|29hkaYSFg# z$gMm8?%5`jk7(x)E#0*!5jt{Px9+>N)ee_7m#R_B2Nr%}Cti5@LWRN&=Q(c28RJttdqYijKNhc9c9 z6h>jDzJI@DBd+$nOinUaZ^ZhzpX=N(5?p;_jX0!7f>K~+?3Sn+APoaj2cN-Nz`6G- zGB^aSt`F(t#mh)GbvBkv!FQ)EfgzLUUgHm+;#cy5Nw!uQQcvctJ2qMfB@(|ZN2Yy* z_Zc)Gpc9iR&E+agk`5V-=sJ2vmXAUif5z^2uHa!Wy5|9_NkK?52ORHS$7U>9;@WJ8ix3y73~54j4s{uxlGTQZFG%OtM8!5hP+ZV_LFfEp#*cB|HPKY@Rw&Z>@B8~fx@(|=VXEj_s0!u8#1>Z}t3|Tj&LUgK zykWD;KMWM@DW`Lv+f~IHZl&OF#7iWnpu^nt1F{MmO$sG;i%#U?57I+@dIFgE^*_Z+ ztoz^hv7v%JsvoB$huJeeR%<Mp2n06{{)j-tD0nij58tgHeAgZp)Ko?yo{M={MbqS3|4L1T z$moyzF}Y=ieV3!0j|u|U74%yt2m}3&N&X(zx&B$yC}E>k!{;YX>aba8tDps6=NL?3 zx3W7xY-pJYMygy30UKO$1nnH&toJ<%&;k<8D$dUXnJ@WY%|N8LV8xN)#tEJ0tO`9y z1?;Dg&R!FYXVcr!u3=^`XFO2`?&tv?E5izVWoAn`s!1D&0s*%U)jxXeB#fscc|40? zbK)ysqL~>V=?oV~7=~e(sRP-gk`1mHMDh@dh@yz|9Cz(isaC9QU0u>b5syheVV;UJ zW7@1_2m%|(q`d|6KO%AHDYORpQ;y)yiHuv@jeNP-c+L10ipROaw*O@?`Qaq8WT* z1l0+yB$nn6GOhGnmmIu(dJ9cP*!B+sWJc;KC8yiSl??8U6 zy&0>uESbB>G|Q)b&^s8?n>s)2 zanG$sCi)fD-+YZ}xcrDaA3J8{dK@lVdrh9uLYlFHO>ZD_tOIllG2z3*d6C1~%SHDq z>KAYi;n6EL?Lt0CyA z7QlgevyFcqCp%x*Y~k@<6s1yQ0=-QFHXY;DpO*#U(Q6#w-u@*rot5WX{CEgt92nbl z-9ec{&HN6Hqiwdb#8snlTxkg+iPlcbaDdNfCUXF zAB=^e#hC2tyqQ6UX;lCjVa-R7;OkR{=cq{5MhA1h5z z?5PyzEC?Wl?X?Ep>u~U$pr=SmVn17Xc!V=7cg>}oAb{C3s!ayUq|m`q)N#7I@>%s? zjKgL^R1Z?3M}cO$q`dOgFD?2^M62k#Ux>2sRo?@(YG~rQ7xKSgG`CXY19i`0JQ}M^ z#={mKO)s|BVNeO=GGL_Pn-XG0Mf;$O$$bKV@*JsUBCH-4=F)&njO@6U$=Ai2O~Z`gEzQz zHX^l5&-&}id!1-bnyj83Lvp3VP?0w&fOu;wRWaObRXlG0csXydcHs6`n93yWvJeXa zd4DRpZ^1E0#L58fP}U_nz@ShDzQ#~3Dnu9t4!ml-Vz1SZ_=A&2HDxbd)3fsp9vG10 z`?Q6nrGk?lCylC^$0pFv(OO^U1KSf1G#hvJwxIyHH4jR_o_Mc#Gv62y*^+tBjgjN= z2q!Kt62?>zP#Lh~!D5QG=z7EHC`oybYLs5ZH#(f5Kt^fYFyKB-%4xBWF>D)Scf`-y zP6k~KtIEZGxSq|iWnx?LFJlzjxX%R8PJvM;TG%dB3wK0ogpLK*@xvV1KAcv&TeKnC zyzd0$OzYL->idcXy}5p$>xLU|7pN9ox5$p0Oq}WpWgI;K8&Yj{<~nDV50%{W17H|E zK2*A_Wa+V%=wrkjuN5Pj=60Tr!wTmhp_Dp=HFF4s5 zOtYb!R~=psph+7%=%b~GzB zpO&H0>N?3G-l1t*0IM8;b(v!Ign(F%zC&i>^V62>%dx9oZ*M&aNfB9 z4k-!x*iQCki_)BLJVqte31)&cV4Z^N8qmzz$+3yAqksX@>y*wG2s6lrfWxpqM8<#+w$5&Y{}#)+482(UfJ!mMzbV#rEa|M5pA&PEp+7zJD>;Quh_I zKfQ?-pGm6cCmQxs3J==Kcr7oNm=2a+cAl18(C|YTQJVYg6y2gmdEQ>ZF;*5y?2%2O zlGh{UxO5KSJQ?GVS_@g+#XWOy@1lWdMLY3&YeDLBkqEzGwHde*{hIF7`bmq#7=c2K zpy!5Ov%?tsKq4VHSB35oYKKKeyg;;J?gzcl^$R&yn#wHnkMO9HV693ZAY`r;9`7CX zp*r|-deKAM7PDTu9Okm3WoPR2 zXL+%>n%su(uJLkes4P~&;&XTO2t;@ZVM)@xzNdVC6$v$`6h%CUaD8yOCWBbC4vP;} z#Ge5FQu%cliu8lMhI!<@!!uc^^d{0sET@${L`g;Sd126fsqd?vG{f+|>|{BlyBMWF zX=wdmub}Y-@x2tyedeOl4c?@j?aZ@E2BZ>#00!O}Vdb5Vj1*?Z@?8 zhrA zMeX2YO=#nTtF!VGRE~m-*q;wl2<3!{(hOq13K*cAaqoAhGHRy0)iuaMWFpojmIBLzq?Nw(GnTh`;$+un`WXQ(PvpK}%Nojxk) z%L}$U7fcWosFa%AkSAz!h$ab1+YVKr_HuHNtWy9o98z+qKEQQyakTD)i6fD@J8ej6 zffz{ZxJ_>3DyXLHx5i)UUBNx!AeeF)b?r_)ka^Zt>jsjce2EL%?0r0Br0=FBK zs6=U7pNrN6NNvaGOPIkPG7g$gH&-dpngez1+@y@bCv((#Jt|7GmW+g{Uf%0q4rwS+Ry1S{zU+O}=m_D*N^KBwxBs){~Q(cRI}|HWFd;(OQ4 zd-u&}j5$U|5o0_y4Q2ep&uRBM9786QE2QA%dy|Dna|RI|q;><<(4OMzJ`R~~5K(<% z5A`bL9s1gg6HS7l))a`dF1 z2Z{0TGDzdHDf0A(o+qn;g755S7OURs2eQ(&*f%w` z?4Q!dUa9Wo2-B}OGl?x?(8zWm!6pQP)fIyr0H~VZa*9)JD<(CT@5KOp3Ln7opewW) zfYVy-IS?2{#mwjOb_+uI9xoq-I^mhIA%-f2J`Hka3j_^WP_^pvfc)##%Z*nO@Nd!V z5bycLFzw#xxS|Ajt|H}-k9Bwp_5oB|DDh@U<7P;m26bNLK9Y3M!~V6*)&8;WH66Ny zhw(_$un!s>53wy3KXkOwxjhx=;X5de`VWz~kRLhR> zYJh!!VD!KKm{XSJ;eV{m6|a{*F;^&E&);&`qj*)wHOdA-WFu(LQwAol{0iwLgBJe- zId2HZjch_`Q-Hkz;ig$A2q>-YTEsHTC|sHDoVS_!2s8Sr(;zN}qY*xf={I~@c=C=C z8&cOgK0T<^xI>B{!iY&(nIf(08&JXAyX-;WPS6e#tCs14ptTcc+PucCgyU9A*Cr9A zt~`s3lwy`tfA#f-7O}J5Fa#pB^OVa%^5ralQjzUsWMq{r4e zY_B)1kC}=7<*o*!JjP!#gW#KsF>-HI^Ca*%A%^dw_De~LFi4;H2N?lB1>YOsX)nu~ z=r~5{v4C`QPo}n9Dsg!ERu60Qtv3KU}YVc$sxFZqLRu5gI%KmL3?bkGZBD&P(NOD-`BeveD}c{ zaUQHmzxD`+>iQUW(U1Ga$aoWpFosBy5J{?>pg*`8SO+MRp8LCh@8|0zZh9=i5?6A7 z4RJ!91i^z*J(hpPf>EPD=1C8LI#tQpa8QP>)v^qT2K^NE;j~wW#=H^nmj2evXt&1$ zLhiZv+*XV*;=HBCKs_!woCEWY5Y&*N$#`>OHrM1PR-B-jMg!`*XK-NDkc^pWSe5=y zITjbQ2=TmCOr4r)+*f{H5|`<|g+2@V<=u=%Qta1KUppGqXd8E~L0<+hWh>+PA(}nS z&F}}do;I3ufqddhevv8XbXtr2uymXidyv1@gr^gZY4(H>Omc1m4*LW3mo zQLjBTu;w$Z;7iB!+lNSNu&8;{ZSEEK)S0)r1bmtY0r#rg%w-6Av zpcwDwf1!vFTr>D0wOrmeJ)ke<@Z?pYP{5!Ee_BI}OABhGL*R>UX4t~7ID$4<%I>y$ z(-8j(&{F)H8;5hI51%J`TEL`nq?FiV#3G(s$I4aUuqpVPItnC872xw7e{C<*4f1tEb%H^XOp94+_3q;@j~{(FF16sAcAGFm%5FDZj%Q|k zL2L=m@g21Kh%JQ=-N1V`5dcZ&Xmi6?={5R?B{;7!wm==6nwjj@5D$dkTAx%#Bc^!M>ks)S7RwWe+j(PsSS|Z7J zWtwtr0~)NA-i{gp{E({5{r*JMxO8HRcf)Xd&#bd$$L63Hk!OoIluFJlc+7N%9@%F{ z;-=dXJcY>tj1jAyhLtZM&ub4n0Kzqh5&TM88O=^UDsbb7&$aqi546hPZ6G($pctBL zW2M3zfgb65op3?Dq9<^NGqAba0ubWojn@1j@muk)iMUjy2q_0*T(Y0N#r(id=L}FK z`h*Vns{dGvl{F{R(qYAwnL@D{coT5`v&!mofvR z+YDM@4(pD;{LJ)PmR_XlgEBOf!qLO40vmx=VRs@77s&Ydy~=62`~91nJEjH1KDo+# z6PaPZ2#p{XFQx$$Y{ttdkJXCr6%_LOwB@L-X50RVC2ZEFF=3A}uvyL#&+}jDDgO}< z^1rF4u>8{|_%}D@AF9Luq^Gd_Cq3n#en$HLbv@;un_>S_Px;5m{{{cy|04&0;?2mstf>hYYP7=(okaM3GCcU#{pXcx_O+B6c)@IWs>&Pk6oNbtKt_=r{#>8CL7>>@p<0ZWAMWz&)&vDJ>xARF;nWSphTv-O3On6#TD@+J9S@l2SKlTmO=J7(w3YzE~`*F@j!!a6?5pHBk))nFyf3#BG>7TTumy>iX;gv zA9~KX3mo{APDNp!a&exuT1W3sU74D(Ds~u>Y(g>aooe%@_=z-yGgMBn z&Y*{^p8KiXm;w*<2v$-;L0pTDz?orU!FK%=>EV@g2u0j4Vxy6u9?2aoxTh)L~ThIp=`d)j9?mj6Y*&i}B4;eIv z-XVYuKnjvD>zpE=a-CuhP$PNn>h~K1JOu{lrHQb-F2@%LM>{j=jBbV7am++x_C69!N4kEM?ON`qJTv_q`UhKW;lhPPY$=#c7$j0D1a})<0xz zFkIC?Z0dU@n2*VKT~$*}t@LLnUrvCXHT!8^C;P624OH6jQZ+=>m9<~R&dgZvrbU@h z@EV=q=|uglCO$fUllJy(H#%OB`Z9_cLSG0OMTMiES8LiKEwYhwd?dVmPNtO#;#|GJ z-@x}ElGJ97^Z3+70tZvXb?PlFeSRS)pnj+scwG-3p>`)YqGv-s=my{jk2CT& zFFcUj2D}{MhRO{EQ8KYXul9Y!P7ZkAK}k=GO$SZE@RpGXKBl>IZ&?dotx)xgVJRL* z@qC|&t(jA6U?J>YG2SV`zL{^G)AP`qPaJp= z_UVKWT}LNh*HWy>d+5UPvtim-lr7j6^Djg1gxWEXIy+WfcRLzd?8mq6X0>>+8oVW*;kUVzrxQ7v(&o-I20sp zJ+!PP>YLs}O6pUj)|IoO3d%pg=L3+PNqD zGAZCU>kN@37PI%@qhn3=9 zUKvTyFJq_}UT4VAqDTuY@;`~rTr4vIK=Hx|=RZ)UDVw|qt8K7JbJ1q^{4jeiGng_{ z2Uq`;QRseh{3(#uo?DeIAgnPu{tVLiE)H!->jdS(Fo%l@=PfozevE2WoJsY1Muc;K zo1>&?1xV0JOgw7JRrdb3RaXl9}bqK8)fyt<}C+%WPn*IU0t6L8%JVq-PwsFzXiUn=@6DyZp#QZ=2fUil>b<{V!)o z`ubC`+n?O)H?J~e!K!eVAb<1Q^QsMaL2?Mc1*c;*fGB-r!Y@g@GJe+N12`JmO6D(Z z-0O@czF4MYs$q*AFKs}tE(SM&Vh+klA+aPpogKv1g>0p+YM*3IM<9$iezMPklXZ=* z0QO%nDS)`lxDcYif-VNY?Evll5Ybs3SDJDdT5fW!PP7bh*^)((mX&CHsp;-L zrkUY{o9ZZD*k-#GLMs*GmO&y)*=1)xq6{>BISfsV0McznyFv;@scs~N0)dzo7V zIDhwzbxAQT6Ou9U;feK}cMx?OiIXCb$4(*)s?prDRZ%Jv6-Zg_j?mt&`nee}2L@SHFCq50lN3}gMbSAa** zSQdA&ufJf{K%XOi1n2i_7_Cd5c*-MfKHQL}PH2*ZDc> zPVv&_kZ#p#vg12T#2lt4z9dfM6h<2^d$Y?|qx?>-x>if?5yHDzFdp5_DCCx4%tr(i@(1Kwdqxbw4&-4SP|TiTjby_t#wGvtOjh zzQ%I{5k>jG_Peo4;dIBAx@#%+%T!F#qJ*(&=0<%apEahtVP$P_*`dYR#!LS|rO!Vn zB@7_RN#8~(3R15Tom6F>&bAX*UtG%JK_OMUys%d~6p<~WUbgSdB5l5Xg+5ZtOt}8Q z9yq=2L6CqX1@4BB^07{~uFF@U+QbXTbHc?7o#^w+3nuZ24T-S>3a9)Q_s8PMS*QAz zT~FG^-foQZ=huT@=Ra1)R`A*SX!gUA79sDL&dZd-Tx@|&g7OI(M51tbASXC+Wfm*2 zq?*lrT|(x`ha5bhnED>t!wc@xq8E}BG2-GxhCyoS01_35h$j?T+e|(ZB5q>iv3%^& zl%m21T(kA@^C6^kiz<-`|3zqH5*+JbHInUv-GGerb9^3!&0N(^FCQ;vR3DRPjPeyB z{{2cJ33;rZ1v(6O)fnX3 zP^GVB9h6pHWol3Ni zeUB7P=S4AKWT4(g-Le&14-rvQhRKeLp}w3}fEBJ^sEhqzB%RsVuk?5Cc>kZJxDv&> z?b1Xe#eLrk?&;;FSXgR%zJ!W{`wy&GfZ_I{1Ze8CShe6Wl;nNtL|e|X(aiWwnNt%z zg9f5*MURy@pIov74xLxG*g&o{w`KIMT;Gt(5bpgDqQ0l|xaSljcyPyyZCW=y&Z1Ck z7fp-{!=bS-bSyQb1&N@7-J#&i;2CvJJo5CK#UBsS8qA!8MM-6{=A;#t!Q>L)0tP-| z6eOAMUZ1i_nFg-)(n|1koMsS`px2OZgA(Riw4Tw9SOuu>o8>E0jW zNz4o~Nf|j*@_Xm^1gjlj^EEe@Dz!7lK(Fn#>TY-CX5GTM#N+ z3!)=rx@@o$+1;PnQO#ywjbO(Ia{BiYdw)Y-7J1A3)+dRu&2RibZ0MiM@{AJ~H*z=? zmX!Yce&(xCQyT|iJ)GO`Y8OTig>!ivzQx)J$0I$?$`*>8)L)O0Gh85;V$&^PYfiX( zXi{)PVjTzQBy4#mR5uX~$8wTCN#Y|3c}E=3PX|xL76;a`pV%^o?Qo1^{V4g((CYWJ z3@OuAhwgCm-Nor3vQ7Ap5KP$5j!p*(W7h~G7R1{Op7qZ&m%=HfG47k~%4+^D6>iPH`#rJ4Iuve2+A8&Mj-@J=X z{Wj=tR_NK0^r;M18d%t26L%Y&|5iNKsX)M(WLZ4OKTl%7*#O9GkgL?&3I{bZpBeQ% z1QYyI>(|R|ImSq4@2g{-3?;;q@jw){R&1xq52eU~IGTV9T_t<|70t3f(yQE2zdZg+ z?EZ9cozc&c2K}k8`hFT#g`p)-;Qxy@!aZid?ajKp4u1p4Z0~w z<$cw+HOD>_u-55sna(1l6(ZfolYl$^4TQ<*q;Fud+g~XWQ_RLPS{!mh6=}e<5qq8K z2tIYCEPn~-(-228jKZ3G98|RH9KLF}iLG%CK^oE4Ju=u(3md|(=9baLnBv=wx*LU# zb1p*hm^d)0`I?K>5sCx?jt=ucH`5KNxYP(}*ywe)+fgtvToQcR^?*9hQ6H~MwIROI z^!>KxvRjRD@(H|fdoa~~H=S|i8gkSyFJ?1_53YWqr-T-97qCY`m`N0<7LBoW;TJ;6h6cpizJ}Lj?77L3^-Jv|ZOOOgiklYW%ywad zE_rji%PJaQxY~fCySj)mRQ1wSiUxNmoP*`oC++nJQ8V15g)ED-S2}Vt)Xd-3M(!i` z=`w5iY7#4q2Z+w(8Tw7|6N&(SgrEd~#5?|qgq5a(W%O0+sJP4~JA`_Qp#^>J#`}1- zq{O7>07%GATt%6#`j$B9u!|Nsqo{>|M-4TKNA0-V zASn8)2tzWg!KN0cNgyWLLDxBC^p1WzJ|IXFg-@~Vl5h24_}}Xn09QIAQ&fJ;Jf8U^!C{XqtmU3;+)ng-jMdWUlcQ=*7!9X^34q{Bgx^r`9-e7#u6bd9Sn)gxe zyF0*}_H8`sui>&NX^3DCZ6RuvPQ79mqp|?8f@2smqqz7-jQptH#8&mD_9xMXtWj2b zqf|^vKPFso!eW;newRyUS`7NGz1Izb`Tz|cOo8Dd6KZNJ^;(aUk@dzr=Z39F*np{c z8peIMR}O*xnmEO|x!e}n7PFn2oh3}CR&U1i0#dLPoYq=^s z!SeQ*`DeXU0Vh^SEjKOXpe{Q8lwI`970T3YUi%tjd8$_!zfU>#cHsD#dHvmEgfdHfU+Hb`d^7DzP} zF>lR!;bg?^hhgi0X@DYUq|TWfwe|8tUXd4gaVq9r%<~nJ(vOX?52N*3gjUm|x*{^K zMwPqiv@HVAndy?{-}jNsKc3CblE9*q5gZD^YIU%2v7_^&8u5o~NQeEFthf01%(m1@|?ylcb zW$=|4%!j!|hbtxfpV%q8WY<7U8;vVk!(ALjtZLbkTg%EBGOeb}03ppaVc#)Q?Kgzc zBv&&d0oS2J+*mLDMJyZiZm9Qy_i8HC%^qOtwr0#BavQ6w+>0YYv9@dsQQ8J1=A;Z` z2oNPF_#pA{l9d_s5bsWfPW4k&s~<&icEebcoiJpbH-Eeb|7puMbu2=HRJ!M1blzP9 z|DLZm$k|}LIIl2BJ@2KOOL4rN*+&1AI>c1BxC;pHo(2#ZHeMzJO1J(c6~n|*cS0F? zxbXV6C;Df)7j?$2-SrWJS|=el>u{Y(27utILylUiYfWYzf#*2yh+sv^sV7X5-xtOeSeSk}KyrqQN%QrxHG*wKjl%<#)nev26dzrr z=J-B!GAIL!zHoJL>h$V&YRQA$H5NI4-=b{09G>6dPipOeqscRpDOsj@k_#s|7j>JC zpq(+{&_KQ>!xn4#@#=|dV;_vz%K;=+8;xBBm|qh!4n`sDL;t{#<{1Q_Yoe8+)r0>o zG9($0B$yCCcFy(AMF6rit&D=WNF?aKHI#XKRJF2mSi%hb?K;WiI9$u&a2W@S?2#tF zBWQ?lrxYHeOi21wKkOYbuY^FBI=f2NVtIuby%I@zd4iwONX|H4m)9lr?8@U#&^mJp z;$ukZfL1Rz;TF#P>=0@rkkV~%3Wn<8tV4)ve!U{VEHfl)<$L|yY?pWQw|rp5+~3Sg zoa2gOL9;9UX8hq8i_X#4IZy{p2G4#7FWtj$6*eJ~4Xk`!nysZt52loQZ0b>B(NOVJ zU^fsxy*aD-G#7Rs6-@m5Y-vIU-J$Mm5JR8ehfvUUKDue2)1&;0?ihm}*2C$Y&J%SG zm^wRV&pLldxBB)4+P*%oEduog9*7$0X1JCl?Mx<(i=d8yqt#gxWNmLop%~6UPRWdP zBMwVPk|MkYr}s%Kswf{yJ-C-eA!xU-WR?utRLez;zwQ~^Vq_>(zB#5UcSzv+?f9jE zR|v!21U+HMg(gK;o)BSI!hYXzDi0Py#uSXoE+QWPvIOVec5dyXfI4$5OL5wss~hGq zofbTOB&Hf(6r@RxaYxD+NsLtJn8S`xm&UYCjm&#(z&a*k$@0<|OI(hw(HYnL08NXs zR`DPxQWlND;%Wwb*|#kG;0x;bRk~C!#4zaSwPU+@NF6YCnZr?3(6op7lJ1GMkw2@K zv{l;2?!KHYa;~LdmJHM3ByPbaX(7m9_6(sVWI32v=K###8Sq@nKo9ZxrOrkG@WKhO zmrQYgaX=Eiwl%TAvTgi)ARSq(Gz(ILI(yBKCFg{=>h{u4Yy0SB9`V@zp6pYItp&$e zy7>VPrErkj6dSA0;Mgm;ag{z8#Jot8C%U?N*>dWL~6lUyt=}6DwH~0|SR}JE){fQ7dvW;(>?Orrhg9`3hFgys# zK3#Yt*x4D|64-0An`$l=-+sc`XM;j`sG1F1yL)?0*>P9w?!2>Tr0QTHP+oF|fdOM4 zJaC`-XF*uA1NNkyuyP^sOkV1f46ABiLP3Q*z1a;3TrEn|HKfaAEQ$Cv!$-mFj})qt z#P3XZ1eJ2x{)4yWm&;_4C{-aF>kN+8LTKiW(&;O7n2o(k>rAP?UUIC+17mLV)Z_lc zhTnCr`eiaDE?PY7wS&a0f!c`zN) zP!Fl{s%ev8yXbFwzdEQkKrL&DtqcehuX%UETi#=2!+}nUR?1}ysgVi-pv3N}0h4`> zbHtZ7a^%}Ko?>($Su)}5g|Y&{Go^eRf5F6Mw$QCx*RAj$6Hd3J>IQ6@uz*cp0D9*w zZ0Oy)jq;(kipMKA7BzL(q67L)zfR{L9H!^HITlmv= zx9I?}J5pe~H3m5L}Pow;7ay|f-0&BM~OVFOFW>p_OhoQqb{E~n#}N4Mh|Gu|CUZLCupcLhMavh~b25(85i`b78ue+?z*i}#Q8x09p0@jNR5Y>!t ztlP`VjU+;o_v@KSyUidV{&lw0!(X%uV~<1BsVg|Z$c>YRzAIEe7$w_y`-7!C0?D}3 zY6i1DQdJB$iGK3vguw(47=@JcsgseH)bSOstKpG-;>jJk7Oobqh`aA|AM3byNvMpd z)~L$03<4i4*MyoJz4h4k3fxR~>nNCwvdl=3-9N?By@>8ElI6y#;TL$(IJ|Bxx1#x6 zT&(j5gcFa*bc%X9YLGPG7u(!Z5Gt5d5&vlwD>IPdgOkn1Q?=;2@bMfcUOLy*_g+a@ zf_OUEqh5?FVAxnVpWz&Tie|dym}Zc$QCr7b(1sO)g)J3>hRM@vN-CRy-M&yw!kBGH zfva+@7P5*xDZCOMAczeY8m0y|uO?TV&8A-&&E$f$Q7|+31+tgy?TzBab!n_TL}dz{e0N-BZ(3dCJBr?-}ee zVdEqKV2{MH;m-8rVq-YR>_QS79aZ&=(P?dLV5g~E`8@k-IR>2x)6qy`r=ny@RfG$C1pqy7p>(iN2V-wSJQlyrU zve?EIf{ZfN#vz0>$eDRb(|<*V&`K^(4t6M|q@@P((?a~F<%lNO=tdtX;=-*^;C%E`etXlkRH5CfQ9lA>IR1by8JF6q-)}rRn!x7NBGH^P?z;WrJ&r^v z?Iy4*?(CL|%>jGO^-fPH&p-ivwZwIbG8FkPZ0aO>ui|BOC=1M9z{3@m>V9het_MWh z;(%20b08lWbIi>xjMv2iqV@U^W4z~0(Y4OtzNbKS43#PQdJ$b!u3VHLNyU)f(b7#R zOw1AEaRHh%2!8m!^W4_2uzQ7V50pD)Eu&&peYcgf{nOTxPt+q-Ptl+&x({o@2E5`j zKV#+-Gw!J9sc663`SE-Q=R|q~FQ@8mvr?>pt>ChB0Mr+7_H8$PrT4zgW+F)QcdKH{ zG|jZrUQQyxm_DZMS#fdomUt1!`rV7E_fPpGp4xY^ij&w+8QXLX?h;xMj)%85TKwKg z1T=zpJYso`VBT`f+|M>~e{SkDr>($C&2e2Mhw&6_aDNoTcPc~-RvsTq3l3q-@V%MJ zR5O=taB%~h*U%|r6-3<|VL}Pwjb4Pb1@OM?Z`2itAPD-UrLU1= z+n|YYA3$X>#_bG&vnD~%);Ae)QTBe>?TAlNFMm9Wn?SQe4z)|)VOCXkr14Ucr9ry* z)nC5c(!fS?#A{XQckY>j16WU{0kmqaWn85hq|WW+%^?~i5-mHpGFuXVh|lZ{YnpSQb1s+H%i$dx=qr- z#S_V4^-HPe^2SCz%goq%z*@Rh0AV;_ZR6goxOW1z=gB99w&>*d*Y2cGI{c~o# zi)ZJ!PFCT*@H&(Zna(Mdlugm&M|phnd*buU!q`(_+R*wP1?Pe-8U4_c{G{I_0fkzb zgI9LpgpV?G{nz?(@SQwDf$jsA4+JB|bvS2O1ALm*#lkjM*G=sngt)H(kuw&y1Iz0z z(6c{{LQjB1XtZrz+_|!gF-6nTCUGMwA0H=kvT*@R(+Xtp;{{VzKP4)#JUgrml<>g` zB)z};GLCT!V`7~CG~Iv!{5ajF7b*5$xL2{9>Y+Sh$E988zH{?BVa;r-mbH#n=t!Xd zlBKIb`2owt+GY&NAyIZZdk*by?EO+lqRaDTF%Qydr=F1o%|oUr$ZFq>h9`R!4mYVC zzGsJenRLVHEf8OB`VdEZ%dn_s`elv!esSa zby#bg`f(VJDLRH=@cH!y;0ZWs`~A@b-m+GIe5)HHB{jBC4DSwT+|!TpB7+OH1rsty zPGmqgf0n@^z4~Q4RN;|6hX#8M5-Q4#OD61N199kef{j=uE@AbPU9nv>cHIpAD`Y() z%H|SC#n5bpcaHY_I2^5a;_aaQ&Aq^VZt9ajlGqeM)Ev}7>0O7@M~}veVE}f zr4dNAAn@Xn_Z?V=i`X`!4Qf8d{4Eni0TwL~blnmDUl13je{hq3XBm!m_W$G|bbram z-_Ktr^7s2M6`^DJS22go|KutE6LDetPsHWlav1)XeTM&DI`=Pf4*z-be@0wXR9OF! zr|@rW@P_EbZn8aR@fwc`5ujWC6?5@; z_)SiMf@|t;7XvHsUDwLv#3ayDSw@wtdCDlT^;DM{rHAX9v-Ju3;&PW900w0Fo!5jI z1CK(hN+GN4`xNEWQX-wV0OdwChkOESRJfN&w)j->YOw-(LQW}vq^fcBucG2x>y)yc z-YjYerb!xoao%{#5Q9Hwlb?Wf1J$J>vb=X;TB?f&%&)D(NhZV0F=FcMX^@wHAn^&Q zx*bpg1@%595T|EtVHlM~W=`kS{8e+q?ZWi&eA(-YvWQ}a?Pe+KK4Esu-xA`lrmND~ z1!2T3L|Y{a+fImqJ?R-np%H{EqLS(^XcvqZjM2nv8K036vAL_zw~a{l0|t7&O@0iZ zTeo-`8=73<@!fC~727M9jrGn~>_vUM&;;VV9P3Qtl-PYy4hC@EKKPdU^(*S>C6zQK zRY^84!vul+w-Cd0NzM89Tdcc_G|&WE{_eH~FQ#9)TSvR_D=cx5Z5eh4dfB*!Idi=y z0#UBQ=B`NrJzWDYz)(pBcqt)7?@)cx7`=DdTfqe@eQp%{f+vZ$uvV_6fHJ5t!JG9d`lC;@Ay@+6R<@JNX0DXDR zI~68wyh0668-jg|{CFBYIA0->&U{xvM4sq9r0gGz3AQ#NPT&yv2Ta@GdeY@^7cQnE zY2rC}f4V--NxMl}L?FPE%HWRhC z&lVi2+|STRWx0UgIvr_n;^nL6cXSi?T)_!a4g~eXgE+iq19vCa1r5Di8dndu1>-bR z;$+8L2P21_Wissm7K#bD894S%ddqgvw`ZSn;((fUeO?PeeH7&!u487PDkmjkQm^nr z*?S$eM01WLg%@Q)Rv~k#^NeBNU;rJzkK=VxT=ZV#XH1^&Y4Q}^>XyY*wcZq=JM^b{ zee?6=eXio8ZVY{_#GsdrR7&GSoVR=}Wrt9{2Pr@Z%Lu9sa|-`MjUrq@ns_BDODmNi zCbLzdRGF6H)7}~>Le589co6`#KwBC;zw73eBdrD~FfK+Q#KqLP1?EhswVyd+&I8UQ zAwIw#$Cl|rc1LQ-S#bTzgetjAuQH872MMQZ)Er#&U3;zs3@UxshF}X0hGbYkA9X30 zvEH|A{;48P&^nwn7`3lk@c{C}`-i0B7!ea};x)lfV_YAFAVE?{r>=4d2}cm#!b|7u z$vRF#0ELM}_`2KQ=_@F$SFq=a{al|TF`Ic&vPaggcyK$x9>M<9R%IgzFyr#pA%a66 za{UHya(h}ZS=IgGHVTDdgs36q_$yR~sj19WbhN9g3(P$saPHF68dB69I2C>MapiIRJcNWVx zeks8?MV3d+6Dvl{isq9Kg@_ac@HqHYEP19C2BUNW4RB=QNu20-y|cbkxc@|K{W|Fq ztp$3W`Z*3?eh=5JsIna@Jb^1QIf2x=c6?i?{)XdY6QG9PqoEHxye;``pK8KxQDLd3 z)p24sBVv;_1y!nBA7n!yleLK@Z1!@Y#=rU#$ykc%WI7A_am`VxNAvQlAff!*U-IaT zBt1$4#5FyzjJG3aeYzR4R$3ktO)+*fv`$m}Y@uqB$|e{0a?0jFEgz{7T?mfEtKGcYSJ*_CV{$m9=~_H`;}dc>C|2WU*C#`%XLhG!WJlG7o0Du!f_<$JE~ zomx>FNvMFNSs~3<-|!nq4h2A}+vu#W)jITi+OmCaLNj~kE||6zD+iX>6Vv%4><}>{ z*c_7F{BfDv$MAgO?V7@1f4jZ^5-hdxBaU{`-sjulg3p@GaQ~k^C1oBbV9M=}b3j9| z968lM?fI-uAXjuevsh2Ix3yzR?WMjC8dS@t3q7-A-}{@3R)PF=EsDD`GF&kCHs}o3 zY`k-M+=V5LC&ji{!v=b)lSG<^ix3{0bB?6tg?D=wF(1>&&*!RKwahyFY@sR5zBld( z2nXk}qIYThvwX*QUkQWDFdnk12e09C9@YrFa!aid82VEiy;<`S+*K$EdAo^iTWJuv zGTZLjC%^S`+ZQ^9x|^1y3cL)|Kc+3_HPLos$)h72l`P_T6I1;~Gfx7@YXpcyL`5{V zzwduC09GzcnvzVjtqRA2NK6|KL8jemc!;agp99@%8M1FjE@>#rp71f&>qLEWPhF=Y*S;cld5cAt38Q~MGuXX5%eOt^vY_SW|7g>W!Z{kbm5 z?+0=MXD(u2C7e=*xZ}lhCq1L2evuG;`2~8!o$^>6@yN3DV4euLxJOR5eI3q{{cs$)3%LvRJOItbSF zIisg4Br*MPzF-~8U@EIIWE_fA;l%zt-Tbqg@;#Y;w?k&%`IWmcg_7y`08Te8k7vj~ z(u(s`Gk>PAJLnzZ%vba=h|@QUN|s9Fo#d$A9+o^?Wy%Srp6*98z)zMek$ls>iVsN# z%@l^p9;{4rZP;WaoU!l1guQG%h^aJdZ)!w!? zIe;JM`=Z*-)VpLPa2c(>sq4mC5q8eA@&rECfvlOQJCSY$F;}c z8QV%r=C0eVVl4wqwvpeKV55!8wq!M@h3EFAM%)!R171=()v^y39A1Ro33}}S>?LhL zMMKu=XC`@An8hO}B3;S%?jfi=a-QOFMB|wi)&QQX+|)$%)b3+$VI6=7HR7f$NY0x?)+PVndaT5eiS>kQSv#|Xm^Q0M|Q zH8LiBPxwtFgGa%M7HkRLwUtnq7l>7TdnchRhVNA*GHfQX=<;5~Fp0nOAx4f}@+V1U zLyfXvIvXYL2sVMpHZ9nioNOcK zTFDQw4gx2O=OmGgF{jOoMAYFHi>@P|m&|C8aUgWBwcQpvp{^+U7hhZ$*Y)5bnR#%KyFd>))E<{|JcC_bVJL6`?pH-e>LR&r_Hzj|HJJ6 zd9VMXp|kxRi2vJh?u2JihOB}ezD=jpf}T>9P`94jrl4$`=E^P05SJzgyQF5?FcoJ! zL6?Sg&8uc>;)p+WE_2hO5&`0V^;`hq1^B?Cha_Dhfqf0uhb40FSy~J`fDxBSZBeXX zT&wkVUhnSx(9mmfxTG5L+BukzPEfv|xn89p4d@Tnt$k>T#(=5Z5T>#mFmg0LuR=CB zzTeF5N4pD?;#q!?36En<_wFiOE;>xuDqy2=3}FWbEH}y1Agr0?5)Kv1+S)xf%AFM- z{R3zs%A2CVLXqJ@50xkCnh@$Ync!>0ebG)a3B%PHeZi%NE$$gf0(ye$r+nXBB|9C1 z8p%i|?@`Xtw4?wOk3uK{;dZC9CEx+<&$SHYB%JNS_7IeZgCYm&ls4RlEk%T>cUT-C zAkcbUEIeco9MnVtAmqqw;$_}vAOqSpB|Te{fOqSe%+Agi_S={j!g$8t)8&#y zukc`p58)})wf?P|K^-Rq3#5J*as<+}}aOYDm`y$U9Ko?ygFGiHf_XR$xsysy#c&ZVxAzGCid0k)}gJO>~j zQA+G9Kf-_QHRX@xo5|-hEowp6*^$vSkgkjPi23Ak$#bAEjV4C%2|Q&m+G3O9ElAeSV}Wp0h7!%$I54F7kJft7Nz^ z(M$H?{f=4Sn-Yk+7U_hj(3YgYw*^&)er#{dAabSzgQbMpR^?(B-AD@@8(5MKzKM&& z?QUegMp$;q>tXGdtug_>OWa<-8HqdlARqb3!x)OT+3js$kt>rYC2**Y*V_aqv@C2 zfDhr)QGGZq2AWI5a2pF_JmV?O?5A8Np9(ehZRnV76It6~q=eRnQ`UKJbNs|{wi$5P zE(4#lkE+YLW66%&BxJ;E+RqzvD1donUeeM<)4_sJudfijxMkXNm# zQ&sHl-xqjnmc_&k4h`OsWVw~*un51msD|AxbrIM#qechLZG|Oqz)(1%oAD-G&1ws( zkRVn+=iw*dPX{8)!vid`4Q2^`t$fcu0DHl1(9O?4m@&EKjd|B2pa6Aj#wb^s<{g>f z4pB9_e=m&UlJ)!2Zz&psp0X}&iOyAb9AQrCCG@dooA>b0(=o#uc@AIFHz;}!bIb&{Apu7;hLJ0)^( zJ_lP!>pqK7tH3|}-dsWFcb2}{pWe!p!S@3+l;WlZ6UKgtbK=yK$+B|PQS)eRC2*mz zkxtgw?7k1`JTMYIma^jjE1Z^$(k2oGN-dyu)~0_rb!S#q%p*d#w*9ucysCv=r1pUx zR#;efoqgc&s;Yb%xAzGfRzt=tGYC;w9#r|&oWJT$uSph`=@Wtv`*u!M#@mz2jv6g7 z;`LdvSRQlI7!|0DAcijOL7R{4D#c-AFMY^Ph83C~dC zBy(b$O-6ZB$NEOPq7_s5*{U-WO5~Kic)trES|f?vj$(=h7eJPK$Rry`bLlTXF|qI2 z%c>!{_a~40t9HGX3RkLLnf=p7F7MCmg|wX?{3R^FBK&1LQpUD__dv68{pV-Q-h(rN{DnzrDta2m0o|d;buM4NCWxu&Pz$7g0J-P zElk!nHHh}h>Ye-pE>?zL_SZCASD7vdb}TC{t@`+nN>+KYf=*XNMmi1$46Ojh1Zu~a z33tA}THx#^T|FtWm2B=V-8Cl)S>zj_VfV<;OXW^)u0+niIBSL8=SwnIyct{nI=Oab zbd^o-BEmiEr0e*mfb~*ylZNNg{lLvb-+qss9(4Nxa6Fna=Vuw0jH$Oz;b<-9iwgto zGZikwll&^$9Grj2JcVnBg~l9=o?p20Es7wWtR{eRByjAv*XpnvCbxM1(wW9F-^_RX!US%R zUvsza?NmC(vyB*HD4}u*?t`l`r^UqkNQNrCNv@uZp&aoCtEh9vRS0r==nGUK7&)hH zY=k8cE7yRZ@!2eB}>sR;$p-q!wiXdM%f>9cXb;et9wLndm+W z_)IJyrOs6T; zZc*8GOSG@uup2G+0FlaiVLASukPFoE=~aKk;m8N~$?nYo-!Y!kq6|jxF@4(D0=#`I zJK;62Y=uj!?U{EyU^)K6xb{7SmeQ3*GAZR8v^%VOi4z3Sl?CjOL@Bu}lm|DIZ504S zez~QQyAU;Hp61oI&4o>Pkvqj!g|F3n14zvcdyln^I^ai1W z(vpGBLt~67ZZZ9WQ={z%yo2cbdb#P|Oesg&r?wjBgd(dXkAvt^zr5&0-yO7tV+SW)N6D0fxEH{cLYU&IIvdecV)Y+?u&nen#h>B~ z8(9S>=KvAIato$C^AoykXrPi&T=AE`n;{zlkN1ENCh4B(kR+~7PhX3n`Jx{x6^!du6IZ{fCo@e_APt2 zDY5D67m>Izg^gFGzI|kmiFJ?1F0Zibs1T_c@kEU0aK%jRSdI7$J#w^I91ezHkl;ag zS~Oe)eHYYj5<1+M8xGYu@xgUS&fyoyED*@DBnOmyY}_G8P|-(9k>H#_G`6O7EU+f?I*S zcvo0s4jsC!ZM)u_w{kOLu&8diJgFXB0k3jYCa6a|XA6B6Ctvp3G4f{hm6Stm7PqciH31I@Rkj4j zaXQ};KrZ<|H{WC{ZP>JdITiBQf13T|f8X3WigmvS*LG#u{CFbezYJeoW~J5&qZUHQ zC4wl6oqT&EQBOh0t_Zn%Ni9UPvU?wYJru&531WPDcAkZkw$H*0T^qHc+?AbjvGvuY zRcggBh>EvWAxYNBW{WN)vLiZFMca;7$3gpkdBEhdi^bQN0@_xVvPIhf)i6 zBWTa@+y_1Q!hICH_v+hfo=EMe%%WiFrrJAgdeXY+PK#=+%nBQh<-u~M9Osl`5paR7 zyC&VzE7sfJ3h}V!4G@^Xm%N5G^c71Hv2(Nt8dLErOAn;&=kZCjFY?(0(=peO36J6hUO6zgtCK&JoIRyxZUfivtytFT)f3{ z6qZX<+-k$6%vTns-S))CBq|t(FIOO)zZlG<%@m$MCLZ$eMK7R%Y|UX*KKl9z9I$Ml zJvIzBA5lfu7`eVY*x>2O%REwGK$c!%dIY7eF%_?2F$Yn;+~TL{TrN6gwAB%994-O?^CAf*iYW6$4{)^uu-ozV-i9ej4mI2(`R9$t>0nNRN4SqQ3GC> z^$CeUyJnmR1-glOgb$k@Q!1sV(pq5cl@w_ywAG1!SW|n$@({@h`i|9u57Y;)OM5_0 zB1)|bZqVH0B)GwfvaS;kEI7z&e{!|5y^qQE8;Z{73=p^F5uEYCkD?KSD9!`+Ha4$j zRbB7n0xPUPq|_ZQ0(f@1YN?vJ3jrSWqcECR>DxLK1Y5|W7Cj~Pl?2q`mkT8sYlXe? zACAM3t_!?Q!#+tyqc-Nh@vHMsCjtZz&Q1zxk#TdETd+@Ti=1t{U}PBO-yv{*Fx{Ri zPMci{XH)FZ=-o}$C5@2Z#<@ClqcC$2~Q>e-P zDx%9u#d=6lQ6@@O0C$S)C`GcqG$5ls+F%b32+s-DlI&h%6=s4(z5J4(Up|)_%vVLY z5RYK3wgisRPI}0}wn!BCviKC9`Bk8+dy?7%??_fH)N0XmM~MoK6kV=DWems}7&06R z+T$KN^)ohS+(nGa=gLUfnEK_1*NQ04E$^qc$P1yeSJ-$~m#T?GO;Pf|cYz^bj=No8-ubat6&L;pU|J`AR+%2X|vcEdu*4 zIk0xFUtT8(`t_SnqTZT5t3zrAN#RrAu(Ge(m+~w?K)>g13f}hyEqkZJVPz+^NP2>$ z1?7?R7MyoAfBI{5%i>@MuaaX@eAdlgkuXk|Zh^PeNq^?TwoOnm5`h9OX_-#S6Opy? zgaSgeix!*UU`X^nmr75qnh0ElFnMiElk?jS;HfjyOFdx>^wNY(2KXbcHEp=wkk%<( zW_6Eyni;src6M4pNkg4fnQF@`l5+u3AJt*|Vp9RQYOE=F+$Cz3i8;2=Q59)W42^)2 zSReetgPNrBOxGEaK7C!i(A1b|rNgJ|;QlDz31KeE3F<&bW%@^YkLSqCR$T>J=PnQE zT=KX322tY)cOnZX^u`cdFvPDF9bQ9G;ZK56y%*htex2dNht~YDIL-dR`!3-ghdU*}Vx_qcXC&hEo1SQC zvFf9JV6`(_FWMrgj^36hPokdOb921VuY@~PJ@3LiB&0%?yKt|H$%o#X ztk-e*`Z*X-v2B_t65H4F`FZe@ru=2Kt4Yrm=cVuqBmerQSqS@M`25FXZ+n@yTH-EC zr;H@m_grocUuakzVq{e3XAcQ+mi&Dhu+95O-1qN0LF?)RzJxH^OE?$X;15&5Oi(T= z+5227cDQR`CulOykJ60I*U(wVYlfiGI?n8D0SKcEi_1T8l?moe6fsDfv-0tDoJ&2L z;Vqomh-@X?afLMT3mGFvHhSt02;=3cO23)f(d3U%_nnIi?O2M2}x2ug;exx_U8 z+TMKkq<;i@J@zTb39KF^1;T$S7UCX@>G z8nEsK6Aznw*GC6VzOs91W=GUAh90LZi`QKEmhF|deDcxLYrS&>gp}m>bf`QR#u_Nu zy3eo>JC`aciK1Txklw45dGGi((UG}GP4`XuzDK}OM#6Vr(}MMW8(|;rW1^%vXdn*Z zy*v%|#rq>@(N{itY|_hK?lZn87HsM?3t<=+(X<8$0*Vr^h4Qg9q^MlcgQ$+1QDg=V zLNt>tGiXMgY!Hg{1tOB}ku(J#H+PoaBe79)1TGqFTy7g$ccb>~(Tm~XWqdqaZ6$?RwJ&CAoE9TpZC`4?Fb zFA4a&-hhCaQ=*xGn0el+*#@`Gi6K&Rt5 zy6wOt)<(IU5+~<`17{tb*%>j)q*>9BVzm+`d$p$~l)^NfoJtgUehH4}!~p3#Kf3 zJj9}o%Wx}+vNYT<>A;sJ?mYP=$1FR$8bL%j^MmI2*7A{{Aw`r-?$wF}Q_mxHWepW<47r#3kev7@ofyXZy8l z;C~%w`^^~IkMp1YbA}`U@N0(T&tsnc=Q}Y1ehkO`ZYRbc|NOt5A<6iChU6ccJpO&U zq>>Wr_kVsM{r~6~s~b_UW3?mFsXOjC8?yj{4DlsO0ZDxH8NWG`q{ic*cZ^=K zW(;g@siy<{{@iUyvmYmlw(o3Sw{5}{KTIm=*aeP6HZ+p!DT7kTG)|pbPPSASsbC)sHO<7c~k$bcm zA8`;?#@HYe0m)aQ^_N#me$CO`)n4w{|a~l&NQd?8>>Xj%e9@|(#bXoL5N@t2-y zY#q;1X$e9njoZ+0jb4X0E{;BnS<+XUc+iOB!w%w9xHiSgQODq!lf z&zX#@>s@Ff2)OPYtYe}Dhpe6l8RX)oVb(8MqzpYX5uMHKOH6wtA4!Ot-wCX@=OI$Q zQylF`f7Bo1U6^2|stU2-dM6*+GWLGiSUE^#x^DuF9{%uF5qs}_p#;7MS`U>b9Wkwr zs95e3W(I@(0D|vmCe+tqKllE+(#*mPlLSJR_!j)*)8ssDAqXzpJ@BM4=x`YgXd6h# zOJUk|52wPyG+CyFZ6lo5q(W*n0c~}3#~d!1NR8p=6iy8`TfPO7Hmq&ugkaL#987rdH#K?rcqp2!3>VIaNMIcyQcL=9 zFUnzXr1g)c8}e&^36KG6pdoqEqNsgCh!@F=SzDa{(c<{rNEyc|VbUK8GMN*maZf>L z8orhF)_R;tlrja)?R6WF;?>2#M*G zbvQOZ>OsW?En9TVzB;1N0uw;qY4T85Sj>VX#7?jutgftj@b0v3eXlpkin%2!6>2rU zM$!QV?_Ljc142-UyHT3*YXt!6xmsa2Vz~2WzlgY& z&>I+>rSEm9)yY6N*8;buk_+Dwb|u4FEaGsW{y5h$CaN?)+2{B~*_buG#XR}&|k@w6aDmSEe6p;zjTCbfY-VaE( z>!tbXkH6854WJX&*$`7flZ9hykok7%Of^2?u>o+Q=%ez?wWeDc&Prizs&8 zD1aXeRac?Wme%5xyUG|-z*5+^$Cr*Bew#OUszym@3@jC#Vl6pXd^yO*Mie?^ZKAysd0C!f_jJGwc|si)}@JolWa|{Un@-fT-K|M>-Rf0 zE{>9Gd-s;S4wW|Nd@B2Da2i+hr%;KN+nr*``a)b zOh!)?WaWFG(lIg|tuOAy@80J|Y%=kI&hvE(y7TnsZNy**y^nP& zL@9YHNktbWcQe8iwP#7lh+dqwlSx`nm2f0=+#4l_DrTK(MAk*xlQN8iA%S7eBM7b^ z`@0$ThrFVi&4*KWuDc?fQt^r5R>Wi2B(&k2oS~n;7DRptS2a0_7Y6Nz%ovy?&wQu6 ziVLevk7k=mGj?Ix9RO0G*-LDCazR53jYa9JVGkVKdCt}+naEp(kjy!%Z~B6kD%?wN*@z5vmGf>I<+E@zvg@4GjmTXCktrp zfew9G>%|cuD1zWwnGe}QGH!}6cE~P(0{M`Xb*PTYw1JF3&IhyZo(bfubY z-vpgd&_4Ds>L#=pVoV3WjeFEFm_&cavc`7WM$91`VzGxuY43izCG z0T!VrN^Xa|2kQsWeenkR5mfq|)S}zxzL)#K$?=8idD93_!+Xi&We-~7RkU=~QvTDd zMJUvr@0BSWN!99>T{ns*~?e54H;-E^B_~<|#PqwmQL-!tg+^iG&!8yt)9vW$gs~{z9;se2v}Zw2pT_K4crsaH z2-`kAn5aW{AZ`t}p}SBH`sJA~_E#!1P+EwP5+$%Dha(k6dSma^Dmc&itOfA6ZYMfPMo_C z&ZwQ+f?s48TD;RD-YC5(zzN)(AF4&5y!fQx2S4`cp`?B(T&U6)i@P<{)zS|7?CScK z$_`p;JB~81yvot%30}N}cLax3K%F)**k2;osBAS!^Eu~4>6^ubz(mQb&37c% zm|4!juR|wAPoO(_*@41XiNyiC=b3?j znFl!jvL;sKBc@9knuZ@4L9IMI!aIZ)#>|iQ(N4IG7#{i^c1PIo&2B`N*}}^Rq9cBG zNDEB_wGthUMD)+a4OSzVK?D&MPi9AyPHgJdAWPdt0xyU zZK_@_*x=Jw99r#%-eBo&(gfV^S8p%NiL$^ExiRI$H5WLy7Zs~Tgl3qpR9R$<;lR3lk_K?*60oPf}C6Rj^2 zbG<}D{$`2LjafnqAJgaznM9?iGTTjK+LW#yn*AplxB2e2GReSX3yWFGnOUua;FUK| zr-2yhy6l+qCxZ*1C6nC+g^HixbK~!%H4jw0cs}6fzMH=zvm~Z0+elGlNnc?`E4}zM z|4rVv&Gqs;L0N3zwu}F^4KRt0NjSkMi;fV3Eon1Lf+Vn$A^RX8ytJ+L0mowAiA0s6Dg4}Y{>66F#vVf~H`fbcIgmuOBPwq5D#~W%e9(wkZ zp-|%-J>JMjjjvicFZf3j=90vMaC5ej2PaFvMKB0@ZJ-4GIg-m{f?n^UDpE?r`bOY6 zxZBpd+4?Osg;mUvYG-9?OR=e$HE#82EZI_+7QWo+3B~pB1deD`LCQS4k+x`IkVT6M zxc314tc-H~lR53Qm#s4n^_tDM;p_IXPeCPfFK9vQl@DrG^;EYHu+O4K$!{}7+5zW6 z2r8}zOUtMh`|}sXk2|z#11z=WAHK+ioIcAk^WUx;rT|_RRF|d(`<6I1dskErs%(R> zBS+E&ONYvG`ygz*X^QGtQ%;Uh3|YA(Zo+~=k$>lgq5FA#LMZ;#L^b7d=E?9E1JXR} z3y)-DU%4LP+I)wNOz-l*26g6-#wYZo(!{zwVJR@*V$VoxB(miqJc)CXS62G`we0w* z8%&z^1eBq<;JMeF+$TdeY~qIv0V=K65Qu!;8llD-h^s@&7>y`)Vy|%$X{z2PyCUFk zc54vA;?QOJc!pPSlhz6->&_5})}|RvB(1rni09mQG`L^}7sx8{2=`qE<^mVnffmS( zXSK@5)6@v_=+n(Tp>Q@EU5t(;muRxRuvj1dPy%saN^nm)upJ~zV8EnQ;R+x=)Am&_ zI~1cVt|xeK)@XtIgyPV0FQ-ZI%Ddke$?X{?GznAtaz=bfoa#~h2`c)V?C^ba3q+^0 zrNXJGGbKgrweS9c)3 zACs8rA&mF`q=D}GRYe`#R*#v7?rPCNQJG$^bkbn+14d)}~Ve{`1G$PZdI@ABA{$;EipJfDqsre=Sf| zbT)8uvorpY{$QXJv9)m$GIlg{Ft>BEbs+pBe<7vs=p<;S??A`^pr=zcH*zv_)C91y z5q`fIfd_JbNBaF@V`C>|VPPd?U}qp?WMuq#0pBsRG5stHeD~*fe}40Sy8l%k2;)1P z9~Tf813mEY?(fLHU#!4Z_1rqlC`TNJr^z=X8{~!=Zz>l)P_dmY-qugKL z{Q-lWiRmY-@9=@}{v_i2orwilAs_+YEBF`jKpcPm{(`@g;e{{BuQkZx@{0ewf~?>wOsGq!Rub}~2A2TqoD_=6ja06JkCLt7(r8&e=x zZ1`;)&HsA-F%|s#mw#jWuS4Z!2$_Cv#3o1hGeh-%5TEZN_BTMk7Ucp3@#oZYIYNdX zN2JjGfW-Kn>A?E^MiPJE{Tr-b6YgaQe~L0trhm+n2ZCY(^7*?^GqD3@iv2%?`p*>F zUqbzBsyncLKR2fPCs}{!2O~Sne}ek4sQPbI{W^k0(aG3a1t{4+R6|DJ+W7CRu+lem zBm}bIyUG~bI1$nS=vjXh6!^a8n}&gf1^5cU3=~!%Q`s3AwCVVN7Gz~$qZ8D(6Eik9 zHFE+s6C0h{AHSIaj6YM6|5QafaVLE%b3=X`Q>*X$?0u(73`o`ANdA>k1v2GlN)y*()_ebzd-$vv%f(7T)Pfr z%1`P0?@|ZUm4Arc50?H3>gRzxbU;u)c0Z-`Z)E*4fWX%JOICh1+kYqPC+5F_ z`iENmDZ+mz3)onU^uX(X6T6>6`ZrL&6idO_&`FaKz)TA?1k7x-KzqcS>}Ec6VttSp3oRF{Q;kru%A-|({k4zFN1{usy7 zzu^6)%|ENl$n;&R7}#imS^@07zdrzFi{-xx6$8WX(1M#pc-(7|{~j;%cP0O+uRrm! zv(f@vnUR&2nF-kcFw)WkpO}DF{oh530q{GlwTnS+D)j$?^t&$q9Vs&lEi0iVN2>%P-Uk2qL%w=Msr3X4cKV_K(C}GV16*1fI@CLnr z2w(gcw7-ncANykf$DID_dMe6Kt2AC|dP(qRA=^a3T}>5c;|yTK;NkNW|{&P(@^GACe)WA)mh?dUwSXHT*c=p#j_#l8n5uvhhj`gD5V# z!!0(4#{g&SL;eR%(=H(!w}9uhYU+^M)9-KK3fj>-+0kL z+51yKgv?D$j2(<^fE&|l0_bF|jZO7|UJM(ZwYiP6BOwDD1D&-quzUON9slW9{W6+B zxA#vtf6DLolK-0v4fKM5y%^mu7a9om4;Px11?WNpwdDU^|F;c(Cj;Nz-rv3d|KmGm zR-j$`6|IQM@QcV$DY{u3*joK9Vgb-8*(%$Z17j9rV4w3tG5*vnpp*aO`8R>f4;A`p z)Bo_sflEe#q0LWk{GX_RuSLwQjDexq&%j3Hhl|eimxm4<6$cji!#!uC2fF9%j6ipk zo&I}333QhJ@XzW0=AG03kNt6W;4B1Sn5<~*L~dW~p|6LY*q~|L`aI_G?X`*)MNEz; z#oz45FDK+5_Ct{F4@1t(%*4av=;UCmZw(J)*v~j&S5c?t?(8<9pDW?E-GZ2$1C?qz zGsyuC(sNNJ>A!JJN+YUV3I>@vYP-5I)%e_3I|eTF^;4Ac9CB8Va_sOmGXlsdwEHP+ z1x6ajC=zgY7joZvrbQ(;e#e)gXLVk9yQ-Ubq$Cr=DS`Y~kYIPxqz$jwd*ujUTzwf4 zc%y2oK%)ckj`!4yQI!t-w7x$0Uf~|Pss8!2At3gWY{s^jlEQc<>h|r0r3JmZD+jYH zafPbPQGfpR{D*fm?1dc*dKTahkumLicDRX2pZTYgNn1O|vmrMHQuhhYX{MRs4&5MC zqSYXLw+-~IO7BfX!`PHvo4?ZNvIyfm>gW zzlev_)Uq`n4S3E&ShjrJXy{MpE5=5qV-L64>5VO>6GRM=P1S|t?P+}WkL7>+JcgO^ z(Kxr1(CjVis}lp(_cME|4#}}jId0jHkD`241LF}qO&^`5>+55&=m5ti=buGRLUyED z#V4C_PqzC&oWou%s^f19)pIDy%%S_e9;6AV1g+1}|5i$8%oGO?0Z;bQfzg1wtUaF&scdm%!? zOz@#);hj`XulK(Z_n#i-Z;AU4XBNP~@V)8&U>pF*MgYtIgmJ$O8Mr-cPLE_xd0b)d zf6q7|rawKLA2}zWg#@tuX`L7WfZy$XPbUK`^@qWfHLy^0vi@bm8i&QccFb~a$(5}> zDx4o#tc+qj#@H;ba4cppT@GyH zBPOou^d9mDW4p{h&E#$R50TuF8NK>k<=-fl?&})97{gpc(X((1LT^xKGx)*=M-sNW z(lkp&Y*5B)Q<6@7AJ6~}y(4Afg^pc!UYAR47zy1Co;7o26cz9Djw;!v(p^ITBGtS$ z&*Cb}yNtKiAm>AGJT|FoEIsbRc0M-WX{dpmiJ$6w;|uWQb5E?LMVB7I;U2_XOZAc) zUzNG#MHy1>xW}zAI|Rst99!iR65GJ1*<};yO)8!`Cye{!71Y}J_IG%QMLt<*d=FTK ztqWfpCF-o?-nV3 zph zsj`3N>pL$ZA6Ea1i^aFAr4hB~7X$g3H3_&D(1IrQwDFPRZ#K!4d0*hzzDq9KMlZsi zuwPGjR|nZ$yL8@o1cMxB;eWr>x={;Py-0AR3}BxZ2JYbU{GN%Ra|jXU6FGzj)720% zZdpo)But6W%<|;ouJGv)O>?OO$+r>z8eIK02vvaxnm0;b?5P1|MDDP3vI9sIvNa8Y zkfHuts0T*JN17couf2l2v_892G+a{?-u;U-61gVe<*ng9Yp*v`Ku2o@_ zcaayzfZCl$rIifmP_glTVC$#Gy6aGgsN;TZ&M#^qk~Ih8sr8lw@nzx3EFx9P5|}mFeReBe)QujO8&;FplJerImRr!P%bzTr&ioMC z$!?h^4t&_7iiuki@5y#F(2585W-37?cD>GW+nvq5iNK$Van!gD<&LOR-%%#2Y<{s(ja$@ z?!{kVNLVxz5J$31_Yn#g?eO<(U0h$hdJLd{U6oWZEv={|j1m}?`Zd8H4|GV~5#}sp zMs>g;h<$(W=J}DU_WFpv$;F1Qo|Q$ETg-*|#L?Hx=^aFq-YlytijKJ*y;HAIlX(Zw{Zs zuNL->G#AHIq;nGa9aM#dOj+zvbA3Rw*Z^;$1ey1A1peS;Aj+4O2!rSzN>g^+tJRlN z(vQwmo$!erRG^m8d6!M%((jAw!>@<6ys8Fj45yCazzbCXwOt7H1AK2}atO?{-_0fy zcQ=0p#<`5~H_E5wr7d<&iT0)ep4hjhM2CNT0Fzr?WFb_Rz?8OM|j4ouf5zp3j)Hj2lNcol^}H@rD{M z>7!-s5($(>XCe%`XsXO&o+O4O@m}}6pWCx1I73|tA!-k4>>LZBFrqq7HmU9N>Q-L! zU|T7m>{+2INjDIl?&M2}3hlyRV>^IVs6P%GZv(evR-8JA&DRtxcsMpQoVd2?FL_Nt zMxc~JdX}HR^+8dNc^lg@980dt<`e_Df4XfdehY$C-bN_|R?l~q=f=`d@vslTN(#fsk!CUFPF%9i%fQ#siR{tiR z?0xRycetwekk;eBH76J4B8G+2?9*K+S3h|iTFjay=FDfmY2$__hAMa@FhIhE@-UocPo7ntvwS?{R4#t(97{}2F2Hhk$oT{M#|Y*oIC;+_BoUYGm9Cb z2iabh8Ng-S2rGExvqPm!SlOefkt;gH^>hh|BNkfmezk?Br}3H1iU))z2}^?NaXRhl27_=WPBGZ=71$l~P*N z4lZbL3sBEHS$ngg(gbDv>?x?;IcB_gXJ#?$aBIC?I;m+DyXr!kh{+J|a{Oyd0b>qAR4hUhqZezn{_QMK_NElDbt9Bo-Y6qc(bXM0c7;-W5a zV=@Ls@7}UvHrh&g1;yYuN*r-3cxx#(-}0}gQh3_CgW)(tlfvQ#RhUm?CqOTtwT-Zc z9L_alVc%4JMK#VlGr!IvPshRQOPFFHUaP_xU_O!;H|9E1F@^6v-W&bGD*fSvjfEjb zD#y!vI!ZFP4*P@LO(!K(uuqwM`xS(x>*uUoeTl8F{sdqHOldXv`ckBd_sd?`1nEPi zP?jmIr4p^tld~lv<*nfNp{$Rtbhr(>*m-RF`C%aUMAYje7L;4tzKsthkuFL|00CCR z>Fkv3G7}$*E=>N}^i+q&p7X>R#D zdY2)hZv{e8wfhH78TPCQ-*R{_eO>l-4c+s?2*_F*qpz z%CR4}2T~a>R?B|=;cRF}L6hxXUXK177x118@l5u3H9$x22skKdio>l7kaR^V{ohNIm9mBLa${iHmCM z5Cm=O4)NaT?|DQ3n$Tg=kjcSrSj<`Nddm$^k z9+gJ*7zuUGQ0z6neqm9YnY-Ml@&Sbi;YPPP)tvp|y!?IYnXz{DN{y8cV(t(P{`eNi z*sI*M*s(V`_6U^pyp=(H3(c$eY6l~9jeIdya2F`!tnpH?gzO>_?boY;`qU%0(yBh8 zbCeg38_7sQa0HigD7P(f6pFJl)2@pfD|$>A65A>{&MCTF6@%av~lPMb!9pp5_=qJW&u~zRqIr3*5tES^fGH@YnYb>l2}e4KMO= zlAH=(V6X1LF!H^{^IP^nOAp{lq$}=Y7DfRVdtio!ASbcnxEQU1A8^uhH=t-Jt0hGd zQGXRSIC~Yn0HEMT8Ue)U%{~f369JD3x|b*d$!%Yo^FyuJQxfne^^ppK&u1yhv+U2? zKtOL!VRQ*wKcEAR^4g0(bHgL(NU1il1jJwKEY|kO*y|(2sbf*}p?~9fTcWhaA48Ub zB!kngQxo?I!1SB2ndMh*{b&5c&dT~{_`|@&_PcG%{`c^QPT186IO)QX@JBQS97O*! zqa&H1j}8spHp*lV_E;#13x1Ax=(OnW0fs~K*jD?Je$Dr&8>X{7#$y7oeOE zlF-)}@QUr3IF+9w-y+kbfShyhi{vDPl+p$d&z94WA)-M~&kN*}Cf?wc$P4JT8P)fD zdUC4hqGXtF=d>)Bw`#c5Es1A|p0Wcqgt62;U-&B2(NjM#XJ$bZqCLj5$$WY5wdLv{ z@_{+PKVaH%XAuAJP32Z>8@7LR8h>+$H>IQ>nblCK)MJr6XUmZ1H-WTR9z)$zjkZqiJSL1uoTrTYgGwLaVDN&vfm(_Pf1-a%*PpqqYQs<1xdI1O@5+zb1T(|Df z{m>Q#%;LbNf}00&53*;a+z7OzT&pPADE74W!2;e7h39fnE+m5l=$;$*r4ALm1-R#H z`6C!&^HK#6}7rtBG4P2~HV!chvy&@T}D21zk)NI1gNs{*u^NuvW z_2vDn>Q`;fakv)t-$u}IX=68k!?FN$12p6ECI~3=FT`&YiP9ooeud2pgA!{URJ-uUt%fMhuf5C_)q0ai zsWDvk?6%nC}`N!Ll#-(R29)eohyJ%RZW+FRCJ)<}+Nh}8Q> zQYWMxQtQK3+>v>{WYwnSkvI(P9W1`3t3bu;$U1B14y?Th2I5DFXqAtz?NnvZSs6%b zQ1Cm@X1M(L4?{AXdaPU4oQZem8ZQYTId7to-^G-E4S_=U=ufw=PZFB&9LRKu@EhnI z!f(-|s5z?=C4b+ii7bgqlkUZmVZA~w9}J1b_~IgM18)Q5mD!75PnKDx+=toh?qYJEVq+X>$N0 zphii(TMA4*KvL$DzA=}rNKN?{p2@7wuP1hm$`9hE)XT4PaVHgJiB&#)S*yXG+lW+s znLCZdCat+*)q;r+f1Xp)K;$eA3Opz2?0wDp$k71}_JIk8&3vZoSUg;PuV2{?{O+a@ zH-EvHoC*LZNu%^UE;xr2j4UrAOs9123wcgQ`;gwtRifCmar$+6CZ&X)+#t7Zk|L7C zJ?*T>rtSLd?rje%y>#1eX@cPOw9RNS?igRcRE*U^_s@2|Q|=D&HNdv@>bbTHc>z<( zOf4$dLqhA)1xgj{v7=HH*DEe>c{LixI8GcA>tjAzH+A#PG{^Y%n`P=_P(EIC<)TMU zY)(*%MOs&Rs9Bu?F5z|T&I?x-p3q_>cpD?EtpS}@sr#AB#`bTPj{L{;hw$_P9=B(L z+xi{soYTU`k`9c$C%!cN*^#(#f^Q~4OcuK#=?XLh7UK?y>oX39-6AyM)P&@!uaWeH z7ie&Duf?Imk#Rmb9`}Vu3T=Lcm2y<_b;)l5B{U>vm^4U>y8ZOK-s9Iejp*lK2G8trNSivL4u)F|4-8#y{e!HQphCuae3G|Bbu37`IY8?Pv zgs}F|YMBPtfmab{y4=*RU2*Uf)lNL_*RgNKCya2m@@ceP7%^ayGh&xROlgNB(ie?F z81+6R80Lh) zkcCJbkW8QnRJ&J#38Go53v^i|U5kaM%L1!kB}6cKQC9X(nb&xjI`?#s!m~V0AW*t7?kd-C8xZNW4Q5vofx zTF>iXNYNH^Y-53S{Ow%m*Q_=YUt_g64)?m|{kwWcKAT9}pQ3p9cV4u_3L?xg1sN*M z$_II3LL1%eI71ST2)|8cRn{j*>*1%D#bt06C`X`-tymN85ZUNo?a|eM9fNQSp`F!S zxrWgj9In_`kT-~+lV!((WbWSal#SRbC(V<7(&B9L1*r0JvWY>}O3(OXAXXOo5vMif=29RKJrmpW!)mMX!kPE;S2tf@~D4l#Qu%GNeftPPJg(AE3tOk%DZ z%C6!k1kG#W15EB7GnmZV_NshQwJu2|Jh0m0C65RpcY*?7Uf5rf$WApxMY4@7UVM~W zcf(2}Skzlfc4rv>stu}s5g(z%#;epMLV~owv^&Za#15anQFQC_%G$_Ij73!$N%jr5 zK(qOVD6d?-m%*VJj1K61b%eV|Eh=s2sk|9AFT;#>t!VgZ4r+1}eMX$9LY~Fhe0_Oq z{hqWdL&HfkV~cK|FXIrrp5U3NdX^u`sm&xPQOS>0g_?eY4^ID2UD01NK5z5tDy) z#CE6(;_#=5j)SRWwT)1rk7>A zR}B*#b^jM>XC6;g7xsO#P^n~0qB0yZWR}VhrHC?qB#-8gYeaC2X zZn;FG;FUEr+M%+x-!|TsZ4Ss@KA(|sWT95__q=lVSl&4~Zcd-M-k#4*ym%{g?*TOf z@sR@0x*ayFZtOecuDZ!8)NJD%{-oP}K1=7bJLaFw3M+|utl;pWZ0lpmbU%5adOj_l z-{yVCG}o-}J7iqw8*nE5<$LKY`3H~GD)og*oYiG&YVtog__!p_KJnSJ{ML!8dvtjh zm~BGr>eslZ=xE3p8EvU$e9wJcT*sis$?R41d;4Fb(Js{-YO7~&;gIXyBr2 zgvgyRSDL8z9r`RXovKoNfp`2*s>E=YM)_^yJi1;%=i53jYi~i_EV&N5ubP@G1FGBR z9<+5|@I0Vp(eH>=%Au!kbE_4wH*qkpGnjid>C)EJ*7?mL$~W?63mH#Tb8EPyeq=Wn zYI(VDf$k~crpy-`U*r@i&?cqz70k~PwfPat7|XOpQc6(BU?PH!J=-$yG)351BuZd; zh|}?p{Z1@Go63BJ?vKXtyEm4Hhe{@@o3ZVv5lOySW;pN7kDk|CDqpRq+wdUi;FY05 zO?JNcW{>PS{MF6@vRlG_hPx=v87sI$_iA4Rvt6^~$NPd-!PzGh_S5@Rw#{KxdNXUd zy@=A_vyI{7p*#(ho$*CN@Mj16;x4?C==P=YJ8124?8y(U%^y5AzAqFgIC0|BPfs7W zSRU3slXU_IgUU)T7zq1oHPZYL&7|8=rkkAOr&D@E%+Ai$cWC*Vcb`?It=)2kn>S`W zO)0r+p!rDkQMRhRX4;08Dc25EEf36J_-fU{ZRPWx?s%tt_RGZ!zI{Qr`){_WIkyT| z2iMYjE@h#&4DPjAXwv&#*Qd)z;QRGg!Y$6NC*5-9t`&Rux?kJyh1}h@+rMt4?l zraSL^Hrv_a8yC0yyAbs&Uo8}Tg}2wc{7S2GOWsv8`}*(D-*r9Q91jZ;GFc=lYqercMDvk%a`Bf(y>32Qbw|Wn@x_PXH&(iN4l(1lZ-X^@7w>fSd!@`? zs><4n|6nYwBb)5pW}6VAQF3v-l5&TKV1u=M7s9mMGKG9y~&CU(C)CrN(I( zRH*RbLn~+7Bb9C|h5gE5S0nOGvmDmNKQOvI94f5n-Z6M6w!Z3<>At2!f9b9k88H$5 z1)@GYtApib%w)6L4b}<9c2wB(EPS`{+*701w0)W#xo;bT#g8JOzv%H@;Zm`SQFBhyAm~ zE<<5ORu?fLn&2Fo7i4yKNOwEMcBAxw|;t+C_bsGDIG-QksrC39&e zhKmLmx0bWYbKLS@azkp3Wgzb^^SM2-<41Zua(9?V&WgOi$+1)7`+VJe)8XNJ+uScN zZr}9t^o`b$$VDMbl%M8teavMx+_kXCc-@>(`sTB{W;Kt+j5FsJ-cy_%Wokrk1TU-A||g=yvjcI{i}(1sS>jT`q08EXYOsZ0{5Ayq-)<3JM^!zXoLG+Q3khZYE9q*4S&G&AYVSsi zfW@olcr2cfIm-Iupt<6;;0y9=o^*IuM8sD-T6WI=YWuk_TqzR8K?~0pcbsIjays^< ziR0XM3$6+p)>?tCjvdE*_`*eew;bfY8hPSS`A+)%HRr|NwfE-p9&!JillcB=mtLRD z{BsX zTR14C7djYMx$mp4`$l#3u!4`nyK~Qv$h5_Mv=V4iE8kQew3ai1M({(N?FQYPhrjo( z9|$`ff2lFRyz|RY|@mk&1A>BsYgU1z<@zv!Z5eRsY5;8v!v zhmCDb#b@`uj}^N(>-NCu5jGxxZDurn+EyjL(_ z)jG`o_$!y&(0-$xM^-5oT`d=%BkWsx(n96X3wjwE(bApyZ>$ZLa`r9V`kYmJ`$oSf z-}_S@NGZCuWL2*?`AO^N_2EL+L;b-Iop^u5ByJ!5AmCj&zu-ik;QrYAA+pE$)vkW% z?X)|YHPbHbaZAhTm_Imcs`O9%DaWCyLgHusO;iCePfy8ck_^KDO( zGT!fSirVV%xo?GD$?3h)n{wqZ8l90=>%3XOV9C7pa;a_}i(Es~lia6I(?7iR9@iA+ z4PLNsz?wT{UTWiy=Z=L(9;ymeUu~>5laKSgmzFAU(Eq1=U?tnTURo2IGntJkB?E8Y zaYReC6}=ifIG(Ezy18HC_0J_;{l=#Hv2(J|32i^2J-*qr$ZA`P@RiN`q<(TRZoR*_ zK53T?^FoH^<0Ym}vF~kpDtmap*j-+(E$y!{e(>|Fg}-7dROU5G^k-5s<*jXoX=RJ` z=+~aTW?d(FLx=U$k({;+v4;W^Tow9PZ@bE*GIY1==?$KsM_2P_`hGI~cZ`$YPvoY5&av`; zna?*5JXWfa`Q~$~y_R7jxHtH9R{cxECW~E-TYHB!-dWBoTQtWiWkl)fLbb8)rSjKz z8N}F_zx0t}-4HtNbBV=;%eAxm$O6R&>s8+5?Z`9KOH>j1q-_*tEE{T=TT|2fOzX$> zpNE(Y%shnRZP%`vU-0_^d%Z~Dp_kv9n&X|scDMO{Gbq=$eZio&!&Pcfd#je0$pf!) z718bY9@|xBMr|_K^+F(YglG2J7&`|0H@5D|wj$vcJDho!rX@IwZ)qGo!nStHre$(v z%MzNbQc8r_?p`@R-rd>t(f!~tQ~ks5bFX!X(ZwAUP{{JD7+KMfk?N!AC$i3`SYe5$ zUcs?}qV*f+sa2dDw@(VAQ@vMq*rKpTr#D*kjLGvAncco;f6uiX{S>+Lmw_wu_dEgT z&pyf5=MQE-cr0<4caBc+9r0DcA?7!ly`H_>W%xlS{^j}1Lr+%9dJpkRjpfliFl|p0 zVBNhp;$wolYy63ao`8B;t7V7<| zyKk{T^uR-x0n<2(wn5_)&Be|kRdcw;3`SxW?L46;`gnWx?7H>E%;6=B!l!)Ax4(aK z{&nuaf`x`_4I?Zgm6XQAo0{A$`dPA;E}17$W|f@tMmr$T_L|}meX-|KNmt@O&0Qv} z_FT_1-A{1S<}a6$hRbp?Vyf2+Gjj~NRM!oJ?%;KhI3eNxwEar`sRYS;flI%QR6ThA zx$RYF#5dP`dmgn$6mXYcKGGFa!h80}EUVD0BCn^RjRB+Y*fh5dQk*syZcTgnK=$o9 zXYbYuzUQ%*+;{}qV_2$bmHSV4bgmUnemVS7sekqGvvk=Nmj+G6QpPGQcO~>k>AmhY zUU{2tTS)q%vGj{^i^hga?i|`vSmhAb9zH9$eiryk*lv1Hf&G)_WtPiC5~Mz zMLavUT>j9$r0RMkZOGQ{ma+Frzt1bZxw`I@?nRIA(oyaRS#>f0uE%5PA;^z_N_cv4IfkJX&nwdboY?Kr8} zaWJw!YthYf)tqi!ItBYzH5WNI=?#=~+@0Dcq33m{&iXxxIjTp*%Fo@3;EQ3~(yG%SI6g1-g8OgY&01$;TfVY7*<@Z?ymn)+ z*6s~flu8~&amzbr0&mVs^A0p|nCXr={pYxocTC0UJEp=+|GMq}$4@k5t1}ZK-WoV? zrT1@fu3TAr?nmZ%u_TrsE)MSgI(mbp6J3mNPYA5IW-qyMe4R|AGF_cGole|y;clZs z`F7jHm&_hAa;Hp(d~Ei$&HhxFy5$KaL|tK9yY>g;rKdO7&a#i$^yZOw&VsT?O&|9( zC!b`+(>p|~PsDAY36R{fZR40~tY{!z|9r)_D!fk0hQDN7U)s+4RS;{l)R#rmVe3BM zgMN{5zc#IX)oE>C3J)PTl%R2YrB&D(Pr={Y-k(XQW!|lR zPl2oWnOJFT=)tC?t?fDey^B5m)EcZS-V=C)?c^LQF) zN_qG@JMzaHZ2^I*oU7W8JpIoIbS~IcJp1y`5l5Lr6&lB$&OUAKwn6_+t%zBs?y9V} z&qGDqlhuwUByJN~JkL$0;JE9aJ^h)Hrrw<#18)1xzUof2+}Qds;ppW`_m!n>SLD^I zc3W0l%Y3tSEG`@mFiF*W`ok@`_6E0vSd16lL%DTJ z>JQx&rqD#zytG{8v1W(dsRZ^LZt-&GVj7)W3e=x0JlK_Abzf}D$r#hU#x*tet?g(6ide#VOV#>bv7w%S0vKW8-&BEc;NlJNMw4LFXr_VIwO6rOq82W3(SY_$QYb3kSJQLF3 zeDQX>{cFuC+Ph2pdVdUB40~JJryY9nm|1s6(%aXqJKLqiv!3QK)`aZY8T`I&t<=#a z(i<2Q3VVP!{Y_C5o56Jcp=A1HjgO+PQ1*!h zgkC*)UGLHP0ZuU|&g3M^UmnX}6c>CEySvZh{-eTbcbTj*nfDbRa$h%H{$7#F;Me*7 zc8!B@rMN=vk&uG0Ssf-ePxJ*>u|COh8`V6~yCpu*S)PyekWAqszb~IGTF%d1r0mPq z{Jr=yo7RK2iwi}#svrZOhJxjh* z8u>_tUTcw-{l_=et$V*d_sw0kYpaDtsXG9Ty-QNtcyp+|4rDBm20`a&~@I|Yd!0E|Lt5ZZq=D? ztJ9w|`0s6XrPBZM7jCJWO4wU^e(=0!uW-*f^1FjRO{l$Zt;fb=6YdXaY^+y&PrJ(CF|POOb6MNjZME~GB{GhVm3|gHvNZrR58Q=D~)%kO;7vY>WR*-;1c-S;9REp{%TpMT=|u~3!UZX5=8+n&E)==f-U z$Njr=d<9PouD(kD+qRgcj^TsFovn;#MMn(smo>yb3=KA?X}J2#G$*8|*Qd{GQ-urz zO}VDagB_{jOJ2`PjnFkTS6i|A%a#VLVd_Co+a@X{su*&hJ^V;5r(MGY%78%_v_Osi#KAiK0(z2Tm z?pDU+S<#(&JeSV0A%gkWtBK)@oSf_5=~+u^P8jq!M0T~x34ZeERUW)rXC(9J(TeJy z3u4PPt3?t&WcBc6xv4Bo+~;n-#Kbq7mQQD1TxDcZlR1lh^N;OeYU5tB1caS^(>~sv ze>vnyL;4OH2JsDD1Jbh(T!`^&t*LQ{W;2znxn14Boh^JOtVluh^_y4Ax@~5sZ@XRN zXut4arF;3(=SrtOChtm+SibyK5oju;xaAeR{5sut3D^Z^vxYLR2;9 zQQkT@oIA+8mV3R?=FKCAf6iH9UC!3F&%`y%i&L?vJ)*z7J~EL;m0tU{Mo{%wZfBlb zUsm6p&#y;@eVT)Z7HU_Xuk3#m*>)!94d;@J5;u>eUi?_?$Wjq;#79x(H_xe!DYP9U z<*YVwJ5M~&E}k2q8Yl1~AmEPAvd**}m-rNY_RZDcRnN~~pmR`Y@8OP+VZp~6ymAMl z)^K#adah$ncd1|ODojY@&!6Ve8dbB~IfaXU3eN7iVZyzwj8EK>x5$G3$_3ZW5oxz~ zVibEJs-b%oO?6N>2d|7s+5WT zL)Z8-Vea6Z@D)pzo(b6`%=%?*l|El+W0#a!$F;L^uQS?z`$}$P?0GFkulZ>AhU~K` zWB&H%2P%WiRGeLJ45n<9)o)B;=Xf~(Qfw22rC7mue6vHvLb`7|i!Q2fo8exlH2u;0 z$$O#fRK%25lKtQPkay9iP72rM3u#fmuGj5+?5ZPhW!ciB?56o0%k*AKIfwpC*tBkB zOyI)0d?9a%?Uae{Us8we$B2PK6YHxz)C2<^tYqTZoltWcs8uJO%pRY z;@Ph&|CA+y)x1}Sm0_ox`E6PK&fS-GXsEw46YVz<8oKcM=P|valM7`<-RkT7M=r#z zcGFH=G#1nPU7g4Nazw7=t)UbBR#igz3_L9bb>D=Ou3K}kuQA^;(Aaog%i$5fyu|G` z7RKjMnPrVe;{~~NHysShFRwYCMM>pf_Q}QI;Rlz`agxh4?v3)ETDQ^c&Qh69!_tI3 z7Ymeqyq_sMe>{3*Wo2K7s#U6C%Q=BDUSpb`bD@$xmEyk~eIIq1rF{Qg^J1=1)uY81 z@A(9r`kHEDvbcwSpORK4#Y&LL)_5fHoJ(7WgZLb8>j5=g<|5CLA=bmU=e5o5c5g7G z2(UklDmz<$^;Tbb*zr$E%|(WfQ{IN1J>T~y;@_Un*Ez%#2WQt|7)Yj zs&x(53?6(pFfY_e5qQ4g&F{!_MpvEgJ4C5c# zJSOhtzQKN8NL_P@!`hZ$U;aU(me1#z!p`qy;o#Y(yYGa%Y~ac;5w=Bh>qBD2d)$?G zZ$6^aC?CJn!DZvU%NK5zjY|3Ft<-g1%^hHTYeD!D?!DKyUuh2ByXmD?VXyx^mzya; z7BSKdoI&5h+}c+C+^t|If6;7f;ffU{oBP!&(l)qtd0W1EWb>nY&bmsqT|+r^tMARv2M$s9=DvxQcr@FhHRo0YyX*Xo^|oa)PK|LYCC_sE z2Ltl!R)ts64w^l6yjsFAr~22Odp8!CPB;Z>d!^nTupIMuFzvX1!*6WW-gkN%ICn=( zjGWwSXld{yMolH3hlh`*Kh9zNT9eRR*0CPDK2OS#nt*rru12VcZU6k3N&Cm}{N>wP zUyEcYE`AfX=NE0{_k{IVdKbrSpPPAi9#aGR`E1WK;(eC z^;!~MOJaMMZ|HLqcO2^N5O3S#Ez*42Fr9Yjmgs$n*Rcz(kMr{QbKP5GGhsKnLFe$L zWJD;Xe34_7gMhzJ)rSw+#F|D#km%r_^!ryyshzvd`*n5{vxoSz{?Z<-fP2 z$|vltzyJ2xmN)FDN5cJTSIM6GHpl+sVZFP?lE+n+U#T^9K00f})pEdaW#o}etu;B9 z`PAut<}=e?8_G?|IW2Q-+a`Ox$BliQKYnKKeJ*w4Q1<%ax&9$i54#!+Z+U&Wz;#Pj z`c=}3jFW9qo4li(ESchE_X)jXlxYgO+1QX0_seqTdbce(J*FaaCGf|!73JBx82Pt#kHeBqJw$VXRc9cO8ed{nwL5L7NgCV zvzhGpUj+DszPx9mI)^+iWybY!mPA7Pc7yMI9K2>Y3tzF|y%(sc40#?(hW zSIV#aU;b8*roGa)B2j4TR;i2?a>CjgZ!bR&>r8(mc|`x_MBf;<#>jDxY%jZx)t+TEzwYu&>=Ifn)*+#B*(~M6sskP3be*>?WKKSbx$&*q zI^WubaqPfmHTes}39e6^w>@MF{t+c3XWw{KI_g)m27^?>#Esm8Hx()`ezLkW5!>=C z)Yv*S&|8k<&FYv1yEt$CWQut2w0)d4+UDHmhOtk|bZbIa)Xig>rPBXVJvC8e)>4m8 zpY?dcFBApV>l?0D57|Ftn7hNRb@o~MB}2mM^DX8Z##TEQBps4nn$)!|_aon>A(L&- zV-}ta`taqGh|dX;)Hla>t-W<7<()(K%TzJuAVYJ(Q|uCD=U>aUrZL}q`7nne?b7QP zX%C`v8qW)4yk(Gn#rtgjjm-;YEekDovZ!E`D{0g1-uJB0G-H$5q1aZ_6wBqex#NFtWOwXt2}-VK61Zz)ImW8$!njej zrhi`3ng@ol967DgruFG^&hsQLZPF^e^)qiz@TZ-#uRql0KNY;X@YTHC8Wz?cjfS

spRp_%UDjN;r&^ySZkq!Nbj38tO;Zdp65#r+}l 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 literal 0 HcmV?d00001 diff --git a/third_party/PySpice-org/PySpice/doc/logo/make-logo-png b/third_party/PySpice-org/PySpice/doc/logo/make-logo-png new file mode 100644 index 00000000..b73afe9e --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/logo/make-logo-png @@ -0,0 +1,12 @@ +#! /bin/bash + +options="--export-area-page --export-background=white --export-background-opacity=0" + +# --export-width= + +height=400 + +for i in 1 2; do + inkscape --export-png=logo-v$i.png ${options} --export-height=${height} svg/logo-v$i.svg + inkscape --export-png=logo-v$i.png ${options} --export-height=${height} svg/logo-v$i.svg +done diff --git a/third_party/PySpice-org/PySpice/doc/logo/svg/logo-v1.svg b/third_party/PySpice-org/PySpice/doc/logo/svg/logo-v1.svg new file mode 100644 index 00000000..89c9a7ee --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/logo/svg/logo-v1.svg @@ -0,0 +1,257 @@ + + + + + + + + + + image/svg+xml + + + + + + + SPICE + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/PySpice-org/PySpice/doc/logo/svg/logo-v2.svg b/third_party/PySpice-org/PySpice/doc/logo/svg/logo-v2.svg new file mode 100644 index 00000000..9ea87e90 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/logo/svg/logo-v2.svg @@ -0,0 +1,268 @@ + + + + + + + + + + image/svg+xml + + + + + + + SPICE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/PySpice-org/PySpice/doc/pelican/content/pages/legal_notice.md b/third_party/PySpice-org/PySpice/doc/pelican/content/pages/legal_notice.md new file mode 100644 index 00000000..ce85660d --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/content/pages/legal_notice.md @@ -0,0 +1,21 @@ +Title: Legal Notice + +## Copyright and Intellectual Property + +This website is protected by French and international legislation on royalties and intellectual property. + +Unless otherwise specified the content of this website is licensed under Creative Commons by-nc-sa. + +## Personal Data and Privacy + +You can visit this site on Internet without providing your identity and personnal data about yourself. + +Apart from the use of a tracker to measure the site audience. + +## Publisher + +**Fabrice SALVAIRE** pyspice AT fabrice-salvaire DOT fr + +## Website Hosting + +This web site is hosted by the OVH SAS society in France. diff --git a/third_party/PySpice-org/PySpice/doc/pelican/content/pages/page-404.md b/third_party/PySpice-org/PySpice/doc/pelican/content/pages/page-404.md new file mode 100644 index 00000000..f269f7df --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/content/pages/page-404.md @@ -0,0 +1,9 @@ +Title: Page Not Found +Status: hidden +Template: 404 +Save_as: 404.html + +# 404 — Page not found + +We are sorry, but the requested page could not be found. + diff --git a/third_party/PySpice-org/PySpice/doc/pelican/pelicanconf.py b/third_party/PySpice-org/PySpice/doc/pelican/pelicanconf.py new file mode 100644 index 00000000..e64cc65d --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/pelicanconf.py @@ -0,0 +1,828 @@ +#################################################################################################### +# +# PySpice - A Spice package for Python +# Copyright (C) 2020 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 . +# +#################################################################################################### + +#################################################################################################### + +from datetime import datetime +from pathlib import Path + +#################################################################################################### + +# source_path = Path(__file__).parent + +# global SITEURL +# def site_url(url): +# print('site_url', SITEURL, url) +# return SITEURL + url + +#################################################################################################### +# +# Basic Settings +# + +# When you don’t specify a category in your post metadata, set this setting to True, and organize +# your articles in subfolders, the subfolder will become the category of your post. If set to False, +# DEFAULT_CATEGORY will be used as a fallback. +USE_FOLDER_AS_CATEGORY = True + +# The default category to fall back on. +DEFAULT_CATEGORY = 'misc' + +# Whether to display pages on the menu of the template. Templates may or may not honor this setting. +DISPLAY_PAGES_ON_MENU = False +# Whether to display categories on the menu of the template. Templates may or not honor this setting. +DISPLAY_CATEGORIES_ON_MENU = False + +# Extra configuration settings for the docutils publisher (applicable only to reStructuredText). See +# Docutils Configuration settings for more details. +DOCUTILS_SETTINGS = {} + +# Delete the output directory, and all of its contents, before generating new files. This can be +# useful in preventing older, unnecessary files from persisting in your output. However, this is a +# destructive setting and should be handled with extreme care. +DELETE_OUTPUT_DIRECTORY = False # dev only + +# A list of filenames that should be retained and not deleted from the output directory. One use +# case would be the preservation of version control data. +OUTPUT_RETENTION = ['.git'] + +# A dictionary of custom Jinja2 filters you want to use. The dictionary should map the filtername to +# the filter function. +JINJA_ENVIRONMENT = { + 'trim_blocks': True, + 'lstrip_blocks': True, + 'extensions': ['jinja2.ext.i18n'], +} + +# A list of tuples containing the logging level (up to warning) and the message to be ignored. +LOG_FILTER = [] + +# A dictionary of file extensions / Reader classes for Pelican to process or ignore. +# For example, to avoid processing .html files, set: +# READERS = {'html': None} +# To add a custom reader for the foo extension, set: +# READERS = {'foo': FooReader} +READERS = {} + +# A list of glob patterns. Files and directories matching any of these patterns will be ignored by +# the processor. For example, the default ['.#*'] will ignore emacs lock files, and ['__pycache__'] +# would ignore Python 3’s bytecode caches. +IGNORE_FILES = ['.#*'] + +# Extra configuration settings for the Markdown processor. Refer to the Python Markdown +# documentation’s Options section for a complete list of supported options. The extensions option +# will be automatically computed from the extension_configs option. +MARKDOWN = { + 'extension_configs': { + 'markdown.extensions.codehilite': {'css_class': 'highlight'}, + 'markdown.extensions.extra': {}, + 'markdown.extensions.meta': {}, + }, + 'output_format': 'html5', +} + +# Where to output the generated files. +OUTPUT_PATH = 'output/' + +# Path to content directory to be processed by Pelican. If undefined, and content path is not +# specified via an argument to the pelican command, Pelican will use the current working directory. +PATH = 'content' + +# A list of directories and files to look at for pages, relative to PATH. +PAGE_PATHS = ['pages'] + +# A list of directories to exclude when looking for pages in addition to ARTICLE_PATHS. +PAGE_EXCLUDES = [] + +# A list of directories and files to look at for articles, relative to PATH. +ARTICLE_PATHS = [''] + +# A list of directories to exclude when looking for articles in addition to PAGE_PATHS. +ARTICLE_EXCLUDES = [] + +# Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the specified OUTPUT_PATH. +OUTPUT_SOURCES = False + +# Controls the extension that will be used by the SourcesGenerator. Defaults to .text. If not a valid string the default value will be used. +OUTPUT_SOURCES_EXTENSION = '.text' + +# The list of plugins to load. See Plugins. +PLUGINS = [ + # https://github.com/getpelican/pelican-plugins/blob/master/i18n_subsites/localizing_using_jinja2.rst + # required else CRITICAL: 'gettext' is undefined + 'i18n_subsites', +] +# A list of directories where to look for plugins. See Plugins +PLUGIN_PATHS = [ + 'pelican-plugins', +] + +# Your site name +SITENAME = 'PySpice' + +# Base URL of your web site. Not defined by default, so it is best to specify your SITEURL; if you +# do not, feeds will not be generated with properly-formed URLs. If your site is available via +# HTTPS, this setting should begin with https:// — otherwise use http://. Then append your domain, +# with no trailing slash at the end. Example: SITEURL = 'https://example.com' +SITEURL = '' # dev only + +# A list of directories (relative to PATH) in which to look for static files. Such files will be +# copied to the output directory without modification. Articles, pages, and other content source +# files will normally be skipped, so it is safe for a directory to appear both here and in +# PAGE_PATHS or ARTICLE_PATHS. Pelican’s default settings include the “images” directory here. +STATIC_PATHS = [ + 'images', +] + +# A list of directories to exclude when looking for static files. +STATIC_EXCLUDES = [] +# If set to False, content source files will not be skipped when copying files found in +# STATIC_PATHS. This setting is for backward compatibility with Pelican releases before version +# 3.5. It has no effect unless STATIC_PATHS contains a directory that is also in ARTICLE_PATHS or +# PAGE_PATHS. If you are trying to publish your site’s source files, consider using the +# OUTPUT_SOURCES setting instead. +STATIC_EXCLUDE_SOURCES = True + +# Create links instead of copying files. If the content and output directories are on the same +# device, then create hard links. Falls back to symbolic links if the output directory is on a +# different filesystem. If symlinks are created, don’t forget to add the -L or --copy-links option +# to rsync when uploading your site. +STATIC_CREATE_LINKS = False + +# If set to True, and STATIC_CREATE_LINKS is False, compare mtimes of content and output files, +# and only copy content files that are newer than existing output files. +STATIC_CHECK_IF_MODIFIED = False + +# If set to True, several typographical improvements will be incorporated into the generated HTML +# via the Typogrify library, which can be installed via: pip install typogrify +TYPOGRIFY = False + +# A list of tags for Typogrify to ignore. By default Typogrify will ignore pre and code tags. This +# requires that Typogrify version 2.0.4 or later is installed +TYPOGRIFY_IGNORE_TAGS = [] + +# When creating a short summary of an article, this will be the default length (measured in words) +# of the text created. This only applies if your content does not otherwise specify a +# summary. Setting to None will cause the summary to be a copy of the original content. +SUMMARY_MAX_LENGTH = 50 + +# If disabled, content with dates in the future will get a default status of draft. See Reading only +# modified content for caveats. +WITH_FUTURE_DATES = True + +# Regular expression that is used to parse internal links. Default syntax when linking to internal +# files, tags, etc., is to enclose the identifier, say filename, in {} or ||. Identifier between { +# and } goes into the what capturing group. For details see Linking to internal content. +INTRASITE_LINK_REGEX = '[{|](?P.*?)[|}]' + +# A list of default Pygments settings for your reStructuredText code blocks. See Syntax highlighting +# for a list of supported options. +PYGMENTS_RST_OPTIONS = [] + +# Specifies where you want the slug to be automatically generated from. Can be set to title to use +# the ‘Title:’ metadata tag or basename to use the article’s file name when creating the slug. +SLUGIFY_SOURCE = 'title' + +# If True, saves content in caches. See Reading only modified content for details about caching. +CACHE_CONTENT = False + +# If set to 'reader', save only the raw content and metadata returned by readers. If set to +# 'generator', save processed content objects. +CONTENT_CACHING_LAYER = 'reader' + +# Directory in which to store cache files. +CACHE_PATH = 'cache' + +# If True, use gzip to (de)compress the cache files. +GZIP_CACHE = True + +# Controls how files are checked for modifications. +CHECK_MODIFIED_METHOD = 'mtime' + +# If True, load unmodified content from caches. +LOAD_CONTENT_CACHE = False + +# If this list is not empty, only output files with their paths in this list are written. Paths +# should be either absolute or relative to the current Pelican working directory. For possible use +# cases see Writing only selected content. +WRITE_SELECTED = [] + +# A list of metadata fields containing reST/Markdown content to be parsed and translated to HTML. +FORMATTED_FIELDS = ['summary'] + +# The TCP port to serve content from the output folder via HTTP when pelican is run with –listen +PORT = 8000 + +# The IP to which to bind the HTTP server. +BIND = '' + +#################################################################################################### +# +# URL settings +# + +# Defines whether Pelican should use document-relative URLs or not. Only set this to True when +# developing/testing and only if you fully understand the effect it can have on links/feeds. +RELATIVE_URLS = False# dev only + +# The URL to refer to an article. +ARTICLE_URL = '{slug}.html' + +# The place where we will save an article. +ARTICLE_SAVE_AS = '{slug}.html' + +# The URL to refer to an article which doesn’t use the default language. +ARTICLE_LANG_URL = '{slug}-{lang}.html' + +# The place where we will save an article which doesn’t use the default language. +ARTICLE_LANG_SAVE_AS = '{slug}-{lang}.html' + +# The URL to refer to an article draft. +DRAFT_URL = 'drafts/{slug}.html' + +# The place where we will save an article draft. +DRAFT_SAVE_AS = 'drafts/{slug}.html' + +# The URL to refer to an article draft which doesn’t use the default language. +DRAFT_LANG_URL = 'drafts/{slug}-{lang}.html' + +# The place where we will save an article draft which doesn’t use the default language. +DRAFT_LANG_SAVE_AS = 'drafts/{slug}-{lang}.html' + +# The URL we will use to link to a page. +PAGE_URL = 'pages/{slug}.html' + +# The location we will save the page. This value has to be the same as PAGE_URL or you need to use a +# rewrite in your server config. +PAGE_SAVE_AS = 'pages/{slug}.html' + +# The URL we will use to link to a page which doesn’t use the default language. +PAGE_LANG_URL = 'pages/{slug}-{lang}.html' + +# The location we will save the page which doesn’t use the default language. +PAGE_LANG_SAVE_AS = 'pages/{slug}-{lang}.html' + +# The URL used to link to a page draft. +DRAFT_PAGE_URL = 'drafts/pages/{slug}.html' + +# The actual location a page draft is saved at. +DRAFT_PAGE_SAVE_AS = 'drafts/pages/{slug}.html' + +# The URL used to link to a page draft which doesn’t use the default language. +DRAFT_PAGE_LANG_URL = 'drafts/pages/{slug}-{lang}.html' + +# The actual location a page draft which doesn’t use the default language is saved at. +DRAFT_PAGE_LANG_SAVE_AS = 'drafts/pages/{slug}-{lang}.html' + +# The URL to use for an author. +AUTHOR_URL = 'author/{slug}.html' + +# The location to save an author. +AUTHOR_SAVE_AS = 'author/{slug}.html' + +# The URL to use for a category. +CATEGORY_URL = 'category/{slug}.html' + +# The location to save a category. +CATEGORY_SAVE_AS = 'category/{slug}.html' + +# The URL to use for a tag. +TAG_URL = 'tag/{slug}.html' + +# The location to save the tag page. +TAG_SAVE_AS = 'tag/{slug}.html' + +# The URL to use for per-year archives of your posts. Used only if you have the {url} placeholder in PAGINATION_PATTERNS. +YEAR_ARCHIVE_URL = '' + +# The location to save per-year archives of your posts. +YEAR_ARCHIVE_SAVE_AS = '' + +# The URL to use for per-month archives of your posts. Used only if you have the {url} placeholder in PAGINATION_PATTERNS. +MONTH_ARCHIVE_URL = '' + +# The location to save per-month archives of your posts. +MONTH_ARCHIVE_SAVE_AS = '' + +# The URL to use for per-day archives of your posts. Used only if you have the {url} placeholder in PAGINATION_PATTERNS. +DAY_ARCHIVE_URL = '' + +# The location to save per-day archives of your posts. +DAY_ARCHIVE_SAVE_AS = '' + +# The location to save the article archives page. +ARCHIVES_SAVE_AS = 'archives.html' + +# The location to save the author list. +AUTHORS_SAVE_AS = 'authors.html' + +# The location to save the category list. +CATEGORIES_SAVE_AS = 'categories.html' + +# The location to save the tag list. +TAGS_SAVE_AS = 'tags.html' + +# The location to save the list of all articles. +INDEX_SAVE_AS = 'index.html' + +# Regex substitutions to make when generating slugs of articles and pages. Specified as a list of +# pairs of (from, to) which are applied in order, ignoring case. The default substitutions have the +# effect of removing non-alphanumeric characters and converting internal whitespace to dashes. Apart +# from these substitutions, slugs are always converted to lowercase ascii characters and leading and +# trailing whitespace is stripped. Useful for backward compatibility with existing URLs. +SLUG_REGEX_SUBSTITUTIONS = [ + (r'[^\w\s-]', ''), # remove non-alphabetical/whitespace/'-' chars + (r'(?u)\A\s*', ''), # strip leading whitespace + (r'(?u)\s*\Z', ''), # strip trailing whitespace + (r'[-\s]+', '-'), # reduce multiple whitespace or '-' to single '-' +] + +# Regex substitutions for author slugs. Defaults to SLUG_REGEX_SUBSTITUTIONS. +AUTHOR_REGEX_SUBSTITUTIONS = SLUG_REGEX_SUBSTITUTIONS + +# Regex substitutions for category slugs. Defaults to SLUG_REGEX_SUBSTITUTIONS. +CATEGORY_REGEX_SUBSTITUTIONS = SLUG_REGEX_SUBSTITUTIONS + +# Regex substitutions for tag slugs. Defaults to SLUG_REGEX_SUBSTITUTIONS. +TAG_REGEX_SUBSTITUTIONS = SLUG_REGEX_SUBSTITUTIONS + +#################################################################################################### +# +# Time and Date +# + +# The timezone used in the date information, to generate Atom and RSS feeds. +# If no timezone is defined, UTC is assumed. This means that the generated Atom and RSS feeds will contain incorrect date information if your locale is not UTC. +# Pelican issues a warning in case this setting is not defined, as it was not mandatory in previous versions. +# Have a look at the wikipedia page to get a list of valid timezone values. +TIMEZONE = 'Europe/Paris' + +# The default date you want to use. If 'fs', Pelican will use the file system timestamp information +# (mtime) if it can’t get date information from the metadata. If given any other string, it will be +# parsed by the same method as article metadata. If set to a tuple object, the default datetime +# object will instead be generated by passing the tuple to the datetime.datetime constructor. +DEFAULT_DATE = None + +# The default date format you want to use. +DEFAULT_DATE_FORMAT = '%a %d %B %Y' + +# If you manage multiple languages, you can set the date formatting here. +DATE_FORMATS = {} + +# If no DATE_FORMATS are set, Pelican will fall back to DEFAULT_DATE_FORMAT. If you need to maintain +# multiple languages with different date formats, you can set the DATE_FORMATS dictionary using the +# language name (lang metadata in your post content) as the key. + +# In addition to the standard C89 strftime format codes that are listed in Python strftime +# documentation, you can use the - character between % and the format character to remove any +# leading zeros. For example, %d/%m/%Y will output 01/01/2014 whereas %-d/%-m/%Y will result in +# 1/1/2014. + +# It is also possible to set different locale settings for each language by using a (locale, format) +# tuple as a dictionary value which will override the LOCALE setting: +# DATE_FORMATS = { +# # 'en': ('en_US','%a, %d %b %Y'), +# # 'jp': ('ja_JP','%Y-%m-%d(%a)'), +# } + +# Change the locale [1]. A list of locales can be provided here or a single string representing one +# locale. When providing a list, all the locales will be tried until one works. +# LOCALE = + +#################################################################################################### +# +# Template pages +# + +# A mapping containing template pages that will be rendered with the blog entries. See Template pages. +# If you want to generate custom pages besides your blog entries, you can point any Jinja2 template +# file with a path pointing to the file and the destination path for the generated file. +# TEMPLATE_PAGES = cf. supra + +# The extensions to use when looking up template files from template names. +TEMPLATE_EXTENSIONS = ['.html'] + +# List of templates that are used directly to render content. Typically direct templates are used to +# generate index pages for collections of content (e.g., category and tag index pages). +# DIRECT_TEMPLATES are searched for over paths maintained in THEME_TEMPLATES_OVERRIDES. +DIRECT_TEMPLATES = [ + 'index', + 'authors', + 'categories', + 'tags', + 'archives', +] + +#################################################################################################### +# +# Metadata +# + +# Default author (usually your name). +AUTHOR = 'Fabrice SALVAIRE' + +# The default metadata you want to use for all articles and pages. +DEFAULT_METADATA = {} + +# The regexp that will be used to extract any metadata from the filename. All named groups that are +# matched will be set in the metadata object. The default value will only extract the date from the +# filename. +FILENAME_METADATA = r'(?Pd{4}-d{2}-d{2}).*' + +# For example, to extract both the date and the slug: +# FILENAME_METADATA = r'(?P\d{4}-\d{2}-\d{2})_(?P.*)' +# See also SLUGIFY_SOURCE. + +# Like FILENAME_METADATA, but parsed from a page’s full path relative to the content source directory. +PATH_METADATA = '' + +# Extra metadata dictionaries keyed by relative path. Relative paths require correct OS-specific +# directory separators (i.e. / in UNIX and \ in Windows) unlike some other Pelican file +# settings. Paths to a directory apply to all files under it. The most-specific path wins conflicts. +EXTRA_PATH_METADATA = {} + +# Not all metadata needs to be embedded in source file itself. For example, blog posts are often +# named following a YYYY-MM-DD-SLUG.rst pattern, or nested into YYYY/MM/DD-SLUG directories. To +# extract metadata from the filename or path, set FILENAME_METADATA or PATH_METADATA to regular +# expressions that use Python’s group name notation (?P…). If you want to attach additional +# metadata but don’t want to encode it in the path, you can set EXTRA_PATH_METADATA: + +# EXTRA_PATH_METADATA = { +# 'relative/path/to/file-1': { +# 'key-1a': 'value-1a', +# 'key-1b': 'value-1b', +# }, +# 'relative/path/to/file-2': { +# 'key-2': 'value-2', +# }, +# } + +# This can be a convenient way to shift the installed location of a particular file: + +# Take advantage of the following defaults +# STATIC_SAVE_AS = '{path}' +# STATIC_URL = '{path}' +# STATIC_PATHS = [ +# 'static/robots.txt', +# ] +# EXTRA_PATH_METADATA = { +# 'static/robots.txt': {'path': 'robots.txt'}, +# } + +#################################################################################################### +# +# Feed settings +# + +# By default, Pelican uses Atom feeds. However, it is also possible to use RSS feeds if you prefer. + +# Pelican generates category feeds as well as feeds for all your articles. It does not generate +# feeds for tags by default, but it is possible to do so using the TAG_FEED_ATOM and TAG_FEED_RSS +# settings: + +# The domain prepended to feed URLs. Since feed URLs should always be absolute, it is highly +# recommended to define this (e.g., “https://feeds.example.com”). If you have already explicitly +# defined SITEURL (see above) and want to use the same domain for your feeds, you can just set: +# FEED_DOMAIN = SITEURL. +# FEED_DOMAIN = None # i.e. base URL is '/' + +# The location to save the Atom feed. +FEED_ATOM = None # i.e. no Atom feed + +# Relative URL of the Atom feed. If not set, FEED_ATOM is used both for save location and URL. +# FEED_ATOM_URL = None + +# The location to save the RSS feed. +FEED_RSS = None # i.e. no RSS + +# Relative URL of the RSS feed. If not set, FEED_RSS is used both for save location and URL. +# FEED_RSS_URL = None + +# The location to save the all-posts Atom feed: this feed will contain all posts regardless of their language. +# FEED_ALL_ATOM = 'feeds/all.atom.xml' +# FEED_ALL_ATOM = None # dev only + +# Relative URL of the all-posts Atom feed. If not set, FEED_ALL_ATOM is used both for save location and URL. +# FEED_ALL_ATOM_URL = None + +# The location to save the the all-posts RSS feed: this feed will contain all posts regardless of their language. +FEED_ALL_RSS = None # i.e. no all-posts RSS + +# Relative URL of the all-posts RSS feed. If not set, FEED_ALL_RSS is used both for save location and URL. +# FEED_ALL_RSS_URL = None + +# The location to save the category Atom feeds. [2] +# CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml' +# CATEGORY_FEED_ATOM = None # dev only + +# Relative URL of the category Atom feeds, including the {slug} placeholder. [2] If not set, +# CATEGORY_FEED_ATOM is used both for save location and URL. +# CATEGORY_FEED_ATOM_URL = None + +# The location to save the category RSS feeds, including the {slug} placeholder. [2] +CATEGORY_FEED_RSS = None # i.e. no RSS + +# Relative URL of the category RSS feeds, including the {slug} placeholder. [2] If not set, +# CATEGORY_FEED_RSS is used both for save location and URL. +# CATEGORY_FEED_RSS_URL = None + +# The location to save the author Atom feeds. [2] +# AUTHOR_FEED_ATOM = 'feeds/{slug}.atom.xml' +# AUTHOR_FEED_ATOM = None + +# Relative URL of the author Atom feeds, including the {slug} placeholder. [2] If not set, +# AUTHOR_FEED_ATOM is used both for save location and URL. +# AUTHOR_FEED_ATOM_URL = None + +# The location to save the author RSS feeds. [2] +# AUTHOR_FEED_RSS = 'feeds/{slug}.rss.xml' +# AUTHOR_FEED_RSS = None + +# Relative URL of the author RSS feeds, including the {slug} placeholder. [2] If not set, +# AUTHOR_FEED_RSS is used both for save location and URL. +# AUTHOR_FEED_RSS_URL = None + +# The location to save the tag Atom feed, including the {slug} placeholder. [2] +TAG_FEED_ATOM = None # i.e. no tag feed + +# Relative URL of the tag Atom feed, including the {slug} placeholder. [2] +# TAG_FEED_ATOM_URL = None + +# Relative URL to output the tag RSS feed, including the {slug} placeholder. If not set, +# TAG_FEED_RSS is used both for save location and URL. +TAG_FEED_RSS = None # i.e. no RSS tag feed + +# Maximum number of items allowed in a feed. Feed item quantity is unrestricted by default. +# FEED_MAX_ITEMS = + +# Only include item summaries in the description tag of RSS feeds. If set to False, the full content +# will be included instead. This setting doesn’t affect Atom feeds, only RSS ones. +# RSS_FEED_SUMMARY_ONLY = True + +# If you don’t want to generate some or any of these feeds, set the above variables to None. + +#################################################################################################### +# +# Pagination +# + +# The default behaviour of Pelican is to list all the article titles along with a short description +# on the index page. While this works well for small-to-medium sites, sites with a large quantity of +# articles will probably benefit from paginating this list. + +# You can use the following settings to configure the pagination. + +# The minimum number of articles allowed on the last page. Use this when you don’t want the last page to only contain a handful of articles. +DEFAULT_ORPHANS = 0 + +# The maximum number of articles to include on a page, not including orphans. False to disable pagination. +# DEFAULT_PAGINATION = False +DEFAULT_PAGINATION = 10 + +# The templates to use pagination with, and the number of articles to include on a page. If this +# value is None, it defaults to DEFAULT_PAGINATION. +PAGINATED_TEMPLATES = { + 'index': None, + 'tag': None, + 'category': None, + 'author': None, +} + +# A set of patterns that are used to determine advanced pagination output. +PAGINATION_PATTERNS = ( + (1, '{name}{extension}', '{name}{extension}'), + (2, '{name}{number}{extension}', '{name}{number}{extension}'), +) + +#################################################################################################### +# +# Translations +# + +# Pelican offers a way to translate articles. See the Content section for more information. + +# The default language to use. +DEFAULT_LANG = 'en' + +I18N_TEMPLATES_LANG = 'en' + +# The metadata attribute(s) used to identify which articles are translations of one another. May be +# a string or a collection of strings. Set to None or False to disable the identification of +# translations. +ARTICLE_TRANSLATION_ID = 'slug' + +# The metadata attribute(s) used to identify which pages are translations of one another. May be a +# string or a collection of strings. Set to None or False to disable the identification of +# translations. +PAGE_TRANSLATION_ID = 'slug' + +# The location to save the Atom feed for translations. [3] +# TRANSLATION_FEED_ATOM = 'feeds/all-{lang}.atom.xml' +TRANSLATION_FEED_ATOM = None + +# Relative URL of the Atom feed for translations, including the {lang} placeholder. [3] If not set, +# TRANSLATION_FEED_ATOM is used both for save location and URL. +TRANSLATION_FEED_ATOM_URL = None + +# Where to put the RSS feed for translations. +TRANSLATION_FEED_RSS = None # i.e. no RSS + +# Relative URL of the RSS feed for translations, including the {lang} placeholder. [3] If not set, +# TRANSLATION_FEED_RSS is used both for save location and URL. +TRANSLATION_FEED_RSS_URL = None + +#################################################################################################### +# +# Ordering content +# + +# Order archives by newest first by date. (False: orders by date with older articles first.) +NEWEST_FIRST_ARCHIVES = True + +# Reverse the category order. (True: lists by reverse alphabetical order; default lists alphabetically.) +REVERSE_CATEGORY_ORDER = False + +# Defines how the articles (articles_page.object_list in the template) are sorted. Valid options +# are: metadata as a string (use reversed- prefix the reverse the sort order), special option +# 'basename' which will use the basename of the file (without path) or a custom function to extract +# the sorting key from articles. The default value, 'reversed-date', will sort articles by date in +# reverse order (i.e. newest article comes first). +ARTICLE_ORDER_BY = 'reversed-date' + +# Defines how the pages (pages variable in the template) are sorted. Options are same as +# ARTICLE_ORDER_BY. The default value, 'basename' will sort pages by their basename. +PAGE_ORDER_BY = 'basename' + +#################################################################################################### +# +# Themes +# + +# Creating Pelican themes is addressed in a dedicated section (see Creating themes). However, here +# are the settings that are related to themes. + +# Theme to use to produce the output. Can be a relative or absolute path to a theme folder, or the +# name of a default theme or a theme installed via pelican-themes (see below). +# THEME = './pelican-themes/pelican-bootstrap3' +THEME = 'theme' + +# Destination directory in the output path where Pelican will place the files collected from THEME_STATIC_PATHS. Default is theme. +THEME_STATIC_DIR = 'theme' + +# Static theme paths you want to copy. Default value is static, but if your theme has other static +# paths, you can put them here. If files or directories with the same names are included in the +# paths defined in this settings, they will be progressively overwritten. +THEME_STATIC_PATHS = ['static'] + +# A list of paths you want Jinja2 to search for templates before searching the theme’s templates/ +# directory. Allows for overriding individual theme template files without having to fork an +# existing theme. Jinja2 searches in the following order: files in THEME_TEMPLATES_OVERRIDES first, +# then the theme’s templates/. +THEME_TEMPLATES_OVERRIDES = [] + +# You can also extend templates from the theme using the {% extends %} directive utilizing the +# !theme prefix as shown in the following example: +# {% extends '!theme/article.html' %} + +# Specify the CSS file you want to load. +CSS_FILE = 'main.css' + +# A subtitle to appear in the header. +# SITESUBTITLE = + +# Pelican can handle Disqus comments. Specify the Disqus sitename identifier here. +# DISQUS_SITENAME = + +# Your GitHub URL (if you have one). It will then use this information to create a GitHub ribbon. +GITHUB_URL = 'https://github.com/FabriceSalvaire/PySpice' + +# Set to UA-XXXXX-Y Property’s tracking ID to activate Google Analytics. +# GOOGLE_ANALYTICS = + +# Set cookie domain field of Google Analytics tracking code. Defaults to auto. +# GA_COOKIE_DOMAIN = + +# Set to ‘XXX-YYYYYY-X’ to activate GoSquared. +# GOSQUARED_SITENAME = + +# A list of tuples (Title, URL) for additional menu items to appear at the beginning of the main menu. +# MENUITEMS = cf. supra + +# URL to your Piwik server - without ‘http://‘ at the beginning. +# PIWIK_URL = + +# If the SSL-URL differs from the normal Piwik-URL you have to include this setting too. (optional) +# PIWIK_SSL_URL = + +# ID for the monitored website. You can find the ID in the Piwik admin interface > Settings > Websites. +# PIWIK_SITE_ID = + +# A list of tuples (Title, URL) for links to appear on the header. +LINKS = ( + # ('Pelican', 'http://getpelican.com/'), + # ('Python.org', 'http://python.org/'), + # ('Jinja2', 'http://jinja.pocoo.org/'), + # ('You can modify those links in your config file', '#'), +) + +# A list of tuples (Title, URL) to appear in the “social” section. +# SOCIAL = + +# Allows for adding a button to articles to encourage others to tweet about them. Add your Twitter username if you want this button to appear. +# TWITTER_USERNAME = + +# Allows override of the name of the links widget. If not specified, defaults to “links”. +# LINKS_WIDGET_NAME = + +# Allows override of the name of the “social” widget. If not specified, defaults to “social”. +# SOCIAL_WIDGET_NAME = + +# In addition, you can use the “wide” version of the notmyidea theme by adding the following to your configuration: +# CSS_FILE = 'wide.css' + +#################################################################################################### + +class TemplatePage: + + # 'src' is relative to 'theme/templates' ! + + ############################################## + + def __init__(self, name, template_name, title=None): + self.name = name + self.title = title or name + self.src = 'src/{}.html'.format(template_name) + self.dst = 'pages/{}.html'.format(template_name) + self.dst_url = '/' + self.dst # SITEURL + + + ############################################## + + @property + def menu_item(self): + return (self.title, self.dst_url) + +#################################################################################################### +# +# Customs Variables +# + +META_DESCRIPTION = 'Simulate electronic circuit using Python and the Ngspice / Xyce simulators' + +LOGO_URL = 'images/logo.svg' + +COPYRIGHT_YEAR = datetime.now().year +COPYRIGHT_NAME = 'PySpice' + +PYSPICE_FORUM = 'https://pyspice.discourse.group' +PYSPICE_BUGTRACKER = GITHUB_URL + '/issues' + +RELEASES_URL = '/releases' +DEV_RELEASE = 'v1.6' +LAST_RELEASE = 'v1.5' +OLD_RELEASES = [ + 'v1.4', + 'v1.3', + 'v1.2', +] + +PAGE_LINKS = { + name: TemplatePage(name, template, title) + for name, template, title in ( + ('legal_notice', 'legal-notice', 'Legal Notice'), + ) +} + +TEMPLATE_PAGES_IN_MENU = { + name: TemplatePage(name, template, title) + for name, template, title in ( + ('help', 'get-help', ' Help'), + ('documentation', 'documentation', ' Documentation'), + ) +} + +PAGE_LINKS.update(TEMPLATE_PAGES_IN_MENU) + +TEMPLATE_PAGES = {template.src: template.dst for template in TEMPLATE_PAGES_IN_MENU.values()} + +MENUITEMS = [ + TEMPLATE_PAGES_IN_MENU['help'].menu_item, + TEMPLATE_PAGES_IN_MENU['documentation'].menu_item, + (' GitHub', GITHUB_URL), +] diff --git a/third_party/PySpice-org/PySpice/doc/pelican/publishconf.py b/third_party/PySpice-org/PySpice/doc/pelican/publishconf.py new file mode 100644 index 00000000..800d84d2 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/publishconf.py @@ -0,0 +1,44 @@ +#################################################################################################### +# +# PySpice - A Spice package for Python +# Copyright (C) 2020 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 file is only used if you use `make publish` or explicitly specify it as your config file. + +#################################################################################################### + +import os +import sys +sys.path.append(os.curdir) +from pelicanconf import * + +#################################################################################################### + +# Delete the output directory, and all of its contents, before generating new files. +DELETE_OUTPUT_DIRECTORY = True + +# If your site is available via HTTPS, make sure SITEURL begins with https:// +SITEURL = 'https://pyspice.fabrice-salvaire.fr' +RELATIVE_URLS = False + +# Atom +FEED_DOMAIN = SITEURL +FEED_ALL_ATOM = 'feeds/all.atom.xml' +CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml' diff --git a/third_party/PySpice-org/PySpice/doc/pelican/src/contact.html b/third_party/PySpice-org/PySpice/doc/pelican/src/contact.html new file mode 100644 index 00000000..d2a50a45 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/src/contact.html @@ -0,0 +1,10 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block html_lang %}en{% endblock %} + +{% block title %}{{ SITENAME }} - Contact {%endblock%} + +{% block content %} +Contact ... +{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/src/documentation.html b/third_party/PySpice-org/PySpice/doc/pelican/src/documentation.html new file mode 100644 index 00000000..98bf3470 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/src/documentation.html @@ -0,0 +1,49 @@ + +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block html_lang %}en{% endblock %} + +{% block title %}{{ SITENAME }} — Documentation{%endblock%} + +{% block content %} + +

PySpice User / API Documentation

+ +

+ Note: if something looks wrong in the lastest documentation, it is advisable to check the older documentations. +

+ +
+
+
+
Last Release Documentation
+ Release {{ LAST_RELEASE }} +
+
+
+
+
Dev Release Documentation
+ Release {{ DEV_RELEASE }} +
+
+ +
+ +
+

Older Releases

+ {# {% for release in OLD_RELEASES[1:] %} #} + {% for release in OLD_RELEASES %} + {# flexbox ??? #} + + {% endfor %} +
+ +{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/src/get-help.html b/third_party/PySpice-org/PySpice/doc/pelican/src/get-help.html new file mode 100644 index 00000000..c5911db0 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/src/get-help.html @@ -0,0 +1,33 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block html_lang %}en{% endblock %} + +{% block title %}{{ SITENAME }} — Help{%endblock%} + +{% block content %} + +
+

PySpice Forum

+

A Discourse forum to discuss about PySpice and related topics

+ Go to Forum +
+ +
+
+
+

Documentation

+

To get the User manual, to lean more about the API

+ Go to Documentation Page +
+
+
+
+

Bug Tracker

+

Pyspice Bug Tracker is hosted on Github

+ Go to Bug Tracker +
+
+
+ +{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/tasks.py b/third_party/PySpice-org/PySpice/doc/pelican/tasks.py new file mode 100644 index 00000000..f0c68875 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/tasks.py @@ -0,0 +1,152 @@ +#################################################################################################### +# +# PySpice - A Spice package for Python +# Copyright (C) 2020 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 shutil +import sys + +from invoke import task +from invoke.util import cd +from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer +from pelican.settings import DEFAULT_CONFIG, get_settings_from_file + +#################################################################################################### + +SETTINGS_FILE_BASE = 'pelicanconf.py' +SETTINGS = {} +SETTINGS.update(DEFAULT_CONFIG) +LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE) +SETTINGS.update(LOCAL_SETTINGS) + +CONFIG = { + 'settings_base': SETTINGS_FILE_BASE, + 'settings_publish': 'publishconf.py', + # Output path. Can be absolute or relative to tasks.py. Default: 'output' + 'deploy_path': SETTINGS['OUTPUT_PATH'], + # Port for `serve` + 'port': 8000, +} + +from SECRET_CONFIG import SSH_CONFIG +CONFIG.update(SSH_CONFIG) + +#################################################################################################### + +@task +def clean(ctx): + """Remove generated files""" + if os.path.isdir(CONFIG['deploy_path']): + shutil.rmtree(CONFIG['deploy_path']) + os.makedirs(CONFIG['deploy_path']) + +#################################################################################################### + +@task +def build(ctx, debug=False): + """Build local version of site""" + debug = '-D' if debug else '' + ctx.run('pelican {debug} -s {settings_base}'.format(**CONFIG, debug=debug)) + +#################################################################################################### + +@task +def rebuild(ctx): + """`build` with the delete switch""" + ctx.run('pelican -d -s {settings_base}'.format(**CONFIG)) + +#################################################################################################### + +@task +def regenerate(ctx): + """Automatically regenerate site upon file modification""" + ctx.run('pelican -r -s {settings_base}'.format(**CONFIG)) + +#################################################################################################### + +@task +def serve(ctx): + """Serve site at http://localhost:$PORT/ (default port is 8000)""" + + class AddressReuseTCPServer(RootedHTTPServer): + allow_reuse_address = True + + server = AddressReuseTCPServer( + CONFIG['deploy_path'], + ('', CONFIG['port']), + ComplexHTTPRequestHandler) + + sys.stderr.write('Serving on port {port} ...\n'.format(**CONFIG)) + server.serve_forever() + +#################################################################################################### + +@task(build) +def reserve(ctx): + """`build`, then `serve`""" + serve(ctx) + +#################################################################################################### + +@task +def preview(ctx): + """Build production version of site""" + ctx.run('pelican -s {settings_publish}'.format(**CONFIG)) + +#################################################################################################### + +@task(build) +def livereload(ctx): + """Automatically reload browser tab upon file modification.""" + from livereload import Server + server = Server() + # Watch the base settings file + server.watch(CONFIG['settings_base'], lambda: build(ctx)) + # Watch content source files + content_file_extensions = ['.md', '.rst'] + for extension in content_file_extensions: + content_blob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension) + server.watch(content_blob, lambda: build(ctx)) + # Watch the theme's templates and static assets + theme_path = SETTINGS['THEME'] + server.watch('{}/templates/*.html'.format(theme_path), lambda: build(ctx)) + static_file_extensions = ['.css', '.js'] + for extension in static_file_extensions: + static_file = '{0}/static/**/*{1}'.format(theme_path, extension) + server.watch(static_file, lambda: build(ctx)) + # Serve output path on configured port + server.serve(port=CONFIG['port'], root=CONFIG['deploy_path']) + +#################################################################################################### + +@task(preview) +def publish(ctx): + """Publish to production via rsync""" + command_template = ( + 'rsync' + ' --delete -pthrvz -c' + ' --exclude ".DS_Store"' + ' --filter "protect releases/"' + ' -e "ssh -p {ssh_port}" {src_path} {ssh_user}@{ssh_host}:{ssh_path}' + ) + ctx.run(command_template.format( + src_path=CONFIG['deploy_path'].rstrip('/') + '/', + **CONFIG)) diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/.eslintrc.json b/third_party/PySpice-org/PySpice/doc/pelican/theme/.eslintrc.json new file mode 100644 index 00000000..0bb5ce87 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/.eslintrc.json @@ -0,0 +1,233 @@ +module.exports = { + env: { + browser: true, + node: true, + jquery: true, + jasmine: true, + es6: true + }, + globals: { + CMS: true, + Promise: true, + __CMS_VERSION__: true + }, + root: true, + ecmaFeatures: { + modules: true + }, + parser: 'babel-eslint', + parserOptions: { + sourceType: 'module' + }, + plugins: ['compat'], + settings: { + polyfills: ['document-currentscript'] + }, + rules: { + // Possible Errors + 'comma-dangle': [2, 'never'], + 'no-cond-assign': 2, + 'no-console': 1, + 'no-constant-condition': 2, + 'no-control-regex': 2, + 'no-debugger': 2, + 'no-dupe-args': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty-character-class': 2, + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-ex-assign': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-parens': 0, + 'no-extra-semi': 2, + 'no-func-assign': 2, + 'no-inner-declarations': 2, + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-negated-in-lhs': 2, + 'no-obj-calls': 2, + 'no-regex-spaces': 2, + 'no-sparse-arrays': 2, + 'no-unexpected-multiline': 2, + 'no-unreachable': 2, + 'use-isnan': 2, + 'valid-jsdoc': [ + 2, + { + requireReturn: false, + requireParamDescription: false, + requireReturnDescription: false, + prefer: { + return: 'returns' + } + } + ], + 'valid-typeof': 2, + + // Best Practices + 'accessor-pairs': 2, + 'block-scoped-var': 2, + complexity: ['error', { max: 10 }], + 'consistent-return': 0, + curly: 2, + 'default-case': 2, + 'dot-location': [2, 'property'], + 'dot-notation': 2, + eqeqeq: 2, + 'guard-for-in': 2, + 'no-alert': 2, + 'no-caller': 2, + 'no-case-declarations': 2, + 'no-div-regex': 2, + 'no-else-return': 1, + 'no-empty-pattern': 2, + 'no-eq-null': 2, + 'no-eval': 2, + 'no-extend-native': 2, + 'no-extra-bind': 2, + 'no-fallthrough': 2, + 'no-floating-decimal': 2, + 'no-implicit-coercion': 0, + 'no-implied-eval': 2, + 'no-invalid-this': 0, + 'no-iterator': 2, + 'no-labels': 2, + 'no-lone-blocks': 2, + 'no-loop-func': 2, + 'no-magic-numbers': [ + 'error', + { + ignore: [0, -1, 1, 2], + ignoreArrayIndexes: true + } + ], + 'no-multi-spaces': 2, + 'no-multi-str': 0, + 'no-native-reassign': 2, + 'no-new-func': 2, + 'no-new-wrappers': 2, + 'no-new': 0, + 'no-octal-escape': 2, + 'no-octal': 2, + 'no-param-reassign': 2, + 'no-process-env': 0, + 'no-proto': 2, + 'no-redeclare': 2, + 'no-return-assign': 2, + 'no-script-url': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-throw-literal': 2, + 'no-unused-expressions': [2, { allowShortCircuit: true }], + 'no-useless-call': 2, + 'no-useless-concat': 2, + 'no-void': 2, + 'no-warning-comments': 0, + 'no-with': 2, + radix: 2, + 'vars-on-top': 0, // FIXME should be enabled at some point + 'wrap-iife': [2, 'inside'], + yoda: [2, 'never', { exceptRange: true }], + + // Strict Mode + strict: 0, // not required with webpack + + // Variables + 'init-declarations': 0, + 'no-catch-shadow': 2, + 'no-delete-var': 2, + 'no-label-var': 2, + 'no-shadow-restricted-names': 2, + 'no-shadow': 2, + 'no-undef-init': 2, + 'no-undef': 2, + 'no-undefined': 0, + 'no-unused-vars': 2, + 'no-use-before-define': 2, + + // Stylistic Issues + 'array-bracket-spacing': [2, 'never'], + 'block-spacing': 2, + 'brace-style': [2, '1tbs'], + camelcase: 0, + 'comma-spacing': [2, { before: false, after: true }], + 'comma-style': [2, 'last'], + 'computed-property-spacing': [2, 'never'], + 'consistent-this': [2, 'that'], + 'eol-last': 2, + 'func-names': 0, + 'func-style': 0, + 'id-length': 0, + 'id-match': 0, + indent: [ + 'error', + 4, + { + SwitchCase: 1 + } + ], + 'jsx-quotes': 0, + 'key-spacing': [2, { beforeColon: false, afterColon: true }], + 'linebreak-style': [2, 'unix'], + 'lines-around-comment': 0, + 'max-nested-callbacks': [2, 5], + 'new-cap': 2, + 'new-parens': 2, + 'newline-after-var': 2, + 'no-array-constructor': 2, + 'no-continue': 2, + 'no-inline-comments': 0, + 'no-lonely-if': 2, + 'no-mixed-spaces-and-tabs': 2, + 'no-multiple-empty-lines': [2, { max: 2 }], + 'no-negated-condition': 2, + 'no-nested-ternary': 2, + 'no-new-object': 2, + 'no-restricted-syntax': 0, + 'no-spaced-func': 0, + 'no-ternary': 0, + 'no-trailing-spaces': 2, + 'no-underscore-dangle': 0, + 'no-unneeded-ternary': 2, + 'object-curly-spacing': [ + 2, + 'always', + { + objectsInObjects: true, + arraysInObjects: true + } + ], + 'one-var': [2, 'never'], + 'operator-assignment': 2, + 'operator-linebreak': 0, + 'padded-blocks': 0, + 'quote-props': [2, 'as-needed'], + quotes: [2, 'single', 'avoid-escape'], + 'require-jsdoc': 2, + 'semi-spacing': [2, { before: false, after: true }], + semi: [2, 'always'], + 'sort-vars': 0, + 'keyword-spacing': 2, + 'space-before-blocks': 2, + // FIXME reenable after running prettier on full codebase + 'space-before-function-paren': [0, { anonymous: 'never', named: 'never' }], + 'space-in-parens': [2, 'never'], + 'space-infix-ops': 2, + 'space-unary-ops': 2, + 'spaced-comment': 2, + 'wrap-regex': 0, + + // ES6 + 'arrow-parens': [2, 'as-needed'], + + // Legacy + 'max-depth': [2, 4], + 'max-len': [2, 120], + 'max-params': [2, 3], + 'max-statements': 0, + 'no-bitwise': 2, + 'no-plusplus': 0, + + 'compat/compat': 2 + } +}; diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/browserslist b/third_party/PySpice-org/PySpice/doc/pelican/theme/browserslist new file mode 100644 index 00000000..fdd653c5 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/browserslist @@ -0,0 +1,15 @@ +# Supported browsers +# https://github.com/twbs/bootstrap/blob/v4.4.1/.browserslistrc +# https://github.com/browserslist/browserslist#readme + +>= 1% +last 1 major version +not dead +Chrome >= 45 +Firefox >= 38 +Edge >= 12 +Explorer >= 10 +iOS >= 9 +Safari >= 9 +Android >= 4.4 +Opera >= 30 diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/gulpfile.js b/third_party/PySpice-org/PySpice/doc/pelican/theme/gulpfile.js new file mode 100644 index 00000000..2e330b54 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/gulpfile.js @@ -0,0 +1,213 @@ +// ################################################################################################# +// +// Gulp Task Manager settings +// +// Documentation https://gulpjs.com +// https://www.liquidlight.co.uk/blog/how-do-i-update-to-gulp-4 +// +// ################################################################################################# + +// ################################################################################################# +// +// Copyright (c) 2019 Fabrice Salvaire +// +// Copyright (c) 2013, Divio AG +// Licensed under BSD +// http://github.com/aldryn/aldryn-boilerplate-bootstrap3 +// +// ################################################################################################# + +// ################################################################################################# +// IMPORTS + +// https://www.npmjs.com/package/autoprefixer +// PostCSS plugin to parse CSS and add vendor prefixes to CSS rules using values from Can I Use. +// It is recommended by Google and used in Twitter. +var autoprefixer = require('autoprefixer'); +var eslint = require('gulp-eslint'); +var fs = require('fs'); +var gulp = require('gulp'); +var gulpif = require('gulp-if'); +var gutil = require('gulp-util'); +var header = require('gulp-header'); +var iconfont = require('gulp-iconfont'); +var iconfont_css = require('gulp-iconfont-css'); +// https://www.npmjs.com/package/gulp-clean-css +// https://github.com/jakubpawlowicz/clean-css +var minify_css = require('gulp-clean-css'); +var plumber = require('gulp-plumber'); +var postcss = require('gulp-postcss'); +var gulp_sass = require('gulp-sass'); +var sourcemaps = require('gulp-sourcemaps'); +// var webpack = require('webpack'); +var browser_sync = require('browser-sync').create(); + +var argv = require('minimist')(process.argv.slice(2)); + +// ################################################################################################# +// SETTINGS + +var options = { + debug: argv.debug +}; + +var PROJECT_ROOT = __dirname; + +var PROJECT_PATH = { + bower: PROJECT_ROOT + '/static/vendor', + css: PROJECT_ROOT + '/static/css', + fonts: PROJECT_ROOT + '/static/fonts', // Doesn't exists !!! + html: PROJECT_ROOT + '/templates', + icons: PROJECT_ROOT + '/private/icons', // Doesn't exists !!! + images: PROJECT_ROOT + '/static/img', + js: PROJECT_ROOT + '/static/js', + sass: PROJECT_ROOT + '/scss', +}; + +var PROJECT_PATTERNS = { + html: [ + PROJECT_PATH.html + '/**/*.{html}' + ], + images: [ + PROJECT_PATH.images + '/**/*', + ], + js: [ + 'gulpfile.js', + PROJECT_PATH.js + '/**/*.js', + // exclude from linting + '!' + PROJECT_PATH.js + '/*.min.js', + '!' + PROJECT_PATH.js + '/**/*.min.js', + ], + sass: [ + PROJECT_PATH.sass + '/**/*.{scss,sass}' + ] +}; + +var DEFAULT_PORT = 8000; +var PORT = parseInt(process.env.PORT, 10) || DEFAULT_PORT; +var DEBUG = argv.debug; + +// ################################################################################################# +// TASKS + +// SASS Task +function sass_task(cb) { + // sourcemaps can be activated through `gulp sass --debug´ + gulp + .src(PROJECT_PATTERNS.sass) + .pipe(gulpif(options.debug, sourcemaps.init())) + .pipe(gulp_sass()) + .on('error', function(error) { + gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.messageFormatted)); + }) + .pipe( + postcss([ + autoprefixer({ + cascade: false + }) + ]) + ) + .pipe( + minify_css({ + rebase: false + }) + ) + .pipe(header('/* This file was generated automatically on server side on ' + new Date().toISOString() + + '. All changes would be lost. */ \n\n')) + .pipe(gulpif(options.debug, sourcemaps.write())) + .pipe(gulp.dest(PROJECT_PATH.css)) + .pipe(browser_sync.reload({ + stream: true, + })); + cb(); +} + +// Icons Task +function icons_task(cb) { + // /private/icons Doesn't exists !!! + gulp + .src(PROJECT_PATH.icons + '/**/*.svg') + .pipe( + iconfont_css({ + fontName: 'iconfont', + appendUnicode: true, + formats: ['ttf', 'eot', 'woff', 'svg'], + fontPath: 'static/fonts/', + path: PROJECT_PATH.sass + '/libs/_iconfont.scss', + targetPath: '../../../scss/layout/_iconography.scss' + }) + ) + .pipe( + iconfont({ + fontName: 'iconfont', + normalize: true + }) + ) + .on('glyphs', function(glyphs, opts) { + gutil.log.bind(glyphs, opts); + }) + .pipe(gulp.dest(PROJECT_PATH.fonts)); + cb(); +} + +// Lint Task +function lint_javascript_task(cb) { + // http://eslint.org + gulp + .src(PROJECT_PATTERNS.js) + .pipe(gulpif(!process.env.CI, plumber())) + .pipe(eslint()) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()) + .pipe(gulpif(!process.env.CI, plumber.stop())); + cb(); +} + +// Django Dev Server Task +function runserver_task(cb) { + var proc = exec('python manage.py runserver'); + cb(); +} + +// Browser Sync Task +// gulp.task('browserSync', ['runserver'], function() {}); +function browser_sync_task(cb) { + browser_sync.init({ + notify: false, + port: DEFAULT_PORT, + proxy: 'localhost:' + DEFAULT_PORT, + }); + cb(); +} + +// Watch Task +function watch_task(cb) { + gulp.watch(PROJECT_PATTERNS.html, browser_sync.reload); + gulp.watch(PROJECT_PATTERNS.sass, gulp.series(sass_task)); + // gulp.watch(PROJECT_PATTERNS.js, lint_javascript_task); + cb(); +} + +// ################################################################################################# + +exports.sass = sass_task; +exports.build = gulp.series(sass_task); +exports.default = gulp.series(browser_sync_task, sass_task, watch_task); +exports.lint = gulp.series(lint_javascript_task); + +// ################################################################################################# + +// Checks project deployment +// @param {String} id - task name +// @returns {Object} - task which finished +// function task (id) { +// return require('./tools/tasks/' + id)(gulp, { +// PROJECT_ROOT: PROJECT_ROOT, +// PROJECT_PATH: PROJECT_PATH, +// PROJECT_PATTERNS: PROJECT_PATTERNS, +// DEBUG: DEBUG, +// PORT: PORT +// }); +// } +// +// gulp.task('sass', task('sass')); diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/package.json b/third_party/PySpice-org/PySpice/doc/pelican/theme/package.json new file mode 100644 index 00000000..37cdf3b2 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/package.json @@ -0,0 +1,37 @@ +{ + "name": "package", + "private": true, + "dependencies": { + "@fortawesome/fontawesome-free": "^5.13.0", + "bootstrap": "^4.5", + "jquery": "3.5.1", + "outdatedbrowser": "^1.1.5", + "popper.js": "^1.16.0" + }, + "devDependencies": { + "autoprefixer": "^9.7.3", + "bower-away": "^1.1.2", + "browser-sync": "^2.26.7", + "gulp": "^4.0.2", + "gulp-cached": "^1.1.1", + "gulp-clean-css": "^4.2.0", + "gulp-eslint": "^6.0.0", + "gulp-header": "^2.0.9", + "gulp-iconfont": "^10.0.3", + "gulp-iconfont-css": "^3.0.0", + "gulp-if": "^3.0.0", + "gulp-imagemin": "^6.2.0", + "gulp-plumber": "^1.2.1", + "gulp-postcss": "^8.0.0", + "gulp-sass": "^4.0.2", + "gulp-scss-lint": "^1.0.0", + "gulp-sourcemaps": "^2.6.5", + "gulp-util": "^3.0.8", + "minimist": "^1.2.0", + "package.json": "^2.0.1" + }, + "engines": { + "yarn": ">= 1.0.0" + }, + "scripts": {} +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_all.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_all.scss new file mode 100644 index 00000000..7b596722 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_all.scss @@ -0,0 +1,18 @@ +// ############################################################################# +// +// If a component is plug-and-playable, it probably belongs in here. Good +// examples are jQuery plugins or Aldryn addons. Sometimes larger +// application such as a shop might also be pluggable. If this is not the +// case, they belong in the /sass/sites directory. +// +// Warning +// You will always encounter the question whether to place a component +// within /sass/addons or /sass/sites. In case of doubts, use the sites +// folder. +// +// this folder is for reusable addons / apps from a cms or alike +// update this file accordingly to include new addons globally +// +// ############################################################################# + +@import "browser"; diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_browser.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_browser.scss new file mode 100644 index 00000000..4ce4104d --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/addons/_browser.scss @@ -0,0 +1,76 @@ +// ############################################################################# +// +// Styles for Outdated Browsers +// +// This addon adds modified styles to the outdated browser script +// found on outdatedbrowser.com we specifically added changes to class +// names and removed id references to be more compliant. +// +// ############################################################################# + +// Fimxe: how to check ??? + +$old-browser-line-height-computed: 20pxw; + +* html .browser-outdated { + position: absolute; +} + +.browser-outdated { + display: none; + position: fixed; + bottom: 0; + left: 0; + z-index: 9999; + color: #fff; + text-align: center; + text-transform: uppercase; + // do not add paddings or margins on the container + width: 100%; + height: auto; + background-color: $danger; + + h3, + p { + color: #fff; + } + .btn-outline { + color: #fff; + border: 2px solid #fff; + &:hover { + color: $danger; + background: #fff; + } + } + + .last { + position: absolute; + top: $old-browser-line-height-computed / 2; + right: $old-browser-line-height-computed; + a { + display: inline-block; + color: #fff; + font-size: 36px; + line-height: 1; + text-decoration: none; + } + } + + // add spacer in order for cross styling between noscript and out-of-date + .btn-outline, + noscript h3 { + margin-bottom: $old-browser-line-height-computed; + } +} + +// additionally also implement a warning when JavaScript is disabled +.noscript .browser-outdated { + display: block; + p, + h3 { + display: none; + } + noscript h3 { + display: block; + } +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/base.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/base.scss new file mode 100644 index 00000000..ee5e42ff --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/base.scss @@ -0,0 +1,37 @@ +// ############################################################################# +// +// Every folder within /private/sass has a file called _all.scss. This +// file ultimately gets imported by /private/sass/base.scss which gets +// compiled into /static/css/base.css. Update the _all.scss file to +// include additional modules. To keep the file simple, do not include +// files directly within base.scss. +// +// Copyright Fabrice Salvaire 2019 +// Copyright (c) 2013, Divio AG +// Licensed under BSD +// http://github.com/aldryn/aldryn-boilerplate-bootstrap3 +// +// ############################################################################# + +@charset "utf-8"; + +// ############################################################################# + +@import "settings/all"; + +// importin mixins +@import "mixins/all"; + +// importing frameworks +@import "libs/all"; + +// @import "libs_tier2/all"; + +// importing modules +@import "layout/all"; + +// importing addons +@import "addons/all"; + +// importing sites +@import "sites/all"; diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_all.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_all.scss new file mode 100644 index 00000000..ab6f3b07 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_all.scss @@ -0,0 +1,36 @@ +/* +* We consider the general look and feel as the layout of a website or +* application. This might include the typography, header and footer, +* icons or the printable version. The layout can be broken down into +* further parts if a website gets very large. We advise in general +* against this strategy and rather prefer to use /sass/sites to create +* specific layouts and derive from a global common style guide. +* +* Warning +* Everything that targets a specific element, such as custom styles for +* Bootstrap components or a specific form error, belongs in /sass/addons +* or /sass/sites. +*/ + +// ############################################################################# +// AUTOIMPORTS + +// this folder is for predefined parts of a website section like header/footer +// or icons which are globally used there are very few occasions where +// you want to add more files here + +// @import "fonts"; +@import "roboto"; +@import "gabriela"; + +// @import "iconography"; +// @import "forms"; +// @import "buttons"; +@import "header"; +@import "breadcrumb"; +@import "footer"; +@import "content"; +@import "images"; +@import "codes"; + +// @import "print"; diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_breadcrumb.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_breadcrumb.scss new file mode 100644 index 00000000..bc2639a1 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_breadcrumb.scss @@ -0,0 +1,124 @@ +// ############################################################################# +// Breadcrumb + +/* +$breadcrumb-li-bg-colour: #eee; +$breadcrumb-li-bg-colour-right: #ddd; +$breadcrumb-li-bg-colour-left: #f5f5f5; +$breadcrumb-li-border-colour: #ccc; +$breadcrumb-li-colour: #444; +*/ + +$breadcrumb-li-bg-colour: $gray-200; +$breadcrumb-li-bg-colour-right: darken($breadcrumb-li-bg-colour, 5%); +$breadcrumb-li-bg-colour-left: lighten($breadcrumb-li-bg-colour-right, 15%); +$breadcrumb-li-border-colour: darken($breadcrumb-li-bg-colour-right, 20%); +$breadcrumb-li-colour: $gray-900; // darken($breadcrumb-li-bg-colour, 80%); + +$breadcrumb-triangle-size: 2em; // set the angle +$breadcrumb-li-padding-horizontal: 1em; +$breadcrumb-li-padding-vertical: .5em; +$breadcrumb-wedge-width: .15em; + +/* +$breadcrumb-li-bg-colour: $navbar-inverse-bg; +$breadcrumb-li-bg-colour-right: lighten($breadcrumb-li-bg-colour, 20%); +$breadcrumb-li-bg-colour-left: darken($breadcrumb-li-bg-colour-right, 25%); +$breadcrumb-li-border-colour: lighten($breadcrumb-li-bg-colour-right, 10%); +$breadcrumb-li-colour: lighten($breadcrumb-li-bg-colour, 70%); +*/ + +// For breadcrumb in header +#page-breadcrumb { + // show for large device + @extend .d-none; + @extend .d-lg-block; + + @extend .pt-0; + @extend .pb-3; + + // @media (min-width: $screen-md-min) { + // Fixme: when the window is very small, > is above the current li + ol.breadcrumb { + margin: 0; + padding: 0; + + // list-style: none; + + background: $breadcrumb-li-bg-colour; + // border-width: 1px; + // border-style: solid; + // border-color: $breadcrumb-li-bg-colour-left #e5e5e5 $breadcrumb-li-border-colour; + // border-radius: 5px; + // box-shadow: 0 0 2px rgba(0,0,0,.2); + overflow: hidden; + // width: 100%; + + > li + li::before { + // Reset Boostrap settins + content: normal; + padding: 0; + } + + li { + float: left; + + &.active { + padding: $breadcrumb-li-padding-vertical $breadcrumb-li-padding-horizontal $breadcrumb-li-padding-vertical 2*$breadcrumb-li-padding-horizontal; + // font-weight: bold; + color: darken($breadcrumb-li-colour, 10%); + } + + a { + padding: $breadcrumb-li-padding-vertical $breadcrumb-li-padding-horizontal $breadcrumb-li-padding-vertical 2*$breadcrumb-li-padding-horizontal; + float: left; + // text-decoration: none; + color: $breadcrumb-li-colour; + position: relative; + // text-shadow: 0 1px 0 rgba(255,255,255,.5); + // background-color: $breadcrumb-li-bg-colour-right; + background-image: linear-gradient(to right, $breadcrumb-li-bg-colour-left, $breadcrumb-li-bg-colour-right); + + color: $breadcrumb-li-colour; + + &::after, + &::before { + // Draw a triangle using border (note: border angle is 45°) + content: ""; + position: absolute; + top: 50%; + margin-top: -$breadcrumb-triangle-size; + border-top: $breadcrumb-triangle-size solid transparent; + border-bottom: $breadcrumb-triangle-size solid transparent; + border-left: $breadcrumb-li-padding-horizontal solid; + right: -$breadcrumb-li-padding-horizontal; + } + + &::after { + z-index: 2; + border-left-color: $breadcrumb-li-bg-colour-right; + } + + &::before{ + border-left-color: $breadcrumb-li-border-colour; + right: - $breadcrumb-li-padding-horizontal - $breadcrumb-wedge-width; + z-index: 1; + } + + &:hover { + text-decoration: none; + background: white; + + &::after { + border-left-color: white; + } + } + } + + &:first-child a { + padding-left: 1em; + // border-radius: 5px 0 0 5px; + } + } + } +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_codes.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_codes.scss new file mode 100644 index 00000000..864b8ff4 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_codes.scss @@ -0,0 +1,11 @@ +// ############################################################################# +// Codes + +pre { + @extend .p-2; + // background-color: $gray-100; + background-color: #f5f5f5; + // border: 1px solid $gray-400; + border: 1px solid #ccc; + border-radius: $border-radius; +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_content.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_content.scss new file mode 100644 index 00000000..fcdb47c2 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_content.scss @@ -0,0 +1,49 @@ +// ################################################################################################# +// Content + +// Sticky Footer +// https://css-tricks.com/couple-takes-sticky-footer/ + +// html, body { +// height: 100%; +// } +// body { +// display: flex; +// flex-direction: column; +// } +// .content { +// flex: 1 0 auto; +// } +// footer #page-footer { +// flex-shrink: 0; +// } + +html { + height: 100%; +} + +body { + min-height: 100%; + display: grid; + grid-template-rows: auto 1fr auto; + grid-template-areas: + "header" + "content" + "footer"; +} + +header#page-header { + grid-area: header; + // grid-row-start: 1; + // grid-row-end: 2; +} + +div#page-content { + grid-area: content; +} + +footer#page-footer { + grid-area: footer; + // grid-row-start: 3; + // grid-row-end: 4; +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_footer.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_footer.scss new file mode 100644 index 00000000..f559e9a0 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_footer.scss @@ -0,0 +1,34 @@ +// ############################################################################# +// Footer + +// all styles related to the global footer should be declared within this file +// apply the "sites/" nesting described within the documentation if the file is +// getting to large + +// .page-footer +footer { + @extend .mt-3; + @extend .py-3; + @include rised-component(); + @include white_on_dark(); +} + +.footer-container { + @extend .container; + @extend .d-flex; + + #copyright-notice { + } + + #page-top-link { + @extend .ml-auto; + } +} + +@media(max-width: $grid-float-breakpoint) { + .footer-container { + // @extend .flex-column; // but don't work in media ... + flex-direction: column; + } +} + diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_gabriela.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_gabriela.scss new file mode 100644 index 00000000..d05a8898 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_gabriela.scss @@ -0,0 +1,6 @@ +@font-face { + font-family: 'Gabriela'; + font-style: normal; + font-weight: 400; + src: local('Gabriela'), local('Gabriela-Regular'), url(https://fonts.gstatic.com/l/font?kit=BEd5TJl5geC-G_qgmtkw3kO-NZ8T69qbbis4mE8I88g&skey=146d0218141ab9a9&v=v4) format('woff2'); +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_header.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_header.scss new file mode 100644 index 00000000..455edd06 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_header.scss @@ -0,0 +1,78 @@ +//################################################################################################### +// HEADER + +// all styles related to the global header should be declared within +// this file apply the "sites/" nesting described within the +// documentation if the file is getting to large + +header.page_header { + // Add vertical space to the header + @extend .mb-3; + @include rised-component(); +} + +#top-navbar { + @extend .navbar-expand-lg; + @extend .py-0; + + text-transform: uppercase; +} + +// Show from small ≥576px device +@media(min-width: $grid-float-breakpoint) { + // Implement an horizontal line below menu item + // Fixme: better ? + #top-navbar { + .nav-item:not(.dropdown) { // doesn't work with dropdown + > a::after { + // position: absolute; + // left: 0; + content: ""; + display: block; + height: 2px; + bottom: 3px; + background-color: #a1c42c; + width: 0; + transition: all .2s ease-out; + } + &.active > a::after, + > a:hover::after { // :active::after, :focus::after + width: 100%; + } + } + } +} + +//################################################################################################### + +header.page_header { + @extend .bg-dark; +} + +$logo-angle: 20deg; +$logo-padding: 30px; + +#top-navbar { + // @extend .navbar-light; + // @extend .bg-light; + + @extend .navbar-dark; + @extend .bg-dark; + + @include white_on_dark(); + + a.navbar-brand { + transform: skew(-$logo-angle); + background: white; + padding-right: $logo-padding; + padding-left: $logo-padding; + img { + transform: skew($logo-angle); + } + } + + // Fix dropdown menu text colour + a.dropdown-item { + @extend .text-dark; + } +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_images.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_images.scss new file mode 100644 index 00000000..0da1d194 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_images.scss @@ -0,0 +1,30 @@ +// ############################################################################# +// Images + +// cf. addons/_rst.scss +.container { + img.left { + clear: left; + float: left; + @extend .mr-3; + // text-align: left; + } + + img.right { + clear: right; + float: right; + // @extend .ml-3; + // text-align: right; + } + img.centered { + margin-left: auto; + margin-right: auto + } +} + +img { + &.rotated-5-acw { + @extend .p-3; + transform: rotate(-5deg); + } +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_roboto.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_roboto.scss new file mode 100644 index 00000000..44b4cbe4 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/layout/_roboto.scss @@ -0,0 +1,282 @@ +// https://fonts.googleapis.com/css?family=Roboto:100,300,400|Roboto+Slab:300,400 + +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/frNV30OaYdlFRtH2VnZZdhTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/gwVJDERN2Amz39wrSoZ7FxTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/aZMswpodYeVhtRvuABJWvBTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/VvXUGKZXbHtX_S_VCTLpGhTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/e7MeVAyvogMqFwwl61PKhBTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 100; + src: local('Roboto Thin'), local('Roboto-Thin'), url(https://fonts.gstatic.com/s/roboto/v15/2tsd397wLxj96qwHyNIkxPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v15/Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJRgVThLs8Y7ETJzDCYFCSLE.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJZiMaisvaUVUsYyVzOmndek.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJbBAWGjcah5Ky0jbCgIwDB8.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJf14vlcfyPYlAcQy2UfDRm4.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJafJul7RR1X4poJgi27uS4w.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJavyPXdneeGd26m9EmFSSWg.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 300; + src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJUo2lTMeWA_kmIyWrkNCwPc.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZjTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZjUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZkbcKLIaa1LC45dFaAfauRA.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zmo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zr6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZiYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto Slab'; + font-style: normal; + font-weight: 400; + src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zo4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/libs/_all.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/libs/_all.scss new file mode 100644 index 00000000..376140f3 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/libs/_all.scss @@ -0,0 +1,22 @@ +// ############################################################################# +// +// All independent files are placed within this folder. This implies that +// the order of inclusion does not matter within /sass/libs/_all.scss. +// +// Hint +// Libraries are, in their very core, plug-and-playable. The main +// difference between libraries and other plug-and-play components is, +// that if a library is removed, things will break. +// +// this folder is for additional independent libraries such as 960gs or +// twitter bootstrap these libraries do not depend on other elements +// +// ############################################################################# + +@import "bootstrap/bootstrap"; + +@import "fontawesome/fontawesome"; +@import "fontawesome/brands"; +@import "fontawesome/solid"; + +// @import "iconfont"; uncomment to enable custom icons diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_all.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_all.scss new file mode 100644 index 00000000..00eac383 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_all.scss @@ -0,0 +1,22 @@ +// ############################################################################# +// +// This folder is used to store additional functions or mixins which are +// not part of the default bootstrap eco-system. +// +// We provide already some helper functions such as em(12px, 16px) +// that calculates the pixel values from a given size in relation to +// the parent size. +// +// Additionally we have mixins for managing z-index layers and +// hide-content. +// +// this folder is for additional mixins +// each mixin should be its own maintained file +// +// ############################################################################# + +@import "functions"; +@import "zindex"; +@import "other"; +@import "material_design"; +@import "colours"; diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_colours.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_colours.scss new file mode 100644 index 00000000..e119a3ef --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_colours.scss @@ -0,0 +1,11 @@ +// ############################################################################# +// Colours + +@mixin white_on_dark() { + @extend .bg-dark; + @extend .text-white; + + a { + @extend .text-white; + } +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_functions.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_functions.scss new file mode 100644 index 00000000..55988d59 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_functions.scss @@ -0,0 +1,12 @@ +// ############################################################################# +// FUNCTIONS + +// basic mathematical functions that are helpful + +// em calculation +@function em($target, $context: $font-size) { + @if $target == 0 { + @return 0 + } + @return $target / $context + em; +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_material_design.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_material_design.scss new file mode 100644 index 00000000..f014ae44 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_material_design.scss @@ -0,0 +1,6 @@ +// ############################################################################# +// Material Design + +@mixin rised-component() { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_other.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_other.scss new file mode 100644 index 00000000..ef116d34 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_other.scss @@ -0,0 +1,25 @@ +// ############################################################################# +// OTHER + +// hides text in an element so you can see the background. +// INFO: conflicts otherwise with hide-text from bootstrap +@mixin hide-content() { + $approximate-em-value: 12px / 1em; + $wider-than-any-screen: -9999em; + text-indent: $wider-than-any-screen * $approximate-em-value; + overflow: hidden; + text-align: left; +} + +// outline button mixin which extends bootstrap button-variant +@mixin button-variant-outline($color, $border, $color-hover: #fff) { + @include button-variant($color, transparent, $border); + &:hover, + &:active, + &.active, + &:focus, + .open > &.dropdown-toggle { + color: $color-hover; + background-color: $border; + } +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_zindex.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_zindex.scss new file mode 100644 index 00000000..8f5c64bf --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/mixins/_zindex.scss @@ -0,0 +1,31 @@ +// ############################################################################# +// ZINDEX + +// handle z-index mor easily +// DOCS: http://www.sitepoint.com/better-solution-managing-z-index-sass/ + +@function map-has-nested-keys($map, $keys...) { + @each $key in $keys { + @if not map-has-key($map, $key) { + @return false; + } + $map: map-get($map, $key); + } + + @return true; +} +@function map-deep-get($map, $keys...) { + @each $key in $keys { + $map: map-get($map, $key); + } + + @return $map; +} +@function z($layers...) { + @if not map-has-nested-keys($z-layers, $layers...) { + @error "No layer found for `#{inspect($layers...)}` + in $z-layers map. Property omitted."; + } + + @return map-deep-get($z-layers, $layers...); +} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_all.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_all.scss new file mode 100644 index 00000000..32e551f8 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_all.scss @@ -0,0 +1,24 @@ +// ############################################################################# +// +// It is very useful to store values, that are used more than twice, +// within their own variable. We even encourage storing all colour values +// within the settings. Don’t repeat yourself. Create a sub-structure, +// similar to /sites if the structure becomes more complex. +// +// Warning +// Do not add additional variables to +// /private/sass/settings/_bootstrap.scss. This file is reserved for +// Bootstrap-only variables. Use /private/sass/settings/_custom.scss +// instead. +// +// this folder contains required variables for colors, sizes, styles and more +// add additional files for encapsulated settings +// +// ############################################################################# + +@import "pre_bootstrap"; +@import "bootstrap4"; + +// @import "fontawesome_v5"; + +//! @import "custom"; diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_bootstrap4.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_bootstrap4.scss new file mode 100644 index 00000000..7f53fe9a --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_bootstrap4.scss @@ -0,0 +1,21 @@ +// ############################################################################# +// Typography + +$font-family-sans-serif: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-serif: "Roboto Slab",Georgia, "Times New Roman", Times, serif; +// Default monospace fonts for ``, ``, and `
`.
+$font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
+
+// #############################################################################
+// Grid system
+
+// Point at which the navbar becomes uncollapsed.
+$grid-float-breakpoint: 768px; // medium
+
+// #############################################################################
+// Code
+
+// User in _getthecode.scss
+// $pre-color
+$pre-bg:                      #f5f5f5;
+$pre-border-color:            #ccc;
diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_custom.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_custom.scss
new file mode 100644
index 00000000..23864328
--- /dev/null
+++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_custom.scss
@@ -0,0 +1,34 @@
+// #############################################################################
+//
+// CUSTOM SETTINGS
+//
+// this setting file is intended for additional variables not defined within the bootstrap default
+// feel free to create additional files if settings get cluttered
+//
+// #############################################################################
+
+// #############################################################################
+
+$speed-base:    200ms;
+
+// #############################################################################
+// Z-INDEX
+// DOCS: http://www.sitepoint.com/better-solution-managing-z-index-sass/
+// http://sassmeister.com/gist/341c052928c956c1a751
+// use case: header { z-index: z("modal", "header"); }
+
+/*
+$z-layers: (
+    "goku":            9001,
+    "shoryuken":       8000,
+    "modal": (
+        "base":         500,
+        "close":        100,
+        "header":        50,
+        "footer":        10
+    ),
+    "default":            1,
+    "below":             -1,
+    "bottomless-pit": -9000
+);
+*/
diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_fontawesome_v5.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_fontawesome_v5.scss
new file mode 100644
index 00000000..2d335bcf
--- /dev/null
+++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_fontawesome_v5.scss
@@ -0,0 +1,2 @@
+// #############################################################################
+// Place to override Font Awesome variables
diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_pre_bootstrap.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_pre_bootstrap.scss
new file mode 100644
index 00000000..e318f7fe
--- /dev/null
+++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/settings/_pre_bootstrap.scss
@@ -0,0 +1,8 @@
+// #############################################################################
+// These variables are used in _bootstrap_v3.scss
+
+$mytheme-dark-gray:                     #35363C;
+$mytheme-green:                         #a1c42c;
+
+$mytheme-highlighted-color:             $mytheme-green;
+// $mytheme-rule-color:                    $gray-darker;
diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/sites/_all.scss b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/sites/_all.scss
new file mode 100644
index 00000000..58d52205
--- /dev/null
+++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/scss/sites/_all.scss
@@ -0,0 +1,50 @@
+// #############################################################################
+//
+// Besides /addons you will work mostly within the /private/sass/sites
+// folder. All custom elements that are in general not plug-and-playable,
+// fixed into the website somewhere or specific components, get thrown in
+// here.
+//
+// This will force you to devise and adhere to structure
+// patterns. Here are some examples depending on the requirements for
+// your project:
+//
+// Multisite Setup
+//  Let’s assume you create one style guide sharing different marketing
+//  websites or applications - your structure might look something
+//  like:
+//
+// sites/
+// ├─ application/
+// │  ├─ _all.scss
+// │  ├─ _general.scss
+// │  └─ _wizard.scss
+// ├─ marketing/
+// │  ├─ _all.scss
+// │  ├─ _layout.scss
+// │  └─ _addons.scss
+// ├─ _application.scss (imports application/_all.scss)
+// └─ _marketing.scss (imports marketing/_all.scss)
+//
+// Theme Setup
+//  If you are using different themes for the same markup, your
+//  structure might look something like:
+//
+// sites/
+// ├─ dark_theme/
+// │  ├─ _all.scss
+// │  ├─ _header.scss
+// │  └─ _footer.scss
+// ├─ white_theme/
+// │  ├─ _all.scss
+// │  ├─ _header.scss
+// │  └─ _footer.scss
+// ├─ dark_theme.scss (imports dark_theme/_all.scss)
+// └─ white_theme.scss (imports white_theme/_all.scss)
+//
+// this folder contains overrides and additional css which cannot be categorised
+// files can be split and included here
+//
+// #############################################################################
+
+//! @import "custom";
diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/static/webfonts/setup.sh b/third_party/PySpice-org/PySpice/doc/pelican/theme/static/webfonts/setup.sh
new file mode 100644
index 00000000..538bcb44
--- /dev/null
+++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/static/webfonts/setup.sh
@@ -0,0 +1 @@
+for i in ../../node_modules/@fortawesome/fontawesome-free/webfonts/* ; do ln -sf $i; done
diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/404.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/404.html
new file mode 100644
index 00000000..b55fdd0f
--- /dev/null
+++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/404.html
@@ -0,0 +1,8 @@
+{# -*- engine: jinja2 -*- #}
+{% extends "base.html" %}
+
+{% block content %}
+    
+ {{ page.content }} +
+{% endblock content %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/archives.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/archives.html new file mode 100644 index 00000000..f2d866b1 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/archives.html @@ -0,0 +1,15 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} - Archives{% endblock %} + +{% block content %} +

Archives for {{ SITENAME }}

+ +
+{% for article in dates %} +
{{ article.locale_date }}
+
{{ article.title }}
+{% endfor %} +
+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/article.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/article.html new file mode 100644 index 00000000..c29784ed --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/article.html @@ -0,0 +1,69 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block html_lang %}{{ article.lang }}{% endblock %} + +{% block title %}{{ SITENAME }} - {{ article.title }}{% endblock %} + +{% block head %} + {{ super() }} + + {% import 'translations.html' as translations with context %} + {% if translations.entry_hreflang(article) %} + {{ translations.entry_hreflang(article) }} + {% endif %} + + {% if article.description %} + + {% endif %} + + {% for tag in article.tags %} + + {% endfor %} + +{% endblock %} + +{% block content %} +
+
+

+ {{ article.title }}

+ {% import 'translations.html' as translations with context %} + {{ translations.translations_for(article) }} +
+
+ + {% if article.modified %} + + {% endif %} + {% if article.authors %} +
+ By {% for author in article.authors %} + {{ author }} + {% endfor %} +
+ {% endif %} + {% if article.category %} +
+ Category: {{ article.category }} +
+ {% endif %} + {% if article.tags %} +
+ Tags: + {% for tag in article.tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} +
+
+ {{ article.content }} +
+
+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/articles.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/articles.html new file mode 100644 index 00000000..da672637 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/articles.html @@ -0,0 +1,41 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block content %} + +
+ + {% block content_title %} +

All articles

+ {% endblock %} + +
    + {% for article in articles_page.object_list %} +
  1. + {#
    #} +

    + + {{ article.title }} + +

    + {#
    #} + {#
    #} + +
    By + {% for author in article.authors %} + {{ author }} + {% endfor %} +
    + {#
    #} +
    {{ article.summary }}
    +
  2. + {% endfor %} +
+ + {% if articles_page.has_other_pages() %} + {% include 'pagination.html' %} + {% endif %} + +
+ +{% endblock content %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/author.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/author.html new file mode 100644 index 00000000..91c46b44 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/author.html @@ -0,0 +1,9 @@ +{# -*- engine: jinja2 -*- #} +{% extends "articles.html" %} + +{% block title %}{{ SITENAME }} - Articles by {{ author }}{% endblock %} + +{% block content_title %} +

Articles by {{ author }}

+{% endblock %} + diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/authors.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/authors.html new file mode 100644 index 00000000..00c16186 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/authors.html @@ -0,0 +1,13 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} - Authors{% endblock %} + +{% block content %} +

Authors on {{ SITENAME }}

+
    + {% for author, articles in authors|sort %} +
  • {{ author }} ({{ articles|count }})
  • + {% endfor %} +
+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base.html new file mode 100644 index 00000000..1f48bb7b --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base.html @@ -0,0 +1,109 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base_root.html" %} + +{% block extend_root %} + + + + {# -> Page Header #} + {% block header %} + {# Doc: http://getbootstrap.com/components/#navbar #} + {# -> Skip link for screen reader #} + + {% trans %}Skip to main content{% endtrans %} + + + + {% endblock header %} + + {# -> Page Content #} + {#
#} +
+ {% block content %} + {% endblock %} +
+ + {# -> Page Footer #} + {% block footer %} + + {% endblock footer %} + +{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base_root.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base_root.html new file mode 100644 index 00000000..11f8a4f4 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/base_root.html @@ -0,0 +1,131 @@ +{# -*- engine: jinja2 -*- #} + + + + {% block head %} + + {# Doc: for old IE ... #} + + + + + {% block title %}{{ SITENAME }}{% endblock title %} + + + {% block meta_tags %} + {# Doc: https://support.google.com/webmasters/answer/79812?hl=fr #} + + + {# #} + {# #} + {% endblock meta_tags %} + + {% block opengraph %} + + {# #} + + + + + + + {# #} + {# #} + {% endblock opengraph %} + + {% block twitter_card %} + {# Doc: https://dev.twitter.com/cards/types/summary-large-image #} + + + + + + + {% endblock twitter_card %} + + + + + + + + {% if FEED_ALL_ATOM %} + + {% endif %} + {% if FEED_ALL_RSS %} + + {% endif %} + {% if FEED_ATOM %} + + {% endif %} + {% if FEED_RSS %} + + {% endif %} + {% if CATEGORY_FEED_ATOM and category %} + + {% endif %} + {% if CATEGORY_FEED_RSS and category %} + + {% endif %} + {% if TAG_FEED_ATOM and tag %} + + {% endif %} + {% if TAG_FEED_RSS and tag %} + + {% endif %} + + {# -> Extra head customisation #}{% block extrahead %}{% endblock %} + + {# FIXME #} + {#% include "includes/third-parties/cookieconsent.html" %#} + {% endblock head %} + + + + {# {% include "includes/third-parties/analytics.html" %} #} + {# FIXME #} + {# {% include "includes/third-parties/piwik.html" %} #} + + {# -> Body Content #}{% block extend_root %}{% endblock %} + + + + + {# #} + + {# -> Extra Foot #}{% block extrafoot %}{% endblock %} + + {% block outdated_browser %} + + + + {% endblock %} + + + diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/categories.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/categories.html new file mode 100644 index 00000000..8d1a985c --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/categories.html @@ -0,0 +1,13 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} - Categories{% endblock %} + +{% block content %} +

Categories on {{ SITENAME }}

+
    + {% for category, articles in categories|sort %} +
  • {{ category }} ({{ articles|count }})
  • + {% endfor %} +
+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/category.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/category.html new file mode 100644 index 00000000..7337dcb1 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/category.html @@ -0,0 +1,9 @@ +{# -*- engine: jinja2 -*- #} +{% extends "articles.html" %} + +{% block title %}{{ SITENAME }} - {{ category }} category{% endblock %} + +{% block content_title %} +

Articles in the {{ category }} category

+{% endblock %} + diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/gosquared.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/gosquared.html new file mode 100644 index 00000000..34e20d08 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/gosquared.html @@ -0,0 +1,15 @@ +{# -*- engine: jinja2 -*- #} +{% if GOSQUARED_SITENAME %} + +{% endif %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/index.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/index.html new file mode 100644 index 00000000..2e754512 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/index.html @@ -0,0 +1,6 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block content %} + +{% endblock content %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/page.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/page.html new file mode 100644 index 00000000..b786de89 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/page.html @@ -0,0 +1,29 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block html_lang %}{{ page.lang }}{% endblock %} + +{% block title %}{{ SITENAME }} - {{ page.title }}{%endblock%} + +{% block head %} + {{ super() }} + + {% import 'translations.html' as translations with context %} + {% if translations.entry_hreflang(page) %} + {{ translations.entry_hreflang(page) }} + {% endif %} +{% endblock %} + +{% block content %} +

{{ page.title }}

+ {% import 'translations.html' as translations with context %} + {{ translations.translations_for(page) }} + + {{ page.content }} + + {% if page.modified %} +

+ Last updated: {{ page.locale_modified }} +

+ {% endif %} +{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/pagination.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/pagination.html new file mode 100644 index 00000000..63f019e1 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/pagination.html @@ -0,0 +1,12 @@ +{# -*- engine: jinja2 -*- #} +{% if DEFAULT_PAGINATION %} +

+ {% if articles_page.has_previous() %} + « + {% endif %} + Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} + {% if articles_page.has_next() %} + » + {% endif %} +

+{% endif %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/period_archives.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/period_archives.html new file mode 100644 index 00000000..fee548be --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/period_archives.html @@ -0,0 +1,15 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} - {{ period | reverse | join(' ') }} archives{% endblock %} + +{% block content %} +

Archives for {{ period | reverse | join(' ') }}

+ +
+{% for article in dates %} +
{{ article.locale_date }}
+
{{ article.title }}
+{% endfor %} +
+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tag.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tag.html new file mode 100644 index 00000000..4e3c9b09 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tag.html @@ -0,0 +1,8 @@ +{# -*- engine: jinja2 -*- #} +{% extends "articles.html" %} + +{% block title %}{{ SITENAME }} - {{ tag }} tag{% endblock %} + +{% block content_title %} +

Articles tagged with {{ tag }}

+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tags.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tags.html new file mode 100644 index 00000000..aaab5430 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/tags.html @@ -0,0 +1,13 @@ +{# -*- engine: jinja2 -*- #} +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} - Tags{% endblock %} + +{% block content %} +

Tags for {{ SITENAME }}

+
    + {% for tag, articles in tags|sort %} +
  • {{ tag }} ({{ articles|count }})
  • + {% endfor %} +
+{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/translations.html b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/translations.html new file mode 100644 index 00000000..bb35a7bf --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/pelican/theme/templates/translations.html @@ -0,0 +1,18 @@ +{# -*- engine: jinja2 -*- #} + +{% macro translations_for(article) %} +{% if article.translations %} + Translations: + {% for translation in article.translations %} + {{ translation.lang }} + {% endfor %} +{% endif %} +{% endmacro %} + +{% macro entry_hreflang(entry) %} + {% if entry.translations %} + {% for translation in entry.translations %} + + {% endfor %} + {% endif %} +{% endmacro %} diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/Makefile.sphinx b/third_party/PySpice-org/PySpice/doc/sphinx/Makefile.sphinx new file mode 100644 index 00000000..d088b869 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/Makefile.sphinx @@ -0,0 +1,139 @@ +# -*- Makefile -*- +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +CLIENTSOPTS = -D master_doc=contents-client + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +htmlclient: + $(SPHINXBUILD) -b html $(CLIENTSOPTS) $(ALLSPHINXOPTS) $(BUILDDIR)/html-client + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html-client." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GVLab.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GVLab.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/GVLab" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GVLab" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +# End diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/make-html b/third_party/PySpice-org/PySpice/doc/sphinx/make-html new file mode 100644 index 00000000..77452db8 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/make-html @@ -0,0 +1,5 @@ +#! /bin/bash + +make -f Makefile.sphinx html + +# End \ No newline at end of file diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/abbreviation.txt b/third_party/PySpice-org/PySpice/doc/sphinx/source/abbreviation.txt new file mode 100644 index 00000000..c2cf32ee --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/abbreviation.txt @@ -0,0 +1,31 @@ +.. -*- 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 diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/bibliography.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/bibliography.rst new file mode 100644 index 00000000..050b8297 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/bibliography.rst @@ -0,0 +1,33 @@ +.. _bibliography-page: + +============== + Bibliography +============== + +SPICE References +---------------- + +The `technical report No. UCB/ERL M520 `_ +written by Nagel gives an interesting description of the algorithms involved in Spice. I recommend +to read part 2 and 3 that explain the equation formulation. The next parts describes the +numerical analysis. + +The relevant references on Spice are by date of publication: + + * `Technical Report No. UCB/ERL M382 `_; + SPICE (Simulation Program with Integrated Circuit Emphasis); + Nagel, Laurence W. and Pederson, D.O.; + April 1973 + + * `Technical Report No. UCB/ERL M520 `_; + SPICE2: A Computer Program to Simulate Semiconductor Circuits; + Nagel, Laurence W.; + 1975 + + * `Technical Report No. UCB/ERL M89/42 `_; + Analysis of Performance and Convergence Issues for Circuit Simulation; + Quarles, Thomas L.; + 1989 + + * `official spice3f5 source code `_ + on eecs.berkeley.edu server, the page contains some links on Spice (historical notes). diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/conf.py b/third_party/PySpice-org/PySpice/doc/sphinx/source/conf.py new file mode 100644 index 00000000..57d92e39 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/conf.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- + +#################################################################################################### +# +# PySpice - A Spice package for Python +# Copyright (C) 2017 Salvaire Fabrice +# +#################################################################################################### + +#################################################################################################### +# +# PySpice documentation build configuration file, created by +# sphinx-quickstart on Fri Apr 8 16:54:03 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. +# +#################################################################################################### + +#################################################################################################### + +import pathlib + +import sys, os +from datetime import datetime + +# http://www.sphinx-doc.org/en/stable/extdev/logging.html +# from sphinx.util import logging +import logging +logger = logging.getLogger(__name__) + +try: + import sphinx_rtd_theme +except: + logger.warning('Failed to import sphinx_rtd_theme') + pass + +import PySpice + +#################################################################################################### + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# for directory in ,: + +pyspice_path = pathlib.Path(__file__).parents[3] +sys.path.insert(0, str(pyspice_path)) + +#################################################################################################### + +exec(compile(open(pyspice_path.joinpath('setup_data.py')).read(), 'setup_data.py', 'exec')) + +#################################################################################################### +# +# General configuration +# + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + # 'sphinx.ext.pngmath', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinxcontrib.getthecode', + ] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'PySpice' +copyright = '{0.year}, Fabrice Salvaire'.format(datetime.now()) + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The full version, including alpha/beta/rc tags. +release = PySpice.__version__ +# The short X.Y version. +version = '.'.join(release.split('.')[:2]) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +#################################################################################################### +# +# Options for Autodoc +# + +# Show both class-level docstring and __init__ docstring in class documentation +autoclass_content = 'both' +autodoc_default_flags = [ + 'members', + 'undoc-members', + # 'private-members', + # 'special-members', + # 'inherited-members', + # 'show-inheritance', + ] + +#################################################################################################### +# +# Options for HTML output +# + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'PySpice' +html_theme = 'PySpiceRtd' +# html_theme = 'sphinx_rtd_theme' + +# on_rtd is whether we are on readthedocs.org +## on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +## if not on_rtd: # only import and set the theme if we're building docs locally +## import sphinx_rtd_theme +## html_theme = 'sphinx_rtd_theme' +## html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['themes'] +# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PySpice' + +#################################################################################################### +# +# Options for LaTeX output +# + +# The paper size ('letter' or 'a4'). +# WARNING: latex_paper_size is deprecated. Use latex_elements['papersize'] instead. +# Fixme: don't work ??? +latex_paper_size = 'a4' + +# The font size ('10pt', '11pt' or '12pt'). +latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'PySpice.tex', 'PySpice Documentation', 'Fabrice Salvaire', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +# latex_preamble = '' + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + +#################################################################################################### +# +# Options for manual page output +# + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'PySpice', 'PySpice Documentation', ['Fabrice Salvaire'], 1) +] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} + +#################################################################################################### +# +# MathJax +# + +# Don't work +# mathjax_path = 'MathJax/MathJax.js' + +#################################################################################################### +# +# Getthecode +# + +getthecode_header_template = ''' +
+ +
+''' + +getthecode_header_template = getthecode_header_template.replace('@VERSION@', version) diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/design-notes.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/design-notes.rst new file mode 100644 index 00000000..d538a741 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/design-notes.rst @@ -0,0 +1,7 @@ +.. _design-note-page: + +============== + Design Notes +============== + +To be written ... diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/development.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/development.rst new file mode 100644 index 00000000..fd57d858 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/development.rst @@ -0,0 +1,32 @@ +.. _development-page: + +========================= + Development & Community +========================= + +PySpice is an open-source project, and relies on its community of users to keep getting better. + +PySpice source code and issues are managed on `Github `_. + +**Note to Packagers: Please don't create PySpice package (PiPY do the job)** + +How you can help ? +------------------ + +As an open source project, anyone is welcome to contribute in whatever form they are able. + +.. , which can include taking part in discussions, filing bug reports, proposing improvements, + contributing code or documentation, and testing it. + +* Promote PySpice on the web and all around you +* Fill bug reports +* Test PySpice on Linux, Windows and OS X +* Check for errors on the documentation +* Propose improvements +* Implement missing features +* Provides additional examples + +Contributors +------------ + +The list of contributors is available at https://github.com/FabriceSalvaire/PySpice/graphs/contributors diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/donate.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/donate.rst new file mode 100644 index 00000000..983fd50b --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/donate.rst @@ -0,0 +1,27 @@ +.. _donate-page: + +======================= + How to Help Further ? +======================= + +If you like PySpice, you can donate for its developement to my `PayPal account +`_. + +This budget could help me to mainly finance a domain, a vps to host a site, a forum (*) and also to +participate to conference on open source Electronic Design Automation. + +(*) This web site is actually hosted on my vps, which is quite loaded. I don't use hosting site +since it is easier to update the documentation using my own infrastructure. + +Professional Support +==================== + +PySpice is provided as is and I don't provide extensive support. I just solve issues and implement +new features when I have time to do it, like usual for any open source projects. + +If you are student, I will not help you to do your exercises. + +If you work on a company, I will not help you to perform simulations. + +However if you are interested by a professional support. You can contact my at the address *pyspice* +at my domain name *fabrice-salvaire.fr* diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/example-introduction.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/example-introduction.rst new file mode 100644 index 00000000..5223cbef --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/example-introduction.rst @@ -0,0 +1,175 @@ +.. include:: abbreviation.txt + +.. _example-introduction-page: + +.. TO BE CHECKED + +============================== + Introduction to the Examples +============================== + +This section gives an overview of the use of PySpice by means of examples inspired from typical +circuits, which can also serve as learning materials. + +.. .. warning:: The PDF generated by the Sphinx documentation generator could be poorly formatted. + +Usually these examples don't involve advanced Python programming. You just need to know basic Python +programming and how to use the Numpy and Matplotlib framework. However the code to make complex +plots can require advanced Matplotlib skills. + +**Each example features a banner with a download icon just after the file name and a button (>>>) on +the right to show the content of the file.** + +--------------------- + How to Learn Python +--------------------- + +Some links to learn about Python programming and Scientific Framework: + +* `The Python Tutorial `_ +* `Pyplot tutorial `_ +* `Python Scientific Lecture Notes `_ +* `Scientific Python Lectures `_ + +--------------------------- + How to Run these Examples +--------------------------- + +.. CHECK JUPYTER + +There are several ways to run the examples: as a script from the console, in the |IPython|_ +interactive environment or in a web browser using a IPython Notebook. Each of them has their +advantages and drawbacks. A script is best when we want to work on a file using an editor, and an +interactive environment is best when we want to play with code interactively. + +To run an example from the console, execute this command: + +.. code-block:: sh + + python examples/.../foo.py + +To start the interactive IPython environment, execute one of these commands: + +.. code-block:: sh + + ipython --matplotlib # enable matplotlib integration + ipython --matplotlib-qt # enable matplotlib integration with qt4 backend + ipython qtconsole # start the qtconsole GUI application + +then run an example using the magic command: + +.. code-block:: py + + %run examples/../foo.py + +To start the IPython notebook in your web browser: + +.. code-block:: sh + + ipython notebook + +When we use IPython notebook, it is convenient to use a Matplotlib backend that outputs the graphics +embedded in the notebook file. To activate this backend, somewhere in the beginning on the notebook, +you must add: + +.. code-block:: py + + %matplotlib inline + +Then you can copy-paste code blocks and execute them. + +.. + .. warning:: + + Notice that for security reason, web browsers don't offer a simple way to copy-paste a code block, + i.e. fill the clipboard using a button and some javascript behind the scene (Github and others use a hack + based on Flash to achieve this). + + You must select and copy the code by hand. + +------------------------ + How to Write a Netlist +------------------------ + +Obviously it is not easy to write a netlist from scratch. The best approach is to make a quick +sketch and to bless each node. However you can also use a schematic capture progam like |Kicad|_ +and then export the netlist to SPICE. + +You can also use `SKiDL `_ which is a Python module +that allows you to compactly describe the interconnection of electronic circuits and components, see +this full example +https://github.com/xesscorp/skidl/blob/master/examples/spice-sim-intro/spice-sim-intro.ipynb + +.. You can sketch on a sheet of paper or using a pen display in a modern way. + +----------------------- + How to Draw a Circuit +----------------------- + +There are several ways to draw a circuit, depending on whether you want a high-quality figure or +just something quickly: + +* Sketch a circuit on a sheet of paper and scan it, or better use a tablet. Note that this can be + artistic if you are skilled. + +* Use a schematic editor like KiCad and export the circuit as SVG, KiCad allows you to choose the + line width, a color or black pen and to remove the frame. You can then edit the SVG using + `Inkscape `_. Usually KiCad SVG output might not be perfect and the lake of + Python interface to the schematic editor does not help. + +* Use `Schemdraw `_ a Python package for producing + high-quality electrical circuit schematic diagrams (SVG). Circuit elements are added, one at a + time, similar to how you might draw them by hand, using Python methods. + +* Use complex graphic languages like `CircuiTikZ `_ or + `Circuit_macros `_. The first one is a LaTeX + Tikz package and the second use M4 macro and LaTeX to render circuits. These tools are well + suited for publications or books. + +A vector graphic format like SVG is preferable for web publications, rather PDF is preferable for +paper publications. The main difference between SVG and PDF is the way they handle fonts, else they +are interchangeable and convertible to each other. + +---------------------------------------------------- + Should I Use a Python File or a Jupyter Notebook ? +---------------------------------------------------- + +`JupyterLab `_ provides an environment as a web application similar to Matlab +for Python and other languages. + +To answer this question in a few words, if you are looking for an environment similar to Matlab, +then Jupyter could be an option. But if you want to do complex programming, then standard Python +files are the way to go. + +From a technical point of view, a `Jupyter Notebook `_ +is a JSON file, i.e. a Python dictionary encoded in a text file, that embed cells which are either +Python code, Markdown text, or images (base-64 encoded). + +Form the Git point of view, a plain Python file is more suited than a JSON file that embed codes and +outputs. + +As an alternative to a Jupyter Notebook, we can implement the concept of **literate programming**, +the idea is to mix rich texts and codes in source files. Some implementations will embed rich texts +as comments, other will require a tool to split the text and the code. Conceptually, it is similar +to a Jupyter Notebook, excepted a notebook can embed results and is not designed to be human +readable. + +PySpice examples use the `Pyterate `_ documentation +generator where a file is a plain Python file with texts encoded as comments. You can also look to +`Pweave `_ as an alternative. + +A Jupyter Notebook has also the following drawbacks : + +* a Notebook imposes to use a web browser as an editor, a workaround is to use an editor plugin like + `Emacs IPython Notebook (EIn) `_, +* JupyterLab is a very bad code editor and doesn't implement common features found in an IDE like + completion, `linting `_ etc. +* JupyterLab require extensions, e.g. https://github.com/matplotlib/ipympl to get interactive plots, +* outputs should not be committed, a workaround is to run the command `jupyter nbconvert + my_input_notebook.ipynb --to notebook --ClearOutputPreprocessor.enabled=True --output + my_output_notebook`, +* a JSON diff is not so readable by humans, +* it is risky to apply a text replacement on files, +* Jupyter hang on OSX continuous platform virtual machines, + this prevents to run notebooks, +* SVG image support seems broken in JupyterLab, diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/example-whish-list.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/example-whish-list.rst new file mode 100644 index 00000000..9f872943 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/example-whish-list.rst @@ -0,0 +1,97 @@ +.. _exemple-wish-list-page: + +=================== + Example Wish List +=================== + +Operational Points +------------------ + +* https://ahkab.readthedocs.io/en/latest/examples/Simple_OP.html +* DAC using resistor network +* Wheastone Bridge + +Amplifier +--------- + +* AC Coupled Amplifier + +Diode +----- + +* Clampers +* Restaurateur de niveau moyen +* Detecteur d'enveloppe +* Redresseur sans seuil + +Filters +------- + +* Wien filter +* Tuinnenga p50 RLC +* RC / RL Bridge +* Butterworth +* High Frequence Probe (Delacressonnière p57) +* https://ahkab.readthedocs.io/en/latest/examples/PZ_Example.html + +Inductors +---------- + +* https://ahkab.readthedocs.io/en/latest/help/Mutual-Inductors.html + +Operational Amplifier +--------------------- + +* Integrator / Derivative +* Exponential / Logarithmic +* Current Source +* Astable +* Active filter +* Rauch +* Thermal Probe (Delacressonnière p43) +* Negative Impedance NIC (Delacressonnière p67) +* Peak-to-Peak Detector + +Oscillators +----------- + +* Colpitts Oscillator + https://ahkab.readthedocs.io/en/latest/examples/Transient-Example.html +* Quartz oscillator + +Power Supplies +-------------- + +* Zener Voltage Regulator + +Switching Power Supplies +------------------------ + +Telecommunication +----------------- + +* Simulate resistance using capacitive commutation (Delacressonnière p27) + +Transistor +---------- + +* All configurations (Bolyestad p293) +* Cascode +* Darlington +* Feedback Pair +* Relay Driver +* Constant-Current Source +* Current Mirror +* Logic Gates +* Random Noise Generator (Bolyestad p329) + +MOSFET +------ + +SCR +--- + +* Thyristor +* Diac +* Triac +* Zero Crossing Detector diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/faq.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/faq.rst new file mode 100644 index 00000000..20dc7aee --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/faq.rst @@ -0,0 +1,163 @@ +.. include:: project-links.txt +.. include:: abbreviation.txt + +.. _user-faq-page: + +========== + User FAQ +========== + +PySpice FAQ +=========== + +How to get help or report an issue ? +------------------------------------ + +PySpice has a **Forum** hosted at https://pyspice.discourse.group + +**If you encounter an issue, please file an issue** on the `Issue Tracker `_. + + +How to typeset :code:`u_kΩ` or :code:`u_μV` in Python code ? +------------------------------------------------------------ + +There are three solutions if you don't have these Unicode characters available on your keyboard. The +first one is to use the ASCII alternative: :code:`u_kOhm` or :code:`u_uV.`. The second one is to +define macros on your favourite editor. The last one is to customise your keyboard settings (on Linux look at https://www.x.org/wiki/XKB/). + + +How to perform division with units ? +------------------------------------ + +According to the Python `operator precedence +`_, division operators +have a higher priority than the matrix multiplication operator. In consequence you must have +parenthesis to perform something like :code:`(10@u_s) / (2@_us)`. + +**It is currently an issue ...** + + +Is unit API well tested ? +------------------------- + +**Unit API is an ongoing work. You must use it with caution since it can be buggy or incomplete.** + + +Is ground node required ? +------------------------- + +**Yes**, according to Ngspice manual, each circuit has to have a ground node (gnd or 0)! + + +How to deal with SPICE parameters that clash with Python keywords ? +------------------------------------------------------------------- + +For such cases, PySpice accepts keyword arguments with a trailing underscore, for example: + +.. code-block:: py3 + + model = circuit.model('Diode', 'D', is_=1) + model.is_ = 1 + model['is'] = 1 + +We can also use uppercase letters since SPICE is case insensitive. + + +How to pass raw SPICE command ? +------------------------------- + +If the API doesn't yet implement a SPICE command, then you can pass raw SPICE commands using: + +.. code-block:: py3 + + circuit.raw_spice = '...' + circuit.raw_spice += '...' + +and raw parameters using: + +.. code-block:: py3 + + r1 = circuit.R('1', 1, 0, raw_spice='...') + r1.raw_spice = '...' + r1.raw_spice += '...' + +.. warning:: However the API must be aware of the nodes in order to retrieve data from the simulation output. + + +How to set the simulator ? +-------------------------- + +You can set globally the default simulator using the attribute :attr:`PySpice.Spice.Simulation.CircuitSimulator.DEFAULT_SIMULATOR`. + +Else you can set the simulator per simulation using the `simulator` option: + +.. code:: py + + simulator = circuit.simulator(simulator='...') + +Actually, theses simulators are available: + + * `ngspice-subprocess` + * `ngspice-shared` + * `xyce-serial` + * `xyce-parallel` + + +Ngspice FAQ +=========== + +How to get the Ngspice manual ? +------------------------------- + +Either download it from http://ngspice.sourceforge.net or use the command: + +.. code-block:: sh + + pyspice-post-installation --download-ngspice-manual + + +How to set the Ngspice library path ? +------------------------------------- + +If the default setting doesn't match your environment, then you have to fix globally the attribute +:attr:`PySpice.Spice.NgSpice.Shared.NgSpiceShared.LIBRARY_PATH`. Note you have to place a brace pair +just before the extension, for example :file:`C:\\...\\ngspice{}.dll`. + +You can also fix the value of :attr:`PySpice.Spice.NgSpice.Shared.NgSpiceShared.NGSPICE_PATH`. + + +How to set the Ngspice executable path ? +---------------------------------------- + +If the default setting doesn't match your environment, then you can fix globally the Ngspice executable +path using the attribute :attr:`PySpice.Spice.NgSpice.Server.SpiceServer.SPICE_COMMAND`, you can also +pass the executable path to the simulator using:: + + simulator = circuit.simulator(spice_command='...') + + +Xyce FAQ +======== + +How to set the Xyce path ? +-------------------------- + +If the default setting doesn't match your environment, then you can fix globally the Xyce executable +path using the attribute :attr:`PySpice.Spice.Xyce.Server.XyceServer.XYCE_COMMAND`, you can also +pass the executable path to the simulator using:: + + simulator = circuit.simulator(xyce_command='...') + + +Is Xyce 100% compatible with SPICE ? +------------------------------------ + +**No**, you have to read the user guide and reference manual to learn what are the actual differences! + +In particular, the device models provided by vendors could need to be adapted for Xyce. + +Notice, you can add the suffix **@xyce** to a *.lib* or *.mod* file in order to have a special +version for Xyce, for example *BAV21.lib@xyce*. The PySpice Library Manager will include this +special version if it found one that correspond to the simulator used for the current simulation. + +PySpice will try to incrementally provide a generic interface in the future. diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/how-to-refer.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/how-to-refer.rst new file mode 100644 index 00000000..1a2d7bec --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/how-to-refer.rst @@ -0,0 +1,30 @@ +.. _how-to-refer-page: + +=========================== + How to Refer to PySpice ? +=========================== + +Up to now, the official url for PySpice is https://pyspice.fabrice-salvaire.fr + +*A permanent redirection will be implemented if the domain change in the future.* + +On Github, you can use the **pyspice** `topic `_ for repository related to PySpice. + +A typical `BibTeX `_ citation would be, for example: + +.. code:: bibtex + + @software{PySpice, + author = {Fabrice Salvaire}, % actual author and maintainer + title = {PySpice}, + url = {https://pyspice.fabrice-salvaire.fr}, + version = {x.y}, + date = {yyyy-mm-dd}, % set to the release date + } + + @Misc{PySpice, + author = {Fabrice Salvaire}, + title = {PySpice}, + howpublished = {\url{https://pyspice.fabrice-salvaire.fr}}, + year = {yyyy} + } diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/index.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/index.rst new file mode 100644 index 00000000..02fdf589 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/index.rst @@ -0,0 +1,171 @@ +.. include:: abbreviation.txt +.. include:: project-links.txt + +.. raw:: html + + + +.. raw:: html + +
+ +########################################################################### + Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators +########################################################################### + +.. image:: /_static/logo.png + :alt: PySpice logo + :width: 750 + +******** +Overview +******** + +PySpice is a free and open source (*) Python module which interface |Python|_ to the |Ngspice|_ and +|Xyce|_ circuit simulators. + +|Ngspice|_ is a fork of the famous `SPICE `_ circuit simulator, +while |Xyce|_ is a SPICE compatible simulator developed by the `Sandia National Laboratories +`_. + +.. rst-class:: small-text + + (*) PySpice is licensed under GPLv3 therms. + +PySpice implements a Ngspice binding and provides an oriented object API on top of SPICE, the +simulation output is converted to |Numpy|_ arrays for convenience. + +PySpice requires Python 3 and works on Linux, Windows and OS X. + +:ref:`To read more on PySpice ` + +.. raw:: html + +
+ +.. rst-class:: clearfix row + +.. rst-class:: column column2 + +:ref:`news-page` +================ + +What's changed in versions + +.. rst-class:: column column2 + +:ref:`Installation-page` +======================== + +How to install PySpice on your system + +.. rst-class:: column column2 + +:ref:`user-faq-page` +==================== + +Answers to frequent questions + +.. rst-class:: column column2 + +:ref:`examples-page` +==================== + +Many examples to learn how to use PySpice. To learn more on how to run theses examples, read the +:ref:`introduction to the examples `. + +.. rst-class:: column column2 + +:ref:`development-page` +======================= + +How to contribute to the project + +.. rst-class:: column column2 + +:ref:`reference-manual-page` +============================ + +Technical reference material, for classes, methods, APIs, commands. + +.. rst-class:: column column2 + +:ref:`how-to-refer-page` +======================== + +Guidelines to cite PySpice + +.. rst-class:: column column2 + +:ref:`donate-page` +================== + +If you want to donate to the project or need a more professional support. + +.. raw:: html + +
+ +.. why here ??? + +.. rst-class:: clearfix row + +.. raw:: html + +
+ +******************* + Table of Contents +******************* + +.. toctree:: + :maxdepth: 3 + :numbered: + + overview.rst + news.rst + roadmap.rst + installation.rst + simulators.rst + faq.rst + example-introduction.rst + examples/index.rst + example-whish-list.rst + design-notes.rst + reference-manual.rst + development.rst + how-to-refer.rst + donate.rst + on-the-web.rst + related-projects.rst + bibliography.rst + +.. End diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/installation.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/installation.rst new file mode 100644 index 00000000..bedce0a8 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/installation.rst @@ -0,0 +1,333 @@ +.. include:: project-links.txt +.. include:: abbreviation.txt + +.. _installation-page: + +============== + Installation +============== + +You must install a Python environment and the Ngspice/Xyce simulator to use PySpice on your +computer. Since there are many ways to do this, we will only explain the easiest ones in details. + +In Python, the standard way is to use a **Virtual Environment** and the **pip** tool, look at this +`guide `_ for +further information. + +But if you are mainly doing data science, the **easiest solution** is probably to install the `Anaconda +Distribution `_ which is specialised for this purpose. +You can also prefer its lightweight counterpart `Miniconda +`_. + +Anaconda has the advantage to provide a self consistent environment to the user, for example it +installs automatically Ngspice for you when you install PySpice. But for this reason, especially on +Linux, an Anaconda distribution will require much more disk space than a virtual environment. + +.. note:: **We recommend that you read the documentation in this order, first Windows to get the + novice story, then Linux to get the Unix OS story and finally OSX if you are concerned.** + + +PySpice Packages +---------------- + +**Note to Linux Packagers: Please do not create a PySpice package, PyPI and Anaconda do the job.** + +PySpice is available as a `Anaconda `_ and `PyPI +`_ package. For Anaconda there is two channels, the official one is +`conda-forge`, the second one `fabricesalvaire` **is only used for testing**. + + +Ngspice on conda-forge +---------------------- + +.. note:: If you decide to use the conda-forge package, you do not need to install Ngspice manually + since it is provided as a dependency package: ngspice-lib. + +However if you want the Ngspice executable on your system, then run this command: + +.. code-block:: sh + + conda install -c conda-forge ngspice-exe # install the ngspice executable + + conda install -c conda-forge ngspice # install the master package + conda install -c conda-forge ngspice-lib # install the ngspice library + + +PySpice Continuous Integration +------------------------------ + +PySpice is tested on theses platforms: + +* Travis CI : + + * Bionic Ubuntu Linux (Ngspice is compiled manually) + * macOS 10.14.4 Mojave (use Brew) + * Windows 10.0.17134 (use `Chocolatey `_) + +* Azure CI + +* Fedora + + +On Windows +---------- + +The preferred solution for installing a Python environment on Windows is to install Anaconda or +Miniconda. + +Once Anaconda is installed, open the `Anaconda Navigator +`_ and launch a **console** for your root environment. + +Then the only thing to do to get PySpice on Anaconda is to run this command: + +.. code-block:: sh + + conda install -c conda-forge pyspice + +**The following steps are not required for the conda-forge package.** + +As an alternative to conda, you can use the pip tool: ``pip install PySpice`` + +The easiest solution to install Ngspice on Windows is to use the PySpice tool to donwload and +install the Windows 64-bit DLL library for you: + +.. code-block:: sh + + pyspice-post-installation --install-ngspice-dll + + +Then check your installation using the *multi-platform* command: + +.. code-block:: sh + + pyspice-post-installation --check-install + + +On Linux +-------- + +You are free to install the packages of your Linux distribution. + +Ngspice Installation +~~~~~~~~~~~~~~~~~~~~ + +If you do not want to use Anaconda, Ngspice and its shared library are available on many distributions: + +* Fedora: **libngspice** https://apps.fedoraproject.org/packages/libngspice +* Ubuntu: **libngspice0** https://packages.ubuntu.com/search?suite=default§ion=all&arch=any&keywords=libngspice&searchon=names +* Debian: **libngspice0** https://packages.debian.org/search?keywords=libngspice0 + +.. warning:: However it is advisable to check how Ngspice is compiled, especially if the maintainer + has enabled experimental features ! + +For RPM distributions, such Fedora, RHEL and Centos, you can use this Copr repository +https://copr.fedorainfracloud.org/coprs/fabricesalvaire/ngspice + +.. code-block:: sh + + dnf copr enable fabricesalvaire/ngspice + dnf install libngspice + +.. The RPM sources are available on `Pagure.io `_ + +If you are not able to easily install the Ngspice shared library on your system, but you can install +the Ngspice program, then you can use the "subprocess" mode instead of the "shared" mode. In this +case, you must define the default simulator globally using the attribute +``PySpice.Spice.Simulation.CircuitSimulator.DEFAULT_SIMULATOR = 'ngspice-subprocess'``. + +Install the Python Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On Linux, you have several possibilities to install the required Python environment: + +#. if you are novice, the easiest solution is to install Anaconda or Miniconda +#. you can install Python packages from your distributions +#. if you are proficient with Python, you can manage a Virtual Environment for this purpose + +Then you can install PySpice using the `conda` command if you are using Anaconda otherwise run the +`pip` command: + +.. code-block:: sh + + conda install -c conda-forge pyspice + + # or + + pip install PySpice + + # and eventually + + pyspice-post-installation --check-install + +On OSX +------ + +There are several ways to get Python on OSX: + + * use the built in Python (but check the version) + * install `Miniconda `_ + * install a full `Anaconda Distribution `_. + * install from Brew: ``brew install python3`` + +To install PySpice, please read the Linux instructions. + +The Ngspice shared library is also available from Brew: + +.. code-block:: sh + + brew install libngspice + +Brew links: + + * https://formulae.brew.sh/formula/ngspice + https://github.com/Homebrew/homebrew-core/blob/master/Formula/ngspice.rb + * https://formulae.brew.sh/formula/libngspice + https://github.com/Homebrew/homebrew-core/blob/master/Formula/libngspice.rb + + +How to get the Examples +----------------------- + +Short answer, you can simply run this command: + +.. code-block:: sh + + pyspice-post-installation --download-example + +Long answer, the examples are not installed by ``pip`` or ``setup.py``. The installation process only install the +PySpice module on your Python environment. You have to download the PySpice archive or clone the +Git repository to get the examples. See "Installation from Source". + + +Install a more Recent Version from GitHub using pip +--------------------------------------------------- + +If you want to install a version which is not yet released on PyPI, you can use one of these +commands to install the stable or devel branch: + +.. code-block:: sh + + pip install git+https://github.com/FabriceSalvaire/PySpice + + pip install git+https://github.com/FabriceSalvaire/PySpice@devel + + +Installation from Source +------------------------ + +PySpice source code is hosted at |PySpice@github| + +You have two solutions to get the source code, the first is to clone the repository, but if you are +not familiar with Git, you can simply download an archive from the GitHub page (using the download +button) or from the PySpice PyPI page. + +To clone the Git repository, run this command: + +.. code-block:: sh + + git clone https://github.com/FabriceSalvaire/PySpice.git + +Then to build and install PySpice run these commands: + +.. code-block:: sh + + python setup.py build + python setup.py install + + +Tips to Set the Development Environment +--------------------------------------- + +**Set the PYTHONPATH on Windows:** + + * To set this variable from PowerShell, use: ``$env:PYTHONPATH=’list;of;paths’`` just before you launch Python. + * To set this variable from the Command Prompt, use: ``set PYTHONPATH=list;of;paths`` + +**Fix CP1252 / Unicode errors on Windows:** + +In some circumstance Windows uses the CP1252 encoding, to change this use: ``$env:PYTHONIOENCODING="utf_8"``. + +**Set PySpice logging level on Windows:** ``$env:PySpiceLogLevel="debug"`` + + +Dependencies +------------ + +PySpice dependencies are listed in: + +* https://github.com/FabriceSalvaire/PySpice/blob/master/requirements.txt +* https://github.com/FabriceSalvaire/PySpice/blob/master/requirements-dev.txt (for devel only) + + +How to Generate the Documentation +--------------------------------- + +To generate the documentation, you will need basically in addition theses Python modules (see +``requirements-dev.txt``): + +* `invoke `_ +* |Sphinx|_ +* `Pyterate `_ + +and also these dependencies (harder to install) to generate some figures: + +* `ImageMagick `_ (to convert SVG to PNG for Notebooks) +* `circuit_macros `_ and a LaTeX environment +* `mutool `_ (to convert circuit_macros PDF to PNG) + +Then the procedure is basically to run these commands: + +.. code-block:: sh + + inv doc.make-examples + inv doc.make-api + + +Ngspice Compilation +------------------- + +Usually Ngspice is available as a package on the most popular Linux distributions. But we recommend +to **check the compilation options** before using it for serious projects. + +The procedure to compile Ngspice is explained in the manual and in the ``INSTALLATION`` file. Ngspice is +an example of complex software where we should not enable everything without care. + +.. warning:: + + The compilation option **--enable-ndev** is known to break the server mode. + +The recommended way to compile Ngspice on Linux is: + +.. code-block:: sh + + mkdir ngspice-32-build + pushd ngspice-32-build + + /.../ngspice-32/configure \ + --prefix=/usr/local \ + --enable-xspice \ + --disable-debug \ + --enable-cider \ + --with-readline=yes \ + --enable-openmp \ + --with-ngshared + + make # -j4 + make install + +.. note:: PySpice source has invoke tasks to compile the Ngspice shared library on Unix, look at + *ngspice.* tasks using the command ``inv -l`` to list them. + + +How to get Xyce ? +----------------- + +Even though Xyce is released under the terms of the GPLv3 licence, Sandia actually requires that you create +a user account on this `sign-in page `_ to get the +source or download an executable, i.e. you have to provide an email address to Sandia. + +The building procedure is clearly explained in the `building guide +`_. + +You can also find the sources for a Xyce RPM package in this `Git repository +`_. diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/news.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/news.rst new file mode 100644 index 00000000..6e4bea93 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/news.rst @@ -0,0 +1,7 @@ +.. _news-page: + +====== + News +====== + +.. include:: news.txt diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/news.txt b/third_party/PySpice-org/PySpice/doc/sphinx/source/news.txt new file mode 100644 index 00000000..91325f57 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/news.txt @@ -0,0 +1,191 @@ +.. -*- Mode: rst -*- + +.. include:: abbreviation.txt +.. include:: project-links.txt + +.. no title here + +V1.6.0 (development release) +---------------------------- + +* **KiCadTools** a proof of concept module to read KiCad 6 + `.kicad_sch` schema file and compute the netlist. *This module can + be used to perform any kind of processings on a KiCad schema. It is + actually hosted in the source but could become a standalone + project.* For PySpice, it provides a very flexible way to draft a + circuit with the help of KiCad and then generate the netlist without + using the netlist export feature of KiCad. And thus leverage the + writing of fastidious cicruit. + +V1.5.0 (production release) 2021-05-15 +-------------------------------------- + +* Support Ngspice up to version 34 +* Renamed custom dunders "__dunder__" to "CONSTANT" or "_private" class attributes +* Fixed typo in documentation (thanks to endolith and brollb) +* Add DC temperature sweep support #272 (thanks to Fatsie) +* PWL support improvements #271 (thanks to Fatsie) +* Assign units on creation of temperature-sweep vectors #263 (thanks to ARF1) +* Prevent memory leaks by freeing ngspice command log #260 thanks to ARF1) +* Performance optimization: dispatch multiple alter commands jointly #259 (thanks to ARF1) +* Added spice library support #258 (thanks to Fatsie) +* Allow to specify DC value for PWL #257 (thanks to Fatsie) +* Support for `.nodeset` type initial condition #256 (thanks to Fatsie) +* Fix accuracy problems #254 (thanks to sotw1957) +* Changes to make it easier to use PySpice with a large archive of SPICE models medium diff #249 (thanks to xesscorp) +* `Netlist.py`: Fix wrong method when joining parameters during netlist parse #245 (thanks to cyber-g) +* Unit: add Pickle support +* Add Parser code from #136 (thanks to jmgc) but not yet merged +* Unit: add np.mean + +V1.4.3 2020-07-04 +----------------- + +A huge effort, thanks to @stuarteberg Stuart Berg, has been made to make Ngspice and PySpice +available on Anaconda (conda-forge) for the Window, OSX and Linux platforms. Thanks to the +conda-forge continuous integration platform, we can now run unit tests and the examples on theses +platforms automatically. Hope this will make the software more robust and easier to run ! + +* PySpice is now available on Anaconda(conda-forge) as well as a wheel on PyPI +* Added a post installation tool to download the Ngspice DLL on Windows and to check the installation. + It should now simplify considerably the PySpice installation on Windows. +* This tool can also download the examples and the Ngspice PDF manual. +* On Linux and OSX, a Ngspice package is now available on Anaconda(conda-forge). + Note that theses two platforms do not download a binary from Ngspice since a compiler can easily be installed on theses platforms. +* Updated installation documentation for Linux, the main distributions now provide a ngspice shared package. + +* Added a front-end web site so as to keep older releases documentation available on the web. +* fixed and rebuilt all examples (but mistakes could happen ...) +* examples are now available as Python files and Jupyter notebooks + (but some issues must be fixed, e.g. due to the way Jupyter handles Matplotlib plots) + +* support NgSpice 32 API (no change) +* removed @substitution@ in PySpice/__init__.py, beacause it breaks pip install from git +* fixed some logging spams +* fixed NonLinearVoltageSource +* fixed Unicode issue with °C (° is Extended ASCII) +* fixed ffi_string_utf8 for UnicodeDecodeError +* fixed logging formater for OSX (removed ANSI codes) +* reworded "Invalid plot name" exception +* removed diacritics in example filenames +* cir2py has been converted to an entry point so as to work on all platforms +* updated Matplotlib subplots in examples +* added a unit example +* added a NMOS example (thanks to cyber-g) cf. #221 + +V1.4.0 2020-05-05 +----------------- + +This release is yanked due to broken Windows support. + +* fixed nasty issue with NgSpice shared for `setlocale(LC_NUMERIC, "C");` cf. #172 +* fixed `AC AC_MAG AC_PASAE SIN` for new NgSpice syntax +* fixed `initial_state` for `VoltageControlledSwitch` +* fixed `LosslessTransmissionLine` #169 +* fixed docstrings for element shortcut methods (thanks to Kyle Dunn) #178 +* fixed parser for leading whitespace (thanks to Matt Huszagh) #182 +* fix for PyYAML newer API +* support NgSpice 31 API (no change) +* added check for `CoupledInductor` #157 +* added `check-installation` tool to help to fix broken installation +* added pole-zero, noise, distorsion, transfer-function analyses (thanks to Peter Garrone) #191 +* added `.measure` support (thanks to ceprio) #160 +* added `log_desk` parameter to `CircuitSimulator` +* added `listing` command method to `NgSpiceShared` +* added Xyce Mosfet nfin #177 + +V1.3.2 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 diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/on-the-web.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/on-the-web.rst new file mode 100644 index 00000000..0c1dd77f --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/on-the-web.rst @@ -0,0 +1,24 @@ +.. _on-the-web-page: + +==================== + PySpice on the Web +==================== + +*Don't hesitate to contact me to aggregate web content about PySpice.* + +Articles +-------- + +You will find here a list of links to some articles talking about PySpice: + + * http://www.tazabekov.com/blog/2017/06/digital-to-analog-converter-modelling-with-pyspice + * http://www.tazabekov.com/blog/2017/07/analog-to-digital-converter-modelling-with-pyspice + +On Github +--------- + +You will find here a list of links to git repositories containing PySpice examples: + + * `https://github.com/OlzhasT/PySpiceCircuits- `_ + cf. http://www.tazabekov.com/blog/2017/06/digital-to-analog-converter-modelling-with-pyspice + * https://github.com/danielvilas/PySpice-Library diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/overview.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/overview.rst new file mode 100644 index 00000000..6e3debcb --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/overview.rst @@ -0,0 +1,204 @@ +.. include:: abbreviation.txt +.. include:: project-links.txt + +.. _overview-page: + +========== + Overview +========== + +What is PySpice ? +----------------- + +PySpice is an open source Python module which provides a |Python|_ interface to the |Ngspice|_ and +|Xyce|_ circuit simulators. + +|Ngspice|_ is a fork of the famous `SPICE `_ circuit simulator, +while |Xyce|_ is a SPICE compatible simulator developed by the `Sandia National Laboratories +`_. + +It permits: + + * definition of a circuit, through a netlist, + * simulation using |Ngspice|_ and |Xyce|_, + * analysis of the output using |Numpy|_ and |Matplotlib|_. + +How is PySpice licensed ? +------------------------- + +PySpice is licensed under the `GPLv3 `_. + +Are there some papers or talks about PySpice ? +---------------------------------------------- + +You can watch `Circuit Simulation using Python +`_ +given at the `PyParis 2017 `_ conference (`PDF file +`_) + +Going further with PySpice +-------------------------- + +The best way to know what you can do with PySpice, and to learn it, is to look at the examples: + + * :ref:`Examples ` + * :ref:`PySpice Reference Manual ` + * :ref:`Bibliography ` + +How can PySpice be used for learning ? +-------------------------------------- + + * PySpice comes with many examples covering several topics. + +.. * PySpice features a documentation generator which generates HTML or PDF documentation +.. +.. | cf. supra for the documentation generator features + +Which platforms are supported by PySpice ? +------------------------------------------ + +PySpice runs on Linux, Windows 64-bit and Mac OS X. + +How to install PySpice ? +------------------------ + +The procedure to install PySpice is described in the :ref:`Installation Manual `. + +How does PySpice differ from simulator like LTspice ? +----------------------------------------------------- + + * PySpice, Ngspice and Xyce are `Free Software `_ and thus open source, + * PySpice doesn't feature a schematic editor (*) or GUI, but, + * It has the power of Python for data analysis, + * And thus provide modern data analysis tools. + * Moreover PySpice is feature unlocked due to its open design. + +(*) However you can export netlist from Kicad to PySpice. + +How can a non-GUI simulator be helpful ? +---------------------------------------- + +It is often not easy to write a netlist, and a tool like a schematic editor can help to +visualise the circuit. Tools like Circuit_macros and Tikz are complex and +need some practices. However, the learning curve is no worse than for a musical instrument. + +Another question is to discuss the possibility to simulate a real design, i.e. to integrate the +simulation in the Electronic Design Automation (EDA) process, from the schematic to the PCB. Often, it does not make +sense to simulate a real design, and we only simulate parts or models of a design to ensure the +real design is right. + +In fact each tool has advantages and drawbacks which are often orthogonal. + +Having discussed the main drawbacks, we will now look at the advantages: + + * Since it is code, you can describe completely your simulation project. There are no actions that + require to use a mouse to interact with the GUI. + * It can be easily versioned using a tool like Git. + * If you work with an editor and a console in parallel, then you can easily and quickly change + things, and rerun the simulation, e.g. comment out a diode or a capacitor to see what happen. Using a + GUI, this task would require many actions. + * Thanks to the tool |Pyterate|_, you can enrich your simulation with text, + formulae and figures. + +Is is possible to use both approaches all together ? The technical answer is, 'yes we +can'. For example the Modelica language uses a concept of annotations to describe the schema. A +schematic editor like Kicad could be updated to interact closely with PySpice. + +What are the benefits of PySpice over Ngspice / Xyce ? +------------------------------------------------------ + + * You can steer your netlist and simulation using Python. + + | Which supersede Spice *parameters* and *expressions*. + | Which make Monte Carlo simulation easier for example. + + * You can analyse the output using scientific packages for Python. + + | Which supersede tools like TclSpice. + +How is PySpice interfaced with Ngspice ? +---------------------------------------- + + * PySpice can parse a Spice netlist and generate the equivalent Python code, or instantiate it directly. + * PySpice can generate a Spice netlist. + * PySpice can send a simulation to Ngspice and read back the output using either the *server* or *shared* mode. + + | By default, PySpice uses the server mode. Shared mode is only required when you need advanced features. + +When using *shared* mode + + * PySpice permits defining the external voltage and current source in Python (or even in C). + * PySpice permits getting and sending data to the simulator during the simulation process. + * |CFFI|_ is used to interface C to Python. + +How is PySpice interfaced with Xyce ? +------------------------------------- + +Actually, PySpice run Xyce as a subprocess and read the raw output. + +How is the netlist defined ? +---------------------------- + + * The netlist is defined using an object-oriented API + * PySpice can also work with Spice netlist and import netlist from a schematic editor like |Kicad|_. + +Can I run Ngspice using interpreter commands ? +---------------------------------------------- + +Thanks to the Ngspice shared library binding, you are not tied to the object-oriented API of +PySpice. You can run Ngspice as you did before and just upload the simulation output as Numpy +arrays. For an example, look at the Ngspice shared examples. + +How are Spice libraries handled ? +--------------------------------- + + * PySpice features a library manager that scan a path for library files. + * Libraries can be included as is using the *include* directive. + * Subcircuits can be defined as Python classes. + +How are units handled ? +----------------------- + + * PySpice features a unit module that support the SI Units. + * Value units can be defined using function shortcuts or a special syntax: e.g. :code:`kilo(1.2)`, :code:`1.2@u_kV`, :code:`1.2@u_mΩ`. + +Which version of Python is required ? +------------------------------------- + +PySpice requires Python 3 and the version 3.5 is recommended so as to benefit from the new *@* syntax +for units. + +Which version of Ngspice is required ? +-------------------------------------- + +You should use the latest version of Ngspice and take care it was compiled according to the Ngspice +manual, i.e. you should check somebody didn't enable experimental features which could break +PySpice, generate a wrong simulation, or produce bugs. + +*Note that Ngspice is not distributed with PySpice !* + +Which version of Xyce is required ? +----------------------------------- + +You should use the latest version provided by Sandia. + +Which flavours of SPICE are supported ? +--------------------------------------- + +Up to now PySpice only support Ngspice and Xyce. + +But PySpice could support easily any simulator providing an API similar to Ngspice. + +What else should you be aware of ? +---------------------------------- + +Users should be aware of these disclaimers: + + * Ngspice and PySpice are provided without any warranty. + + | Thus you must use it with care for real design. + | Best is to cross-check the simulation using an industrial grade simulator. + + * Ngspice is not compliant with industrial quality assurance processes. + + * Simulation is a design tool and not a perfect description of the real world. diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/project-links.txt b/third_party/PySpice-org/PySpice/doc/sphinx/source/project-links.txt new file mode 100644 index 00000000..dabdd63f --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/project-links.txt @@ -0,0 +1,43 @@ +.. -*- Mode: rst -*- + +.. |PySpiceUrl| replace:: https://pyspice.fabrice-salvaire.fr + +.. |PySpiceHomePage| replace:: PySpice Home Page +.. _PySpiceHomePage: https://pyspice.fabrice-salvaire.fr + + +.. |PySpice@github| replace:: https://github.com/FabriceSalvaire/PySpice + + +.. |PySpice@pypi| replace:: https://pypi.python.org/pypi/PySpice + + +.. |PySpice@anaconda| replace:: https://anaconda.org/conda-forge/pyspice + +.. |PySpice@fs-anaconda| replace:: https://anaconda.org/fabricesalvaire/pyspice + +.. |Anaconda Version| image:: https://anaconda.org/conda-forge/pyspice/badges/version.svg + :target: https://anaconda.org/conda-forge/pyspice/badges/version.svg + :alt: Anaconda last version + +.. |Anaconda Downloads| image:: https://anaconda.org/conda-forge/pyspice/badges/downloads.svg + :target: https://anaconda.org/conda-forge/pyspice/badges/downloads.svg + :alt: Anaconda donwloads + + +.. |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 + + +.. |Tavis CI master| image:: https://travis-ci.com/FabriceSalvaire/PySpice.svg?branch=master + :target: https://travis-ci.com/FabriceSalvaire/PySpice + :alt: PySpice build status @travis-ci.org diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/reference-manual.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/reference-manual.rst new file mode 100644 index 00000000..c7853c85 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/reference-manual.rst @@ -0,0 +1,31 @@ +.. include:: abbreviation.txt + +.. _reference-manual-page: + +=================== + API Documentation +=================== + +This is the API documentation for the PySpice library. + +.. warning:: The API documentation is automatically generated from the docstrings in the source + using the |Sphinx| tool. This way to produce the documentation is know to be perfectible + actually, but not too bad. + +Contents: + +.. toctree:: + :maxdepth: 2 + + api/PySpice + +Indexes +------- + + * :ref:`genindex` + * :ref:`modindex` + +.. * :ref:`search` + +.. End + diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/related-projects.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/related-projects.rst new file mode 100644 index 00000000..125fcdab --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/related-projects.rst @@ -0,0 +1,62 @@ +.. _related-projects-page: + +================== + Related Projects +================== + +Schematic Editor +================ + +`KiCad `_ is an open source software suite for Electronic Design Automation +(EDA). The programs handle Schematic Capture, and PCB Layout with Gerber output. The suite runs on +Windows, Linux and macOS and is licensed under GNU GPL v3. + +Netlist Tools +============= + +SKIDL +----- + +`SKiDL `_ is a module that allows you to +compactly describe the interconnection of electronic circuits and components using Python. The +resulting Python program performs electrical rules checking for common mistakes and outputs a +netlist that serves as input to a PCB layout tool (e.g. Kicad). + +SKiDL can generate a circuit using the PySpice API, see this full example +https://github.com/xesscorp/skidl/blob/master/examples/spice-sim-intro/spice-sim-intro.ipynb + +Circuit Simulator +================= + +Ahkab +----- + +`Ahkab `_ is a SPICE-like electronic circuit simulator written in Python. + +Lcapy +----- + +`Lcapy `_ is a Python package for linear circuit analysis. Lcapy +uses `SymPy `_ for symbolic analysis. Lcapy can semi-automate the drawing of +schematics from a netlist using Circuitikz. Lcapy sources are available at +https://github.com/mph-/lcapy + +Other Projects +-------------- + + * `eispice `_ + * `SPICE OPUS `_ and `PyOPUS `_ + * `A Python interface for SPICE-based simulations `_ + +CPU Simulator +============= + +PyCpuSimulator +-------------- + +`PyCpuSimulator `_ is CPU simulator written in Python featuring: + +* Micro-Code Language to describe instruction +* Opcode Decoder using Decision Tree +* Read HEX firmware format +* AVR Core CPU simulation (not completed) diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/roadmap.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/roadmap.rst new file mode 100644 index 00000000..7ab735d7 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/roadmap.rst @@ -0,0 +1,46 @@ +.. _roadmap-page: + +.. include:: abbreviation.txt +.. include:: project-links.txt + +========= + Roadmap +========= + +`Roadmap @Github `_ + +V1.3 +---- + +`V1.3 Roadmap @Github `_ + +* to be defined + +V2 +-- + +`V2 Roadmap @Github `_ + +* `Implement missing analyses `_ +* `Implement missing devices `_ + +.. What are the planned features ? + +Some ideas for the future +------------------------- + +These features are planned in the future: + +* Improve the analyse experience +* Complete Spice expression parser +* Complete missing devices and simulations +* Jupyter notebook examples + +Some other ideas are: + +* Implement a Modelica backend. |Modelica|_ is a very interesting solution for transient analysis. + +The implementation of a simulator in Python is not planned since it would be too challenging to +release a full featured and proved simulator. However you could look at the project `Ahkab +`_ which aims to implement such Python based simulator. Notice any +of the projects like Ngspice or Ahkab are compliant with industrial quality assurance processes. diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/simulators.rst b/third_party/PySpice-org/PySpice/doc/sphinx/source/simulators.rst new file mode 100644 index 00000000..c40a69e5 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/simulators.rst @@ -0,0 +1,76 @@ +.. _simulators-page: + +.. include:: abbreviation.txt + +============ + Simulators +============ + +This page presents the main open source circuit simulators which are actually available. + +You can get an overview of what is going on in FOSS circuit simulation in the `FOSDEM'17 Electronic +Design Automation Track +`_ + +Ngspice +------- + +|Ngspice|_ is a mixed-level/mixed-signal circuit simulator, based on three open source software +packages: Spice3f5, Cider1b1 and Xspice. + +*Spice3f5* is the last Berkeley’s release of Spice3 simulator family. + +*Cider* couples Spice3f5 circuit level simulator to DSIM device simulator to provide greater +simulation accuracy of critical devices. DSIM devices are described in terms of their structures and +materials. + +*Xspice* is an extension to Ngspice that provides code modeling support and simulation of digital +components through an embedded event driven algorithm. + +.. is an update of + It includes some extension like XSPICE and CIDER. XSPICE allows to add event-driven simulation + capabilities, and CIDER implements a mixed-level circuit and device simulator. + +The main drawback of Ngspice is to inherit the old C code base of Berkeley SPICE. + +To read more on Ngspice, look at the `Ngspice documentation page `_. + +Xyce +---- + +|Xyce|_ is an open source, SPICE-compatible, high-performance analog circuit simulator, capable of +solving extremely large circuit problems developed at `Sandia National Laboratories +`_. + +Sandia is a US laboratory involved in the national nuclear weapons program. Its French counterpart +is the Alternative Energies and Atomic Energy Commission or CEA. + +Sandia has released several open source programs, you can find a list on its `Wikipedia page +`_. + +Xyce was developed from scratch at Sandia in order to meet special needs like the simulation of +chips under radiations. Xyce is developed in C++. + +Gnucap +------ + +The initial version of Gnucap was developed by Albert Davis in 1983, then it was released as a GNU project in 2001. + +Like Xyce, Gnucap was developed from scratch in C++. + +In 2015, a fork `Gnucap-uf `_ targeted at research +and experiments was released by the University of Frankfurt in Germany. + +Actually Felix Salfelder is performing an unforking and adding new extensions. Look at its `talk +`_ at FOSDEM'17 for further information. + +Qucs +---- + +`QUCS `_ (Quite Universal Circuit Simulator) was an attempt to develop +a circuit simulator featuring an IDE similar to LTspice by Michael Margraf, Stefan Jahn et al. + +QUCS is actually slowly maintained, a Qt4 port from Qt3 is on going since several years. + +You can find an update of the project on the `Guilherme Brondani Torri Talk +`_ at FOSDEM'17. diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/layout.html b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/layout.html new file mode 100644 index 00000000..2a4db767 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/layout.html @@ -0,0 +1,55 @@ +{# -*- engine: jinja2 -*- #} + +{%- extends "sphinx_rtd_theme/layout.html" %} + +{%- block extrahead %} + + + +{% endblock %} + +{%- block footer %} + + +{% endblock %} + +{% block sidebartitle %} + + {{ project }} + + + + {#
#} + + PySpice {{ version }} + + + {% include "searchbox.html" %} +{% endblock %} diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/PySpice.css b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/PySpice.css new file mode 100644 index 00000000..b2a6878d --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/PySpice.css @@ -0,0 +1,119 @@ +/* -*- Mode: css -*- */ + +/* ----------------------------------------------------------------------------------------------- * + * + * PySpice stylesheet + * + * :copyright: Copyright 2017 Fabrice Salvaire + * + * This file is part of the PySpice software. + * + */ + +@import url("theme.css"); + +/* ----------------------------------------------------------------------------------------------- * + * + * Overwrite RTD Theme + * + */ + +.wy-nav-content { + padding: 1.618em 3.236em; + height: 100%; + max-width: 100%; + margin: auto; +} + +p { + font-size: 100%; /* 16 px */ +} + +div[class^="highlight"] { + border: 0; + box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12); +} + +div[class^="highlight"] pre { + font-size: 100%; /* 16 px */ +} + +div.highlight-pytb { + background: #ffcfcf; + border-left-width: 10px; + border-left-color: #fc4545; + border-left-style: solid; +} + +div.highlight-none { + background: #d2ffcf; + border-left-width: 10px; + border-left-color: #1ada52; + border-left-style: solid; +} + +a.btn.btn-neutral { + box-shadow: none; + border: 0; + border-radius: 0; + background: #4caf50 !important; + color: white !important; +} + +a.btn.btn-neutral:hover { + background: #6ec071 !important; + color: white !important; +} + +/* ----------------------------------------------------------------------------------------------- */ + +dl.class, dl.function { + border-top: 1px solid black; + padding-top: 5px; +} + +/* ----------------------------------------------------------------------------------------------- * + * + * GetTheCode + * + */ + +div.getthecode { + margin-bottom: 1em; +} + +div.getthecode-header { + padding: 5px; + border-radius: 4px; + background: #2980b9; + color: white; + /* background: #52cc79; */ + /* background: #4fa6e6; */ + /* color: black; */ +} + +div.getthecode-header a { + /* color: black; */ + color: white; +} + +.rst-content div.getthecode-header ul { + margin: 0; /* Reset */ + list-style-type: none; +} +.rst-content div.getthecode-header ul li { + margin: 0; /* Reset */ + margin-left: 1em; + display: inline; +} + +.rst-content div.getthecode-header li.show-code-button { + cursor: pointer; + float: right; + margin-right: 1em; +} + +div.getthecode pre { + margin-top: 0px; + padding: 12px; +} diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/theme.css b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/theme.css new file mode 100644 index 00000000..c1631d84 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/css/theme.css @@ -0,0 +1,5 @@ +*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,.rst-content code,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,.rst-content .toctree-wrapper p.caption,h3{orphans:3;widows:3}h2,.rst-content .toctree-wrapper p.caption,h3{page-break-after:avoid}}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.6.3");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff2?v=4.6.3") format("woff2"),url("../fonts/fontawesome-webfont.woff?v=4.6.3") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.6.3") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.wy-menu-vertical li span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.rst-content .fa-pull-left.admonition-title,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content dl dt .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.rst-content code.download span.fa-pull-left:first-child,.fa-pull-left.icon{margin-right:.3em}.fa.fa-pull-right,.wy-menu-vertical li span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.rst-content .fa-pull-right.admonition-title,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content dl dt .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.rst-content code.download span.fa-pull-right:first-child,.fa-pull-right.icon{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.wy-menu-vertical li span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.rst-content code.download span.pull-left:first-child,.pull-left.icon{margin-right:.3em}.fa.pull-right,.wy-menu-vertical li span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.rst-content code.download span.pull-right:first-child,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-remove:before,.fa-close:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-hotel:before,.fa-bed:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-yc:before,.fa-y-combinator:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-tv:before,.fa-television:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:""}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-signing:before,.fa-sign-language:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .rst-content p.caption .headerlink,.rst-content p.caption a .headerlink,a .rst-content tt.download span:first-child,.rst-content tt.download a span:first-child,a .rst-content code.download span:first-child,.rst-content code.download a span:first-child,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .btn span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.btn .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .rst-content p.caption .headerlink,.rst-content p.caption .btn .headerlink,.btn .rst-content tt.download span:first-child,.rst-content tt.download .btn span:first-child,.btn .rst-content code.download span:first-child,.rst-content code.download .btn span:first-child,.btn .icon,.nav .fa,.nav .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand,.nav .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .rst-content p.caption .headerlink,.rst-content p.caption .nav .headerlink,.nav .rst-content tt.download span:first-child,.rst-content tt.download .nav span:first-child,.nav .rst-content code.download span:first-child,.rst-content code.download .nav span:first-child,.nav .icon{display:inline}.btn .fa.fa-large,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .btn span.fa-large:first-child,.btn .rst-content code.download span.fa-large:first-child,.rst-content code.download .btn span.fa-large:first-child,.btn .fa-large.icon,.nav .fa.fa-large,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.nav .rst-content code.download span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .btn span.fa-spin:first-child,.btn .rst-content code.download span.fa-spin:first-child,.rst-content code.download .btn span.fa-spin:first-child,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.nav .rst-content code.download span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.wy-menu-vertical li span.btn.toctree-expand:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.rst-content code.download span.btn:first-child:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.rst-content code.download span.btn:first-child:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.rst-content tt.download .btn-mini span:first-child:before,.btn-mini .rst-content code.download span:first-child:before,.rst-content code.download .btn-mini span:first-child:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 .3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:.34375em .625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type="radio"][disabled],input[type="checkbox"][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{width:36px;height:12px;margin:12px 0;position:relative;border-radius:4px;background:#ccc;cursor:pointer;-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.wy-switch:before{position:absolute;content:"";display:block;width:18px;height:18px;border-radius:4px;background:#999;left:-3px;top:-3px;-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.wy-switch:after{content:"false";position:absolute;left:48px;display:block;font-size:12px;color:#ccc}.wy-switch.active{background:#1e8449}.wy-switch.active:before{left:24px;background:#27AE60}.wy-switch.active:after{content:"true"}.wy-switch.disabled,.wy-switch.active.disabled{cursor:not-allowed}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,.rst-content .toctree-wrapper p.caption,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2,.rst-content .toctree-wrapper p.caption{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt,.rst-content code{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9B59B6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#EAF2F5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs li code,.wy-breadcrumbs li .rst-content tt,.rst-content .wy-breadcrumbs li tt{padding:5px;border:none;background:none}.wy-breadcrumbs li code.literal,.wy-breadcrumbs li .rst-content tt.literal,.rst-content .wy-breadcrumbs li tt.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;margin-bottom:0;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#555;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li code,.wy-menu-vertical li .rst-content tt,.rst-content .wy-menu-vertical li tt{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:0.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.on a:hover span.toctree-expand,.wy-menu-vertical li.current>a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand{display:block;font-size:0.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current li.toctree-l2>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>ul{display:none}.wy-menu-vertical li.toctree-l1.current li.toctree-l2.current>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>ul{display:block}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{display:block;background:#c9c9c9;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3{font-size:0.9em}.wy-menu-vertical li.toctree-l3.current>a{background:#bdbdbd;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{display:block;background:#bdbdbd;padding:.4045em 5.663em;border-top:none;border-bottom:none}.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.toctree-l4{font-size:0.9em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#b3b3b3}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980B9;text-align:center;padding:.809em;display:block;color:#fcfcfc;margin-bottom:.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-side-nav-search>a img.logo,.wy-side-nav-search .wy-dropdown>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search>a.icon img.logo,.wy-side-nav-search .wy-dropdown>a.icon img.logo{margin-top:0.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:normal;color:rgba(255,255,255,0.3)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}footer span.commit code,footer span.commit .rst-content tt,.rst-content footer span.commit tt{padding:0px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:1em;background:none;border:none;color:#999}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:before,.rst-breadcrumbs-buttons:after{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-side-scroll{width:auto}.wy-side-nav-search{width:auto}.wy-menu.wy-menu-vertical{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto !important}.rst-content .highlight>pre{line-height:normal}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content .toctree-wrapper p.caption .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content .toctree-wrapper p.caption .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content p.caption .headerlink:after{visibility:visible;content:"";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content .toctree-wrapper p.caption:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink,.rst-content p.caption:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.docutils.citation tt,.rst-content table.docutils.citation code,.rst-content table.docutils.footnote tt,.rst-content table.docutils.footnote code{color:#555}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt,.rst-content tt,.rst-content code{color:#000;padding:2px 5px}.rst-content tt big,.rst-content tt em,.rst-content tt big,.rst-content code big,.rst-content tt em,.rst-content code em{font-size:100% !important;line-height:normal}.rst-content tt.literal,.rst-content tt.literal,.rst-content code.literal{color:#E74C3C}.rst-content tt.xref,a .rst-content tt,.rst-content tt.xref,.rst-content code.xref,a .rst-content tt,a .rst-content code{font-weight:bold;color:#404040}.rst-content a tt,.rst-content a tt,.rst-content a code{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:#555}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) code{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) code.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}.rst-content tt.download,.rst-content code.download{background:inherit;padding:inherit;font-weight:normal;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content tt.download span:first-child,.rst-content code.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}@font-face{font-family:"Inconsolata";font-style:normal;font-weight:400;src:local("Inconsolata"),local("Inconsolata-Regular"),url(../fonts/Inconsolata-Regular.ttf) format("truetype")}@font-face{font-family:"Inconsolata";font-style:normal;font-weight:700;src:local("Inconsolata Bold"),local("Inconsolata-Bold"),url(../fonts/Inconsolata-Bold.ttf) format("truetype")}@font-face{font-family:"Lato";font-style:normal;font-weight:400;src:local("Lato Regular"),local("Lato-Regular"),url(../fonts/Lato-Regular.ttf) format("truetype")}@font-face{font-family:"Lato";font-style:normal;font-weight:700;src:local("Lato Bold"),local("Lato-Bold"),url(../fonts/Lato-Bold.ttf) format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:400;src:local("Roboto Slab Regular"),local("RobotoSlab-Regular"),url(../fonts/RobotoSlab-Regular.ttf) format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:700;src:local("Roboto Slab Bold"),local("RobotoSlab-Bold"),url(../fonts/RobotoSlab-Bold.ttf) format("truetype")} +/*# sourceMappingURL=theme.css.map */ diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/getthecode.js b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/getthecode.js new file mode 100644 index 00000000..76364e23 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/getthecode.js @@ -0,0 +1,27 @@ +// https://github.com/FabriceSalvaire/sphinx-getthecode +// Licensed under GPLv3 +// Require http://fontawesome.io +$(document).ready(function() { + // Add a button on the top-right corner of getthecode div to show the code + $('.getthecode-header').each(function(index) { + var div = $(this); + var button = $('
  • '); + button.attr('title', 'Show/Hide the code'); + div.find('ul:first').append(button); + div_highlight = div.parent().find('.highlight').parent(); + if (div_highlight.hasClass('highlight-hidden')) { + button.find('.fa-eye-slash').toggle(); + div_highlight.toggle(); + } else { + button.find('.fa-eye').toggle(); + } + }); + + // define the behavior of the button when it's clicked + $('.show-code-button').click(function() { + var button = $(this); + button.parents().eq(3).find('.highlight').parent().toggle(); + button.find('.fa-eye').toggle(); + button.find('.fa-eye-slash').toggle(); + }); +}); diff --git a/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/jquery.min.js b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/jquery.min.js new file mode 100644 index 00000000..fad9ab12 --- /dev/null +++ b/third_party/PySpice-org/PySpice/doc/sphinx/source/themes/PySpiceRtd/static/jquery.min.js @@ -0,0 +1,5 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("