# Changelog¶

Nutils is being actively developed and the API is continuously evolving. The following overview lists user facing changes as well as newly added features in inverse chronological order.

## New in v7.0 (in development)¶

Changed: locate arguments

The

`nutils.topology.Topology.locate()`

method now allows`tol`

to be left unspecified if`eps`

is specified instead, which is repurposed as stop criterion for distances in element coordinates. Conversely, if only`tol`

is specified, a corresponding minimal`eps`

value is set automatically to match points near element edges. The`ischeme`

and`scale`

arguments are deprecated and replaced by`maxdist`

, which can be left unspecified in general. The optional`weights`

argument results in a sample that is suitable for integration.Moved: unit from types to separate module

The

`unit`

type has been moved into its own`nutils.unit`

module, with the old location`types.unit`

now holding a forward method. The forward emits a deprecation warning prompting to change`nutils.types.unit.create`

(or its shorthand`nutils.types.unit`

) to`nutils.unit.create()`

.Removed: loading libraries from .local

Libraries that are installed in odd locations will no longer be automatically located by Nutils (see b8b7a6d5 for reasons). Instead the user will need to set the appropriate environment variable, prior to starting Python. In Windows this is the

`PATH`

variable, in Linux and OS X`LD_LIBRARY_PATH`

.Crucially, this affects the MKL libraries when they are user-installed via pip. By default Nutils selects the best available matrix backend that it finds available, which could result in it silently falling back on Scipy or Numpy. To confirm that the path variable is set correctly run your application with

`matrix=mkl`

to force an error if MKL cannot be loaded.Function module split into

`function`

and`evaluable`

The function module has been split into a high-level, numpy-like

`function`

module and a lower-level`evaluable`

module. The`evaluable`

module is agnostic to the so-called points axis. Scripts that don’t use custom implementations of`function.Array`

should work without modification.Custom implementations of the old

`function.Array`

should now derive from`evaluable.Array`

. Furthermore, an accompanying implementation of`function.Array`

should be added with a`prepare_eval`

method that returns the former.The following example implementation of an addition

>>> class Add(function.Array): ... def __init__(self, a, b): ... super().__init__(args=[a, b], shape=a.shape, dtype=a.dtype) ... def evalf(self, a, b): ... return a+b

should be converted to

>>> class Add(function.Array): ... def __init__(self, a: function.Array, b: function.Array) -> None: ... self.a = a ... self.b = b ... super().__init__(shape=a.shape, dtype=a.dtype) ... def prepare_eval(self, **kwargs) -> evaluable.Array: ... a = self.a.prepare_eval(**kwargs) ... b = self.b.prepare_eval(**kwargs) ... return Add_evaluable(a, b) ... >>> class Add_evaluable(evaluable.Array): ... def __init__(self, a, b): ... super().__init__(args=[a, b], shape=a.shape, dtype=a.dtype) ... def evalf(self, a, b): ... return a+b

Functions generating or consuming axes in expressions

The expression syntax now supports functions that generate and/or consume axes. The namespace has built-in support for

`sum`

,`norm2`

and`J`

(jacobian):'sum:i(u_ij)' # sum the first axis of `u` 'norm2:i(u_i)' # 2-norm of `u` 'J:i(x_i)' # jacobian of `x`

If all axes of function arguments are consumed, it is allowed to omit the indices:

'norm2(u)' 'J(x)'

New derivative and normal syntax

The

`Namespace`

now supports writing derivatives and normals as functions:'d(u, x_i)' # alternative for 'u_,i', deprecates 'u_,x_i' 'd(u, x_i, x_j)' # alternative for 'u_,ij' 'surfgrad(u, x_i)' # alternative for 'u_;i', deprecates 'u_;x_i' 'd(u, ?a)' # deprecates 'u_,?a' 'n(x_i)' # deprecates 'n:x_i'

User-defined functions in

`Namespace`

The

`Namespace`

can be initialized with a dictionary of user-defined functions:>>> def mul(a, b): ... return a[(...,)+(None,)*b.ndim] * b[(None,)*a.ndim] >>> ns = Namespace(functions=dict(mul=mul)) >>> 'mul(a_i, b_j)' @ ns # equivalent to `'a_i b_j' @ ns`

