Fourier Surface
Added in version 2.0.0.
With surface_method='fourier', MembraneCurvature fits a smooth periodic
surface to atomistic or bead height data using a truncated 2D Fourier series. The Fourier method comprises six steps:
- Choose a non-redundant set of Fourier modes:
- Compute the corresponding wave numbers:
- Build a design matrix from cosine and sine basis functions
- Solve the least-squares problem for the Fourier coefficients
- Reconstruct the continuous surface on a grid:
_eval_fourier_surface()withderivatives=False
- Evaluate partial derivatives analytically:
_eval_fourier_surface()withderivatives=True, or
Public entry points fourier_height_from_atoms() and
fourier_height_derivatives_from_atoms() chain these steps on bin centres.
Note that the use of a non-redundant set of Fourier modes is justified by the conjugate symmetry of Fourier coefficients for real-valued functions:
We therefore keep only the non-redundant half of the mode grid. The \(m = 0\) row has the additional symmetry
so we keep only \(n \ge 0\) there and store the mean term \(A_{00}\) separately.
A diagram of a non-redundant Fourier mode grid looks like:
n
^
N | ✓ | ✓ ✓ ✓ ... ✓ |
: | ✓ | ✓ ✓ ✓ ... ✓ |
2 | ✓ | ✓ ✓ ✓ ... ✓ |
1 | ✓ | ✓ ✓ ✓ ... ✓ |
0 | A00 | ✓ ✓ ✓ ... ✓ | <- right block includes n=0; left cell is A00
-1 | x | ✓ ✓ ✓ ... ✓ |
-2 | x | ✓ ✓ ✓ ... ✓ |
: | x | ✓ ✓ ✓ ... ✓ |
-N | x | ✓ ✓ ✓ ... ✓ |
+-----+-------------------+--> m
0 1 2 3 ... M
^ ^
| |
modes_with_ modes_with_
m_equal_zero m_positive
(n=1..N only) (all n, -N..N)
where the ✓ symbol denotes that the wave includes 2 parameters each: \(cos\), and \(sin\) coefficients, and where the x symbols denotes that the wave is excluded to avoid redundancy by \(F(0,-n) = F(0,n)^*\), only occurs at \(m=0\).
The associated wavevector for each mode is
\[\mathbf{k}_{mn} = \left(\frac{2\pi m}{L_x}, \frac{2\pi n}{L_y}\right)\]
Important
The simulation box defines the fundamental periodic domain and therefore sets the largest allowed wavelength in the system. The surface is represented as a sum of periodic waves that are compatible with these boundary conditions, meaning all basis functions must fit exactly within the box without discontinuities at the boundaries.
As a result, only discrete Fourier modes are allowed, corresponding to integer wave numbers:
with \(m, n \in \mathbb{Z}\).
The box size \(L_x\) and \(L_y\) therefore determines the spacing of allowed wavevectors, while higher modes represent progressively shorter wavelengths.
For more details on the Fourier method, refer to the 2.1 Fourier method section in the Algorithm page.
References
The Fourier method in MembraneCurvature uses the Fourier surface modeling [CAG2009] and molecular Fourier shape descriptors [JMG1988] as conceptual references:
Shen et al., Fourier method for large-scale surface modeling and registration, Computers & Graphics (2009), doi: 10.1016/j.cag.2009.03.002.
Leicester et al., Description of molecular surface shape using Fourier descriptors, Journal of Molecular Graphics (1988), doi: 10.1016/0263-7855(88)85008-2.
Functions
- membrane_curvature.fourier_surface._bin_centre_mesh(Lx: float, Ly: float, n_x_bins: int, n_y_bins: int) tuple[ndarray, ndarray][source]
Relative-coordinate mesh at bin centres for surface reconstruction (step 5).
Bin centres lie at
(i + 1/2) L / n_binsfor each axis, compatible with the periodic domain[0, L).- Parameters:
Lx (float) – Period length in \(x\).
Ly (float) – Period length in \(y\).
n_x_bins (int) – Number of bins along \(x\).
n_y_bins (int) – Number of bins along \(y\).
- Returns:
X_rel (ndarray) – Mesh of relative \(x\) coordinates, shape
(n_x_bins, n_y_bins).Y_rel (ndarray) – Mesh of relative \(y\) coordinates, same shape as
X_rel.
- Raises:
ValueError – If
n_x_binsorn_y_binsis not a positive integer. The public entry points (fourier_height_from_atoms(),fourier_height_derivatives_from_atoms(),MembraneCurvature) already validate viavalidate_positive_bin_counts().
- membrane_curvature.fourier_surface._build_fourier_matrix(x_positions: ndarray, y_positions: ndarray, box_length_x: float, box_length_y: float, fourier_modes: list[tuple[int, int]]) ndarray[source]
Build the least-squares design matrix: basis functions evaluated at sample points.
Implements step 3 (and uses
_compute_wavevector()for step 2 inside the loop). Each row is an atom position; columns are1, thencos(k\cdot r),sin(k\cdot r)pairs for each mode, so that fitting recovers \(z(x,y)\) at the atoms.In the matrix, each row corresponds to a spatial point \((x_i, y_i)\), and each column corresponds to a basis function:
\[[1, cos(k·r), sin(k·r), cos(k·r), sin(k·r), ...]\]where the wave vectors are given by \(k=(2\pi m/L_x, 2\pi n/L_y)\).
- Parameters:
x_positions (ndarray) – \(x\) coordinates of points within one periodic box (same shape as
y_positions).y_positions (ndarray) – \(y\) coordinates of points within one periodic box.
box_length_x (float) – Period length \(L_x\).
box_length_y (float) – Period length \(L_y\).
fourier_modes (list of tuple of int) – Non-redundant
(m, n)indices, e.g. fromfourier_mode_list().
- Returns:
design_matrix – Basis evaluated at each point;
dtypeisnumpy.float64.- Return type:
ndarray, shape (n_points, 1 + 2 * n_modes)
- membrane_curvature.fourier_surface._compute_wavevector(mode_x, mode_y, kx_base, ky_base)[source]
Wavevector components for a single mode (step 2 of the module workflow).
Builds \((k_x, k_y) = (m\, k_{x,\mathrm{base}}, n\, k_{y,\mathrm{base}})\) used in the phase \(k_x x + k_y y\) for that mode.
- Parameters:
mode_x (int) – Mode index \(m\).
mode_y (int) – Mode index \(n\).
kx_base (float) – Reciprocal step \(2\pi / L_x\).
ky_base (float) – Reciprocal step \(2\pi / L_y\).
- Returns:
kx (float) –
mode_x * kx_base.ky (float) –
mode_y * ky_base.
- membrane_curvature.fourier_surface._eval_fourier_surface(X_rel: ndarray, Y_rel: ndarray, Lx: float, Ly: float, A00: float, coeffs: dict[tuple[int, int], tuple[float, float]], *, derivatives: Literal[False]) tuple[ndarray][source]
- membrane_curvature.fourier_surface._eval_fourier_surface(X_rel: ndarray, Y_rel: ndarray, Lx: float, Ly: float, A00: float, coeffs: dict[tuple[int, int], tuple[float, float]], *, derivatives: Literal[True]) tuple[ndarray, ndarray, ndarray, ndarray, ndarray, ndarray]
Evaluate the fitted Fourier sum on a relative-coordinate grid (steps 5-6).
Step 5: reconstruct \(Z(x,y) = A_{00} + \sum_{(m,n)} A_{mn}\cos\phi_{mn} + B_{mn}\sin\phi_{mn}\) with \(\phi_{mn} = k_x x + k_y y\). Step 6 (optional): accumulate first and second partial derivatives for Monge-gauge curvature using the analytic derivatives of the same sum.
- Parameters:
X_rel (ndarray) – Relative \(x\) coordinates on the periodic domain (same shape as
Y_rel).Y_rel (ndarray) – Relative \(y\) coordinates on the periodic domain.
Lx (float) – Period length in \(x\) (same units as
X_rel).Ly (float) – Period length in \(y\).
A00 (float) – Mean term \(A_{00}\).
coeffs (dict[tuple[int, int], tuple[float, float]]) – Mode amplitudes
(A, B)from_fourier_fit_from_atoms()/_unpack_coefficients().derivatives (bool) – If
False, return(Z,). IfTrue, return(Z, fx, fy, fxx, fyy, fxy).
- Returns:
If
derivativesisFalse, the 1-tuple(Z,)whereZmatches the shape ofX_rel. IfderivativesisTrue, the 6-tuple with each grid matchingX_rel.- Return type:
tuple of ndarray
Notes
The
derivatives=Falsebranch returns(Z,)— a 1-tuple, not a barendarray— to keep a single return type (tuple[ndarray, ...]) regardless of the flag. Callers therefore unwrap with[0].
- membrane_curvature.fourier_surface._fourier_fit_from_atoms(positions: ndarray, x_range: tuple[float, float], y_range: tuple[float, float], M: int, N: int, rcond: float | None = None) tuple[float, float, float, dict[tuple[int, int], tuple[float, float]]][source]
Fit Fourier coefficients to atom heights (steps 1-4; no bin grid).
Builds the design matrix, solves for \(A_{00}\) and the mode amplitudes, and returns domain sizes and coefficients for subsequent grid evaluation (steps 5-6).
- Parameters:
positions (ndarray, shape (n_atoms, 3)) – Atom coordinates; the third column (
positions[:, 2]) is the height \(z\).x_range, y_range (tuple of float) –
(min, max)extents of the periodic domain.M, N (int) – Maximum Fourier mode indices (see
fourier_mode_list()).rcond (float or None, optional) – Relative cutoff for small singular values in
_solve_design_least_squares_svd().
- Returns:
Lx (float) – Period length \(L_x = x_{\max} - x_{\min}\).
Ly (float) – Period length \(L_y = y_{\max} - y_{\min}\).
A00 (float) – Mean height coefficient \(A_{00}\).
coeffs (dict[tuple[int, int], tuple[float, float]]) – Mapping
(m, n) -> (A, B)cosine and sine coefficients per mode.
- Raises:
ValueError – If
positionscannot be converted to a float64 NumPy array, or has the wrong shape (seevalidate_positions()); ifx_range/y_rangehave non-positive width (seevalidate_positive_domain_widths()); or ifM/Nare negative (seefourier_mode_list()).- Warns:
UserWarning – If the design matrix is rank-deficient or underdetermined (same condition as
fourier_height_from_atoms()andfourier_height_derivatives_from_atoms()).
- membrane_curvature.fourier_surface._harmonic_height_and_phi_derivatives(A: float, B: float, cos_phase: ndarray, sin_phase: ndarray) tuple[ndarray, ndarray, ndarray][source]
Height and \(\phi\)-derivatives for one mode \(h(\phi)=A\cos\phi+B\sin\phi\).
Used internally by
_eval_fourier_surface()to accumulatefx,fy,fxx,fyy,fxyusing the chain rule. For example, \(\partial_x h = (\partial_\phi h)\, \partial_x \phi\).- Parameters:
A, B (float) – Cosine and sine coefficients for this mode.
cos_phase (ndarray) –
cos(\phi), same shape assin_phase.sin_phase (ndarray) –
sin(\phi).
Notes
This helper returns derivatives with respect to phase \(\phi\) (
h_phi,h_phiphi), not spatial derivatives. Spatial derivatives (fx,fy,fxx,fyy,fxy) are accumulated in_eval_fourier_surface()via the chain rule using \(k_x\) and \(k_y\).See also
_eval_fourier_surface()Consumes these intermediate terms to accumulate
fx,fy,fxx,fyy, andfxy.
- Returns:
h (ndarray) – Contribution \(A\cos\phi + B\sin\phi\).
h_phi (ndarray) – Intermediate \(\partial h/\partial \phi\) used for
fxandfyaccumulation.h_phiphi (ndarray) – Intermediate \(\partial^2 h/\partial \phi^2\) used for
fxx,fyy,fxyaccumulation.
- membrane_curvature.fourier_surface._solve_design_least_squares_svd(design_matrix: ndarray, targets: ndarray, rcond: float | None = None) tuple[ndarray, int, ndarray][source]
Least-squares solution with
numpy.linalg.svd()and singular-value truncation.Solves
min ||design_matrix @ theta - targets||_2with a relative singular-value cutoffrcond(values \(s \le rcond \cdot s_{\max}\) are treated as zero). When the system is rank-deficient, returns the minimum Euclidean-norm coefficient vector among least-squares minimizers.- Parameters:
design_matrix (ndarray, shape (n_rows, n_columns)) – Design matrix with rows corresponding to atom positions and columns corresponding to the basis functions.
targets (ndarray, shape (n_rows,)) – Target heights.
rcond (float or None, optional) – Relative cutoff for small singular values.
Noneuses a heuristic cutoff based on machine precision and the problem size.
- Returns:
theta (ndarray, shape (n_columns,)) – Fitted coefficients.
rank (int) – Effective rank after truncation.
singular_values (ndarray) – Singular values of
design_matrix... note:: – Here rank is the number of singular values greater than the cutoff. The number of columns in the design matrix is the number of parameters.
- membrane_curvature.fourier_surface._unpack_coefficients(theta: ndarray, modes: list[tuple[int, int]]) tuple[float, dict[tuple[int, int], tuple[float, float]]][source]
Split the least-squares coefficient vector into the mean and per-mode amplitudes.
Used after step 4 (
_solve_design_least_squares_svd()in_fourier_fit_from_atoms()). Index0is \(A_{00}\); remaining entries are cosine/sine pairs inmodesorder.- Parameters:
theta (ndarray, shape (n_coefficients,)) – Least-squares solution vector: mean at index 0, then pairs
(A_{mn}, B_{mn})in the same order asmodes.modes (list of tuple of int) – Mode list from
fourier_mode_list()(or equivalent ordering).
- Returns:
A00 (float) – Constant (mean) term.
coeffs (dict) – Mapping
(m, n) -> (A, B)cosine and sine coefficients per mode.
- membrane_curvature.fourier_surface.fourier_height_derivatives_from_atoms(positions: ndarray, x_range: tuple[float, float], y_range: tuple[float, float], n_x_bins: int, n_y_bins: int, M: int, N: int, rcond: float | None = None) tuple[ndarray, ndarray, ndarray, ndarray, ndarray, ndarray][source]
Fourier height and analytic partial derivatives on bin centres (steps 1-6).
Same pipeline as
fourier_height_from_atoms(), but also performs step 6 by requesting analytic derivatives from_eval_fourier_surface()withderivatives=True.- Parameters:
positions (ndarray, shape (n_atoms, 3)) – Atom coordinates; the third column (
positions[:, 2]) is the height \(z\).x_range, y_range (tuple of float) –
(min, max)domain extents.n_x_bins, n_y_bins (int) – Output grid size at bin centres.
M, N (int) – Maximum Fourier mode indices; see
fourier_mode_list().rcond (float or None, optional) – Relative cutoff for small singular values in
_solve_design_least_squares_svd().
- Returns:
Z, fx, fy, fxx, fyy, fxy – Each array has shape
(n_x_bins, n_y_bins).- Return type:
ndarray
- Raises:
ValueError – If
n_x_binsorn_y_binsis not a positive integer (seevalidate_positive_bin_counts()); ifpositionscannot be converted to a float64 array or has the wrong shape (seevalidate_positions()); ifx_range/y_rangehave non-positive width (seevalidate_positive_domain_widths()); or ifM/Nare negative.- Warns:
UserWarning – If the least-squares system is rank-deficient or underdetermined.
- membrane_curvature.fourier_surface.fourier_height_from_atoms(positions: ndarray, x_range: tuple[float, float], y_range: tuple[float, float], n_x_bins: int, n_y_bins: int, M: int, N: int, rcond: float | None = None) ndarray[source]
Fourier height field on bin centres (module workflow steps 1-5).
Runs steps 1-4 via
_fourier_fit_from_atoms(), then step 5 via_bin_centre_mesh()and_eval_fourier_surface()withderivatives=False.- Parameters:
positions (ndarray, shape (n_atoms, 3)) – Atom coordinates in the same length unit as the box; the third column (
positions[:, 2]) is the height \(z\).x_range, y_range (tuple of float) –
(min, max)domain extents.n_x_bins, n_y_bins (int) – Output grid size at bin centres.
M, N (int) – Maximum Fourier mode indices; see
fourier_mode_list().rcond (float or None, optional) – Relative cutoff for small singular values in
_solve_design_least_squares_svd().
- Returns:
Z – Fitted surface height.
- Return type:
ndarray, shape (n_x_bins, n_y_bins)
- Raises:
ValueError – If
n_x_binsorn_y_binsis not a positive integer (seevalidate_positive_bin_counts()); ifpositionscannot be converted to a float64 array or has the wrong shape (seevalidate_positions()); ifx_range/y_rangehave non-positive width (seevalidate_positive_domain_widths()); or ifM/Nare negative.- Warns:
UserWarning – If the least-squares system is rank-deficient or underdetermined.
- membrane_curvature.fourier_surface.fourier_mode_list(M: int, N: int) list[tuple[int, int]][source]
Non-redundant 2D Fourier modes for a real-valued function on a periodic domain.
Implements step 1 of the module workflow (non-redundant mode grid).
Modes are \((m, n)\) where:
\(m=1..M\), \(n=-N..N\) (all n retained, no symmetry to exploit)
\(m=0\), \(n=1..N\) only (\(F(0,-n) = F(0,n)^*\) makes n<0 redundant)
The \((0,0)\) mean is excluded and handled separately as \(A_{00}\).
- membrane_curvature.fourier_surface.n_fourier_parameters(M: int, N: int) int[source]
Total number of scalar Fourier parameters for the truncated series.
Uses the same mode list as step 1 (
fourier_mode_list()): one scalar parameter for the mean term \(A_{00}\) and two parameters per non-zero mode (cosine and sine coefficients), son = 1 + 2 * len(fourier_mode_list(M, N)).Warning
Use \(M = N = 2\) unless you have a clear reason to resolve shorter wavelengths; always ensure your leaflet has many more atoms than Fourier parameters (
n_fourier_parameters()), and increase \(M\) and \(N\) only until curvature stops changing systematically and starts chasing noise.- Parameters:
M (int) – Maximum positive mode index in \(x\) (see
fourier_mode_list()).N (int) – Maximum mode index magnitude in \(y\).
- Returns:
n – Total number of scalar Fourier parameters
1 + 2 * n_modes.- Return type: