1010import metatensor .torch as mts
1111import torch
1212from metatensor .torch import Labels , LabelsEntry , TensorBlock , TensorMap
13+ from metatensor .torch .learn import nn
1314from metatomic .torch import ModelOutput , System
1415
1516from .documentation import FixedCompositionWeights # noqa: F401
1617
1718
18- class BaseCompositionModel (torch . nn .Module ):
19+ class BaseCompositionModel (nn .Module ):
1920 """
2021 Fits a composition model for a dict of targets.
2122
@@ -88,9 +89,11 @@ def __init__(
8889 self .atomic_types = torch .as_tensor (atomic_types , dtype = torch .int32 )
8990 self .target_names = []
9091 self .sample_kinds = {}
91- self .XTX = {}
92- self .XTY = {}
93- self .weights = {}
92+ # `XTX` and `XTY` are only used during fitting, not at inference, so they
93+ # are registered as non-persistent buffers to keep them out of the state_dict.
94+ self .register_buffer ("XTX" , {}, persistent = False )
95+ self .register_buffer ("XTY" , {}, persistent = False )
96+ self .register_buffer ("weights" , {})
9497
9598 # go from an atomic type to its position in `self.atomic_types`
9699 self .register_buffer (
@@ -167,7 +170,6 @@ def add_output(self, target_name: str, layout: TensorMap) -> None:
167170 values = torch .zeros (
168171 len (self .atomic_types ),
169172 len (self .atomic_types ),
170- dtype = torch .float64 ,
171173 ),
172174 samples = Labels (["center_type" ], self .atomic_types .reshape (- 1 , 1 )),
173175 components = [],
@@ -186,7 +188,6 @@ def add_output(self, target_name: str, layout: TensorMap) -> None:
186188 len (self .atomic_types ),
187189 * [len (c ) for c in block .components ],
188190 len (block .properties ),
189- dtype = torch .float64 ,
190191 ),
191192 samples = Labels (["center_type" ], self .atomic_types .reshape (- 1 , 1 )),
192193 components = block .components ,
@@ -203,7 +204,6 @@ def add_output(self, target_name: str, layout: TensorMap) -> None:
203204 len (self .atomic_types ),
204205 * [len (c ) for c in block .components ],
205206 len (block .properties ),
206- dtype = torch .float64 ,
207207 ),
208208 samples = Labels (["center_type" ], self .atomic_types .reshape (- 1 , 1 )),
209209 components = block .components ,
@@ -239,10 +239,15 @@ def accumulate(
239239 :param targets: Dict of target names to :py:class:`TensorMap` containing
240240 the target values for each system in the batch.
241241 """
242-
243242 device = systems [0 ].positions .device
244243 dtype = systems [0 ].positions .dtype
245- self ._sync_device_dtype (device , dtype )
244+
245+ if dtype != torch .float64 :
246+ raise ValueError (
247+ "Composition model accumulation must be done in float64. "
248+ f"Got systems with dtype { dtype } . Please move the systems to "
249+ "float64 before accumulating."
250+ )
246251
247252 # check that the systems contain no unexpected atom types
248253 for system in systems :
@@ -386,6 +391,15 @@ def fit(
386391 if targets_to_fit is None :
387392 targets_to_fit = self .target_names
388393
394+ if len (targets_to_fit ) > 0 :
395+ dtype = self .XTX [targets_to_fit [0 ]].block (0 ).values .dtype
396+ if dtype != torch .float64 :
397+ raise ValueError (
398+ "Composition model fitting must be done in float64. "
399+ f"Got dtype { dtype } . Please move the model to "
400+ "float64 before fitting (e.g. `model.to(dtype=torch.float64)`)."
401+ )
402+
389403 sanitized_fixed_weights = self ._sanitize_fixed_weights (fixed_weights )
390404
391405 # fit
@@ -486,10 +500,7 @@ def forward(
486500 :raises ValueError: If no weights have been computed or if `outputs` keys
487501 contain unsupported keys.
488502 """
489-
490503 device = systems [0 ].positions .device
491- dtype = systems [0 ].positions .dtype
492- self ._sync_device_dtype (device , dtype )
493504
494505 # Build the sample labels that are required
495506 _ , sample_labels = _get_system_indices_and_labels (systems , device )
@@ -616,32 +627,6 @@ def _compute_X_per_atom(
616627 )
617628 return one_hot_encoding .to (dtype )
618629
619- def _sync_device_dtype (self , device : torch .device , dtype : torch .dtype ) -> None :
620- """
621- Move the accumulated quantities and the fitted weights to the given
622- device and dtype.
623-
624- Needed because they are stored as ``TensorMap`` dicts, which
625- ``torch.nn.Module.to`` does not move.
626-
627- :param device: Device to move the quantities to.
628- :param dtype: Dtype to convert the quantities to.
629- """
630- self .atomic_types = self .atomic_types .to (device = device )
631- self .type_to_index = self .type_to_index .to (device = device )
632- self .XTX = {
633- target_name : tm .to (device = device , dtype = dtype )
634- for target_name , tm in self .XTX .items ()
635- }
636- self .XTY = {
637- target_name : tm .to (device = device , dtype = dtype )
638- for target_name , tm in self .XTY .items ()
639- }
640- self .weights = {
641- target_name : tm .to (device = device , dtype = dtype )
642- for target_name , tm in self .weights .items ()
643- }
644-
645630
646631def _include_key (key : LabelsEntry ) -> bool :
647632 """
0 commit comments