Solve multiple residuals to multiple targets

In problems involving multiple fields, where formerly it was required to

`nutils.function.chain()`

the bases in order to construct and solve a block system, an alternative possibility is now to keep the residuals and targets separate and reference the several parts at the solving phase:# old, still valid approach >>> ns.ubasis, ns.pbasis = function.chain([ubasis, pbasis]) >>> ns.u_i = 'ubasis_ni ?dofs_n' >>> ns.p = 'pbasis_n ?dofs_n' # new, alternative approach >>> ns.ubasis = ubasis >>> ns.pbasis = pbasis >>> ns.u_i = 'ubasis_ni ?u_n' >>> ns.p = 'pbasis_n ?p_n' # common: problem definition >>> ns.σ_ij = '(u_i,j + u_j,i) / Re - p δ_ij' >>> ures = topo.integral('ubasis_ni,j σ_ij d:x d:x' @ ns, degree=4) >>> pres = topo.integral('pbasis_n u_,kk d:x' @ ns, degree=4) # old approach: solving a single residual to a single target >>> dofs = solver.newton('dofs', ures + pres).solve(1e-10) # new approach: solving multiple residuals to multiple targets >>> state = solver.newton(['u', 'p'], [ures, pres]).solve(1e-10)

In the new, multi-target approach, the return value is no longer an array but a dictionary that maps a target to its solution. If additional arguments were specified to newton (or any of the other solvers) then these are copied into the return dictionary so as to form a complete state, which can directly be used as an arguments to subsequent evaluations.

If an argument is specified for a solve target then its value is used as an initial guess (newton, minimize) or initial condition (thetamethod). This replaces the

`lhs0`

argument which is not supported for multiple targets.New thetamethod argument

`historysuffix`

deprecates`target0`

To explicitly refer to the history state in

`nutils.solver.thetamethod()`

and its derivatives`impliciteuler`

and`cranknicolson`

, instead of specifiying the target through the`target0`

parameter, the new argument`historysuffix`

specifies only the suffix to be added to the main target. Hence, the following three invocations are equivalent:# deprecated >>> solver.impliciteuler('target', residual, inertia, target0='target0') # new syntax >>> solver.impliciteuler('target', residual, inertia, historysuffix='0') # equal, since '0' is the default suffix >>> solver.impliciteuler('target', residual, inertia)

In-place modification of newton, minimize, pseudotime iterates

When

`nutils.solver.newton`

,`nutils.solver.minimize`

or`nutils.solver.pseudotime`

are used as iterators, the generated vectors are now modified in place. Therefore, if iterates are stored for analysis, be sure to use the`.copy`

method.Deprecated

`function.elemwise`

The function

`function.elemwise`

has been deprecated. Use`function.Elemwise`

instead:>>> function.elemwise(topo.transforms, values) # deprecated >>> function.Elemwise(values, topo.f_index) # new

Removed

`transforms`

attribute of basesThe

`transforms`

attribute of bases has been removed due to internal restructurings. The`transforms`

attribute of the topology on which the basis was created can be used as a replacement:>>> reftopo = topo.refined >>> refbasis = reftopo.basis(...) >>> supp = refbasis.get_support(...) >>> #topo = topo.refined_by(refbasis.transforms[supp]) # no longer valid >>> topo = topo.refined_by(reftopo.transforms[supp]) # still valid

## New in v6.0 “garak-guksu”¶

Release date: 2020-04-29.

Sparse module

The new

`nutils.sparse`

module introduces a data type and a suite of manipulation methods for arbitrary dimensional sparse data. The existing integrate and integral methods now create data of this type under the hood, and then convert it to a scalar, Numpy array or`nutils.matrix.Matrix`

upon return. To prevent this conversion and receive the sparse objects instead use the new`nutils.sample.Sample.integrate_sparse()`

or`nutils.sample.eval_integrals_sparse()`

.External dependency for parsing gmsh files

The

`nutils.mesh.gmsh()`

method now depends on the external meshio module to parse .msh files:$ python3 -m pip install --user --upgrade meshio

Change dof order in basis.vector

When creating a vector basis using

