toolkit

pfc_util.toolkit.get_unit_cell(eps: str, liquid=False) RealField2D

Retrieve a relaxed and minimized solid/liquid unit cell

Parameters:
  • eps – PFC \(\epsilon\), specified with a string

  • liquid – If True, the liquid profile will be returned

pfc_util.toolkit.get_coexistent_mu_bounds(eps: str) Tuple[floating | float64 | float, floating | float64 | float]

Return the bounds of solid-liquid coexistent chemical potential

pfc_util.toolkit.get_coexistent_mu_final(eps: str) floating | float64 | float

Return the final value (during simulation) of solid-liquid coexistent chemical potential

pfc_util.toolkit.get_relaxed_unit_cell_size(eps: str, ratio=True) Tuple[floating | float64 | float, floating | float64 | float]

Return the unit cell size at solid-liquid equilibrium.

pfc_util.toolkit.evolve_and_elongate_interface(ifc: RealField2D, delta_sol: RealField2D, delta_liq: RealField2D, *, minimizer_supplier: MinimizerSupplier, n_steps: int = 31, hooks: EvolverHooks[FieldEvolver[RealField2D]] | None = None, verbose: bool = False) RealField2D

Evolve interface -> elongate interface

Parameters:
  • ifc,delta_sol,delta_liq – 2D real fields of the same height & vertical shape

  • minimizer_supplier – A function that returns a constant \(\mu\) minimizer given a field and \(\mu\)

pfc_util.toolkit.find_coexistent_mu(solid_field: RealField2D, mu_min: floating | float64 | float, mu_max: floating | float64 | float, fef: FreeEnergyFunctionalBase, *, relaxer_supplier: MuMinimizerSupplier, relaxer_nsteps: int = 31, relaxer_hooks: EvolverHooks[FieldEvolver[RealField2D]] | None = None, const_mu_supplier: MuMinimizerSupplier, const_mu_nsteps: int = 31, const_mu_hooks: EvolverHooks[FieldEvolver[RealField2D]] | None = None, max_iters: int | None = None, precision: floating | float64 | float = 0.0, verbose: bool = True, liquid_tol: floating | float64 | float = 0.0001, search_method: Literal['binary', 'interpolate'] = 'binary', error_if_liquefied: bool = False)
Given:
  • a solid profile \(\psi_s(\mathbf{r})\)

  • minimum and maximum values of chemical potential

  • a free energy functional,

find the chemical potential that satisfies:

\[\Omega[\psi_s] = \Omega[\psi_l]\]
\[\iff F[\psi_s] - \mu \int d\mathbf{r}\psi_s = F[\psi_l] - \mu V\psi_l\]

via binary search or linear interpolation.

Parameters:
  • solid_field\(\psi_s\)

  • mu_min – initial mu lower bound

  • mu_max – initial mu upper bound

  • fef – A free energy functional object (instance of pfc_util.FreeEnergyFunctionalBase)

  • relaxer_supplier – A function that returns a relaxer given a field and \(\mu\)

  • const_mu_supplier – A function that returns a constant \(\mu\) minimizer given a field and \(\mu\)

  • max_iters – Maximum number of iterations

  • precision – The relative tolerance of \(\mu\)

class pfc_util.toolkit.MuSearchRecord(*, initial_range: Tuple[floating | float64 | float, floating | float64 | float], search_method: Literal['binary', 'interpolate'] = 'binary')

Bases: ZeroSearchRecord

Container for information acquired during search for \(\mu\).

__init__(*, initial_range: Tuple[floating | float64 | float, floating | float64 | float], search_method: Literal['binary', 'interpolate'] = 'binary') None
Parameters:
  • initial_range – initial upper and lower bounds

  • search_method – can be either binary or interpolate

class pfc_util.toolkit.ZeroSearchRecord(*, initial_range: Tuple[floating | float64 | float, floating | float64 | float] | None = None, search_method: Literal['binary', 'interpolate'] = 'binary')

This interface handles the search of a zero of a monotonically increasing function that is expensive to evaluate on a given interval.

There are currently two supported modes:

  • binary:

    Perform a binary search

  • interpolate:

    Really interpolate & extrapolate. This assumes that the function has definite concavity on the interval of interest.

__init__(*, initial_range: Tuple[floating | float64 | float, floating | float64 | float] | None = None, search_method: Literal['binary', 'interpolate'] = 'binary') None
Parameters:
  • initial_range – initial upper and lower bounds

  • search_method – can be either binary or interpolate

property upper_bound

Upper bound of zero

property lower_bound

Lower bound of zero

property zero

Return the zero, if found

polate_zero(x1: floating | float64 | float, x2: floating | float64 | float, *, rtol: floating | float64 | float = 0)
Parameters:

x1,x2 – previously evaluated \(x\) values

Returns:

The x coordinate the intersection betwee the x axis and the line passing through (x1, f(x1)) and (x2, f(x2)).

Raises:

ValueError – If f(x1) and f(x2) is too close (w.r.t. rtol), a ValueError is raised

next(verbose: bool = True) floating | float64 | float

Return the next point (x) to sample

We want a balance between quick convergence and narrow bounds.

The problem with binary search is that although the bounds are guaranteed to be narrow from both directions, the convergence is slow due to the method of iteration.

The problem with linear inter/extrapolation is that it is easy to run into an endless loop of unilateral sampling. Assuming a monotonically increasing function with definite concavity within the interval of interest, then one iteration results in

Convex:
- - → +
- + → -
+ + → +
Concave:
- - → -
- + → +
+ + → -

where -/+ means to the left/right of the zero. So if the function is concave, for example, one would keep sampling from the left of the zero.

The algorithm provided here tackles this problem in the following way:

  • The sampling is done in four phases

  • In phases 0 & 2, we use the current upper and lower bounds to obtain an inter/extrapolated zero.

  • In phase 1, the current and previous upper bounds are used

  • In phase 3, the current and previous lower bounds are used

  • If the above is not possible (due to lack of samples at the beginning or vanishing denominator), then fall back to binary sampling