89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
"""Lateral Force Simulation — topography artifacts in friction channels."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from backend.node_registry import register_node
|
|
from backend.data_types import DataField
|
|
|
|
|
|
@register_node(display_name="Lateral Force Simulation")
|
|
class LateralForceSim:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"field": ("DATA_FIELD",),
|
|
"direction": (["forward", "reverse", "both"],),
|
|
"friction_coefficient": ("FLOAT", {
|
|
"default": 0.3, "min": 0.0, "max": 10.0, "step": 0.01,
|
|
}),
|
|
"adhesion": ("FLOAT", {
|
|
"default": 1e-9, "min": 0.0, "max": 1e-6, "step": 1e-12,
|
|
}),
|
|
"load": ("FLOAT", {
|
|
"default": 10e-9, "min": 1e-12, "max": 1e-6, "step": 1e-12,
|
|
}),
|
|
}
|
|
}
|
|
|
|
OUTPUTS = (
|
|
('DATA_FIELD', 'forward'),
|
|
('DATA_FIELD', 'reverse'),
|
|
)
|
|
FUNCTION = "process"
|
|
|
|
DESCRIPTION = (
|
|
"Simulates topography-induced artifacts in lateral force (friction) "
|
|
"microscopy channels. Computes the lateral force signal from surface "
|
|
"slope, friction coefficient, adhesion, and normal load for forward "
|
|
"and/or reverse scan directions. Based on a contact-mechanics model "
|
|
"where the measured lateral force depends on the local tilt angle of "
|
|
"the sample surface."
|
|
)
|
|
|
|
KEYWORDS = ("lfm", "friction", "ffm", "tribology")
|
|
|
|
def process(
|
|
self,
|
|
field: DataField,
|
|
direction: str,
|
|
friction_coefficient: float,
|
|
adhesion: float,
|
|
load: float,
|
|
) -> tuple:
|
|
data = np.asarray(field.data, dtype=np.float64)
|
|
|
|
# Surface slope angle from x-gradient of topography
|
|
dz_dx = np.gradient(data, axis=1)
|
|
theta = np.arctan2(dz_dx, field.dx)
|
|
|
|
sin_theta = np.sin(theta)
|
|
cos_theta = np.cos(theta)
|
|
|
|
va = load * sin_theta
|
|
vc = cos_theta
|
|
vb = friction_coefficient * (load * vc + adhesion)
|
|
vd = friction_coefficient * sin_theta
|
|
|
|
# Forward scan (tip moves in +x)
|
|
denom_fwd = vc - vd
|
|
safe_fwd = np.abs(denom_fwd) >= 1e-30
|
|
lateral_forward = np.where(safe_fwd, (va + vb) / np.where(safe_fwd, denom_fwd, 1.0), 0.0)
|
|
|
|
# Reverse scan (tip moves in -x)
|
|
denom_rev = vc + vd
|
|
safe_rev = np.abs(denom_rev) >= 1e-30
|
|
lateral_reverse = np.where(safe_rev, -(va - vb) / np.where(safe_rev, denom_rev, 1.0), 0.0)
|
|
|
|
if direction == "forward":
|
|
lateral_reverse = lateral_forward.copy()
|
|
elif direction == "reverse":
|
|
lateral_forward = lateral_reverse.copy()
|
|
|
|
out_fwd = field.replace(data=lateral_forward, si_unit_z="N")
|
|
out_rev = field.replace(data=lateral_reverse, si_unit_z="N")
|
|
|
|
return (out_fwd, out_rev)
|