From 5c2050461e4fc5a443cbe4bb2ed37aa9a8943290 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Wed, 21 Jan 2026 20:11:43 -0800 Subject: [PATCH 01/10] update readme, update packaging --- .gitchangelog.rc | 296 ----------------------------------------------- README.md | 7 -- pyproject.toml | 19 ++- 3 files changed, 18 insertions(+), 304 deletions(-) delete mode 100644 .gitchangelog.rc diff --git a/.gitchangelog.rc b/.gitchangelog.rc deleted file mode 100644 index 882a96d5..00000000 --- a/.gitchangelog.rc +++ /dev/null @@ -1,296 +0,0 @@ -# -*- coding: utf-8; mode: python -*- -## -## Format -## -## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] -## -## Description -## -## ACTION is one of 'chg', 'fix', 'new' -## -## Is WHAT the change is about. -## -## 'chg' is for refactor, small improvement, cosmetic changes... -## 'fix' is for bug fixes -## 'new' is for new features, big improvement -## -## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' -## -## Is WHO is concerned by the change. -## -## 'dev' is for developpers (API changes, refactors...) -## 'usr' is for final users (UI changes) -## 'pkg' is for packagers (packaging changes) -## 'test' is for testers (test only related changes) -## 'doc' is for doc guys (doc only changes) -## -## COMMIT_MSG is ... well ... the commit message itself. -## -## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' -## -## They are preceded with a '!' or a '@' (prefer the former, as the -## latter is wrongly interpreted in github.) Commonly used tags are: -## -## 'refactor' is obviously for refactoring code only -## 'minor' is for a very meaningless change (a typo, adding a comment) -## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) -## 'wip' is for partial functionality but complete subfunctionality. -## -## Example: -## -## new: usr: support of bazaar implemented -## chg: re-indentend some lines !cosmetic -## new: dev: updated code to be compatible with last version of killer lib. -## fix: pkg: updated year of licence coverage. -## new: test: added a bunch of test around user usability of feature X. -## fix: typo in spelling my name in comment. !minor -## -## Please note that multi-line commit message are supported, and only the -## first line will be considered as the "summary" of the commit message. So -## tags, and other rules only applies to the summary. The body of the commit -## message will be displayed in the changelog without reformatting. -def writefile(lines): - # Develop Full String of Log - log = '' - for line in lines: - log += line - import re - log = re.sub(r'\[\w{1,20}\n? {0,8}\w{1,20}\]','', log) - print(log) - with open('source/changelog.rst','w') as changelog: - changelog.write(log) - print("Update CHANGELOG.rst Complete.") - -## -## ``ignore_regexps`` is a line of regexps -## -## Any commit having its full commit message matching any regexp listed here -## will be ignored and won't be reported in the changelog. -## -ignore_regexps = [ - r'\(private\)', - r'@minor', r'!minor', - r'@cosmetic', r'!cosmetic', - r'@refactor', r'!refactor', - r'@wip', r'!wip', - r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', - r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', - r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', - r'^$', ## ignore commits with empty messages -] - - -## ``section_regexps`` is a list of 2-tuples associating a string label and a -## list of regexp -## -## Commit messages will be classified in sections thanks to this. Section -## titles are the label, and a commit is classified under this section if any -## of the regexps associated is matching. -## -## Please note that ``section_regexps`` will only classify commits and won't -## make any changes to the contents. So you'll probably want to go check -## ``subject_process`` (or ``body_process``) to do some changes to the subject, -## whenever you are tweaking this variable. -## -section_regexps = [ - ('New', [ - r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', - ]), - ('Changes', [ - r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', - ]), - ('Fix', [ - r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', - ]), - - ('Other', None ## Match all lines - ), - -] - - -## ``body_process`` is a callable -## -## This callable will be given the original body and result will -## be used in the changelog. -## -## Available constructs are: -## -## - any python callable that take one txt argument and return txt argument. -## -## - ReSub(pattern, replacement): will apply regexp substitution. -## -## - Indent(chars=" "): will indent the text with the prefix -## Please remember that template engines gets also to modify the text and -## will usually indent themselves the text if needed. -## -## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns -## -## - noop: do nothing -## -## - ucfirst: ensure the first letter is uppercase. -## (usually used in the ``subject_process`` pipeline) -## -## - final_dot: ensure text finishes with a dot -## (usually used in the ``subject_process`` pipeline) -## -## - strip: remove any spaces before or after the content of the string -## -## - SetIfEmpty(msg="No commit message."): will set the text to -## whatever given ``msg`` if the current text is empty. -## -## Additionally, you can `pipe` the provided filters, for instance: -#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") -#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') -#body_process = noop -body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip - - -## ``subject_process`` is a callable -## -## This callable will be given the original subject and result will -## be used in the changelog. -## -## Available constructs are those listed in ``body_process`` doc. -subject_process = (strip | - ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | - SetIfEmpty("No commit message.") | ucfirst | final_dot) - - -## ``tag_filter_regexp`` is a regexp -## -## Tags that will be used for the changelog must match this regexp. -## -tag_filter_regexp = r'^[0-9]+(\.[0-9]+)?$' - - -## ``unreleased_version_label`` is a string or a callable that outputs a string -## -## This label will be used as the changelog Title of the last set of changes -## between last valid tag and HEAD if any. -unreleased_version_label = "(unreleased)" - - -## ``output_engine`` is a callable -## -## This will change the output format of the generated changelog file -## -## Available choices are: -## -## - rest_py -## -## Legacy pure python engine, outputs ReSTructured text. -## This is the default. -## -## - mustache() -## -## Template name could be any of the available templates in -## ``templates/mustache/*.tpl``. -## Requires python package ``pystache``. -## Examples: -## - mustache("markdown") -## - mustache("restructuredtext") -## -## - makotemplate() -## -## Template name could be any of the available templates in -## ``templates/mako/*.tpl``. -## Requires python package ``mako``. -## Examples: -## - makotemplate("restructuredtext") -## -output_engine = rest_py -#output_engine = mustache("restructuredtext") -#output_engine = mustache("markdown") -#output_engine = makotemplate("restructuredtext") - - -## ``include_merge`` is a boolean -## -## This option tells git-log whether to include merge commits in the log. -## The default is to include them. -include_merge = True - - -## ``log_encoding`` is a string identifier -## -## This option tells gitchangelog what encoding is outputed by ``git log``. -## The default is to be clever about it: it checks ``git config`` for -## ``i18n.logOutputEncoding``, and if not found will default to git's own -## default: ``utf-8``. -#log_encoding = 'utf-8' - - -## ``publish`` is a callable -## -## Sets what ``gitchangelog`` should do with the output generated by -## the output engine. ``publish`` is a callable taking one argument -## that is an interator on lines from the output engine. -## -## Some helper callable are provided: -## -## Available choices are: -## -## - stdout -## -## Outputs directly to standard output -## (This is the default) -## -## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start()) -## -## Creates a callable that will parse given file for the given -## regex pattern and will insert the output in the file. -## ``idx`` is a callable that receive the matching object and -## must return a integer index point where to insert the -## the output in the file. Default is to return the position of -## the start of the matched string. -## -## - FileRegexSubst(file, pattern, replace, flags) -## -## Apply a replace inplace in the given file. Your regex pattern must -## take care of everything and might be more complex. Check the README -## for a complete copy-pastable example. -## -publish = writefile -#publish = stdout - - -## ``revs`` is a list of callable or a list of string -## -## callable will be called to resolve as strings and allow dynamical -## computation of these. The result will be used as revisions for -## gitchangelog (as if directly stated on the command line). This allows -## to filter exaclty which commits will be read by gitchangelog. -## -## To get a full documentation on the format of these strings, please -## refer to the ``git rev-list`` arguments. There are many examples. -## -## Using callables is especially useful, for instance, if you -## are using gitchangelog to generate incrementally your changelog. -## -## Some helpers are provided, you can use them:: -## -## - FileFirstRegexMatch(file, pattern): will return a callable that will -## return the first string match for the given pattern in the given file. -## If you use named sub-patterns in your regex pattern, it'll output only -## the string matching the regex pattern named "rev". -## -## - Caret(rev): will return the rev prefixed by a "^", which is a -## way to remove the given revision and all its ancestor. -## -## Please note that if you provide a rev-list on the command line, it'll -## replace this value (which will then be ignored). -## -## If empty, then ``gitchangelog`` will act as it had to generate a full -## changelog. -## -## The default is to use all commits to make the changelog. -#revs = ["^1.0.3", ] -#revs = [ -# Caret( -# FileFirstRegexMatch( -# "CHANGELOG.rst", -# r"(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")), -# "HEAD" -#] -revs = [] diff --git a/README.md b/README.md index 82adf6b8..112b3e04 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ [![sphinx](https://github.com/engineerjoe440/ElectricPy/actions/workflows/sphinx-build.yml/badge.svg?branch=master)](https://github.com/engineerjoe440/ElectricPy/actions/workflows/sphinx-build.yml) [![Documentation Status](https://readthedocs.org/projects/electricpy/badge/?version=latest)](https://electricpy.readthedocs.io/en/latest/?badge=latest) -![Tox Import Test](https://github.com/engineerjoe440/ElectricPy/workflows/Tox%20Tests/badge.svg) [![pytest](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pytest.yml) [![pydocstyle](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pydocstyle.yml/badge.svg?branch=master)](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pydocstyle.yml) @@ -32,12 +31,6 @@ development, education, and exploration in the realm of electrical engineering. Check out our full documentation: https://electricpy.readthedocs.io/en/latest/ -Antu dialog-warning **Documentation has recently been updated to use [ReadTheDocs](https://readthedocs.org/)** - -GitHub Pages are still active, and will continue to be for the forseeable -future, but they're intended for developmental updates rather than primary -documentation. - ## Features * Extensive set of common functions and formulas for electrical engineering and diff --git a/pyproject.toml b/pyproject.toml index 2ca60057..d38f09a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ - "NumPy", + "numpy >= 2.0.0", "matplotlib", "SciPy", "SymPy", @@ -41,6 +41,23 @@ dependencies = [ numerical = ["numdifftools"] fault = ["arcflash"] full = ["numdifftools", "arcflash"] +test = [ + "pytest >=2.7.3", + "xdoctest >= 1.1.3", + "pytest-pydocstyle >= 2.3.2", + "pygments >= 2.18.0", +] + +[tool.pytest.ini_options] +testpaths = [ + "test", + "electricpy", +] +addopts = "--pydocstyle --xdoctest" + +[tool.pydocstyle] +convention = "numpy" +match-dir = "electricpy/" [project.urls] Home = "https://electricpy.readthedocs.io/en/latest/index.html" From 07fda47bf02bc4a2e21c4b3fddb33d0747470202 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Wed, 21 Jan 2026 20:26:43 -0800 Subject: [PATCH 02/10] removed requirements files --- .github/workflows/pydocstyle.yml | 28 ---------------------------- .github/workflows/pytest.yml | 7 +++---- .github/workflows/sphinx.yml | 4 +--- .vscode/settings.json | 15 +++++++++++++++ cspell.json | 23 ----------------------- docsource/requirements.txt | 13 ------------- pyproject.toml | 15 ++++++++++++++- requirements.txt | 5 ----- test/requirements.txt | 2 -- 9 files changed, 33 insertions(+), 79 deletions(-) delete mode 100644 .github/workflows/pydocstyle.yml delete mode 100644 cspell.json delete mode 100644 docsource/requirements.txt delete mode 100644 requirements.txt delete mode 100644 test/requirements.txt diff --git a/.github/workflows/pydocstyle.yml b/.github/workflows/pydocstyle.yml deleted file mode 100644 index b1c1143a..00000000 --- a/.github/workflows/pydocstyle.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: pydocstyle - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.11"] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.11 - uses: actions/setup-python@v3 - with: - python-version: "3.11" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pydocstyle - pip install . - python3 -c "import electricpy; print('electricpy.__file__')" - - name: Test NumpyDoc Style - run: | - cd electricpy - pydocstyle --convention=numpy \ No newline at end of file diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 7e62afed..3401776e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v3 @@ -25,9 +25,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - if [ -f test/requirements.txt ]; then pip install -r test/requirements.txt; fi - pip install . + pip install .[dev] python3 -c "import electricpy; print('electricpy.__file__')" - name: Test with pytest run: | - pytest --xdoctest + pytest --pydoctest --xdoctest diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 27656c69..0130ba78 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -23,17 +23,15 @@ jobs: run: | python -m pip install --upgrade pip cp logo/ElectricpyLogo.svg docsource/static/ElectricpyLogo.svg - pip install -r docsource/requirements.txt - name: Build Sphinx docs if: success() run: | - pip install . + pip install .[dev] python3 -c "import electricpy; print('electricpy.__file__')" sphinx-build -M html docsource docs - name: Generate Coverage Badge if: success() run: | - pip install -r test/requirements.txt coverage run --source=./electricpy -m pytest coverage-badge -o docs/html/coverage.svg diff --git a/.vscode/settings.json b/.vscode/settings.json index 0bad4d33..078ed0cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,21 @@ "editor.rulers": [ 80, 120 ], + "cSpell.words": [ + "electricpy", + "epmath", + "fsolve", + "matplotlib", + "parallelz", + "phasor", + "phasorlist", + "phasorplot", + "phasors", + "powerset", + "pyplot", + "scipy", + "visu" + ], "python.testing.pytestArgs": [ "-m", "test" diff --git a/cspell.json b/cspell.json deleted file mode 100644 index 2e7da734..00000000 --- a/cspell.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "0.2", - "ignorePaths": [], - "dictionaryDefinitions": [], - "dictionaries": [], - "words": [ - "electricpy", - "epmath", - "fsolve", - "matplotlib", - "parallelz", - "phasor", - "phasorlist", - "phasorplot", - "phasors", - "powerset", - "pyplot", - "scipy", - "visu" - ], - "ignoreWords": [], - "import": [] -} diff --git a/docsource/requirements.txt b/docsource/requirements.txt deleted file mode 100644 index bf7380ac..00000000 --- a/docsource/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -wheel -sphinx -numpydoc -myst-parser[linkify] -sphinx-sitemap -sphinx-git -sphinx-immaterial -coverage-badge -numpy -matplotlib -scipy -sympy -numdifftools \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d38f09a0..c96a59bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,11 +41,24 @@ dependencies = [ numerical = ["numdifftools"] fault = ["arcflash"] full = ["numdifftools", "arcflash"] -test = [ +dev = [ "pytest >=2.7.3", "xdoctest >= 1.1.3", "pytest-pydocstyle >= 2.3.2", "pygments >= 2.18.0", + "wheel", + "sphinx", + "numpydoc", + "myst-parser[linkify]", + "sphinx-sitemap", + "sphinx-git", + "sphinx-immaterial", + "coverage-badge", + "numpy", + "matplotlib", + "scipy", + "sympy", + "numdifftools", ] [tool.pytest.ini_options] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ace01b04..00000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -NumPy -matplotlib -SciPy -SymPy -numdifftools diff --git a/test/requirements.txt b/test/requirements.txt deleted file mode 100644 index 5225ed33..00000000 --- a/test/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest -xdoctest \ No newline at end of file From c217738454b9cce71d20d5d161564251be98acd2 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Wed, 21 Jan 2026 20:28:35 -0800 Subject: [PATCH 03/10] remove 2.0.0 requirement --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c96a59bf..da7e7c49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,10 +30,10 @@ classifiers = [ dynamic = ["version"] dependencies = [ - "numpy >= 2.0.0", + "numpy", "matplotlib", - "SciPy", - "SymPy", + "scipy", + "sympy", "numdifftools", ] From 330c5f39169a3d43903f451cad1a0185261c60f0 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Wed, 21 Jan 2026 20:31:26 -0800 Subject: [PATCH 04/10] fix docstyle --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 3401776e..940f9158 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -29,4 +29,4 @@ jobs: python3 -c "import electricpy; print('electricpy.__file__')" - name: Test with pytest run: | - pytest --pydoctest --xdoctest + pytest --pydocstyle --xdoctest From 7c5bffce0cf55121f9091080c623bcbb1cf1c1e4 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Wed, 21 Jan 2026 21:05:10 -0800 Subject: [PATCH 05/10] started migrating to md documentation --- .github/workflows/sphinx.yml | 2 +- docsource/additionalresources.md | 24 ++++++++++ docsource/additionalresources.rst | 41 ------------------ docsource/changes.rst | 4 -- docsource/conf.py | 4 +- docsource/constants.md | 22 ++++++++++ docsource/constants.rst | 26 ----------- docsource/index.rst | 1 - .../static/InductionMotorCircleExample.png | Bin 67607 -> 67608 bytes docsource/static/PhasorPlot.png | Bin 57152 -> 57153 bytes docsource/static/PowerTriangle.png | Bin 24314 -> 24315 bytes .../static/ReceivingEndPowerCircleExample.png | Bin 40813 -> 40814 bytes .../static/ReceivingPowerCircleExample.png | Bin 40813 -> 40814 bytes docsource/static/convbar-example.png | Bin 20696 -> 20697 bytes docsource/static/series-rlc-r10-l0.5.png | Bin 0 -> 60417 bytes docsource/static/series-rlc-r5-l0.4.png | Bin 0 -> 32352 bytes pyproject.toml | 1 - 17 files changed, 48 insertions(+), 77 deletions(-) create mode 100644 docsource/additionalresources.md delete mode 100644 docsource/additionalresources.rst delete mode 100644 docsource/changes.rst create mode 100644 docsource/constants.md delete mode 100644 docsource/constants.rst create mode 100644 docsource/static/series-rlc-r10-l0.5.png create mode 100644 docsource/static/series-rlc-r5-l0.4.png diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 0130ba78..09eb27f4 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -17,7 +17,7 @@ jobs: - name: Install Python uses: actions/setup-python@v3 with: - python-version: "3.11" + python-version: "3.12" # I don't know where the "run" thing is documented. - name: Install dependencies run: | diff --git a/docsource/additionalresources.md b/docsource/additionalresources.md new file mode 100644 index 00000000..4706e69f --- /dev/null +++ b/docsource/additionalresources.md @@ -0,0 +1,24 @@ +# Additional Resources + + +## Generic and Data Science + +* NumPy: https://numpy.org/ +* SciPy: https://scipy.org/ +* Matplotlib: https://matplotlib.org/ +* SymPy: https://www.sympy.org/en/index.html +* Pyomo: https://www.pyomo.org/ +* Pint: https://pint.readthedocs.io/en/stable/ +* numdifftools: https://numdifftools.readthedocs.io/en/latest/ + +## Electrical Engineering Focus + +* Python COMTRADE File Interpreter: https://github.com/dparrini/python-comtrade +* Python COMTRADE Writer: https://github.com/relihanl/comtradehandlers +* Arc Flash Calculator: https://github.com/LiaungYip/arcflash +* PandaPower: https://www.pandapower.org/start/ +* PyPSA: https://github.com/PyPSA/PyPSA +* PyPower (no longer supported): https://pypi.org/project/PYPOWER/ +* minpower: http://adamgreenhall.github.io/minpower/index.html +* oemof (Open Energy MOdeling Framework): https://oemof.org/ +* PowerGAMA: https://bitbucket.org/harald_g_svendsen/powergama/wiki/Home diff --git a/docsource/additionalresources.rst b/docsource/additionalresources.rst deleted file mode 100644 index d8999ca2..00000000 --- a/docsource/additionalresources.rst +++ /dev/null @@ -1,41 +0,0 @@ -Additional Resources -================================================================================ - - -Generic and Data Science ------------------------- - - * NumPy: https://numpy.org/ - - * SciPy: https://scipy.org/ - - * Matplotlib: https://matplotlib.org/ - - * SymPy: https://www.sympy.org/en/index.html - - * Pyomo: https://www.pyomo.org/ - - * Pint: https://pint.readthedocs.io/en/stable/ - - * numdifftools: https://numdifftools.readthedocs.io/en/latest/ - -Electrical Engineering Focus ----------------------------- - - * Python COMTRADE File Interpreter: https://github.com/dparrini/python-comtrade - - * Python COMTRADE Writer: https://github.com/relihanl/comtradehandlers - - * Arc Flash Calculator: https://github.com/LiaungYip/arcflash - - * PandaPower: https://www.pandapower.org/start/ - - * PyPSA: https://github.com/PyPSA/PyPSA - - * PyPower (no longer supported): https://pypi.org/project/PYPOWER/ - - * minpower: http://adamgreenhall.github.io/minpower/index.html - - * oemof (Open Energy MOdeling Framework): https://oemof.org/ - - * PowerGAMA: https://bitbucket.org/harald_g_svendsen/powergama/wiki/Home diff --git a/docsource/changes.rst b/docsource/changes.rst deleted file mode 100644 index 1dd04244..00000000 --- a/docsource/changes.rst +++ /dev/null @@ -1,4 +0,0 @@ -Recent Changes -================================================================================ - -.. git_changelog:: diff --git a/docsource/conf.py b/docsource/conf.py index 0be00c19..759e1093 100644 --- a/docsource/conf.py +++ b/docsource/conf.py @@ -38,7 +38,7 @@ # -- Project information ----------------------------------------------------- project = 'electricpy' -copyright = '2022, Joe Stanley' +copyright = '2026, Joe Stanley' author = 'Joe Stanley' # The full version, including alpha/beta/rc tags @@ -56,8 +56,6 @@ 'sphinx.ext.mathjax', 'sphinx.ext.autosummary', 'sphinx.ext.viewcode', - 'numpydoc', - 'sphinx_git', 'myst_parser', 'sphinx_immaterial', ] diff --git a/docsource/constants.md b/docsource/constants.md new file mode 100644 index 00000000..34d51bef --- /dev/null +++ b/docsource/constants.md @@ -0,0 +1,22 @@ +# Constants + +In addition to the variety of funcitons provided, several common, and useful, +constants are provided to simplify arithmetic. + + +| Name | Value | +|----------------|----------------------------------------------------------------| +| `pi` | π (derived from numpy.pi) | +| `a` | :math:`1\angle{120}` | +| `p` | 10^-12 (pico) | +| `n` | 10^-9 (nano) | +| `u` | 10^-6 (micro) | +| `m` | 10^-3 (mili) | +| `k` | 10^3 (kila) | +| `M` | 10^6 (mega) | +| `G` | 10^9 (giga) | +| `u0` | :math:`µ0` (mu-not) 4πE-7 | +| `e0` | :math:`ε0` (epsilon-not) 8.854E-12 | +| `carson_r` | 9.869e-7 (Carson's Ristance Constant) | +| `WATTS_PER_HP` | 745.699872 | +| `KWH_PER_BTU` | 3412.14 | diff --git a/docsource/constants.rst b/docsource/constants.rst deleted file mode 100644 index cc98f7f4..00000000 --- a/docsource/constants.rst +++ /dev/null @@ -1,26 +0,0 @@ -Constants -================================================================================ - -.. _constants.py: - -In addition to the variety of funcitons provided, several common, and useful, -constants are provided to simplify arithmetic. - -============== ================================================================= -Name Value -============== ================================================================= -`pi` π (derived from numpy.pi) -`a` :math:`1\angle{120}` -`p` 10^-12 (pico) -`n` 10^-9 (nano) -`u` 10^-6 (micro) -`m` 10^-3 (mili) -`k` 10^3 (kila) -`M` 10^6 (mega) -`G` 10^9 (giga) -`u0` :math:`µ0` (mu-not) 4πE-7 -`e0` :math:`ε0` (epsilon-not) 8.854E-12 -`carson_r` 9.869e-7 (Carson's Ristance Constant) -`WATTS_PER_HP` 745.699872 -`KWH_PER_BTU` 3412.14 -============== ================================================================= \ No newline at end of file diff --git a/docsource/index.rst b/docsource/index.rst index e2346bd2..0aeeae95 100644 --- a/docsource/index.rst +++ b/docsource/index.rst @@ -36,7 +36,6 @@ Contents: electricpyapi constants additionalresources - changes Github PyPI diff --git a/docsource/static/InductionMotorCircleExample.png b/docsource/static/InductionMotorCircleExample.png index 4c2e29a30257204ea0442d04446d50f07a72a181..a04618542526d18a5f1d0de6217eece121aed692 100644 GIT binary patch delta 54 zcmbO}fn~-7mI*41Rufef6%7saEOZnyN=gcft@QPC6H5wm@=J0ull1b7()ABUm!4}( K+nUC>ksScz>Jtn+b;e!50!{Aq|9T5iVB&>L}saEib9l7na6|-5kktCOd&-PnaPw8D#?(9 zk|`lmzjNL9v-k17$M4<$?LYT(JRSFai?6lT=NiuIJkM)|>7CHnLeD`@B9XQn(^NGe zk;wf?B(h;zYJ5eiYv3#XA>*cI>SlQPyxVmv7h95!m7BBUWj9BAYwl~dF0S^MFC7%y zFClhhFSnhWo3pEoxVY2*{tmIrE*HdW&s^=mr_ecTo^mCTwptN?$Z{02?Mb8`PRCT0 zjNI=}J-BYla*{Q~uVT}tDdSRwv zP^#&}&c&Plyw{Xiio0biys8wOHEw>+`VhY=|MsZFQcC$!d(ySV0YPqF@}OEv3AWAD znFHKwJ@mxC5nqrOYbUe$_dkR$NYF(8=X>tgaw`7w_iPa^)UG7)n3A0A%asNmNu)1(zDYtiJ zf|7>jSBkxplhcP!pGL=1{nWLzZr!SU^-50W>UX2Or@ARe!wSz<)OB}nvo2=)_U+s3 z;$joNF!<|~w`a!#*^uF3%hE&VBnGJqbPGs9={Dbca-E%>hqHR~jDIlO*IQQZT$&kr zT_3Ww*kw#>Gl%4aVLkinVG-3ewe>>6WP3Zb97Qt*tyqHRwH%6WuCu9 zJMK&WG8xlJIm&LBeIhtEmigvpc5+!+*-Fpf^s7rV8ZRz=esX10eR*Z2Dz+nJE0=Hf z=uq&~l%t*ft3Rr#vey|5A89L9`>yMyD+lX7VjcYvfXDLo=Hky!nZD~QS$E#Fv;>&$ zy{Xq6_+dut{CUBJ-@g?~IV^U)O;w=rfBWTJL1hzf3@vZWMC`=u`qEIQb>l6PKaa}I zw39{VA6{PFBX;)X4eBi%ak-Co$zHR>T`zB}&J^3f6STW{(bWSt(wMFi5}lg*w?ihp z@yj+_`JM{RlP8y6-FA#n{q>uyXNk`GZI(6s2b%N(Rq4$Y92pMK{Bg! zXZqP+8Wa=5w6L%+JUJQm?84i;IM;9O?B2iL9PaGx4bIHuyDM(dA*Cx479Jj?$gOB+ zm(x1;edQ}ZKfh&-KiQKfPyC~z7##W@he=)R%jxy|^xTP-jxHcFa*Ny5t6h1t>~dvx;Y^gfK`PI&`zDO5W`3*<#CDa#Bry=|vt!T1F4!lI+2)4J}q z{)!9>3n(fQkBE%yDzP^d^Q*tP#rgX65LMoQurNB$-`|5ZZttFJE%RCudwKN-_3`7! zC%%8LYigo?<@sCL!9nz`34WlyDRSq?moMaJ&z>!-s2J_ZH58RmQ&mm#SzV0Q%94J?k7Y4UG7 za^whc+eg`hl>`3#8qYlc<{t5wf`h3LH-TTj+C)Cz(>^!)y6f?&gA+e~WGV;K&(6)& zB7w@+5D&HQ-Z7IpUwK`pr3|!Qy)!mGey%l6#M#9qu&_`}TSteuS59e{8$#EA^zHNh zyS^l`e}4e}>ihem+poNq@=A?OP1$ZT?4oydb^Y++!|eR6MY5yL!=&r-H*emY_)yGu zX`rk&)?;NsWMye)>&~4!CnhFpT3hMm<>fDbPJMFf*?AH@Zhkh( z#_+}?zH3MNUtHdL{rYunq?~d(C=w$=g-qJFx=D@ ztpC~)Yh`6M*S8|?wYWKwL+Z=@T?Y>yHdWeH@P;x^Qv`cZ zPmj?W%F)ET?5$$vmCY@9nv@S@t_H@(Z�CBSWAJRC+M4F8-_!W#S_;j!!~@ap&Pn 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 W;zdEhdv7rKbx!SqYLT*K$o~P12+olJ literal 0 HcmV?d00001 diff --git a/docsource/static/series-rlc-r5-l0.4.png b/docsource/static/series-rlc-r5-l0.4.png new file mode 100644 index 0000000000000000000000000000000000000000..5995f32735df70046bd1d74c06877adb50eab52d GIT binary patch literal 32352 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 H^3J~jREekA literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index da7e7c49..93b36e57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ dev = [ "pygments >= 2.18.0", "wheel", "sphinx", - "numpydoc", "myst-parser[linkify]", "sphinx-sitemap", "sphinx-git", From d8a634c8f848cf68f9311f16bb54c37e468d54f3 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 1 Feb 2026 11:17:36 -0800 Subject: [PATCH 06/10] cleaned up pytest errors --- conftest.py | 9 +++++++++ electricpy/geometry/__init__.py | 25 +++++++++++++++++-------- electricpy/geometry/circle.py | 23 ++++++++++++++--------- electricpy/geometry/triangle.py | 33 +++++++++++++++------------------ electricpy/passive.py | 8 ++++---- test/__init__.py | 1 - 6 files changed, 59 insertions(+), 40 deletions(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..e36004ec --- /dev/null +++ b/conftest.py @@ -0,0 +1,9 @@ +# Pytest configuration + +import pytest +import numpy as np + +@pytest.fixture(scope="session", autouse=True) +def setup_environment(): + """Use Legacy Numpy Printing Options for consistent test outputs.""" + np.set_printoptions(legacy='1.25') diff --git a/electricpy/geometry/__init__.py b/electricpy/geometry/__init__.py index 9b226519..3a797296 100644 --- a/electricpy/geometry/__init__.py +++ b/electricpy/geometry/__init__.py @@ -16,7 +16,7 @@ import math from dataclasses import dataclass -from typing import Iterable, Iterator, Optional, Tuple, Union +from typing import Iterator, Tuple, Union Number = Union[int, float] @@ -24,6 +24,8 @@ def _as_float(x) -> float: """ + Cast as a float. + Coerce numeric-like values (including complex with ~0 imag) to float. This defends against accidental cmath usage elsewhere in the library. """ @@ -40,7 +42,8 @@ def _is_close(a: float, b: float, *, rel_tol: float = 1e-9, abs_tol: float = 1e- @dataclass(frozen=False) class Point: - """A point in 2D space. + """ + A point in 2D space. Parameters ---------- @@ -49,6 +52,7 @@ class Point: y : float The y coordinate of the point """ + x: float y: float @@ -78,18 +82,18 @@ def is_close(self, other: "Point", *, tol: float = 1e-9) -> bool: return _is_close(self.x, other.x, rel_tol=tol, abs_tol=tol) and _is_close(self.y, other.y, rel_tol=tol, abs_tol=tol) def __repr__(self) -> str: + """Developer representation of the Point.""" return f"Point({self.x}, {self.y})" def __str__(self) -> str: + """Representation of the Point.""" return f"({self.x}, {self.y})" @dataclass(frozen=False) class Line: - """A line in 2D space in the form: + """A line in 2D space in the form (`ax + by + c = 0`).""" - ax + by + c = 0 - """ a: float b: float c: float @@ -149,6 +153,8 @@ def intersection(self, other: object) -> Point: def normalized(self) -> Tuple[float, float, float]: """ + Normalize the geometry coefficients. + Return a normalized (a,b,c) such that sqrt(a^2+b^2)=1 and sign is stable. This helps with comparisons and distances. """ @@ -178,6 +184,8 @@ def is_close(self, other: "Line", *, tol: float = 1e-9) -> bool: def __eq__(self, other: object) -> bool: """ + Evaluate the equality of geometric object. + Exact-ish equality (proportional coefficients), but using normalization for better behavior than raw ratio checks. This is safer than the original. """ @@ -186,10 +194,11 @@ def __eq__(self, other: object) -> bool: return self.is_close(other, tol=1e-9) def __repr__(self) -> str: + """Developer representation of the Line.""" return f"Line({self.a}, {self.b}, {self.c})" def __str__(self) -> str: - # Keep a readable form; avoid division by zero when possible. + """Keep a readable form; avoid division by zero when possible.""" if _is_close(self.a, 0.0): # by + c = 0 => y = -c/b return f"y = {-self.c / self.b}" @@ -220,8 +229,8 @@ def angle_btw_lines(l1: Line, l2: Line) -> float: raise ValueError("Cannot compute angle for degenerate line.") # Clamp to [-1,1] to protect against tiny numeric drift - cosang = max(-1.0, min(1.0, dot / (n1 * n2))) - ang = math.acos(cosang) + cosine_angle = max(-1.0, min(1.0, dot / (n1 * n2))) + ang = math.acos(cosine_angle) # Return acute angle if ang > math.pi / 2: diff --git a/electricpy/geometry/circle.py b/electricpy/geometry/circle.py index ed8e9d90..980bebb6 100644 --- a/electricpy/geometry/circle.py +++ b/electricpy/geometry/circle.py @@ -1,7 +1,6 @@ ################################################################################ """ -electricpy.geometry.circle - Collection of methods which operate on Cartesian -circles. +electricpy.geometry.circle - Collection of methods for Cartesian circles. >>> import electricpy.geometry.circle as circle @@ -13,7 +12,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Generator, Iterable, Optional, Tuple, Union, overload +from typing import Generator, Tuple, Union import math from electricpy import geometry @@ -52,6 +51,7 @@ class Circle: radius : float The radius of the circle (must be >= 0) """ + center: Point radius: float @@ -139,9 +139,9 @@ def is_tangent(self, l: Line, *, tol: float = 1e-9) -> bool: def is_normal(self, l: Line, *, tol: float = 1e-9) -> bool: """ - Return True if the line passes through the circle's center (within tolerance). + Line passes through the circle's center (within tolerance). - IMPORTANT + Important --------- A line being a "normal to the circle" is only well-defined at a specific point of contact. This method keeps backward compatibility with the @@ -175,11 +175,11 @@ def equation(self) -> str: B = -2.0 * k C = const - def _term(coeff: float, var: str) -> str: - if _is_close(coeff, 0.0): + def _term(coef: float, var: str) -> str: + if _is_close(coef, 0.0): return "" - sign = " + " if coeff > 0 else " - " - mag = abs(coeff) + sign = " + " if coef > 0 else " - " + mag = abs(coef) # Prefer integer-like display when possible if _is_close(mag, round(mag)): mag_str = str(int(round(mag))) @@ -343,20 +343,25 @@ def intersetion(self, other) -> Union[None, Point, Tuple[Point, Point], str]: # Dunder methods # ------------------------------------------------------------------------- def __repr__(self) -> str: + """Representation of the Circle.""" return f"Circle(center={self.center}, radius={self.radius})" def __str__(self) -> str: + """Form of the Circle.""" return f"Circle(center={self.center}, radius={self.radius})" def __eq__(self, other: object) -> bool: + """Equality comparison for Circle.""" if isinstance(other, Circle): return self.center == other.center and self.radius == other.radius return False def __ne__(self, other: object) -> bool: + """Inequality comparison for Circle.""" return not self == other def __hash__(self) -> int: + """Hash for Circle.""" return hash((self.center, self.radius)) diff --git a/electricpy/geometry/triangle.py b/electricpy/geometry/triangle.py index 39e3accd..1ee192a4 100644 --- a/electricpy/geometry/triangle.py +++ b/electricpy/geometry/triangle.py @@ -1,7 +1,6 @@ ################################################################################ """ -electricpy.geometry.triangle - Collection of methods which operate on Cartesian -triangles. +electricpy.geometry.triangle - Collection of methods for Cartesian triangles. >>> import electricpy.geometry.triangle as triangle @@ -12,7 +11,7 @@ from __future__ import annotations -from typing import Iterable, Optional, Sequence, Tuple, Union +from typing import Tuple, Union import math from electricpy.geometry import Point, Line @@ -42,9 +41,7 @@ def _is_close(a: float, b: float, *, rel_tol: float = 1e-9, abs_tol: float = 1e- def _triangle_twice_area(p0: Point, p1: Point, p2: Point) -> float: - """ - Return twice the signed area (cross product magnitude). - """ + """Return twice the signed area (cross product magnitude).""" return (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) @@ -115,9 +112,7 @@ def perimeter(self) -> float: return self.a + self.b + self.c def perimeters(self) -> float: - """ - Backward-compatible alias for perimeter(). - """ + """Backward-compatible alias for perimeter().""" return self.perimeter() def area(self) -> float: @@ -151,9 +146,9 @@ def centroid(self) -> Point: def in_center(self) -> Point: """ - Return the incenter of the triangle. + Return the in_center of the triangle. - The incenter is the weighted average of vertices by the lengths of + The in_center is the weighted average of vertices by the lengths of the opposite sides. With our naming: a = |p0-p1| opposite vertex p2 b = |p1-p2| opposite vertex p0 @@ -172,7 +167,7 @@ def in_center(self) -> Point: def in_radius(self) -> float: """ - Return the inradius of the triangle. + Return the in_radius of the triangle. r = A / s where s is semiperimeter. """ @@ -184,12 +179,12 @@ def in_radius(self) -> float: def ortho_center(self) -> Point: """ - Return the orthocenter of the triangle. + Return the ortho_center of the triangle. Construct two altitudes: - altitude from p0 to line through p1-p2 - altitude from p1 to line through p0-p2 - Their intersection is the orthocenter. + Their intersection is the ortho_center. Requires Line objects returned by geometry.line_equation to support: - foot_perpendicular(Point) -> Point @@ -207,7 +202,7 @@ def ortho_center(self) -> Point: def circum_center(self) -> Point: """ - Return the circumcenter of the triangle. + Return the circum_center of the triangle. Intersection of perpendicular bisectors of two sides. """ @@ -217,13 +212,13 @@ def circum_center(self) -> Point: def circum_radius(self) -> float: """ - Return the circumradius of the triangle. + Return the circum_radius of the triangle. R = abc / (4A) """ A = self.area() if _is_close(A, 0.0, abs_tol=self._tol): - raise ValueError("Degenerate triangle: area is zero, circumradius undefined") + raise ValueError("Degenerate triangle: area is zero, circum_radius undefined") return (self.a * self.b * self.c) / (4.0 * A) # ------------------------------------------------------------------------- @@ -231,7 +226,9 @@ def circum_radius(self) -> float: # ------------------------------------------------------------------------- def __is_valid(self) -> bool: """ - Validate triangle: + Validate triangle. + + Checks: - triangle inequality with tolerance - non-collinear (non-degenerate) using cross-product area test """ diff --git a/electricpy/passive.py b/electricpy/passive.py index 65024554..bd344801 100644 --- a/electricpy/passive.py +++ b/electricpy/passive.py @@ -126,7 +126,7 @@ def capbacktoback(C1, C2, Lm, VLN=None, VLL=None): Function to calculate the maximum current and the frequency of the inrush current of two capacitors connected in parallel when one (energized) capacitor - is switched into another (non-engergized) capacitor. + is switched into another (non-energized) capacitor. .. note:: This formula is only valid for three-phase systems. @@ -443,7 +443,7 @@ def air_core_inductance(d: float, coil_l: float, n: int): def air_core_required_length(d: float, L: float, n: int): r""" - Compute Required Length of Air Core Inductor + Compute Required Length of Air Core Inductor. .. math:: l = \frac{1000 d^2 n^2 - 457418 d L}{1016127 L} @@ -467,7 +467,7 @@ def air_core_required_length(d: float, L: float, n: int): def air_core_required_diameter(coil_l: float, L: float, n: int): r""" - Compute Diameter of Air Core Inductor + Compute Diameter of Air Core Inductor. .. math:: 1000 n^2 d^2 - 457418 L d - 1016127 L l = 0 @@ -496,7 +496,7 @@ def air_core_required_diameter(coil_l: float, L: float, n: int): def air_core_required_num_turns(d: float, coil_l: float, L: float): r""" - Compute Required Number of Turns of Air Core Inductor + Compute Required Number of Turns of Air Core Inductor. .. math:: n = \sqrt{\frac{L(1016127 l + 457418 d)}{1000 d^2}} diff --git a/test/__init__.py b/test/__init__.py index 7fbe89ec..ea0e4fa7 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,6 +1,5 @@ from electricpy.geometry import Point from electricpy.geometry import Line -import numpy as np from numpy.testing import assert_almost_equal def compare_points(p1: Point, p2: Point) -> bool: From fda6145669e4708b2c42e0f313bd7cd8f64b27f0 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 1 Feb 2026 11:33:36 -0800 Subject: [PATCH 07/10] fixed a test --- electricpy/compute.py | 17 +++++++++++++++-- electricpy/math.py | 29 ++++++++++++++--------------- test/test_compute.py | 17 ++++++++++++++--- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/electricpy/compute.py b/electricpy/compute.py index b2549535..197a6bba 100644 --- a/electricpy/compute.py +++ b/electricpy/compute.py @@ -264,7 +264,20 @@ def string_to_bits(str): The binary representation of the input string. """ - data = (''.join(format(ord(x), 'b') for x in str)) - return data + data = ''.join(format(ord(x), 'b') for x in str) + + # If empty, return as-is + if len(data) == 0: + return data + + # If length is already a power of two, return unchanged + n = len(data) + if n & (n - 1) == 0: + return data + + # Compute next power of two and pad with leading zeros + next_pow = 1 << n.bit_length() + pad_len = next_pow - n + return '0' * pad_len + data # END diff --git a/electricpy/math.py b/electricpy/math.py index 9188d577..a825b873 100644 --- a/electricpy/math.py +++ b/electricpy/math.py @@ -49,7 +49,7 @@ def step(t): r""" Step Function [ u(t) ]. - Simple implimentation of numpy.heaviside function to provide standard + Simple implementation of numpy.heaviside function to provide standard step-function as specified to be zero at :math:`x < 0`, and one at :math:`x \geq 0`. @@ -75,7 +75,7 @@ def funcrms(func, T): Root-Mean-Square (RMS) Evaluator for Callable Functions. Integral-based RMS calculator, evaluates the RMS value - of a repetative signal (f) given the signal's specific + of a repetitive signal (f) given the signal's specific period (T) Parameters @@ -89,8 +89,7 @@ def funcrms(func, T): ------- RMS: The RMS value of the function (f) over the interval ( 0, T ) """ - fn = lambda x: func(x) ** 2 - integral, _ = integrate(fn, 0, T) + integral, _ = integrate(lambda x: func(x) ** 2, 0, T) return _np.sqrt(1 / T * integral) @@ -140,7 +139,7 @@ def gausdist(x, mu=0, sigma=1): Returns ------- F: numpy.ndarray - Computed distribution of the gausian function at the + Computed distribution of the gaussian function at the points specified by (array) x """ # Define Integrand @@ -149,7 +148,7 @@ def integrand(sq): try: lx = len(x) # Find length of Input - except: + except TypeError: lx = 1 # Length 1 x = [x] # Pack into list F = _np.zeros(lx, dtype=_np.float64) @@ -157,7 +156,7 @@ def integrand(sq): x_tmp = x[i] # Evaluate X (altered by mu and sigma) X = (x_tmp - mu) / sigma - integral = integrate(integrand, _np.NINF, X) # Integrate + integral = integrate(integrand, -_np.inf, X) # Integrate result = 1 / _np.sqrt(2 * _np.pi) * integral[0] # Evaluate Result F[i] = result # Return only the 0-th value if there's only 1 value available @@ -197,7 +196,7 @@ def probdensity(func, x, x0=0, scale=True): sumx = _np.array([]) try: lx = len(x) # Find length of Input - except: + except TypeError: lx = 1 # Length 1 x = [x] # Pack into list # Recursively Find Probability Density @@ -210,7 +209,7 @@ def probdensity(func, x, x0=0, scale=True): if scale: mx = sumx.max() sumx /= mx - elif scale != False: + elif not scale: sumx /= scale return sumx @@ -220,7 +219,7 @@ def rfft(arr, dt=0.01, absolute=True, resample=True): """ RFFT Function. - This function is designed to evaluat the real FFT + This function is designed to evaluate the real FFT of a input signal in the form of an array or list. Parameters @@ -252,13 +251,13 @@ def rfft(arr, dt=0.01, absolute=True, resample=True): # Evaluate the Downsampling Ratio dn = int(dt * len(arr)) # Downsample to remove unnecessary points - fixedfft = filter.dnsample(fourier, dn) - return (fixedfft) + fixed_fft = filter.dnsample(fourier, dn) + return fixed_fft elif not resample: - return (fourier) + return fourier else: # Condition Resample Value resample = int(resample) # Downsample to remove unnecessary points - fixedfft = filter.dnsample(fourier, resample) - return fixedfft + fixed_fft = filter.dnsample(fourier, resample) + return fixed_fft diff --git a/test/test_compute.py b/test/test_compute.py index 1364baed..4e28d589 100644 --- a/test/test_compute.py +++ b/test/test_compute.py @@ -27,6 +27,17 @@ def test_crc_sender_and_remainder(): assert set(remainder) == {"0"} -def test_string_to_bits(): - bits = compute.string_to_bits("A") - assert bits == "01000001" +@pytest.mark.parametrize("character_string, expected_bits", [ + ("A", "01000001"), + ("B", "01000010"), + ("C", "01000011"), + ("a", "01100001"), + ("b", "01100010"), + ("c", "01100011"), + ("0", "00110000"), + ("1", "00110001"), + ("2", "00110010"), +]) +def test_string_to_bits(character_string, expected_bits): + bits = compute.string_to_bits(character_string) + assert bits == expected_bits From 5d964a0d8cac4f40b3749e8a54ca0b7fd1920b86 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 1 Feb 2026 11:40:01 -0800 Subject: [PATCH 08/10] improved documentation and test --- electricpy/compute.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/electricpy/compute.py b/electricpy/compute.py index 197a6bba..92e2fd24 100644 --- a/electricpy/compute.py +++ b/electricpy/compute.py @@ -253,6 +253,14 @@ def string_to_bits(str): Converts a Pythonic string to the string's binary representation. + Examples + -------- + >>> from electricpy import compute as cmp + >>> cmp.string_to_bits("A") + '01000001' + >>> cmp.string_to_bits("Hello") + '0000010010001100101110110011011001101111' + Parameters ---------- str: string @@ -266,18 +274,11 @@ def string_to_bits(str): """ data = ''.join(format(ord(x), 'b') for x in str) - # If empty, return as-is - if len(data) == 0: - return data - - # If length is already a power of two, return unchanged - n = len(data) - if n & (n - 1) == 0: - return data + # Pad to Nearest Byte + offset = len(data) % 8 + if offset != 0: + data = (8 - offset) * '0' + data - # Compute next power of two and pad with leading zeros - next_pow = 1 << n.bit_length() - pad_len = next_pow - n - return '0' * pad_len + data + return data # END From fc5d9265e5ff83f7b6a776f7f316f81e798e188e Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 1 Feb 2026 11:46:53 -0800 Subject: [PATCH 09/10] cleanup test and fix dig-sim --- electricpy/sim.py | 12 +++++++----- test/test_sim.py | 12 ++++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/electricpy/sim.py b/electricpy/sim.py index f5b44618..2cfa1e4f 100644 --- a/electricpy/sim.py +++ b/electricpy/sim.py @@ -79,7 +79,8 @@ def digifiltersim(fin, filter, freqs, NN=1000, dt=0.01, title="", figsize: tuple, optional The figure dimensions for each subplot, default=None """ - if (figsize != None): _plt.figure(figsize=figsize) + if figsize is not None: + _plt.figure(figsize=figsize) flen = len(freqs) for i in range(flen): # Gather frequency @@ -94,7 +95,7 @@ def digifiltersim(fin, filter, freqs, NN=1000, dt=0.01, title="", x[k] = fin(k * dt, freq) # Identify how many rows were provided - sz = filter.size + sz = len(filter) if isinstance(filter, (tuple, list)) else filter.size if (sz < 5): raise ValueError("ERROR: Too few filter arguments provided. " + "Refer to documentation for proper format.") @@ -131,10 +132,11 @@ def digifiltersim(fin, filter, freqs, NN=1000, dt=0.01, title="", _plt.plot(ytime, 'k', label="Output") _plt.title(title) _plt.grid(which='both') - if legend: _plt.legend(title="Frequency = " + str(freq) + "Hz") - if xlim != False: + if legend: + _plt.legend(title="Frequency = " + str(freq) + "Hz") + if not xlim: _plt.xlim(xlim) - elif xmxscale != None: + elif xmxscale is not None: _plt.xlim((0, xmxscale / (freq * dt))) _plt.tight_layout() diff --git a/test/test_sim.py b/test/test_sim.py index 4ecfafe3..9cd1a8e5 100644 --- a/test/test_sim.py +++ b/test/test_sim.py @@ -7,10 +7,14 @@ def test_digifiltersim_basic(): - fin = lambda t, f: np.sin(2 * np.pi * f * t) - filter_row = [0.0, 0.0, 1.0, 0.0, 0.0] - - plot_module = sim.digifiltersim(fin, filter_row, freqs=[1], NN=10, dt=0.1, legend=False) + plot_module = sim.digifiltersim( + lambda t, f: np.sin(2 * np.pi * f * t), + [0.0, 0.0, 1.0, 0.0, 0.0], + freqs=[1], + NN=10, + dt=0.1, + legend=False + ) assert plot_module is not None From d1bbde21afc15a003d188eea40f074e945f4af6f Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 1 Feb 2026 11:52:03 -0800 Subject: [PATCH 10/10] clean up lint issues --- electricpy/sim.py | 68 +++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/electricpy/sim.py b/electricpy/sim.py index 2cfa1e4f..bb076101 100644 --- a/electricpy/sim.py +++ b/electricpy/sim.py @@ -212,7 +212,7 @@ def step_response(system, npts=1000, dt=0.01, combine=True, xlim=False, _plt.grid() _plt.legend() _plt.xlabel("Time (seconds)") - if xlim != False: + if not xlim: _plt.xlim(xlim) _plt.subplot(122) _plt.title(errtitle) @@ -220,10 +220,10 @@ def step_response(system, npts=1000, dt=0.01, combine=True, xlim=False, _plt.grid() _plt.legend() _plt.xlabel("Time (seconds)") - if xlim != False: + if not xlim: _plt.xlim(xlim) _plt.subplots_adjust(wspace=0.3) - if filename != None: + if filename is not None: _plt.savefig(filename) return _plt @@ -297,7 +297,7 @@ def ramp_response(system, npts=1000, dt=0.01, combine=True, xlim=False, _plt.grid() _plt.legend() _plt.xlabel("Time (seconds)") - if xlim != False: + if not xlim: _plt.xlim(xlim) _plt.subplot(122) _plt.title(errtitle) @@ -305,10 +305,10 @@ def ramp_response(system, npts=1000, dt=0.01, combine=True, xlim=False, _plt.grid() _plt.legend() _plt.xlabel("Time (seconds)") - if xlim != False: + if not xlim: _plt.xlim(xlim) _plt.subplots_adjust(wspace=0.3) - if filename != None: + if filename is not None: _plt.savefig(filename) return _plt @@ -381,7 +381,7 @@ def parabolic_response(system, npts=1000, dt=0.01, combine=True, xlim=False, _plt.grid() _plt.legend() _plt.xlabel("Time (seconds)") - if xlim != False: + if not xlim: _plt.xlim(xlim) _plt.subplot(122) _plt.title(errtitle) @@ -389,10 +389,10 @@ def parabolic_response(system, npts=1000, dt=0.01, combine=True, xlim=False, _plt.grid() _plt.legend() _plt.xlabel("Time (seconds)") - if xlim != False: + if not xlim: _plt.xlim(xlim) _plt.subplots_adjust(wspace=0.3) - if filename != None: + if filename is not None: _plt.savefig(filename) return _plt @@ -652,58 +652,58 @@ def func_c(self, x): # Concatenated Function # Plot Forcing Functions if (plotforcing): - fffig = _plt.figure("Forcing Functions") + _ = _plt.figure("Forcing Functions") if fnc > 1: for x in range(fnc): _plt.plot(TT, fn_arr[x], label="f" + str(x + 1)) else: _plt.plot(TT, fn_arr, label="f1") - if xlim != False: + if not xlim: _plt.xlim(xlim) - if ylim != False: + if not ylim: _plt.ylim(ylim) _plt.title("Forcing Functions " + title) _plt.xlabel("Time (seconds)") _plt.legend(title="Forcing Functions") _plt.grid() - if filename != None: + if filename is not None: _plt.savefig('Simulation Forcing Functions.png') if plotstate: _plt.show() # Plot each state-variable over time - stvfig = _plt.figure("State Variables") + _ = _plt.figure("State Variables") for x in range(xtim_len): _plt.plot(TT, xtim[x], label="x" + str(x + 1)) - if xlim != False: + if not xlim: _plt.xlim(xlim) - if ylim != False: + if not ylim: _plt.ylim(ylim) _plt.title("Simulated Output Terms " + soltype[solution] + title) _plt.xlabel("Time (seconds)") _plt.legend(title="State Variable") _plt.grid() - if filename != None: + if filename is not None: _plt.savefig('Simulation Terms.png') if plotstate: _plt.show() # Plot combined output if (plotresult and solution == 3): - cofig = _plt.figure("Combined Output") + _ = _plt.figure("Combined Output") C = _np.asarray(C) # convert back to array for operation for i in range(cC): yout = yout + xtim[i] * C[0][i] # Sum all st-space var mult. by their coeff yout = _np.asarray(yout) # convert output to array for plotting purposes _plt.plot(TT, yout[0]) - if xlim != False: + if not xlim: _plt.xlim(xlim) - if ylim != False: + if not ylim: _plt.ylim(ylim) _plt.title("Combined Output " + title) _plt.xlabel("Time (seconds)") _plt.grid() - if filename != None: + if filename is not None: _plt.savefig('Simulation Combined Output.png') if plotresult: _plt.show() @@ -958,7 +958,6 @@ def func_c(self, x): # Concatenated Function Q_funcs = [] P_strgs = [] Q_strgs = [] - Vi_list = [] lists = [P_strgs, Q_strgs] i = 0 # Index ii = 0 # String Index @@ -986,12 +985,12 @@ def func_c(self, x): # Concatenated Function Padd = False Qadd = False for _j in range(N): - if P_list[_k] == None: + if P_list[_k] is None: continue # Don't Generate Requirements for Slack Bus if (_k != _j) and not Padd: # Skip i,i Terms ang_len += 1 Padd = True - if (_k != _j) and (Q_list[_k] != None) and not Qadd: + if (_k != _j) and (Q_list[_k] is not None) and not Qadd: mag_len += 1 Qadd = True Vxdim = ang_len + mag_len @@ -1006,20 +1005,21 @@ def func_c(self, x): # Concatenated Function # Add New Entry To Lists for LST in lists: LST.append(None) - if P_list[_k] == None: + if P_list[_k] is None: continue # Don't Generate Requirements for Slack Bus # Collect Other Terms Yind = "[{}][{}]".format(_k, _j) - if verbose: print("K:", _k, "\tJ:", _j) + if verbose: + print("K:", _k, "\tJ:", _j) # Generate Voltage-Related Strings if _k != _j: # Skip i,i Terms # Generate K-Related Strings - if V_list[_k][0] == None: # The Vk magnitude is unknown + if V_list[_k][0] is None: # The Vk magnitude is unknown Vkm = "Vx[{}]".format(_k + ang_len - magoff * _k) # Use Variable Magnitude Vka = "Vx[{}]".format(_k - angoff) # Use Variable Angle else: # The Vk magnitude is known Vkm = "V_list[{}][0]".format(_k) # Load Magnitude - if V_list[_k][1] == None: # The Vj angle is unknown + if V_list[_k][1] is None: # The Vj angle is unknown Vka = "Vx[{}]".format(_k - angoff) # Use Variable Angle else: Vka = "V_list[{}][1]".format(_k) # Load Angle @@ -1035,7 +1035,8 @@ def func_c(self, x): # Concatenated Function Vja = "V_list[{}][1]".format(_j) # Load Angle # Generate String and Append to List of Functions P_strgs[i] = (Pstr.format(Vkm, Vka, Vjm, Vja, Yind)) - if verbose: print("New P-String:", P_strgs[i]) + if verbose: + print("New P-String:", P_strgs[i]) # Generate Q Requirement if Q_list[_k] is not None: # Generate String and Append to List of Functions @@ -1045,7 +1046,8 @@ def func_c(self, x): # Concatenated Function else: Qgen = "" Q_strgs[i] = (Qstr.format(Vkm, Vka, Vjm, Vja, Yind, Qgen)) - if verbose: print("New Q-String:", Q_strgs[i]) + if verbose: + print("New Q-String:", Q_strgs[i]) # Increment Index at Each Interior Level i += 1 tempPstr = "P_funcs.append(lambda Vx: -P_list[{0}]".format(_k) @@ -1060,10 +1062,12 @@ def func_c(self, x): # Concatenated Function tempPstr += ")" tempQstr += ")" if any(P_strgs[ii:i]): - if verbose: print("Full P-Func Str:", tempPstr) + if verbose: + print("Full P-Func Str:", tempPstr) exec(tempPstr) if any(Q_strgs[ii:i]): - if verbose: print("Full Q-Func Str:", tempQstr) + if verbose: + print("Full Q-Func Str:", tempQstr) exec(tempQstr) ii = i # Increase Lower Index retset = (P_funcs, Q_funcs)