Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splits integration / examples / unit tests #204

Merged
merged 18 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/scheduled_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ on:
branches:
- main

# runs every day at 09:00 UTC
# runs every day at 09:00 and 15:00 UTC
schedule:
- cron: '0 9 * * *'
- cron: '0 15 * * *'

# Check noxfile.py for associated environment variables
env:
Expand Down Expand Up @@ -84,7 +85,7 @@ jobs:
python -m pip install --upgrade pip nox

- name: Unit tests with nox
run: python -m nox -s unit
run: python -m nox -s coverage

- name: Run notebooks with nox
run: python -m nox -s notebooks
Expand Down Expand Up @@ -117,7 +118,7 @@ jobs:
eval "$(pyenv init -)"
pyenv activate pybop-${{ matrix.python_version }}-${{ matrix.pybamm_version }}
python -m pip install --upgrade pip nox
python -m nox -s unit
python -m nox -s coverage
python -m nox -s notebooks

- name: Uninstall pyenv-virtualenv & python
Expand Down
70 changes: 67 additions & 3 deletions .github/workflows/test_on_push.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: test_on_push
name: PyBOP

on:
workflow_dispatch:
Expand Down Expand Up @@ -27,8 +27,36 @@ jobs:
python -m pip install pre-commit
pre-commit run ruff

integration_tests:
needs: style
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.11"]
# Include MacOS M-series Runners
include:
- os: macos-14
python-version: "3.11"

name: Integration tests (${{ matrix.os }} / Python ${{ matrix.python-version }})
BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip nox

- name: Integration tests
run: |
nox -s integration

build:
unit_tests:
needs: style
runs-on: ${{ matrix.os }}
strategy:
Expand All @@ -48,6 +76,8 @@ jobs:
- os: macos-14
python-version: "3.12"

name: Unit tests (${{ matrix.os }} / Python ${{ matrix.python-version }})

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -57,13 +87,47 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip nox
- name: Unit and notebook tests with nox

- name: Unit tests
run: |
nox -s unit

example_tests:
needs: style
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.11"]
# Include MacOS M-series Runners
include:
- os: macos-14
python-version: "3.11"

BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved
name: Test examples (${{ matrix.os }} / Python ${{ matrix.python-version }})

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip nox

- name: Run examples
run: |
nox -s examples

- name: Run notebooks
run: |
nox -s notebooks

# Runs only on Ubuntu with Python 3.11
check_coverage:
needs: style
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
63 changes: 47 additions & 16 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,21 @@ def pytest_addoption(parser):
parser.addoption(
"--unit", action="store_true", default=False, help="run unit tests"
)
parser.addoption(
"--integration",
action="store_true",
default=False,
help="run integration tests",
)
parser.addoption(
"--examples", action="store_true", default=False, help="run examples tests"
)
parser.addoption(
"--plots", action="store_true", default=False, help="run plotting tests"
)
parser.addoption(
"--notebooks", action="store_true", default=False, help="run notebook tests"
)


def pytest_terminal_summary(terminalreporter, exitstatus, config):
Expand All @@ -25,28 +37,47 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):

def pytest_configure(config):
config.addinivalue_line("markers", "unit: mark test as a unit test")
config.addinivalue_line("markers", "integration: mark test as an integration test")
config.addinivalue_line("markers", "examples: mark test as an example")
config.addinivalue_line("markers", "plots: mark test as a plot test")
config.addinivalue_line("markers", "notebook: mark test as a notebook test")


def pytest_collection_modifyitems(config, items):
unit_option = config.getoption("--unit")
examples_option = config.getoption("--examples")
options = {
"unit": "unit",
"examples": "examples",
"integration": "integration",
"plots": "plots",
"notebooks": "notebooks",
}
selected_markers = [
marker for option, marker in options.items() if config.getoption(option)
]

if not unit_option and not examples_option:
skip_all = pytest.mark.skip(reason="need --unit or --examples option to run")
for item in items:
item.add_marker(skip_all)
if (
"notebooks" in selected_markers
): # Notebooks are meant to be run as an individual session
return

