├── .coveragerc ├── .gitchangelog.rc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── pipaudit.yml │ ├── pydocstyle.yml │ ├── pytest.yml │ ├── release.yml │ └── sphinx.yml ├── .gitignore ├── .readthedocs.yaml ├── .vscode ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── cspell.json ├── docsource ├── Thumbs.db ├── _templates │ ├── base.rst │ └── module.rst ├── additionalresources.rst ├── changes.rst ├── conf.py ├── constants.rst ├── electricpyapi.rst ├── extra │ └── google307ea14d75106813.html ├── index.rst ├── render_images.py ├── requirements.txt └── static │ ├── ElectricpyLogo.svg │ ├── InductionMotorCircleExample.png │ ├── PhasorPlot.png │ ├── PowerTriangle.png │ ├── ReceivingEndPowerCircleExample.png │ ├── ReceivingPowerCircleExample.png │ ├── SuspensionInuslator.png │ ├── SuspensionInuslator.svg │ ├── Thumbs.db │ ├── WheatstoneBridgeCircuit.png │ ├── WheatstoneBridgeCircuit.svg │ ├── convbar-example.png │ ├── inductive-voltage-divider-circuit.png │ ├── mbuspowerflow_example.png │ ├── pi-attenuator-circuit.png │ ├── synmach_ifault_formula.png │ ├── t-attenuator-circuit.png │ └── zenerdiode.png ├── electricpy ├── __init__.py ├── active │ └── __init__.py ├── bode.py ├── compute.py ├── constants.py ├── conversions.py ├── fault.py ├── geometry │ ├── __init__.py │ ├── circle.py │ └── triangle.py ├── latex.py ├── machines.py ├── math.py ├── passive.py ├── phasors.py ├── sim.py ├── thermal.py ├── version.py └── visu.py ├── logo ├── ElectricpyBanner.png ├── ElectricpyBanner.pub ├── ElectricpyLogo.png ├── ElectricpyLogo.pub ├── ElectricpyLogo.svg ├── ElectricpyLogo_raw.png ├── Thumbs.db └── tecnico.zip ├── pyproject.toml ├── release-version.py ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── requirements.txt ├── test_circle.py ├── test_convertion.py ├── test_electricpy.py ├── test_geometry.py ├── test_imports.py ├── test_phasor.py ├── test_triangle.py └── test_visu.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | # Regexes for lines to exclude from consideration 3 | exclude_lines = 4 | 5 | # Don't complain if tests don't hit defensive assertion code: 6 | raise AssertionError 7 | raise NotImplementedError 8 | raise ValueError 9 | -------------------------------------------------------------------------------- /.gitchangelog.rc: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | ## 3 | ## Format 4 | ## 5 | ## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] 6 | ## 7 | ## Description 8 | ## 9 | ## ACTION is one of 'chg', 'fix', 'new' 10 | ## 11 | ## Is WHAT the change is about. 12 | ## 13 | ## 'chg' is for refactor, small improvement, cosmetic changes... 14 | ## 'fix' is for bug fixes 15 | ## 'new' is for new features, big improvement 16 | ## 17 | ## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' 18 | ## 19 | ## Is WHO is concerned by the change. 20 | ## 21 | ## 'dev' is for developpers (API changes, refactors...) 22 | ## 'usr' is for final users (UI changes) 23 | ## 'pkg' is for packagers (packaging changes) 24 | ## 'test' is for testers (test only related changes) 25 | ## 'doc' is for doc guys (doc only changes) 26 | ## 27 | ## COMMIT_MSG is ... well ... the commit message itself. 28 | ## 29 | ## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' 30 | ## 31 | ## They are preceded with a '!' or a '@' (prefer the former, as the 32 | ## latter is wrongly interpreted in github.) Commonly used tags are: 33 | ## 34 | ## 'refactor' is obviously for refactoring code only 35 | ## 'minor' is for a very meaningless change (a typo, adding a comment) 36 | ## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) 37 | ## 'wip' is for partial functionality but complete subfunctionality. 38 | ## 39 | ## Example: 40 | ## 41 | ## new: usr: support of bazaar implemented 42 | ## chg: re-indentend some lines !cosmetic 43 | ## new: dev: updated code to be compatible with last version of killer lib. 44 | ## fix: pkg: updated year of licence coverage. 45 | ## new: test: added a bunch of test around user usability of feature X. 46 | ## fix: typo in spelling my name in comment. !minor 47 | ## 48 | ## Please note that multi-line commit message are supported, and only the 49 | ## first line will be considered as the "summary" of the commit message. So 50 | ## tags, and other rules only applies to the summary. The body of the commit 51 | ## message will be displayed in the changelog without reformatting. 52 | def writefile(lines): 53 | # Develop Full String of Log 54 | log = '' 55 | for line in lines: 56 | log += line 57 | import re 58 | log = re.sub(r'\[\w{1,20}\n? {0,8}\w{1,20}\]','', log) 59 | print(log) 60 | with open('source/changelog.rst','w') as changelog: 61 | changelog.write(log) 62 | print("Update CHANGELOG.rst Complete.") 63 | 64 | ## 65 | ## ``ignore_regexps`` is a line of regexps 66 | ## 67 | ## Any commit having its full commit message matching any regexp listed here 68 | ## will be ignored and won't be reported in the changelog. 69 | ## 70 | ignore_regexps = [ 71 | r'\(private\)', 72 | r'@minor', r'!minor', 73 | r'@cosmetic', r'!cosmetic', 74 | r'@refactor', r'!refactor', 75 | r'@wip', r'!wip', 76 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', 77 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', 78 | r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', 79 | r'^$', ## ignore commits with empty messages 80 | ] 81 | 82 | 83 | ## ``section_regexps`` is a list of 2-tuples associating a string label and a 84 | ## list of regexp 85 | ## 86 | ## Commit messages will be classified in sections thanks to this. Section 87 | ## titles are the label, and a commit is classified under this section if any 88 | ## of the regexps associated is matching. 89 | ## 90 | ## Please note that ``section_regexps`` will only classify commits and won't 91 | ## make any changes to the contents. So you'll probably want to go check 92 | ## ``subject_process`` (or ``body_process``) to do some changes to the subject, 93 | ## whenever you are tweaking this variable. 94 | ## 95 | section_regexps = [ 96 | ('New', [ 97 | r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 98 | ]), 99 | ('Changes', [ 100 | r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 101 | ]), 102 | ('Fix', [ 103 | r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 104 | ]), 105 | 106 | ('Other', None ## Match all lines 107 | ), 108 | 109 | ] 110 | 111 | 112 | ## ``body_process`` is a callable 113 | ## 114 | ## This callable will be given the original body and result will 115 | ## be used in the changelog. 116 | ## 117 | ## Available constructs are: 118 | ## 119 | ## - any python callable that take one txt argument and return txt argument. 120 | ## 121 | ## - ReSub(pattern, replacement): will apply regexp substitution. 122 | ## 123 | ## - Indent(chars=" "): will indent the text with the prefix 124 | ## Please remember that template engines gets also to modify the text and 125 | ## will usually indent themselves the text if needed. 126 | ## 127 | ## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns 128 | ## 129 | ## - noop: do nothing 130 | ## 131 | ## - ucfirst: ensure the first letter is uppercase. 132 | ## (usually used in the ``subject_process`` pipeline) 133 | ## 134 | ## - final_dot: ensure text finishes with a dot 135 | ## (usually used in the ``subject_process`` pipeline) 136 | ## 137 | ## - strip: remove any spaces before or after the content of the string 138 | ## 139 | ## - SetIfEmpty(msg="No commit message."): will set the text to 140 | ## whatever given ``msg`` if the current text is empty. 141 | ## 142 | ## Additionally, you can `pipe` the provided filters, for instance: 143 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") 144 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') 145 | #body_process = noop 146 | body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip 147 | 148 | 149 | ## ``subject_process`` is a callable 150 | ## 151 | ## This callable will be given the original subject and result will 152 | ## be used in the changelog. 153 | ## 154 | ## Available constructs are those listed in ``body_process`` doc. 155 | subject_process = (strip | 156 | ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | 157 | SetIfEmpty("No commit message.") | ucfirst | final_dot) 158 | 159 | 160 | ## ``tag_filter_regexp`` is a regexp 161 | ## 162 | ## Tags that will be used for the changelog must match this regexp. 163 | ## 164 | tag_filter_regexp = r'^[0-9]+(\.[0-9]+)?$' 165 | 166 | 167 | ## ``unreleased_version_label`` is a string or a callable that outputs a string 168 | ## 169 | ## This label will be used as the changelog Title of the last set of changes 170 | ## between last valid tag and HEAD if any. 171 | unreleased_version_label = "(unreleased)" 172 | 173 | 174 | ## ``output_engine`` is a callable 175 | ## 176 | ## This will change the output format of the generated changelog file 177 | ## 178 | ## Available choices are: 179 | ## 180 | ## - rest_py 181 | ## 182 | ## Legacy pure python engine, outputs ReSTructured text. 183 | ## This is the default. 184 | ## 185 | ## - mustache() 186 | ## 187 | ## Template name could be any of the available templates in 188 | ## ``templates/mustache/*.tpl``. 189 | ## Requires python package ``pystache``. 190 | ## Examples: 191 | ## - mustache("markdown") 192 | ## - mustache("restructuredtext") 193 | ## 194 | ## - makotemplate() 195 | ## 196 | ## Template name could be any of the available templates in 197 | ## ``templates/mako/*.tpl``. 198 | ## Requires python package ``mako``. 199 | ## Examples: 200 | ## - makotemplate("restructuredtext") 201 | ## 202 | output_engine = rest_py 203 | #output_engine = mustache("restructuredtext") 204 | #output_engine = mustache("markdown") 205 | #output_engine = makotemplate("restructuredtext") 206 | 207 | 208 | ## ``include_merge`` is a boolean 209 | ## 210 | ## This option tells git-log whether to include merge commits in the log. 211 | ## The default is to include them. 212 | include_merge = True 213 | 214 | 215 | ## ``log_encoding`` is a string identifier 216 | ## 217 | ## This option tells gitchangelog what encoding is outputed by ``git log``. 218 | ## The default is to be clever about it: it checks ``git config`` for 219 | ## ``i18n.logOutputEncoding``, and if not found will default to git's own 220 | ## default: ``utf-8``. 221 | #log_encoding = 'utf-8' 222 | 223 | 224 | ## ``publish`` is a callable 225 | ## 226 | ## Sets what ``gitchangelog`` should do with the output generated by 227 | ## the output engine. ``publish`` is a callable taking one argument 228 | ## that is an interator on lines from the output engine. 229 | ## 230 | ## Some helper callable are provided: 231 | ## 232 | ## Available choices are: 233 | ## 234 | ## - stdout 235 | ## 236 | ## Outputs directly to standard output 237 | ## (This is the default) 238 | ## 239 | ## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start()) 240 | ## 241 | ## Creates a callable that will parse given file for the given 242 | ## regex pattern and will insert the output in the file. 243 | ## ``idx`` is a callable that receive the matching object and 244 | ## must return a integer index point where to insert the 245 | ## the output in the file. Default is to return the position of 246 | ## the start of the matched string. 247 | ## 248 | ## - FileRegexSubst(file, pattern, replace, flags) 249 | ## 250 | ## Apply a replace inplace in the given file. Your regex pattern must 251 | ## take care of everything and might be more complex. Check the README 252 | ## for a complete copy-pastable example. 253 | ## 254 | publish = writefile 255 | #publish = stdout 256 | 257 | 258 | ## ``revs`` is a list of callable or a list of string 259 | ## 260 | ## callable will be called to resolve as strings and allow dynamical 261 | ## computation of these. The result will be used as revisions for 262 | ## gitchangelog (as if directly stated on the command line). This allows 263 | ## to filter exaclty which commits will be read by gitchangelog. 264 | ## 265 | ## To get a full documentation on the format of these strings, please 266 | ## refer to the ``git rev-list`` arguments. There are many examples. 267 | ## 268 | ## Using callables is especially useful, for instance, if you 269 | ## are using gitchangelog to generate incrementally your changelog. 270 | ## 271 | ## Some helpers are provided, you can use them:: 272 | ## 273 | ## - FileFirstRegexMatch(file, pattern): will return a callable that will 274 | ## return the first string match for the given pattern in the given file. 275 | ## If you use named sub-patterns in your regex pattern, it'll output only 276 | ## the string matching the regex pattern named "rev". 277 | ## 278 | ## - Caret(rev): will return the rev prefixed by a "^", which is a 279 | ## way to remove the given revision and all its ancestor. 280 | ## 281 | ## Please note that if you provide a rev-list on the command line, it'll 282 | ## replace this value (which will then be ignored). 283 | ## 284 | ## If empty, then ``gitchangelog`` will act as it had to generate a full 285 | ## changelog. 286 | ## 287 | ## The default is to use all commits to make the changelog. 288 | #revs = ["^1.0.3", ] 289 | #revs = [ 290 | # Caret( 291 | # FileFirstRegexMatch( 292 | # "CHANGELOG.rst", 293 | # r"(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")), 294 | # "HEAD" 295 | #] 296 | revs = [] 297 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a problem or unexpected behavior in ElectricPy or its Documentation 4 | title: '' 5 | labels: bug 6 | assignees: engineerjoe440 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Reproduction Code** 14 | ```python 15 | # Setup 16 | import electricpy as ep 17 | 18 | # Code which causes failure 19 | ep.do_something_broken(breaking_input) 20 | ``` 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Desktop (please complete the following information):** 29 | - OS: [e.g. Linux] 30 | - Python Version [e.g. 3.7] 31 | - Version [e.g. 0.2.1] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for ElectricPy 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Link to Formulas and Example References** 17 | * [link 1](https://engineerjoe440.github.io/ElectricPy/) 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/pipaudit.yml: -------------------------------------------------------------------------------- 1 | name: pip-audit 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | selftest: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: install 11 | run: python -m pip install . 12 | - uses: pypa/gh-action-pip-audit@v1.0.8 13 | with: 14 | # NOTE: this can be `.`, for the current directory 15 | inputs: . -------------------------------------------------------------------------------- /.github/workflows/pydocstyle.yml: -------------------------------------------------------------------------------- 1 | name: pydocstyle 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ["3.11"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python 3.11 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: "3.11" 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install pydocstyle 23 | pip install . 24 | python3 -c "import electricpy; print('electricpy.__file__')" 25 | - name: Test NumpyDoc Style 26 | run: | 27 | cd electricpy 28 | pydocstyle --convention=numpy -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: pytest 2 | 3 | on: 4 | push: 5 | path: 6 | - '**.py' 7 | pull_request: 8 | path: 9 | - '**.py' 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: ["3.8", "3.9", "3.10", "3.11"] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v3 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | if [ -f test/requirements.txt ]; then pip install -r test/requirements.txt; fi 29 | pip install . 30 | python3 -c "import electricpy; print('electricpy.__file__')" 31 | - name: Test with pytest 32 | run: | 33 | pytest --xdoctest 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'electricpy/version.py' 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | # https://github.com/marketplace/actions/setup-python 16 | # ^-- This gives info on matrix testing. 17 | - name: Install Python 18 | uses: actions/setup-python@v3 19 | with: 20 | python-version: "3.11" 21 | - name: Identify Version 22 | id: version 23 | run: | 24 | python -m pip install requests build --user 25 | python -m pip install -r requirements.txt --user 26 | output=$(python release-version.py) 27 | echo "::set-output name=version::$output" 28 | - name: Build Artifacts 29 | if: success() 30 | id: build 31 | run: | 32 | python -m build --sdist --wheel --outdir dist/ 33 | - name: Create Release 34 | uses: ncipollo/release-action@v1 35 | with: 36 | tag: ${{ steps.version.outputs.version }} 37 | name: Release ${{ steps.version.outputs.version }} 38 | body: ${{ steps.tag_version.outputs.changelog }} 39 | artifacts: "dist/*" 40 | - name: Publish distribution 📦 to PyPI 41 | if: success() 42 | uses: pypa/gh-action-pypi-publish@master 43 | with: 44 | password: ${{ secrets.PYPI_API_TOKEN }} 45 | -------------------------------------------------------------------------------- /.github/workflows/sphinx.yml: -------------------------------------------------------------------------------- 1 | # Syntax reference for this file: 2 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 3 | 4 | name: sphinx 5 | on: [push] 6 | 7 | # https://gist.github.com/c-bata/ed5e7b7f8015502ee5092a3e77937c99 8 | jobs: 9 | build-and-delpoy: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | # https://github.com/marketplace/actions/checkout 14 | - uses: actions/checkout@v3 15 | # https://github.com/marketplace/actions/setup-python 16 | # ^-- This gives info on matrix testing. 17 | - name: Install Python 18 | uses: actions/setup-python@v3 19 | with: 20 | python-version: "3.11" 21 | # I don't know where the "run" thing is documented. 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | cp logo/ElectricpyLogo.svg docsource/static/ElectricpyLogo.svg 26 | pip install -r docsource/requirements.txt 27 | - name: Build Sphinx docs 28 | if: success() 29 | run: | 30 | pip install . 31 | python3 -c "import electricpy; print('electricpy.__file__')" 32 | sphinx-build -M html docsource docs 33 | - name: Generate Coverage Badge 34 | if: success() 35 | run: | 36 | pip install -r test/requirements.txt 37 | coverage run --source=./electricpy -m pytest 38 | coverage-badge -o docs/html/coverage.svg 39 | 40 | # https://github.com/marketplace/actions/github-pages 41 | #- if: success() 42 | # uses: crazy-max/ghaction-github-pages@master 43 | # env: 44 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | # with: 46 | # target_branch: gh-pages 47 | # build_dir: _build/html/ 48 | 49 | # https://github.com/peaceiris/actions-gh-pages 50 | - name: Deploy 51 | if: success() 52 | uses: peaceiris/actions-gh-pages@v3 53 | with: 54 | publish_branch: gh-pages 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | publish_dir: docs/html/ 57 | 58 | 59 | # This action probably does everything for you: 60 | # https://github.com/marketplace/actions/sphinx-build 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built Documentation Reference Pages 2 | docs/ 3 | docsource/api/ 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the docs/ directory with Sphinx 19 | sphinx: 20 | configuration: docsource/conf.py 21 | 22 | # If using Sphinx, optionally build your docs in additional formats such as PDF 23 | # formats: 24 | # - pdf 25 | 26 | # Optionally declare the Python requirements required to build your docs 27 | python: 28 | install: 29 | - requirements: docsource/requirements.txt -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [ 3 | 80, 120 4 | ], 5 | "python.testing.pytestArgs": [ 6 | "-m", 7 | "test" 8 | ], 9 | "files.exclude": { 10 | ".venv":true, 11 | ".pytest_cache": true, 12 | "**/__pycache__/**":true 13 | }, 14 | "python.testing.unittestEnabled": false, 15 | "python.testing.pytestEnabled": true, 16 | "python.testing.cwd": "${workspaceRoot}" 17 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Pytest", 6 | "type": "shell", 7 | "command": "python -m pytest test", 8 | "problemMatcher": [], 9 | "group": { 10 | "kind": "test", 11 | "isDefault": true 12 | }, 13 | "presentation": { 14 | "reveal": "always", 15 | "panel": "new" 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines for ElectricPy 2 | *ElectricPy - The Electrical Engineer's Python Toolkit* 3 | 4 | We'd *gladly* accept contributions that add functions, enhance documentation, 5 | improve testing practices, or better round out this project in general; but to 6 | help move things along more quickly, here are a few things to keep in mind. 7 | 8 | ### Adding New Functions 9 | 10 | When adding additional functions to the package, we'd love to maintain 11 | consistency wherever possible, a few of these things to keep in mind include: 12 | 13 | **Documentation:** *(a must!)* 14 | * Format function docstrings according to 15 | [NumPyDoc](https://numpydoc.readthedocs.io/en/latest/format.html) standards 16 | * Use the very first line of docstrings to give a brief (one-line) description 17 | of what the function is used for 18 | * Whenever possible, use LaTeX `.. math::` blocks to help document the formula(s) 19 | used in the function; as a general rule, any time there's more to a function than 20 | a simple addition/multiplication, it's best to show the formula. 21 | * If diagrams or images would make the documentation more clear, their inclusion 22 | would be *greatly* appreciated 23 | 24 | **Code:** 25 | * When possible, use other core functions already in ElectricPy to build upon 26 | * When multiple voltages, currents, or other similar quantities need to be used 27 | in the same function, their variables should be uniquely named so to help 28 | clarify their purpose. So, instead of using `V1` and `V2`, use descriptive names 29 | such as `Vgenerator` and `Vline`. 30 | * Whenever possible, comments should be added in the code to help clarify what 31 | operations are being performed 32 | 33 | 34 | ### Adding new Tests/Test Routines 35 | 36 | When adding additional test functions and routines, they should be added using 37 | the `pytest` framework. 38 | 39 | * Tests should be added under the `test` directory in the repository 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Joe Stanley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | recursive-exclude tests * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | logo 3 | 4 | 5 | # ElectricPy 6 | 7 | *Electrical-Engineering-for-Python* 8 | 9 | [![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) 10 | [![Documentation Status](https://readthedocs.org/projects/electricpy/badge/?version=latest)](https://electricpy.readthedocs.io/en/latest/?badge=latest) 11 | ![Tox Import Test](https://github.com/engineerjoe440/ElectricPy/workflows/Tox%20Tests/badge.svg) 12 | 13 | [![pytest](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pytest.yml) 14 | [![pydocstyle](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pydocstyle.yml/badge.svg?branch=master)](https://github.com/engineerjoe440/ElectricPy/actions/workflows/pydocstyle.yml) 15 | ![Coverage](https://raw.githubusercontent.com/engineerjoe440/ElectricPy/gh-pages/coverage.svg) 16 | 17 | [![](https://img.shields.io/pypi/v/electricpy.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/electricpy/) 18 | [![](https://pepy.tech/badge/electricpy)](https://pepy.tech/project/electricpy) 19 | [![](https://img.shields.io/github/stars/engineerjoe440/electricpy?logo=github)](https://github.com/engineerjoe440/electricpy/) 20 | [![](https://img.shields.io/pypi/l/electricpy.svg?color=blue)](https://github.com/engineerjoe440/electricpy/blob/master/LICENSE.txt) 21 | 22 | [![Matrix](https://img.shields.io/matrix/electricpy:stanleysolutionsn.com?label=%23electricpy:stanleysolutionsnw.com&logo=matrix&server_fqdn=matrix.stanleysolutionsnw.com&style=for-the-badge)](https://matrix.to/#/#electricpy:stanleysolutionsnw.com) 23 | 24 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/engineerjoe440) 25 | 26 | 27 | Python Libraries with functions and constants related to electrical engineering. 28 | 29 | The functions and constants that make up these modules represent a library of 30 | material compiled with the intent of being used primarily for research, 31 | development, education, and exploration in the realm of electrical engineering. 32 | 33 | Check out our full documentation: https://electricpy.readthedocs.io/en/latest/ 34 | 35 | Antu dialog-warning **Documentation has recently been updated to use [ReadTheDocs](https://readthedocs.org/)** 36 | 37 | GitHub Pages are still active, and will continue to be for the forseeable 38 | future, but they're intended for developmental updates rather than primary 39 | documentation. 40 | 41 | ## Features 42 | 43 | * Extensive set of common functions and formulas for electrical engineering and 44 | electronics. 45 | * Support for LaTeX math generation (use this in conjunction with your Jupyter 46 | notebooks!) 47 | * Generate focussed and simple plots, diagrams, and figures. 48 | 49 | ### Samples Generated with ElectricPy 50 | 51 | | Phasor Plot | Power Triangle | Induction Motor Circle | 52 | |-------------|----------------|------------------------| 53 | | ![](https://raw.githubusercontent.com/engineerjoe440/ElectricPy/gh-pages/_images/PhasorPlot.png) | ![](https://raw.githubusercontent.com/engineerjoe440/ElectricPy/gh-pages/_images/PowerTriangle.png) | ![](https://raw.githubusercontent.com/engineerjoe440/ElectricPy/gh-pages/_images/InductionMotorCircleExample.png) | 54 | 55 | 56 | | RLC Frequency Response | | Receiving Power Circle | 57 | |------------------------|----------------|------------------------| 58 | | ![](https://raw.githubusercontent.com/engineerjoe440/ElectricPy/gh-pages/_images/series-rlc-r5-l0.4.png) | | ![](https://raw.githubusercontent.com/engineerjoe440/ElectricPy/gh-pages/_images/ReceivingPowerCircleExample.png) | 59 | 60 | ## Installing / Getting Started 61 | 62 | 1. ElectricPy has a few basic installation options for use with `pip`. For most 63 | common users, use the following command to install ElectricPy with `pip` 64 | 65 | ``` 66 | pip install electricpy[full] 67 | ``` 68 | 69 | 2. Check installation success in Python environment: 70 | 71 | ```python 72 | import electricpy 73 | electricpy._version_ 74 | ``` 75 | 76 | 3. Start using the electrical engineering formulas 77 | 78 | ```python 79 | >>> import electricpy as ep 80 | >>> voltage = ep.phasor(67, 120) # 67 volts at angle 120 degrees 81 | >>> voltage 82 | (-33.499999999999986+58.02370205355739j) 83 | >>> ep.cprint(voltage) 84 | 67.0 ∠ 120.0° 85 | ``` 86 | 87 | ### Installing from Source 88 | 89 | If you're looking to get the "latest and greatest" from electricpy, you'll want 90 | to install directly from GitHub, you can do that one of two ways, the easiest of 91 | which is to simply issue the following command for `pip` 92 | 93 | ``` 94 | pip install git+https://github.com/engineerjoe440/ElectricPy.git 95 | ``` 96 | 97 | Alternatively, you can do it the "old fashioned way" by cloning the repository 98 | and installing locally. 99 | 100 | 1. Clone/Download Source Code from [GitHub Repository](https://github.com/engineerjoe440/ElectricPy) 101 | 2. Open Terminal and Navigate to Folder with `cd` Commands: 102 | - `cd \electricpy` 103 | 3. Use Python to Install Module from `setup.py`: 104 | - `pip install .` 105 | 106 | ### Dependencies 107 | 108 | - [NumPy](https://numpy.org/) 109 | - [matplotlib](https://matplotlib.org/) 110 | - [SciPy](https://scipy.org/) 111 | - [SymPy](https://www.sympy.org/en/index.html) 112 | 113 | #### Optional Dependencies 114 | 115 | For numerical analysis (install with `pip install electricpy[numerical]`): 116 | 117 | - [numdifftools](https://numdifftools.readthedocs.io/en/latest/) 118 | 119 | For fault analysis (install with `pip install electricpy[fault]`) 120 | 121 | - [arcflash](https://github.com/LiaungYip/arcflash) 122 | 123 | 124 | ## Get Involved / Contribute 125 | 126 | If you're interested in contributing, we'd love to see your support in a number 127 | of ways! 128 | 129 | 1. **Write Tests** - We're really lacking in this area. We've recently added 130 | simple GitHub actions to test installation, but that's about it. We hope that 131 | someday we can test all functions in this module for verification. 132 | 2. **Contribute New Electrical Engineering Functions** - If you've got a new 133 | function related to electrical engineering that you'd like to see added, we'd 134 | love to throw it into this module. Our goal is that this module can become the 135 | comprehensive electrical engineering toolkit in Python. Drop us a note, or 136 | create a [pull request](https://github.com/engineerjoe440/ElectricPy/pulls)! 137 | 3. **Report Issues** - We don't want issues to go unnoticed. Please help us 138 | track bugs in [our issues](https://github.com/engineerjoe440/ElectricPy/issues) 139 | and resolve them! 140 | 4. **Get the Word Out** - This project is still in its infancy, so please share 141 | it with your friends and colleagues. We want to make sure that everyone has the 142 | opportunity to take advantage of this project. 143 | 144 | **Check out the [contribution guide](https://github.com/engineerjoe440/ElectricPy/blob/master/CONTRIBUTING.md)** 145 | 146 | **Come [chat about ElectricPy](https://matrix.to/#/#electricpy:stanleysolutionsnw.com)** 147 | 148 | ### Special thanks to... 149 | 150 | - Stephen Weeks | Student - U of Idaho 151 | - Jeremy Perhac | Student - U of Idaho 152 | - Daniel Allen | Student - Universtiy of Idaho 153 | - Dr. Dennis Sullivan | Proffessor - U of Idaho 154 | - Dr. Brian Johnson | Proffessor - U of Idaho 155 | - Dr. Joe Law | Proffessor - U of Idaho 156 | - StackOverflow user gg349 157 | - Shaurya Uppal | Online Code Contributor 158 | - Paul Ortman | Power Quality Engineer - Idaho Power | Instructor - U of Idaho 159 | 160 | *and* 161 | 162 | 163 | contributors 164 | 165 | 166 | ## Contact 167 | 168 | For more information regarding this resource, please contact Joe Stanley 169 | 170 | - 171 | 172 | ## License and Usage 173 | 174 | ElectricPy is licensed under the standard MIT license, and as such, you are 175 | permitted to use this resource as you see fit. Please feel free to ask 176 | questions, suggest edits and report bugs or other issues. 177 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "ignorePaths": [], 4 | "dictionaryDefinitions": [], 5 | "dictionaries": [], 6 | "words": [ 7 | "electricpy", 8 | "epmath", 9 | "fsolve", 10 | "matplotlib", 11 | "parallelz", 12 | "phasor", 13 | "phasorlist", 14 | "phasorplot", 15 | "phasors", 16 | "powerset", 17 | "pyplot", 18 | "scipy", 19 | "visu" 20 | ], 21 | "ignoreWords": [], 22 | "import": [] 23 | } 24 | -------------------------------------------------------------------------------- /docsource/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/Thumbs.db -------------------------------------------------------------------------------- /docsource/_templates/base.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | 4 | 5 | .. auto{{ objtype }}:: {{ objname }} -------------------------------------------------------------------------------- /docsource/_templates/module.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline }} 2 | 3 | .. rubric:: Description 4 | 5 | .. automodule:: {{ fullname }} 6 | 7 | .. currentmodule:: {{ fullname }} 8 | 9 | 10 | 11 | {% if classes %} 12 | .. rubric:: Classes 13 | 14 | .. autosummary:: 15 | :toctree: . 16 | {% for class in classes %} 17 | {{ class }} 18 | {% endfor %} 19 | 20 | {% endif %} 21 | 22 | 23 | 24 | {% if functions %} 25 | .. rubric:: Functions 26 | 27 | .. autosummary:: 28 | :toctree: . 29 | {% for function in functions %} 30 | {{ function }} 31 | {% endfor %} 32 | 33 | {% endif %} -------------------------------------------------------------------------------- /docsource/additionalresources.rst: -------------------------------------------------------------------------------- 1 | Additional Resources 2 | ================================================================================ 3 | 4 | 5 | Generic and Data Science 6 | ------------------------ 7 | 8 | * NumPy: https://numpy.org/ 9 | 10 | * SciPy: https://scipy.org/ 11 | 12 | * Matplotlib: https://matplotlib.org/ 13 | 14 | * SymPy: https://www.sympy.org/en/index.html 15 | 16 | * Pyomo: https://www.pyomo.org/ 17 | 18 | * Pint: https://pint.readthedocs.io/en/stable/ 19 | 20 | * numdifftools: https://numdifftools.readthedocs.io/en/latest/ 21 | 22 | Electrical Engineering Focus 23 | ---------------------------- 24 | 25 | * Python COMTRADE File Interpreter: https://github.com/dparrini/python-comtrade 26 | 27 | * Python COMTRADE Writer: https://github.com/relihanl/comtradehandlers 28 | 29 | * Arc Flash Calculator: https://github.com/LiaungYip/arcflash 30 | 31 | * PandaPower: https://www.pandapower.org/start/ 32 | 33 | * PyPSA: https://github.com/PyPSA/PyPSA 34 | 35 | * PyPower (no longer supported): https://pypi.org/project/PYPOWER/ 36 | 37 | * minpower: http://adamgreenhall.github.io/minpower/index.html 38 | 39 | * oemof (Open Energy MOdeling Framework): https://oemof.org/ 40 | 41 | * PowerGAMA: https://bitbucket.org/harald_g_svendsen/powergama/wiki/Home 42 | -------------------------------------------------------------------------------- /docsource/changes.rst: -------------------------------------------------------------------------------- 1 | Recent Changes 2 | ================================================================================ 3 | 4 | .. git_changelog:: 5 | -------------------------------------------------------------------------------- /docsource/conf.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """Configuration file for the Sphinx documentation builder.""" 3 | ################################################################################ 4 | 5 | import os 6 | import re 7 | import sys 8 | 9 | print("Build with:", sys.version) 10 | parent_dir = os.path.dirname(os.getcwd()) 11 | initfile = os.path.join(parent_dir, 'electricpy', 'version.py') 12 | sys.path.insert(0, parent_dir) 13 | sys.path.insert(1, os.path.dirname(os.path.abspath(__file__))) 14 | print(parent_dir) 15 | 16 | # Generate all Documentation Images 17 | from render_images import main as render_images 18 | render_images() 19 | 20 | # Gather Version Information from Python File 21 | with open(initfile) as fh: 22 | file_str = fh.read() 23 | name = re.search('NAME = \"(.*)\"', file_str).group(1) 24 | ver = re.search('VERSION = \"(.*)\"', file_str).group(1) 25 | # Version Breakdown: 26 | # MAJOR CHANGE . MINOR CHANGE . MICRO CHANGE 27 | print("Sphinx HTML Build For:", name," Version:", ver) 28 | 29 | 30 | # Verify Import 31 | try: 32 | import electricpy 33 | except: 34 | print("Couldn't import `electricpy` module!") 35 | sys.exit(9) 36 | 37 | 38 | # -- Project information ----------------------------------------------------- 39 | 40 | project = 'electricpy' 41 | copyright = '2022, Joe Stanley' 42 | author = 'Joe Stanley' 43 | 44 | # The full version, including alpha/beta/rc tags 45 | release = ver 46 | 47 | 48 | # -- General configuration --------------------------------------------------- 49 | 50 | # Add any Sphinx extension module names here, as strings. They can be 51 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 52 | # ones. 53 | extensions = [ 54 | 'sphinx.ext.autodoc', 55 | 'sphinx.ext.napoleon', 56 | 'sphinx.ext.mathjax', 57 | 'sphinx.ext.autosummary', 58 | 'sphinx.ext.viewcode', 59 | 'numpydoc', 60 | 'sphinx_git', 61 | 'myst_parser', 62 | 'sphinx_immaterial', 63 | ] 64 | autosummary_generate = True 65 | numpydoc_show_class_members = False 66 | 67 | myst_enable_extensions = [ 68 | "amsmath", 69 | "colon_fence", 70 | "deflist", 71 | "dollarmath", 72 | "fieldlist", 73 | "html_admonition", 74 | "html_image", 75 | "linkify", 76 | "replacements", 77 | "smartquotes", 78 | "strikethrough", 79 | "substitution", 80 | "tasklist", 81 | ] 82 | 83 | # List of patterns, relative to source directory, that match files and 84 | # directories to ignore when looking for source files. 85 | # This pattern also affects html_static_path and html_extra_path. 86 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', "requirements.txt"] 87 | 88 | templates_path = ["_templates"] 89 | 90 | source_suffix = { 91 | '.rst': 'restructuredtext', 92 | '.txt': 'markdown', 93 | '.md': 'markdown', 94 | } 95 | 96 | # -- Options for HTML output ------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. See the documentation for 99 | # a list of builtin themes. 100 | # 101 | html_theme = 'sphinx_immaterial' 102 | 103 | # Add any paths that contain custom static files (such as style sheets) here, 104 | # relative to this directory. They are copied after the builtin static files, 105 | # so a file named "default.css" will overwrite the builtin "default.css". 106 | html_static_path = ['static'] 107 | html_extra_path = ['extra'] 108 | html_logo="static/ElectricpyLogo.svg" 109 | html_favicon="static/ElectricpyLogo.svg" 110 | html_sidebars = { 111 | "**": ["logo-text.html", "globaltoc.html", "searchbox.html"] 112 | } 113 | 114 | # github_repo = "electricpy" 115 | # github_user = "engineerjoe440" 116 | # github_button = True 117 | 118 | # Material theme options (see theme.conf for more information) 119 | html_theme_options = { 120 | 121 | # Specify a base_url used to generate sitemap.xml. If not 122 | # specified, then no sitemap will be built. 123 | 'site_url': 'https://electricpy.readthedocs.io/en/latest/', 124 | 125 | # Set the color and the accent color 126 | "palette": [ 127 | { 128 | "primary": "light-blue", 129 | "accent": "blue", 130 | "media": "(prefers-color-scheme: light)", 131 | "scheme": "default", 132 | "toggle": { 133 | "icon": "material/toggle-switch-off-outline", 134 | "name": "Switch to dark mode", 135 | } 136 | }, 137 | { 138 | "primary": "blue", 139 | "accent": "light-blue", 140 | "media": "(prefers-color-scheme: dark)", 141 | "scheme": "slate", 142 | "toggle": { 143 | "icon": "material/toggle-switch", 144 | "name": "Switch to light mode", 145 | } 146 | }, 147 | ], 148 | 149 | # Set the repo location to get a badge with stats 150 | 'repo_url': 'https://github.com/engineerjoe440/ElectricPy/', 151 | 'repo_name': 'ElectricPy', 152 | 153 | "icon": { 154 | "repo": "fontawesome/brands/github", 155 | "logo": "material/library", 156 | }, 157 | } 158 | -------------------------------------------------------------------------------- /docsource/constants.rst: -------------------------------------------------------------------------------- 1 | Constants 2 | ================================================================================ 3 | 4 | .. _constants.py: 5 | 6 | In addition to the variety of funcitons provided, several common, and useful, 7 | constants are provided to simplify arithmetic. 8 | 9 | ============== ================================================================= 10 | Name Value 11 | ============== ================================================================= 12 | `pi` π (derived from numpy.pi) 13 | `a` :math:`1\angle{120}` 14 | `p` 10^-12 (pico) 15 | `n` 10^-9 (nano) 16 | `u` 10^-6 (micro) 17 | `m` 10^-3 (mili) 18 | `k` 10^3 (kila) 19 | `M` 10^6 (mega) 20 | `G` 10^9 (giga) 21 | `u0` :math:`µ0` (mu-not) 4πE-7 22 | `e0` :math:`ε0` (epsilon-not) 8.854E-12 23 | `carson_r` 9.869e-7 (Carson's Ristance Constant) 24 | `WATTS_PER_HP` 745.699872 25 | `KWH_PER_BTU` 3412.14 26 | ============== ================================================================= -------------------------------------------------------------------------------- /docsource/electricpyapi.rst: -------------------------------------------------------------------------------- 1 | ElectricPy API 2 | ================================================================================ 3 | 4 | .. _electricpyapi.py: 5 | 6 | Python functions and constants related to electrical engineering. 7 | 8 | The functions and constants that make up these modules represent a library of 9 | material compiled with the intent of being used primarily for research, 10 | development, education, and exploration in the realm of electrical engineering. 11 | 12 | The base module for the `electricpy` package, electricpy.py may be leveraged 13 | in any Python script or program by using the *import* command similar to that 14 | shown below. 15 | 16 | >>> import electricpy as ep 17 | 18 | Filled with calculators, evaluators, and plotting functions, this package will 19 | provide a wide array of capabilities to any electrical engineer. 20 | 21 | Built to support operations similar to Numpy and Scipy, this package is designed 22 | to aid in scientific calculations. 23 | 24 | .. rubric:: Modules 25 | 26 | .. autosummary:: 27 | :recursive: 28 | :toctree: api 29 | :template: module.rst 30 | 31 | electricpy 32 | electricpy.bode 33 | electricpy.conversions 34 | electricpy.fault 35 | electricpy.latex 36 | electricpy.math 37 | electricpy.passive 38 | electricpy.phasor 39 | electricpy.sim 40 | electricpy.visu 41 | -------------------------------------------------------------------------------- /docsource/extra/google307ea14d75106813.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google307ea14d75106813.html -------------------------------------------------------------------------------- /docsource/index.rst: -------------------------------------------------------------------------------- 1 | .. _electricpy_home: 2 | 3 | ========== 4 | ElectricPy 5 | ========== 6 | 7 | 8 | *Electrical-Engineering-for-Python* 9 | 10 | Python functions and constants related to electrical engineering. 11 | 12 | The functions and constants that make up these modules represent a library of 13 | material compiled with the intent of being used primarily for research, 14 | development, education, and exploration in the realm of electrical engineering. 15 | 16 | The base module for the `electricpy` package, electricpy.py may be leveraged 17 | in any Python script or program by using the *import* command similar to that 18 | shown below. 19 | 20 | >>> import electricpy as ep 21 | 22 | Filled with calculators, evaluators, and plotting functions, this package will 23 | provide a wide array of capabilities to any electrical engineer. 24 | 25 | Built to support operations similar to Numpy and Scipy, this package is designed 26 | to aid in scientific calculations. 27 | 28 | 29 | ------------------------- 30 | Contents: 31 | ------------------------- 32 | 33 | .. toctree:: 34 | :maxdepth: 1 35 | 36 | electricpyapi 37 | constants 38 | additionalresources 39 | changes 40 | Github 41 | PyPI 42 | 43 | 44 | .. include:: ../README.md 45 | :parser: myst_parser.sphinx_ 46 | -------------------------------------------------------------------------------- /docsource/render_images.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """Render images as needed using the various ElectricPy functions for docs.""" 3 | ################################################################################ 4 | 5 | import os 6 | import math, cmath 7 | import numpy as np 8 | import electricpy as ep 9 | from electricpy import visu 10 | from matplotlib import pyplot 11 | 12 | FIGURE_DIRECTORY = os.path.join( 13 | os.path.dirname(os.path.abspath(__file__)), "static" 14 | ) 15 | 16 | def canvas_capture(image_label): 17 | """Decorate a function to save the canvas as an image.""" 18 | def decorate(render_method): 19 | def wrapper(*args, **kwargs): 20 | fig = pyplot.figure() 21 | render_method(*args, **kwargs) 22 | fig.savefig(f"{FIGURE_DIRECTORY}/{image_label}.png") 23 | return wrapper 24 | return decorate 25 | 26 | @canvas_capture("PowerTriangle") 27 | def render_power_triangle(): 28 | """Render the Power Triangle Function.""" 29 | visu.powertriangle(400, 200) 30 | 31 | # @canvas_capture("PhasorPlot") 32 | def render_phasor_plot(): 33 | """Render the Phasor Plot Function.""" 34 | # This phasorplot is ploted on seperate canvas 35 | phasors = ep.phasors.phasorlist([ 36 | [67,0], 37 | [45,-120], 38 | [52,120] 39 | ]) 40 | plt = visu.phasorplot(phasors=phasors, colors=["red", "green", "blue"]) 41 | plt.savefig(f"{FIGURE_DIRECTORY}/PhasorPlot.png") 42 | 43 | @canvas_capture("InductionMotorCircleExample") 44 | def render_motor_circle(): 45 | """Render the Induction Motor Circle and Draw.""" 46 | open_circuit_test_data = {'V0': 400, 'I0': 9, 'W0': 1310} 47 | blocked_rotor_test_data = {'Vsc': 200, 'Isc': 50, 'Wsc': 7100} 48 | ratio = 1 # stator copper loss/ rotor copper loss 49 | output_power = 15000 50 | visu.InductionMotorCircle( 51 | no_load_data=open_circuit_test_data, 52 | blocked_rotor_data=blocked_rotor_test_data, 53 | output_power=output_power, 54 | torque_ration=ratio, 55 | frequency=50, 56 | poles=4 57 | ).plot() 58 | 59 | @canvas_capture("ReceivingEndPowerCircleExample") 60 | def render_receiving_end_power_circle(): 61 | """Render the Receiving End Power Circle Plot.""" 62 | visu.receiving_end_power_circle( 63 | A=cmath.rect(0.895, math.radians(1.4)), 64 | B=cmath.rect(182.5, math.radians(78.6)), 65 | Vr=cmath.rect(215, 0), 66 | Pr=50, 67 | power_factor=-0.9 68 | ).plot() 69 | 70 | @canvas_capture("ReceivingPowerCircleExample") 71 | def render_receiving_power_circle(): 72 | """Render the Receiving End Power Circle Plot.""" 73 | visu.PowerCircle( 74 | power_circle_type="receiving", 75 | A=cmath.rect(0.895, math.radians(1.4)), 76 | B=cmath.rect(182.5, math.radians(78.6)), 77 | Vr=cmath.rect(215, 0), 78 | Pr=50, 79 | power_factor=-0.9 80 | ).plot() 81 | 82 | @canvas_capture("convbar-example") 83 | def render_convbar_example(): 84 | """Render the Plot Generated by the `electricpy.convbar` Function.""" 85 | h = np.array([0, 1, 1, 1, 0]) 86 | x = np.array([0, 1, 1, 1, 0]) 87 | visu.convbar(h, x) 88 | 89 | @canvas_capture("series-rlc-r5-l0.4") 90 | def render_series_rlc_5_ohm(): 91 | """Render the Series RLC Circuit's Visualization.""" 92 | visu.SeriesRLC( 93 | resistance=5, 94 | inductance=0.4, 95 | capacitance=25.3e-6, 96 | frequency=50 97 | ).graph(lower_frequency_cut=0.1, upper_frequency_cut=100, samples=1000) 98 | 99 | @canvas_capture("series-rlc-r10-l0.5") 100 | def render_series_rlc_10_ohm(): 101 | """Render the Series RLC Circuit's Visualization.""" 102 | visu.SeriesRLC( 103 | resistance=10, 104 | inductance=0.5, 105 | capacitance=25.3e-6, 106 | frequency=50 107 | ).graph( 108 | lower_frequency_cut=0.1, 109 | upper_frequency_cut=100, 110 | samples=1000, 111 | show_legend=True 112 | ) 113 | 114 | def main(): 115 | """Run all of the Image Generators.""" 116 | # Add function calls here as new rendering functions are added 117 | render_power_triangle() 118 | render_phasor_plot() 119 | render_motor_circle() 120 | render_receiving_end_power_circle() 121 | render_receiving_power_circle() 122 | render_convbar_example() 123 | render_series_rlc_5_ohm() 124 | render_series_rlc_10_ohm() 125 | 126 | 127 | # Entrypoint 128 | if __name__ == '__main__': 129 | main() 130 | 131 | 132 | # END 133 | -------------------------------------------------------------------------------- /docsource/requirements.txt: -------------------------------------------------------------------------------- 1 | wheel 2 | sphinx 3 | numpydoc 4 | myst-parser[linkify] 5 | sphinx-sitemap 6 | sphinx-git 7 | sphinx-immaterial 8 | coverage-badge 9 | numpy 10 | matplotlib 11 | scipy 12 | sympy 13 | numdifftools -------------------------------------------------------------------------------- /docsource/static/InductionMotorCircleExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/InductionMotorCircleExample.png -------------------------------------------------------------------------------- /docsource/static/PhasorPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/PhasorPlot.png -------------------------------------------------------------------------------- /docsource/static/PowerTriangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/PowerTriangle.png -------------------------------------------------------------------------------- /docsource/static/ReceivingEndPowerCircleExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/ReceivingEndPowerCircleExample.png -------------------------------------------------------------------------------- /docsource/static/ReceivingPowerCircleExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/ReceivingPowerCircleExample.png -------------------------------------------------------------------------------- /docsource/static/SuspensionInuslator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/SuspensionInuslator.png -------------------------------------------------------------------------------- /docsource/static/SuspensionInuslator.svg: -------------------------------------------------------------------------------- 1 | 2 | 21 | 23 | 25 | capacitor 27 | 30 | 34 | 38 | 39 | 40 | 42 | ground 44 | 47 | 51 | 52 | 53 | 55 | cground 57 | 60 | 64 | 65 | 66 | 68 | barrier 70 | 73 | 77 | 81 | 82 | 83 | 84 | 108 | 110 | 111 | 113 | image/svg+xml 114 | 116 | 117 | 118 | 119 | 120 | 125 | 134 | 143 | 152 | 161 | 170 | 179 | 188 | 197 | 206 | 211 | 216 | 221 | 226 | 231 | C 242 | C 253 | C 264 | mc 275 | mc 286 | mc 297 | mc 308 | Transmission Tower 319 | 330 | V1 341 | V2 352 | V3 363 | V4 374 | V 385 | 386 | 387 | -------------------------------------------------------------------------------- /docsource/static/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/Thumbs.db -------------------------------------------------------------------------------- /docsource/static/WheatstoneBridgeCircuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/WheatstoneBridgeCircuit.png -------------------------------------------------------------------------------- /docsource/static/WheatstoneBridgeCircuit.svg: -------------------------------------------------------------------------------- 1 | 2 | 21 | 23 | 29 | 31 | generic 33 | 36 | 40 | 44 | 45 | 46 | 48 | battery1 50 | 53 | 57 | 61 | 62 | 63 | 65 | resistor 67 | 70 | 74 | 78 | 79 | 80 | 82 | european_resistor 84 | 87 | 91 | 95 | 96 | 97 | 103 | 109 | 115 | 121 | 127 | 133 | 139 | 145 | 146 | 166 | 168 | 169 | 171 | image/svg+xml 172 | 174 | 175 | 176 | 177 | 178 | 182 | 185 | 194 | 203 | 212 | 221 | 222 | 225 | 234 | 243 | 244 | 248 | 252 | 256 | 260 | 264 | 268 | 277 | 281 | 285 | Z1 293 | Z2 301 | Z4 309 | Z3 317 | Z5 325 | 326 | 327 | -------------------------------------------------------------------------------- /docsource/static/convbar-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/convbar-example.png -------------------------------------------------------------------------------- /docsource/static/inductive-voltage-divider-circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/inductive-voltage-divider-circuit.png -------------------------------------------------------------------------------- /docsource/static/mbuspowerflow_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/mbuspowerflow_example.png -------------------------------------------------------------------------------- /docsource/static/pi-attenuator-circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/pi-attenuator-circuit.png -------------------------------------------------------------------------------- /docsource/static/synmach_ifault_formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/synmach_ifault_formula.png -------------------------------------------------------------------------------- /docsource/static/t-attenuator-circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/t-attenuator-circuit.png -------------------------------------------------------------------------------- /docsource/static/zenerdiode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/docsource/static/zenerdiode.png -------------------------------------------------------------------------------- /electricpy/active/__init__.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | `electricpy.active` Active electronics sub module. 4 | 5 | >>> import electricpy.active as active 6 | 7 | Built to support operations similar to Numpy and Scipy, this package is designed 8 | to aid in scientific calculations. 9 | """ 10 | ################################################################################ -------------------------------------------------------------------------------- /electricpy/bode.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | Bode Plotting Functionality for ElectricPy Package. 4 | 5 | >>> from electricpy import bode 6 | """ 7 | ################################################################################ 8 | 9 | from cmath import exp as _exp 10 | 11 | 12 | import matplotlib.pyplot as _plt 13 | import numpy as _np 14 | import scipy.signal as _sig 15 | from numpy import pi as _pi 16 | 17 | from electricpy.math import convolve 18 | 19 | # Define System Conditioning Function 20 | def _sys_condition(system, feedback): 21 | if len(system) == 2: # System found to be num and den 22 | num = system[0] 23 | den = system[1] 24 | # Convolve numerator or denominator as needed 25 | if str(type(num)) == tuple: 26 | num = convolve(num) # Convolve terms in numerator 27 | if str(type(den)) == tuple: 28 | den = convolve(den) # Convolve terms in denominator 29 | if feedback: # If asked to add the numerator to the denominator 30 | ld = len(den) # Length of denominator 31 | ln = len(num) # Length of numerator 32 | if ld > ln: 33 | num = _np.append(_np.zeros(ld - ln), num) # Pad beginning with zeros 34 | if ld < ln: 35 | den = _np.append(_np.zeros(ln - ld), den) # Pad beginning with zeros 36 | den = den + num # Add numerator and denominator 37 | for i in range(len(num)): 38 | if num[i] != 0: 39 | num = num[i:] # Slice zeros off the front of the numerator 40 | break # Break out of for loop 41 | for i in range(len(den)): 42 | if den[i] != 0: 43 | den = den[i:] # Slice zeros off the front of the denominator 44 | break # Break out of for loop 45 | system = (num, den) # Repack system 46 | return system # Return the conditioned system 47 | 48 | 49 | # Define System Bode Plotting Function 50 | def bode(system, mn=0.001, mx=1000, npts=100, title="", xlim=False, ylim=False, sv=False, 51 | disp3db=False, lowcut=None, magnitude=True, angle=True, freqaxis="rad"): 52 | """ 53 | System Bode Plotting Function. 54 | 55 | A simple function to generate the Bode Plot for magnitude 56 | and frequency given a transfer function system. 57 | 58 | Parameters 59 | ---------- 60 | system: transfer function object 61 | The Transfer Function; can be provided as the following: 62 | - 1 (instance of lti) 63 | - 2 (num, den) 64 | - 3 (zeros, poles, gain) 65 | - 4 (A, B, C, D) 66 | mn: float, optional 67 | The minimum frequency to be calculated for. default=0.01. 68 | mx: float, optional 69 | The maximum frequency to be calculated for. default=1000. 70 | npts: float, optional 71 | The number of points over which to calculate the system. 72 | default=100. 73 | title: string, optional 74 | Additional string to be added to plot titles; 75 | default="". 76 | xlim: list of float, optional 77 | Limit in x-axis for graph plot. Accepts tuple of: (xmin, xmax). 78 | Default is False. 79 | ylim: list of float, optional 80 | Limit in y-axis for graph plot. Accepts tuple of: (ymin, ymax). 81 | Default is False. 82 | sv: bool, optional 83 | Save the plots as PNG files. Default is False. 84 | disp3db: bool, optional 85 | Control argument to enable the display of the 3dB line, 86 | default=False. 87 | lowcut: float, optional 88 | An additional marking line that can be plotted, default=None 89 | magnitude: bool, optional 90 | Control argument to enable plotting of magnitude, default=True 91 | angle: bool, optional 92 | Control argument to enable plotting of angle, default=True 93 | freqaxis: string, optional 94 | Control argument to specify the freqency axis in degrees or 95 | radians, default is radians (rad) 96 | """ 97 | # Condition system input to ensure proper execution 98 | system = _sys_condition(system, False) 99 | 100 | # Condition min and max freq terms 101 | degrees = False 102 | if freqaxis.lower().find("deg") != -1: # degrees requested 103 | degrees = True 104 | # Scale Degrees to Radians for calculation 105 | mn = 2 * _np.pi * mn 106 | mx = 2 * _np.pi * mx 107 | mn = _np.log10(mn) # find the _exponent value 108 | mx = _np.log10(mx) # find the _exponent value 109 | 110 | # Generate the frequency range to calculate over 111 | wover = _np.logspace(mn, mx, npts) 112 | 113 | # Calculate the bode system 114 | w, mag, ang = _sig.bode(system, wover) 115 | 116 | def _plot(plot_title, y_label): 117 | _plt.title(plot_title) 118 | _plt.ylabel(y_label) 119 | if degrees: # Plot in degrees 120 | _plt.plot(w / (2 * _np.pi), ang) 121 | _plt.xlabel("Frequency (Hz)") 122 | else: # Plot in radians 123 | _plt.plot(w, ang) 124 | _plt.xlabel("Frequency (rad/sec)") 125 | _plt.xscale("log") 126 | _plt.grid(which="both") 127 | if xlim: 128 | _plt.xlim(xlim) 129 | if ylim: 130 | _plt.ylim(ylim) 131 | if sv: 132 | _plt.savefig(title + ".png") 133 | 134 | # Plot Magnitude 135 | if magnitude: 136 | magTitle = "Magnitude " + title 137 | _plot(magTitle, "Magnitude (DB)") 138 | if disp3db: 139 | _plt.axhline(-3) 140 | if lowcut is not None: 141 | _plt.axhline(lowcut) 142 | _plt.show() 143 | 144 | # Plot Angle 145 | if angle: 146 | angTitle = "Angle " + title 147 | _plot(angTitle, "Angle (degrees)") 148 | _plt.show() 149 | 150 | 151 | def _magnitude_plot(title, disp3db, lowcut, xlim, ylim, sv): 152 | _plt.title(title + " Magnitude") 153 | _plt.grid(which='both') 154 | if disp3db: 155 | _plt.axhline(-3) 156 | if lowcut is not None: 157 | _plt.axhline(lowcut) 158 | if xlim: 159 | _plt.xlim(xlim) 160 | if ylim: 161 | _plt.ylim(ylim) 162 | if sv: 163 | _plt.savefig(title + " Magnitude.png") 164 | _plt.show() 165 | 166 | 167 | def sbode(f, NN=1000, title="", xlim=False, ylim=False, mn=0, mx=1000, 168 | sv=False, disp3db=False, lowcut=None, magnitude=True, angle=True): 169 | """ 170 | S-Domain Bode Plotting Function. 171 | 172 | Parameters 173 | ---------- 174 | f: function 175 | The Input Function, must be callable function object. 176 | NN: int, optional 177 | The Interval over which to be generated, default=1000 178 | title: string, optional 179 | Additional string to be added to plot titles; 180 | default="". 181 | xlim: list of float, optional 182 | Limit in x-axis for graph plot. Accepts tuple of: (xmin, xmax). 183 | Default is False. 184 | ylim: list of float, optional 185 | Limit in y-axis for graph plot. Accepts tuple of: (ymin, ymax). 186 | Default is False. 187 | mn: float, optional 188 | The minimum W value to be generated, default=0 189 | mx: float, optional 190 | The maximum W value to be generated, default=1000 191 | sv: bool, optional 192 | Save the plots as PNG files. Default is False. 193 | disp3db: bool, optional 194 | Control argument to enable the display of the 3dB line, 195 | default=False. 196 | lowcut: float, optional 197 | An additional marking line that can be plotted, default=None 198 | magnitude: bool, optional 199 | Control argument to enable plotting of magnitude, default=True 200 | angle: bool, optional 201 | Control argument to enable plotting of angle, default=True 202 | """ 203 | W = _np.linspace(mn, mx, NN) 204 | H = _np.zeros(NN, dtype=_np.complex) 205 | 206 | for n in range(0, NN): 207 | s = 1j * W[n] 208 | H[n] = f(s) 209 | if magnitude: 210 | _plt.semilogx(W, 20 * _np.log10(abs(H)), 'k') 211 | _plt.ylabel('|H| dB') 212 | _plt.xlabel('Frequency (rad/sec)') 213 | _magnitude_plot(title, disp3db, lowcut, xlim, ylim, sv) 214 | 215 | aaa = _np.angle(H) 216 | for n in range(NN): 217 | if aaa[n] > _pi: 218 | aaa[n] = aaa[n] - 2 * _pi 219 | 220 | if angle: 221 | _plt.title(title + " Phase") 222 | _plt.semilogx(W, (180 / _pi) * aaa, 'k') 223 | _plt.ylabel('H phase (degrees)') 224 | _plt.xlabel('Frequency (rad/sec)') 225 | _plt.grid(which='both') 226 | if xlim: 227 | _plt.xlim(xlim) 228 | if ylim: 229 | _plt.ylim(ylim) 230 | if sv: 231 | _plt.savefig(title + " Phase.png") 232 | _plt.show() 233 | 234 | 235 | def zbode(f, dt=0.01, NN=1000, title="", mn=0, mx=2 * _pi, xlim=False, ylim=False, 236 | approx=False, sv=False, disp3db=False, lowcut=None, magnitude=True, 237 | angle=True): 238 | """ 239 | Z-Domain Bode Plotting Function. 240 | 241 | Parameters 242 | ---------- 243 | f: function 244 | The Input Function, must be callable function object. 245 | Must be specified as transfer function of type: 246 | - S-Domain (when approx=False, default) 247 | - Z-Domain (when approx=True) 248 | dt: float, optional 249 | The time-step used, default=0.01 250 | NN: int, optional 251 | The Interval over which to be generated, default=1000 252 | mn: float, optional 253 | The minimum phi value to be generated, default=0 254 | mx: float, optional 255 | The maximum phi value to be generated, default=2*pi 256 | approx: bool, optional, callable 257 | Control argument to specify whether input funciton 258 | should be treated as Z-Domain function or approximated 259 | Z-Domain function. default=False 260 | title: string, optional 261 | Additional string to be added to plot titles; 262 | default="". 263 | xlim: list of float, optional 264 | Limit in x-axis for graph plot. Accepts tuple of: (xmin, xmax). 265 | Default is False. 266 | ylim: list of float, optional 267 | Limit in y-axis for graph plot. Accepts tuple of: (ymin, ymax). 268 | Default is False. 269 | sv: bool, optional 270 | Save the plots as PNG files. Default is False. 271 | disp3db: bool, optional 272 | Control argument to enable the display of the 3dB line, 273 | default=False. 274 | lowcut: float, optional 275 | An additional marking line that can be plotted, default=None 276 | magnitude: bool, optional 277 | Control argument to enable plotting of magnitude, default=True 278 | angle: bool, optional 279 | Control argument to enable plotting of angle, default=True 280 | """ 281 | phi = _np.linspace(mn, mx, NN) 282 | 283 | H = _np.zeros(NN, dtype=_np.complex) 284 | for n in range(0, NN): 285 | z = _exp(1j * phi[n]) 286 | if approx is not False and callable(approx): 287 | # Approximated Z-Domain 288 | s = approx(z, dt) # Pass current z-value and dt 289 | H[n] = f(s) 290 | else: # Z-Domain Transfer Function Provided 291 | H[n] = dt * f(z) 292 | 293 | if magnitude: 294 | _plt.semilogx((180 / _pi) * phi, 20 * _np.log10(abs(H)), 'k') 295 | _plt.ylabel('|H| dB') 296 | _plt.xlabel('Frequency (degrees)') 297 | _magnitude_plot(title, disp3db, lowcut, xlim, ylim, sv) 298 | 299 | aaa = _np.angle(H) 300 | for n in range(NN): 301 | if aaa[n] > _pi: 302 | aaa[n] = aaa[n] - 2 * _pi 303 | 304 | if angle: 305 | _plt.semilogx((180 / _pi) * phi, (180 / _pi) * aaa, 'k') 306 | _plt.ylabel('H (degrees)') 307 | _plt.grid(which='both') 308 | _plt.xlabel('Frequency (degrees)') 309 | _plt.title(title + " Phase") 310 | if xlim: 311 | _plt.xlim(xlim) 312 | if ylim: 313 | _plt.ylim(ylim) 314 | if sv: 315 | _plt.savefig(title + " Phase.png") 316 | _plt.show() 317 | 318 | # End of BODE.PY 319 | -------------------------------------------------------------------------------- /electricpy/compute.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | Functions to Support Computer-Science Related Formulas. 4 | 5 | >>> from electricpy import compute as cmp 6 | 7 | Filled with calculators, evaluators, and plotting functions related to 8 | electrical phasors, this package will provide a wide array of capabilities to 9 | any electrical engineer. 10 | 11 | Built to support operations similar to Numpy and Scipy, this package is designed 12 | to aid in scientific calculations. 13 | """ 14 | ################################################################################ 15 | 16 | 17 | # Define Simple Maximum Integer Evaluator 18 | def largest_integer(numBits, signed=True): 19 | """ 20 | Evaluate the largest integer that may be represented by an n-bit variable. 21 | 22 | This function will evaluate the largest integer an n-bit variable may hold 23 | in computers. It can evaluate signed or unsigned integers. The formulas for 24 | which it determines these values are as follows: 25 | 26 | **Signed Integers** 27 | 28 | .. math:: 2^{n-1} - 1 29 | 30 | **Unsigned Integers** 31 | 32 | .. math:: 2^{n} - 1 33 | 34 | Parameters 35 | ---------- 36 | numBits: int 37 | The number of bits that should be used to interpret the overall 38 | maximum size. 39 | signed: bool, optional 40 | Control to specify whether the value should be evaluated for 41 | signed, or unsigned, integers. Defaults to True. 42 | 43 | Returns 44 | ------- 45 | int: The maximum value that can be stored in an integer of numBits. 46 | 47 | Examples 48 | -------- 49 | >>> import electricpy.compute as cmp 50 | >>> cmp.largest_integer(8, signed=False) 51 | 255 52 | >>> cmp.largest_integer(32, signed=False) 53 | 4294967295 54 | >>> cmp.largest_integer(32, signed=True) 55 | 2147483647 56 | """ 57 | # Use Signed or Unsigned Formula 58 | if signed: 59 | return int(2 ** (numBits - 1) - 1) 60 | else: 61 | return int(2 ** (numBits) - 1) 62 | 63 | 64 | # Define CRC Generator (Sender Side) 65 | def crcsender(data, key): 66 | """ 67 | CRC Sender Function. 68 | 69 | Function to generate a CRC-embedded message ready for transmission. 70 | 71 | Contributing Author Credit: 72 | Shaurya Uppal 73 | Available from: geeksforgeeks.org 74 | 75 | Parameters 76 | ---------- 77 | data: string of bits 78 | The bit-string to be encoded. 79 | key: string of bits 80 | Bit-string representing key. 81 | 82 | Returns 83 | ------- 84 | codeword: string of bits 85 | Bit-string representation of 86 | encoded message. 87 | """ 88 | # Define Sub-Functions 89 | def xor(a, b): 90 | # initialize result 91 | result = [] 92 | 93 | # Traverse all bits, if bits are 94 | # same, then XOR is 0, else 1 95 | for i in range(1, len(b)): 96 | if a[i] == b[i]: 97 | result.append('0') 98 | else: 99 | result.append('1') 100 | 101 | return ''.join(result) 102 | 103 | # Performs Modulo-2 division 104 | def mod2div(divident, divisor): 105 | # Number of bits to be XORed at a time. 106 | pick = len(divisor) 107 | 108 | # Slicing the divident to appropriate 109 | # length for particular step 110 | tmp = divident[0: pick] 111 | 112 | while pick < len(divident): 113 | 114 | if tmp[0] == '1': 115 | 116 | # replace the divident by the result 117 | # of XOR and pull 1 bit down 118 | tmp = xor(divisor, tmp) + divident[pick] 119 | 120 | else: # If leftmost bit is '0' 121 | 122 | # If the leftmost bit of the dividend (or the 123 | # part used in each step) is 0, the step cannot 124 | # use the regular divisor; we need to use an 125 | # all-0s divisor. 126 | tmp = xor('0' * pick, tmp) + divident[pick] 127 | 128 | # increment pick to move further 129 | pick += 1 130 | 131 | # For the last n bits, we have to carry it out 132 | # normally as increased value of pick will cause 133 | # Index Out of Bounds. 134 | if tmp[0] == '1': 135 | tmp = xor(divisor, tmp) 136 | else: 137 | tmp = xor('0' * pick, tmp) 138 | 139 | checkword = tmp 140 | return checkword 141 | 142 | # Condition data 143 | data = str(data) 144 | # Condition Key 145 | key = str(key) 146 | l_key = len(key) 147 | 148 | # Appends n-1 zeroes at end of data 149 | appended_data = data + '0' * (l_key - 1) 150 | remainder = mod2div(appended_data, key) 151 | 152 | # Append remainder in the original data 153 | codeword = data + remainder 154 | return codeword 155 | 156 | 157 | # Define CRC Generator (Sender Side) 158 | def crcremainder(data, key): 159 | """ 160 | CRC Remainder Function. 161 | 162 | Function to calculate the CRC remainder of a CRC message. 163 | 164 | Contributing Author Credit: 165 | Shaurya Uppal 166 | Available from: geeksforgeeks.org 167 | 168 | Parameters 169 | ---------- 170 | data: string of bits 171 | The bit-string to be decoded. 172 | key: string of bits 173 | Bit-string representing key. 174 | 175 | Returns 176 | ------- 177 | remainder: string of bits 178 | Bit-string representation of 179 | encoded message. 180 | """ 181 | # Define Sub-Functions 182 | def xor(a, b): 183 | # initialize result 184 | result = [] 185 | 186 | # Traverse all bits, if bits are 187 | # same, then XOR is 0, else 1 188 | for i in range(1, len(b)): 189 | if a[i] == b[i]: 190 | result.append('0') 191 | else: 192 | result.append('1') 193 | 194 | return ''.join(result) 195 | 196 | # Performs Modulo-2 division 197 | def mod2div(divident, divisor): 198 | # Number of bits to be XORed at a time. 199 | pick = len(divisor) 200 | 201 | # Slicing the divident to appropriate 202 | # length for particular step 203 | tmp = divident[0: pick] 204 | 205 | while pick < len(divident): 206 | 207 | if tmp[0] == '1': 208 | 209 | # replace the divident by the result 210 | # of XOR and pull 1 bit down 211 | tmp = xor(divisor, tmp) + divident[pick] 212 | 213 | else: # If leftmost bit is '0' 214 | 215 | # If the leftmost bit of the dividend (or the 216 | # part used in each step) is 0, the step cannot 217 | # use the regular divisor; we need to use an 218 | # all-0s divisor. 219 | tmp = xor('0' * pick, tmp) + divident[pick] 220 | 221 | # increment pick to move further 222 | pick += 1 223 | 224 | # For the last n bits, we have to carry it out 225 | # normally as increased value of pick will cause 226 | # Index Out of Bounds. 227 | if tmp[0] == '1': 228 | tmp = xor(divisor, tmp) 229 | else: 230 | tmp = xor('0' * pick, tmp) 231 | 232 | checkword = tmp 233 | return checkword 234 | 235 | # Condition data 236 | data = str(data) 237 | # Condition Key 238 | key = str(key) 239 | l_key = len(key) 240 | 241 | # Appends n-1 zeroes at end of data 242 | appended_data = data + '0' * (l_key - 1) 243 | remainder = mod2div(appended_data, key) 244 | 245 | return remainder 246 | 247 | 248 | # Define String to Bits Function 249 | def string_to_bits(str): 250 | # noqa: D401 "String" is an intended leading word. 251 | """ 252 | String to Bits Converter. 253 | 254 | Converts a Pythonic string to the string's binary representation. 255 | 256 | Parameters 257 | ---------- 258 | str: string 259 | The string to be converted. 260 | 261 | Returns 262 | ------- 263 | data: string 264 | The binary representation of the 265 | input string. 266 | """ 267 | data = (''.join(format(ord(x), 'b') for x in str)) 268 | return data 269 | 270 | # END 271 | -------------------------------------------------------------------------------- /electricpy/constants.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | Numerical Constants for Electrical Engineering - Pi, Tau, α, etc. 4 | 5 | Defenition of all required constants and matricies for 6 | *electricpy* module. 7 | """ 8 | ################################################################################ 9 | 10 | import numpy as _np 11 | import cmath as _c 12 | 13 | # Define Electrical Engineering Constants 14 | pi = _np.pi #: PI Constant 3.14159... 15 | a = _c.rect(1, _np.radians(120)) #: 'A' Operator for Symmetrical Components 16 | p = 1e-12 #: Pico Multiple (10^-12) 17 | n = 1e-9 #: Nano Multiple (10^-9) 18 | u = 1e-6 #: Micro (mu) Multiple (10^-6) 19 | m = 1e-3 #: Mili Multiple (10^-3) 20 | k = 1e+3 #: Kili Multiple (10^3) 21 | M = 1e+6 #: Mega Multiple (10^6) 22 | G = 1e+9 #: Giga Multiple (10^9) 23 | u0 = 4 * _np.pi * 10 ** (-7) #: µ0 (mu-not) 4πE-7 24 | e0 = 8.8541878128e-12 #: ε0 (epsilon-not) 8.854E-12 25 | carson_r = 9.869e-7 #: Carson's Ristance Constant 8.869E-7 26 | De0 = 2160 #: De Constant for Use with Transmission Impedance Calculations =2160 27 | NAN = float('nan') 28 | VLLcVLN = _c.rect(_np.sqrt(3), _np.radians(30)) # Conversion Operator 29 | ILcIP = _c.rect(_np.sqrt(3), _np.radians(30)) # Conversion Operator 30 | WATTS_PER_HP = 745.699872 31 | KWH_PER_BTU = 3412.14 32 | 33 | # Define Symmetrical Component Matricies 34 | Aabc = 1 / 3 * _np.array([[1, 1, 1], # Convert ABC to 012 35 | [1, a, a ** 2], # (i.e. phase to sequence) 36 | [1, a ** 2, a]]) 37 | A012 = _np.array([[1, 1, 1], # Convert 012 to ABC 38 | [1, a ** 2, a], # (i.e. sequence to phase) 39 | [1, a, a ** 2]]) 40 | # Define Clarke Component Matricies 41 | Cabc = _np.sqrt(2 / 3) * _np.array([ 42 | [1, -1 / 2, -1 / 2], # Convert ABC to alpha/beta/gamma 43 | [0, _np.sqrt(3) / 2, -_np.sqrt(3) / 2], 44 | [1 / _np.sqrt(2), 1 / _np.sqrt(2), 1 / _np.sqrt(2)] 45 | ]) 46 | Cxyz = _np.array([ 47 | [2 / _np.sqrt(6), 0, 1 / _np.sqrt(3)], # Convert alpha/beta/gamma to ABC 48 | [-1 / _np.sqrt(6), 1 / _np.sqrt(2), 1 / _np.sqrt(3)], 49 | [-1 / _np.sqrt(6), -1 / _np.sqrt(2), 1 / _np.sqrt(3)] 50 | ]) 51 | # Define Park Components Matricies 52 | _rad = lambda th: _np.radians(th) 53 | _Pdq0_im = lambda th: _np.sqrt(2 / 3) * _np.array([ 54 | [_np.cos(_rad(th)), _np.cos(_rad(th) - 2 * pi / 3), _np.cos(_rad(th) + 2 * pi / 3)], 55 | [-_np.sin(_rad(th)), -_np.sin(_rad(th) - 2 * pi / 3), -_np.sin(_rad(th) + 2 * pi / 3)], 56 | [_np.sqrt(2) / 2, _np.sqrt(2) / 2, _np.sqrt(2) / 2] 57 | ]) 58 | _Pabc_im = lambda th: _np.sqrt(2 / 3) * _np.array([ 59 | [_np.cos(_rad(th)), -_np.sin(_rad(th)), _np.sqrt(2) / 2], 60 | [_np.cos(_rad(th) - 2 * pi / 3), -_np.sin(_rad(th) - 2 * pi / 3), _np.sqrt(2) / 2], 61 | [_np.cos(_rad(th) + 2 * pi / 3), -_np.sin(_rad(th) + 2 * pi / 3), _np.sqrt(2) / 2] 62 | ]) 63 | Pdq0 = 2 / 3 * _np.array([[0, -_np.sqrt(3 / 2), _np.sqrt(3 / 2)], 64 | [1, -1 / 2, -1 / 2], 65 | [1 / 2, 1 / 2, 1 / 2]]) 66 | Pqd0 = 2 / 3 * _np.array([[1, -1 / 2, -1 / 2], 67 | [0, -_np.sqrt(3 / 2), _np.sqrt(3 / 2)], 68 | [1 / 2, 1 / 2, 1 / 2]]) 69 | 70 | # Define Transformer Shift Correction Matricies 71 | XFMY0 = _np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) 72 | XFMD1 = 1 / _np.sqrt(3) * _np.array([[1, -1, 0], [0, 1, -1], [-1, 0, 1]]) 73 | XFMD11 = 1 / _np.sqrt(3) * _np.array([[1, 0, -1], [-1, 1, 0], [0, -1, 1]]) 74 | XFM12 = 1 / 3 * _np.array([[2, -1, -1], [-1, 2, -1], [-1, -1, 2]]) 75 | 76 | # Define Complex Angle Terms 77 | e30 = _c.rect(1, _np.radians(30)) #: 30° Phase Operator 78 | en30 = _c.rect(1, _np.radians(-30)) #: -30° Phase Operator 79 | e60 = _c.rect(1, _np.radians(60)) #: 60° Phase Operator 80 | en60 = _c.rect(1, _np.radians(-60)) #: -60° Phase Operator 81 | e90 = _c.rect(1, _np.radians(90)) #: 90° Phase Operator 82 | en90 = _c.rect(1, _np.radians(-90)) #: -90° Phase Operator 83 | e45 = _c.rect(1, _np.radians(45)) #: 45° Phase Operator 84 | en45 = _c.rect(1, _np.radians(-45)) #: -45° Phase Operator 85 | 86 | # Define Material Resistivity (Rho) 87 | resistivity_rho = { 88 | 'silver': 15.9, 89 | 'copper': 16.8, 90 | 'aluminium': 6.5, 91 | 'tungsten': 56, 92 | 'iron': 97.1, 93 | 'platinum': 106, 94 | 'manganin': 482, 95 | 'lead': 220, 96 | 'mercury': 980, 97 | 'nichrome': 1000, 98 | 'constantan': 490, 99 | } 100 | THERMO_COUPLE_DATA = { 101 | "J": [ 102 | [-6.4936529E+01, 2.5066947E+02, 6.4950262E+02, 9.2510550E+02, 1.0511294E+03], 103 | [-3.1169773E+00, 1.3592329E+01, 3.6040848E+01, 5.3433832E+01, 6.0956091E+01], 104 | [2.2133797E+01, 1.8014787E+01, 1.6593395E+01, 1.6243326E+01, 1.7156001E+01], 105 | [2.0476437E+00, -6.5218881E-02, 7.3009590E-01, 9.2793267E-01, -2.5931041E+00], 106 | [-4.6867532E-01, -1.2179108E-02, 2.4157343E-02, 6.4644193E-03, -5.8339803E-02], 107 | [-3.6673992E-02, 2.0061707E-04, 1.2787077E-03, 2.0464414E-03, 1.9954137E-02], 108 | [1.1746348E-01, -3.9494552E-03, 4.9172861E-02, 5.2541788E-02, -1.5305581E-01], 109 | [-2.0903413E-02, -7.3728206E-04, 1.6813810E-03, 1.3682959E-04, -2.9523967E-03], 110 | [-2.1823704E-03, 1.6679731E-05, 7.6067922E-05, 1.3454746E-04, 1.1340164E-03] 111 | ], 112 | "K": [ 113 | [-1.2147164E+02, -8.7935962E+00, 3.1018976E+02, 6.0572562E+02, 1.0184705E+03], 114 | [-4.1790858E+00, -3.4489914E-01, 1.2631386E+01, 2.5148718E+01, 4.1993851E+01], 115 | [3.6069513E+01, 2.5678719E+01, 2.4061949E+01, 2.3539401E+01, 2.5783239E+01], 116 | [3.0722076E+01, -4.9887904E-01, 4.0158622E+00, 4.6547228E-02, -1.8363403E+00], 117 | [7.7913860E+00, -4.4705222E-01, 2.6853917E-01, 1.3444400E-02, 5.6176662E-02], 118 | [5.2593991E-01, -4.4869203E-02, -9.7188544E-03, 5.9236853E-04, 1.8532400E-04], 119 | [9.3939547E-01, 2.3893439E-04, 1.6995872E-01, 8.3445513E-04, -7.4803355E-02], 120 | [2.7791285E-01, -2.0397750E-02, 1.1413069E-02, 4.6121445E-04, 2.3841860E-03], 121 | [2.5163349E-02, -1.8424107E-03, -3.9275155E-04, 2.5488122E-05, 0.0] 122 | ], 123 | "B": [ 124 | [5.0000000E+02, 1.2461474E+03], 125 | [1.2417900E+00, 7.2701221E+00], 126 | [1.9858097E+02, 9.4321033E+01], 127 | [2.4284248E+01, 7.3899296E+00], 128 | [-9.7271640E+01, -1.5880987E-01], 129 | [-1.5701178E+01, 1.2681877E-02], 130 | [3.1009445E-01, 1.0113834E-01], 131 | [-5.0880251E-01, -1.6145962E-03], 132 | [-1.6163342E-01, -4.1086314E-06]], 133 | "E": [ 134 | [-1.1721668E+02, -5.0000000E+01, 2.5014600E+02, 6.0139890E+02, 8.0435911E+02], 135 | [-5.9901698E+00, -2.7871777E+00, 1.7191713E+01, 4.5206167E+01, 6.1359178E+01], 136 | [2.3647275E+01, 1.9022736E+01, 1.3115522E+01, 137 | 1.2399357E+01, 1.2759508E+01], 138 | [1.2807377E+01, -1.7042725E+00, 1.1780364E+00, 139 | 4.3399963E-01, -1.1116072E+00], 140 | [2.0665069E+00, -3.5195189E-01, 3.6422433E-02, 141 | 9.1967085E-03, 3.5332536E-02], 142 | [8.6513472E-02, 4.7766102E-03, 3.9584261E-04, 143 | 1.6901585E-04, 3.3080380E-05], 144 | [5.8995860E-01, -6.5379760E-02, 9.3112756E-02, 145 | 3.4424680E-02, -8.8196889E-02], 146 | [1.0960713E-01, -2.1732833E-02, 2.9804232E-03, 147 | 6.9741215E-04, 2.8497415E-03], 148 | [6.1769588E-03, 0.0, 3.3263032E-05, 1.2946992E-05, 0.0]], 149 | "N": [ 150 | [-5.9610511E+01, 3.1534505E+02, 1.0340172E+03], 151 | [-1.5000000E+00, 9.8870997E+00, 3.7565475E+01], 152 | [4.2021322E+01, 2.7988676E+01, 2.6029492E+01], 153 | [4.7244037E+00, 1.5417343E+00, -6.0783095E-01], 154 | [-6.1153213E+00, -1.4689457E-01, -9.7742562E-03], 155 | [-9.9980337E-01, -6.8322712E-03, -3.3148813E-06], 156 | [1.6385664E-01, 6.2600036E-02, -2.5351881E-02], 157 | [-1.4994026E-01, -5.1489572E-03, -3.8746827E-04], 158 | [-3.0810372E-02, -2.8835863E-04, 1.7088177E-06] 159 | ], 160 | "R": [ 161 | [1.3054315E+02, 5.4188181E+02, 1.0382132E+03, 1.5676133E+03], 162 | [8.8333090E-01, 4.9312886E+00, 1.1014763E+01, 1.8397910E+01], 163 | [1.2557377E+02, 9.0208190E+01, 7.4669343E+01, 7.1646299E+01], 164 | [1.3900275E+02, 6.1762254E+00, 3.4090711E+00, -1.0866763E+00], 165 | [3.3035469E+01, -1.2279323E+00, -1.4511205E-01, -2.0968371E+00], 166 | [-8.5195924E-01, 1.4873153E-02, 6.3077387E-03, -7.6741168E-01], 167 | [1.2232896E+00, 8.7670455E-02, 5.6880253E-02, -1.9712341E-02], 168 | [3.5603023E-01, -1.2906694E-02, -2.0512736E-03, -2.9903595E-02], 169 | [0.0, 0.0, 0.0, -1.0766878E-02] 170 | ], 171 | "S": [ 172 | [1.3792630E+02, 4.7673468E+02, 9.7946589E+02, 1.6010461E+03], 173 | [9.3395024E-01, 4.0037367E+00, 9.3508283E+00, 1.6789315E+01], 174 | [1.2761836E+02, 1.0174512E+02, 8.7126730E+01, 8.4315871E+01], 175 | [1.1089050E+02, -8.9306371E+00, -2.3139202E+00, -1.0185043E+01], 176 | [1.9898457E+01, -4.2942435E+00, -3.2682118E-02, -4.6283954E+00], 177 | [9.6152996E-02, 2.0453847E-01, 4.6090022E-03, -1.0158749E+00], 178 | [9.6545918E-01, -7.1227776E-02, -1.4299790E-02, -1.2877783E-01], 179 | [2.0813850E-01, -4.4618306E-02, -1.2289882E-03, -5.5802216E-02], 180 | [0.0, 1.6822887E-03, 0.0, -1.2146518E-02] 181 | ], 182 | "T": [ 183 | [-1.9243000E+02, -6.0000000E+01, 1.3500000E+02, 3.0000000E+02], 184 | [-5.4798963E+00, -2.1528350E+00, 5.9588600E+00, 1.4861780E+01], 185 | [5.9572141E+01, 3.0449332E+01, 2.0325591E+01, 1.7214707E+01], 186 | [1.9675733E+00, -1.2946560E+00, 3.3013079E+00, -9.3862713E-01], 187 | [-7.8176011E+01, -3.0500735E+00, 1.2638462E-01, -7.3509066E-02], 188 | [-1.0963280E+01, -1.9226856E-01, -8.2883695E-04, 2.9576140E-04], 189 | [2.7498092E-01, 6.9877863E-03, 1.7595577E-01, -4.8095795E-02], 190 | [-1.3768944E+00, -1.0596207E-01, 7.9740521E-03, -4.7352054E-03], 191 | [-4.5209805E-01, -1.0774995E-02, 0.0, 0.0] 192 | ] 193 | } 194 | THERMO_COUPLE_KEYS = ['To', 'Vo', 'P1', 'P2', 'P3', 'P4', 'Q1', 'Q2', 'Q3'] 195 | THERMO_COUPLE_VOLTAGES = { 196 | "J": [-8.095, 0, 21.840, 45.494, 57.953, 69.553], 197 | "K": [-6.404, -3.554, 4.096, 16.397, 33.275, 69.553], 198 | "B": [0.291, 2.431, 13.820, None, None, None], 199 | "E": [-9.835, -5.237, 0.591, 24.964, 53.112, 76.373], 200 | "N": [-4.313, 0, 20.613, 47.513, None, None], 201 | "R": [-0.226, 1.469, 7.461, 14.277, 21.101, None], 202 | "S": [-0.236, 1.441, 6.913, 12.856, 18.693, None], 203 | "T": [-6.18, -4.648, 0, 9.288, 20.872, None] 204 | } 205 | 206 | COLD_JUNCTION_DATA = { 207 | "To": [4.2000000E+01, 2.5000000E+01, 2.5000000E+01, 2.5000000E+01, 7.0000000E+00, 2.5000000E+01, 2.5000000E+01, 208 | 2.5000000E+01], 209 | "Vo": [3.3933898E-04, 1.4950582E+00, 1.2773432E+00, 1.0003453E+00, 1.8210024E-01, 1.4067016E-01, 1.4269163E-01, 210 | 9.9198279E-01], 211 | "P1": [2.1196684E-04, 6.0958443E-02, 5.1744084E-02, 4.0514854E-02, 2.6228256E-02, 5.9330356E-03, 5.9829057E-03, 212 | 4.0716564E-02], 213 | "P2": [3.3801250E-06, -2.7351789E-04, -5.4138663E-05, -3.8789638E-05, -1.5485539E-04, 2.7736904E-05, 4.5292259E-06, 214 | 7.1170297E-04], 215 | "P3": [-1.4793289E-07, -1.9130146E-05, -2.2895769E-06, -2.8608478E-06, 2.1366031E-06, -1.0819644E-06, 216 | -1.3380281E-06, 6.8782631E-07], 217 | "P4": [-3.3571424E-09, -1.3948840E-08, -7.7947143E-10, -9.5367041E-10, 9.2047105E-10, -2.3098349E-09, 218 | -2.3742577E-09, 4.3295061E-11], 219 | "Q1": [-1.0920410E-02, -5.2382378E-03, -1.5173342E-03, -1.3948675E-03, -6.4070932E-03, 2.6146871E-03, 220 | -1.0650446E-03, 1.6458102E-02], 221 | "Q2": [-4.9782932E-04, -3.0970168E-04, -4.2314514E-05, -6.7976627E-05, 8.2161781E-05, -1.8621487E-04, 222 | -2.2042420E-04, 0.0000000E+00] 223 | } 224 | COLD_JUNCTION_KEYS = ["To", "Vo", "P1", "P2", "P3", "P4", "Q1", "Q2"] 225 | 226 | RTD_TYPES = { 227 | "PT100": [100, 0.00385], 228 | "PT1000": [1000, 0.00385], 229 | "CU100": [100, 0.00427], 230 | "NI100": [100, 0.00618], 231 | "NI120": [120, 0.00672], 232 | "NIFE": [604, 0.00518] 233 | } 234 | 235 | RHO_VALUES = { 236 | 'SEA': 0.01, 237 | 'SWAMP': 10, 238 | 'AVG': 100, 239 | 'AVERAGE': 100, 240 | 'DAMP': 100, 241 | 'DRY': 1000, 242 | 'SAND': 1E9, 243 | 'SANDSTONE': 1E9, 244 | } 245 | 246 | # END OF FILE 247 | -------------------------------------------------------------------------------- /electricpy/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | `electricpy.geometry` Geometry Sub Module. 4 | 5 | >>> import electricpy.geometry as geometry 6 | 7 | This Package help to handle coordinate geometry calculations which are required 8 | for plotting various graphs in electrical engineering. 9 | 10 | Built to support operations similar to Numpy and Scipy, this package is designed 11 | to aid in scientific calculations. 12 | """ 13 | ################################################################################ 14 | import cmath 15 | from typing import Tuple, Union 16 | from typing import Tuple 17 | 18 | 19 | class Point: 20 | """A point in 2D space. 21 | 22 | Parameters 23 | ---------- 24 | x : float 25 | The x coordinate of the point 26 | y : float 27 | The y coordinate of the point 28 | """ 29 | 30 | def __init__(self, x: float, y: float): 31 | """Initialize the point.""" 32 | self.x = x 33 | self.y = y 34 | 35 | def __iter__(self) -> float: 36 | """Return an iterator for the point.""" 37 | yield self.x 38 | yield self.y 39 | 40 | def __call__(self) -> Tuple: 41 | """Return the coordinates of the point.""" 42 | return (self.x, self.y) 43 | 44 | def __ne__(self, other): 45 | """Return true if the points are not equal.""" 46 | return not self == other 47 | 48 | def __eq__(self, __o: object) -> bool: 49 | """Return true if the points are equal.""" 50 | if isinstance(__o, Point): 51 | return self.x == __o.x and self.y == __o.y 52 | else: 53 | return False 54 | 55 | def __repr__(self) -> str: 56 | """Return the representation of the point.""" 57 | return f"Point({self.x}, {self.y})" 58 | 59 | def __str__(self) -> str: 60 | """Return the string representation of the point.""" 61 | return f"({self.x}, {self.y})" 62 | 63 | 64 | class Line: 65 | """A line in 2D space in the form . 66 | 67 | math:: ax + by + c = 0 68 | 69 | Parameters 70 | ---------- 71 | a : float 72 | The a coefficient of the line 73 | b : float 74 | The b coefficient of the line 75 | c : float 76 | The c coefficient of the line 77 | """ 78 | 79 | def __init__(self, a: float, b: float, c: float): 80 | """Initialize the line.""" 81 | self.a = a 82 | self.b = b 83 | self.c = c 84 | try: 85 | assert self.a or self.b != 0 86 | except AssertionError: 87 | raise AssertionError("line can not have all co-efficients zeros") 88 | 89 | def ordinate(self, x): 90 | """Return the ordinate of the line at the given x value.""" 91 | try: 92 | return -1*(self.a * x + self.c)/self.b 93 | except ZeroDivisionError: 94 | raise ZeroDivisionError( 95 | "ordinate is not defined for vertical lines") 96 | 97 | @staticmethod 98 | def construct(p1: Point, p2: Point): 99 | """Construct a line from two points.""" 100 | return line_equation(p1, p2) 101 | 102 | def __call__(self, p: Point) -> float: 103 | """Return the value of the point when subsituted in a line.""" 104 | return self.a * p.x + self.b * p.y + self.c 105 | 106 | def __str__(self): 107 | """Return the string representation of the line.""" 108 | if self.a == 0: 109 | return f"y = {-self.c/self.b}" 110 | elif self.b == 0: 111 | return f"x = {-self.c/self.a}" 112 | elif self.c == 0: 113 | if self.a < 0: 114 | return f'{-self.a}x + {-self.b}y = 0' 115 | else: 116 | return f"{self.a}x + {self.b}y = 0" 117 | elif self.a < 0: 118 | return f'{-self.a}x + {-self.b}y + {-self.c} = 0' 119 | else: 120 | return f"{self.a}x + {self.b}y + {self.c} = 0" 121 | 122 | def __repr__(self) -> str: 123 | """Return the representation of the line.""" 124 | return f"Line({self.a}, {self.b}, {self.c})" 125 | 126 | def slope(self): 127 | """Return the slope of the line.""" 128 | try: 129 | return -self.a / self.b 130 | except ZeroDivisionError: 131 | raise ZeroDivisionError("slope is not defined for vertical lines") 132 | 133 | def intercepts(self): 134 | """Return the intercepts of the line.""" 135 | data = dict() 136 | try: 137 | data['x'] = -self.c / self.a 138 | except ZeroDivisionError: 139 | data['x'] = None 140 | try: 141 | data['y'] = -self.c / self.b 142 | except ZeroDivisionError: 143 | data['y'] = None 144 | return data 145 | 146 | def __eq__(self, __o: object) -> bool: 147 | """Return true if the lines are equal.""" 148 | if self.a == 0 and __o.a == 0: 149 | if self.c == 0 and __o.c == 0: 150 | return True 151 | else: 152 | try: 153 | return self.b / __o.b == self.c / __o.c 154 | except ZeroDivisionError: 155 | return False 156 | 157 | if self.b == 0 and __o.b == 0: 158 | if self.c == 0 and __o.c == 0: 159 | return True 160 | else: 161 | try: 162 | return self.a / __o.a == self.c / __o.c 163 | except ZeroDivisionError: 164 | return False 165 | 166 | if self.c == 0 and __o.c == 0: 167 | try: 168 | return self.a / __o.a == self.b / __o.b 169 | except ZeroDivisionError: 170 | return False 171 | try: 172 | return self.a / __o.a == self.b / __o.b == self.c / __o.c 173 | except ZeroDivisionError: 174 | return False 175 | 176 | def distance(self, p: Point) -> float: 177 | """Return the distance of the point from the line.""" 178 | return line_distance(p, self) 179 | 180 | def foot_perpendicular(self, p: Point) -> Point: 181 | """Return the foot of the perpendicular from a point to line.""" 182 | return foot_perpendicular(p, self) 183 | 184 | def image(self, p: Point) -> Point: 185 | """Return the image of a point with respect to line.""" 186 | return point_image(p, self) 187 | 188 | def intersection(self, l1: object) -> Union[Point, None]: 189 | """Return the intersection of two lines.""" 190 | return line_intersection(self, l1) 191 | 192 | def angle_btw_lines(l1: Line, l2: Line) -> float: 193 | """Return angle (in radians) between two lines l1 and l2.""" 194 | a1, b1 = l1.a, l1.b 195 | a2, b2 = l2.a, l2.b 196 | 197 | if a1*a2 + b1*b2 == 0: 198 | return cmath.pi/2 199 | 200 | ans = abs((b1*a2 - a1*b2)/(a1*a2 + b1*b2)) 201 | return cmath.atan(ans) 202 | 203 | def line_intersection(l1: Line, l2: Line) -> Point: 204 | """Calculate the intersection point of two lines.""" 205 | a1, b1, c1 = l1.a, l1.b, -l1.c 206 | a2, b2, c2 = l2.a, l2.b, -l2.c 207 | 208 | try: 209 | x = (b2*c1 - b1*c2) / (a1*b2 - a2*b1) 210 | y = (a1*c2 - a2*c1) / (a1*b2 - a2*b1) 211 | except ZeroDivisionError: 212 | raise ZeroDivisionError("lines are parallel") 213 | return Point(x, y) 214 | 215 | 216 | def distance(p1: Point, p2: Point) -> float: 217 | """Calculate the distance between two points.""" 218 | d = ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5 219 | 220 | if isinstance(d, complex): 221 | return d.real 222 | else: 223 | return d 224 | 225 | 226 | def section(p1: Point, p2: Point, ratio: Union[Tuple, float]) -> Point: 227 | """Calculate the point on a line section.""" 228 | if isinstance(ratio, float): 229 | return Point(p1.x + ratio * (p2.x - p1.x), p1.y + ratio * (p2.y - p1.y)) 230 | else: 231 | (x, y) = ratio 232 | m = x / (x + y) 233 | n = y / (x + y) 234 | return Point(n*p1.x + m*p2.x, n*p1.y + m*p2.y) 235 | 236 | 237 | def midpoint(p1: Point, p2: Point) -> Point: 238 | """Calculate the midpoint between two points.""" 239 | return Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2) 240 | 241 | 242 | def slope(p1: Point, p2: Point) -> float: 243 | """Calculate the slope between two points.""" 244 | try: 245 | return (p2.y - p1.y) / (p2.x - p1.x) 246 | except ZeroDivisionError: 247 | raise ZeroDivisionError("slope is not defined for vertical lines") 248 | 249 | 250 | def line_equation(p1: Point, p2: Point) -> Line: 251 | """Calculate the line equation between two points.""" 252 | # try: 253 | # a = slope(p1, p2) 254 | # except ZeroDivisionError: 255 | # assert p1.x == p2.x 256 | # a = 1 257 | # c = -p1.x 258 | # return Line(a, 0, c) 259 | # b = -1 260 | # c = p1.y - a * p1.x 261 | # return Line(a, b, c) 262 | return Line(p2.y - p1.y, p1.x - p2.x, p1.x*(p1.y - p2.y) + p1.y*(p2.x - p1.x)) 263 | 264 | 265 | def slope_point_line(slope: float, p: Point) -> Line: 266 | """Calculate the line equation from a slope and a point.""" 267 | a = slope 268 | b = -1 269 | c = p.y - a * p.x 270 | return Line(a, b, c) 271 | 272 | 273 | def line_distance(p: Point, line: Line) -> float: 274 | """Calculate the distance between a point and a line.""" 275 | return abs(line(p)) / (line.a ** 2 + line.b ** 2) ** 0.5 276 | 277 | 278 | def foot_perpendicular(p: Point, line: Line) -> Point: 279 | """Calculate the foot perpendicular from a point to a line.""" 280 | d = -line(p)/(line.a**2+line.b**2) 281 | x = p.x + line.a * d 282 | y = p.y + line.b * d 283 | return Point(x, y) 284 | 285 | 286 | def point_image(p: Point, line: Line) -> Point: 287 | """Calculate the image of a point when a line is acting like a mirror.""" 288 | p1: Point = foot_perpendicular(p, line) 289 | return Point(2*p1.x - p.x, 2*p1.y - p.y) 290 | 291 | 292 | def perpendicular_bisector(p1: Point, p2: Point) -> Line: 293 | """Calculate the perpendicular bisector of two points.""" 294 | try: 295 | m = slope(p1, p2) 296 | except ZeroDivisionError: 297 | return Line(0, 1, -(p1.y + p2.y) / 2) 298 | 299 | if m == 0: 300 | return Line(1, 0, -(p1.x + p2.x) / 2) 301 | else: 302 | x1, x2 = p1.x, p2.x 303 | y1, y2 = p1.y, p2.y 304 | return Line(2*(x2 - x1), 2*(y2 - y1), -x1**2 + -y1**2 + x2**2 + y2**2) 305 | 306 | 307 | def colinear(p1: Point, p2: Point, p3: Point) -> bool: 308 | """Determine whether 3 points are colinear or not .""" 309 | l1 = line_equation(p1, p2) 310 | return l1(p3) == 0 311 | -------------------------------------------------------------------------------- /electricpy/geometry/circle.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | `electricpy.geometry.circle` - Collection of methods which operate on \ 4 | cartesial Circle. 5 | 6 | >>> import electricpy.geometry.circle as circle 7 | 8 | This sub package help to handle coordinate geometry calculatios on circles 9 | which are required for plotting various graphs in electrical engineering. 10 | """ 11 | ################################################################################ 12 | from typing import Union 13 | from electricpy import geometry 14 | from electricpy.geometry import Line 15 | from electricpy.geometry import Point 16 | import cmath 17 | 18 | class Circle: 19 | r""" 20 | Circle in cartesian plane. 21 | 22 | Parameters 23 | ---------- 24 | center : Point 25 | The center of the circle 26 | radius : float 27 | The radius of the circle 28 | """ 29 | 30 | def __init__(self, center: Union[Point, tuple, list], radius: float): 31 | """Initialize the circle.""" 32 | if isinstance(center, tuple) or isinstance(center, list): 33 | assert len(center) == 2, "Center must be a 2-tuple or list" 34 | center = Point(center[0], center[1]) 35 | 36 | self.center = center 37 | self.radius = radius 38 | 39 | def area(self) -> float: 40 | """Return the area of the circle.""" 41 | return cmath.pi * self.radius ** 2 42 | 43 | def circumference(self) -> float: 44 | """Return the circumference of the circle.""" 45 | return 2 * cmath.pi * self.radius 46 | 47 | def tangent(self, p: Point) -> Line: 48 | """Return the tangent line to the circle at point p.""" 49 | # try: 50 | # m = Geometry.slope(self.center, p) 51 | # except ZeroDivisionError: 52 | # return Line(0, 1, -p.y) 53 | 54 | # if m == 0: 55 | # return Line(1, 0, -p.x) 56 | 57 | # m = -1/m 58 | # return Geometry.slope_point_line(m, p) 59 | x, y = p.x, p.y 60 | c_x , c_y = -self.center.x, -self.center.y 61 | return Line(x + c_x, y + c_y, x*c_x + y*c_y + c_x**2 + c_y**2 - self.radius**2) 62 | 63 | def normal(self, p: Point) -> Line: 64 | """Return the normal line to the circle at point p.""" 65 | return Line.construct(p, self.center) 66 | 67 | def power(self, p: Point) -> float: 68 | """Return the power of the circle at point p.""" 69 | return geometry.distance(self.center, p) ** 2 - self.radius ** 2 70 | 71 | def is_tangent(self, l: Line) -> bool: 72 | """Return True if the line is tangent to the circle.""" 73 | return l.distance(self.center) == self.radius 74 | 75 | def is_normal(self, l: Line) -> bool: 76 | """Return True if the line is normal to the circle.""" 77 | return l(self.center) == 0 78 | 79 | def equation(self) -> str: 80 | """Return the equation of the circle.""" 81 | (x, y) = self.center 82 | return f"x^2 + 2*{-x}*x + 2*{-y}*y + y^2 + {x**2 + y**2 - self.radius**2} = 0" 83 | 84 | def parametric_equation(self, theta_resolution: float = 0.01, semi=False): 85 | """Return the parametric equation of the circle.""" 86 | i = 0 87 | if semi: 88 | k = cmath.pi 89 | else: 90 | k = 2 * cmath.pi 91 | while i < k: 92 | yield self.center.x + self.radius * cmath.cos(i), self.center.y + self.radius * cmath.sin(i) 93 | i += theta_resolution 94 | 95 | def sector_length(self, theta: float) -> float: 96 | """Return the length of a sector of the circle which subtended angle theta(radians) at center.""" 97 | return self.radius * theta 98 | 99 | def sector_area(self, theta: float) -> float: 100 | """Return the area of a sector of the circle which subtended angle theta(radians) at center.""" 101 | return self.radius ** 2 * theta / 2 102 | 103 | def intersetion(self, other) -> Union[Point, None]: 104 | """Return the intersection point of the circle and the other circle.""" 105 | if isinstance(other, Circle): 106 | c1 = self.center 107 | c2 = other.center 108 | 109 | m = geometry.slope(c1, c2) 110 | theta = cmath.atan(m) 111 | 112 | d = geometry.distance(c1, c2) 113 | 114 | if d == self.radius + other.radius: 115 | """Two circles are touching each other""" 116 | x = c1.x + self.radius * cmath.cos(theta) 117 | y = c1.y + self.radius * cmath.sin(theta) 118 | return Point(x, y) 119 | 120 | elif d < self.radius + other.radius: 121 | """Two circles intersect""" 122 | r1 = self.radius 123 | r2 = other.radius 124 | 125 | theta = cmath.asin(r2 / d) 126 | 127 | x = c1.x + r1 * cmath.cos(theta) 128 | y = c1.y + r1 * cmath.sin(theta) 129 | 130 | p1 = Point(x, y) 131 | l = Line.construct(c1, c2) 132 | p2 = l.image(p1) 133 | 134 | return (p1, p2) 135 | else: 136 | return None 137 | 138 | else: 139 | raise ValueError("Can only intersect with another circle") 140 | 141 | def __repr__(self): 142 | """Return a string representation of the circle.""" 143 | return 'Circle(center={0}, radius={1})'.format(self.center, self.radius) 144 | 145 | def __eq__(self, other): 146 | """Return True if the circles are equal.""" 147 | if isinstance(other, Circle): 148 | return self.center == other.center and self.radius == other.radius 149 | else: 150 | return False 151 | 152 | def __ne__(self, other): 153 | """Return True if the circles are not equal.""" 154 | return not self == other 155 | 156 | def __hash__(self): 157 | """Return a hash of the circle.""" 158 | return hash((self.center, self.radius)) 159 | 160 | def __str__(self): 161 | """Return a string representation of the circle.""" 162 | return 'Circle(center={0}, radius={1})'.format(self.center, self.radius) 163 | 164 | def construct(p0: Point, p1: Point, p2: Point) -> Circle: 165 | """Return a circle which passes through the three points.""" 166 | try: 167 | assert not geometry.colinear(p0, p1, p2) 168 | except AssertionError: 169 | raise AssertionError("Circle can not be constructed from three points that are colinear") 170 | 171 | l1 = geometry.perpendicular_bisector(p0, p1) 172 | l2 = geometry.perpendicular_bisector(p1, p2) 173 | 174 | center = l1.intersection(l2) 175 | radius = geometry.distance(center, p0) 176 | 177 | return Circle(center, radius) 178 | -------------------------------------------------------------------------------- /electricpy/geometry/triangle.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | `electricpy.geometry.triangle` - Collection of methods which operate on \ 4 | cartesial Triangle. 5 | 6 | >>> import electricpy.geometry.triangle as triangle 7 | 8 | This sub package help to handle coordinate geometry calculatios on triangle 9 | which are required for plotting various graphs in electrical engineering. 10 | """ 11 | ################################################################################ 12 | from typing import List 13 | from electricpy.geometry import Point 14 | from electricpy.geometry import Line 15 | from electricpy import geometry 16 | 17 | #Type cast complex to float 18 | 19 | class Triangle: 20 | r""" 21 | Triangle in cartesian plane. 22 | 23 | Parameters 24 | ---------- 25 | points : list of Point, List[Point] 26 | The points of the triangle in cartesian plane. 27 | """ 28 | 29 | def __init__(self, *points: List[Point]): 30 | """Initialize the triangle.""" 31 | if len(points) != 3: 32 | raise ValueError('Triangle must have 3 points') 33 | 34 | self.a = geometry.distance(points[0], points[1]) 35 | self.b = geometry.distance(points[1], points[2]) 36 | self.c = geometry.distance(points[0], points[2]) 37 | 38 | self.l1 = geometry.line_equation(points[0], points[1]) 39 | self.l2 = geometry.line_equation(points[1], points[2]) 40 | self.l3 = geometry.line_equation(points[0], points[2]) 41 | 42 | if not self.__is_valid(): 43 | raise ValueError("Invalid triangle") 44 | else: 45 | self.points = points 46 | 47 | def centroid(self): 48 | """Return the centroid of the triangle.""" 49 | x = (self.points[0].x + self.points[1].x + self.points[2].x) / 3 50 | y = (self.points[0].y + self.points[1].y + self.points[2].y) / 3 51 | return Point(x, y) 52 | 53 | def in_center(self): 54 | """Return the inCenter of the triangle.""" 55 | s = self.perimeters() 56 | i = (self.a * self.points[2].x + self.b * self.points[0].x + self.c * self.points[1].x) / s, \ 57 | (self.a * self.points[2].y + self.b * self.points[0].y + self.c * self.points[1].y) / s 58 | return Point(i[0], i[1]) 59 | 60 | def in_radius(self): 61 | """Return the inRadius of the triangle.""" 62 | return self.area() / (self.perimeters()/2) 63 | 64 | def ortho_center(self): 65 | """Return the orthoCenter of the triangle.""" 66 | d1 = self.l2.foot_perpendicular(self.points[0]) 67 | d2 = self.l3.foot_perpendicular(self.points[1]) 68 | 69 | alt_1 = Line.construct(self.points[0], d1) 70 | alt_2 = Line.construct(self.points[1], d2) 71 | 72 | return alt_1.intersection(alt_2) 73 | 74 | def circum_center(self): 75 | """Return the circumCenter of the triangle.""" 76 | pb_1 = geometry.perpendicular_bisector(self.points[0], self.points[1]) 77 | pb_2 = geometry.perpendicular_bisector(self.points[1], self.points[2]) 78 | 79 | return pb_1.intersection(pb_2) 80 | 81 | def circum_radius(self): 82 | """Return the circumRadius of the triangle.""" 83 | return (self.a*self.b*self.c) / (4 * self.area()) 84 | 85 | def area(self): 86 | """Return the area of the triangle.""" 87 | s = (self.a + self.b + self.c) / 2 88 | return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5 89 | 90 | def perimeters(self): 91 | """Return the perimeters of the triangle.""" 92 | return self.a + self.b + self.c 93 | 94 | def __is_valid(self): 95 | return (self.a + self.b > self.c) and (self.a + self.c > self.b) and (self.b + self.c > self.a) -------------------------------------------------------------------------------- /electricpy/latex.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | LaTeX Support Module to help Generate LaTeX formatted Math Symbols and Formulas. 4 | 5 | >>> from electricpy import latex 6 | 7 | This module is specifically designed to help create strings that represent LaTeX 8 | formatted formulas, functions, and more for easy printing in tools such as 9 | Jupyter Notebooks. 10 | 11 | Built to support operations similar to Numpy and Scipy, this package is designed 12 | to aid in scientific calculations. 13 | """ 14 | ################################################################################ 15 | 16 | import cmath as _c 17 | 18 | import numpy as _np 19 | 20 | 21 | # Define Complex LaTeX Generator 22 | def clatex(val, round=3, polar=True, predollar=True, postdollar=True, 23 | double=False): 24 | """ 25 | Complex Value Latex Generator. 26 | 27 | Function to generate a LaTeX string of complex value(s) 28 | in either polar or rectangular form. May generate both dollar 29 | signs. 30 | 31 | Parameters 32 | ---------- 33 | val: complex 34 | The complex value to be printed, if value 35 | is a list or numpy array, the result will be 36 | demonstrated as a matrix. 37 | round: int, optional 38 | Control to specify number of decimal places 39 | that should displayed. default=True 40 | polar: bool, optional 41 | Control argument to force result into polar 42 | coordinates instead of rectangular. default=True 43 | predollar: bool, optional 44 | Control argument to enable/disable the dollar 45 | sign before the string. default=True 46 | postdollar: bool, optional 47 | Control argument to enable/disable the dollar 48 | sign after the string. default=True 49 | double: bool, optional 50 | Control argument to specify whether or not 51 | LaTeX dollar signs should be double or single, 52 | default=False 53 | 54 | Returns 55 | ------- 56 | latex: str 57 | LaTeX string for the complex value. 58 | """ 59 | # Define Interpretation Functions 60 | def polarstring(val, round): 61 | mag, ang_r = _c.polar(val) # Convert to polar form 62 | ang = _np.degrees(ang_r) # Convert to degrees 63 | mag = _np.around(mag, round) # Round 64 | ang = _np.around(ang, round) # Round 65 | latex = str(mag) + '∠' + str(ang) + '°' 66 | return latex 67 | 68 | def rectstring(val, round): 69 | real = _np.around(val.real, round) # Round 70 | imag = _np.around(val.imag, round) # Round 71 | if imag > 0: 72 | latex = str(real) + "+j" + str(imag) 73 | else: 74 | latex = str(real) + "-j" + str(abs(imag)) 75 | return latex 76 | 77 | # Interpret as numpy array if simple list 78 | if isinstance(val, list): 79 | val = _np.asarray(val) # Ensure that input is array 80 | # Find length of the input array 81 | if isinstance(val, _np.ndarray): 82 | shp = val.shape 83 | try: 84 | row, col = shp # Interpret Shape of Object 85 | except ValueError: 86 | row = shp[0] 87 | col = 1 88 | _ = val.size 89 | # Open Matrix 90 | latex = r'\begin{bmatrix}' 91 | # Iteratively Process Each Item in Array 92 | for ri in range(row): 93 | if ri != 0: # Insert Row Separator 94 | latex += r'\\' 95 | if col > 1: 96 | for ci in range(col): 97 | if ci != 0: # Insert Column Separator 98 | latex += r' & ' 99 | # Add Complex Represetation of Value 100 | if polar: 101 | latex += polarstring(val[ri][ci], round) 102 | else: 103 | latex += rectstring(val[ri][ci], round) 104 | else: 105 | # Add Complex Represetation of Value 106 | if polar: 107 | latex += polarstring(val[ri], round) 108 | else: 109 | latex += rectstring(val[ri], round) 110 | # Close Matrix 111 | latex += r'\end{bmatrix}' 112 | elif isinstance(val, complex): 113 | # Treat as Polar When Directed 114 | if polar: 115 | latex = polarstring(val, round) 116 | else: 117 | latex = rectstring(val, round) 118 | else: 119 | raise ValueError("Invalid Input Type") 120 | # Add Dollar Sign pre-post 121 | if double: 122 | dollar = r'$$' 123 | else: 124 | dollar = r'$' 125 | if predollar: 126 | latex = dollar + latex 127 | if postdollar: 128 | latex = latex + dollar 129 | return latex 130 | 131 | 132 | # Define Transfer Function LaTeX Generator 133 | def tflatex(sys, sysp=None, var='s', predollar=True, 134 | postdollar=True, double=False, tolerance=1e-8): 135 | r""" 136 | Transfer Function LaTeX String Generator. 137 | 138 | LaTeX string generating function to create a transfer 139 | function string in LaTeX. Particularly useful for 140 | demonstrating systems in Interactive Python Notebooks. 141 | 142 | Parameters 143 | ---------- 144 | sys: list 145 | If provided in conjunction with optional 146 | parameter `sysp`, the parameter `sys` will 147 | act as the numerator set. Otherwise, can be 148 | passed as a list containing two sublists, 149 | the first being the numerator set, and the 150 | second being the denominator set. 151 | sysp: list, optional 152 | If provided, this input will act as the 153 | denominator of the transfer function. 154 | var: str, optional 155 | The variable that should be printed for each 156 | term (i.e. 's' or 'j\omega'). default='s' 157 | predollar: bool, optional 158 | Control argument to enable/disable the dollar 159 | sign before the string. default=True 160 | postdollar: bool, optional 161 | Control argument to enable/disable the dollar 162 | sign after the string. default=True 163 | double: bool, optional 164 | Control argument to specify whether or not 165 | LaTeX dollar signs should be double or single, 166 | default=False 167 | tolerance: float, optional 168 | The floating point tolerance cutoff to evaluate 169 | each term against. If the absolute value of the 170 | particular term is greater than the tolerance, 171 | the value will be printed, if not, it will not 172 | be printed. default=1e-8 173 | 174 | Returns 175 | ------- 176 | latex: str 177 | LaTeX string for the transfer function. 178 | """ 179 | # Collect Numerator and Denominator Terms 180 | if isinstance(sysp, (list, tuple, _np.ndarray)): 181 | num = sys 182 | den = sysp 183 | else: 184 | num, den = sys 185 | 186 | # Generate String Function 187 | def genstring(val): 188 | length = len(val) 189 | strg = '' 190 | for i, v in enumerate(val): 191 | # Add Each Term to String 192 | if abs(v) > tolerance: 193 | # Add '+' Symbol After Each Term 194 | if i != 0: 195 | strg += r'+' 196 | strg += str(v) 197 | # Determine Exponent 198 | xpnt = length - i - 1 199 | if xpnt == 1: 200 | strg += var 201 | elif xpnt == 0: 202 | pass # Don't Do Anything 203 | else: 204 | strg += var + r'^{' + str(xpnt) + r'}' 205 | return strg 206 | 207 | # Generate Total TF String 208 | latex = r'\frac{' + genstring(num) + r'}{' 209 | latex += genstring(den) + r'}' 210 | # Add Dollar Sign pre-post 211 | if double: 212 | dollar = r'$$' 213 | else: 214 | dollar = r'$' 215 | if predollar: 216 | latex = dollar + latex 217 | if postdollar: 218 | latex = latex + dollar 219 | return latex 220 | -------------------------------------------------------------------------------- /electricpy/math.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | Common Mathematic Functions for Electrical Engineering. 4 | 5 | >>> from electricpy import math as epmath 6 | 7 | Focussed on simplifying common mathematic formulas for electrical engineering, 8 | this module exposes a few common functions like convolution, step-functions, 9 | etc. 10 | 11 | Built to support operations similar to Numpy and Scipy, this package is designed 12 | to aid in scientific calculations. 13 | """ 14 | ################################################################################ 15 | 16 | import numpy as _np 17 | import scipy.signal as _sig 18 | from scipy.integrate import quad as integrate 19 | 20 | 21 | # Define convolution function 22 | def convolve(tuple): 23 | """ 24 | Filter Convolution Function. 25 | 26 | Given a tuple of terms, convolves all terms in tuple to 27 | return one tuple as a numpy array. 28 | 29 | Parameters 30 | ---------- 31 | tuple: tuple of numpy.ndarray 32 | Tuple of terms to be convolved. 33 | 34 | Returns 35 | ------- 36 | c: The convolved set of the individual terms. 37 | i.e. numpy.ndarray([ x1, x2, x3, ..., xn ]) 38 | """ 39 | c = _sig.convolve(tuple[0], tuple[1]) 40 | if (len(tuple) > 2): 41 | # Iterate starting with second element and continuing 42 | for i in range(2, len(tuple)): 43 | c = _sig.convolve(c, tuple[i]) 44 | return (c) 45 | 46 | 47 | # Define Step function 48 | def step(t): 49 | r""" 50 | Step Function [ u(t) ]. 51 | 52 | Simple implimentation of numpy.heaviside function to provide standard 53 | step-function as specified to be zero at :math:`x < 0`, and one at 54 | :math:`x \geq 0`. 55 | 56 | Examples 57 | -------- 58 | >>> import numpy as np 59 | >>> from electricpy.math import step 60 | >>> t = np.array([-10, -8, -5, -3, 0, 1, 2, 5, 7, 15]) 61 | >>> step(t) 62 | array([0., 0., 0., 0., 1., 1., 1., 1., 1., 1.]) 63 | 64 | Parameters 65 | ---------- 66 | t: arraylike 67 | Time samples for which the step response should be generated. 68 | """ 69 | return _np.heaviside(t, 1) 70 | 71 | 72 | # Arbitrary Waveform RMS Calculating Function 73 | def funcrms(func, T): 74 | """ 75 | Root-Mean-Square (RMS) Evaluator for Callable Functions. 76 | 77 | Integral-based RMS calculator, evaluates the RMS value 78 | of a repetative signal (f) given the signal's specific 79 | period (T) 80 | 81 | Parameters 82 | ---------- 83 | func: float 84 | The periodic function, a callable like f(t) 85 | T: float 86 | The period of the function f, so that f(0)==f(T) 87 | 88 | Returns 89 | ------- 90 | RMS: The RMS value of the function (f) over the interval ( 0, T ) 91 | """ 92 | fn = lambda x: func(x) ** 2 93 | integral, _ = integrate(fn, 0, T) 94 | return _np.sqrt(1 / T * integral) 95 | 96 | 97 | # Define Gaussian Function 98 | def gaussian(x, mu=0, sigma=1): 99 | """ 100 | Gaussian Function. 101 | 102 | This function is designed to generate the gaussian 103 | distribution curve with configuration mu and sigma. 104 | 105 | Parameters 106 | ---------- 107 | x: float 108 | The input (array) x. 109 | mu: float, optional 110 | Optional control argument, default=0 111 | sigma: float, optional 112 | Optional control argument, default=1 113 | 114 | Returns 115 | ------- 116 | Computed gaussian (numpy.ndarray) of the input x 117 | """ 118 | return (1 / (sigma * _np.sqrt(2 * _np.pi)) * 119 | _np.exp(-(x - mu) ** 2 / (2 * sigma ** 2))) 120 | 121 | 122 | # Define Gaussian Distribution Function 123 | def gausdist(x, mu=0, sigma=1): 124 | """ 125 | Gaussian Distribution Function. 126 | 127 | This function is designed to calculate the generic 128 | distribution of a gaussian function with controls 129 | for mu and sigma. 130 | 131 | Parameters 132 | ---------- 133 | x: numpy.ndarray 134 | The input (array) x 135 | mu: float, optional 136 | Optional control argument, default=0 137 | sigma: float, optional 138 | Optional control argument, default=1 139 | 140 | Returns 141 | ------- 142 | F: numpy.ndarray 143 | Computed distribution of the gausian function at the 144 | points specified by (array) x 145 | """ 146 | # Define Integrand 147 | def integrand(sq): 148 | return _np.exp(-sq ** 2 / 2) 149 | 150 | try: 151 | lx = len(x) # Find length of Input 152 | except: 153 | lx = 1 # Length 1 154 | x = [x] # Pack into list 155 | F = _np.zeros(lx, dtype=_np.float64) 156 | for i in range(lx): 157 | x_tmp = x[i] 158 | # Evaluate X (altered by mu and sigma) 159 | X = (x_tmp - mu) / sigma 160 | integral = integrate(integrand, _np.NINF, X) # Integrate 161 | result = 1 / _np.sqrt(2 * _np.pi) * integral[0] # Evaluate Result 162 | F[i] = result 163 | # Return only the 0-th value if there's only 1 value available 164 | if len(F) == 1: 165 | F = F[0] 166 | return F 167 | 168 | 169 | # Define Probability Density Function 170 | def probdensity(func, x, x0=0, scale=True): 171 | """ 172 | Probability Density Function. 173 | 174 | This function uses an integral to compute the probability 175 | density of a given function. 176 | 177 | Parameters 178 | ---------- 179 | func: function 180 | The function for which to calculate the PDF 181 | x: numpy.ndarray 182 | The (array of) value(s) at which to calculate 183 | the PDF 184 | x0: float, optional 185 | The lower-bound of the integral, starting point 186 | for the PDF to be calculated over, default=0 187 | scale: bool, optional 188 | The scaling to be applied to the output, 189 | default=True 190 | 191 | Returns 192 | ------- 193 | sumx: numpy.ndarray 194 | The (array of) value(s) computed as the PDF at 195 | point(s) x 196 | """ 197 | sumx = _np.array([]) 198 | try: 199 | lx = len(x) # Find length of Input 200 | except: 201 | lx = 1 # Length 1 202 | x = [x] # Pack into list 203 | # Recursively Find Probability Density 204 | for i in range(lx): 205 | sumx = _np.append(sumx, integrate(func, x0, x[i])[0]) 206 | # Return only the 0-th value if there's only 1 value available 207 | if len(sumx) == 1: 208 | sumx = sumx[0] 209 | else: 210 | if scale: 211 | mx = sumx.max() 212 | sumx /= mx 213 | elif scale != False: 214 | sumx /= scale 215 | return sumx 216 | 217 | 218 | # Define Real FFT Evaluation Function 219 | def rfft(arr, dt=0.01, absolute=True, resample=True): 220 | """ 221 | RFFT Function. 222 | 223 | This function is designed to evaluat the real FFT 224 | of a input signal in the form of an array or list. 225 | 226 | Parameters 227 | ---------- 228 | arr: numpy.ndarray 229 | The input array representing the signal 230 | dt: float, optional 231 | The time-step used for the array, 232 | default=0.01 233 | absolute: bool, optional 234 | Control argument to force absolute 235 | values, default=True 236 | resample: bool, optional 237 | Control argument specifying whether 238 | the FFT output should be resampled, 239 | or if it should have a specific 240 | resampling rate, default=True 241 | 242 | Returns 243 | ------- 244 | FFT Array 245 | """ 246 | # Calculate with Absolute Values 247 | if absolute: 248 | fourier = abs(_np.fft.rfft(arr)) 249 | else: 250 | fourier = _np.fft.rfft(arr) 251 | if resample: 252 | # Evaluate the Downsampling Ratio 253 | dn = int(dt * len(arr)) 254 | # Downsample to remove unnecessary points 255 | fixedfft = filter.dnsample(fourier, dn) 256 | return (fixedfft) 257 | elif not resample: 258 | return (fourier) 259 | else: 260 | # Condition Resample Value 261 | resample = int(resample) 262 | # Downsample to remove unnecessary points 263 | fixedfft = filter.dnsample(fourier, resample) 264 | return fixedfft 265 | -------------------------------------------------------------------------------- /electricpy/phasors.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | Functions to Support Common Electrical Engineering Formulas Related to Phasors. 4 | 5 | >>> from electricpy import phasors 6 | 7 | Filled with calculators, evaluators, and plotting functions related to 8 | electrical phasors, this package will provide a wide array of capabilities to 9 | any electrical engineer. 10 | 11 | Built to support operations similar to Numpy and Scipy, this package is designed 12 | to aid in scientific calculations. 13 | """ 14 | ################################################################################ 15 | 16 | import numpy as _np 17 | import cmath as _c 18 | 19 | 20 | # Define Phase Angle Generator 21 | def phs(ang): 22 | """ 23 | Complex Phase Angle Generator. 24 | 25 | Generate a complex value given the phase angle 26 | for the complex value. 27 | 28 | Same as `phase`. 29 | 30 | Parameters 31 | ---------- 32 | ang: float 33 | The angle (in degrees) for which 34 | the value should be calculated. 35 | 36 | See Also 37 | -------- 38 | electricpy.cprint: Complex Variable Printing Function 39 | electricpy.phasors.phasorlist: Phasor Generator for List or Array 40 | electricpy.phasors.phasorz: Impedance Phasor Generator 41 | electricpy.phasors.phasor: Phasor Generating Function 42 | """ 43 | # Return the Complex Angle Modulator 44 | return _np.exp(1j * _np.radians(ang)) 45 | 46 | 47 | phase = phs # Create Duplicate Name 48 | 49 | 50 | # Define Phasor Generator 51 | def phasor(mag, ang=0): 52 | """ 53 | Complex Phasor Generator. 54 | 55 | Generates the standard Pythonic complex representation 56 | of a phasor voltage or current when given the magnitude 57 | and angle of the specific voltage or current. 58 | 59 | Parameters 60 | ---------- 61 | mag: float 62 | The Magnitude of the Voltage/Current 63 | ang: float 64 | The Angle (in degrees) of the Voltage/Current 65 | 66 | Returns 67 | ------- 68 | phasor: complex 69 | Standard Pythonic Complex Representation of 70 | the specified voltage or current. 71 | 72 | Examples 73 | -------- 74 | >>> from electricpy import phasors 75 | >>> phasor(67, 120) # 67 volts at angle 120 degrees 76 | (-33.499999999999986+58.02370205355739j) 77 | 78 | See Also 79 | -------- 80 | electricpy.cprint: Complex Variable Printing Function 81 | electricpy.phasors.phasorlist: Phasor Generator for List or Array 82 | electricpy.phasors.phasorz: Impedance Phasor Generator 83 | electricpy.phasors.phs: Complex Phase Angle Generator 84 | """ 85 | # Test for Tuple/List Arg 86 | if isinstance(mag, (tuple, list, _np.ndarray)): 87 | ang = mag[1] 88 | mag = mag[0] 89 | return _c.rect(mag, _np.radians(ang)) 90 | 91 | 92 | # Define Impedance Conversion function 93 | def phasorz(C=None, L=None, freq=60, complex=True): 94 | r""" 95 | Phasor Impedance Generator. 96 | 97 | This function's purpose is to generate the phasor-based 98 | impedance of the specified input given as either the 99 | capacitance (in Farads) or the inductance (in Henreys). 100 | The function will return the phasor value (in Ohms). 101 | 102 | .. math:: Z = \frac{-j}{\omega*C} 103 | 104 | .. math:: Z = j*\omega*L 105 | 106 | where: 107 | 108 | .. math:: \omega = 2*\pi*freq 109 | 110 | Parameters 111 | ---------- 112 | C: float, optional 113 | The capacitance value (specified in Farads), 114 | default=None 115 | L: float, optional 116 | The inductance value (specified in Henreys), 117 | default=None 118 | freq: float, optional 119 | The system frequency to be calculated upon, default=60 120 | complex: bool, optional 121 | Control argument to specify whether the returned 122 | value should be returned as a complex value. 123 | default=True 124 | 125 | Returns 126 | ------- 127 | Z: complex 128 | The ohmic impedance of either C or L (respectively). 129 | """ 130 | w = 2 * _np.pi * freq 131 | # C Given in ohms, return as Z 132 | if C is not None: 133 | Z = -1 / (w * C) 134 | # L Given in ohms, return as Z 135 | if L is not None: 136 | Z = w * L 137 | # If asked for imaginary number 138 | if complex: 139 | Z *= 1j 140 | return Z 141 | 142 | 143 | # Define Phasor Array Generator 144 | def phasorlist(arr): 145 | """ 146 | Complex Phasor Generator for 2-D Array or 2-D List. 147 | 148 | Generates the standard Pythonic complex representation 149 | of a phasor voltage or current when given the magnitude 150 | and angle of the specific voltage or current for a list 151 | or array of values. 152 | 153 | Parameters 154 | ---------- 155 | arr: array-like 156 | 2-D array or list of magnitudes and angles. 157 | Each item must be set of magnitude and angle 158 | in form of: [mag, ang]. 159 | 160 | Returns 161 | ------- 162 | list[complex]: List of standard Pythonic complex representation of the 163 | specified voltage or current. 164 | 165 | Examples 166 | -------- 167 | >>> import numpy as np 168 | >>> from electricpy import phasors 169 | >>> voltages = np.array([ 170 | ... [67,0], 171 | ... [67,-120], 172 | ... [67,120] 173 | ... ]) 174 | >>> Vset = phasors.phasorlist( voltages ) 175 | >>> print(Vset) 176 | 177 | See Also 178 | -------- 179 | electricpy.cprint: Complex Variable Printing Function 180 | electricpy.phasors.phasor: Phasor Generating Function 181 | electricpy.phasors.vectarray: Magnitude/Angle Array Pairing Function 182 | electricpy.phasors.phasorz: Impedance Phasor Generator 183 | """ 184 | # Use List Comprehension to Process 185 | 186 | # Return Array 187 | return _np.array([phasor(i) for i in arr]) 188 | 189 | 190 | # Define Vector Array Generator 191 | def vectarray(arr, degrees=True, flatarray=False): 192 | """ 193 | Format Complex as Array of Magnitude/Angle Pairs. 194 | 195 | Consume an iterable (list/tuple/ndarray/etc.) of 196 | complex numbers and generate an ndarray of magnitude 197 | and angle pairs, formatted as a 2-dimension (or 198 | optionally 1-dimension) array. 199 | 200 | Parameters 201 | ---------- 202 | arr: array-like 203 | Array or list of complex numbers to be 204 | cast to magnitude/angle pairs. 205 | degrees: bool, optional 206 | Control option to set the angles in 207 | degrees. Defaults to True. 208 | flatarray: bool, optional 209 | Control option to set the array return 210 | to work as a 1-dimension list. Defaults 211 | to False, formatting as a 2-dimension 212 | list. 213 | 214 | Returns 215 | ------- 216 | polararr: ndarray 217 | Array of magnitude/angle pairs as a 218 | 2-dimension array (or optionally 219 | 1-dimension array). 220 | 221 | See Also 222 | -------- 223 | electricpy.phasors.phasor: Phasor Generating Function 224 | electricpy.phasors.phasorlist: Phasor Generator for List or Array 225 | """ 226 | # Iteratively Append Arrays to the Base 227 | 228 | def vector_cast(num): 229 | mag, ang = _c.polar(num) 230 | 231 | if degrees: 232 | ang = _np.degrees(ang) 233 | 234 | return [mag, ang] 235 | 236 | polararr = _np.array([vector_cast(num) for num in arr]) 237 | # Reshape Array if Needed 238 | if not flatarray: 239 | polararr = _np.reshape(polararr, (-1, 2)) 240 | return polararr 241 | 242 | 243 | # Define Phasor Data Generator 244 | def phasordata(mn, mx=None, npts=1000, mag=1, ang=0, freq=60, 245 | retstep=False, rettime=False, sine=False): 246 | """ 247 | Complex Phasor Data Generator. 248 | 249 | Generates a sinusoidal data set with minimum, maximum, 250 | frequency, magnitude, and phase angle arguments. 251 | 252 | Parameters 253 | ---------- 254 | mn: float, optional 255 | Minimum time (in seconds) to generate data for. 256 | default=0 257 | mx: float 258 | Maximum time (in seconds) to generate data for. 259 | npts: float, optional 260 | Number of data samples. default=1000 261 | mag: float, optional 262 | Sinusoid magnitude, default=1 263 | ang: float, optional 264 | Sinusoid angle in degrees, default=0 265 | freq: float, optional 266 | Sinusoid frequency in Hz 267 | retstep: bool, optional 268 | Control argument to request return of time 269 | step size (dt) in seconds. 270 | sine: bool, optional 271 | Control argument to require data be generated 272 | with a sinusoidal function instead of cosine. 273 | 274 | Returns 275 | ------- 276 | data: numpy.ndarray 277 | The resultant data array. 278 | """ 279 | # Test Inputs for Min/Max 280 | if mx == None: 281 | # No Minimum provided, use Value as Maximum 282 | mx = mn 283 | mn = 0 284 | # Generate Omega 285 | w = 2 * _np.pi * freq 286 | # Generate Time Array 287 | t, dt = _np.linspace(mn, mx, npts, retstep=True) 288 | # Generate Data Array 289 | if not sine: 290 | data = mag * _np.cos(w * t + _np.radians(ang)) 291 | else: 292 | data = mag * _np.sin(w * t + _np.radians(ang)) 293 | # Generate Return Data Set 294 | dataset = [data] 295 | if retstep: 296 | dataset.append(dt) 297 | if rettime: 298 | dataset.append(t) 299 | # Return Dataset 300 | if len(dataset) == 1: 301 | return dataset[0] 302 | return dataset 303 | 304 | 305 | # Define Complex Composition Function 306 | def compose(*arr): 307 | """ 308 | Complex Composition Function. 309 | 310 | Accepts a set of real values and generates an array 311 | of complex values. Input must be array-like, but can 312 | appear in various forms: 313 | 314 | - [ real, imag] 315 | - [ [ real1, ..., realn ], [ imag1, ..., imagn ] ] 316 | - [ [ real1, imag1 ], ..., [ realn, imagn ] ] 317 | 318 | Will always return values in form: 319 | 320 | [ complex1, ... complexn ] 321 | 322 | Parameters 323 | ---------- 324 | arr: array_like 325 | The input of real and imaginary term(s) 326 | """ 327 | # Condition Input 328 | if len(arr) == 1: 329 | arr = arr[0] # Extract 0-th term 330 | # Input comes in various forms, we must first detect shape 331 | arr = _np.asarray(arr) # Format as Numpy Array 332 | # Gather Shape to Detect Format 333 | try: 334 | row, col = arr.shape 335 | # Passed Test, Valid Shape 336 | retarr = _np.array([]) # Empty Return Array 337 | # Now, Determine whether is type 2 or 3 338 | if col == 2: # Type 3 339 | for i in range(row): # Iterate over each row 340 | item = arr[i][0] + 1j * arr[i][1] 341 | retarr = _np.append(retarr, item) 342 | elif row == 2: # Type 2 343 | for i in range(col): # Iterate over each column 344 | item = arr[0][i] + 1j * arr[1][i] 345 | retarr = _np.append(retarr, item) 346 | else: 347 | raise ValueError("Invalid Array Shape, must be 2xN or Nx2.") 348 | # Successfully Generated Array, Return 349 | return (retarr) 350 | except: # 1-Dimension Array 351 | length = arr.size 352 | # Test for invalid Array Size 353 | if length != 2: 354 | raise ValueError("Invalid Array Size, Saw Length of " + str(length)) 355 | # Valid Size, Calculate and Return 356 | return arr[0] + 1j * arr[1] 357 | 358 | 359 | # Define Parallel Impedance Adder 360 | def parallelz(*args): 361 | r""" 362 | Parallel Impedance Calculator. 363 | 364 | This function is designed to generate the total parallel 365 | impedance of a set (tuple) of impedances specified as real 366 | or complex values. 367 | 368 | .. math:: 369 | Z_{eq}=(\frac{1}{Z_1}+\frac{1}{Z_2}+\dots+\frac{1}{Z_n})^{-1} 370 | 371 | Parameters 372 | ---------- 373 | Z: tuple of complex 374 | The tupled input set of impedances, may be a tuple 375 | of any size greater than 2. May be real, complex, or 376 | a combination of the two. 377 | 378 | Returns 379 | ------- 380 | Zp: complex 381 | The calculated parallel impedance of the input tuple. 382 | """ 383 | # Gather length (number of elements in tuple) 384 | L = len(args) 385 | if L == 1: 386 | Z = args[0] # Only One Tuple Provided 387 | try: 388 | L = len(Z) 389 | if L == 1: 390 | Zp = Z[0] # Only one impedance, burried in tuple 391 | else: 392 | # Inversely add the first two elements in tuple 393 | Zp = (1 / Z[0] + 1 / Z[1]) ** (-1) 394 | # If there are more than two elements, add them all inversely 395 | if L > 2: 396 | for i in range(2, L): 397 | Zp = (1 / Zp + 1 / Z[i]) ** (-1) 398 | except ValueError or IndexError: 399 | Zp = Z # Only one impedance 400 | else: 401 | Z = args # Set of Args acts as Tuple 402 | # Inversely add the first two elements in tuple 403 | Zp = (1 / Z[0] + 1 / Z[1]) ** (-1) 404 | # If there are more than two elements, add them all inversely 405 | if L > 2: 406 | for i in range(2, L): 407 | Zp = (1 / Zp + 1 / Z[i]) ** (-1) 408 | return Zp 409 | 410 | # END 411 | -------------------------------------------------------------------------------- /electricpy/thermal.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | Thermal and Sensory Models for Thermal Arithmetic in Electrical Engineering. 4 | 5 | Filled with plotting functions and visualization tools for electrical engineers, 6 | this module is designed to assist engineers visualize their designs. 7 | """ 8 | ################################################################################ 9 | 10 | import numpy as _np 11 | 12 | from electricpy.constants import * 13 | 14 | # Define Cold-Junction-Voltage Calculator 15 | def coldjunction(Tcj, coupletype="K", To=None, Vo=None, P1=None, P2=None, 16 | P3=None, P4=None, Q1=None, Q2=None, round=None): 17 | """ 18 | Thermocouple Cold-Junction Formula. 19 | 20 | Function to calculate the expected cold-junction-voltage given 21 | the temperature at the cold-junction. 22 | 23 | Parameters 24 | ---------- 25 | Tcj: float 26 | The temperature (in degrees C) that the junction is 27 | currently subjected to. 28 | coupletype: string, optional 29 | Thermocouple Type, may be one of (B,E,J,K,N,R,S,T), default="K" 30 | To: float, optional 31 | Temperature Constant used in Polynomial. 32 | Vo: float, optional 33 | Voltage Constant used in Polynomial. 34 | P1: float, optional 35 | Polynomial constant. 36 | P2: float, optional 37 | Polynomial constant. 38 | P3: float, optional 39 | Polynomial constant. 40 | P4: float, optional 41 | Polynomial constant. 42 | Q1: float, optional 43 | Polynomial constant. 44 | Q2: float, optional 45 | Polynomial constant. 46 | round: int, optional 47 | Control input to specify how many decimal places the result 48 | should be rounded to, default=1. 49 | 50 | Returns 51 | ------- 52 | Vcj: float 53 | The calculated cold-junction-voltage in volts. 54 | """ 55 | # Condition Inputs 56 | coupletype = coupletype.upper() 57 | # Validate Temperature Range 58 | if coupletype == "B": 59 | if not (0 < Tcj < 70): 60 | raise ValueError("Temperature out of range.") 61 | else: 62 | if not (-20 < Tcj < 70): 63 | raise ValueError("Temperature out of range.") 64 | # Define Constant Lookup System 65 | lookup = ["B", "E", "J", "K", "N", "R", "S", "T"] 66 | if not (coupletype in lookup): 67 | raise ValueError("Invalid Thermocouple Type") 68 | index = lookup.index(coupletype) 69 | # Define Constant Dictionary 70 | # Load Data Into Terms 71 | parameters = {} 72 | for var in COLD_JUNCTION_DATA.keys(): 73 | parameters[var] = parameters.get(var, None) or COLD_JUNCTION_DATA[var][index] 74 | To, Vo, P1, P2, P3, P4, Q1, Q2 = [parameters[key] for key in COLD_JUNCTION_KEYS] 75 | # Define Formula Terms 76 | tx = (Tcj - To) 77 | num = tx * (P1 + tx * (P2 + tx * (P3 + P4 * tx))) 78 | den = 1 + tx * (Q1 + Q2 * tx) 79 | Vcj = Vo + num / den 80 | # Round Value if Allowed 81 | if round is not None: 82 | Vcj = _np.around(Vcj, round) 83 | # Return in milivolts 84 | return Vcj * m 85 | 86 | 87 | # Define Thermocouple Temperature Calculation 88 | def thermocouple(V, coupletype="K", fahrenheit=False, cjt=None, To=None, 89 | Vo=None, P1=None, P2=None, P3=None, P4=None, Q1=None, Q2=None, 90 | Q3=None, round=1): 91 | """ 92 | Thermocouple Temperature Calculator. 93 | 94 | Utilizes polynomial formula to calculate the temperature being monitored 95 | by a thermocouple. Allows for various thermocouple types (B,E,J,K,N,R,S,T) 96 | and various cold-junction-temperatures. 97 | 98 | Parameters 99 | ---------- 100 | V: float 101 | Measured voltage (in Volts) 102 | coupletype: string, optional 103 | Thermocouple Type, may be one of (B,E,J,K,N,R,S,T), default="K" 104 | fahrenheit: bool, optional 105 | Control to enable return value as Fahrenheit instead of Celsius, 106 | default=False 107 | cjt: float, optional 108 | Cold-Junction-Temperature 109 | To: float, optional 110 | Temperature Constant used in Polynomial. 111 | Vo: float, optional 112 | Voltage Constant used in Polynomial. 113 | P1: float, optional 114 | Polynomial constant. 115 | P2: float, optional 116 | Polynomial constant. 117 | P3: float, optional 118 | Polynomial constant. 119 | P4: float, optional 120 | Polynomial constant. 121 | Q1: float, optional 122 | Polynomial constant. 123 | Q2: float, optional 124 | Polynomial constant. 125 | Q3: float, optional 126 | Polynomial constant. 127 | round: int, optional 128 | Control input to specify how many decimal places the result 129 | should be rounded to, default=1. 130 | 131 | Returns 132 | ------- 133 | T: float 134 | The temperature (by default in degrees C, but optionally in 135 | degrees F) as computed by the function. 136 | """ 137 | # Condition Inputs 138 | coupletype = coupletype.upper() 139 | V = V / m # Scale volts to milivolts 140 | # Determine Cold-Junction-Voltage 141 | if cjt is not None: 142 | Vcj = coldjunction(cjt, coupletype, To, Vo, P1, P2, P3, P4, Q1, Q2, round) 143 | V += Vcj / m 144 | # Define Constant Lookup System 145 | lookup = ["B", "E", "J", "K", "N", "R", "S", "T"] 146 | if not (coupletype in lookup): 147 | raise ValueError("Invalid Thermocouple Type") 148 | # Determine Array Selection 149 | vset = THERMO_COUPLE_VOLTAGES[coupletype] 150 | if V < vset[0] * m: 151 | raise ValueError("Voltage Below Lower Bound") 152 | elif vset[0] <= V < vset[1]: 153 | select = 0 154 | elif vset[1] <= V < vset[2]: 155 | select = 1 156 | elif vset[2] <= V < vset[3]: 157 | select = 2 158 | elif vset[3] <= V < vset[4]: 159 | select = 3 160 | elif vset[4] <= V <= vset[5]: 161 | select = 4 162 | elif vset[5] < V: 163 | raise ValueError("Voltage Above Upper Bound") 164 | else: 165 | raise ValueError("Internal Error!") 166 | # Load Data Into Terms 167 | parameters = {} 168 | for i, key in enumerate(THERMO_COUPLE_KEYS): 169 | parameters[key] = parameters.get(key, None) or THERMO_COUPLE_DATA[coupletype][i][select] 170 | Vo, To, P1, P2, P3, P4, Q1, Q2, Q3 = [parameters[key] for key in THERMO_COUPLE_KEYS] 171 | # Calculate Temperature in Degrees C 172 | num = (V - Vo) * (P1 + (V - Vo) * (P2 + (V - Vo) * (P3 + P4 * (V - Vo)))) 173 | den = 1 + (V - Vo) * (Q1 + (V - Vo) * (Q2 + Q3 * (V - Vo))) 174 | temp = To + num / den 175 | # Return Temperature 176 | if fahrenheit: 177 | temp = (temp * 9 / 5) + 32 178 | temp = _np.around(temp, round) 179 | return temp 180 | 181 | 182 | # Define RTD Calculator 183 | def rtdtemp(RT, rtdtype="PT100", fahrenheit=False, Rref=None, Tref=None, 184 | a=None, round=1): 185 | """ 186 | RTD Temperature Calculator. 187 | 188 | Evaluates the measured temperature based on the measured resistance 189 | and the RTD type. 190 | 191 | Parameters 192 | ---------- 193 | RT: float 194 | The measured resistance (in ohms). 195 | rtdtype: string 196 | RTD Type string, may be one of: (PT100, PT1000, 197 | CU100, NI100, NI120, NIFE), default=PT100 198 | fahrenheit: bool, optional 199 | Control parameter to force return into degrees 200 | fahrenheit, default=False 201 | Rref: float, optional 202 | Resistance reference, commonly used if non-standard 203 | RTD type being used. Specified in ohms. 204 | Tref: float, optional 205 | Temperature reference, commonly used if non-standard 206 | RTD type being used. Specified in degrees Celsius. 207 | a: float, optional 208 | Scaling value, commonly used if non-standard 209 | RTD type being used. 210 | round: int, optional 211 | Control argument to specify number of decimal points 212 | in returned value. 213 | 214 | Returns 215 | ------- 216 | temp: float 217 | Calculated temperature, defaults to degrees Celsius. 218 | """ 219 | # Load Variables 220 | if Rref is None: 221 | Rref = RTD_TYPES[rtdtype][0] 222 | if Tref is None: 223 | Tref = 0 224 | if a is None: 225 | a = RTD_TYPES[rtdtype][1] 226 | # Define Terms 227 | num = RT - Rref + Rref * a * Tref 228 | den = Rref * a 229 | temp = num / den 230 | # Return Temperature 231 | if fahrenheit: 232 | temp = (temp * 9 / 5) + 32 233 | temp = _np.around(temp, round) 234 | return temp 235 | 236 | # END 237 | -------------------------------------------------------------------------------- /electricpy/version.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | """ 3 | `electricpy` Package - `version` Module. 4 | 5 | >>> from electricpy import version 6 | 7 | This file is to keep track of Name, Current Version of electricpy for CI and CD 8 | """ 9 | ################################################################################ 10 | 11 | NAME = "electricpy" 12 | VERSION = "0.4.0" 13 | -------------------------------------------------------------------------------- /logo/ElectricpyBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/ElectricpyBanner.png -------------------------------------------------------------------------------- /logo/ElectricpyBanner.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/ElectricpyBanner.pub -------------------------------------------------------------------------------- /logo/ElectricpyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/ElectricpyLogo.png -------------------------------------------------------------------------------- /logo/ElectricpyLogo.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/ElectricpyLogo.pub -------------------------------------------------------------------------------- /logo/ElectricpyLogo_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/ElectricpyLogo_raw.png -------------------------------------------------------------------------------- /logo/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/Thumbs.db -------------------------------------------------------------------------------- /logo/tecnico.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineerjoe440/ElectricPy/68a69725c9c7a12c3f707729eee6842566b0e14b/logo/tecnico.zip -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "electricpy" 7 | description = "Electrical Engineering Functions in Python" 8 | readme = "README.md" 9 | requires-python = ">=3.7" 10 | authors = [ 11 | {name = "Joe Stanley", email = "engineerjoe440@yahoo.com"}, 12 | {name = "Lakshmikanth Ayyadevara"}, 13 | {name = "Khan Asfi Reza", email = "khanasfireza10@gmail.com"}, 14 | {name = "Arthur Lorencini Bergamaschi", email = "arthur.lorencini@gmail.com"}, 15 | {name = "Miguel Zabala"}, 16 | {name = "Soham Ratnaparkhi", email = "soham.ratnaparkhi@gmail.com"}, 17 | {name = "I Wayan Kurniawan"}, 18 | {name = "Ankush", email = "ankush.opensource@gmail.com"}, 19 | {name = "Tanvir Riyad"} 20 | ] 21 | maintainers = [ 22 | {name = "Joe Stanley", email = "engineerjoe440@yahoo.com"} 23 | ] 24 | license = {file = "LICENSE"} 25 | classifiers = [ 26 | "Programming Language :: Python :: 3", 27 | "License :: OSI Approved :: MIT License", 28 | "Operating System :: OS Independent" 29 | ] 30 | dynamic = ["version"] 31 | 32 | dependencies = [ 33 | "NumPy", 34 | "matplotlib", 35 | "SciPy", 36 | "SymPy", 37 | "numdifftools", 38 | ] 39 | 40 | [project.optional-dependencies] 41 | numerical = ["numdifftools"] 42 | fault = ["arcflash"] 43 | full = ["numdifftools", "arcflash"] 44 | 45 | [project.urls] 46 | Home = "https://electricpy.readthedocs.io/en/latest/index.html" 47 | Repository = "https://github.com/engineerjoe440/ElectricPy" 48 | Issues = "https://github.com/engineerjoe440/ElectricPy/issues" 49 | Documentation = "https://electricpy.readthedocs.io/en/latest/" 50 | -------------------------------------------------------------------------------- /release-version.py: -------------------------------------------------------------------------------- 1 | # Release Versioning Support Script 2 | # Joe Stanley | 2021 3 | 4 | import re 5 | import requests 6 | 7 | USERNAME = 'engineerjoe440' 8 | REPO = 'electricpy' 9 | 10 | try: 11 | import electricpy as ep 12 | except ImportError: 13 | import os, sys 14 | sys.path.insert(0, os.getcwd()) 15 | import electricpy as ep 16 | 17 | import requests 18 | 19 | response = requests.get(f"https://api.github.com/repos/{USERNAME}/{REPO}/releases/latest") 20 | try: 21 | latest = response.json()["name"] 22 | latest = re.findall(r'v\d\.\d\.\d', latest)[0] 23 | except Exception: 24 | latest = '0.0.0' 25 | 26 | # Verify Version is Newer 27 | version = f"v{ep._version_}" 28 | if version <= latest: 29 | raise ValueError( 30 | f"Module version ({version}) is not newer than previous release " 31 | f"({latest})!" 32 | ) 33 | else: 34 | print(version) 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | NumPy 2 | matplotlib 3 | SciPy 4 | SymPy 5 | numdifftools 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | from electricpy.geometry import Point 2 | from electricpy.geometry import Line 3 | import numpy as np 4 | from numpy.testing import assert_almost_equal 5 | 6 | def compare_points(p1: Point, p2: Point) -> bool: 7 | try: 8 | assert_almost_equal(p1.x, p2.x) 9 | assert_almost_equal(p1.y, p2.y) 10 | except AssertionError: 11 | return False 12 | return True 13 | 14 | def compare_lines(l1: Line, l2: Line) -> bool: 15 | try: 16 | assert_almost_equal(l1.a / l2.a , l1.b / l2.b) 17 | assert_almost_equal(l1.b / l2.b , l1.c / l2.c) 18 | assert_almost_equal(l1.c / l2.c , l1.a / l2.a) 19 | except AssertionError: 20 | return False 21 | except ZeroDivisionError: 22 | if l1.a == 0 and l2.a == 0: 23 | try: 24 | assert_almost_equal(l1.c / l2.c , l1.b / l2.b) 25 | except ZeroDivisionError: 26 | return False 27 | except AssertionError: 28 | return False 29 | if l1.b == 0 and l2.b == 0: 30 | try: 31 | assert_almost_equal(l1.c / l2.c , l1.a / l2.a) 32 | except ZeroDivisionError: 33 | return False 34 | except AssertionError: 35 | return False 36 | if l1.c == 0 and l2.c == 0: 37 | try: 38 | assert_almost_equal(l1.a / l2.a , l1.b / l2.b) 39 | except ZeroDivisionError: 40 | return False 41 | except AssertionError: 42 | return False 43 | finally: 44 | return True 45 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | xdoctest -------------------------------------------------------------------------------- /test/test_circle.py: -------------------------------------------------------------------------------- 1 | import cmath 2 | from electricpy.geometry.circle import Circle 3 | from electricpy.geometry import Line, Point 4 | 5 | 6 | class TestArea: 7 | def test_0(self): 8 | 9 | c = Circle((0, 0), 1) 10 | assert c.area() == cmath.pi 11 | 12 | c = Circle((0, 0), 2) 13 | assert c.area() == cmath.pi * 4 14 | 15 | def test_1(self): 16 | 17 | c = Circle((0, 0), 1.1) 18 | assert c.area() == cmath.pi * 1.1**2 19 | 20 | c = Circle((0, 0), 2.2) 21 | assert c.area() == cmath.pi * 2.2**2 22 | 23 | 24 | class TestCircumference: 25 | def test_0(self): 26 | 27 | c = Circle((0, 0), 1) 28 | assert c.circumference() == cmath.pi * 2 29 | 30 | c = Circle((0, 0), 2) 31 | assert c.circumference() == cmath.pi * 4 32 | 33 | def test_1(self): 34 | 35 | c = Circle((0, 0), 1.1) 36 | assert c.circumference() == cmath.pi * 2.2 37 | 38 | c = Circle((0, 0), 2.2) 39 | assert c.circumference() == cmath.pi * 4.4 40 | 41 | 42 | class TestTangent: 43 | def test_0(self): 44 | c = Circle((0, 0), 1) 45 | 46 | assert c.tangent(Point(0, 1)) == Line(0, 1, -1) 47 | assert c.tangent(Point(0, -1)) == Line(0, -1, -1) 48 | assert c.tangent(Point(1, 0)) == Line(1, 0, -1) 49 | assert c.tangent(Point(-1, 0)) == Line(-1, 0, -1) 50 | 51 | def test_1(self): 52 | 53 | from test import compare_lines 54 | 55 | c = Circle((0, 0), 1) 56 | 57 | p = Point(cmath.cos(cmath.pi / 4), cmath.sin(cmath.pi / 4)) 58 | p1 = Point(cmath.sqrt(2), 0) 59 | p2 = Point(0, cmath.sqrt(2)) 60 | 61 | assert compare_lines(c.tangent(p), Line.construct(p1, p2)) 62 | 63 | 64 | class TestNormal: 65 | def test_0(self): 66 | c = Circle((0, 0), 1) 67 | 68 | assert c.normal(Point(0, 1)) == Line(1, 0, 0) 69 | assert c.normal(Point(0, -1)) == Line(1, 0, 0) 70 | assert c.normal(Point(1, 0)) == Line(0, 1, 0) 71 | assert c.normal(Point(-1, 0)) == Line(0, 1, 0) 72 | 73 | def test_1(self): 74 | 75 | from test import compare_lines 76 | 77 | c = Circle((0, 0), 1) 78 | p0 = Point(cmath.cos(cmath.pi / 4), cmath.sin(cmath.pi / 4)) 79 | p1 = Point(-cmath.cos(cmath.pi / 4), cmath.sin(cmath.pi / 4)) 80 | 81 | assert compare_lines(c.normal(p0), Line(1, -1, 0)) 82 | assert compare_lines(c.normal(p1), Line(1, 1, 0)) 83 | -------------------------------------------------------------------------------- /test/test_convertion.py: -------------------------------------------------------------------------------- 1 | import cmath 2 | import numpy as np 3 | 4 | def test_abc_to_seq(): 5 | 6 | from electricpy.conversions import abc_to_seq 7 | a = cmath.rect(1, np.radians(120)) 8 | 9 | def test_0(): 10 | np.testing.assert_array_almost_equal(abc_to_seq([1, 1, 1]), [1+0j, 0j, 0j]) 11 | np.testing.assert_array_almost_equal(abc_to_seq([1, 0, 0]), [1/3+0j, 1/3+0j, 1/3+0j]) 12 | np.testing.assert_array_almost_equal(abc_to_seq([0, 1, 0]), [1/3+0j, a/3+0j, a*a/3+0j]) 13 | np.testing.assert_array_almost_equal(abc_to_seq([0, 0, 1]), [1/3+0j, a*a/3, a/3]) 14 | 15 | test_0() 16 | 17 | def test_seq_to_abc(): 18 | from electricpy.conversions import seq_to_abc 19 | a = cmath.rect(1, np.radians(120)) 20 | 21 | def test_0(): 22 | np.testing.assert_array_almost_equal(seq_to_abc([1, 1, 1]), [3+0j, 0j, 0j]) 23 | np.testing.assert_array_almost_equal(seq_to_abc([1, 0, 0]), [1+0j, 1+0j, 1+0j]) 24 | np.testing.assert_array_almost_equal(seq_to_abc([0, 1, 0]), [1+0j, a*a+0j, a+0j]) 25 | np.testing.assert_array_almost_equal(seq_to_abc([0, 0, 1]), [1+0j, a, a*a]) 26 | 27 | test_0() -------------------------------------------------------------------------------- /test/test_electricpy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from numpy.testing import assert_almost_equal 4 | 5 | # Electricpy Imports 6 | from electricpy import powerflow 7 | from electricpy import phasor 8 | from electricpy.passive import air_core_inductance 9 | 10 | def test_bridge_impedance(): 11 | # Perfectly Balanced Wheat Stone Bridge 12 | from electricpy import bridge_impedance 13 | 14 | z1 = complex(1, 0) 15 | z2 = complex(1, 0) 16 | z3 = complex(1, 0) 17 | z4 = complex(1, 0) 18 | z5 = complex(np.random.random(), np.random.random()) 19 | 20 | zeq = bridge_impedance(z1, z2, z3, z4, z5) 21 | 22 | zactual = complex(1, 0) 23 | 24 | assert zeq == zactual 25 | 26 | # Balanced Wheat Stone Bridge 27 | z1 = complex(2, 0) 28 | z2 = complex(4, 0) 29 | z3 = complex(8, 0) 30 | z4 = complex(4, 0) 31 | z5 = complex(np.random.random(), np.random.random()) 32 | 33 | zeq = bridge_impedance(z1, z2, z3, z4, z5) 34 | 35 | zactual = complex(4, 0) 36 | 37 | assert zeq == zactual 38 | 39 | # Base Case 40 | z1 = complex(10, 0) 41 | z2 = complex(20, 0) 42 | z3 = complex(30, 0) 43 | z4 = complex(40, 0) 44 | z5 = complex(50, 0) 45 | 46 | zeq = bridge_impedance(z1, z2, z3, z4, z5) 47 | 48 | zactual = complex(4 + (50 / 3), 0) 49 | 50 | assert_almost_equal(zeq, zactual) 51 | 52 | def test_dynetz(): 53 | 54 | from electricpy import dynetz 55 | z1 = complex(3, 3) 56 | z2 = complex(3, 3) 57 | z3 = complex(3, 3) 58 | 59 | za, zb, zc = dynetz(delta=(z1, z2, z3)) 60 | 61 | assert (za, zb, zc) == (z1 / 3, z2 / 3, z3 / 3) 62 | 63 | za, zb, zc = dynetz(wye=(z1, z2, z3)) 64 | 65 | assert (za, zb, zc) == (3 * z1, 3 * z2, 3 * z3) 66 | 67 | def test_powerset(): 68 | 69 | from electricpy import powerset 70 | 71 | # Test case 0 72 | P = 10 73 | PF = 0.8 74 | _, Q, S, _ = powerset(P=P, PF=PF) 75 | 76 | assert_almost_equal(S, P / PF) 77 | assert_almost_equal(Q, S * np.sqrt(1 - PF ** 2)) 78 | 79 | # Test case 1 80 | Q = 8 81 | P = 6 82 | 83 | _, _, S, PF = powerset(P=P, Q=Q) 84 | 85 | assert_almost_equal(S, 10) 86 | assert_almost_equal(PF, 0.6) 87 | 88 | def test_voltdiv(): 89 | from electricpy import voltdiv 90 | from electricpy import phasors 91 | 92 | # Test case 0 R1 == R2 == Rload 93 | Vin = 10 94 | R1 = 10 95 | R2 = 10 96 | Rload = 10 97 | 98 | Vout = voltdiv(Vin, R1, R2, Rload=Rload) 99 | 100 | assert Vout == R1 / 3 101 | 102 | # Test case 1 Vin, R1 and R2 are in complex form 103 | Vin = phasor(220, 30) 104 | 105 | R1 = complex(10, 0) 106 | R2 = complex(10, 10) 107 | Rload = complex(10, -10) 108 | Vout = voltdiv(Vin, R1, R2, Rload=Rload) 109 | 110 | Vout_actual = phasor(110, 30) 111 | 112 | assert_almost_equal(Vout, Vout_actual) 113 | 114 | def test_suspension_insulators(): 115 | """Electric Power Systems by C.L Wadhwa Overhead Line Insulator example.""" 116 | from electricpy import suspension_insulators 117 | 118 | number_capacitors = 5 119 | 120 | capacitance_ration = 5 121 | 122 | Voltage = 66 123 | 124 | _, string_efficiency = suspension_insulators(number_capacitors, 125 | capacitance_ration, 126 | Voltage) 127 | 128 | string_efficiency_actual = 54.16 129 | assert_almost_equal(string_efficiency, string_efficiency_actual, decimal=2) 130 | 131 | def test_propagation_constants(): 132 | 133 | from electricpy import propagation_constants 134 | z = complex(0.5, 0.9) 135 | y = complex(0, 6e-6) 136 | params_dict = propagation_constants(z, y, 520) 137 | 138 | alpha_cal = 0.622 * (10 ** -3) 139 | beta_cal = 2.4 * (10 ** -3) 140 | 141 | assert_almost_equal(params_dict['alpha'], alpha_cal, decimal = 4) 142 | assert_almost_equal(params_dict['beta'], beta_cal, decimal = 4) 143 | 144 | def test_funcrms(): 145 | 146 | from electricpy.math import funcrms 147 | 148 | f = lambda x:np.sin(x) 149 | 150 | assert_almost_equal(funcrms(f,np.pi), 1/np.sqrt(2)) 151 | 152 | def test_convolve(): 153 | 154 | from electricpy.math import convolve 155 | A = (1,1,1) 156 | B = (1,1,1) 157 | 158 | assert ([1,2,3,2,1] == convolve((A,B))).all() 159 | 160 | def test_ic_555_astable(): 161 | 162 | from electricpy import ic_555_astable 163 | 164 | # Astable configuration 165 | R = [10, 10] 166 | C = 1e-6 167 | result = ic_555_astable(R, C) 168 | 169 | for key,value in result.items(): 170 | result[key] = np.round(value, decimals=6) 171 | 172 | assert_almost_equal(result['duty_cycle'], 66.666666667, decimal = 3) 173 | assert_almost_equal(result['t_low'], 6.931*10**-6, decimal = 3) 174 | assert_almost_equal(result['t_high'], 1.386*10**-5, decimal = 3) 175 | 176 | def test_slew_rate(): 177 | 178 | from electricpy import slew_rate 179 | 180 | SR = slew_rate(V=1, freq=1, find='SR') 181 | V = slew_rate(SR=1, freq=1, find='V') 182 | freq = slew_rate(V=1, SR=1, find='freq') 183 | 184 | assert_almost_equal(np.pi*2, SR) 185 | assert_almost_equal(1/(np.pi*2), V) 186 | assert_almost_equal(1/(np.pi*2), freq) 187 | 188 | def test_t_attenuator(): 189 | Adb = 1 190 | Z0 = 1 191 | 192 | from electricpy import t_attenuator 193 | 194 | R1, R2 = t_attenuator(Adb, Z0) 195 | 196 | assert_almost_equal(R1, 0.0575, decimal = 3) 197 | assert_almost_equal(R2, 8.6673, decimal = 3) 198 | 199 | def test_pi_attenuator(): 200 | Adb = 1 201 | Z0 = 1 202 | 203 | from electricpy import pi_attenuator 204 | 205 | R1, R2 = pi_attenuator(Adb, Z0) 206 | assert_almost_equal(R1, 17.39036, decimal = 3) 207 | assert_almost_equal(R2, 0.11538, decimal = 3) 208 | 209 | def test_inductor_voltdiv(): 210 | 211 | from electricpy.passive import inductive_voltdiv 212 | 213 | params = { 214 | 'Vin':1, 215 | 'L1':1, 216 | 'L2':1 217 | } 218 | Vout = inductive_voltdiv(**params, find='Vout') 219 | assert (Vout == params['Vin']/2) 220 | 221 | params = { 222 | 'Vout':1, 223 | 'L1':1, 224 | 'L2':1 225 | } 226 | 227 | Vin = inductive_voltdiv(**params, find = 'Vin') 228 | assert (Vin == params['Vout']*2) 229 | 230 | params = { 231 | 'Vout':1, 232 | 'Vin':2, 233 | 'L2':1 234 | } 235 | 236 | L1 = inductive_voltdiv(**params, find='L1') 237 | assert(L1 == 1) 238 | 239 | params = { 240 | 'Vout':1, 241 | 'Vin':2, 242 | 'L1':1 243 | } 244 | L1 = inductive_voltdiv(**params, find='L1') 245 | assert(L1 == 1) 246 | 247 | def test_induction_machine_slip(): 248 | from electricpy import induction_machine_slip 249 | 250 | Nr = 1200 251 | freq = 50 252 | p = 4 253 | 254 | # Test case 1 255 | assert induction_machine_slip(0) == 1 256 | assert induction_machine_slip(1800) == 0 257 | 258 | # Test case 2 259 | assert induction_machine_slip(1000) == 4/9 260 | assert induction_machine_slip(1200) == 1/3 261 | 262 | # Test case 3 263 | assert induction_machine_slip(Nr, freq=freq, poles=p) == 0.2 264 | assert induction_machine_slip(1500, freq=freq, poles=p) == 0 265 | assert induction_machine_slip(0, freq=freq, poles=p) == 1 266 | 267 | def test_parallel_plate_capacitance(): 268 | from electricpy import parallel_plate_capacitance 269 | 270 | # Test 1: In the free space (by default e=e0=8.8542E-12) 271 | 272 | A1 = 100e-4 273 | d1 = 8.8542e-2 274 | C1 = 1e-12 275 | 276 | # Test capacitance given area and distance 277 | assert_almost_equal(parallel_plate_capacitance(A=A1, d=d1), C1) 278 | # Test area given capacitance and distance 279 | assert_almost_equal(parallel_plate_capacitance(C=C1, d=d1), A1) 280 | # Test distance given capacitance and area 281 | assert_almost_equal(parallel_plate_capacitance(C=C1, A=A1), d1) 282 | 283 | # Test 2: Not in the free space (e≠8.8542E-12) 284 | 285 | A2 = 100e-4 286 | d2 = 8.8542e-2 287 | e2 = 17.7084e-12 288 | C2 = 2e-12 289 | 290 | # Test capacitance given area, distance and permitivity 291 | assert_almost_equal(parallel_plate_capacitance(A=A2, d=d2, e=e2), C2) 292 | # Test area given capacitance, distance and permitivity 293 | assert_almost_equal(parallel_plate_capacitance(C=C2, d=d2, e=e2), A2) 294 | # Test distance given capacitance, area and permitivity 295 | assert_almost_equal(parallel_plate_capacitance(C=C2, A=A2, e=e2), d2) 296 | 297 | def test_solenoid_inductance(): 298 | from electricpy import solenoid_inductance 299 | 300 | # Test 1: In the free space (by default u=u0=4πE-7) 301 | 302 | A1 = 2e-3 303 | N1 = 550 304 | l1 = 20e-2*np.pi 305 | L1 = 1.21e-3 306 | 307 | # Test inductance given area, number of turns and length 308 | assert_almost_equal(solenoid_inductance(A=A1, N=N1, l=l1), L1) 309 | # Test area given inductance, number of turns and length 310 | assert_almost_equal(solenoid_inductance(L=L1, N=N1, l=l1), A1) 311 | # Test number of turns given inductance, area and length 312 | assert_almost_equal(solenoid_inductance(L=L1, A=A1, l=l1), N1) 313 | # Test length given inductance, area and number of turns 314 | assert_almost_equal(solenoid_inductance(L=L1, A=A1, N=N1), l1) 315 | 316 | # Test 2: Not the free space (u≠4πE-7) 317 | 318 | A2 = 2e-3 319 | N2 = 550 320 | l2 = 20e-2*np.pi 321 | u2 = 100*4e-7*np.pi # Iron permeability 322 | L2 = 0.121 323 | 324 | # Test inductance given area, number of turns, length and permeability 325 | assert_almost_equal(solenoid_inductance(A=A2, N=N2, l=l2, u=u2), L2) 326 | # Test area given inductance, number of turns, length and permeability 327 | assert_almost_equal(solenoid_inductance(L=L2, N=N2, l=l2, u=u2), A2) 328 | # Test number of turns given inductance, area, length and permeability 329 | assert_almost_equal(solenoid_inductance(L=L2, A=A2, l=l2, u=u2), N2) 330 | # Test length given inductance, area, number of turns and permeability 331 | assert_almost_equal(solenoid_inductance(L=L2, A=A2, N=N2, u=u2), l2) 332 | 333 | def test_syncspeed(): 334 | from electricpy import syncspeed 335 | assert syncspeed(4, freq = 60, rpm = True) == 1800 336 | assert syncspeed(4, freq = 60, Hz = True) == 30 337 | 338 | with pytest.raises(ZeroDivisionError, match = "Poles of an electrical machine \ 339 | can not be zero"): 340 | syncspeed(0) 341 | 342 | def test_tcycle(): 343 | from electricpy import tcycle 344 | 345 | # Test 0 346 | assert tcycle(1, 1) == 1 347 | assert tcycle(2, 1) == 2 348 | assert tcycle(1, 2) == 0.5 349 | 350 | # Test 1 351 | assert (tcycle(ncycles = [1, 2, 3], freq = 2) == np.array([1/2, 2/2, 3/2])).all() 352 | assert (tcycle(ncycles = [1, 2, 3], freq = 3) == np.array([1/3, 2/3, 3/3])).all() 353 | assert (tcycle(ncycles = 2, freq= [1, 2, 3]) == np.array([2/1, 2/2, 2/3])).all() 354 | assert (tcycle(ncycles = 3, freq= [1, 2, 3]) == np.array([3/1, 3/2, 3/3])).all() 355 | 356 | # Test 2 357 | assert (tcycle(ncycles = [1, 2, 3], freq = [2, 3, 4]) == np.array([1/2, 2/3, 3/4])).all() 358 | assert (tcycle(ncycles = [1, 2, 3], freq = [3, 4, 5]) == np.array([1/3, 2/4, 3/5])).all() 359 | 360 | # Test 3 361 | with pytest.raises(ZeroDivisionError): 362 | print(tcycle(ncycles = [1, 2, 3], freq = [0, 0, 0])) 363 | 364 | # Test 4 365 | with pytest.raises(ValueError, match = "ncycles and freq must be the same length"): 366 | tcycle(ncycles = [1, 2, 3], freq = [2, 3, 4, 5]) 367 | 368 | # Test 5 369 | with pytest.raises(ValueError, match = "Frequency must be postive value"): 370 | tcycle(ncycles=[1, 2, 3, 4], freq = [-2, -3, 4, 5]) 371 | 372 | def test_nr_pqd(): 373 | from electricpy import sim 374 | from numpy.testing import assert_array_almost_equal 375 | ybustest = [[-10j,10j], 376 | [10j,-10j]] 377 | Vlist = [[1,0],[None,None]] 378 | Plist = [None,-2.0] 379 | Qlist = [None,-1.0] # 1pu vars consumed 380 | F = sim.nr_pq(ybustest,Vlist,Plist,Qlist) 381 | X0 = [0,1] # Define Initial Conditions 382 | J = sim.jacobian(F) # Find Jacobian 383 | # Now use Newton-Raphson to Solve 384 | results, iter = sim.NewtonRaphson(F,J,X0) 385 | assert_array_almost_equal(results, [-0.236,0.8554], decimal = 3) 386 | assert iter == 4 # Iteration Counter 387 | 388 | def test_tcycle(): 389 | from electricpy import tcycle 390 | 391 | # Test 0 392 | assert tcycle(1, 1) == 1 393 | assert tcycle(2, 1) == 2 394 | assert tcycle(1, 2) == 0.5 395 | 396 | # Test 1 397 | assert (tcycle(ncycles = [1, 2, 3], freq = 2) == np.array([1/2, 2/2, 3/2])).all() 398 | assert (tcycle(ncycles = [1, 2, 3], freq = 3) == np.array([1/3, 2/3, 3/3])).all() 399 | assert (tcycle(ncycles = 2, freq= [1, 2, 3]) == np.array([2/1, 2/2, 2/3])).all() 400 | assert (tcycle(ncycles = 3, freq= [1, 2, 3]) == np.array([3/1, 3/2, 3/3])).all() 401 | 402 | # Test 2 403 | assert (tcycle(ncycles = [1, 2, 3], freq = [2, 3, 4]) == np.array([1/2, 2/3, 3/4])).all() 404 | assert (tcycle(ncycles = [1, 2, 3], freq = [3, 4, 5]) == np.array([1/3, 2/4, 3/5])).all() 405 | 406 | # Test 3 407 | with pytest.raises(ZeroDivisionError): 408 | print(tcycle(ncycles = [1, 2, 3], freq = [0, 0, 0])) 409 | 410 | # Test 4 411 | with pytest.raises(ValueError, match = "ncycles and freq must be the same length"): 412 | tcycle(ncycles = [1, 2, 3], freq = [2, 3, 4, 5]) 413 | 414 | # Test 5 415 | with pytest.raises(ValueError, match = "Frequency must be postive value"): 416 | tcycle(ncycles=[1, 2, 3, 4], freq = [-2, -3, 4, 5]) 417 | 418 | class TestPowerflow(): 419 | 420 | def test_0(self): 421 | Vsend = phasor(1.01, 30) 422 | Vrecv = phasor(1, 0) 423 | Xline = 0.2 424 | 425 | ans = powerflow(Vsend, Vrecv, Xline) 426 | assert_almost_equal(ans, 2.525) 427 | 428 | def test_1(self): 429 | Vsend = 1.01 430 | Vrecv = 1 431 | Xline = 0.2 432 | 433 | ans = powerflow(Vsend, Vrecv, Xline) 434 | assert ans == 0 435 | 436 | class TestAirCoreInductor: 437 | 438 | def check_result(self, expected_result): 439 | computed_result = air_core_inductance(self.coil_diameter, self.coil_length, self.turn) 440 | assert_almost_equal(computed_result, expected_result, decimal = 3) 441 | 442 | def test_0(self): 443 | self.coil_diameter = 1e-3 444 | self.coil_length = 1e-3 445 | self.turn = 1000 446 | 447 | expected_result = 0.678640 448 | self.check_result(expected_result) 449 | 450 | def test_1(self): 451 | self.coil_diameter = 1e-2 452 | self.coil_length = 1e-2 453 | self.turn = 251 454 | 455 | expected_result = 0.42755 456 | self.check_result(expected_result) -------------------------------------------------------------------------------- /test/test_geometry.py: -------------------------------------------------------------------------------- 1 | import cmath 2 | from electricpy import geometry as Geometry 3 | from electricpy.geometry import Point 4 | from electricpy.geometry import Line 5 | from numpy.testing import assert_array_almost_equal 6 | 7 | class TestDistance(): 8 | 9 | def test_0(self): 10 | p1 = Point(1, 2) 11 | p2 = Point(3, 4) 12 | assert Geometry.distance(p1, p2) == 2*(2**0.5) 13 | 14 | p1 = Point(4, -6) 15 | p2 = Point(-2, -5) 16 | assert Geometry.distance(p2, p1) == (37**0.5) 17 | 18 | p1 = Point(1.3, 2.3) 19 | p2 = Point(1.4, 2.4) 20 | 21 | d_output = Geometry.distance(p1, p2) 22 | d_actual = 0.1*(2**0.5) 23 | 24 | assert_array_almost_equal(d_output, d_actual, decimal=6) 25 | 26 | def test_1(self): 27 | p1 = Point(1, 2) 28 | p2 = Point(1, 3) 29 | assert Geometry.distance(p1, p2) == 1 30 | 31 | p1 = Point(2.0, 1) 32 | p2 = Point(3.0, 1) 33 | assert Geometry.distance(p1, p2) == 1 34 | 35 | class Testslope(): 36 | 37 | def test_0(self): 38 | p1 = Point(1, 2) 39 | p2 = Point(3, 4) 40 | assert Geometry.slope(p1, p2) == 1 41 | 42 | p1 = Point(4, -6) 43 | p2 = Point(-2, -5) 44 | assert Geometry.slope(p2, p1) == -1/6 45 | 46 | def test_1(self): 47 | 48 | p1 = Point(1, 2) 49 | p2 = Point(2, 2) 50 | 51 | assert Geometry.slope(p1, p2) == 0 52 | 53 | p1 = Point(1, 2) 54 | p2 = Point(1, 3) 55 | try: 56 | Geometry.slope(p1, p2) 57 | except ZeroDivisionError: 58 | assert True 59 | 60 | class Testsection(): 61 | 62 | def test_0(self): 63 | p1 = Point(1, 2) 64 | p2 = Point(3, 4) 65 | 66 | p = Geometry.section(p1, p2, 0.5) 67 | assert p == Point(2, 3) 68 | 69 | def test_1(self): 70 | p1 = Point(-1, 3) 71 | p2 = Point(1, -3) 72 | 73 | p_computed = Geometry.section(p1, p2, (2, 3)) 74 | p_actual = Point(-1/5, 3/5) 75 | 76 | assert_array_almost_equal(p_computed(), p_actual(), decimal=6) 77 | 78 | class Testline_equaltion(): 79 | 80 | def test_0(self): 81 | p1 = Point(1, 2) 82 | p2 = Point(3, 4) 83 | assert Geometry.line_equation(p1, p2) == Line(1, -1, 1) 84 | 85 | p1 = Point(4, -6) 86 | p2 = Point(-2, -5) 87 | assert Geometry.line_equation(p1, p2) == Line(1, 6, 32) 88 | 89 | def test_1(self): 90 | p1 = Point(1, 2) 91 | p2 = Point(1, 3) 92 | assert Geometry.line_equation(p1, p2) == Line(1, 0, -1) 93 | 94 | p1 = Point(1, 2) 95 | p2 = Point(2, 2) 96 | assert Geometry.line_equation(p1, p2) == Line(0, 1, -2) 97 | 98 | def test_2(self): 99 | assert Line(1, 2, 3) == Line(2, 4, 6) 100 | assert Line(1, -1, 0) == Line(3, -3, 0) 101 | assert Line(1, 0, -1) == Line(3, 0, -3) 102 | 103 | class Testline_distance(): 104 | 105 | def test_0(self): 106 | p1 = Point(1, 2) 107 | p2 = Point(3, 4) 108 | l = Line.construct(p1, p2) 109 | assert Geometry.line_distance(p1, l) == 0 110 | assert Geometry.line_distance(p2, l) == 0 111 | 112 | def test_1(self): 113 | p1 = Point(2, 0) 114 | p2 = Point(2, 4) 115 | p = Point(0, 0) 116 | l = Line.construct(p1, p2) 117 | assert Geometry.line_distance(p, l) == 2 118 | assert l.distance(p) == 2 119 | 120 | l = Line(0, 1, -3) 121 | assert l.distance(p) == 3 122 | 123 | class Testfoot_perpendicular(): 124 | 125 | def test_0(self): 126 | p1 = Point(1, 2) 127 | p2 = Point(3, 4) 128 | l = Line.construct(p1, p2) 129 | p = Point(2, 2) 130 | assert Geometry.foot_perpendicular(p, l) == Point(1.5, 2.5) 131 | 132 | p = Point(2, 3) 133 | assert Geometry.foot_perpendicular(p, l) == Point(2, 3) 134 | 135 | def test_1(self): 136 | p = Point(-1, 3) 137 | l = Line(3, -4, -16) 138 | 139 | p_actual = l.foot_perpendicular(p) 140 | p_image = l.image(p) 141 | 142 | p_desired = Point(68/25, -49/25) 143 | 144 | assert_array_almost_equal(p_actual(), p_desired(), decimal=6) 145 | assert Geometry.midpoint(p, p_image) == p_actual 146 | 147 | class TestPerpendicularBisector(): 148 | 149 | def test_0(self): 150 | p1 = Point(3, 0) 151 | p2 = Point(0, 3) 152 | l = Geometry.perpendicular_bisector(p1, p2) 153 | assert l == Line(1, -1, 0) 154 | 155 | def test_1(self): 156 | p1 = Point(-3, 0) 157 | p2 = Point(0, 3) 158 | l = Geometry.perpendicular_bisector(p1, p2) 159 | assert l == Line(1, 1, 0) 160 | 161 | def test_2(self): 162 | p1 = Point(3, 0) 163 | p2 = Point(5, 0) 164 | l = Geometry.perpendicular_bisector(p1, p2) 165 | assert l == Line(1, 0, -4) 166 | 167 | def test_3(self): 168 | p1 = Point(0, 3) 169 | p2 = Point(0, 5) 170 | l = Geometry.perpendicular_bisector(p1, p2) 171 | assert l == Line(0, 1, -4) 172 | 173 | class Testcolinear(): 174 | 175 | def test_0(self): 176 | p1 = Point(1, 2) 177 | p2 = Point(3, 4) 178 | p3 = Point(5, 6) 179 | assert Geometry.colinear(p1, p2, p3) 180 | 181 | def test_1(self): 182 | p1 = Point(1, 2) 183 | p2 = Point(3, 4) 184 | p3 = Point(5, 7) 185 | assert not Geometry.colinear(p1, p2, p3) 186 | 187 | def test_2(self): 188 | p1 = Point(1, 0) 189 | p2 = Point(2, 0) 190 | p3 = Point(3, 0) 191 | assert Geometry.colinear(p1, p2, p3) 192 | 193 | class TestAngleBtwLines(): 194 | 195 | def test_0(self): 196 | l1 = Line(3, 4, 7) 197 | l2 = Line(4, -3, 5) 198 | 199 | assert cmath.pi/2 == Geometry.angle_btw_lines(l1, l2) 200 | 201 | def test_1(self): 202 | l1 = Line(3, 0, 0) 203 | l2 = Line(4, 0, 0) 204 | 205 | assert 0 == Geometry.angle_btw_lines(l1, l2) 206 | 207 | def test_3(self): 208 | l1 = Line(0, 4, 0) 209 | l2 = Line(0, 3, 0) 210 | assert 0 == Geometry.angle_btw_lines(l1, l2) -------------------------------------------------------------------------------- /test/test_imports.py: -------------------------------------------------------------------------------- 1 | # Import ElectricPy modules just to make sure they load correctly 2 | 3 | # Test importing the package itself 4 | def test_import_by_name(): 5 | try: 6 | import electricpy 7 | assert True 8 | except: 9 | assert False 10 | 11 | # Test importing the `bode` module 12 | def test_import_bode(): 13 | try: 14 | from electricpy import bode 15 | assert True 16 | except: 17 | assert False 18 | 19 | # Test importing the `constants` module 20 | def test_import_constants(): 21 | try: 22 | from electricpy import constants 23 | assert True 24 | except: 25 | assert False 26 | 27 | # Test importing the `fault` module 28 | def test_import_fault(): 29 | try: 30 | from electricpy import fault 31 | assert True 32 | except: 33 | assert False 34 | 35 | # Test importing the `sim` module 36 | def test_import_sim(): 37 | try: 38 | from electricpy import sim 39 | assert True 40 | except: 41 | assert False 42 | 43 | # Test importing the `visu` module 44 | def test_import_visu(): 45 | try: 46 | from electricpy import visu 47 | assert True 48 | except: 49 | assert False 50 | 51 | # Testing Imports of geometry submodule 52 | 53 | # Testing geometry import 54 | def test_Geometry(): 55 | try: 56 | from electricpy import geometry 57 | assert True 58 | except ImportError: 59 | assert False 60 | 61 | # Testing circle import from electricpy.geometry 62 | def test_circle(): 63 | try: 64 | from electricpy.geometry.circle import Circle 65 | assert True 66 | except ImportError: 67 | assert False 68 | 69 | # Testing triangle import from electricpy.geometry 70 | def test_triangle(): 71 | try: 72 | from electricpy.geometry.triangle import Triangle 73 | assert True 74 | except ImportError: 75 | assert False 76 | 77 | -------------------------------------------------------------------------------- /test/test_phasor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from electricpy.phasors import phs 3 | from electricpy.phasors import phasor 4 | from electricpy.phasors import vectarray 5 | from numpy.testing import assert_almost_equal 6 | 7 | def test_phasor(): 8 | magnitude = 10 9 | # basic angles test case 0 10 | z1 = phasor(magnitude, 0) 11 | z2 = phasor(magnitude, 30) 12 | z3 = phasor(magnitude, 45) 13 | z4 = phasor(magnitude, 60) 14 | z5 = phasor(magnitude, 90) 15 | 16 | assert_almost_equal(z1, complex(magnitude, 0)) 17 | assert_almost_equal(z2, complex(magnitude * np.sqrt(3) / 2, magnitude / 2)) 18 | assert_almost_equal(z3, complex(magnitude / np.sqrt(2), magnitude / np.sqrt(2))) 19 | assert_almost_equal(z4, complex(magnitude / 2, magnitude * np.sqrt(3) / 2)) 20 | assert_almost_equal(z5, complex(0, magnitude)) 21 | 22 | # z(theta) = z(theta+360) test case 1 23 | theta = np.random.randint(360) 24 | assert_almost_equal(phasor(magnitude, theta), phasor(magnitude, theta + 360)) 25 | 26 | # z(-theta)*z(theta) == abs(z)^2 test case 2. 27 | z0 = phasor(magnitude, theta) 28 | z1 = phasor(magnitude, -theta) 29 | assert_almost_equal(z0 * z1, np.power(abs(z0), 2)) 30 | 31 | # z(theta+180) = -1*Z(theta) 32 | z0 = phasor(magnitude, theta) 33 | z1 = phasor(magnitude, 180 + theta) 34 | assert_almost_equal(z0, -1 * z1) 35 | 36 | class TestPhs(): 37 | 38 | def test_0(self): 39 | inputs = [0, 90, 180, 270, 360] 40 | 41 | outputs = [phs(x) for x in inputs] 42 | actual_outputs = [1, 1j, -1, -1j, 1] 43 | 44 | for x,y in zip(outputs, actual_outputs): 45 | assert_almost_equal(x, y) 46 | 47 | def test_1(self): 48 | inputs = [30, 45, 60, 135] 49 | 50 | outputs = [phs(x) for x in inputs] 51 | actual_outputs = [0.866025+0.5j, 0.707106+0.707106j, 0.5+0.866025j, -0.707106+0.707106j] 52 | 53 | for x,y in zip(outputs, actual_outputs): 54 | assert_almost_equal(x, y, decimal = 3) 55 | 56 | class TestVectarray(): 57 | 58 | def test_0(self): 59 | 60 | A = [2+3j, 4+5j, 6+7j, 8+9j] 61 | B = vectarray(A) 62 | 63 | B_test = [[np.abs(x), np.degrees(np.angle(x))] for x in A] 64 | 65 | np.testing.assert_array_almost_equal(B, B_test) 66 | 67 | def test_1(self): 68 | 69 | A = np.random.random(size = 16) 70 | B = vectarray(A) 71 | 72 | B_test = [[np.abs(x), 0] for x in A] 73 | np.testing.assert_array_almost_equal(B, B_test) 74 | 75 | A = np.random.random(size = 16)*1j 76 | B = vectarray(A) 77 | 78 | B_test = [[np.abs(x), 90] for x in A] 79 | np.testing.assert_array_almost_equal(B, B_test) 80 | -------------------------------------------------------------------------------- /test/test_triangle.py: -------------------------------------------------------------------------------- 1 | import cmath 2 | from electricpy.geometry import triangle 3 | from electricpy.geometry import Point 4 | from test import compare_points 5 | 6 | class TestCentroid(): 7 | 8 | def test_0(self): 9 | p1 = Point(0, 1) 10 | p2 = Point(1, 0) 11 | p3 = Point(0, 0) 12 | t = triangle.Triangle(p1, p2, p3) 13 | assert t.centroid() == Point(1/3, 1/3) 14 | 15 | def test_1(self): 16 | p1 = Point(1.1, 2.2) 17 | p2 = Point(3.1, 4.2) 18 | p3 = Point(5.1, 6.7) 19 | t = triangle.Triangle(p1, p2, p3) 20 | assert compare_points(t.centroid(), Point(3.1, 131/30)) 21 | 22 | 23 | class TestInCenter(): 24 | 25 | def test_0(self): 26 | p1 = Point(0, 1) 27 | p2 = Point(1, 0) 28 | p3 = Point(0, 0) 29 | t = triangle.Triangle(p1, p2, p3) 30 | assert compare_points(t.in_center(), Point(1/(2 + cmath.sqrt(2)), 1/(2 + cmath.sqrt(2)))) 31 | 32 | def test_1(self): 33 | p1 = Point(0, 0) 34 | p2 = Point(1, 0) 35 | p3 = Point(1*cmath.cos(cmath.pi/3), 1*cmath.sin(cmath.pi/3)) 36 | t = triangle.Triangle(p1, p2, p3) 37 | assert compare_points(t.in_center(), Point(0.5, cmath.sqrt(3)/6)) 38 | 39 | class TestOrthoCenter(): 40 | 41 | def test_0(self): 42 | p1 = Point(0, 1) 43 | p2 = Point(1, 0) 44 | p3 = Point(0, 0) 45 | t = triangle.Triangle(p1, p2, p3) 46 | assert compare_points(t.ortho_center(), Point(0, 0)) 47 | 48 | def test_1(self): 49 | p1 = Point(0, 0) 50 | p2 = Point(1, 0) 51 | p3 = Point(1*cmath.cos(cmath.pi/3), 1*cmath.sin(cmath.pi/3)) 52 | t = triangle.Triangle(p1, p2, p3) 53 | assert compare_points(t.ortho_center(), Point(0.5, cmath.sqrt(3)/6)) 54 | 55 | 56 | class TestCircumCenter(): 57 | def test_0(self): 58 | p1 = Point(0, 1) 59 | p2 = Point(1, 0) 60 | p3 = Point(0, 0) 61 | t = triangle.Triangle(p1, p2, p3) 62 | assert compare_points(t.circum_center(), Point(0.5, 0.5)) 63 | 64 | def test_1(self): 65 | p1 = Point(0, 0) 66 | p2 = Point(1, 0) 67 | p3 = Point(1*cmath.cos(cmath.pi/3), 1*cmath.sin(cmath.pi/3)) 68 | t = triangle.Triangle(p1, p2, p3) 69 | assert compare_points(t.circum_center(), Point(0.5, cmath.sqrt(3)/6)) -------------------------------------------------------------------------------- /test/test_visu.py: -------------------------------------------------------------------------------- 1 | import math 2 | import cmath 3 | from matplotlib.legend import Legend 4 | import matplotlib.pyplot as plt 5 | from numpy.testing import assert_almost_equal 6 | 7 | class Test_visualization: 8 | 9 | def test_induction_motor_circle(self): 10 | from electricpy.visu import InductionMotorCircle 11 | 12 | open_circuit_test_data = {'V0': 400, 'I0': 9, 'W0': 1310} 13 | blocked_rotor_test_data = {'Vsc': 200, 'Isc': 50, 'Wsc': 7100} 14 | ratio = 1 # stator copper loss/ rotor copper loss 15 | output_power = 15000 16 | MotorCircle = InductionMotorCircle( 17 | open_circuit_test_data, 18 | blocked_rotor_test_data, 19 | output_power, 20 | torque_ration=ratio, 21 | frequency=50, 22 | poles=4 23 | ) 24 | 25 | assert_almost_equal( 26 | MotorCircle()['no_load_loss'], 27 | open_circuit_test_data['W0'] 28 | ) 29 | 30 | def test_power_circle(self): 31 | from electricpy.visu import receiving_end_power_circle 32 | data = { 33 | "A" : cmath.rect(0.895, math.radians(1.4)), 34 | "B" : cmath.rect(182.5, math.radians(78.6)), 35 | "Vr" : cmath.rect(215, 0), 36 | "Pr": 50, 37 | "power_factor": -0.9, 38 | } 39 | 40 | power_circle = receiving_end_power_circle(**data) 41 | 42 | assert_almost_equal(abs(power_circle()['Vs']), 224.909, decimal = 3) 43 | 44 | def test_rlc_frequency_response(self): 45 | # import RLC from electricpy.visu 46 | from electricpy.visu import SeriesRLC 47 | 48 | # test RLC 49 | rlc_obj1 = SeriesRLC( 50 | resistance=5, inductance=0.4, capacitance=25.3e-6, frequency=50 51 | ) 52 | 53 | # gh1 = rlc_obj1.graph(lower_frequency_cut=0.1, upper_frequency_cut=100, samples=1000) 54 | 55 | rlc_obj2 = SeriesRLC( 56 | resistance=10, inductance=0.5, capacitance=25.3e-6, frequency=50 57 | ) 58 | 59 | # gh2 = rlc_obj2.graph(lower_frequency_cut=0.1, upper_frequency_cut=100, samples=1000) 60 | 61 | # plt.gca().add_artist(gh1.legend(rlc_obj1.legend(), title=f"(R, L, C) => (5, 0.4, 25.3e-6)", loc='upper right')) 62 | # plt.gca().add_artist(gh2.legend(rlc_obj2.legend(), title=f"(R, L, C) => (10, 0.5 25.3e-6)", loc='upper left')) 63 | 64 | # plt.show() 65 | 66 | 67 | --------------------------------------------------------------------------------