`topo.basis(..).vector(nd)`

, the order of the degrees of freedom changed from grouping by vector components to grouping by scalar basis functions:[b0, 0] [b0, 0] [b1, 0] [ 0, b0] [.., ..] old [b1, 0] [bn, 0] ------> [ 0, b1] [ 0, b0] new [.., ..] [.., ..] [bn, 0] [ 0, bn] [ 0, bn]

This should not affect applications unless the solution vector is manipulated directly, such as might happen in unit tests. If required for legacy purposes the old vector can be retrieved using

`old = new.reshape(-1,nd).T.ravel()`

. Note that the change does not extend to`nutils.function.vectorize()`

.Change from stickybar to bottombar

For

`nutils.cli.run()`

to draw a status bar, it now requires the external bottombar module to be installed:$ python3 -m pip install --user bottombar

This replaces stickybar, which is no longer used. In addition to the log uri and runtime the status bar will now show the current memory usage, if that information is available. On Windows this requires psutil to be installed; on Linux and OSX it should work by default.

Support for gmsh ‘msh4’ file format

The

`nutils.mesh.gmsh()`

method now supports input in the ‘msh4’ file format, in addition to the ‘msh2’ format which remains supported for backward compatibility. Internally,`nutils.mesh.parsegmsh()`

now takes file contents instead of a file name.New command line option: gracefulexit

The new boolean command line option

`gracefulexit`

determines what happens when an exception reaches`nutils.cli.run()`

. If true (default) then the exception is handled as before and a system exit is initiated with an exit code of 2. If false then the exception is reraised as-is. This is useful in particular when combined with an external debugging tool.Log tracebacks at debug level

The way exceptions are handled by

`nutils.cli.run()`

is changed from logging the entire exception and traceback as a single error message, to logging the exceptions as errors and tracebacks as debug messages. Additionally, the order of exceptions and traceback is fully reversed, such that the most relevant message is the first thing shown and context follows.Solve leniently to relative tolerance in Newton systems

The

`nutils.solver.newton`

method now sets the relative tolerance of the linear system to`1e-3`

unless otherwise specified via`linrtol`

. This is mainly useful for iterative solvers which can save computational effort by having their stopping criterion follow the current Newton residual, but it may also help with direct solvers to warn of ill conditioning issues. Iterations furthermore use`nutils.matrix.Matrix.solve_leniently()`

, thus proceeding after warning that tolerances have not been met in the hope that Newton convergence might be attained regardless.Linear solver arguments

The methods

`nutils.solver.newton`

,`nutils.solver.minimize`

,`nutils.solver.pseudotime`

,`nutils.solver.solve_linear()`

and`nutils.solver.optimize()`

now receive linear solver arguments as keyword arguments rather than via the`solveargs`

dictionary, which is deprecated. To avoid name clashes with the remaining arguments, argument names must be prefixed by`lin`

:>>> solver.solve_linear('lhs', res, ... solveargs=dict(solver='gmres')) # deprecated syntax >>> solver.solve_linear('lhs', res, ... linsolver='gmres') # new syntax

Iterative refinement

Direct solvers enter an iterative refinement loop in case the first pass did not meet the configured tolerance. In machine precision mode (atol=0, rtol=0) this refinement continues until the residual stagnates.

Matrix solver tolerances

The absolute and/or relative tolerance for solutions of a linear system can now be specified in

`nutils.matrix.Matrix.solve()`

via the`atol`

resp.`rtol`

arguments, regardless of backend and solver. If the backend returns a solution that violates both tolerances then an exception is raised of type`nutils.matrix.ToleranceNotReached`

, from which the solution can still be obtained via the .best attribute. Alternatively the new method`nutils.matrix.Matrix.solve_leniently()`

always returns a solution while logging a warning if tolerances are not met. In case both tolerances are left at their default value or zero then solvers are instructed to produce a solution to machine precision, with subsequent checks disabled.Use stringly for command line parsing

Nutils now depends on stringly (version 1.0b1) for parsing of command line arguments. The new implementation of

`nutils.cli.run()`

is fully backwards compatible, but the preferred method of annotating function arguments is now as demonstrated in all of the examples.For new Nutils installations Stringly will be installed automatically as a dependency. For existing setups it can be installed manually as follows:

