# Solving contact

The resolution of contact problems is done with classes that inherit from `tamaas::ContactSolver`. These usually take as argument a `tamaas::Model` object, a surface described by a `tamaas::Grid` or a 2D numpy array, and a tolerance. We will see the specificities of the different solver objects below.

## Elastic contact

The most common case is normal elastic contact, and is most efficiently solved with `tamaas::PolonskyKeerRey`. The advantage of this class is that it combines two algorithms into one. By default, it considers that the contact pressure field is the unknown, and tries to minimize the complementary energy of the system under the constraint that the mean pressure should be equal to the value supplied by the user, for example:

```# ...
solver = tm.PolonskyKeerRey(model, surface, 1e-12)
solver.solve(1e-2)
```

Here the average pressure is `1e-2`. The solver can also be instanciated by specifying the the constraint should be on the mean gap instead of mean pressure:

```solver = tm.PolonskyKeerRey(model, surface, 1e-12, constraint_type=tm.PolonskyKeerRey.gap)
solver.solve(1e-2)
```

The contact problem is now solved for a mean gap of `1e-2`. Note that the choice of constraint affects the performance of the algorithm.

### Non-periodic contact

When the primal type is pressure, it is possible to solve a non-periodic normal contact problem by first registering a non-periodic integral operator (based on the DC-FFT method) then telling the contact solver to use it in its energy functional:

```tm.ModelFactory.registerNonPeriodic(model, 'dcfft')
solver.setIntegralOperator('dcfft')
solver.solve(1e-2)
```

Note however that the solver still shifts the displacement field so that the gap is everywhere positive and zero in the contact zones. This loses the information of the absolute displacement relative to the initial position of the surface. This can be recovered post-solve with:

```model.operators['dcfft'](model.traction, model.displacement)
```

The example `nonperiodic.py` shows an Hertzian contact example.

The module `tamaas.utils` defines a convenience function `load_path`, which generates solution models for a sequence of loads. This allows lazy evalution and reduces boiler-plate:

```from tamaas.utils import load_path

...  # do some computation on model, e.g. compute contact clusters
```

The function `load_path` accepts the following optional arguments:

`verbose`

Prints solver output (i.e. iteration, cost function and error)

`callback`

A function to call after each solve, before the next load step. Useful for dumping model in generator expressions.

The second algorithm hidden in `tamaas::PolonskyKeerRey` considers the gap to be the unknown. The constaint on the mean value can be chosen as above:

```solver = tm.PolonskyKeerRey(model, surface, 1e-12,
primal_type=tm.PolonskyKeerRey.gap,
constraint_type=tm.PolonskyKeerRey.gap)
solver.solve(1e-2)
```

The advantage of this formulation is to be able to solve adhesion problems (Rey et al.). This is done by adding a term to the potential energy functional that the solver tries to minimize:

```adhesion_params = {
"rho": 2e-3,
"surface_energy": 2e-5
}

solver.solve(1e-2)
```

Custom classes can be used in place of the example term here. One has to inherit from `tamaas::Functional`:

```import numpy

"""
Functional class that extends a C++ class and implements the virtual
methods
"""

def __init__(self, rho, gamma):
super().__init__(self)
self.rho = rho
self.gamma = gamma

def computeF(self, gap, pressure):
return -self.gamma * numpy.sum(np.exp(-gap / self.rho))

gradient += self.gamma * numpy.exp(-gap / self.rho) / self.rho
```

This example is actually equivalent to `tamaas::functional::ExponentialAdhesionFunctional`.

## Tangential contact

For frictional contact, several solver classes are available. Among them, `tamaas::Condat` is able to solve a coupled normal/tangential contact problem regardless of the material properties. It however solves an associated version of the Coulomb friction law. In general, the Coulomb friction used in contact makes the problem ill-posed.

Solving a tangential contact problem is not much different from normal contact:

```mu = 0.3  # Friction coefficient
solver = tm.Condat(model, surface, 1e-12, mu)
solver.max_iter = 5000         # The default of 1000 may be too little
solver.solve([1e-2, 0, 1e-2])  # 3D components of applied mean pressure
```

## Elasto-plastic contact

For elastic-plastic contact, one needs three different solvers: an elastic contact solver like the ones described above, a non-linear solver and a coupling solver. The non-linear solvers available in Tamaas are implemented in python and inherit from the C++ class `tamaas::EPSolver`. They make use of the non-linear solvers available in scipy:

`DFSANESolver`

Implements an interface to `scipy.optimize.root()` wiht the DFSANE method.

`NewtonKrylovSolver`

Implements an interface to `scipy.optimize.newton_krylov()`.

These solvers require a residual vector to cancel, the creation of which is handled with `tamaas::ModelFactory`. Then, an instance of `tamaas::EPICSolver` is needed to couple the contact and non-linear solvers for the elastic-plastic contact problem:

```import tamaas as tm

from tamaas.nonlinear_solvers import DFSANESolver

# Definition of modeled domain
model_type = tm.model_type.volume_2d
discretization = [32, 51, 51]  # Order: [z, x, y]
flat_domain = [1, 1]
system_size = [0.5] + flat_domain

# Setup for plasticity
material = tm.materials.IsotropicHardening(model,
sigma_y=0.1 * model.E,
hardening=0.01 * model.E)
residual = tm.Residual(model, material)
epsolver = DFSANESolver(residual)

# ...

csolver = tm.PolonskyKeerRey(model, surface, 1e-12)

epic = tm.EPICSolver(csolver, epsolver, 1e-7, relaxation=0.3)
epic.solve(1e-3)

# or use an accelerated scheme (which can sometimes not converge)

epic.acceleratedSolve(1e-3)
```

By default, `tamaas::EPICSolver::solve()` uses a relaxed fixed point. It can be tricky to make it converge: you need to decrease the relaxation parameter passed as argument of the constructor, but this also hinders the convergence rate. The function `tamaas::EPICSolver::acceleratedSolve()` does not require the tweaking of a relaxation parameter, so it can be faster if the latter does not have an optimal value. However, it is not guaranteed to converge.

Finally, during the first iterations, the fixed point error will be large compared to the error of the non-linear solver. It can improve performance to have the tolerance of the non-linear solver (which is the most expensive step of the fixed point solve) decrease over the iterations. This can be achieved with the decorator class `ToleranceManager`:

```from tamaas.nonlinear_solvers import ToleranceManager, DFSANESolver

@ToleranceManager(start=1e-2,
end=1e-9,
rate=1 / 3)
class Solver(DFSANESolver):
pass

# ...

epsolver = Solver(residual)

# or

epsolver = ToleranceManager(1e-2, 1e-9, 1/3)(DFSANESolver)(residual)
```

## Custom Contact Solvers

The `tamaas::ContactSolver` class can be derived in Python so that users can interface with Scipy’s `scipy.optimize.minimize()` function or PETSc’s solvers accessible in the Python interface. See `examples/scipy_penalty.py` for an example on how to proceed.