# Supporting new functions

## Overloading a new function

If a method for `Particles`

is not implemented for your function `yourfunc`

, the pattern to register your function looks like this

`register_primitive(yourfunc)`

This defines both a one-argument method and a multi-arg method for both `Particles`

and `StaticParticles`

. If you only want to define one of these, see `register_primitive_single`

/`register_primitive_multi`

. If the function is from base or stdlib, you can just add it to the appropriate list in the source and submit a PR :)

## Monte-Carlo simulation by `map/pmap`

Some functions will not work when the input arguments are of type `Particles`

. For this kind of function, we provide a fallback onto a traditional `map(f,p.particles)`

. The only thing you need to do is to decorate the function call with the function `bymap`

or the macro `@bymap`

like so:

```
f(x) = 3x^2
p = 1 ± 0.1
r = @bymap f(p) # bymap(f,p) may give better error traces
```

We further provide the macro `@bypmap`

(and `bypmap`

) which does exactly the same thing, but with a `pmap`

(parallel map) instead, allowing you to run several invocations of `f`

in a distributed fashion.

These utilities will map the function `f`

over each element of `p::Particles{T,N}`

, such that `f`

is only called with arguments of type `T`

, e.g., `Float64`

. This handles arguments that are multivaiate particles `<: Vector{<:AbstractParticles}`

as well.

These utilities will typically be slower than calling `f(p)`

. If `f`

is very expensive, `@bypmap`

might prove prove faster than calling `f`

with `p`

, it's worth a try. The usual caveats for distributed computing applies, all code must be loaded on all workers etc.

## Array-to-array functions

These functions might not work with `Particles`

out of the box. Special cases are currently implemented for

`exp : ℝ(n×n) → ℝ(n×n)`

matrix exponential`log : ℝ(n×n) → C(n×n)`

matrix logarithm`eigvals : ℝ(n×n) → C(n)`

**warning**: eigenvalues are sorted, when two eigenvalues cross, this function is nondifferentiable. Eigenvalues can thus appear to have dramatically widened distributions. Make sure you interpret the result of this call in the right way.

The function `ℝⁿ2ℝⁿ_function`

`(f::Function, p::AbstractArray{T})`

applies `f : ℝⁿ → ℝⁿ`

to an array of particles. See also `ℝⁿ2ℂⁿ_function`

which is used to implement, e.g., `log,eigvals`

## Complex functions

These functions do not work with `Particles`

out of the box. Special cases are currently implemented for

`sqrt`

,`exp`

,`sin`

,`cos`

We also provide in-place versions of the above functions, e.g.,

`sqrt!(out, p)`

,`exp!(out, p)`

,`sin!(out, p)`

,`cos!(out, p)`

The function `ℂ2ℂ_function`

`(f::Function, z)`

(`ℂ2ℂ_function!(f::Function, out, z)`

) applies `f : ℂ → ℂ`

to `z::Complex{<:AbstractParticles}`

.

## Difficult cases

Sometimes, defining a primitive function can be difficult, such as when the uncertain parameters are baked into some object. In such cases, we can call the function `unsafe_comparisons`

`(true)`

, which defines all comparison operators for uncertain values to compare using the `mean`

. Note however that this enabling this is somewhat *unsafe* as this corresponds to a fallback to linear uncertainty propagation, why it's turned off by default. We also provide the macro `@unsafe ex`

to enable mean comparisons only locally in the expression `ex`

.

In some cases, defining a primitive is not possible but allowing unsafe comparisons are not acceptable. One such case is functions that internally calculate eigenvalues of uncertain matrices. The eigenvalue calculation makes use of comparison operators. If the uncertainty is large, eigenvalues might change place in the sorted list of returned eigenvalues, completely ruining downstream computations. For this we recommend, in order of preference

- Use
`bymap`

. Applicable if all uncertain values appears as arguments to your entry function. - Create a
`Workspace`

object and call it using your entry function. Applicable if uncertain parameters appear nested in an object that is an argument to your entry function:

```
# desired computation: y = f(obj), obj contains uncertain parameters inside
y = with_workspace(f, obj)
# or equivalently
w = Workspace(f,obj) # This is somewhat expensive and can be reused
use_invokelatest = true # Set this to false to gain 0.1-1 ms, at the expense of world-age problems if w is created and used in the same function.
w(obj, use_invokelatest)
```

This interface is so far not tested very well and may throw strange errors. Some care has been taken to make error messages informative. Internally, a `w::Workspace`

object is created that tries to automatically construct an object identical to `obj`

, but where all uncertain parameters are replaced by conventional `Real`

. If the heuristics used fail, an error message is displayed detailing which method you need to implement to make it work. When called, `w`

populates the internal buffer object with particle `i`

, calls `f`

using a `Particles`

-free `obj`

and stores the result in an output object at particle index `i`

. This is done for `i ∈ 1:N`

after which the output is returned. Some caveats include: `Workspace`

must not be created or used inside a `@generated`

function.