$ python3 -m pip install --user --upgrade stringly

Fixed and fallback lengths in (namespace) expressions

The

`nutils.function.Namespace`

has two new arguments:`length_<indices>`

and`fallback_length`

. The former can be used to assign fixed lengths to specific indices in expressions, say index`i`

should have length 2, which is used for verification and resolving undefined lengths. The latter is used to resolve remaining undefined lengths:>>> ns = nutils.function.Namespace(length_i=2, fallback_length=3) >>> ns.eval_ij('δ_ij') # using length_i Array<2,2> >>> ns.eval_jk('δ_jk') # using fallback_length Array<3,3>

Treelog update

Nutils now depends on treelog version 1.0b5, which brings improved iterators along with other enhancements. For transitional convenience the backwards incompatible changes have been backported in the

`nutils.log`

wrapper, which now emits a warning in case the deprecated methods are used. This wrapper is scheduled for deletion prior to the release of version 6.0. To update treelog to the most recent version use:python -m pip install -U treelog

Unit type

The new

`nutils.types.unit`

allows for the creation of a unit system for easy specification of physical quantities. Used in conjunction with`nutils.cli.run()`

this facilitates specifying units from the command line, as well as providing a warning mechanism against incompatible units:>>> U = types.unit.create(m=1, s=1, g=1e-3, N='kg*m/s2', Pa='N/m2') >>> def main(length=U('2m'), F=U('5kN')): ... topo, geom = mesh.rectilinear([numpy.linspace(0,length,10)]) # python myscript.py length=25cm # OK # python myscript.py F=10Pa # error!

Sample basis

Samples now provide a

`nutils.sample.Sample.basis()`

: an array that for any point in the sample evaluates to the unit vector corresponding to its index. This new underpinning of`nutils.sample.Sample.asfunction()`

opens the way for sampled arguments, as demonstrated in the last example below:>>> H1 = mysample.asfunction(mydata) # mysample.eval(H1) == mydata >>> H2 = mysample.basis().dot(mydata) # mysample.eval(H2) == mydata >>> ns.Hbasis = mysample.basis() >>> H3 = 'Hbasis_n ?d_n' @ ns # mysample.eval(H3, d=mydata) == mydata

Higher order gmsh geometries

Gmsh element support has been extended to include cubic and quartic meshes in 2D and quadratic meshes in 3D, and parsing the msh file is now a cacheable operation. Additionally, tetrahedra now define bezier points at any order.

Repository location

The Nutils repository has moved to https://github.com/evalf/nutils.git. For the time being the old address is maintained by Github as an alias, but in the long term you are advised to update your remote as follows:

git remote set-url origin https://github.com/evalf/nutils.git

## New in v5.0 “farfalle”¶

Release date: 2019-06-11.

Matrix matmul operator, solve with multiple right hand sides

The

`Matrix.matvec`

method has been deprecated in favour of the new`__matmul__`

(@) operator, which supports multiplication arrays of any dimension. The`nutils.matrix.Matrix.solve()`

method has been extended to support multiple right hand sides:>>> matrix.matvec(lhs) # deprecated >>> matrix @ lhs # new syntax >>> matrix @ numpy.stack([lhs1, lhs2, lhs3], axis=1) >>> matrix.solve(rhs) >>> matrix.solve(numpy.stack([rhs1, rhs2, rhs3], axis=1)

MKL’s fgmres method

Matrices produced by the

`MKL`

backend now support the`nutils.matrix.Matrix.solve()`

argument solver=’fmgres’ to use Intel MKL’s fgmres method.Thetamethod time target

The

`nutils.solver.thetamethod`

class, as well as its special cases`impliciteuler`

and`cranknicolson`

, now have a`timetarget`

argument to specify that the formulation contains a time variable:>>> res = topo.integral('...?t... d:x' @ ns, degree=2) >>> solver.impliciteuler('dofs', res, ..., timetarget='t')

New leveltopo argument for trimming

In

`nutils.topology.Topology.trim()`

, in case the levelset cannot be evaluated on the to-be-trimmed topology itself, the correct topology can now be specified via the new`leveltopo`

