-
Notifications
You must be signed in to change notification settings - Fork 108
Fix/ase fire deform grad forces #602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,25 +247,8 @@ def bfgs_step( # noqa: C901, PLR0915 | |
| ) | ||
|
|
||
| if isinstance(state, CellBFGSState): | ||
| # Get current deformation gradient | ||
| # reference_cell.mT: [S, 3, 3], row_vector_cell: [S, 3, 3] | ||
| cur_deform_grad = cell_filters.deform_grad( | ||
| state.reference_cell.mT, state.row_vector_cell | ||
| ) # [S, 3, 3] | ||
|
|
||
| # Transform forces to scaled coordinates | ||
| # forces: [N, 3], cur_deform_grad[system_idx]: [N, 3, 3] | ||
| forces_scaled = torch.bmm( | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fire, bfgs, and l-bfgs all individually calculate frac_positions which we will use the |
||
| state.forces.unsqueeze(1), # [N, 1, 3] | ||
| cur_deform_grad[state.system_idx], # [N, 3, 3] | ||
| ).squeeze(1) # [N, 3] | ||
|
|
||
| # Current fractional positions | ||
| # positions: [N, 3] -> frac_positions: [N, 3] | ||
| frac_positions = torch.linalg.solve( | ||
| cur_deform_grad[state.system_idx], # [N, 3, 3] | ||
| state.positions.unsqueeze(-1), # [N, 3, 1] | ||
| ).squeeze(-1) # [N, 3] | ||
| forces_scaled = state.deform_grad_forces() # [N, 3] | ||
| frac_positions = state.frac_positions() # [N, 3] | ||
|
|
||
| # Pack into dense tensors [N, 3] -> [S, M, 3] -> [S, D] | ||
| # For cell state, prev_positions is already fractional (stored that way) | ||
|
|
@@ -495,15 +478,7 @@ def bfgs_step( # noqa: C901, PLR0915 | |
| # Apply position step in fractional space, then convert to Cartesian | ||
| new_frac = frac_positions + flat_step # [N, 3] | ||
|
|
||
| new_deform_grad = cell_filters.deform_grad( | ||
| state.reference_cell.mT, state.row_vector_cell | ||
| ) # [S, 3, 3] | ||
| # new_positions = new_frac @ deform_grad^T | ||
| new_positions = torch.bmm( | ||
| new_frac.unsqueeze(1), # [N, 1, 3] | ||
| new_deform_grad[state.system_idx].transpose(-2, -1), # [N, 3, 3] | ||
| ).squeeze(1) # [N, 3] | ||
| state.set_constrained_positions(new_positions) # [N, 3] | ||
| state.set_constrained_positions(state.positions_from_frac(new_frac)) # [N, 3] | ||
| else: | ||
| state.prev_positions = state.positions.clone() # [N, 3] | ||
| state.prev_forces = state.forces.clone() # [N, 3] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,7 @@ | |
| import torch_sim.math as tsm | ||
| from torch_sim.models.interface import ModelInterface | ||
| from torch_sim.optimizers.state import BFGSState, FireState, LBFGSState, OptimState | ||
| from torch_sim.state import SimState | ||
| from torch_sim.state import DeformGradMixin, SimState | ||
|
|
||
|
|
||
| MAX_LOG_DEFORM = 2.0 | ||
|
|
@@ -299,7 +299,7 @@ def unit_cell_step[T: AnyCellState](state: T, cell_lr: float | torch.Tensor) -> | |
| cell_lr = cell_lr.expand(state.n_systems) | ||
|
|
||
| # Get current deformation gradient | ||
| cur_deform_grad = deform_grad(state.reference_cell.mT, state.row_vector_cell) | ||
| cur_deform_grad = state.deform_grad() | ||
|
|
||
| # Calculate cell positions from current deformation gradient | ||
| cell_factor_expanded = state.cell_factor.expand(state.n_systems, 3, 1) | ||
|
|
@@ -371,7 +371,7 @@ def compute_cell_forces[T: AnyCellState]( | |
|
|
||
| if is_frechet: | ||
| # Frechet cell force computation | ||
| cur_deform_grad = deform_grad(state.reference_cell.mT, state.row_vector_cell) | ||
| cur_deform_grad = state.deform_grad() | ||
| ucf_cell_grad = torch.bmm( | ||
| virial, torch.linalg.inv(torch.transpose(cur_deform_grad, 1, 2)) | ||
| ) | ||
|
|
@@ -392,7 +392,7 @@ def compute_cell_forces[T: AnyCellState]( | |
| else: # Unit cell force computation | ||
| # Note (AG): ASE transforms virial as: | ||
| # virial = np.linalg.solve(cur_deform_grad, virial.T).T | ||
| cur_deform_grad = deform_grad(state.reference_cell.mT, state.row_vector_cell) | ||
| cur_deform_grad = state.deform_grad() | ||
| virial_transformed = torch.linalg.solve( | ||
| cur_deform_grad, virial.transpose(-2, -1) | ||
| ).transpose(-2, -1) | ||
|
|
@@ -424,10 +424,9 @@ def get_cell_filter(cell_filter: "CellFilter | tuple") -> CellFilterFuncs: | |
|
|
||
|
|
||
| @dataclass(kw_only=True) | ||
| class CellOptimState(OptimState): | ||
| class CellOptimState(OptimState, DeformGradMixin): | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. adding this mixin is the key fix of this PR, it gives the |
||
| """State class for cell optimization.""" | ||
|
|
||
| reference_cell: torch.Tensor | ||
| cell_filter: CellFilterFuncs | ||
| cell_factor: torch.Tensor = field(default_factory=lambda: None) | ||
| pressure: torch.Tensor = field(default_factory=lambda: None) | ||
|
|
@@ -453,6 +452,45 @@ class CellOptimState(OptimState): | |
| "frechet_method", | ||
| } | ||
|
|
||
| def deform_grad_forces(self) -> torch.Tensor: | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered adding this function to DeformGradMixin but decided against it since it needs forces and system_idx which are missing from DeformGradMixin but CellOptimState provides |
||
| """Atomic forces in deformation gradient space, ``forces @ deform_grad``. | ||
|
|
||
| Mirrors the transform ASE's ``get_forces_unitcellfilter`` and | ||
| ``get_forces_frechet`` apply to the atomic forces. Equals ``forces`` when | ||
| the cell is undeformed relative to the reference cell. | ||
|
|
||
| Returns: | ||
| The transformed atomic forces, shape (n_atoms, 3) | ||
| """ | ||
| # per-atom row vector @ its system's deform_grad: | ||
| # (n_atoms, 1, 3) @ (n_atoms, 3, 3) -> (n_atoms, 1, 3) -> (n_atoms, 3) | ||
| return torch.bmm( | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ase's version of this function is here: |
||
| self.forces.unsqueeze(1), self.deform_grad()[self.system_idx] | ||
| ).squeeze(1) | ||
|
|
||
| def frac_positions(self) -> torch.Tensor: | ||
| """Atomic positions in the reference cell frame, ``solve(deform_grad, r)``. | ||
|
|
||
| Returns: | ||
| The reference-frame positions, shape (n_atoms, 3) | ||
| """ | ||
| return torch.linalg.solve( | ||
| self.deform_grad()[self.system_idx], self.positions.unsqueeze(-1) | ||
| ).squeeze(-1) | ||
|
|
||
| def positions_from_frac(self, frac_positions: torch.Tensor) -> torch.Tensor: | ||
| """Cartesian positions from reference-frame positions, ``frac @ deform_grad.mT``. | ||
|
|
||
| Args: | ||
| frac_positions: Reference-frame positions, shape (n_atoms, 3) | ||
|
|
||
| Returns: | ||
| The Cartesian positions, shape (n_atoms, 3) | ||
| """ | ||
| return torch.bmm( | ||
| frac_positions.unsqueeze(1), self.deform_grad()[self.system_idx].mT | ||
| ).squeeze(1) | ||
|
|
||
|
|
||
| @dataclass(kw_only=True) | ||
| class CellFireState(CellOptimState, FireState): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is such a subtle bug that ONLY affected FIRE, and NOT the BFGS or L-BFGS optimizers since the BFGS optimizers read the
reference_celldirectly. whereas if you look at fire, it looked forgetattr(state, "reference_row_vector_cell",There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could've fixed this bug by making the fire implementation match the other 2, but it's cleaner to just add the DeformGradMixin to the CellOptimState and use shared helper functions