landau.interpolate¶
- class landau.interpolate.CalphadFittedSurface(f0_model, df_model, L_models)[source]¶
Bases:
FittedSurfaceFitted surface for
CalphadSurface2DInterpolator.Holds SGTE models for the terminal free energies and polynomial models for each Redlich-Kister interaction coefficient.
slice_at()evaluates them at T to produce aRedlichKisterInterpolationwith an analytic c-derivative.
- class landau.interpolate.CalphadSurface2DInterpolator(num_coeffs=5, terminal_sgte_order=4, coeff_poly_order=2)[source]¶
Bases:
SurfaceInterpolatorCALPHAD-style 2-D surface: SGTE terminals + polynomial-in-T Redlich-Kister coefficients.
Fits the entropy-removed free energy
H(T, c) = (1-c) f0(T) + c (f0(T)+df(T)) + c(1-c) Σ_v L_v(T) (1-2c)^v
in two stages:
Fit
RedlichKisterindependently at each T in the incoming grid, extracting terminalsf0(T),df(T)and interaction coefficientsL_v(T).Fit the T-dependence: terminals via
SGTE, interaction coefficients viaPolyFit.
slice_at()evaluates these models at T and returns aRedlichKisterInterpolationwith an analytic c-derivative, so the parent phase’s logit-Newton solver uses exact gradients (~1 ms solves).Requires terminal concentrations at c=0 and c=1 in the training data.
- coeff_poly_order: int = 2¶
Polynomial order in T for each interaction coefficient L_v(T).
- fit(T, c, f)[source]¶
Fit a surface from flat arrays of (temperature, concentration, entropy-removed free energy).
- Parameters:
T – 1-D array of temperatures.
c – 1-D array of concentrations (same length as T).
f – 1-D array of entropy-removed free energies H = f_raw + T·S(c).
- Return type:
- Returns:
A
FittedSurfacewhoseslice_at()yields a 1-DInterpolationat any requested temperature.
- num_coeffs: int = 5¶
Number of Redlich-Kister interaction coefficients L_0..L_{n-1}.
- terminal_sgte_order: int = 4¶
SGTE order for the terminal free energies f0(T) and df(T).
- class landau.interpolate.ConcentrationInterpolator[source]¶
Bases:
Interpolator
- class landau.interpolate.FittedSurface[source]¶
Bases:
ABCA fitted 2-D free-energy surface; slices to a 1-D
Interpolationat fixed T.
- class landau.interpolate.Interpolation[source]¶
Bases:
ABCA fitted 1-D interpolation
f(x).Subclasses must be callable.
deriv()returnsf'as anotherInterpolation; the default is a vectorised central difference, and subclasses with a closed form (polynomials, SGTE, Redlich-Kister) override it with the exact derivative.- deriv()[source]¶
Return
f'as anotherInterpolation(numerical by default).- Return type:
- class landau.interpolate.Interpolator[source]¶
Bases:
ABCThis class acts as a factory for an interplation.
Call
fit()to obtain a specific interpolation.Implementations should be hashable and immutable to allow caching in the thermodynamics module.
- class landau.interpolate.NumericalDerivative(func, step=1e-06)[source]¶
Bases:
Interpolationf'by a scale-aware central difference; the generic fallback derivative.A single fixed-step central difference (vectorised, no Python-level loop) rather than
scipy.differentiate.derivative(), which only exists from SciPy 1.15 and would lift the package’sscipy>=1.11.2floor.- Parameters:
func (
Callable) – the interpolation to differentiate.step (
float) – relative step; the absolute step isstep * max(1, |x|).
- class landau.interpolate.PolyFit(nparam, regularizer_strength=1e-08, enforce_curvature=False)[source]¶
Bases:
TemperatureInterpolator,ConcentrationInterpolator- enforce_curvature: bool = False¶
Ensure that the interpolation has negative curvature as expected for thermodynamic potentials.
- nparam: int | Literal['auto']¶
Number of parameters, if “auto” fit a 10 parameter polynomial under L1 and discard parameters <1e-10, then refit.
- regularizer_strength: float = 1e-08¶
Strength of L2-norm regularization.
- class landau.interpolate.PolynomialInterpolation(poly)[source]¶
Bases:
InterpolationA polynomial fit with an analytic (polynomial) derivative.
Wraps a
numpy.poly1dso the poly1d representation (and its descending-power coefficient order) stays an implementation detail;coefficientsexposes them in ascending power order.- property coefficients: ndarray¶
c0 + c1*x + c2*x**2 + ....- Type:
Coefficients in ascending power order
- class landau.interpolate.RedlichKister(nparam)[source]¶
Bases:
ConcentrationInterpolatorFits the “enthalpic” part of a Redlich-Kister expansion, i.e. without the ideal configuration entropy.
- nparam: int¶
- class landau.interpolate.RedlichKisterInterpolation(df, f0, rk_parameters)[source]¶
Bases:
Interpolation- df: float¶
Change in mixing “enthalpy” across composition range.
- f0: float¶
Absolute “enthalpy” at concentration 0
- rk_parameters: ndarray[float]¶
Redlich-Kister parameters.
- class landau.interpolate.SGTE(nparam)[source]¶
Bases:
TemperatureInterpolator- nparam: int¶
- class landau.interpolate.SGTEInterpolation(parameters)[source]¶
Bases:
InterpolationG(T) = T*ln(T)*pl + sum_i p_i*T^iwith the analyticdG/dT.- parameters: tuple[float, ...]¶
- class landau.interpolate.SoftplusFit(n_softplus=2, loss='soft_l1', max_nfev=100, f_scale=1.0)[source]¶
Bases:
ConcentrationInterpolator,TemperatureInterpolatorFits data using a sum of softplus functions for smooth, flexible interpolation.
The softplus function provides numerically stable, smooth approximations suitable for thermodynamic data. This interpolator uses multiple softplus terms to capture complex behavior.
fit()runs a local Levenberg–Marquardt / trust-region fit with analytical Jacobian.- f_scale: float = 1.0¶
Soft margin between inlier and outlier residuals for the robust loss (see
scipy.optimize.least_squares()). Larger values weaken the influence of the robust loss — at the limit it behaves quadratically over the full residual range, equivalent toloss='linear'. Has no effect whenloss='linear'.
- loss: Literal['linear', 'soft_l1', 'huber', 'cauchy', 'arctan'] = 'soft_l1'¶
Loss function for robust fitting.
- max_nfev: int = 100¶
Maximum number of function evaluations for the local fit.
- n_softplus: int = 2¶
Number of softplus terms to fit.
- class landau.interpolate.SoftplusFittedSurface(A, B, C, O, Tm, Ts, wm, ws, cm, cs, monotone_slope=False, signs=None, wmin=0.0)[source]¶
Bases:
FittedSurfaceFitted surface for
SoftplusSurface2DInterpolator.Holds the per-term coefficient polynomials
A/B/C(amplitude pre-activation / slope / knee) and the offset polynomialO, plus the T, 1/T and c normalisations. The amplitude, knee and offset polynomials are in normalised T; the slope ``B`` is a polynomial in normalised 1/T (well sharpness scales with inverse temperature).slice_at()evaluates them at T to produce a_SoftplusSlicewith an analytic c-derivative; the amplitude passes through the softplus linka_i(T) = softplus(polyval(Tn, A[i]))so it is non-negative and the slice is convex in c.Under
SoftplusSurface2DInterpolator.monotone_slope,Bholds the pre-link slope coefficients instead: the slope isb_i(T) = signs[i] * sum_k softplus(B[i,k]) * w^kwithw = max(Wn - wmin, 0), so|b_i(T)|is non-increasing in T.signsandwminare stored forslice_at(); both are unused for the default free polynomial slope.
- class landau.interpolate.SoftplusSurface2DInterpolator(n_softplus=2, a_order=1, b_order=2, c_order=1, shared_knee=False, offset_order=2, loss='linear', f_scale=1.0, max_nfev=4000, method=None, monotone_slope=False)[source]¶
Bases:
SurfaceInterpolatorTemperature-aware 2-D softplus surface
f(T, c).Fits the entropy-removed free energy
H(T, c) = off(T) + sum_i a_i(T) softplus( b_i(T) (cn + c_i(T)) )
as a single nonlinear least-squares problem with an analytic Jacobian.
ccarries the shape;Tonly modulates the coefficients. The amplitudea_i, kneec_iand offset polynomials are in normalisedT; the slope ``b_i`` is a polynomial in normalised 1/T – the well sharpens as the temperature drops, so its inverse-temperature dependence is far better approximated by a low-order polynomial in 1/T than in T (a polynomial in T cannot track the strong sharpening at the cold end over a wide range). Setmonotone_slopeto additionally constrain|b_i(T)|to never reverse – a physically convex well only ever sharpens as it cools.Convexity. The amplitudes are reparametrised through a non-negative link,
a_i(T) = softplus(alpha_i(T))withalpha_i(T)the fitted polynomial, soa_i(T) >= 0at every temperature (training and extrapolated). Each terma_i * softplus(b_i*(cn + c_i))is then convex inc(softplus is convex, affine argument, non-negative weight), so the fittedH(c)is convex by construction – no spurious miscibility gaps, matching the guarantee the bounded 1-DSoftplusFitgives. A plain unconstrained polynomial amplitude could dip negative between knots and break this.Shared knee. Set
shared_kneeto tie every term to one knee polynomialc(T)instead of fittingn_softplusindependent ones – the knee still moves with T, it’s just the same function for every term. This matches a single well built from an opposite-slope softplus pair, which is already what the per-slice seed assumes (see_smoothv_seed()); turning it on removes degrees of freedom the seed was not using anyway.Avoiding bad minima. The coupled fit is multimodal – the sharp V-wells that motivate softplus sit in a flat-gradient landscape where a single optimisation easily stalls with the knee or branch slopes badly placed. Two slices – the coldest (sharpest, most structure) and the central – are each given a convex-well-aware per-slice seed (the knee taken from the data minimum, an opposite-slope softplus pair seeding the V; see
_fit_slice()) rather than a generic quantile guess. The two seeds are then raced: each gets a short coupled polish and only the better-converging one is finished (a full solve restarted from that seed), so a bad basin is avoided without paying to converge both. Seeding cost is independent of how many temperatures are sampled (hundreds in practice).Solver. By default the coupled solve picks its least-squares method by scipy version (see
_LM_HAS_INTERNAL_SCALING); setmethodto'lm'or'trf'to override. scipy 1.16 switched the default variable scaling ofleast_squares(method='lm')to MINPACK’s internal scaling (mode=1), matching what plainleastsqalways did – see gh-22790 and the bug it fixed, gh-19459 over on gh/scipy. Before 1.16,lmsilently disabled that scaling (mode=2with an all-onesdiag) and converged poorly on the badly conditioned sharp-knee Jacobian. So on scipy >= 1.16 the solve defaults tolm(no per-iteration SVD, faster); on older scipy it defaults to the trust-region solver with an explicitx_scale='jac'. A robustlossalways usestrf(lmsupports only the linear loss). The residual and Jacobian are evaluated together (_model_and_jac()) so the per-termsoftplus/sigmoidare computed once per point. Most of the speed-up is from doing less redundant work – racing the seeds instead of converging both, capping the per-slice seed, and the shared evaluation.Unlike
CalphadSurface2DInterpolatorthere is no terminal-phase requirement, so this works for narrow intermetallics as well as solution phases; and unlikeWhitneySurface2DInterpolatorthe fixed-T slice carries an analytic c-derivative, so the parent phase’s logit-Newton solver uses exact gradients.- a_order: int = 1¶
Polynomial order in T for the amplitudes a_i(T).
- b_order: int = 2¶
Polynomial order in 1/T for the slopes b_i(T) (the strongest T-dependence).
- c_order: int = 1¶
Polynomial order in T for the knee positions c_i(T).
- f_scale: float = 1.0¶
Soft margin between inlier and outlier residuals for a robust
loss; ignored whenloss='linear'.
- fit(T, c, f)[source]¶
Fit a surface from flat arrays of (temperature, concentration, entropy-removed free energy).
- Parameters:
T – 1-D array of temperatures.
c – 1-D array of concentrations (same length as T).
f – 1-D array of entropy-removed free energies H = f_raw + T·S(c).
- Return type:
- Returns:
A
FittedSurfacewhoseslice_at()yields a 1-DInterpolationat any requested temperature.
- loss: Literal['linear', 'soft_l1', 'huber', 'cauchy', 'arctan'] = 'linear'¶
Loss for the surface fit (see
scipy.optimize.least_squares()). The default islinear– the well bottom is the signal, not an outlier, so a robust loss that down-weights large residuals is counter-productive here. Set a robust loss only for data with genuine outlier points.
- max_nfev: int = 4000¶
Maximum function evaluations per start of the surface fit.
- method: Literal['lm', 'trf'] | None = None¶
Least-squares solver for the coupled fit.
None(default) picks by scipy version:'lm'on scipy >= 1.16 (faster, no per-iteration SVD) and'trf'on older scipy, where'lm'lacks variable scaling and converges poorly on stiff data (see_LM_HAS_INTERNAL_SCALING). Force'lm'or'trf'to override the default;'lm'requiresloss='linear'and, on scipy < 1.16, still converges poorly on sharp-knee data.
- monotone_slope: bool = False¶
Constrain every term’s slope magnitude
|b_i(T)|to be non-increasing inT– a convex well only ever sharpens as it cools, so the fitted sharpness should never reverse. WhenTruethe slope is reparametrised asb_i(T) = s_i * [ softplus(B_i0) + sum_{k>=1} softplus(B_ik) * w^k ],w = max((1/T) - min_train(1/T), 0),with the branch sign
s_ifixed from the per-slice seed. Eachsoftplus(B_ik) >= 0andw^kis non-decreasing in1/T, so|b_i|is non-decreasing in1/T(= non-increasing inT) by construction at every temperature, training and extrapolated – themax(., 0)holds the magnitude flat forThotter than the training range rather than letting an even-power term turn it back up. Softplus (not sigmoid) is the positivity link: it is unbounded above and keeps the solve unconstrained, so the defaultlmfast path is retained and the slope is free to grow at the cold end (a bounded sigmoid link caps the slope and converges worse on sharp wells). The branch signs_iis frozen from the per-slice seed and not refined by the coupled solve – reliable for the intended convex-V wells (the opposite-slope seed yields[+, -]), but a near-degenerate seed slice could lock in a wrong sign. The defaultFalseleavesb_i(T)a free polynomial in1/T.
- n_softplus: int = 2¶
Number of softplus terms.
- offset_order: int = 2¶
Polynomial order in T for the vertical offset off(T).
When True, every softplus term shares a single knee polynomial c(T) instead of fitting one independently per term – the knee can still vary with T, it is just the same function for all
n_softplusterms. This is the constraint the smooth-V seed already assumes for an opposite-slope term pair (_smoothv_seed()seeds identical knees), so turning it on only removes degrees of freedom the seed was not using anyway. Reduces the knee parameter count fromn_softplus * (c_order + 1)toc_order + 1.
- class landau.interpolate.SplineFit(degree=3, smoothing=0.0)[source]¶
Bases:
ConcentrationInterpolatorFits data with a univariate B-spline of degree
degree.Wraps
scipy.interpolate.UnivariateSpline.smoothingselects between strict interpolation (0.0, the default — the spline passes through every sample) and least-squares smoothing (> 0— the spline may miss the samples by a total squared residual ofsmoothingin exchange for a smoother curve).Nonedefers to scipy’s own default (sequal to the number of samples).Outside
[min(x), max(x)]the fit continues the boundary spline segment (scipy’s defaultext=0):degree=1extends linearly, while higher degrees follow the edge polynomial and can diverge quickly, so extrapolated values should be treated with care.- degree: int = 3¶
Spline degree
k(3 = cubic). Clamped tolen(x) - 1so a fit with fewer samples thandegree + 1drops to the highest degree the data supports.
- smoothing: float | None = 0.0¶
Smoothing factor
sforUnivariateSpline.0.0interpolates strictly; larger values trade fidelity for smoothness;Noneuses scipy’s default.
- class landau.interpolate.StitchedFit(interpolating=SGTE(nparam=4), low=None, upp=PolyFit(nparam=2, regularizer_strength=1e-08, enforce_curvature=False), edge=10)[source]¶
Bases:
TemperatureInterpolatorAn interpolator with more control over the extrapolation regions.
- edge: int = 10¶
- interpolating: TemperatureInterpolator = SGTE(nparam=4)¶
- low: TemperatureInterpolator | None = None¶
- upp: TemperatureInterpolator | None = PolyFit(nparam=2, regularizer_strength=1e-08, enforce_curvature=False)¶
How many samples near the edges to use to fit the extrapolating interpolator.
- class landau.interpolate.SurfaceInterpolator[source]¶
Bases:
ABCFits a 2-D free-energy surface f(T, c) from flat scattered (T, c, H) samples.
H = f_raw + T·S(c)is the entropy-removed free energy (the quantity the parent phase solver operates on). Implementations should be hashable (frozen dataclasses satisfy this) so they can be stored as fields inSurface2DInterpolatingPhase.- abstractmethod fit(T, c, f)[source]¶
Fit a surface from flat arrays of (temperature, concentration, entropy-removed free energy).
- Parameters:
T (
ArrayLike) – 1-D array of temperatures.c (
ArrayLike) – 1-D array of concentrations (same length as T).f (
ArrayLike) – 1-D array of entropy-removed free energies H = f_raw + T·S(c).
- Return type:
- Returns:
A
FittedSurfacewhoseslice_at()yields a 1-DInterpolationat any requested temperature.
- class landau.interpolate.TemperatureInterpolator[source]¶
Bases:
Interpolator
- class landau.interpolate.WhitneyFittedSurface(rbf)[source]¶
Bases:
FittedSurfaceFitted surface for
WhitneySurface2DInterpolator.Wraps a
WhitneyRBFInterpolatortrained over the 2-D (T, c) space.slice_at()returns a closure at fixed T wrapped in_CallableInterpolation; the c-derivative is a numerical central difference (inherited fromInterpolation).
- class landau.interpolate.WhitneySurface2DInterpolator(kernel='thin_plate_spline', smoothing=0.001, degree=2, epsilon=1.0, grad_eps=0.0001)[source]¶
Bases:
SurfaceInterpolator2-D free-energy surface backed by a Whitney-extended RBF.
Trains a single
WhitneyRBFInterpolatorover all scattered (T, c, H) samples at once; slicing at a fixed T returns a closure with numerical c-derivatives (via the inheritedderiv()).Unlike
CalphadSurface2DInterpolator, there is no terminal-phase requirement, so this works for narrow intermetallics as well as solution phases. The trade-off is slower per-(T, mu) solves (numerical derivative) and a need fortemperature_rangeto span the solve grid to keep query points inside the RBF’s convex hull (fast batched path).- degree: int = 2¶
- epsilon: float = 1.0¶
- fit(T, c, f)[source]¶
Fit a surface from flat arrays of (temperature, concentration, entropy-removed free energy).
- Parameters:
T – 1-D array of temperatures.
c – 1-D array of concentrations (same length as T).
f – 1-D array of entropy-removed free energies H = f_raw + T·S(c).
- Return type:
- Returns:
A
FittedSurfacewhoseslice_at()yields a 1-DInterpolationat any requested temperature.
- grad_eps: float = 0.0001¶
- kernel: str = 'thin_plate_spline'¶
- smoothing: float = 0.001¶
- class landau.interpolate.WhitneyTemperatureInterpolator(kernel='thin_plate_spline', smoothing=0.0, degree=2, epsilon=1.0, grad_eps=0.0001)[source]¶
Bases:
TemperatureInterpolatorA
TemperatureInterpolatorbacked byWhitneyRBFInterpolator.Fits a 1-D Whitney RBF on temperature data and returns a callable that extrapolates smoothly outside the training range.
Warning
Fitting this interpolator can be very slow, especially when taking data e.g. from calphy as is. It’s better to pre-smooth raw free energies with a plain interpolator or sub set data and then feeding it to this class.
- Parameters:
kernel (
str)smoothing (
float)degree (
int)epsilon (
float)grad_eps (
float)
- degree: int = 2¶
- epsilon: float = 1.0¶
- fit(T, y)[source]¶
Fit the interpolator.
- Parameters:
T ((N,) array-like — temperature values)
y ((N,) array-like — target values)
- Returns:
callable
- Return type:
(M,) ndarray -> (M,) ndarray
- grad_eps: float = 0.0001¶
- kernel: str = 'thin_plate_spline'¶
- smoothing: float = 0.0¶