argument.New unittest assertion assertAlmostEqual64

`nutils.testing.TestCase`

now facilitates comparison against base64 encoded, compressed, and packed data via the new method`nutils.testing.TestCase.assertAlmostEqual64()`

. This replaces`numeric.assert_allclose64`

which is now deprecated and scheduled for removal in Nutils 6.Fast locate for structured topology, geometry

A special case

`nutils.topology.Topology.locate()`

method for structured topologies checks of the geometry is an affine transformation of the natural configuration, in which case the trivial inversion is used instead of expensive Newton iterations:>>> topo, geom = mesh.rectilinear([2, 3]) >>> smp = topo.locate(geom/2-1, [[-.1,.2]]) # locate detected linear geometry: x = [-1. -1.] + [0.5 0.5] xi ~+2.2e-16

Lazy references, transforms, bases

The introduction of sequence abstractions

`nutils.elementseq`

and`nutils.transformseq`

, together with and a lazy implementation of`nutils.function.Basis`

basis functions, help to prevent the unnecessary generation of data. In hierarchically refined topologies, in particular, this results in large speedups and a much reduced memory footprint.Switch to treelog

The

`nutils.log`

module is deprecated and will be replaced by the externally maintained treelog, which is now an installation dependency.Replace pariter, parmap by fork, range.

The

`nutils.parallel`

module is largely rewritten. The old methods`pariter`

and`parmap`

are replaced by the`nutils.parallel.fork()`

context, combined with the shared`nutils.parallel.range()`

iterator:>>> indices = parallel.range(10) >>> with parallel.fork(nprocs=2) as procid: >>> for index in indices: >>> print('procid={}, index={}'.format(procid, index))

## New in v4.0 “eliche”¶

Release date: 2018-08-22.

Spline basis continuity argument

In addition to the

`knotmultiplicities`

argument to define the continuity of basis function on structured topologies, the`nutils.topology.Topology.basis()`

method now supports the`continuity`

argument to define the global continuity of basis functions. With negative numbers counting backwards from the`degree`

, the default value of`-1`

corresponds to a knot multiplicity of 1.Eval arguments

Functions of type

`nutils.function.Evaluable`

can receive arguments in addition to element and points by depending on instances of`nutils.function.Argument()`

and having their values specified via nutils.sample.Sample.eval:>>> f = geom.dot(function.Argument('myarg', shape=geom.shape)) >>> f = 'x_i ?myarg_i' @ ns # equivalent operation in namespace >>> topo.sample('uniform', 1).eval(f, myarg=numpy.ones(geom.shape))

The d:-operator

Namespace expression syntax now includes the

`d:`

Jacobian operator, allowing one to write`'d:x' @ ns`

instead of`function.J(ns.x)`

. Since including the Jacobian in the integrand is preferred over specifying it separately, the`geometry`

argument of`nutils.topology.Topology.integrate()`

is deprecated:>>> topo.integrate(ns.f, geometry=ns.x) # deprecated >>> topo.integrate(ns.f * function.J(ns.x)) # was and remains valid >>> topo.integrate('f d:x' @ ns) # new namespace syntax

Truncated hierarchical bsplines

Hierarchically refined topologies now support basis truncation, which reduces the supports of individual basis functions while maintaining the spanned space. To select between truncated and non-truncated the basis type must be prefixed with ‘th-‘ or ‘h-‘, respectively. A non-prefixed basis type falls back on the default implementation that fails on all types but discont:

>>> htopo.basis('spline', degree=2) # no longer valid >>> htopo.basis('h-spline', degree=2) # new syntax for original basis >>> htopo.basis('th-spline', degree=2) # new syntax for truncated basis >>> htopo.basis('discont', degree=2) # still valid

Transparent function cache

The

`nutils.cache`

module provides a memoizing function decorator`nutils.cache.function()`

which reads return values from cache in case a set of function arguments has been seen before. It is similar in function to Python’s functools.lru_cache, except that the cache is maintained on disk and`nutils.types.nutils_hash()`

is used to compare arguments, which means that arguments need not be Python hashable. The mechanism is activated via`nutils.cache.enable()`

