API Reference¶
Solver¶
High-level solver API for moire relaxation.
Typical usage:
from moire_metrology import RelaxationSolver
from moire_metrology.interfaces import GRAPHENE_GRAPHENE
solver = RelaxationSolver()
result = solver.solve(
moire_interface=GRAPHENE_GRAPHENE,
theta_twist=2.0,
)
result.plot_stacking()
- class moire_metrology.solver.SolverConfig(method='trust-ncg', max_iter=200, gtol=1e-06, rtol=0.0001, etol=1e-06, etol_window=5, pixel_size=0.2, n_scale=1, display=True, min_mesh_points=100, beta=1.0, dt0=None, linear_solver='direct', linear_solver_tol=1e-06, linear_solver_maxiter=200, elastic_strain='cauchy', max_iter_discover=300, gtol_discover_factor=10.0)[source]¶
Bases:
objectConfiguration for the relaxation solver.
- Parameters:
method (str) –
Optimization method.
'newton'– Levenberg-Marquardt damped Newton with a modified per-vertex GSFE Hessian (negative eigenvalues are flipped so the assembled Hessian is positive-definite by construction). Fast and well-behaved for nearly-convex problems (typical at moderate-to-high twist angles), but by design cannot follow negative-curvature directions and therefore cannot cross saddles between basins. In multi-basin landscapes (e.g. low-twist TDBG with significant AB/BA asymmetry, where single-DW and 2DW topologies are both stationary states) the eigenvalue flipping causes the iterate to commit early to whichever basin is locally convex-nearest, with no recourse to escape that basin later. Not recommended as a default; kept available for high-twist / near-convex problems where its fast quadratic convergence is desirable and saddle-crossing is moot.'trust-ncg'(default) – True trust-region Newton-CG viascipy.optimize.minimize(Steihaug-Toint inner solver) using the true (unmodified) sparse Hessian via matrix-free Hessian- vector products. Detects negative-curvature directions and steps to the trust-region boundary along them — the property that lets it cross saddles, which the'newton'LM-modified path cannot. Good general-purpose default; supports the same mean-constraint API as the other methods via null-space projection. Caveat: although saddle-crossing is available, from a generic IC like U=0 the inner CG’s quadratic-step preference still biases toward whichever basin is locally quadratic-nearest. For deliberate basin discovery from U=0 in low-twist problems, prefer'L-BFGS-B'or'two_phase'.'pseudo_dynamics'– implicit theta-method on the gradient flow dU/dt = -nabla E. At each step solves (M + beta*dt*H)*dU = -dt*grad with adaptive dt and energy-monitored step rejection. More robust than Newton on stiff/multi-layer systems. Ports the algorithm used in the paper MATLAB code.'L-BFGS-B'– Gradient-only quasi-Newton via scipy.optimize.minimize. Slower convergence but no Hessian needed. Supports the same mean-constraint API as'newton'/'trust-ncg'via null-space projection. Empirically lands in qualitatively different basins than'trust-ncg'from the same U=0 initial guess in multi-basin landscapes (e.g. low-twist TDBG with AB/BA asymmetry); use this for discovery when you suspect the U=0 IC sits near a saddle.'two_phase'– L-BFGS-B discovery (usingmax_iter_discoverand gradient tolerancegtol_discover_factor * gtol) followed bytrust-ncgpolish (using the standardmax_iter/gtol/rtol/etolcriteria) from the discovery output. Combines L-BFGS-B’s basin-selection behaviour with trust-ncg’s tight 2nd-order convergence. Recommended for low-twist relaxation when both basin choice and high precision matter.
max_iter (int) – Maximum number of optimizer iterations.
gtol (float) – Absolute gradient norm tolerance for convergence. Convergence is declared when
|grad| < gtol. For L-BFGS-B this is passed to scipy as thegtoloption (max-norm threshold).rtol (float) – Relative gradient tolerance. Convergence is declared when
|grad| / |grad_initial| < rtol, i.e. the gradient has dropped by this factor from its initial value. This criterion matters at low twist angles where the absolute gradient norm at the energy minimum can be O(1-10) due to large total energies, making any reasonable absolute gtol unreachable. For L-BFGS-B the check is applied post-hoc after scipy returns.etol (float) – Energy stagnation tolerance. If the fractional energy improvement over the last
etol_windowaccepted steps falls belowetol, convergence is declared (newton and pseudo_dynamics only).etol_window (int) – Number of recent iterations over which to measure energy stagnation.
pixel_size (float) – Target mesh element size in nm.
n_scale (int) – Number of moire unit cells in each direction.
display (bool) – If True, print progress during optimization.
min_mesh_points (int) – Floor on the mesh grid resolution per direction. Default 100.
beta (float) – Theta-method parameter for the ‘pseudo_dynamics’ solver. 0 = explicit Euler (unstable), 1 = fully implicit backward Euler (default, unconditionally stable). Values in [0.5, 1.0] are stable.
dt0 (float or None) – Initial pseudo-time step for the ‘pseudo_dynamics’ solver. If None, chosen automatically as ~1/(K1+G1) so that dt0·|H| ~ 1.
linear_solver (str) –
Linear-system solver used inside each pseudo_dynamics step.
'direct'(default) – build the sparse Hessian explicitly and call spsolve. Per-iteration cost is the LU factorization, which scales like O(n_sol^1.5) on 2D meshes. Best for small/moderate problems.'iterative'– matrix-free preconditioned MINRES, using only Hessian-vector products and a Jacobi preconditioner from the diagonal of the constant elastic Hessian. Much faster per iteration on large problems (n_sol >> 10^4). Uses MINRES (not CG) so it stays robust when the operator is symmetric but indefinite.
linear_solver_tol (float) – Relative-residual tolerance for the iterative linear solver. Only used when linear_solver=’iterative’. Default 1e-6.
linear_solver_maxiter (int) – Max iterations for the iterative linear solver per pseudo_dynamics step. Default 200. The outer pseudo_dynamics loop will rebuild the operator and try again with a smaller dt if MINRES fails to converge.
elastic_strain (str)
max_iter_discover (int)
gtol_discover_factor (float)
- class moire_metrology.solver.RelaxationSolver(config=None)[source]¶
Bases:
objectSolver for atomic relaxation in twisted 2D heterostructures.
- Parameters:
config (SolverConfig | None)
- solve(moire_interface=None, *, top_interface=None, bottom_interface=None, n_top=1, n_bottom=1, theta_twist=0.0, delta=None, theta0=0.0, initial_solution=None, constraints=None, fix_top=False, fix_bottom=False, pin_mean=False, mesh=None, mean_constraints=None, **legacy_kwargs)[source]¶
Solve the relaxation problem.
- Parameters:
moire_interface (Interface) – The twisted A-B interface between the bottom layer of the top flake and the top layer of the bottom flake. Carries both materials (
moire_interface.bottomis the bottom-flake material,moire_interface.topis the top-flake material) as well as the GSFE coefficients for their stacking.top_interface (Interface, optional) – Required iff
n_top > 1. The homobilayer interface used between successive layers within the top flake. Must havebottom == top == moire_interface.top.bottom_interface (Interface, optional) – Required iff
n_bottom > 1. The homobilayer interface used between successive layers within the bottom flake. Must havebottom == top == moire_interface.bottom.n_top (int) – Number of layers in each flake.
n_bottom (int) – Number of layers in each flake.
theta_twist (float) – Twist angle in degrees.
delta (float or None) – Lattice mismatch. If None, computed from the two materials.
theta0 (float) – Lattice orientation angle in degrees.
initial_solution (ndarray or None) – Initial guess. If None, starts from zero.
constraints (PinnedConstraints or None) – If set, pins certain DOFs to fixed displacements while optimizing the rest. Build via PinningMap.build_constraints(). Mutually exclusive with fix_top / fix_bottom.
fix_top (bool) – Pin all DOFs of the topmost layer (top of the top flake) to zero. Use this to clamp the upper free surface of the heterostructure and approximate a semi-infinite top.
fix_bottom (bool) – Pin all DOFs of the bottommost layer (bottom of the bottom flake) to zero. Use this to clamp the substrate and approximate a semi-infinite bottom — typical for simulating a twisted flake on a thick substrate (e.g. graphene on graphite).
mesh (MoireMesh or None) – Pre-built mesh to use. If None (default), the solver builds a periodic moire-cell mesh from the SolverConfig parameters (pixel_size, n_scale, min_mesh_points). Pass a mesh built via
moire_metrology.mesh.generate_finite_mesh(or any other MoireMesh constructor) to relax on a finite, non-periodic domain — typically combined withconstraintsfromPinningMap.build_constraintsto pin selected stacking sites in the experimental image.pin_mean (bool)
mean_constraints (list | None)
- Return type:
Notes
Internally the solver still uses the legacy “stack 1” / “stack 2” numbering, where stack 1 is the top flake and stack 2 is the bottom flake. This is an implementation detail of the construction layer; the public API is in terms of
moire_interface.top/moire_interface.bottomand then_top/n_bottomflake sizes. The translation happens once at the top of this method.
Result¶
Result container for moire relaxation calculations.
- class moire_metrology.result.RelaxationResult(mesh, geometry, moire_interface, top_interface, bottom_interface, displacement_x1, displacement_y1, displacement_x2, displacement_y2, total_energy, unrelaxed_energy, gsfe_map, elastic_map1, elastic_map2, solution_vector, optimizer_result)[source]¶
Bases:
objectContainer for relaxation results with post-processing and plotting.
- Parameters:
mesh (MoireMesh)
geometry (MoireGeometry)
moire_interface (Interface)
top_interface (Interface | None)
bottom_interface (Interface | None)
displacement_x1 (ndarray)
displacement_y1 (ndarray)
displacement_x2 (ndarray)
displacement_y2 (ndarray)
total_energy (float)
unrelaxed_energy (float)
gsfe_map (ndarray)
elastic_map1 (ndarray)
elastic_map2 (ndarray)
solution_vector (ndarray)
optimizer_result (object)
- geometry¶
Moire geometry used in the calculation.
- Type:
- moire_interface¶
The twisted A-B interface that drove the calculation. Carries both materials (
moire_interface.top,moire_interface.bottom) and the GSFE coefficients of the moire stacking.- Type:
- top_interface¶
Homobilayer interface used inside the top flake when
nlayer1 > 1.Nonefor monolayer top flakes.- Type:
Interface or None
- bottom_interface¶
Homobilayer interface used inside the bottom flake when
nlayer2 > 1.Nonefor monolayer bottom flakes.- Type:
Interface or None
- displacement_x1, displacement_y1
Displacement fields for the top flake (stack 1).
- Type:
ndarray, shape (nlayer1, Nv)
- displacement_x2, displacement_y2
Displacement fields for the bottom flake (stack 2).
- Type:
ndarray, shape (nlayer2, Nv)
- gsfe_map¶
GSFE energy density at interface vertices (meV/nm^2).
- Type:
ndarray, shape (Nv,)
- elastic_map1¶
Elastic energy density per layer in the top flake (meV/nm^2).
- Type:
ndarray, shape (nlayer1, Nv)
- elastic_map2¶
Elastic energy density per layer in the bottom flake (meV/nm^2).
- Type:
ndarray, shape (nlayer2, Nv)
- solution_vector¶
Raw optimizer solution vector.
- Type:
ndarray
- property material1: Material¶
Top flake material (stack 1).
Convenience accessor — equivalent to
self.moire_interface.top. Stack-1 / stack-2 numbering follows the internal solver convention (stack 1 = top flake, stack 2 = bottom flake).
- property material2: Material¶
Bottom flake material (stack 2).
Convenience accessor — equivalent to
self.moire_interface.bottom.
- local_twist(stack=1, layer=0)[source]¶
Compute local twist angle (degrees) at each vertex.
local_twist = theta_twist + 0.5*(duy/dx - dux/dy) * 180/pi
- plot_stacking(ax=None, n_tile=2, **kwargs)[source]¶
Plot the GSFE (stacking energy) map at the interface.
- Parameters:
n_tile (int)
- plot_elastic_energy(stack=1, layer=0, ax=None, n_tile=2, **kwargs)[source]¶
Plot elastic energy density for a given layer.
Materials¶
Per-layer material properties for 2D van der Waals heterostructures.
A Material carries the intra-layer properties of a 2D
crystal: lattice constant and 2D elastic moduli. The inter-layer
stacking interaction (GSFE) is a property of a pair of materials in
contact, not of either material individually, and lives on
moire_metrology.interfaces.Interface instead.
Units convention¶
Lattice constants are in nm. Bulk and shear moduli are stored in meV per unit cell, matching the Carr/Halbertal convention used throughout the package: the elastic energy density per unit cell is
- E_elastic_per_uc = (1/2) K (∂_x u_x + ∂_y u_y)²
(1/2) G [(∂_x u_x − ∂_y u_y)² + (∂_x u_y + ∂_y u_x)²]
with K = λ + μ (the 2D bulk modulus) and G = μ (the 2D shear modulus). The conversion to literature-standard 2D moduli in N/m is
K [N/m] = K [meV/uc] / (S_uc [m²] × 6.241509e21 meV/J)
where S_uc = (√3/2) × a² is the unit cell area. For graphene this
factor is ~329.5 meV/uc per N/m, so K = 69518 meV/uc corresponds
to K = 211 N/m — matching the experimental indentation value of
Lee et al. Science 2008.
The Material.from_2d_moduli_n_per_m() constructor and the
Material.moduli_n_per_m property provide the round-trip in
both directions, so user-defined materials can be specified directly
in N/m and existing materials can be sanity-checked against the
literature.
Sources for the bundled values¶
GRAPHENE: K, G from Carr et al. PRB 98, 224102 (2018), Table I.HBN_AA,HBN_AAP: K, G derived from Falin et al. Nat. Commun. 8, 15815 (2017), monolayer hBN indentation,E_2D ≈ 286 N/m, combined with the literature 2D Poisson ratioν ≈ 0.21to giveK_2D ≈ 181 N/m, G_2D ≈ 118 N/m. The AA / AA’ distinction is a stacking convention for the hBN/hBN bilayer interface, not a monolayer material property — the Material entries are physically the same; only the paired Interface entries differ.MOSE2,WSE2: K, G from Halbertal et al. Nat. Commun. 12, 242 (2021), SI Table 1, and independently confirmed in the Shabani, Halbertal et al. Nat. Phys. 17, 720 (2021) Methods section.GRAPHENE_BILAYER: a Bernal-stacked graphene bilayer treated as a single effective 2D elastic continuum, with K and G taken as exactly twice the single-layer graphene values per the convention of Halbertal et al. Nat. Commun. 12, 242 (2021), SI Table 1 (the “TDBG” rows). Lattice constant is inherited from graphene.
- class moire_metrology.materials.Material(name, lattice_constant, bulk_modulus, shear_modulus)[source]¶
Bases:
objectPer-layer properties of a single 2D material.
- Parameters:
- property moduli_n_per_m: tuple[float, float]¶
Return
(K, G)converted to literature-standard units of N/m.The package stores moduli in meV/unit-cell (the Carr/Halbertal convention used throughout the elastic energy expression). This property converts back to the more familiar 2D N/m units used in the experimental literature, e.g. for sanity-checking a bundled material against published indentation values.
For graphene the conversion factor is ~329.5 meV/uc per N/m, so
K = 69518 meV/ucreturns ~211 N/m, matching the Lee et al. Science 2008 indentation value.
- classmethod from_2d_moduli_n_per_m(name, lattice_constant, bulk_modulus_n_per_m, shear_modulus_n_per_m)[source]¶
Construct a Material from K, G in literature-standard N/m units.
The package internally stores moduli in meV per unit cell to match the Carr/Halbertal elastic energy convention. Most published 2D elastic constants (Lee et al. Science 2008, Carr et al. PRB 98, etc.) are reported in N/m instead, so this constructor exists to make user-defined materials specifiable in their natural units without forcing the user to redo the conversion arithmetic by hand.
- Parameters:
- Return type:
Examples
Reproducing the bundled GRAPHENE from literature values:
>>> g = Material.from_2d_moduli_n_per_m( ... name="Graphene", ... lattice_constant=0.247, ... bulk_modulus_n_per_m=211.0, # Lee et al. Science 2008 ... shear_modulus_n_per_m=144.0, ... ) >>> int(round(g.bulk_modulus)) # within rounding of paper Table 1 69533
- classmethod n_layer_stack(base, n=1, name=None)[source]¶
Return a Material for
ncoherently-strained copies ofbase.The returned Material has the same lattice constant as
baseand elastic moduli scaled byn(each additional layer adds linearly to the effective stiffness under the assumption of identical in-plane strain). It is otherwise a first-class Material that can be used anywhere a single-layer one can.This is the modelling convention used in the literature for twisted double-bilayer graphene (n=2) and twisted double- trilayer graphene (n=3): see e.g. Halbertal et al. Nat. Commun. 12, 242 (2021), SI Table 1, which states
K, G were simply taken as twice that of TBGfor the TDBG row, i.e. n=2.- Parameters:
- Return type:
Examples
>>> from moire_metrology import GRAPHENE >>> bilayer = Material.n_layer_stack(GRAPHENE, n=2) >>> round(bilayer.bulk_modulus / GRAPHENE.bulk_modulus) 2 >>> bilayer.lattice_constant == GRAPHENE.lattice_constant True
- classmethod from_dict(data)[source]¶
Build a Material from a plain
dict(e.g. parsed from TOML).Required keys:
name,lattice_constant,bulk_modulus,shear_modulus. Extra keys raise aValueErrorso that typos in user TOML files surface immediately instead of being silently ignored.
- classmethod from_toml(path)[source]¶
Load a Material from a TOML file.
The file must contain a top-level
[material]table with the four required fields:[material] name = "MyTMD" lattice_constant = 0.330 # nm bulk_modulus = 42000.0 # meV/uc shear_modulus = 28000.0 # meV/uc
See
Material.from_dict()for the field semantics.
Interfaces¶
Interlayer interfaces for moire heterostructures.
An Interface describes the registry-dependent stacking energy
between two adjacent layers — the GSFE — together with the convention
for how the upper layer is offset relative to the lower layer when
multi-layer flakes are stacked. GSFE is physically a property of the
pair of materials in contact, not of either material individually,
so this module separates that concern from the per-material elastic
moduli that live in moire_metrology.materials.
The GSFE Fourier expansion follows the Carr et al. convention:
- V(v, w) = c0 + c1*(cos(v) + cos(w) + cos(v + w))
c2*(cos(v + 2w) + cos(v - w) + cos(2v + w))
c3*(cos(2v) + cos(2w) + cos(2v + 2w))
c4*(sin(v) + sin(w) - sin(v + w))
c5*(sin(2v + 2w) - sin(2v) - sin(2w))
For centrosymmetric homobilayers (e.g. graphene/graphene, hBN-AA) the sin coefficients vanish (c4 = c5 = 0). Heterointerfaces with broken inversion symmetry (e.g. MoSe2/WSe2 H-stacking, hBN AA’) carry non-zero c4, c5.
References
Zhou et al., PRB 92, 155438 (2015) — original GSFE parameterizations for graphene and hBN homo/heterointerfaces.
Carr et al., PRB 98, 224102 (2018) — Fourier basis convention used here, including the Zhou->Carr coefficient transformation.
Shabani, Halbertal et al., Nature Physics 17, 720 (2021) — GSFE parameters for the H-stacked MoSe2/WSe2 heterointerface (already in Carr basis, no transformation needed).
- class moire_metrology.interfaces.Interface(name, bottom, top, gsfe_coeffs, stacking_func=None, reference='')[source]¶
Bases:
objectThe registry-dependent stacking interaction between two adjacent layers.
GSFE characterizes a pair of materials in contact, not a single material. An
Interfacecouples twoMaterialinstances with the GSFE Fourier coefficients that describe their stacking energy landscape, plus (for homobilayer interfaces) the natural Bernal stacking convention used when multiple layers are stacked within a flake.- Parameters:
name (str) – Human-readable name (e.g. “Graphene/Graphene”, “MoSe2/WSe2 (H)”).
bottom (Material) – The lower layer of the pair. Boundary conditions like
fix_bottomclamp the bottommost layer of the bottom flake.top (Material) – The upper layer of the pair. May equal
bottom(homobilayer interface) or differ (heterointerface).gsfe_coeffs (tuple[float, ...]) – GSFE Fourier coefficients
(c0, c1, c2, c3, c4, c5)in meV per unit cell, in the Carr basis (see module docstring). For centrosymmetric homobilayers, c4 = c5 = 0.stacking_func (callable or None) –
(k: int) -> (I, J)returning the natural Bernal stacking offsets for layerkwithin a flake of more than one layer of this same material.Nonefor heterointerfaces (where no Bernal convention applies) and for monolayer flake usage.reference (str) – Free-form citation string for the GSFE numbers, so the provenance travels with the data.
- classmethod from_dict(data)[source]¶
Build an Interface from a plain
dict(e.g. parsed from TOML).The dict must contain the following keys:
name(str)bottom(dict — inline Material spec, seeMaterial.from_dict())top(dict — inline Material spec)gsfe_coeffs(sequence of 6 floats, Carr basis, meV/uc)reference(optional str)
stacking_funcis intentionally NOT loadable from a plain dict — it is a Python callable, and serializing arbitrary callables is out of scope for the TOML loader. If you need a non-trivial stacking convention, construct the Interface directly in Python or post-process the loaded instance.Extra keys raise a
ValueErrorso typos in user TOML files surface immediately.
- classmethod from_toml(path)[source]¶
Load an Interface from a TOML file.
The file must contain a top-level
[interface]table with inline Material definitions for both layers:[interface] name = "MoSe2/WSe2 (H-stacked)" gsfe_coeffs = [42.6, 16.0, -2.7, -1.1, 3.7, 0.6] reference = "Shabani et al., Nat. Phys. 17, 720 (2021)" [interface.bottom] name = "WSe2" lattice_constant = 0.3282 bulk_modulus = 43113.0 shear_modulus = 30770.0 [interface.top] name = "MoSe2" lattice_constant = 0.3288 bulk_modulus = 40521.0 shear_modulus = 26464.0
See
Interface.from_dict()for the field semantics. Thestacking_funcfield cannot be loaded from TOML; if you need a non-trivial stacking convention for multi-layer flakes, construct the Interface directly in Python instead.
Lattice and geometry¶
Hexagonal lattice geometry and moire superlattice calculations.
- Conventions:
Angles in degrees externally, radians internally
Lattice constants in nm
b1, b2 are unit vectors at angles theta0 and theta0 + 60 degrees
- moire_metrology.lattice.rotation_matrix(angle_deg)[source]¶
2x2 rotation matrix for a given angle in degrees.
- class moire_metrology.lattice.HexagonalLattice(alpha, theta0=0.0)[source]¶
Bases:
objectA 2D hexagonal lattice.
- Parameters:
- class moire_metrology.lattice.MoireGeometry(lattice, theta_twist, delta=0.0)[source]¶
Bases:
objectGeometry of a moire superlattice from two hexagonal layers.
- Parameters:
lattice (HexagonalLattice) – The reference lattice (layer 2 / substrate).
theta_twist (float) – Twist angle in degrees between the two layers.
delta (float) – Lattice mismatch: (alpha_layer1 / alpha_layer2) - 1. For homostructures (e.g., TBLG), delta = 0.
- property moire_matrix: ndarray¶
Mr = R(+theta) - (1+delta)*I, shape (2,2).
The moire vectors satisfy: [V1|V2] = inv(Mr) * B.
Using R(+theta) (not R(-theta)) ensures that the stacking phases jump by exact integer multiples of 2*pi across V1 and V2, so the GSFE tiles perfectly over the moire unit cell.
- property Mu1: ndarray¶
Conversion matrix for layer 1 (twisted layer) stacking phases.
- Maps displacement (ux, uy) to stacking phase change via:
delta_v = -Mu1[0,:] @ [ux, uy] delta_w = -Mu1[1,:] @ [ux, uy]
- property Mu2: ndarray¶
Conversion matrix for layer 2 (substrate) stacking phases.
- For the substrate layer, displacement shifts stacking phase as:
delta_v = Mu2[0,:] @ [ux, uy] delta_w = Mu2[1,:] @ [ux, uy]
- stacking_phases(x, y, ux=None, uy=None)[source]¶
Compute stacking phase coordinates (v, w) at positions (x, y).
- Parameters:
x (ndarray) – Spatial coordinates in nm.
y (ndarray) – Spatial coordinates in nm.
ux (ndarray or None) – Displacement field components. If None, assumed zero (unrelaxed).
uy (ndarray or None) – Displacement field components. If None, assumed zero (unrelaxed).
- Returns:
v, w – Stacking phase coordinates.
- Return type:
ndarray
Mesh¶
Triangular mesh generation for moire unit cells.
Generates a regular grid on a parallelogram domain spanned by the moire vectors V1, V2 (or multiples thereof), then triangulates with Delaunay.
- class moire_metrology.mesh.MoireMesh(points, triangles, V1, V2, ns, nt, n_scale, is_periodic=True, _boundary_info=None)[source]¶
Bases:
objectTriangular mesh on a moire unit cell parallelogram (or finite domain).
- Parameters:
- points¶
Vertex coordinates (x, y) in nm.
- Type:
ndarray, shape (2, Nv)
- triangles¶
Triangle connectivity (vertex indices).
- Type:
ndarray, shape (Nt, 3)
- V1, V2
Parallelogram edge vectors in nm. For a periodic mesh these are the moire lattice vectors. For a finite mesh they describe the bounding parallelogram of the domain (used by some plotting helpers).
- Type:
ndarray, shape (2,)
- ns, nt
Number of grid divisions along V1 and V2.
- Type:
- is_periodic¶
True if the triangulation uses periodic wrap-around connectivity (the default, produced by
MoireMesh.generate). False if the mesh covers a finite domain with open boundaries (produced bygenerate_finite_mesh). The discretization uses this flag to decide whether to apply lattice-vector wrap corrections to relative vertex positions when assembling differentiation matrices: periodic meshes need them, finite meshes don’t.- Type:
- classmethod generate(geometry, pixel_size=0.2, n_scale=1, min_points=100, max_points=500000)[source]¶
Generate a mesh on the moire unit cell parallelogram.
- Parameters:
geometry (MoireGeometry) – Defines V1, V2 moire vectors.
pixel_size (float) – Target element size in nm.
n_scale (int) – Number of moire unit cells along each direction.
min_points (int) – Bounds on the number of grid points per direction.
max_points (int) – Bounds on the number of grid points per direction.
- Return type:
- moire_metrology.mesh.generate_finite_mesh(geometry, n_cells=3, pixel_size=0.5, *, n_cells_x=None, n_cells_y=None)[source]¶
Generate a non-periodic mesh covering multiple moire unit cells.
Creates a parallelogram domain spanning n_cells moire vectors in each direction (or n_cells_x along V1 and n_cells_y along V2 when a rectangular extent is needed), with no periodic wrapping. Suitable for constrained relaxation.
- Parameters:
geometry (MoireGeometry) – Moire geometry.
n_cells (int) – Number of moire unit cells along each direction. Used as the default for both axes when n_cells_x / n_cells_y aren’t given.
pixel_size (float) – Target element size in nm.
n_cells_x (int or None) – Optional overrides for the number of moire cells along V1 and V2 respectively. If either is None, falls back to
n_cells. Use these to build a rectangular finite domain that’s wider in one direction than the other (e.g. for a twist-gradient demo where you want many wavelengths along the gradient axis but only a few perpendicular to it).n_cells_y (int or None) – Optional overrides for the number of moire cells along V1 and V2 respectively. If either is None, falls back to
n_cells. Use these to build a rectangular finite domain that’s wider in one direction than the other (e.g. for a twist-gradient demo where you want many wavelengths along the gradient axis but only a few perpendicular to it).
- Returns:
Non-periodic mesh (boundary triangles do NOT wrap).
- Return type:
- moire_metrology.mesh.generate_hex_periodic_mesh(geometry, pixel_size=2.0, eps_boundary_frac=0.05)[source]¶
Generate a hexagonal Wigner-Seitz periodic mesh centered on the AA stacking site (origin in our convention).
The mesh has exact 6-fold rotational symmetry around the center: vertex positions are placed on a triangular sub-lattice through origin with spacing vectors V1/N, V2/N (so the sub-lattice has the same orientation as the moire lattice), then filtered to those inside the WS hexagon. The 6 WS corners are added explicitly.
Use this together with the boundary identification helper
identify_hex_periodic_boundary()andPeriodicPairConstraint(3 pair sets, one for each pair of opposite edges) plus aPinnedConstraintspinning the 6 corners at U=0 — see the online docs/example for a complete recipe.Compared to
generate_finite_mesh()(parallelogram supercell, C2 symmetry only), the hexagonal cell respects C3 symmetry around the AA site and the C3 orbit of AB / BA corner pins. This eliminates the direction-selection artifact that parallelogram supercells exhibit at low twist angles in problems with rich GSFE structure.- Parameters:
geometry (MoireGeometry) – Moire geometry defining V1, V2.
pixel_size (float) – Target vertex spacing in nm. The actual spacing is set by rounding |V1| / pixel_size to the nearest integer N, then using spacing |V1| / N (so the triangular sub-lattice tiles the cell exactly).
eps_boundary_frac (float) – Tolerance for the “inside hex” test, in units of pixel_size. Vertices within this tolerance of the boundary are kept.
- Returns:
Hexagonal-cell mesh with
is_periodic=False(open boundary; periodicity is enforced via PeriodicPairConstraint after boundary identification). V1, V2 stored as the moire vectors (NOT the rhombus-cell vectors), so downstream code computing stacking phases from positions uses the correct geometry.- Return type:
- moire_metrology.mesh.identify_hex_periodic_boundary(mesh, tol=1e-06)[source]¶
Identify boundary topology of a hexagonal-cell mesh built by
generate_hex_periodic_mesh().Returns a dict with:
"corners"— shape (6,) int array, mesh vertex indices for the 6 WS corners, ordered c1, c2, c3, c4, c5, c6 (counterclockwise)."edges"— dict mapping k ∈ {1, …, 6} to the array of vertex indices on edge k, sorted along the c_{k} → c_{k+1} tangent direction. Each edge includes its two endpoint corners."pairs"— list of three dicts, one per opposite-edge pair:{"source": k_src, "dest": k_dst, "translation": v, "src_indices": array, "dst_indices": array}, wheredst_indices[i] ≈ src_indices[i] + translationin the parent moire lattice.
- Edge labelling (counterclockwise, edge k from c_k to c_{k+1}):
k=1: c1→c2, outward normal +V2, pair with k=4 (translation -V2) k=2: c2→c3, outward normal V2-V1, pair with k=5 (-(V2-V1)) k=3: c3→c4, outward normal -V1, pair with k=6 (+V1) k=4: c4→c5, outward normal -V2, k=5: c5→c6, outward normal V1-V2, k=6: c6→c1, outward normal +V1.
The three pairs (1↔4, 2↔5, 3↔6) cover ALL boundary vertices including corners (each corner is on two edges, so appears in two pair lists).
- moire_metrology.mesh.generate_custom_mesh(geometry, *, outer_boundary='disk', outer_radius=70.0, center=(0.0, 0.0), holes=None, max_area=2.0, min_angle=25.0, boundary_density=None)[source]¶
Generate a mesh on an arbitrary 2D domain using meshpy.
Produces an unstructured triangulation with smooth boundaries, suitable for constrained relaxation on non-rectangular domains and domains with interior holes.
- Parameters:
geometry (MoireGeometry) – Moire geometry (provides V1, V2 for metadata).
outer_boundary (
"disk"or list of (x, y) tuples) – Outer domain boundary."disk"creates a circle of radiusouter_radiuscentred atcenter.outer_radius (float) – Radius in nm (used only when
outer_boundary="disk").center (tuple of float) – Centre of the disk in nm (default origin).
holes (list of dict, optional) – Each dict describes a hole:
{"center": (cx, cy), "radius": r}for a circular hole, or{"points": [(x1,y1), ...]}for an arbitrary polygon.max_area (float) – Maximum triangle area in nm² (controls mesh density).
min_angle (float) – Minimum triangle angle in degrees (controls quality).
boundary_density (int or None) – Number of boundary points per circle. If None, chosen automatically from
outer_radius / sqrt(max_area).
- Returns:
Non-periodic mesh with
is_periodic=False.- Return type:
Examples
Disk with a central hole:
mesh = generate_custom_mesh( geom, outer_boundary="disk", outer_radius=70.0, holes=[{"center": (0, 0), "radius": 10.0}], max_area=1.5, )
Hourglass (two pads connected by a neck):
# Define as a polygon mesh = generate_custom_mesh( geom, outer_boundary=hourglass_points, max_area=1.0, )
GSFE surface¶
Generalized Stacking Fault Energy (GSFE) surface and its derivatives.
The GSFE is parameterized as a 6-coefficient Fourier expansion in stacking phase coordinates (v, w):
- V(v,w) = c0 + c1*(cos(v) + cos(w) + cos(v+w))
c2*(cos(v+2w) + cos(v-w) + cos(2v+w))
c3*(cos(2v) + cos(2w) + cos(2v+2w))
c4*(sin(v) + sin(w) - sin(v+w))
c5*(sin(2v+2w) - sin(2v) - sin(2w))
All methods operate element-wise on numpy arrays.
Energy functional¶
Energy functional assembly for moire relaxation.
Computes the total energy E = E_elastic + E_GSFE, its gradient, and Hessian.
The elastic Hessian is constant (precomputed once as sparse matrix). The GSFE Hessian is vertex-diagonal (cheap per iteration).
For multi-layer systems, GSFE coupling exists at: - The interface between the two stacks (always present) - Between adjacent layers within each stack (intra-stack, when nlayer > 1)
- class moire_metrology.energy.RelaxationEnergy(disc, conv, geometry, gsfe_interface, K1, G1, K2, G2, nlayer1=1, nlayer2=1, gsfe_flake1=None, gsfe_flake2=None, I1_vect=None, J1_vect=None, I2_vect=None, J2_vect=None, constraints=None, elastic_strain='cauchy')[source]¶
Bases:
objectTotal energy functional for moire relaxation.
Provides: - __call__(U) -> (E, grad) - hessian(U) -> sparse matrix - hessp(U, p) -> Hessian-vector product
- Parameters:
disc (Discretization)
conv (ConversionMatrices)
geometry (MoireGeometry)
gsfe_interface (GSFESurface)
K1 (float)
G1 (float)
K2 (float)
G2 (float)
nlayer1 (int)
nlayer2 (int)
gsfe_flake1 (GSFESurface | None)
gsfe_flake2 (GSFESurface | None)
I1_vect (np.ndarray | None)
J1_vect (np.ndarray | None)
I2_vect (np.ndarray | None)
J2_vect (np.ndarray | None)
constraints (PinnedConstraints | None)
elastic_strain (str)
- hessp(U, p)[source]¶
Compute Hessian-vector product H(U) @ p.
If constrained, U and p are in free-DOF space; result is also free-DOF space.
- hessian(U, modified=False)[source]¶
Compute the Hessian. If constrained, returns the free x free subblock.
- Parameters:
U (ndarray) – Current DOF vector (free-space if constrained).
modified (bool) – If True, flip negative eigenvalues of the per-vertex GSFE Hessian blocks so the returned matrix is positive definite (modulo the elastic part, which is always PSD). This enables Newton steps at non-minimum stacking sites (AA, saddle points) where the unmodified GSFE Hessian is indefinite.
- Return type:
Discretization¶
FEM discretization on triangular meshes.
Provides differentiation matrices, area weights, and conversion matrices for mapping the flat solution vector to per-layer displacement fields.
For a periodic mesh, all vertices are independent (periodicity is encoded in the triangulation via wrapping indices). For constrained problems, PinnedConstraints tracks which DOFs are free vs pinned.
- class moire_metrology.discretization.PinnedConstraints(free_indices, pinned_indices, pinned_values, n_free, n_full)[source]¶
Bases:
objectTracks which DOFs are free vs pinned in a constrained relaxation.
The full solution vector has length n_full. The optimizer only sees the n_free free DOFs. This class handles the mapping between the two.
- Parameters:
- pinned_values¶
Displacement values at pinned DOFs.
- Type:
ndarray
- expand_zeros(v_free)[source]¶
Map free DOFs to full vector with zeros at pinned positions.
Used for perturbation directions (Hessian-vector products) where pinned DOFs should not contribute.
- moire_metrology.discretization.build_outer_layer_constraints(conv, fix_top, fix_bottom, pin_mean=False)[source]¶
Build PinnedConstraints clamping the outer (free-surface) layers to zero.
For a multi-layer stack, the “top” outer layer is the topmost layer of stack 1 (the layer farthest from the twisted interface, on the upper side of the heterostructure). The “bottom” outer layer is the deepest layer of stack 2 (the layer farthest from the interface on the lower side, i.e. the substrate’s free surface in a typical setup).
Pinning these to zero displacement simulates the layers being bonded to a rigid bulk — the conventional way to approximate a semi-infinite substrate with a finite stack. This matches the fix_external_layers parameter in the MATLAB paper code.
- Parameters:
conv (ConversionMatrices) – Conversion matrices describing the multi-layer DOF layout.
fix_top (bool) – Pin all DOFs (ux and uy at every vertex) of stack 1, layer 0.
fix_bottom (bool) – Pin all DOFs of stack 2, layer (nlayer2 - 1).
pin_mean (bool)
- Returns:
Suitable to pass to RelaxationSolver.solve(constraints=…).
- Return type:
- class moire_metrology.discretization.ConversionMatrices(conv_x1, conv_y1, conv_x2, conv_y2, n_sol, n_vertices, nlayer1, nlayer2)[source]¶
Bases:
objectMatrices mapping the flat solution vector U to per-layer displacements.
For a system with nlayer1 layers in stack 1 and nlayer2 layers in stack 2, the solution vector has length 2 * (nlayer1 + nlayer2) * Nv (x and y displacements for each layer at each vertex).
The conversion matrices extract the x/y displacement for each layer and apply the appropriate strain partition factor (eps for stack 1, 1-eps for stack 2).
- Parameters:
conv_x1 (csr_matrix)
conv_y1 (csr_matrix)
conv_x2 (csr_matrix)
conv_y2 (csr_matrix)
n_sol (int)
n_vertices (int)
nlayer1 (int)
nlayer2 (int)
- class moire_metrology.discretization.Discretization(mesh, geometry)[source]¶
Bases:
objectFEM infrastructure for a triangular moire mesh.
Handles BOTH periodic and finite (open) meshes. The mesh’s
is_periodicflag controls whether the diff matrix builder applies lattice-vector wrap corrections to relative vertex positions: periodic meshes need them (the structured triangulation contains wrap-around triangles whose vertices are placed on opposite sides of the parallelogram), finite meshes don’t.- Parameters:
mesh (MoireMesh) – The triangular mesh.
geometry (MoireGeometry) – Moire geometry for computing stacking phases.
- property diff_mat_x: csr_matrix¶
maps vertex values to per-triangle x-derivatives.
- Type:
Sparse matrix (Nt, Nv)
- property diff_mat_y: csr_matrix¶
maps vertex values to per-triangle y-derivatives.
- Type:
Sparse matrix (Nt, Nv)
- property vertex_areas: ndarray¶
Area associated with each vertex (Voronoi dual), shape (Nv,).
Computed by distributing each triangle’s area equally among its 3 vertices.
- property triangle_to_vertex: csr_matrix¶
maps triangle values to vertex values by area-weighted average.
- Type:
Sparse matrix (Nv, Nt)
- build_conversion_matrices(nlayer1=1, nlayer2=1, eps=0.5)[source]¶
Build matrices mapping solution vector U to per-layer displacements.
- The solution vector U has the layout:
- [ux_layer1_0, …, ux_layer1_{n1-1},
ux_layer2_0, …, ux_layer2_{n2-1}, uy_layer1_0, …, uy_layer1_{n1-1}, uy_layer2_0, …, uy_layer2_{n2-1}]
Each layer block has Nv entries (one per vertex).
For a bilayer (nlayer1=nlayer2=1), the strain partition factor eps controls how the total relative displacement is shared: - Layer 1 displacement = eps * u - Layer 2 displacement = (1-eps) * u With eps=0.5, both layers move symmetrically.
- Parameters:
- Return type:
- moire_metrology.discretization.PeriodicDiscretization¶
alias of
Discretization
Pinning¶
Interactive and programmatic tools for defining pinning sites.
Provides three ways to define where known stacking configurations are pinned:
Programmatic — PinningMap with pin_stacking() calls
Interactive — InteractivePinner with matplotlib click interface
From file — PinningMap.from_csv()
- Example:
from moire_metrology.pinning import PinningMap from moire_metrology import GRAPHENE, RelaxationSolver, SolverConfig from moire_metrology.lattice import HexagonalLattice, MoireGeometry from moire_metrology.mesh import MoireMesh from moire_metrology.discretization import Discretization
geometry = MoireGeometry(HexagonalLattice(0.247), theta_twist=0.5) mesh = MoireMesh.generate(geometry, pixel_size=0.5)
pins = PinningMap(mesh, geometry) pins.pin_stacking(x=10.0, y=20.0, stacking=”AB”, radius=3.0) pins.pin_stacking(x=30.0, y=15.0, stacking=”BA”, radius=3.0)
disc = Discretization(mesh, geometry) conv = disc.build_conversion_matrices() constraints = pins.build_constraints(conv)
solver = RelaxationSolver(SolverConfig(display=True)) result = solver.solve(GRAPHENE, GRAPHENE, 0.5, constraints=constraints)
- class moire_metrology.pinning.PinSite(x, y, stacking, radius)[source]¶
Bases:
objectA single pinning site with known stacking.
- class moire_metrology.pinning.PinningMap(mesh, geometry)[source]¶
Bases:
objectCollection of pinning sites for constrained relaxation.
- Parameters:
mesh (MoireMesh) – The computational mesh.
geometry (MoireGeometry) – Moire geometry for computing stacking phases.
- pin_stacking(x, y, stacking, radius=1.0)[source]¶
Pin vertices near (x, y) to a known stacking configuration.
- pin_vertices(vertex_indices, stacking)[source]¶
Pin specific vertices to a known stacking configuration.
- get_pinned_vertex_indices()[source]¶
Get all pinned vertex indices (union of all pin sites).
- Return type:
- build_constraints(conv, nlayer1=1, nlayer2=1)[source]¶
Build PinnedConstraints from the current pin sites.
Pins are applied to the interface layers (innermost of each stack). Both interface layers are pinned to produce the target stacking.
- Parameters:
conv (ConversionMatrices) – From disc.build_conversion_matrices().
nlayer1 (int) – Number of layers in each stack (needed for DOF offset calculation).
nlayer2 (int) – Number of layers in each stack (needed for DOF offset calculation).
- Return type:
- classmethod from_csv(path, mesh, geometry)[source]¶
Load pin sites from a CSV file.
Expected format (with header): x, y, stacking, radius
- Return type:
- Parameters:
mesh (MoireMesh)
geometry (MoireGeometry)
- class moire_metrology.pinning.InteractivePinner(mesh, geometry, background_image=None, image_extent=None)[source]¶
Bases:
objectInteractive matplotlib tool for placing pinning sites on a moire mesh.
- Usage:
pinner = InteractivePinner(mesh, geometry) pinner.show() # opens interactive window # Left-click to place pins, right-click to remove nearest # Press 1/2/3 to select AA/AB/BA stacking type # Press +/- to adjust radius # Close window when done constraints = pinner.get_constraints(conv)
- Parameters:
mesh (MoireMesh)
geometry (MoireGeometry)
background_image (str or ndarray or None) – Path to an image file (e.g., PFM/STM data) to display behind the mesh, or a 2D numpy array. If None, shows the unrelaxed GSFE map.
image_extent (tuple or None) – (xmin, xmax, ymin, ymax) for the background image in nm.
- get_constraints(conv, nlayer1=1, nlayer2=1)[source]¶
Build constraints from the interactively placed pins.
- Return type:
- Parameters:
conv (ConversionMatrices)
nlayer1 (int)
nlayer2 (int)
Plotting¶
Visualization utilities for moire relaxation results.
Uses matplotlib’s Triangulation and tripcolor for efficient rendering of scalar fields on triangular meshes, with optional tiling to show multiple moire unit cells.
- moire_metrology.plotting.plot_scalar_field(mesh, values, ax=None, n_tile=1, title='', cmap='viridis', colorbar=True, **kwargs)[source]¶
Plot a scalar field on the moire mesh.
- Parameters:
mesh (MoireMesh) – The triangular mesh.
values (ndarray, shape (Nv,)) – Scalar values at each vertex.
ax (matplotlib Axes or None) – If None, creates a new figure.
n_tile (int) – Number of periodic copies in each direction (for visualization).
title (str) – Plot title.
cmap (str) – Colormap name.
colorbar (bool) – Whether to add a colorbar.
**kwargs – Passed to tripcolor.
- Return type:
Strain extraction¶
Strain extraction from moire patterns.
Two workflows:
Pointwise extraction — given moire vector observables
(lambda1, lambda2, phi1, phi2)for a single point:from moire_metrology.strain import get_strain_minimize_compression result = get_strain_minimize_compression( alpha1, alpha2, lambda1, lambda2, phi1_deg, phi2_deg, )
Spatial strain mapping — from traced moire fringe polylines:
from moire_metrology.strain import ( FringeSet, compute_strain_field, compute_displacement_field, convex_hull_mask, ) fringes = FringeSet.from_matlab("points.mat") I_field, J_field = fringes.fit_registry_fields(order=11) strain = compute_strain_field( x, y, I_field, J_field, alpha1, alpha2, phi0_deg=-65.6, ) ux, uy = compute_displacement_field( x, y, I_field, J_field, geometry, target_stacking="BA", )
- class moire_metrology.strain.StrainResult(theta_twist, phi0, S11, S12, S22, eps_c, eps_s, eps1, eps2, strain_angle)[source]¶
Bases:
objectResult of strain extraction from moire observables.
- Parameters:
- S11, S12, S22
Strain tensor components (symmetric: S21 = S12).
- Type:
- eps1, eps2
Principal strains (eigenvalues of strain tensor).
- Type:
- moire_metrology.strain.get_strain(alpha1, alpha2, lambda1, lambda2, phi1_deg, phi2_deg, phi0)[source]¶
Extract twist angle and strain from moire observables at a given phi0.
- Parameters:
alpha1 (float) – Lattice constants of the two layers (nm).
alpha2 (float) – Lattice constants of the two layers (nm).
lambda1 (float) – Moire periods along the two moire vectors (nm).
lambda2 (float) – Moire periods along the two moire vectors (nm).
phi1_deg (float) – Orientations of the two moire vectors (degrees).
phi2_deg (float) – Orientations of the two moire vectors (degrees).
phi0 (float) – Substrate lattice orientation angle (degrees).
- Return type:
- moire_metrology.strain.get_strain_minimize_compression(alpha1, alpha2, lambda1, lambda2, phi1_deg, phi2_deg, phi0_guess=0.0)[source]¶
Extract strain with phi0 chosen to minimize compression strain |eps_c|.
This solves analytically for phi0 such that eps_c = 0 when possible, or finds the closest achievable phi0 when eps_c = 0 is infeasible.
- Parameters:
alpha1 (float) – Lattice constants (nm).
alpha2 (float) – Lattice constants (nm).
lambda1 (float) – Moire periods (nm).
lambda2 (float) – Moire periods (nm).
phi1_deg (float) – Moire vector orientations (degrees).
phi2_deg (float) – Moire vector orientations (degrees).
phi0_guess (float) – Initial guess for phi0 (degrees), used for disambiguation.
- Return type:
- moire_metrology.strain.get_strain_axis(S11, S12, S22)[source]¶
Diagonalize the strain tensor to get principal strains.
- Parameters:
- Return type:
- Returns:
eps1 (float) – Principal strain 1 (larger).
eps2 (float) – Principal strain 2 (smaller).
strain_angle (float) – Principal strain axis angle in degrees.
- moire_metrology.strain.shear_strain_invariant(alpha1, alpha2, lambda1, lambda2, phi1_deg, phi2_deg)[source]¶
Compute the phi0-independent shear strain from moire observables.
- This is the closed-form formula from the paper:
eps_s = alpha * r_minus / (2 * lambda1 * lambda2 * sin(dphi))
where r_minus = sqrt(l1^2 + l2^2 - 2*l1*l2*cos(dphi - pi/3))
This quantity depends only on the moire observables, not on phi0.
- Parameters:
- Returns:
Shear strain magnitude (always non-negative).
- Return type:
- moire_metrology.strain.compute_displacement_field(x, y, I_field, J_field, geometry, target_stacking='BA', remove_mean=True)[source]¶
Build a displacement field from polynomial registry fits.
The pointwise pinning machinery in
moire_metrology.pinningsolvesMu @ u = -(v_target - v0)at individual sites to put a given mesh vertex at a chosen stacking. This function does the same solve at every query point(x, y), but withv_targetcoming from a continuous polynomial fit of the registry fields rather than discrete pin assignments. The result is a smooth displacement field suitable as the initial condition for relaxation against an experimentally measured moiré pattern.The target phase at each point is
v_target(r) = 2π · I(r) + v_offset w_target(r) = 2π · J(r) + w_offset
where
(v_offset, w_offset)is the constant phase shift frommoire_metrology.pinning.STACKING_PHASESthat puts integer(I, J)data sites at the requestedtarget_stacking(e.g."BA"is the global GSFE minimum for H-stacked TMDs).- Parameters:
x (ndarray) – Query coordinates (nm), of any common shape.
y (ndarray) – Query coordinates (nm), of any common shape.
I_field (RegistryField) – Polynomial fits of the moire registry index fields.
J_field (RegistryField) – Polynomial fits of the moire registry index fields.
geometry (MoireGeometry) – The average moire geometry. Provides
stacking_phasesandMu1for the per-point linear solve.target_stacking (str) – One of
"AA","AB","BA". Default"BA".remove_mean (bool) – If True, subtract the mean of the resulting displacement so the global translation gauge is fixed at zero. Default True.
- Returns:
ux, uy – Displacement field components (nm), of the same shape as
x.- Return type:
ndarray
Notes
The polynomial fits are unconstrained outside the convex hull of their training data and grow rapidly there. Mask the query points with
convex_hull_mask()before calling this function on a mesh that extends past the data extent.
- moire_metrology.strain.compute_strain_field(x, y, I_field, J_field, alpha1, alpha2, phi0_deg)[source]¶
Compute the spatially-varying strain field from registry polynomials.
Implements the spatial extension of the ACS Nano paper strain inversion. At each query point, the local moiré reciprocal lattice vectors are obtained from the polynomial fit gradients via eq. 9:
[v1(r), v2(r)] = inv([∇I(r); ∇J(r)])
yielding (λ1, λ2, φ1, φ2) at every point. The pointwise
get_strain()is then called with a globalphi0_degto recover (θ, ε_c, ε_s) per point.- Parameters:
x (ndarray) – Query coordinates (nm), of any common shape.
y (ndarray) – Query coordinates (nm), of any common shape.
I_field (RegistryField) – Polynomial fits of the moire registry index fields, with analytic
dx/dyevaluators (e.g.moire_metrology.strain.RegistryField).J_field (RegistryField) – Polynomial fits of the moire registry index fields, with analytic
dx/dyevaluators (e.g.moire_metrology.strain.RegistryField).alpha1 (float) – Lattice constants of the two layers (nm). Convention follows
get_strain()—alpha2is the larger of the two for the H-MoSe2/WSe2 sample reproduced in the paper.alpha2 (float) – Lattice constants of the two layers (nm). Convention follows
get_strain()—alpha2is the larger of the two for the H-MoSe2/WSe2 sample reproduced in the paper.phi0_deg (float) – Substrate lattice orientation angle (degrees), held constant across the field. The paper uses a global
φ0rather than a per-point one.
- Returns:
Each entry has the same shape as
x. Keys:theta,eps_c,eps_sLocal twist angle (deg) and the volumetric / shear strain scalars used in the paper figures.
S11,S12,S22Components of the symmetric strain tensor in the global
(x, y)frame.S21 = S12. These are the natural inputs to a gradient-integration IC builder for the relaxation framework.eps1,eps2,strain_anglePrincipal strains and the principal-axis angle (deg).
lambda1,lambda2,phi1_deg,phi2_degThe local moire vector lengths and orientations recovered from the eq. 9 inversion (intermediate quantities, exposed for diagnostics).
- Return type:
- moire_metrology.strain.displacement_from_strain_field(discretization, *, theta_deg, theta_avg_deg, S11, S12, S22, pin_vertex=0, dirichlet_vertices=None, dirichlet_ux=None, dirichlet_uy=None)[source]¶
Build a displacement field from a target local twist + strain.
Translates the spatially-varying
(θ(r), S(r))output ofcompute_strain_field()into the relaxation framework’s nativeu(r)language. The framework holds a single global twistθ_avgfixed, so any local twist deviationδθ(r) = θ(r) − θ_avgand any local strainS(r)must show up as a gradient ofu. Specifically (with the framework’s eps=0.5 layer partition, confirmed againstenergy.pyanddiscretization.py):∂u_x/∂x = S11(r) ∂u_y/∂y = S22(r) ∂u_x/∂y = S12(r) − δθ(r) ∂u_y/∂x = S12(r) + δθ(r)
These four equations are over-determined (four constraints on the four components of
∂u/∂rper point — exactly the count, but with a global compatibility condition that need not hold for noisy measured data). The implementation reads them as a sparse linear least-squares problem on the FEM mesh:[Dx; Dy] @ u_x = (S11; S12 − δθ)and similarly foru_y, whereDxandDyareDiscretization’s vertex-to-triangle gradient operators.When
dirichlet_verticesis given, those vertices are held at fixed(dirichlet_ux, dirichlet_uy)values and the Poisson solve optimizes the remaining vertices. This is the natural way to combine the gradient IC with pinning constraints: the pinned vertices get the displacement that thePinningMapcomputes for their target stacking, and the free vertices are interpolated to match the target gradient field. When no Dirichlet vertices are given, a singlepin_vertexis pinned to zero to fix the global translation gauge.- Parameters:
discretization (Discretization) – FEM discretization carrying the mesh and the gradient operators.
theta_deg (ndarray, shape (Nv,)) – Local twist angle at each mesh vertex (degrees). Use
np.abs(compute_strain_field(...)["theta"])since the package convention reports-θ.theta_avg_deg (float) – Global twist angle the relaxation framework holds fixed (deg).
S11 (ndarray, shape (Nv,)) – Strain tensor components at each mesh vertex (the
compute_strain_fieldoutput keys of the same names).S12 (ndarray, shape (Nv,)) – Strain tensor components at each mesh vertex (the
compute_strain_fieldoutput keys of the same names).S22 (ndarray, shape (Nv,)) – Strain tensor components at each mesh vertex (the
compute_strain_fieldoutput keys of the same names).pin_vertex (int) – Index of the vertex pinned to
u = 0to fix the global translation gauge. Ignored whendirichlet_verticesis set.dirichlet_vertices (ndarray of int or None) – Vertex indices where
uis prescribed. When given,dirichlet_uxanddirichlet_uymust also be provided.dirichlet_ux (ndarray or None) – Prescribed displacement values at the Dirichlet vertices (nm).
dirichlet_uy (ndarray or None) – Prescribed displacement values at the Dirichlet vertices (nm).
- Returns:
ux, uy – Per-vertex displacement field components (nm), suitable as the initial condition for a relaxation against the average-twist moiré geometry.
- Return type:
ndarray, shape (Nv,)
- class moire_metrology.strain.FringeLine(x, y, index, family)[source]¶
Bases:
objectA single traced moire fringe line.
- x¶
x-coordinates along the fringe (nm).
- Type:
ndarray
- y¶
y-coordinates along the fringe (nm).
- Type:
ndarray
- class moire_metrology.strain.FringeSet(fringes=<factory>)[source]¶
Bases:
objectCollection of traced moire fringes for strain extraction.
- Parameters:
fringes (list[FringeLine])
- fringes¶
All traced fringe lines.
- Type:
list of FringeLine
- property i_fringes: list[FringeLine]¶
Fringes of family 1 (I-type).
- property j_fringes: list[FringeLine]¶
Fringes of family 2 (J-type).
- fit_registry_fields(order=11)[source]¶
Fit polynomial registry fields I(x,y) and J(x,y) to the fringe data.
The two families of fringes are fit independently, each using its raw polyline points (one constraint per traced point) — this matches the approach in the maintainer’s MATLAB pipeline that was validated against ACS Nano 16, 1471 (2022) Fig. 1.
- Parameters:
order (int) – Polynomial order for the 2D fit (default 11, matching the paper Methods section).
- Returns:
I_field, J_field – Polynomial fits to the I and J registry fields.
- Return type:
- estimate_moire_wavelength()[source]¶
Estimate the moire wavelength from fringe spacing.
- Return type:
- class moire_metrology.strain.RegistryField(coeffs, order, x_center, y_center, x_scale, y_scale)[source]¶
Bases:
objectA 2D polynomial fit of a moire registry field (I or J).
- The polynomial has the form:
f(x,y) = sum_{i+j <= order} c_{ij} * x^i * y^j
- Parameters:
- coeffs¶
Polynomial coefficients in the order used by _poly_terms.
- Type:
ndarray
- x_center, y_center
Coordinate centering for numerical stability.
- Type:
- x_scale, y_scale
Coordinate scaling for numerical stability.
- Type:
- classmethod fit(x, y, values, order=8)[source]¶
Fit a 2D polynomial to scattered data.
- Parameters:
x (ndarray, shape (N,)) – Data point coordinates (nm).
y (ndarray, shape (N,)) – Data point coordinates (nm).
values (ndarray, shape (N,)) – Field values at each point (integer registry indices).
order (int) – Maximum polynomial order (default 8).
- Return type:
- moire_metrology.strain.convex_hull_mask(data_x, data_y, query_x, query_y)[source]¶
Boolean mask of query points inside the convex hull of input data.
- Parameters:
data_x (ndarray) – Coordinates of the points used to fit a registry polynomial.
data_y (ndarray) – Coordinates of the points used to fit a registry polynomial.
query_x (ndarray) – Query coordinates (any common shape).
query_y (ndarray) – Query coordinates (any common shape).
- Returns:
inside – Same shape as
query_x. True where the point lies inside (or on the boundary of) the convex hull of the input data.- Return type:
ndarray of bool
Multi-layer¶
Multi-layer relaxation convenience APIs.
- Example:
from moire_metrology.multilayer import LayerStack from moire_metrology import GRAPHENE
stack = LayerStack(top=GRAPHENE, n_top=5, bottom=GRAPHENE, n_bottom=3, theta_twist=0.5) result = stack.solve()
- class moire_metrology.multilayer.LayerStack(moire_interface, top_interface=None, bottom_interface=None, n_top=1, n_bottom=1, theta_twist=0.0, delta=None, theta0=0.0)[source]¶
Bases:
objectA heterostructure stack of two flakes.
- Parameters:
moire_interface (Interface) – The twisted interface between the bottom layer of the top flake and the top layer of the bottom flake. Carries both materials (
moire_interface.top,moire_interface.bottom) and the GSFE coefficients of the moire stacking.top_interface (Interface or None) – Required iff
n_top > 1. The homobilayer interface inside the top flake. Must satisfytop_interface.bottom is top_interface.top is moire_interface.top.bottom_interface (Interface or None) – Required iff
n_bottom > 1. The homobilayer interface inside the bottom flake. Must satisfybottom_interface.bottom is bottom_interface.top is moire_interface.bottom.n_top (int) – Number of layers in each flake.
n_bottom (int) – Number of layers in each flake.
theta_twist (float) – Twist angle between the two flakes (degrees).
delta (float or None) – Lattice mismatch. If None, computed from material lattice constants.
theta0 (float) – Lattice orientation angle (degrees).
- property top¶
Top flake material (convenience accessor).
- property bottom¶
Bottom flake material (convenience accessor).
- solve(config=None, *, fix_top=False, fix_bottom=False, pin_mean=False)[source]¶
Run relaxation on this stack.
- Parameters:
config (SolverConfig or None) – Solver configuration. If None, uses defaults.
fix_top (bool) – Pin all DOFs of the topmost layer (the topmost layer of the top flake) to zero. Use this to simulate a rigid bulk above the heterostructure.
fix_bottom (bool) – Pin all DOFs of the bottommost layer (the deepest layer of the bottom flake) to zero. Use this to simulate a rigid bulk below the heterostructure — typical for a twisted flake on a thick substrate (e.g. graphene on graphite).
pin_mean (bool)
- Return type: