Unverified Commit eba8bfca authored by Stephan Rave's avatar Stephan Rave Committed by GitHub

Merge pull request #1118 from pymor/typer

Use typer in demo scripts
parents ee0e7817 4987f9ea
Pipeline #68553 passed with stages
in 40 minutes and 28 seconds
......@@ -27,7 +27,8 @@ def setup_requires():
other = ['setuptools>=40.8.0,<49.2.0', 'wheel', 'pytest-runner>=2.9', 'cython>=0.28', 'packaging',]
return numpys + other
install_requires = ['scipy>=1.1;python_version < "3.8"','scipy>=1.3.3;python_version >= "3.8"', 'Qt.py>=1.2.4', 'packaging','diskcache', 'docopt-ng'] + setup_requires()
install_requires = ['scipy>=1.1;python_version < "3.8"','scipy>=1.3.3;python_version >= "3.8"', 'Qt.py>=1.2.4',
'packaging','diskcache', 'typer'] + setup_requires()
install_suggests = {'ipython>=5.0': 'an enhanced interactive python shell',
'ipyparallel>=6.2.5': 'required for pymor.parallel.ipython',
'matplotlib': 'needed for error plots in demo scipts',
......
......@@ -6,7 +6,6 @@ Qt.py>=1.2.4
bash_kernel
cython>=0.28
diskcache
docopt-ng
gmsh
https://pymor.github.io/wheels/pymess-1.0.0-cp36-cp36m-manylinux1_x86_64.whl ; python_version == "3.6" and "linux" in sys_platform
https://pymor.github.io/wheels/pymess-1.0.0-cp37-cp37m-manylinux1_x86_64.whl ; python_version == "3.7" and "linux" in sys_platform
......@@ -40,4 +39,5 @@ sphinx-qt-documentation
sphinx>=1.7
sympy
torch
typer
wheel
......@@ -2,7 +2,6 @@
Qt.py>=1.2.4
cython>=0.28
diskcache
docopt-ng
numpy>=1.15.4;python_version == "3.7"
numpy>=1.16.0;python_version != "3.6" and python_version != "3.7" and python_version != "3.8"
numpy>=1.16.0;python_version == "3.6"
......@@ -12,4 +11,5 @@ pytest-runner>=2.9
scipy>=1.1;python_version < "3.8"
scipy>=1.3.3;python_version >= "3.8"
setuptools>=40.8.0,<49.2.0
typer
wheel
......@@ -8,6 +8,7 @@ ignore = E221,E226,E241,E242, E731, E741, W0105, W503, N803, N806
# E242 tab after `,' [ignored by default]
# E731 do not assign a lambda expression, use a def
# E741 do not use variables named 'l', 'O', or 'I'
# F722 syntax error in forward annotation
# W0105 String statement has no effect (we use triple qoted strings as documentation in some files)
# W503 line break before binary operator
# N803 argument name should be lowercase (we use single capital letters everywhere for vectorarrays)
......@@ -16,7 +17,7 @@ ignore = E221,E226,E241,E242, E731, E741, W0105, W503, N803, N806
[flake8]
max-line-length = 120
max-doc-length = 100
ignore = E221,E226,E241,E242, E731, E741, W0105, W503, N803, N806
ignore = E221,E226,E241,E242, E731, E741, F722, W0105, W503, N803, N806
# The following exclude avoids wrong warnings for unused imports
exclude = __init__.py
......
......@@ -96,7 +96,6 @@ def is_nbconvert():
_PACKAGES = {
'CYTHON': lambda: import_module('cython').__version__,
'DEALII': lambda: import_module('pydealii'),
'DOCOPT': lambda: import_module('docopt').__version__,
'FENICS': _get_fenics_version,
'GL': lambda: import_module('OpenGL.GL') and import_module('OpenGL').__version__,
'IPYTHON': _get_ipython_version,
......@@ -118,6 +117,7 @@ _PACKAGES = {
'SLYCOT': lambda: _get_slycot_version(),
'SPHINX': lambda: import_module('sphinx').__version__,
'TORCH': lambda: import_module('torch').__version__,
'TYPER': lambda: import_module('typer').__version__,
}
......
#!/usr/bin/env python
# This file is part of the pyMOR project (http://www.pymor.org).
# Copyright 2013-2020 pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
from enum import Enum
def Choices(choices):
"""Multipe-choice options for typer.
This is a convenicence function that creates string Enums to be
used as the type of command-line arguments that can take a fixed set
of values. For example, the command::
@app.command()
def main(arg: Choices('value1 value2')):
pass
takes one argument that may either have the value `value1` or the
value `value2`.
"""
class StringEnum(str, Enum):
pass
return StringEnum('Choices', ((o, o) for o in choices.split(' ')))
......@@ -3,13 +3,16 @@
# Copyright 2013-2020 pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
"""Analyze pickled data demo.
import sys
import time
Usage:
analyze_pickle.py histogram [--detailed=DETAILED_DATA] [--error-norm=NORM] REDUCED_DATA SAMPLES
analyze_pickle.py convergence [--detailed=DETAILED_DATA] [--error-norm=NORM] [--ndim=NDIM] REDUCED_DATA SAMPLES
analyze_pickle.py (-h | --help)
import numpy as np
import matplotlib.pyplot as plt
from typer import Argument, Option, Typer
from pymor.core.pickle import load
app = Typer(help='''
This demo loads a pickled reduced model, solves for random
parameters, estimates the reduction errors and then visualizes these
estimates. If the detailed model and the reductor are
......@@ -18,49 +21,25 @@ the real reduction error.
The needed data files are created by the thermal block demo, by
setting the '--pickle' option.
'''[1:])
Arguments:
REDUCED_DATA File containing the pickled reduced model.
SAMPLES Number of parameter samples to test with.
Options:
--detailed=DETAILED_DATA File containing the high-dimensional model
and the reductor.
--error-norm=NORM Name of norm in which to compute the errors.
REDUCED_DATA = Argument(..., help='File containing the pickled reduced model.')
SAMPLES = Argument(..., min=1, help='Number of parameter samples to test with. ')
ERROR_NORM = Option(None, help='Name of norm in which to compute the errors.')
--ndim=NDIM Number of reduced basis dimensions for which to estimate
the error.
"""
import sys
import time
import numpy as np
import matplotlib.pyplot as plt
from docopt import docopt
from pymor.core.pickle import load
@app.command()
def histogram(
reduced_data: str = REDUCED_DATA,
samples: int = SAMPLES,
def _bins(start, stop, steps=100):
''' numpy has a quirk in unreleased master where logspace
might sometimes not return a 1d array
'''
bins = np.logspace(np.log10(start), np.log10(stop), steps)
if bins.shape == (steps,1):
bins = bins[:,0]
return bins
def analyze_pickle_histogram(args):
args['SAMPLES'] = int(args['SAMPLES'])
detailed_data: str = Option(None, help='File containing the high-dimensional model and the reductor.'),
error_norm: str = ERROR_NORM
):
print('Loading reduced model ...')
rom, parameter_space = load(open(args['REDUCED_DATA'], 'rb'))
rom, parameter_space = load(open(reduced_data, 'rb'))
mus = parameter_space.sample_randomly(args['SAMPLES'])
mus = parameter_space.sample_randomly(samples)
us = []
for mu in mus:
print(f'Solving reduced for {mu} ... ', end='')
......@@ -78,17 +57,17 @@ def analyze_pickle_histogram(args):
ests.append(rom.estimate_error(mu))
print('done')
if args['--detailed']:
if detailed_data:
print('Loading high-dimensional data ...')
fom, reductor = load(open(args['--detailed'], 'rb'))
fom, reductor = load(open(detailed_data, 'rb'))
errs = []
for u, mu in zip(us, mus):
print(f'Calculating error for {mu} ... ')
sys.stdout.flush()
err = fom.solve(mu) - reductor.reconstruct(u)
if args['--error-norm']:
errs.append(np.max(getattr(fom, args['--error-norm'] + '_norm')(err)))
if error_norm:
errs.append(np.max(getattr(fom, error_norm + '_norm')(err)))
else:
errs.append(np.max(err.norm()))
print('done')
......@@ -100,7 +79,7 @@ def analyze_pickle_histogram(args):
except AttributeError:
pass # plt.style is only available in newer matplotlib versions
if hasattr(rom, 'estimate') and args['--detailed']:
if hasattr(rom, 'estimate') and detailed_data:
# setup axes
left, width = 0.1, 0.65
......@@ -155,7 +134,7 @@ def analyze_pickle_histogram(args):
plt.show()
elif args['--detailed']:
elif detailed_data:
total_min = np.min(ests) * 0.9
total_max = np.max(ests) * 1.1
......@@ -172,25 +151,29 @@ def analyze_pickle_histogram(args):
raise ValueError('Nothing to plot!')
def analyze_pickle_convergence(args):
args['SAMPLES'] = int(args['SAMPLES'])
@app.command()
def convergence(
reduced_data: str = REDUCED_DATA,
detailed_data: str = Argument(..., help='File containing the high-dimensional model and the reductor.'),
samples: int = SAMPLES,
error_norm: str = ERROR_NORM,
ndim: int = Option(None, help='Number of reduced basis dimensions for which to estimate the error.')
):
print('Loading reduced model ...')
rom, parameter_space = load(open(args['REDUCED_DATA'], 'rb'))
rom, parameter_space = load(open(reduced_data, 'rb'))
if not args['--detailed']:
raise ValueError('High-dimensional data file must be specified.')
print('Loading high-dimensional data ...')
fom, reductor = load(open(args['--detailed'], 'rb'))
fom, reductor = load(open(detailed_data, 'rb'))
fom.enable_caching('disk')
dim = rom.solution_space.dim
if args['--ndim']:
dims = np.linspace(0, dim, args['--ndim'], dtype=np.int)
if ndim:
dims = np.linspace(0, dim, ndim, dtype=np.int)
else:
dims = np.arange(dim + 1)
mus = parameter_space.sample_randomly(args['SAMPLES'])
mus = parameter_space.sample_randomly(samples)
ESTS = []
ERRS = []
......@@ -224,8 +207,8 @@ def analyze_pickle_convergence(args):
errs = []
for u, mu in zip(us, mus):
err = fom.solve(mu) - reductor.reconstruct(u)
if args['--error-norm']:
errs.append(np.max(getattr(fom, args['--error-norm'] + '_norm')(err)))
if error_norm:
errs.append(np.max(getattr(fom, error_norm + '_norm')(err)))
else:
errs.append(np.max(err.norm()))
ERRS.append(max(errs))
......@@ -257,15 +240,15 @@ def analyze_pickle_convergence(args):
plt.show()
def analyze_pickle_demo(args):
if args['histogram']:
analyze_pickle_histogram(args)
else:
analyze_pickle_convergence(args)
def _bins(start, stop, steps=100):
''' numpy has a quirk in unreleased master where logspace
might sometimes not return a 1d array
'''
bins = np.logspace(np.log10(start), np.log10(stop), steps)
if bins.shape == (steps, 1):
bins = bins[:, 0]
return bins
if __name__ == '__main__':
# parse arguments
args = docopt(__doc__)
# run demo
analyze_pickle_demo(args)
app()
......@@ -5,75 +5,59 @@
"""Burgers demo.
Solves a two-dimensional Burgers-type equation. See pymor.analyticalproblems.burgers for more details.
Usage:
burgers.py [-h] [--grid=NI] [--grid-type=TYPE] [--initial-data=TYPE] [--lxf-lambda=VALUE] [--nt=COUNT]
[--not-periodic] [--num-flux=FLUX] [--vx=XSPEED] [--vy=YSPEED] EXP
Arguments:
EXP Exponent
Options:
--grid=NI Use grid with (2*NI)*NI elements [default: 60].
--grid-type=TYPE Type of grid to use (rect, tria) [default: rect].
--initial-data=TYPE Select the initial data (sin, bump) [default: sin]
--lxf-lambda=VALUE Parameter lambda in Lax-Friedrichs flux [default: 1].
--nt=COUNT Number of time steps [default: 100].
--not-periodic Solve with dirichlet boundary conditions on left
and bottom boundary.
--num-flux=FLUX Numerical flux to use (lax_friedrichs, engquist_osher)
[default: engquist_osher].
-h, --help Show this message.
--vx=XSPEED Speed in x-direction [default: 1].
--vy=YSPEED Speed in y-direction [default: 1].
"""
import sys
import math
import time
from docopt import docopt
from typer import Argument, Option, run
from pymor.analyticalproblems.burgers import burgers_problem_2d
from pymor.discretizers.builtin import discretize_instationary_fv, RectGrid, TriaGrid
from pymor.tools.typer import Choices
def burgers_demo(args):
args['--grid'] = int(args['--grid'])
args['--grid-type'] = args['--grid-type'].lower()
assert args['--grid-type'] in ('rect', 'tria')
args['--initial-data'] = args['--initial-data'].lower()
assert args['--initial-data'] in ('sin', 'bump')
args['--lxf-lambda'] = float(args['--lxf-lambda'])
args['--nt'] = int(args['--nt'])
args['--not-periodic'] = bool(args['--not-periodic'])
args['--num-flux'] = args['--num-flux'].lower()
assert args['--num-flux'] in ('lax_friedrichs', 'engquist_osher', 'simplified_engquist_osher')
args['--vx'] = float(args['--vx'])
args['--vy'] = float(args['--vy'])
args['EXP'] = float(args['EXP'])
def main(
exp: float = Argument(..., help='Exponent'),
grid: int = Option(60, help='Use grid with (2*NI)*NI elements.'),
grid_type: Choices('rect tria') = Option('rect', help='Type of grid to use.'),
initial_data: Choices('sin bump') = Option('sin', help='Select the initial data.'),
lxf_lambda: float = Option(1., help='Parameter lambda in Lax-Friedrichs flux.'),
periodic: bool = Option(True, help='If not, solve with dirichlet boundary conditions on left and bottom boundary.'),
nt: int = Option(100, help='Number of time steps.'),
num_flux: Choices('lax_friedrichs engquist_osher simplified_engquist_osher') = Option(
'engquist_osher',
help='Numerical flux to use.'
),
vx: float = Option(1., help='Speed in x-direction.'),
vy: float = Option(1., help='Speed in y-direction.'),
):
"""Solves a two-dimensional Burgers-type equation.
See pymor.analyticalproblems.burgers for more details.
"""
print('Setup Problem ...')
problem = burgers_problem_2d(vx=args['--vx'], vy=args['--vy'], initial_data_type=args['--initial-data'],
parameter_range=(0, 1e42), torus=not args['--not-periodic'])
problem = burgers_problem_2d(vx=vx, vy=vy, initial_data_type=initial_data.value,
parameter_range=(0, 1e42), torus=periodic)
print('Discretize ...')
if args['--grid-type'] == 'rect':
args['--grid'] *= 1. / math.sqrt(2)
if grid_type == 'rect':
grid *= 1. / math.sqrt(2)
m, data = discretize_instationary_fv(
problem,
diameter=1. / args['--grid'],
grid_type=RectGrid if args['--grid-type'] == 'rect' else TriaGrid,
num_flux=args['--num-flux'],
lxf_lambda=args['--lxf-lambda'],
nt=args['--nt']
diameter=1. / grid,
grid_type=RectGrid if grid_type == 'rect' else TriaGrid,
num_flux=num_flux.value,
lxf_lambda=lxf_lambda,
nt=nt
)
print(m.operator.grid)
print(f'The parameters are {m.parameters}')
mu = args['EXP']
mu = exp
print(f'Solving for exponent = {mu} ... ')
sys.stdout.flush()
tic = time.perf_counter()
......@@ -81,8 +65,6 @@ def burgers_demo(args):
print(f'Solving took {time.perf_counter()-tic}s')
m.visualize(U)
if __name__ == '__main__':
# parse arguments
args = docopt(__doc__)
# run demo
burgers_demo(args)
run(main)
This diff is collapsed.
......@@ -3,20 +3,22 @@
# Copyright 2013-2020 pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
"""Delay demo
Cascade of delay and integrator
"""
import matplotlib.pyplot as plt
import numpy as np
import scipy.linalg as spla
import matplotlib.pyplot as plt
from typer import run
from pymor.models.iosys import TransferFunction
from pymor.reductors.interpolation import TFBHIReductor
from pymor.reductors.h2 import TFIRKAReductor
if __name__ == '__main__':
def main():
"""Delay demo
Cascade of delay and integrator
"""
tau = 0.1
def H(s):
......@@ -101,3 +103,7 @@ if __name__ == '__main__':
ax.set_title('Step responses of the full and reduced model 2')
ax.set_xlabel('$t$')
plt.show()
if __name__ == '__main__':
run(main)
......@@ -3,29 +3,8 @@
# Copyright 2013-2020 pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
"""Simple demonstration of solving the Poisson equation in 2D using pyMOR's builtin discretizations.
Usage:
elliptic.py [--fv] [--rect] PROBLEM-NUMBER DIRICHLET-NUMBER NEUMANN-NUMBER NEUMANN-COUNT
Arguments:
PROBLEM-NUMBER {0,1}, selects the problem to solve
DIRICHLET-NUMBER {0,1,2}, selects the Dirichlet data function
NEUMANN-NUMBER {0,1}, selects the Neumann data function
NEUMANN-COUNT 0: no neumann boundary
1: right edge is neumann boundary
2: right+top edges are neumann boundary
3: right+top+bottom edges are neumann boundary
Options:
-h, --help Show this message.
--fv Use finite volume discretization instead of finite elements.
--rect Use RectGrid instead of TriaGrid.
"""
from docopt import docopt
import numpy as np
from typer import Argument, Option, run
from pymor.analyticalproblems.domaindescriptions import RectDomain
from pymor.analyticalproblems.elliptic import StationaryProblem
......@@ -33,15 +12,24 @@ from pymor.analyticalproblems.functions import ExpressionFunction, ConstantFunct
from pymor.discretizers.builtin import discretize_stationary_cg, discretize_stationary_fv, RectGrid, TriaGrid
def elliptic_demo(args):
args['PROBLEM-NUMBER'] = int(args['PROBLEM-NUMBER'])
assert 0 <= args['PROBLEM-NUMBER'] <= 1, ValueError('Invalid problem number')
args['DIRICHLET-NUMBER'] = int(args['DIRICHLET-NUMBER'])
assert 0 <= args['DIRICHLET-NUMBER'] <= 2, ValueError('Invalid Dirichlet boundary number.')
args['NEUMANN-NUMBER'] = int(args['NEUMANN-NUMBER'])
assert 0 <= args['NEUMANN-NUMBER'] <= 2, ValueError('Invalid Neumann boundary number.')
args['NEUMANN-COUNT'] = int(args['NEUMANN-COUNT'])
assert 0 <= args['NEUMANN-COUNT'] <= 3, ValueError('Invalid Neumann boundary count.')
def main(
problem_number: int = Argument(..., min=0, max=1, help='Selects the problem to solve [0 or 1].'),
dirichlet_number: int = Argument(..., min=0, max=2, help='Selects the Dirichlet data function [0 to 2].'),
neumann_number: int = Argument(..., min=0, max=2, help='Selects the Neumann data function.'),
neumann_count: int = Argument(
...,
min=0,
max=3,
help='0: no neumann boundary\n\n'
'1: right edge is neumann boundary\n\n'
'2: right+top edges are neumann boundary\n\n'
'3: right+top+bottom edges are neumann boundary\n\n'
),
fv: bool = Option(False, help='Use finite volume discretization instead of finite elements.'),
rect: bool = Option(False, help='Use RectGrid instead of TriaGrid.'),
):
"""Solves the Poisson equation in 2D using pyMOR's builtin discreization toolkit."""
rhss = [ExpressionFunction('ones(x.shape[:-1]) * 10', 2, ()),
ExpressionFunction('(x[..., 0] - 0.5) ** 2 * 1000', 2, ())]
......@@ -57,10 +45,10 @@ def elliptic_demo(args):
RectDomain(right='neumann', top='neumann'),
RectDomain(right='neumann', top='neumann', bottom='neumann')]
rhs = rhss[args['PROBLEM-NUMBER']]
dirichlet = dirichlets[args['DIRICHLET-NUMBER']]
neumann = neumanns[args['NEUMANN-NUMBER']]
domain = domains[args['NEUMANN-COUNT']]
rhs = rhss[problem_number]
dirichlet = dirichlets[dirichlet_number]
neumann = neumanns[neumann_number]
domain = domains[neumann_count]
problem = StationaryProblem(
domain=domain,
......@@ -72,11 +60,11 @@ def elliptic_demo(args):
for n in [32, 128]:
print('Discretize ...')
discretizer = discretize_stationary_fv if args['--fv'] else discretize_stationary_cg
discretizer = discretize_stationary_fv if fv else discretize_stationary_cg
m, data = discretizer(
analytical_problem=problem,
grid_type=RectGrid if args['--rect'] else TriaGrid,
diameter=np.sqrt(2) / n if args['--rect'] else 1. / n
grid_type=RectGrid if rect else TriaGrid,
diameter=np.sqrt(2) / n if rect else 1. / n
)
grid = data['grid']
print(grid)
......@@ -89,5 +77,4 @@ def elliptic_demo(args):
if __name__ == '__main__':
args = docopt(__doc__)
elliptic_demo(args)
run(main)
......@@ -3,27 +3,8 @@
# Copyright 2013-2020 pyMOR developers and contributors. All rights reserved.
# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
"""Simple demonstration of solving the Poisson equation in 2D using pyMOR's builtin discretizations.
Usage:
elliptic2.py [--fv] PROBLEM-NUMBER N NORM
Arguments:
PROBLEM-NUMBER {0,1}, selects the problem to solve
N Triangle count per direction
NORM h1: compute the h1-norm of the last snapshot.
l2: compute the l2-norm of the last snapshot
k: compute the energy norm of the last snapshot, where the energy-product is constructed
with a parameter {'mu': k}.
Options:
-h, --help Show this message.
--fv Use finite volume discretization instead of finite elements.
"""
from docopt import docopt
import numpy as np
from typer import Argument, Option, run
from pymor.analyticalproblems.domaindescriptions import RectDomain
from pymor.analyticalproblems.elliptic import StationaryProblem
......@@ -32,43 +13,51 @@ from pymor.discretizers.builtin import discretize_stationary_cg, discretize_stat
from pymor.parameters.functionals import ProjectionParameterFunctional
def elliptic2_demo(args):
args['PROBLEM-NUMBER'] = int(args['PROBLEM-NUMBER'])
assert 0 <= args['PROBLEM-NUMBER'] <= 1, ValueError('Invalid problem number.')
args['N'] = int(args['N'])
norm = args['NORM']
def main(
problem_number: int = Argument(..., min=0, max=1, help='Selects the problem to solve [0 or 1].'),
n: int = Argument(..., help='Triangle count per direction'),
norm: str = Argument(
...,
help="h1: compute the h1-norm of the last snapshot.\n\n"
"l2: compute the l2-norm of the last snapshot.\n\n"
"k: compute the energy norm of the last snapshot, where the energy-product"
"is constructed with a parameter {'mu': k}."
),
fv: bool = Option(False, help='Use finite volume discretization instead of finite elements.'),
):
"""Solves the Poisson equation in 2D using pyMOR's builtin discreization toolkit."""
norm = float(norm) if not norm.lower() in ('h1', 'l2') else norm.lower()
rhss = [ExpressionFunction('ones(x.shape[:-1]) * 10', 2, ()),
LincombFunction(
[ExpressionFunction('ones(x.shape[:-1]) * 10', 2, ()), ConstantFunction(1.,2)],
[ProjectionParameterFunctional('mu'), 0.1])]
LincombFunction(
[ExpressionFunction('ones(x.shape[:-1]) * 10', 2, ()), ConstantFunction(1., 2)],
[ProjectionParameterFunctional('mu'), 0.1])]
dirichlets = [ExpressionFunction('zeros(x.shape[:-1])', 2, ()),
LincombFunction(
[ExpressionFunction('2 * x[..., 0]', 2, ()), ConstantFunction(1.,2)],
[ExpressionFunction('2 * x[..., 0]', 2, ()), ConstantFunction(1., 2)],
[ProjectionParameterFunctional('mu'), 0.5])]
neumanns = [None,
LincombFunction(
[ExpressionFunction('1 - x[..., 1]', 2, ()), ConstantFunction(1.,2)],
[ProjectionParameterFunctional('mu'), 0.5**2])]
LincombFunction(
[ExpressionFunction('1 - x[..., 1]', 2, ()), ConstantFunction(1., 2)],