:>>> @cache.function >>> def f(x): >>> return x * 2 >>> >>> with cache.enable(): >>> f(10)

If

`nutils.cli.run()`

is used then the cache can also be enabled via the new`--cache`

command line argument. With many internal Nutils functions already decorated, including all methods in the`nutils.solver()`

module, transparent caching is available out of the box with no further action required.New module: types

The new

`nutils.types`

module unifies and extends components relating to object types. The following preexisting objects have been moved to the new location:util.enforcetypes -> types.apply_annotations util.frozendict -> types.frozendict numeric.const -> types.frozenarray

MKL matrix, Pardiso solver

The new

`MKL`

backend generates matrices that are powered by Intel’s Math Kernel Library, which notably includes the reputable Pardiso solver. This requires`libmkl`

to be installed, which is conveniently available through pip:$ pip install mkl

When

`nutils.cli.run()`

is used the new matrix type is selected automatically if it is available, or manually using`--matrix=MKL`

.Nonlinear minimization

For problems that adhere to an energy structure, the new solver method

`nutils.solver.minimize()`

provides an alternative mechanism that exploits this structure to robustly find the energy minimum:>>> res = sqr.derivative('dofs') >>> solver.newton('dofs', res, ...) >>> solver.minimize('dofs', sqr, ...) # equivalent

Data packing

Two new methods,

`nutils.numeric.pack()`

and its inverse`nutils.numeric.unpack()`

, provide lossy compression to floating point data. Primarily useful for regression tests, the convenience method`numeric.assert_allclose64`

combines data packing with zlib compression and base64 encoding for inclusion in Python codes.

## New in v3.0 “dragon beard”¶

Release date: 2018-02-05.

New: function.Namespace

The

`nutils.function.Namespace`

object represents a container of`nutils.function.Array`

instances:>>> ns = function.Namespace() >>> ns.x = geom >>> ns.basis = domain.basis('std', degree=1).vector(2)

In addition to bundling arrays, arrays can be manipulated using index notation via string expressions using the

`nutils.expression`

syntax:>>> ns.sol_i = 'basis_ni ?dofs_n' >>> f = ns.eval_i('sol_i,j n_j')

New: Topology.integral

Analogous to

`nutils.topology.Topology.integrate()`

, which integrates a function and returns the result as a (sparse) array, the new method`nutils.topology.Topology.integral()`

with identical arguments results in an`nutils.sample.Integral`

object for postponed evaluation:>>> x = domain.integrate(f, geometry=geom, degree=2) # direct >>> integ = domain.integral(f, geometry=geom, degree=2) # indirect >>> x = integ.eval()

Integral objects support linear transformations, derivatives and substitutions. Their main use is in combination with routines from the

`nutils.solver`

module.Removed: TransformChain, CanonicalTransformChain

Transformation chains (sequences of transform items) are stored as standard tuples. Former class methods are replaced by module methods:

>>> elem.transform.promote(ndims) # no longer valid >>> transform.promote(elem.transform, ndims) # new syntax

In addition, every

`edge_transform`

and`child_transform`

of Reference objects is changed from (typically unit-length)`TransformChain`

to`nutils.transform.TransformItem`

.Changed: command line interface

Command line parsers

`nutils.cli.run()`

or`nutils.cli.choose()`

dropped support for space separated arguments (–arg value), requiring argument and value to be joined by an equals sign instead:$ python script.py --arg=value

Boolean arguments are specified by omitting the value and prepending ‘no’ to the argument name for negation:

$ python script.py --pdb --norichoutput

For convenience, leading dashes have been made optional:

$ python script.py arg=value pdb norichoutput

New: Topology intersections (deprecates common_refinement)

Intersections between topologies can be made using the

`&`

operator. In case the operands have different refinement patterns, the resulting topology will consist of the common refinements of the intersection:>>> intersection = topoA & topoB >>> interface = topo['fluid'].boundary & ~topo['solid'].boundary

Changed: Topology.indicator

The

`nutils.topology.Topology.indicator()`

