From c6ed168f202b6b434da42955070028f9f5838594 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Thu, 19 Mar 2026 11:47:09 +0100 Subject: [PATCH 1/7] Switch to src layout This patch moves all of nutils' code into a "src layout" structure (https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/) which makes it a lot easier to work with modularized nutils packages from within the root of the repository, for instance to run unit tests. --- .github/workflows/test.yaml | 4 ---- {nutils => src/nutils}/SI.py | 0 {nutils => src/nutils}/__init__.py | 0 {nutils => src/nutils}/_backports.py | 0 {nutils => src/nutils}/_graph.py | 0 {nutils => src/nutils}/_pyast.py | 0 {nutils => src/nutils}/_util.py | 0 {nutils => src/nutils}/cache.py | 0 {nutils => src/nutils}/cli.py | 0 {nutils => src/nutils}/debug_flags.py | 0 {nutils => src/nutils}/element.py | 0 {nutils => src/nutils}/elementseq.py | 0 {nutils => src/nutils}/evaluable.py | 0 {nutils => src/nutils}/export.py | 0 {nutils => src/nutils}/expression_v1.py | 0 {nutils => src/nutils}/expression_v2.py | 0 {nutils => src/nutils}/function.py | 0 {nutils => src/nutils}/matrix/__init__.py | 0 {nutils => src/nutils}/matrix/_auto.py | 0 {nutils => src/nutils}/matrix/_base.py | 0 {nutils => src/nutils}/matrix/_mkl.py | 0 {nutils => src/nutils}/matrix/_numpy.py | 0 {nutils => src/nutils}/matrix/_scipy.py | 0 {nutils => src/nutils}/mesh.py | 0 {nutils => src/nutils}/numeric.py | 0 {nutils => src/nutils}/parallel.py | 0 {nutils => src/nutils}/points.py | 0 {nutils => src/nutils}/pointsseq.py | 0 {nutils => src/nutils}/sample.py | 0 {nutils => src/nutils}/solver.py | 0 {nutils => src/nutils}/testing.py | 0 {nutils => src/nutils}/topology.py | 0 {nutils => src/nutils}/transform.py | 0 {nutils => src/nutils}/transformseq.py | 0 {nutils => src/nutils}/types.py | 0 {nutils => src/nutils}/unit.py | 0 {nutils => src/nutils}/warnings.py | 0 37 files changed, 4 deletions(-) rename {nutils => src/nutils}/SI.py (100%) rename {nutils => src/nutils}/__init__.py (100%) rename {nutils => src/nutils}/_backports.py (100%) rename {nutils => src/nutils}/_graph.py (100%) rename {nutils => src/nutils}/_pyast.py (100%) rename {nutils => src/nutils}/_util.py (100%) rename {nutils => src/nutils}/cache.py (100%) rename {nutils => src/nutils}/cli.py (100%) rename {nutils => src/nutils}/debug_flags.py (100%) rename {nutils => src/nutils}/element.py (100%) rename {nutils => src/nutils}/elementseq.py (100%) rename {nutils => src/nutils}/evaluable.py (100%) rename {nutils => src/nutils}/export.py (100%) rename {nutils => src/nutils}/expression_v1.py (100%) rename {nutils => src/nutils}/expression_v2.py (100%) rename {nutils => src/nutils}/function.py (100%) rename {nutils => src/nutils}/matrix/__init__.py (100%) rename {nutils => src/nutils}/matrix/_auto.py (100%) rename {nutils => src/nutils}/matrix/_base.py (100%) rename {nutils => src/nutils}/matrix/_mkl.py (100%) rename {nutils => src/nutils}/matrix/_numpy.py (100%) rename {nutils => src/nutils}/matrix/_scipy.py (100%) rename {nutils => src/nutils}/mesh.py (100%) rename {nutils => src/nutils}/numeric.py (100%) rename {nutils => src/nutils}/parallel.py (100%) rename {nutils => src/nutils}/points.py (100%) rename {nutils => src/nutils}/pointsseq.py (100%) rename {nutils => src/nutils}/sample.py (100%) rename {nutils => src/nutils}/solver.py (100%) rename {nutils => src/nutils}/testing.py (100%) rename {nutils => src/nutils}/topology.py (100%) rename {nutils => src/nutils}/transform.py (100%) rename {nutils => src/nutils}/transformseq.py (100%) rename {nutils => src/nutils}/types.py (100%) rename {nutils => src/nutils}/unit.py (100%) rename {nutils => src/nutils}/warnings.py (100%) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d8af114e8..6efc30360 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -76,8 +76,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Move nutils directory - run: mv nutils _nutils - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -170,8 +168,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Move nutils directory - run: mv nutils _nutils - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/nutils/SI.py b/src/nutils/SI.py similarity index 100% rename from nutils/SI.py rename to src/nutils/SI.py diff --git a/nutils/__init__.py b/src/nutils/__init__.py similarity index 100% rename from nutils/__init__.py rename to src/nutils/__init__.py diff --git a/nutils/_backports.py b/src/nutils/_backports.py similarity index 100% rename from nutils/_backports.py rename to src/nutils/_backports.py diff --git a/nutils/_graph.py b/src/nutils/_graph.py similarity index 100% rename from nutils/_graph.py rename to src/nutils/_graph.py diff --git a/nutils/_pyast.py b/src/nutils/_pyast.py similarity index 100% rename from nutils/_pyast.py rename to src/nutils/_pyast.py diff --git a/nutils/_util.py b/src/nutils/_util.py similarity index 100% rename from nutils/_util.py rename to src/nutils/_util.py diff --git a/nutils/cache.py b/src/nutils/cache.py similarity index 100% rename from nutils/cache.py rename to src/nutils/cache.py diff --git a/nutils/cli.py b/src/nutils/cli.py similarity index 100% rename from nutils/cli.py rename to src/nutils/cli.py diff --git a/nutils/debug_flags.py b/src/nutils/debug_flags.py similarity index 100% rename from nutils/debug_flags.py rename to src/nutils/debug_flags.py diff --git a/nutils/element.py b/src/nutils/element.py similarity index 100% rename from nutils/element.py rename to src/nutils/element.py diff --git a/nutils/elementseq.py b/src/nutils/elementseq.py similarity index 100% rename from nutils/elementseq.py rename to src/nutils/elementseq.py diff --git a/nutils/evaluable.py b/src/nutils/evaluable.py similarity index 100% rename from nutils/evaluable.py rename to src/nutils/evaluable.py diff --git a/nutils/export.py b/src/nutils/export.py similarity index 100% rename from nutils/export.py rename to src/nutils/export.py diff --git a/nutils/expression_v1.py b/src/nutils/expression_v1.py similarity index 100% rename from nutils/expression_v1.py rename to src/nutils/expression_v1.py diff --git a/nutils/expression_v2.py b/src/nutils/expression_v2.py similarity index 100% rename from nutils/expression_v2.py rename to src/nutils/expression_v2.py diff --git a/nutils/function.py b/src/nutils/function.py similarity index 100% rename from nutils/function.py rename to src/nutils/function.py diff --git a/nutils/matrix/__init__.py b/src/nutils/matrix/__init__.py similarity index 100% rename from nutils/matrix/__init__.py rename to src/nutils/matrix/__init__.py diff --git a/nutils/matrix/_auto.py b/src/nutils/matrix/_auto.py similarity index 100% rename from nutils/matrix/_auto.py rename to src/nutils/matrix/_auto.py diff --git a/nutils/matrix/_base.py b/src/nutils/matrix/_base.py similarity index 100% rename from nutils/matrix/_base.py rename to src/nutils/matrix/_base.py diff --git a/nutils/matrix/_mkl.py b/src/nutils/matrix/_mkl.py similarity index 100% rename from nutils/matrix/_mkl.py rename to src/nutils/matrix/_mkl.py diff --git a/nutils/matrix/_numpy.py b/src/nutils/matrix/_numpy.py similarity index 100% rename from nutils/matrix/_numpy.py rename to src/nutils/matrix/_numpy.py diff --git a/nutils/matrix/_scipy.py b/src/nutils/matrix/_scipy.py similarity index 100% rename from nutils/matrix/_scipy.py rename to src/nutils/matrix/_scipy.py diff --git a/nutils/mesh.py b/src/nutils/mesh.py similarity index 100% rename from nutils/mesh.py rename to src/nutils/mesh.py diff --git a/nutils/numeric.py b/src/nutils/numeric.py similarity index 100% rename from nutils/numeric.py rename to src/nutils/numeric.py diff --git a/nutils/parallel.py b/src/nutils/parallel.py similarity index 100% rename from nutils/parallel.py rename to src/nutils/parallel.py diff --git a/nutils/points.py b/src/nutils/points.py similarity index 100% rename from nutils/points.py rename to src/nutils/points.py diff --git a/nutils/pointsseq.py b/src/nutils/pointsseq.py similarity index 100% rename from nutils/pointsseq.py rename to src/nutils/pointsseq.py diff --git a/nutils/sample.py b/src/nutils/sample.py similarity index 100% rename from nutils/sample.py rename to src/nutils/sample.py diff --git a/nutils/solver.py b/src/nutils/solver.py similarity index 100% rename from nutils/solver.py rename to src/nutils/solver.py diff --git a/nutils/testing.py b/src/nutils/testing.py similarity index 100% rename from nutils/testing.py rename to src/nutils/testing.py diff --git a/nutils/topology.py b/src/nutils/topology.py similarity index 100% rename from nutils/topology.py rename to src/nutils/topology.py diff --git a/nutils/transform.py b/src/nutils/transform.py similarity index 100% rename from nutils/transform.py rename to src/nutils/transform.py diff --git a/nutils/transformseq.py b/src/nutils/transformseq.py similarity index 100% rename from nutils/transformseq.py rename to src/nutils/transformseq.py diff --git a/nutils/types.py b/src/nutils/types.py similarity index 100% rename from nutils/types.py rename to src/nutils/types.py diff --git a/nutils/unit.py b/src/nutils/unit.py similarity index 100% rename from nutils/unit.py rename to src/nutils/unit.py diff --git a/nutils/warnings.py b/src/nutils/warnings.py similarity index 100% rename from nutils/warnings.py rename to src/nutils/warnings.py From fa4e1b88654e495660cd145a2e31b0d36b008ec1 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Thu, 19 Mar 2026 12:13:49 +0100 Subject: [PATCH 2/7] Install nutils-units in container --- devtools/container/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/container/build.py b/devtools/container/build.py index 50493b366..d73ef2a2d 100644 --- a/devtools/container/build.py +++ b/devtools/container/build.py @@ -68,7 +68,7 @@ container = stack.enter_context(Container.new_from(base, mounts=[Mount(src=wheel, dst=f'/{wheel.name}')])) - container.run('pip', 'install', '--break-system-packages', '--no-cache-dir', f'/{wheel.name}[export-mpl,import-gmsh,matrix-scipy]', env=dict(PYTHONHASHSEED='0')) + container.run('pip', 'install', '--break-system-packages', '--no-cache-dir', f'/{wheel.name}[export-mpl,import-gmsh,matrix-scipy]', 'nutils-units', env=dict(PYTHONHASHSEED='0')) container.add_label('org.opencontainers.image.url', 'https://github.com/evalf/nutils') container.add_label('org.opencontainers.image.source', 'https://github.com/evalf/nutils') container.add_label('org.opencontainers.image.authors', 'Evalf') From 95006dd37894582616c4ae74b22895dbe93be077 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Thu, 19 Mar 2026 11:56:36 +0100 Subject: [PATCH 3/7] Convert cahnhilliard example to nutils.units --- examples/cahnhilliard.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/cahnhilliard.py b/examples/cahnhilliard.py index 5b3a263b9..8a8eee1af 100644 --- a/examples/cahnhilliard.py +++ b/examples/cahnhilliard.py @@ -1,23 +1,32 @@ from nutils import mesh, function, numeric, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace -from nutils.SI import Length, Time, Density, Tension, Energy, Pressure, Velocity, parse +try: + from nutils.units.typing import Length, Time, Density, Tension, Energy, Pressure, Velocity +except ModuleNotFoundError as e: + if hasattr(e, 'add_note'): + e.add_note("Consider installing the units package via: pip install nutils-units") + raise import numpy import treelog as log -def main(size: Length = parse('10cm'), - epsilon: Length = parse('1mm'), - mobility: Time/Density = parse('1mL*s/kg'), - stens: Tension = parse('50mN/m'), - wtensn: Tension = parse('30mN/m'), - wtensp: Tension = parse('20mN/m'), +Mobility = Time / Density +LED = Energy / Length # linear energy density + + +def main(size: Length = Length('10cm'), + epsilon: Length = Length('1mm'), + mobility: Mobility = Mobility('1mL*s/kg'), + stens: Tension = Tension('50mN/m'), + wtensn: Tension = Tension('30mN/m'), + wtensp: Tension = Tension('20mN/m'), nelems: int = 0, etype: str = 'rectilinear', degree: int = 1, - timestep: Time = parse('.1s'), - tol: Energy/Length = parse('1nJ/m'), - endtime: Time = parse('1min'), + timestep: Time = Time('.1s'), + tol: LED = LED('1nJ/m'), + endtime: Time = Time('1min'), seed: int = 0, circle: bool = True, stable: bool = False, @@ -207,14 +216,14 @@ def main(size: Length = parse('10cm'), class test(testing.TestCase): def test_initial(self): - args = main(epsilon=parse('5cm'), mobility=parse('1μL*s/kg'), nelems=3, degree=2, timestep=parse('1h'), endtime=parse('1h'), circle=False) + args = main(epsilon=Length('5cm'), mobility=Mobility('1μL*s/kg'), nelems=3, degree=2, timestep=Time('1h'), endtime=Time('1h'), circle=False) with self.subTest('concentration'): self.assertAlmostEqual64(args['φ0'], ''' eNoBYgCd/xM3LjTtNYs3MDcUyt41uc14zjo0LzKzNm812jFhNNMzwDYgzbMzV8o0yCM1rzWeypE3Tcnx L07NzTa4NlMyETREyrPIGMxYMl82VDbjy1/M8clZyf3IRjday6XLmMl6NRnJMF4tqQ==''') def test_square(self): - args = main(epsilon=parse('5cm'), mobility=parse('1μL*s/kg'), nelems=3, degree=2, timestep=parse('1h'), endtime=parse('2h'), circle=False) + args = main(epsilon=Length('5cm'), mobility=Mobility('1μL*s/kg'), nelems=3, degree=2, timestep=Time('1h'), endtime=Time('2h'), circle=False) with self.subTest('concentration'): self.assertAlmostEqual64(args['φ'], ''' eNoBYgCd/y41EjX2NZ829DXcMxUz0jTANL41ajaNNZox/9EoNRY1LDUkNZAw1cqnysI1njWdNNkxMMuk @@ -225,7 +234,7 @@ def test_square(self): xlrGoziaOEA3os8VyJLHk8hlyTw2sDZXydPISMoPy5zGe8i7yzfIncgAzGLKwgYwXw==''') def test_multipatchcircle(self): - args = main(epsilon=parse('5cm'), mobility=parse('1μL*s/kg'), nelems=3, etype='multipatch', degree=2, timestep=parse('1h'), endtime=parse('2h')) + args = main(epsilon=Length('5cm'), mobility=Mobility('1μL*s/kg'), nelems=3, etype='multipatch', degree=2, timestep=Time('1h'), endtime=Time('2h')) with self.subTest('concentration'): self.assertAlmostEqual64(args['φ'], ''' eNoNz01IlFEUBmByEcVsWkiBoKHYoh9nvnvPOa5GcCE1gqNjDZOBBUM1iSYYEf2JEGZE0SoIokWMCCYk From f87a2e0e5c757c4172df91c8d08f4ff78e0d8a34 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Thu, 19 Mar 2026 12:04:40 +0100 Subject: [PATCH 4/7] Convert turek example to nutils.units --- examples/turek.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/turek.py b/examples/turek.py index 35cd10e67..4a1d52ed4 100644 --- a/examples/turek.py +++ b/examples/turek.py @@ -1,7 +1,12 @@ from nutils import cli, export, function, testing from nutils.mesh import gmsh from nutils.solver import System -from nutils.SI import Length, Density, Viscosity, Velocity, Time, Pressure, Acceleration +try: + from nutils.units.typing import Length, Density, Viscosity, Velocity, Time, Pressure, Acceleration +except ModuleNotFoundError as e: + if hasattr(e, 'add_note'): + e.add_note("Consider installing the units package via: pip install nutils-units") + raise from nutils.expression_v2 import Namespace from collections import defaultdict, deque from dataclasses import dataclass @@ -221,7 +226,7 @@ def main(domain: Domain = Domain(), solid: Optional[Solid] = Solid(), fluid: Opt if dynamic: ns.v, ns.a = dynamic.newmark_defo(ns.d) else: - ns.a = Acceleration.wrap(function.zeros((2,))) + ns.a = numpy.repeat(Acceleration.zero, 2) # Deformed geometry ns.x_i = 'xref_i + d_i' @@ -260,8 +265,8 @@ def main(domain: Domain = Domain(), solid: Optional[Solid] = Solid(), fluid: Opt else: # fully rigid solid ns.x = ns.xref - ns.v = Velocity.wrap(function.zeros((2,))) - ns.a = Acceleration.wrap(function.zeros((2,))) + ns.v = numpy.repeat(Velocity.zero, 2) + ns.a = numpy.repeat(Acceleration.zero, 2) if fluid: @@ -339,7 +344,7 @@ def main(domain: Domain = Domain(), solid: Optional[Solid] = Solid(), fluid: Opt DL = uxy = None # for unit tests only - for t in log.iter.fraction('timestep', dynamic.times) if dynamic else [Time.wrap(float('inf'))]: + for t in log.iter.fraction('timestep', dynamic.times) if dynamic else [Time.zero * float('inf')]: if dynamic: if solid: From c6c3f907f6bd7ddc9cce4e14a7d714eaca9e16b1 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Thu, 19 Mar 2026 12:09:24 +0100 Subject: [PATCH 5/7] Add deprecation warning for nutils.SI, nutils.unit --- src/nutils/SI.py | 4 +++- src/nutils/unit.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nutils/SI.py b/src/nutils/SI.py index e0d250606..9b6a62c2a 100644 --- a/src/nutils/SI.py +++ b/src/nutils/SI.py @@ -125,7 +125,9 @@ import typing import numpy from functools import partial, partialmethod, reduce -from . import function, topology, sample +from . import function, topology, sample, warnings + +warnings.deprecation("nutils.SI is deprecated and will be removed in Nutils 11; please use the externally installable nutils.units instead (pip install nutils-units)") class DimensionError(TypeError): diff --git a/src/nutils/unit.py b/src/nutils/unit.py index b22d680bb..8f512b285 100644 --- a/src/nutils/unit.py +++ b/src/nutils/unit.py @@ -39,6 +39,9 @@ ''' import re +from . import warnings + +warnings.deprecation("nutils.unit is deprecated and will be removed in Nutils 11; please use the externally installable nutils.units instead (pip install nutils-units)") def create(_typename='unit', **units): From 4f1f33f65b7600af3fc785f7b1829aa413753ca1 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Mon, 23 Mar 2026 11:36:27 +0100 Subject: [PATCH 6/7] Add inline script metadata to examples The metadata follows PEP 723 as specified in https://packaging.python.org/en/latest/specifications/inline-script-metadata/#inline-script-metadata --- examples/adaptivity.py | 5 +++++ examples/burgers.py | 5 +++++ examples/cahnhilliard.py | 5 +++++ examples/coil.py | 5 +++++ examples/cylinderflow.py | 5 +++++ examples/drivencavity.py | 5 +++++ examples/elasticity.py | 5 +++++ examples/finitestrain.py | 5 +++++ examples/laplace.py | 5 +++++ examples/platewithhole.py | 5 +++++ examples/poisson.py | 5 +++++ examples/torsion.py | 5 +++++ examples/turek.py | 5 +++++ 13 files changed, 65 insertions(+) diff --git a/examples/adaptivity.py b/examples/adaptivity.py index 42d9a6465..f658e8916 100644 --- a/examples/adaptivity.py +++ b/examples/adaptivity.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/burgers.py b/examples/burgers.py index f9067f1c0..a3c90e26a 100644 --- a/examples/burgers.py +++ b/examples/burgers.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/cahnhilliard.py b/examples/cahnhilliard.py index 8a8eee1af..369e9209b 100644 --- a/examples/cahnhilliard.py +++ b/examples/cahnhilliard.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=10a6", "nutils-units>=0.2", "matplotlib>=3"] +# /// + from nutils import mesh, function, numeric, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/coil.py b/examples/coil.py index aa4b4dd97..2289f8adc 100644 --- a/examples/coil.py +++ b/examples/coil.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import export, function, mesh, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/cylinderflow.py b/examples/cylinderflow.py index dcf1bf93a..216c17798 100644 --- a/examples/cylinderflow.py +++ b/examples/cylinderflow.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing, numeric from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/drivencavity.py b/examples/drivencavity.py index 7edc95de5..af57e8d1f 100644 --- a/examples/drivencavity.py +++ b/examples/drivencavity.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System, LinesearchNewton from nutils.expression_v2 import Namespace diff --git a/examples/elasticity.py b/examples/elasticity.py index e27eab59c..0e6107354 100644 --- a/examples/elasticity.py +++ b/examples/elasticity.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/finitestrain.py b/examples/finitestrain.py index cdf2f6081..e0e8dc600 100644 --- a/examples/finitestrain.py +++ b/examples/finitestrain.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System, Minimize from nutils.expression_v2 import Namespace diff --git a/examples/laplace.py b/examples/laplace.py index f66a45af3..375aaaa68 100644 --- a/examples/laplace.py +++ b/examples/laplace.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/platewithhole.py b/examples/platewithhole.py index 3be8ba20a..3997c8776 100644 --- a/examples/platewithhole.py +++ b/examples/platewithhole.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System from nutils.expression_v2 import Namespace diff --git a/examples/poisson.py b/examples/poisson.py index 2a83f9172..c5b311d07 100644 --- a/examples/poisson.py +++ b/examples/poisson.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System diff --git a/examples/torsion.py b/examples/torsion.py index bd9a6e169..7f141db80 100644 --- a/examples/torsion.py +++ b/examples/torsion.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=9", "matplotlib>=3"] +# /// + from nutils import mesh, function, export, testing from nutils.solver import System, Minimize from nutils.expression_v2 import Namespace diff --git a/examples/turek.py b/examples/turek.py index 4a1d52ed4..a4324353e 100644 --- a/examples/turek.py +++ b/examples/turek.py @@ -1,3 +1,8 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["nutils>=10a6", "nutils-units>=0.2", "gmsh>=4", "meshio>=5", "matplotlib>=3"] +# /// + from nutils import cli, export, function, testing from nutils.mesh import gmsh from nutils.solver import System From 26063552ac669b148a52b5d3f3c691ae679f2de1 Mon Sep 17 00:00:00 2001 From: Gertjan van Zwieten Date: Mon, 23 Mar 2026 12:46:51 +0100 Subject: [PATCH 7/7] Create test matrix to test script metadata This patch changes the github test workflow such that every example is tested in its own virtual environment populated only with the dependencies pecified in the embedded metadata. --- .github/workflows/test.yaml | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6efc30360..6a75dbeec 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -151,20 +151,34 @@ jobs: run: python -um devtools.gha.delete_coverage_artifacts test-examples: needs: build-python-package - name: 'Test examples ${{ matrix.os }}' + name: 'Test ${{ matrix.example }} on ${{ matrix.os }}' runs-on: ${{ matrix.os }}-latest strategy: matrix: + example: + - adaptivity + - burgers + - cahnhilliard + - coil + - cylinderflow + - drivencavity + - elasticity + - finitestrain + - laplace + - platewithhole + - poisson + - torsion + - turek os: [ubuntu, macos, windows] fail-fast: false env: _wheel: ${{ needs.build-python-package.outputs.wheel }} - NUTILS_MATRIX: scipy NUTILS_NPROCS: 1 NUTILS_DEBUG: all OMP_NUM_THREADS: 1 VECLIB_MAXIMUM_THREADS: 1 PYTHONHASHSEED: 0 + PYTHONUTF8: 1 # pending https://github.com/pypa/pip/pull/13862 steps: - name: Checkout uses: actions/checkout@v4 @@ -178,19 +192,17 @@ jobs: name: python-package path: dist/ - name: Install Gmsh - # install gmsh via pip on windows and macos and via apt on linux, as - # the latter version is dynamically linked and requires libgl etc. - run: | - ${{ matrix.os == 'ubuntu' && 'sudo apt install gmsh' || 'python -um pip install gmsh' }} - echo "NUTILS_TESTING_REQUIRES=$NUTILS_TESTING_REQUIRES app:gmsh" >> $GITHUB_ENV + if: ${{ matrix.os == 'ubuntu' }} + # install gmsh dependencies on ubuntu as the pip package is dynamically linked + run: sudo apt install libglu1-mesa - name: Install Nutils and dependencies id: install run: | - python -um pip install --upgrade --upgrade-strategy eager wheel + python -um pip install --upgrade --upgrade-strategy eager pip # Install Nutils from `dist` dir created in job `build-python-package`. - python -um pip install "$_wheel[import-gmsh,matrix-scipy,export-mpl]" + python -um pip install "$_wheel" --requirements-from-script examples/${{ matrix.example }}.py - name: Test - run: python -um unittest discover -b -q -t . -s examples + run: python -um unittest -bv examples.${{ matrix.example }} test-sphinx: name: Test building docs runs-on: ubuntu-latest