elif unit_option and not examples_option:
skip_examples = pytest.mark.skip(
reason="need --examples option to run examples tests"
# If no options were passed, skip all tests
if not selected_markers:
skip_all = pytest.mark.skip(
reason="Need at least one of --unit, --examples, --integration, or --plots option to run"
)
for item in items:
if "examples" in item.keywords:
item.add_marker(skip_examples)
item.add_marker(skip_all)
return

if examples_option and not unit_option:
skip_unit = pytest.mark.skip(reason="need --unit option to run unit tests")
for item in items:
if "unit" in item.keywords:
item.add_marker(skip_unit)
# Skip tests that don't match any of the selected markers
for item in items:
item_markers = {
mark.name for mark in item.iter_markers()
} # Gather markers of the test item
if not item_markers.intersection(
selected_markers
): # Skip if there's no intersection with selected markers
skip_this = pytest.mark.skip(
reason=f"Test does not match the selected options: {', '.join(selected_markers)}"
)
item.add_marker(skip_this)
30 changes: 26 additions & 4 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,41 @@ def unit(session):
session.install("-e", ".[all,dev]", silent=False)
if PYBOP_SCHEDULED:
session.run("pip", "install", f"pybamm=={PYBAMM_VERSION}", silent=False)
session.run("pytest", "--unit")
session.run("pytest", "--unit", "-n", "auto")
BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved


@nox.session
def coverage(session):
session.install("-e", ".[all,dev]", silent=False)
if PYBOP_SCHEDULED:
session.run("pip", "install", f"pybamm=={PYBAMM_VERSION}", silent=False)
session.run(
"pytest", "--unit", "--cov", "--cov-append", "--cov-report=xml", "-n", "auto"
)
session.run(
"pytest",
"--unit",
"--examples",
"--integration",
"--cov",
"--cov-append",
"--cov-report=xml",
"-n",
"auto",
)
session.run("pytest", "--plots", "--cov", "--cov-append", "--cov-report=xml")


@nox.session
def integration(session):
session.run_always("pip", "install", "-e", ".[all,dev]")
BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved
session.install("pytest", "pytest-mock")
session.run("pytest", "--integration", "-n", "auto")


@nox.session
def examples(session):
session.run_always("pip", "install", "-e", ".[all,dev]")
session.install("pytest", "pytest-mock")
BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved
session.run("pytest", "--examples", "-n", "auto")


@nox.session
Expand All @@ -39,7 +59,9 @@ def notebooks(session):
session.install("-e", ".[all,dev]", silent=False)
if PYBOP_SCHEDULED:
session.run("pip", "install", f"pybamm=={PYBAMM_VERSION}", silent=False)
session.run("pytest", "--nbmake", "--examples", "examples/", external=True)
session.run(
"pytest", "--notebooks", "--nbmake", "examples/", "-n", "auto", external=True
)
BradyPlanden marked this conversation as resolved.
Show resolved Hide resolved


@nox.session
Expand Down
2 changes: 1 addition & 1 deletion pybop/_optimisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ def set_max_iterations(self, iterations=1000):
raise ValueError("Maximum number of iterations cannot be negative.")
self._max_iterations = iterations

def set_max_unchanged_iterations(self, iterations=25, threshold=1e-5):
def set_max_unchanged_iterations(self, iterations=5, threshold=1e-5):
"""
Set the maximum number of iterations without significant change as a stopping criterion.
Credit: PINTS
Expand Down
40 changes: 40 additions & 0 deletions pybop/costs/fitting_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,46 @@
else:
return np.sqrt(np.mean((prediction - self._target) ** 2))

def _evaluateS1(self, x):
"""
Compute the cost and its gradient with respect to the parameters.

Parameters
----------
x : array-like
The parameters for which to compute the cost and gradient.

Returns
-------
tuple
A tuple containing the cost and the gradient. The cost is a float,
and the gradient is an array-like of the same length as `x`.

Raises
------
ValueError
If an error occurs during the calculation of the cost or gradient.
"""
y, dy = self.problem.evaluateS1(x)
if len(y) < len(self._target):
e = np.float64(np.inf)
de = self._de * np.ones(self.n_parameters)

Check warning on line 70 in pybop/costs/fitting_costs.py

View check run for this annotation

Codecov / codecov/patch

pybop/costs/fitting_costs.py#L69-L70

Added lines #L69 - L70 were not covered by tests
else:
dy = dy.reshape(
(
self.problem.n_time_data,
self.n_outputs,
self.n_parameters,
)
)
r = y - self._target
e = np.sqrt(np.mean((r) ** 2))
de = np.mean((r.T * dy.T), axis=2) / np.sqrt(
np.mean((r.T * dy.T) ** 2, axis=2)
)

return e, de.flatten()


class SumSquaredError(BaseCost):
"""
Expand Down
File renamed without changes.
Loading
Loading