method is moved from subtopology to parent topology, i.e. the topology you want to evaluate the indicator on, and now takes the subtopology is an argument:>>> ind = domain.boundary['top'].indicator() # no longer valid >>> ind = domain.boundary.indicator(domain.boundary['top']) # new syntax >>> ind = domain.boundary.indicator('top') # equivalent shorthand

Changed: Evaluable.eval

The

`nutils.function.Evaluable.eval`

method accepts a flexible number of keyword arguments, which are accessible to`evalf`

by depending on the`EVALARGS`

token. Standard keywords are`_transforms`

for transformation chains,`_points`

for integration points, and`_cache`

for the cache object:>>> f.eval(elem, 'gauss2') # no longer valid >>> ip, iw = elem.getischeme('gauss2') >>> tr = elem.transform, elem.opposite >>> f.eval(_transforms=tr, _points=ip) # new syntax

New: numeric.const

The

`numeric.const`

array represents an immutable, hashable array:>>> A = numeric.const([[1,2],[3,4]]) >>> d = {A: 1}

Existing arrays can be wrapped into a

`const`

object by adding`copy=False`

. The`writeable`

flag of the original array is set to False to prevent subsequent modification:>> A = numpy.array([1,2,3]) >> Aconst = numeric.const(A, copy=False) >> A[1] = 4 ValueError: assignment destination is read-only

New: function annotations

The

`util.enforcetypes`

decorator applies conversion methods to annotated arguments:>>> @util.enforcetypes >>> def f(a:float, b:tuple) >>> print(type(a), type(b)) >>> f(1, [2]) <class 'float'> <class 'tuple'>

The decorator is by default active to constructors of cache.Immutable derived objects, such as function.Evaluable.

Changed: Evaluable._edit

Evaluable objects have a default edit implementation that re-instantiates the object with the operand applied to all constructor arguments. In situations where the default implementation is not sufficient it can be overridden by implementing the

`edit`

method (note: without the underscore):>>> class B(function.Evaluable): >>> def __init__(self, d): >>> assert isinstance(d, dict) >>> self.d = d >>> def edit(self, op): >>> return B({key: op(value) for key, value in self.d.items()})

Changed: function derivatives

The

`nutils.function.derivative`

`axes`

argument has been removed;`derivative(func, var)`

now takes the derivative of`func`

to all the axes in`var`

:>>> der = function.derivative(func, var, ... axes=numpy.arange(var.ndim)) # no longer valid >>> der = function.derivative(func, var) # new syntax

New module: cli

The

`nutils.util.run`

function is deprecated and replaced by two new functions,`nutils.cli.choose()`

and`nutils.cli.run()`

. The new functions are very similar to the original, but have a few notable differences:`cli.choose`

requires the name of the function to be executed (typically ‘main’), followed by any optional arguments`cli.run`

does not require the name of the function to be executed, but only a single one can be specifiedargument conversions follow the type of the argument’s default value, instead of the result of

`eval`

the

`--tbexplore`

option for post-mortem debugging is replaced by`--pdb`

, replacing Nutils’ own traceback explorer by Python’s builtin debuggeron-line debugging is provided via the ctrl+c signal handler

function annotations can be used to describe arguments in both help messages and logging output (see examples)

New module: solver

The

`nutils.solver`

module provides infrastructure to facilitate formulating and solving complicated nonlinear problems in a structured and largely automated fashion.New: topology.with{subdomain,boundary,interfaces,points}

Topologies have been made fully immutable, which means that the old setitem operation is no longer supported. Instead, to add a subtopology to the domain, its boundary, its interfaces, or points, any of the methods :func:

`withsubdomain`

,`withboundary`

,`withinterfaces`

, and`withpoints`

, respectively, will return a copy of the topology with the desired groups added:>> topo.boundary['wall'] = topo.boundary['left,top'] # no longer valid >> newtopo = topo.withboundary(wall=topo.boundary['left,top']) # new syntax >> newtopo = topo.withboundary(wall='left,top') # equivalent shorthand >> newtopo.boundary['wall'].integrate(...)

New: circular symmetry

Any topology can be revolved using the new

`nutils.topology.Topology.revolved()`

method, which interprets the first geometry dimension as a radius and replaces it by two new dimensions, shifting the remaining axes backward. In addition to the modified topology and geometry, simplifying function is returned as the third return value which replaces all occurrences of the revolution angle by zero. This should only be used after all gradients have been computed:>> rdomain, rgeom, simplify = domain.revolved(geom) >> basis = rdomain.basis('spline', degree=2) >> M = function.outer(basis.grad(rgeom)).sum(-1) >> rdomain.integrate(M, geometry=rgeom, ischeme='gauss2', edit=simplify)

Renamed mesh.gmesh to mesh.gmsh; added support for periodicity

The gmsh importer was unintentionally misnamed as gmesh; this has been fixed. With that the old name is deprecated and will be removed in future. In addition, support for the non-physical mesh format and externally supplied boundary labels has been removed (see the unit test tests/mesh.py for examples of valid .geo format). Support is added for periodicity and interface groups.

## New in v2.0 “chuka men”¶

Release date: 2016-02-18.

Changed: jump sign

The jump operator has been changed according to the following definition:

`jump(f) = opposite(f) - f`

. In words, it represents the value of the argument from the side that the normal is pointing toward, minus the value from the side that the normal is pointing away from. Compared to the old definition this means the sign is flipped.Changed: Topology objects

The Topology base class no longer takes a list of elements in its constructor. Instead, the

`__iter__`

method should be implemented by the derived class, as well as`__len__`

for the number of elements, and getelem(index) to access individual elements. The ‘elements’ attribute is deprecated.The

`nutils.topology.StructuredTopology`

object no longer accepts an array with elements. Instead, an ‘axes’ argument is provided with information that allows it to generate elements in the fly. The ‘structure’ attribute is deprecated. A newly added`shape`

tuple is now a documented attribute.Changed: properties dumpdir, outdir, outrootdir

Two global properties have been renamed as follows:

dumpdir -> outdir outdir -> outrootdir

The

`outrootdir`

defaults to ~/public_html and can be redefined from the command line or in the .nutilsrc configuration file. The outdir defaults to the current directory and is redefined by`util.run`

, nesting the name/date/time subdirectory sequence under`outrootdir`

.Changed: sum axis argument

The behaviour of

`nutils.function.sum`

is inconsistent with that of the Numpy counterparts. In case no axes argument is specified, Numpy sums over all axes, whereas Nutils sums over the last axis. To undo this mistake and transition to Numpy’s behaviour, calling sum without an axes argument is deprecated and will be forbidden in Nutils 3.0. In Nutils 4.0 it will be reintroduced with the corrected meaning.Changed: strict dimension equality in function.outer

The

`nutils.function.outer()`

method allows arguments of different dimension by left-padding the smallest prior to multiplication. There is no clear reason for this generality and it hinders error checking. Therefore in future in`function.outer(a, b)`

,`a.ndim`

must equal`b.ndim`

. In a brief transition period non-equality emits a warning.Changed: Evaluable base class

Relevant only for custom

`nutils.function.Evaluable`

objects, the`evalf`

method changes from constructor argument to instance/class method:>> class MyEval( function.Evaluable): >> def __init__(self, ...): >> function.Evaluable(args=[...], shape=...) >> def evalf( self, ...): >> ...

Moreover, the

`args`

argument may only contain Evaluable objects. Static information is to be passed through`self`

.Removed: _numeric C-extension

At this point Nutils is pure Python. It is no longer necessary to run make to compile extension modules. The numeric.py module remains unchanged.

Periodic boundary groups

Touching elements of periodic domains are no longer part of the

`boundary`

topology. It is still available as boundary of an appropriate non-periodic subtopology:>> domain.boundary['left'] # no longer valid >> domain[:,:1].boundary['left'] # still valid

New module: transform

The new

`nutils.transform`

module provides objects and operations relating to affine coordinate transformations.Traceback explorer disabled by default

The new command line switch

`--tbexplore`

activates the traceback explorer on program failure. To change the default behavior add`tbexplore=True`

to your .nutilsrc file.Rich output

The new command line switch

`--richoutput`

activates color and unicode output. To change the default behavior add`richoutput=True`

to your .nutilsrc file.

## Older releases¶

v1.0 “bakmi” was released 2014-08-04.

v0.0 “anelli” was released 2013-10-28.