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:
ZeroSearchRecordContainer 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
binaryorinterpolate
- 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
binaryorinterpolate
- 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