Unverified Commit 9fbbd636 authored by René Fritze's avatar René Fritze Committed by GitHub
Browse files

Merge pull request #1648 from pymor/ci_tutorials

Refactor Tutorial testing
parents cdf0cbb5 7724c777
Pipeline #152541 passed with stages
in 30 minutes and 45 seconds
......@@ -36,6 +36,8 @@ dependencies:
- pytest-regressions==2.3.1
- pytest-xdist==2.5.0
- pytest==7.1.2
- qtpy>2.0
- rstcheck==6.0.0.post1
- scipy>=1.3
- scipy>=1.3.3
- scipy>=1.5.4
......
......@@ -23,8 +23,8 @@ stages:
when: never
- when: on_success
variables:
PYPI_MIRROR_TAG: 9382dd473883b0db4d4b88d71467802052cdc58a
CI_IMAGE_TAG: 9382dd473883b0db4d4b88d71467802052cdc58a
PYPI_MIRROR_TAG: a1f842181f0ac663b694820c516844c0c1af28b4
CI_IMAGE_TAG: a1f842181f0ac663b694820c516844c0c1af28b4
PYMOR_HYPOTHESIS_PROFILE: ci
PYMOR_PYTEST_EXTRA: ""
BINDERIMAGE: ${CI_REGISTRY_IMAGE}/binder:${CI_COMMIT_REF_SLUG}
......
......@@ -16,6 +16,10 @@ cd "${PYMOR_ROOT}"
# any failure here should fail the whole test
set -eux
# pymor checks if this file's owner uid matches with the interpreter executor's
ME=$(id -u)
sudo chown ${ME} docs/source/pymor_defaults.py
# switches default index to pypi-mirror service
export PIP_CONFIG_FILE=/usr/local/share/ci.pip.conf
......@@ -32,9 +36,11 @@ export PYTHONHASHSEED=0
python -c "from matplotlib import pyplot" || true
PYMOR_VERSION=$(python -c 'import pymor;print(pymor.__version__)')
# the tutorials script needs a different option
COV_OPTION=${COV_OPTION:---cov=}
# `--cov-report=` suppresses terminal output
COMMON_PYTEST_OPTS="--junitxml=test_results_${PYMOR_VERSION}.xml \
--cov-report= --cov --cov-config=setup.cfg --cov-context=test \
--cov-report= ${COV_OPTION} --cov-config=${PYMOR_ROOT}/setup.cfg --cov-context=test \
--hypothesis-profile ${PYMOR_HYPOTHESIS_PROFILE} ${PYMOR_PYTEST_EXTRA}"
pytest src/pymortests/docker_ci_smoketest.py
\ No newline at end of file
......@@ -4,8 +4,4 @@ THIS_DIR="$(cd "$(dirname ${BASH_SOURCE[0]})" ; pwd -P )"
export PYMOR_HYPOTHESIS_PROFILE=dev
source ${THIS_DIR}/common_test_setup.bash
# pymor checks if this file's owner uid matches with the interpreter executor's
ME=$(id -u)
chown ${ME} docs/source/pymor_defaults.py
make docs
#!/bin/bash
THIS_DIR="$(cd "$(dirname ${BASH_SOURCE[0]})" ; pwd -P )"
COV_OPTION="--nb-coverage"
source ${THIS_DIR}/common_test_setup.bash
xvfb-run -a py.test ${COMMON_PYTEST_OPTS} docs/test_tutorials.py
for fn in ${PYMOR_ROOT}/docs/source/tutorial*md ; do
mystnb-to-jupyter -o "${fn}" "${fn/tutorial/..\/converted_tutorial}".ipynb
done
# manually add plugins to load that are excluded for other runs
xvfb-run -a py.test ${COMMON_PYTEST_OPTS} -s --cov= -p no:pycharm \
-p nb_regression -p notebook docs/test_tutorials.py
coverage xml
......@@ -2,8 +2,8 @@ DOCKER_BASE_PYTHON=3.9
PYMOR_TEST_SCRIPT=vanilla
PYPI_MIRROR=stable
PYMOR_TEST_OS=debian-buster
PYPI_MIRROR_TAG=9382dd473883b0db4d4b88d71467802052cdc58a
CI_IMAGE_TAG=9382dd473883b0db4d4b88d71467802052cdc58a
PYPI_MIRROR_TAG=a1f842181f0ac663b694820c516844c0c1af28b4
CI_IMAGE_TAG=a1f842181f0ac663b694820c516844c0c1af28b4
PYMOR_HYPOTHESIS_PROFILE=dev
PYMOR_PYTEST_EXTRA=--lf
......@@ -34,7 +34,7 @@ jobs:
name: Event File
path: ${{ github.event_path }}
miniconda:
name: Conda ${{ matrix.os }} - Python ${{ matrix.python }}
name: ${{ matrix.os }} - Python ${{ matrix.python }}
runs-on: ${{ matrix.os }}
timeout-minutes: 55
strategy:
......@@ -160,19 +160,16 @@ jobs:
name: Conda Env Exports
path: conda-env__${{ runner.os }}-${{ matrix.python }}.yml
full_install:
name: Conda ${{ matrix.os }} - Python ${{ matrix.python }}
name: Install with on ${{ matrix.os }} - Python ${{ matrix.python }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-11, windows-2022]
os: [ubuntu-22.04, macos-12, windows-2022]
python: [3.8, 3.9]
include:
- os: ubuntu-20.04
- os: ubuntu-22.04
prefix: /usr/share/miniconda3/envs/pyMOR-ci
- os: macos-11
prefix: /Users/runner/miniconda3/envs/pyMOR-ci
- os: macos-10.15
python: 3.8
- os: macos-12
prefix: /Users/runner/miniconda3/envs/pyMOR-ci
- os: windows-2022
prefix: C:\Miniconda3\envs\pyMOR-ci
......
......@@ -99,7 +99,7 @@ docker_docs: docker_image
docker_run: docker_image
$(DOCKER_COMPOSE) up -d pypi_mirror
$(DOCKER_COMPOSE) run --service-ports pytest bash
$(DOCKER_COMPOSE) down pypi_mirror
$(DOCKER_COMPOSE) down --remove-orphans -v
docker_exec: docker_image
$(DOCKER_COMPOSE) run --service-ports pytest bash -l -c "${DOCKER_CMD}"
......@@ -119,7 +119,7 @@ docker_run_oldest: PYPI_MIRROR=oldest
docker_run_oldest: docker_image
$(DOCKER_COMPOSE) up -d pypi_mirror
$(DOCKER_COMPOSE) run pytest bash
$(DOCKER_COMPOSE) down pypi_mirror
$(DOCKER_COMPOSE) down --remove-orphans -v
docker_jupyter: docker_image
NB_DIR=$(NB_DIR) $(DOCKER_COMPOSE) up jupyter
......
......@@ -78,6 +78,7 @@ ci_requires = ['check-manifest==0.48',
_PYTEST,
'pytest-cov==3.0.0',
'pytest-memprof==0.2.0',
'pytest-notebook==0.8.0',
'pytest-parallel==0.1.1',
'pytest-regressions==2.3.1',
'pytest-xdist==2.5.0',
......
......@@ -21,11 +21,12 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
```
:tags: [remove-cell]
```
Tutorial: Building a Reduced Basis
==================================
......@@ -299,6 +300,7 @@ which will give us a {math}`25\times 1` {{ NumPy_array }} of all inner products.
```{code-cell}
:tags: [remove-cell]
assert R.shape == (25,1)
```
......@@ -530,6 +532,7 @@ re-orthogonalization to improve numerical accuracy:
```{code-cell}
:tags: [remove-output]
gram_schmidt(greedy_basis, product=fom.h1_0_semi_product, copy=False)
gram_schmidt(trivial_basis, product=fom.h1_0_semi_product, copy=False)
```
......
......@@ -21,11 +21,12 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
```
:tags: [remove-cell]
```
# Tutorial: Reducing an LTI system using balanced truncation
......@@ -50,6 +51,8 @@ Then we build the matrices
```{code-cell}
:load: heat_equation.py
```
and form the full-order model.
......@@ -267,5 +270,3 @@ print(f'Relative Hankel error: {err.hankel_norm() / fom.hankel_norm():.3e}')
Download the code:
{download}`tutorial_bt.md`,
{nb-download}`tutorial_bt.ipynb`.
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Using pyMOR’s discretization toolkit
......@@ -501,5 +503,3 @@ for all possible parameter value combinations.
Download the code:
{download}`tutorial_builtin_discretizer.md`
{nb-download}`tutorial_builtin_discretizer.ipynb`
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Binding an external PDE solver to pyMOR
......@@ -41,16 +43,18 @@ First, we need a class to store our data in and with some basic linear algebra o
declared on it.
```{literalinclude} minimal_cpp_demo/model.hh
:lines: 4-19
:language: cpp
:lines: 4-19
```
Next, we need the operator that discretizes our PDE.
```{literalinclude} minimal_cpp_demo/model.hh
:lines: 22-32
:language: cpp
:lines: 22-32
```
......@@ -59,16 +63,16 @@ Together with some header guards, these two snippets make up our {download}`mode
The definitions for the `Vector` class are pretty straightforward:
```{literalinclude} minimal_cpp_demo/model.cc
:lines: 7-35
:language: cpp
:lines: 7-35
```
Just like the diffusion operator that computes a central differences stencil:
```{literalinclude} minimal_cpp_demo/model.cc
:lines: 39-49
:language: cpp
:lines: 39-49
```
......@@ -97,8 +101,8 @@ to represent the full-order model.
All of the C++ code related to the extension module is defined inside a scope started with
```{literalinclude} minimal_cpp_demo/model.cc
:lines: 56-57
:language: cpp
:lines: 56-57
```
......@@ -190,13 +194,13 @@ compilation.
```{code-cell}
:tags: [raises-exception]
%%bash
mkdir -p minimal_cpp_demo/build
cmake -B minimal_cpp_demo/build -S minimal_cpp_demo
make -C minimal_cpp_demo/build
```
To be able to use this extension module we need to insert the build directory into the path where the Python
interpreter looks for things to import. Afterwards we can import the module and create and use the exported classes.
......@@ -209,7 +213,6 @@ mymodel = model.DiffusionOperator(10, 0, 1)
myvector = model.Vector(10, 0)
mymodel.apply(myvector, myvector)
dir(model)
```
## Using the exported Python classes with pyMOR
......@@ -437,5 +440,3 @@ fom.visualize((U-U_RB), title=f'mu = {mu}', legend=('error'))
You can download this demonstration plus the wrapper definitions as a
notebook {nb-download}`tutorial_external_solver.ipynb` or
as Markdown text {download}`tutorial_external_solver.md`.
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Linear time-invariant systems
......@@ -108,6 +110,8 @@ approximation using standard methods of NumPy and SciPy.
```{code-cell}
:load: heat_equation.py
```
Then, we can create an {{ LTIModel }} from NumPy and SciPy matrices `A`, `B`, `C`,
......@@ -503,4 +507,3 @@ fom.hankel_norm()
Download the code:
{download}`tutorial_lti_systems.md`,
{nb-download}`tutorial_lti_systems.ipynb`.
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Model order reduction with artificial neural networks
......@@ -167,8 +169,8 @@ To train the neural network, we create a training and a validation set
consisting of 100 and 20 randomly chosen {{ parameter_values }}, respectively:
```{code-cell}
training_set = parameter_space.sample_uniformly(100)
validation_set = parameter_space.sample_randomly(20)
training_set = parameter_space.sample_uniformly(100)
validation_set = parameter_space.sample_randomly(20)
```
In this tutorial, we construct the reduced basis such that no more modes than
......@@ -407,5 +409,3 @@ corresponding {class}`~pymor.models.neural_network.NeuralNetworkInstationaryStat
Download the code:
{download}`tutorial_mor_with_anns.md`
{nb-download}`tutorial_mor_with_anns.ipynb`
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Model order reduction for PDE-constrained optimization problems
......@@ -766,7 +768,8 @@ report(opt_along_path_adaptively_result, opt_along_path_adaptively_minimization_
```
```{code-cell}
:tags: [hide-code,hide-output]
:tags: [hide-code, hide-output]
assert fom_result.nit == 7
assert opt_along_path_result.nit == 7
assert opt_along_path_minimization_data['num_evals'] == 9
......
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Projecting a Model
......@@ -564,7 +566,7 @@ In the case of `fom.operator`, which is a {{ LincombOperator }}, the rule with i
be the first matching rule. We can take a look at it:
```{code-cell}
:tags: [hide-code,hide-output]
:tags: [hide-code, hide-output]
assert ProjectRules.rules[8].action_description == 'LincombOperator'
```
......@@ -588,7 +590,8 @@ In our case, `ProjectRules` will be applied to all {{ NumpyMatrixOperators }} he
will apply:
```{code-cell}
:tags: [hide-code,hide-output]
:tags: [hide-code, hide-output]
assert ProjectRules.rules[3].action_description == 'apply_basis'
```
......@@ -674,5 +677,3 @@ print_source(reductor.reconstruct)
Download the code:
{download}`tutorial_projection.md`
{nb-download}`tutorial_projection.ipynb`
......@@ -21,8 +21,10 @@ kernelspec:
```
```{code-cell}
:tags: [remove-cell]
:load: myst_code_init.py
:tags: [remove-cell]
```
# Tutorial: Model order reduction for unstable LTI systems
......@@ -89,6 +91,8 @@ approximation using standard methods of NumPy and SciPy. Here we use
```{code-cell}
:load: unstable_heat_equation.py
```
Then, we can create an {{ LTIModel }} from NumPy and SciPy matrices `A`, `B`, `C`,
......
......@@ -2,86 +2,36 @@
# Copyright pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (https://opensource.org/licenses/BSD-2-Clause)
import os
import sys
from pathlib import Path
import io
import importlib.machinery
import importlib.util
from docutils.core import publish_doctree
from docutils.parsers.rst import Directive
from docutils.parsers.rst.directives import flag, register_directive
import pytest
from pytest_notebook.nb_regression import NBRegressionFixture
from pytest_notebook.plugin import gather_config_options
from pymor.tools.io import change_to_directory
from pymortests.base import runmodule
from pymortests.demos import _test_demo
TUT_DIR = Path(os.path.dirname(__file__)).resolve() / 'source'
TUT_DIR = Path(os.path.dirname(__file__)).resolve().absolute()
_exclude_files = []
EXCLUDE = [TUT_DIR / t for t in _exclude_files]
TUTORIALS = [t for t in TUT_DIR.glob('tutorial_*rst') if t not in EXCLUDE]
TUTORIALS += [t for t in TUT_DIR.glob('tutorial_*md') if t not in EXCLUDE]
TUTORIALS = [t for t in TUT_DIR.glob('converted_tutorial_*ipynb') if t not in EXCLUDE]
class CodeCell(Directive):
class NBLaxFixture(NBRegressionFixture):
"""Same functionality as base class, but result comparison for regressions is skipped"""
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {'hide-output': flag,
'hide-code': flag,
'raises': flag}
has_content = True
def check(self, path):
return super().check(path=path, raise_errors=False)
def run(self):
self.assert_has_content()
if 'raises' in self.options:
text = 'try:\n ' + '\n '.join(
self.content) + '\nexcept:\n import traceback; traceback.print_exc()'
else:
text = '\n'.join(self.content)
print('# %%')
print(text)
print()
return []
@pytest.fixture(scope="function")
def nb_lax(pytestconfig):
kwargs, other_args = gather_config_options(pytestconfig)
return NBLaxFixture(**kwargs)
@pytest.fixture(params=TUTORIALS, ids=[t.name for t in TUTORIALS])
def tutorial_code(request):
filename = request.param
with change_to_directory(TUT_DIR):
code = io.StringIO()
register_directive('jupyter-execute', CodeCell)
with open(filename, 'rt') as f:
original = sys.stdout
sys.stdout = code
publish_doctree(f.read(), settings_overrides={'report_level': 42})
sys.stdout = original
code.seek(0)
source_fn = Path(f'{str(filename).replace(".rst", "_rst")}_extracted.py')
with open(source_fn, 'wt') as source:
# filter line magics
source.write(''.join([line for line in code.readlines() if not line.startswith('%')]))
return request.param, source_fn
def test_tutorial(tutorial_code):
filename, source_module_path = tutorial_code
# make sure (picture) resources can be loaded as in sphinx-build
with change_to_directory(TUT_DIR):
def _run():
loader = importlib.machinery.SourceFileLoader(source_module_path.stem, str(source_module_path))
spec = importlib.util.spec_from_loader(loader.name, loader)
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
try:
# wrap module execution in hacks to auto-close Qt-Apps, etc.
_test_demo(_run)
except Exception as e:
print(f'Failed: {source_module_path}')
raise e
@pytest.mark.parametrize("filename", TUTORIALS, ids=[t.name for t in TUTORIALS])
def test_check(filename, nb_lax):
nb_lax.check(filename)
if __name__ == "__main__":
......
......@@ -13,6 +13,7 @@ pyqt5-qt5==5.15.2
pyqt5==5.15.7
pytest-cov==3.0.0
pytest-memprof==0.2.0
pytest-notebook==0.8.0
pytest-parallel==0.1.1
pytest-regressions==2.3.1
pytest-xdist==2.5.0
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment