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 “hiyamugi”¶
New: expression and namespace version 2
The
nutils.expression
module has been renamed tonutils.expression_v1
, thenutils.function.Namespace
class tonutils.expression_v1.Namespace
and thenutils.expression_v2
module has been added, featuring a newNamespace
. The version 2 of the namespace v2 has an expression language that differs slightly from version 1, most notably in the way derivatives are written. The old namespace remains available for the time being. All examples are updated to the new namespace. You are encouraged to use the new namespace for newly written code.Changed: bifurcate has been replaced by spaces
In the past using functions on products of
Topology
instances required usingfunction.bifurcate
. This has been replaced by the concept of ‘spaces’. Every topology is defined in a space, identified by a name (str
). Functions defined on some topology are considered constant on other topologies (defined on other spaces).If you want to multiply two topologies, you have to make sure that the topologies have different spaces, e.g. via the
space
parameter ofnutils.mesh.rectilinear()
. Example:>>> from nutils import mesh, function >>> Xtopo, x = mesh.rectilinear([4], space='X') >>> Ytopo, y = mesh.rectilinear([2], space='Y') >>> topo = Xtopo * Ytopo >>> geom = function.concatenate([x, y])
Changed: function.Array shape must be constant
Resulting from to the function/evaluable split introduced in #574, variable length axes such as relating to integration points or sparsity can stay confined to the evaluable layer. In order to benefit from this situation and improve compatibility with Numpy’s arrays,
nutils.function.Array
objects are henceforth limited to constant shapes. Additionally:The sparsity construct
nutils.function.inflate
has been removed;The
nutils.function.Elemwise()
function requires all element arrays to be of the same shape, and its remaining use has been deprecated in favor ofnutils.function.get()
;Aligning with Numpy’s API,
nutils.function.concatenate()
no longer automatically broadcasts its arguments, but instead demands that all dimensions except for the concatenation axis match exactly.
Changed: locate arguments
The
nutils.topology.Topology.locate()
method now allowstol
to be left unspecified ifeps
is specified instead, which is repurposed as stop criterion for distances in element coordinates. Conversely, if onlytol
is specified, a corresponding minimaleps
value is set automatically to match points near element edges. Theischeme
andscale
arguments are deprecated and replaced bymaxdist
, which can be left unspecified in general. The optionalweights
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 ownnutils.unit
module, with the old locationtypes.unit
now holding a forward method. The forward emits a deprecation warning prompting to changenutils.types.unit.create
(or its shorthandnutils.types.unit
) tonutils.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 XLD_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
andevaluable
The function module has been split into a high-level, numpy-like
function
module and a lower-levelevaluable
module. Theevaluable
module is agnostic to the so-called points axis. Scripts that don’t use custom implementations offunction.Array
should work without modification.Custom implementations of the old
function.Array
should now derive fromevaluable.Array
. Furthermore, an accompanying implementation offunction.Array
should be added with aprepare_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
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
deprecatestarget0
To explicitly refer to the history state in
nutils.solver.thetamethod()
and its derivativesimpliciteuler
andcranknicolson
, instead of specifiying the target through thetarget0
parameter, the new argumenthistorysuffix
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
ornutils.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. Usefunction.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. Thetransforms
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 ornutils.matrix.Matrix
upon return. To prevent this conversion and receive the sparse objects instead use the newnutils.sample.Sample.integrate_sparse()
ornutils.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 tonutils.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 reachesnutils.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 to1e-3
unless otherwise specified vialinrtol
. 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 usenutils.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()
andnutils.solver.optimize()
now receive linear solver arguments as keyword arguments rather than via thesolveargs
dictionary, which is deprecated. To avoid name clashes with the remaining arguments, argument names must be prefixed bylin
:>>> 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 theatol
resp.rtol
arguments, regardless of backend and solver. If the backend returns a solution that violates both tolerances then an exception is raised of typenutils.matrix.ToleranceNotReached
, from which the solution can still be obtained via the .best attribute. Alternatively the new methodnutils.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>
andfallback_length
. The former can be used to assign fixed lengths to specific indices in expressions, say indexi
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 withnutils.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 ofnutils.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. Thenutils.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 thenutils.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 casesimpliciteuler
andcranknicolson
, now have atimetarget
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 newleveltopo
argument.New unittest assertion assertAlmostEqual64
nutils.testing.TestCase
now facilitates comparison against base64 encoded, compressed, and packed data via the new methodnutils.testing.TestCase.assertAlmostEqual64()
. This replacesnumeric.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
andnutils.transformseq
, together with and a lazy implementation ofnutils.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 methodspariter
andparmap
are replaced by thenutils.parallel.fork()
context, combined with the sharednutils.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, thenutils.topology.Topology.basis()
method now supports thecontinuity
argument to define the global continuity of basis functions. With negative numbers counting backwards from thedegree
, 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 ofnutils.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 offunction.J(ns.x)
. Since including the Jacobian in the integrand is preferred over specifying it separately, thegeometry
argument ofnutils.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 decoratornutils.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 andnutils.types.nutils_hash()
is used to compare arguments, which means that arguments need not be Python hashable. The mechanism is activated vianutils.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 thenutils.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 requireslibmkl
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 inversenutils.numeric.unpack()
, provide lossy compression to floating point data. Primarily useful for regression tests, the convenience methodnumeric.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 ofnutils.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 methodnutils.topology.Topology.integral()
with identical arguments results in annutils.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
andchild_transform
of Reference objects is changed from (typically unit-length)TransformChain
tonutils.transform.TransformItem
.Changed: command line interface
Command line parsers
nutils.cli.run()
ornutils.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 toevalf
by depending on theEVALARGS
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 addingcopy=False
. Thewriteable
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 offunc
to all the axes invar
:>>> 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()
andnutils.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 argumentscli.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
, andwithpoints
, 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 addedshape
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 byutil.run
, nesting the name/date/time subdirectory sequence underoutrootdir
.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 infunction.outer(a, b)
,a.ndim
must equalb.ndim
. In a brief transition period non-equality emits a warning.Changed: Evaluable base class
Relevant only for custom
nutils.function.Evaluable
objects, theevalf
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 throughself
.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 addtbexplore=True
to your .nutilsrc file.Rich output
The new command line switch
--richoutput
activates color and unicode output. To change the default behavior addrichoutput=True
to your .nutilsrc file.
Older releases¶
v1.0 “bakmi” was released 2014-08-04.
v0.0 “anelli” was released 2013-10-28.