Nutils 3 Dragon Beard

Nutils 3.0 was released on August 22nd, 2018.

Nutils 3.1 was released on February 5th, 2018.

What's New?

These are the main additions and changes since Nutils 2 Chuka Men.

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 specified
  • argument 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 debugger
  • on-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 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.