From a327176e6b0a720c4318b2837b3bfc79f26b74b4 Mon Sep 17 00:00:00 2001 From: Doris Mai Date: Fri, 12 Jun 2026 15:21:20 -0700 Subject: [PATCH] fix(mask): normalize Fmask FFT by cell volume to match Fprotein scale --- SFC_Torch/Fmodel.py | 8 +++++--- SFC_Torch/mask.py | 9 +++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/SFC_Torch/Fmodel.py b/SFC_Torch/Fmodel.py index 24365f7..6258241 100644 --- a/SFC_Torch/Fmodel.py +++ b/SFC_Torch/Fmodel.py @@ -754,7 +754,7 @@ def calc_fsolvent( rs_grid, solvent_percent=solventpct, exponent=exponent, ) # type: ignore if not self.HKL_array is None: - self.Fmask_HKL = realmask2Fmask(self.real_grid_mask, self.HKL_array) + self.Fmask_HKL = realmask2Fmask(self.real_grid_mask, self.HKL_array, cell_volume=self.unit_cell.volume) zero_hkl_bool = torch.tensor(self.dHKL <= dmin_nonzero, device=self.device) self.Fmask_HKL[zero_hkl_bool] = torch.tensor( 0.0, device=self.device, dtype=torch.complex64 @@ -762,7 +762,7 @@ def calc_fsolvent( if Return: return self.Fmask_HKL else: - self.Fmask_asu = realmask2Fmask(self.real_grid_mask, self.Hasu_array) + self.Fmask_asu = realmask2Fmask(self.real_grid_mask, self.Hasu_array, cell_volume=self.unit_cell.volume) zero_hkl_bool = torch.tensor(self.dHasu <= dmin_nonzero, device=self.device) self.Fmask_asu[zero_hkl_bool] = torch.tensor( 0.0, device=self.device, dtype=torch.complex64 @@ -1373,7 +1373,9 @@ def calc_fsolvent_batch( real_grid_mask = rsgrid2realmask( rs_grid, solvent_percent=solventpct, exponent=exponent, Batch=True ) # type: ignore - Fmask_batch_j = realmask2Fmask(real_grid_mask, HKL_array, end - start) + Fmask_batch_j = realmask2Fmask( + real_grid_mask, HKL_array, cell_volume=self.unit_cell.volume, batchsize=end - start + ) if j == 0: Fmask_batch = Fmask_batch_j else: diff --git a/SFC_Torch/mask.py b/SFC_Torch/mask.py index c0fc7eb..29f62ba 100644 --- a/SFC_Torch/mask.py +++ b/SFC_Torch/mask.py @@ -80,7 +80,7 @@ def rsgrid2realmask(rs_grid, solvent_percent=0.50, exponent=50.0, Batch=False): return real_grid_mask -def realmask2Fmask(real_grid_mask, H_array, batchsize=None): +def realmask2Fmask(real_grid_mask, H_array, cell_volume=1.0, batchsize=None): """ Convert real space solvent mask grid to mask structural factor, in a fully differentiable manner @@ -93,12 +93,17 @@ def realmask2Fmask(real_grid_mask, H_array, batchsize=None): H_array: array-like, int The HKL list we are interested in to assign structural factors + cell_volume: float + Unit cell volume, used to normalize the FFT so that Fmask is on the same + absolute (electron) scale as the atomic structure factors Fprotein. + Fmask(h) = (V/N) * sum_j rho_j * exp(2*pi*i*h.r_j) + Return: ------- torch.complex64 tensor Solvent mask structural factor corresponding to the HKL list in H_array """ - Fmask_grid = torch.fft.ifftn(real_grid_mask, dim=(-3, -2, -1), norm="forward") + Fmask_grid = torch.fft.ifftn(real_grid_mask, dim=(-3, -2, -1)) * cell_volume tuple_index = tuple(torch.tensor(H_array.T, device=Fmask_grid.device, dtype=int)) # type: ignore if batchsize is not None: Fmask = Fmask_grid[(slice(None), *tuple_index)]