diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index e76ca4a..855ac41 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -22,4 +22,4 @@ The optional `-- format` and `-- write` arguments (see above) attempt to correct
## Further checks:
- [ ] The documentation builds: `$ nox -s docs`.
- [ ] Code is commented, particularly in hard-to-understand areas.
-- [ ] Tests are added that prove fix is effective or that feature works.
+- [ ] Tests are added that prove fix is effective or that feature works.
\ No newline at end of file
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
new file mode 100644
index 0000000..3d62b56
--- /dev/null
+++ b/.github/workflows/codecov.yml
@@ -0,0 +1,52 @@
+name: codecov
+
+on:
+ push:
+ paths-ignore:
+ - "*.md"
+ - "LICENSE"
+ - "docs/**"
+ - "images/**"
+ - ".github/ISSUE_TEMPLATE/**"
+
+ pull_request:
+ branches: [main]
+ paths-ignore:
+ - "*.md"
+ - "LICENSE"
+ - "docs/**"
+ - "images/**"
+ - ".github/ISSUE_TEMPLATE/**"
+
+jobs:
+ tests:
+ name: Collect and upload coverage
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.14"
+
+ - name: Install thevenin
+ run: |
+ python -m pip install --upgrade pip
+ pip install .[tests]
+
+ - name: Pytest with coverage
+ run: pytest --cov=thevenin --cov-report=xml tests/
+
+ - name: Upload to codecov
+ uses: codecov/codecov-action@v5
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ verbose: true
+ flags: unittests
+ env_vars: OS,PYTHON
+ files: ./coverage.xml
+ name: codecov-umbrella
+ fail_ci_if_error: false
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3296b6b..f15e110 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
- `initial_state` was renamed to `state0` in `sim.pre()` ([#14](https://github.com/NatLabRockies/thevenin/pull/14))
### Chores
+- Switch linter over to `ruff`, enable `codecov` workflow, update README badges ([#31](https://github.com/NatLabRockies/thevenin/pull/31))
- Make GitHub hyperlinks reference new org name `NREL` -> `NatLabRockies` ([#30](https://github.com/NatLabRockies/thevenin/pull/30))
- Allow single backticks for sphinx inline code (`default_role = 'literal'`) ([#28](https://github.com/NatLabRockies/thevenin/pull/28))
- Rebrand NREL to NLR, and include name change for Alliance as well ([#27](https://github.com/NatLabRockies/thevenin/pull/27))
diff --git a/README.md b/README.md
index ae61b7a..7c07645 100644
--- a/README.md
+++ b/README.md
@@ -5,19 +5,11 @@
# thevenin
-[![CI][ci-b]][ci-l]
-![tests][test-b]
-![coverage][cov-b]
-[![pep8][pep-b]][pep-l]
-
-[ci-b]: https://github.com/NatLabRockies/thevenin/actions/workflows/ci.yml/badge.svg
-[ci-l]: https://github.com/NatLabRockies/thevenin/actions/workflows/ci.yml
-
-[test-b]: https://github.com/NatLabRockies/thevenin/blob/main/images/tests.svg?raw=true
-[cov-b]: https://github.com/NatLabRockies/thevenin/blob/main/images/coverage.svg?raw=true
-
-[pep-b]: https://img.shields.io/badge/code%20style-pep8-orange.svg
-[pep-l]: https://www.python.org/dev/peps/pep-0008
+[](https://github.com/NatLabRockies/thevenin/actions/workflows/ci.yml)
+[](https://codecov.io/gh/NatLabRockies/thevenin)
+[](https://github.com/NatLabRockies/thevenin/blob/main/LICENSE)
+[](https://pepy.tech/projects/thevenin)
+[](https://pypi.org/project/thevenin)
## Summary
This package is a wrapper for the well-known Thevenin equivalent circuit model. The model is comprised of a single series resistor followed by any number of parallel RC pairs. Figure 1 below illustrates a circuit with 2 RC pairs; however, the model can be run with as few as zero, and as many as $N$.
diff --git a/docs/source/development/code_style_and_linting.rst b/docs/source/development/code_style_and_linting.rst
index 6990d00..6a24e0e 100644
--- a/docs/source/development/code_style_and_linting.rst
+++ b/docs/source/development/code_style_and_linting.rst
@@ -8,11 +8,11 @@ We adhere to `PEP8 `_ with minimal exceptions
Code Formatting
---------------
-While `black `_ is a popular auto-formatting package, we do not permit it to be used for this codebase. Although it adheres to the same PEP8 standards that we follow, the `black` styling can be a bit more opinionated at times and does not always help improve clarity. For those still looking for auto-formatting, we permit the use of `autopep8 `_ is when paired with the `.flake8` configuration file found in `.github/linters`. IDEs supporting `autopep8` should be configured accordingly. Developers can also run the formatter manually using::
+While `black `_ is a popular auto-formatting package, we do not permit it to be used for this codebase. Although it adheres to the same PEP8 standards that we follow, the `black` styling is also much more opinionated and does not always help improve clarity. For those still looking for auto-formatting, we permit the use of `autopep8 `_ when paired with the `.flake8` configuration file found in `.github/linters`. However, we do not automatically include and configure `autopep8` as a dependency. If you'd like to use it in your IDE, you should install it and configure it with the provided `.flake8` file. We also provide a `nox` session that uses a pre-configured `ruff` linter to apply minimal fixes to the codebase that is well-aligned with our style guidelines. To run this session, use the following command::
nox -s linter -- format
-When used with the optional `format` argument, this `nox` command will first run the auto-formatter and then check for errors. This means that is errors persist, then `autopep8` was unable to address them and that they must be addressed manually.
+When used with the optional `format` argument, this `nox` command will run `ruff check` with the `--fix` option enabled. Note that there is also an option to run `nox -s linter -- stats` if you want to see a summary of the linting errors rather than a full report. Lastly, you can run `nox -s linter -- format-unsafe` to apply auto-fixes that the linter considers "unsafe". These fixes may potentially change the behavior of the code, so they should be used with caution and reviewed carefully. The `stats` and `format` or `format-unsafe` options can also be used together to see the summary of errors that remain after applying fixes. If you do not lint and format your code locally, the CI will catch any issues during the push process. Failed tests may result in a delayed reviewer assignments when you open pull requests.
Enforcement
-----------
diff --git a/noxfile.py b/noxfile.py
index 04f574b..355e5b9 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -9,8 +9,7 @@
@nox.session(name='cleanup', python=False)
def run_cleanup(_) -> None:
- """Use os/shutil to remove some files/directories"""
-
+ """Use os/shutil to remove some files/directories."""
if os.path.exists('.coverage'):
os.remove('.coverage')
@@ -21,34 +20,38 @@ def run_cleanup(_) -> None:
@nox.session(name='linter', python=False)
-def run_flake8(session: nox.Session) -> None:
+def run_ruff(session: nox.Session) -> None:
"""
- Run flake8 with the github config file
+ Run ruff to check for linting errors.
- Use the optional 'format' argument to run autopep8 prior to the linter.
+ Use the optional 'format' or 'format-unsafe' arguments to run ruff with the
+ --fix or --unsafe-fixes option prior to the linter. You can also use 'stats'
+ to show a summary of the found errors rather than a full report.
"""
+ session.run('pip', 'install', '--upgrade', '--quiet', 'ruff')
- session.run('pip', 'install', '--upgrade', '--quiet', 'flake8')
- session.run('pip', 'install', '--upgrade', '--quiet', 'autopep8')
+ command = ['ruff', 'check']
+ if 'stats' in session.posargs:
+ command.append('--statistics')
if 'format' in session.posargs:
- session.run('autopep8', '.', '--in-place', '--recursive',
- '--global-config=.github/linters/.flake8')
+ command.append('--fix')
+ elif 'format-unsafe' in session.posargs:
+ command.extend(['--fix', '--unsafe-fixes'])
- session.run('flake8', '--config=.github/linters/.flake8')
+ session.run(*command)
@nox.session(name='codespell', python=False)
def run_codespell(session: nox.Session) -> None:
"""
- Run codespell with the github config file
+ Run codespell with the github config file.
Use the optional 'write' argument to write the corrections directly into
the files. Otherwise, you will only see a summary of the found errors.
"""
-
session.run('pip', 'install', '--upgrade', '--quiet', 'codespell')
command = ['codespell', '--config=.github/linters/.codespellrc']
@@ -61,13 +64,12 @@ def run_codespell(session: nox.Session) -> None:
@nox.session(name='spellcheck', python=False)
def run_spellcheck(session: nox.Session) -> None:
"""
- Run codespell with docs files included
+ Run codespell with docs files included.
Use the optional 'write' argument to write the corrections directly into
the files. Otherwise, you will only see a summary of the found errors.
"""
-
run_codespell(session)
command = ['codespell', '--config=.github/linters/.codespellrc']
@@ -80,14 +82,13 @@ def run_spellcheck(session: nox.Session) -> None:
@nox.session(name='tests', python=False)
def run_pytest(session: nox.Session) -> None:
"""
- Run pytest and generate test/coverage reports
+ Run pytest and generate test/coverage reports.
Use the optional 'parallel' argument to run the tests in parallel. As just
a flag, the number of workers will be determined automatically. Otherwise,
you can specify the number of workers using an int, e.g., parallel=4.
"""
-
package = importlib.util.find_spec('thevenin')
coverage_folder = os.path.dirname(package.origin)
@@ -98,6 +99,8 @@ def run_pytest(session: nox.Session) -> None:
'tests/',
]
else:
+ os.makedirs('reports', exist_ok=True)
+
command = [
'pytest',
'--cov=src/thevenin',
@@ -115,15 +118,13 @@ def run_pytest(session: nox.Session) -> None:
elif arg.startswith('parallel'):
command[1:1] = ['-n', 'auto']
+ os.environ['MPLBACKEND'] = 'Agg'
session.run(*command)
- run_cleanup(session)
-
@nox.session(name='badges', python=False)
def run_genbadge(session: nox.Session) -> None:
- """Run genbadge to make test/coverage badges"""
-
+ """Run genbadge to make test/coverage badges."""
session.run(
'genbadge', 'coverage', '-l',
'-i', 'reports/coverage.xml',
@@ -140,7 +141,7 @@ def run_genbadge(session: nox.Session) -> None:
@nox.session(name='docs', python=False)
def run_sphinx(session: nox.Session) -> None:
"""
- Run spellcheck and then use sphinx to build docs
+ Run spellcheck and then use sphinx to build docs.
Use the optional 'clean' argument to remove everything under the 'build'
and 'source/api' folders prior to re-building the docs. This is important
@@ -148,7 +149,6 @@ def run_sphinx(session: nox.Session) -> None:
are not showing new pages. In general, try without 'clean' first.
"""
-
if 'clean' in session.posargs:
os.chdir('docs')
session.run('make', 'clean')
@@ -169,14 +169,13 @@ def run_sphinx(session: nox.Session) -> None:
@nox.session(name='pre-commit', python=False)
def run_pre_commit(session: nox.Session) -> None:
"""
- Run all linters/tests and make new badges
+ Run all linters/tests and make new badges.
- Order of sessions: flake8, spellcheck, pytest, genbadge. Using 'format' for
+ Order of sessions: ruff, spellcheck, pytest, genbadge. Using 'format' for
linter, 'write' for spellcheck, and/or 'parallel' for pytest is permitted.
"""
-
- run_flake8(session)
+ run_ruff(session)
run_spellcheck(session)
run_pytest(session)
diff --git a/pyproject.toml b/pyproject.toml
index 78033e4..12a06ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,6 +46,31 @@ where = ["src"]
[tool.setuptools.package-data]
thevenin = ["_resources/*.yaml"]
+[tool.ruff]
+line-length = 80
+exclude = ["build", "docs", "images", "reports"]
+
+[tool.ruff.lint]
+preview = true
+select = [
+ "E", # All pycodestyle errors
+ "W", # All pycodestyle warnings
+ "F", # All pyflakes errors
+ "B", # All flake8-bugbear checks
+ "D", # All pydocstyle checks
+]
+extend-select = [
+ "E302", # Expected 2 blank lines, found 1 (requires preview=true, above)
+]
+ignore = [
+ "B007", "B009", "B028", "B905",
+ "D100", "D103", "D104", "D203", "D205", "D212", "D401",
+ "E201", "E202", "E226", "E241", "E731",
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/**/*" = ["D"]
+
[project.optional-dependencies]
docs = [
"sphinx",
@@ -64,8 +89,7 @@ tests = [
]
dev = [
"nox",
- "flake8",
- "autopep8",
+ "ruff",
"codespell",
"genbadge[all]",
"thevenin[docs,tests]",
diff --git a/scripts/version_checker.py b/scripts/version_checker.py
index 6994e34..aad453f 100644
--- a/scripts/version_checker.py
+++ b/scripts/version_checker.py
@@ -27,7 +27,6 @@ def get_latest_version(package: str, prefix: str = None) -> str:
Failed to fetch PyPI data for requested package.
"""
-
url = f"https://pypi.org/pypi/{package}/json"
response = requests.get(url)
@@ -72,7 +71,6 @@ def check_against_pypi(pypi: str, local: str) -> None:
Local package is older than PyPI.
"""
-
pypi = Version(pypi)
local = Version(local)
@@ -103,7 +101,6 @@ def check_against_tag(tag: str, local: str) -> None:
Version mismatch: tag differs from local.
"""
-
tag = Version(tag)
local = Version(local)
diff --git a/src/thevenin/__init__.py b/src/thevenin/__init__.py
index bc74b29..f418834 100644
--- a/src/thevenin/__init__.py
+++ b/src/thevenin/__init__.py
@@ -15,7 +15,7 @@
the documentation by visiting the website, hosted on Read the Docs. The website
includes search functionality and more detailed examples.
-"""
+""" # noqa: D400, D415 (ignore missing punctuation on first line)
# core package
from ._experiment import Experiment
diff --git a/src/thevenin/_basemodel.py b/src/thevenin/_basemodel.py
index 0e2243f..2a92782 100644
--- a/src/thevenin/_basemodel.py
+++ b/src/thevenin/_basemodel.py
@@ -43,7 +43,6 @@ def calculated_current(voltage, ocv, hyst, eta_j, R0) -> float | np.ndarray:
Calculated current at a single or multiple times [A].
"""
-
if eta_j.ndim == 1:
return -(voltage - ocv - hyst + np.sum(eta_j)) / R0
elif eta_j.ndim == 2:
@@ -83,7 +82,6 @@ def calculated_voltage(current, ocv, hyst, eta_j, R0) -> float | np.ndarray:
Calculated voltage at a single or multiple times [V].
"""
-
if eta_j.ndim == 1:
return ocv + hyst - np.sum(eta_j) - current*R0
elif eta_j.ndim == 2:
@@ -223,7 +221,6 @@ def __repr__(self) -> str: # pragma: no cover
A console-readable instance representation.
"""
-
keys = self._repr_keys
values = [getattr(self, k) for k in keys]
@@ -253,7 +250,6 @@ def _classname(self) -> str:
@property
def _get_params_dict(self) -> dict:
"""Return the params dictionary needed to initialize a new instance."""
-
params = {}
for k in self._repr_keys:
params[k] = getattr(self, k)
@@ -275,7 +271,6 @@ def pre(self, *args, **kwargs) -> None: # pragma: no cover
def _check_RC_pairs(self) -> None:
"""Verify correct attributes are set based on num_RC_pairs."""
-
missing_attrs = []
for j in range(1, self.num_RC_pairs + 1):
if not hasattr(self, 'R' + str(j)):
diff --git a/src/thevenin/_experiment.py b/src/thevenin/_experiment.py
index a0b07e1..7853a76 100644
--- a/src/thevenin/_experiment.py
+++ b/src/thevenin/_experiment.py
@@ -25,7 +25,7 @@ def __init__(self, **kwargs) -> None:
**kwargs : dict, optional
IDASolver keyword arguments that span all steps.
- See also
+ See Also
--------
~thevenin.solvers.IDASolver :
The solver class, with documentation for most keyword arguments
@@ -34,7 +34,6 @@ def __init__(self, **kwargs) -> None:
The correct model interface to use with the `Experiment` class.
"""
-
self._steps = []
self._kwargs = []
self._options = kwargs.copy()
@@ -49,7 +48,6 @@ def __repr__(self) -> str: # pragma: no cover
A console-readable instance representation.
"""
-
keys = ['num_steps', 'options']
values = [self.num_steps, self._options]
@@ -161,7 +159,7 @@ def add_step(self, mode: str, value: float | Callable, tspan: tuple,
ValueError
'tspan' arrays must be monotonically increasing.
- See also
+ See Also
--------
~thevenin.solvers.IDASolver :
The solver class, with documentation for most keyword arguments
@@ -188,7 +186,6 @@ def add_step(self, mode: str, value: float | Callable, tspan: tuple,
these checks fail, a `ValueError` is raised.
"""
-
_check_mode(mode)
_check_limits(limits)
@@ -257,7 +254,6 @@ def _check_mode(mode: str) -> None:
'mode' is invalid.
"""
-
valid = ['current_A', 'current_C', 'voltage_V', 'power_W']
if mode not in valid:
@@ -270,7 +266,7 @@ def _check_limits(limits: tuple[str, float]) -> None:
Parameters
----------
- limit : tuple[str, float]
+ limits : tuple[str, float]
Stopping criteria and limiting value.
Returns
@@ -285,7 +281,6 @@ def _check_limits(limits: tuple[str, float]) -> None:
A 'limits' name is invalid.
"""
-
valid = ['soc', 'temperature_K', 'current_A', 'current_C', 'voltage_V',
'power_W', 'capacity_Ah', 'time_s', 'time_min', 'time_h']
diff --git a/src/thevenin/_prediction.py b/src/thevenin/_prediction.py
index 782d9b3..11ed439 100644
--- a/src/thevenin/_prediction.py
+++ b/src/thevenin/_prediction.py
@@ -18,8 +18,8 @@ class TransientState:
def __init__(self, soc: float, T_cell: float, hyst: float,
eta_j: np.ndarray | None) -> None:
"""
- This class allows the user to manage the state when working with model
- classes. The user only has control over independent state variables
+ A container that allows users to manage the internal hidden states of
+ model classes. Users only have control over independent state variables
(i.e., soc, T_cell, hyst, eta_j). The :class:`~thevenin.Prediction`
interface requires an instance of the `TransientState` each time a
prediction step is made. You can optionally use instances of this class
@@ -42,13 +42,12 @@ def __init__(self, soc: float, T_cell: float, hyst: float,
eta_j : 1D np.array | None
RC pair overpotentials [V].
- See also
+ See Also
--------
Simulation : The model wrapper used for timeseries simulations.
Prediction : The model wrapper used for step-by-step predictions.
"""
-
self.soc = soc
self.T_cell = T_cell
self.hyst = hyst
@@ -77,7 +76,6 @@ def __repr__(self) -> str: # pragma: no cover
A console-readable instance representation.
"""
-
keys = self._repr_keys
values = [getattr(self, k) for k in keys]
@@ -140,7 +138,6 @@ def pre(self) -> None:
class instance.
"""
-
self._check_RC_pairs() # inherited from BaseModel
ptr = {}
@@ -160,19 +157,18 @@ def set_options(self, **options) -> None:
Parameters
----------
- **kwargs : dict, optional
+ **options : dict, optional
CVODESolver keyword arguments that span all steps. You can re-run
this method between prediction steps if you need different settings
per step.
- See also
+ See Also
--------
~thevenin.solvers.CVODESolver :
The solver class, with documentation for most keyword arguments
that you might want to adjust.
"""
-
from .solvers import CVODESolver
self._userdata = {}
@@ -202,7 +198,6 @@ def take_step(self, state: TransientState, current: float | Callable,
Predicted state at the end of the time step.
"""
-
if callable(current):
self._userdata['current'] = current
else:
@@ -258,7 +253,6 @@ def to_simulation(
RC pairs in the current model.
"""
-
from ._simulation import Simulation
sim = Simulation(self._get_params_dict)
@@ -281,7 +275,6 @@ def _to_state(self, array: np.ndarray) -> TransientState:
Human-interpretable instance of the state variables array.
"""
-
ptr = self._ptr.copy()
_ = ptr.pop('size')
@@ -312,7 +305,6 @@ def _to_array(self, state: TransientState) -> np.ndarray:
must be consistent with the model's 'num_RC_pairs' attribute.
"""
-
if state.num_RC_pairs != self.num_RC_pairs:
raise ValueError(f"{state.eta_j=} has an invalid length since"
f" num_RC_pairs={self.num_RC_pairs}.")
@@ -351,5 +343,4 @@ def _svdot(self, t: float, sv: np.ndarray, svdot: np.ndarray,
None.
"""
-
svdot[:] = self._rhsfn(t, sv, userdata)
diff --git a/src/thevenin/_simulation.py b/src/thevenin/_simulation.py
index e7addc4..3cca40f 100644
--- a/src/thevenin/_simulation.py
+++ b/src/thevenin/_simulation.py
@@ -67,7 +67,6 @@ def pre(self, state0: bool | Solution | TransientState = True) -> None:
RC pairs in the current model.
"""
-
from ._solutions import BaseSolution
from ._prediction import TransientState
@@ -130,7 +129,7 @@ def pre(self, state0: bool | Solution | TransientState = True) -> None:
self._sv0 = sv0
self._svdot0 = svdot0
- def run_step(self, exp: Experiment, stepidx: int) -> StepSolution:
+ def run_step(self, expr: Experiment, stepidx: int) -> StepSolution:
"""
Run a single experimental step.
@@ -154,7 +153,7 @@ def run_step(self, exp: Experiment, stepidx: int) -> StepSolution:
steps afterward. If at any time you want to reset the internal hidden
state back to a rested condition, use `pre()`.
- See also
+ See Also
--------
Experiment : Build an experiment.
StepSolution : Wrapper for a single-step solution.
@@ -168,12 +167,11 @@ def run_step(self, exp: Experiment, stepidx: int) -> StepSolution:
decisions to be performed between experimental steps.
"""
-
from .solvers import IDASolver
from ._solutions import StepSolution
- step = exp.steps[stepidx].copy()
- kwargs = exp._kwargs[stepidx].copy()
+ step = expr.steps[stepidx].copy()
+ kwargs = expr._kwargs[stepidx].copy()
if not callable(step['value']):
value = step['value']
@@ -200,7 +198,7 @@ def run_step(self, exp: Experiment, stepidx: int) -> StepSolution:
return soln
- def run(self, exp: Experiment, reset_state: bool = True,
+ def run(self, expr: Experiment, reset_state: bool = True,
t_shift: float = 1e-3) -> CycleSolution:
"""
Run a full experiment.
@@ -234,18 +232,17 @@ def run(self, exp: Experiment, reset_state: bool = True,
can bypass this by using `reset_state=False`, which keeps the state
at the end of the final experimental step.
- See also
+ See Also
--------
Experiment : Build an experiment.
CycleSolution : Wrapper for an all-steps solution.
"""
-
from ._solutions import CycleSolution
solns = []
- for i in range(exp.num_steps):
- solns.append(self.run_step(exp, i))
+ for i in range(expr.num_steps):
+ solns.append(self.run_step(expr, i))
soln = CycleSolution(*solns, t_shift=t_shift)
@@ -267,7 +264,6 @@ def to_prediction(self) -> Prediction:
properties as the current Simulation instance.
"""
-
from ._prediction import Prediction
return Prediction(self._get_params_dict)
@@ -299,7 +295,6 @@ def _resfn(self, t: float, sv: np.ndarray, svdot: np.ndarray,
None.
"""
-
res[:] = self._mass_matrix*svdot - self._rhsfn(t, sv, userdata)
@@ -308,7 +303,7 @@ class _EventsFunction:
def __init__(self, limits: tuple[str, float]) -> None:
"""
- This class is a generalized event function callable.
+ A generalized event function callable.
Parameters
----------
@@ -317,7 +312,6 @@ def __init__(self, limits: tuple[str, float]) -> None:
limit names and values, e.g., ('time_h', 10., 'voltage_V', 4.2).
"""
-
self.keys = limits[0::2]
self.values = limits[1::2]
self.size = len(self.keys)
@@ -352,7 +346,6 @@ def __call__(self, t: float, sv: np.ndarray, svdot: np.ndarray,
None.
"""
-
for i, (key, value) in enumerate(zip(self.keys, self.values)):
events[i] = userdata['events'][key] - value
@@ -380,7 +373,6 @@ def _setup_eventsfn(limits: tuple[str, float], kwargs: dict) -> None:
None.
"""
-
eventsfn = _EventsFunction(limits)
kwargs['eventsfn'] = eventsfn
diff --git a/src/thevenin/_solutions.py b/src/thevenin/_solutions.py
index 3062e5a..c866ede 100644
--- a/src/thevenin/_solutions.py
+++ b/src/thevenin/_solutions.py
@@ -29,6 +29,7 @@ class ExitHandler:
forgets to explicitly call it.
"""
+
_registered = []
@classmethod
@@ -49,7 +50,6 @@ def __init__(self) -> None:
consistent between all solutions.
"""
-
self.vars = {}
def __repr__(self) -> str: # pragma: no cover
@@ -62,7 +62,6 @@ def __repr__(self) -> str: # pragma: no cover
A console-readable instance representation.
"""
-
classname = self.__class__.__name__
def wrap_string(label: str, value: list, width: int):
@@ -115,7 +114,6 @@ def plot(self, x: str, y: str, **kwargs) -> None:
None.
"""
-
plt.figure()
plt.plot(self.vars[x], self.vars[y], **kwargs)
@@ -148,7 +146,6 @@ def _fill_vars(self) -> None:
None.
"""
-
from ._basemodel import calculated_current
sim = self._sim
@@ -219,7 +216,6 @@ def __init__(self, sim: Simulation, ida_soln: IDAResult,
Amount of time it took for IDASolver to perform the integration.
"""
-
super().__init__()
self._sim = deepcopy(sim)
@@ -278,7 +274,6 @@ def __init__(self, *soln: StepSolution, t_shift: float = 1e-3) -> None:
time of its following step. The default is 1e-3.
"""
-
super().__init__()
soln = deepcopy(list(soln)) # ensure type list, memory safe copy
@@ -378,7 +373,6 @@ def get_steps(self, idx: int | tuple) -> StepSolution | CycleSolution:
requested steps when 'idx' is a tuple.
"""
-
if isinstance(idx, int):
return deepcopy(self._solns[idx])
elif isinstance(idx, (tuple, list)):
@@ -428,7 +422,6 @@ def append_soln(self, soln: Solution, t_shift: float = 1e-3) -> None:
instances do not share any common memory.
"""
-
if not isinstance(soln, (StepSolution, CycleSolution)):
raise TypeError("'soln' input must be StepSolution, CycleSolution.")
diff --git a/src/thevenin/loadfns/__init__.py b/src/thevenin/loadfns/__init__.py
index bb3153e..50f76c0 100644
--- a/src/thevenin/loadfns/__init__.py
+++ b/src/thevenin/loadfns/__init__.py
@@ -1,5 +1,5 @@
"""
-This module contains classes to help construct time-varying load profiles.
+The `loadfns` module contains classes to construct time-varying load profiles.
All of the classes are callable after construction and take in a value of
time in seconds. Most load functions include a linear ramp that "smooths"
transitions from rest to a constant load, or between constant steps. Using
diff --git a/src/thevenin/loadfns/_ramps.py b/src/thevenin/loadfns/_ramps.py
index a3d757e..5aa3074 100644
--- a/src/thevenin/loadfns/_ramps.py
+++ b/src/thevenin/loadfns/_ramps.py
@@ -67,7 +67,6 @@ def __init__(self, m: float, step: float, b: float = 0.,
'sharpness' must be strictly positive.
"""
-
if m == 0. or abs(m) == np.inf:
raise ValueError("m = 0. and m = inf are invalid slopes.")
elif m > 0. and b >= step:
diff --git a/src/thevenin/loadfns/_steps.py b/src/thevenin/loadfns/_steps.py
index c5f53d7..307a928 100644
--- a/src/thevenin/loadfns/_steps.py
+++ b/src/thevenin/loadfns/_steps.py
@@ -15,7 +15,7 @@ def __init__(self, tp: np.ndarray, yp: np.ndarray, y0: float = 0.,
ignore_nan: bool = False) -> None:
"""
Construct a piecewise step function given the times at which step
- changes occur and the values for each time interval. For example,
+ changes occur and the values for each time interval. For example,.
.. code-block:: python
@@ -66,7 +66,6 @@ def __init__(self, tp: np.ndarray, yp: np.ndarray, y0: float = 0.,
[nan -1. 0. 1.]
"""
-
if tp.ndim != 1 or yp.ndim != 1:
raise ValueError("tp and yp must both be 1D.")
@@ -115,7 +114,7 @@ class RampedSteps:
def __init__(self, tp: np.ndarray, yp: np.ndarray, t_ramp: float,
y0: float = 0.) -> None:
"""
- This class acts like StepFunction, with the same tp, yp, and y0, but
+ A class similar to StepFunction, with the same tp, yp, and y0, but
step transitions include ramps with duration t_ramp. Generally, this
profile will be more stable compared to a StepFunction profile.
@@ -142,14 +141,13 @@ def __init__(self, tp: np.ndarray, yp: np.ndarray, t_ramp: float,
ValueError
tp must be strictly increasing.
- See also
+ See Also
--------
StepFunction :
Uses hard discontinuous steps rather than ramped steps. Generally
non-ideal for simulations, but may be useful elsewhere.
"""
-
if tp.ndim != 1 or yp.ndim != 1:
raise ValueError("tp and yp must both be 1D.")
diff --git a/src/thevenin/plotutils/_colors.py b/src/thevenin/plotutils/_colors.py
index 14c9591..d50888e 100644
--- a/src/thevenin/plotutils/_colors.py
+++ b/src/thevenin/plotutils/_colors.py
@@ -43,7 +43,6 @@ def get_colors(size: int, data: ndarray = None, norm: ndarray = None,
'norm' length must equal 2.
"""
-
import numpy as np
import matplotlib as mpl