ParaDiag#

class ParaDiag[source]#

Bases: object

What ParaDiag is, independently of how the block is spread over processes.

Mixed into a controller, this replaces its iteration and leaves everything around it alone:

class controller_ParaDiag_nonMPI(ParaDiag, controller_nonMPI) class controller_ParaDiag_MPI(ParaDiag, controller_MPI)

It deliberately has no __init__ and no controller of its own to inherit from, so that it can be mixed into either transport’s controller without the two having to agree on a constructor signature. A concrete ParaDiag controller calls prepare_ParaDiag_params on the two dictionaries and then hands them to its controller’s initialisation.

What stays with the concrete classes is everything whose implementation depends on where the other steps are: apply_matrix, prepare_Jacobians, compute_all_at_once_residual, update_G_inv and the block driver.

FFT_in_time(quantity: Any, k: int = 0) → None[source]#

Compute weighted forward FFT in time. The weighting is determined by the alpha parameter in ParaDiag

Note: The implementation via matrix-vector multiplication may be inefficient and less stable compared to an FFT

with transposes!

Parameters:
  • quantity (str) – the level attribute to transform

  • k (int) – iteration index, for an iteration dependent alpha

apply_matrix(mat: Any, quantity: str) → None[source]#

Apply a square matrix across the steps, in place.

How this is done depends entirely on where the other steps are, so the concrete controllers implement it.

Parameters:
  • mat – square matrix with as many rows as there are steps

  • quantity (str) – ‘residual’ or ‘increment’, the level attribute to transform

compute_residual_after_spread(S: Any) → None[source]#

ParaDiag’s residual is the one of the composite collocation problem, which it_ParaDiag computes as part of the iteration. The convergence check runs before the first iteration, so the initial guess needs its residual here.

Parameters:

S (pySDC.Step.step) – The current step

get_FFT_matrices(k: int = 0) → Any[source]#

Get the weighted FFT and iFFT matrices for iteration k, rebuilding them only when alpha actually changes.

Parameters:

k (int) – iteration index

Returns:

tuple – the forward and backward weighted FFT matrices

get_alpha(k: int = 0) → float[source]#

Get the ParaDiag alpha parameter for iteration k.

Parameters:

k (int) – iteration index

Returns:

float – alpha to use for this iteration

get_stages() → Dict[str, Any][source]#

ParaDiag has one iteration stage, and no predictor because it has no coarse level.

Returns:

dict – stage name -> the method that runs it

iFFT_in_time(quantity: Any, k: int = 0) → None[source]#

Compute weighted backward FFT in time. The weighting is determined by the alpha parameter in ParaDiag

Parameters:
  • quantity (str) – the level attribute to transform

  • k (int) – iteration index, for an iteration dependent alpha

next_iteration_stage(S: Any) → str[source]#
Parameters:

S (pySDC.Step.step) – The current step

Returns:

str – name of the stage to enter

static prepare_ParaDiag_params(controller_params: Dict[str, Any], description: Dict[str, Any]) → None[source]#

Check and complete the parameters ParaDiag needs, in place.

Call this before the controller’s own initialisation: it only reads and writes the two dictionaries, and must have run by the time the steps are built.

Parameters:
  • controller_params (dict) – parameter set for the controller and the steps

  • description (dict) – all the parameters to set up the rest (levels, problems, …)

prepare_convergence_check(*args: Any, **kwargs: Any) → None[source]#

The residual is already current – it_ParaDiag computed it, and recomputing it the way a sweep-based algorithm does would need initial conditions that have not been communicated yet. The end point is not, because nothing sent it anywhere, so compute it here: it_check is what publishes uend when a step is done.

Takes whatever its controller passes – a block of steps or a communicator – and needs none of it, because the steps to do this for are the ones this controller owns.

static resolve_alpha(alpha: Any, k: int = 0) → float[source]#

Read the alpha for iteration k out of whatever the user supplied.

alpha may be a single number, a sequence indexed by iteration (the last entry is reused once it runs out), or a callable taking the iteration index. Making it iteration dependent lets the outer iteration start with a well-conditioned alpha and tighten it later.

Static because the steps need an alpha before the controller has parameters to read it from.

Parameters:
  • alpha – the alpha parameter as supplied by the user

  • k (int) – iteration index

Returns:

float – alpha to use for this iteration

step_is_active(time: float, block_start: float, Tend: float) → bool[source]#

ParaDiag diagonalizes across the whole block, so it cannot drop a step out of one. A block that starts before Tend is run whole, past Tend if need be.

Parameters:
  • time (float) – when this step starts

  • block_start (float) – when the first step of this step’s block starts

  • Tend (float) – ending time

Returns:

bool – whether this step takes part

update_G_inv(k: int = 0) → None[source]#

Rebuild G^-1 on the local step(s) when alpha changes with the iteration.

G^-1 depends on alpha, so an iteration dependent alpha means the sweeper’s diagonalization has to be recomputed. Subclasses implement this because only they know which steps they own.

Parameters:

k (int) – iteration index