Dealing with additional node features and derivatives w.r.t. them #231
-
We are specifically dealing with atomic clusters of around 40-50 atoms (single species for now) where the atoms have something in addition to their positions as an input feature. The output we need to learn is the cluster energy and force on each atom. Here are a couple of questions I have in this regard:
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 21 replies
-
Hi @shasax , Sounds like an interesting project! Question 1: Almost. Providing that
The correct way to do this is to define a new "model builder": a function that builds/updates the model in
You will want to replace def MyFancyEnergyModel(
config, initialize: bool, dataset: Optional[AtomicDataset] = None
) -> SequentialGraphNetwork:
logging.debug("Start building the network model")
builder_utils.add_avg_num_neighbors(
config=config, initialize=initialize, dataset=dataset
)
num_layers = config.get("num_layers", 3)
layers = {
# -- Encode --
"one_hot": (OneHotAtomEncoding, dict(set_features=False)), # !!!
"spharm_edges": SphericalHarmonicEdgeAttrs,
"radial_basis": RadialBasisEdgeEncoding,
# -- Embed features --
"chemical_embedding": AtomwiseLinear,
}
# add convnet layers
# insertion preserves order
for layer_i in range(num_layers):
layers[f"layer{layer_i}_convnet"] = ConvNetLayer
# .update also maintains insertion order
layers.update(
{
# TODO: the next linear throws out all L > 0, don't create them in the last layer of convnet
# -- output block --
"conv_to_output_hidden": AtomwiseLinear,
"output_hidden_to_scalar": (
AtomwiseLinear,
dict(irreps_out="1x0e", out_field=AtomicDataDict.PER_ATOM_ENERGY_KEY),
),
}
)
layers["total_energy_sum"] = (
AtomwiseReduce,
dict(
reduce="sum",
field=AtomicDataDict.PER_ATOM_ENERGY_KEY,
out_field=AtomicDataDict.TOTAL_ENERGY_KEY,
),
)
return SequentialGraphNetwork.from_parameters(
shared_params=config,
layers=layers,
irreps_in={"node_features": "4x0e"} #!!!
) I've marked edited lines with
If you want to do the version of this where you concatenate the one-hot with your features, you'd add another module to the Question 2: This is not possible from the config file alone, but is possible in a straightforward and supported way again using model builders. With the same set up as before, we can modify the existing model builder for forces (https://github.com/mir-group/nequip/blob/main/nequip/model/_grads.py) to wrap the model: def NodeFeaturesGradOutput(model: GraphModuleMixin) -> GradientOutput:
return GradientOutput(
func=model,
of=AtomicDataDict.TOTAL_ENERGY_KEY,
wrt=AtomicDataDict.NODE_FEATURES_KEY,
out_field="node_feature_forces",
sign=-1, # force is the negative gradient
) Then you just add this builder to your list in your YAML config:
|
Beta Was this translation helpful? Give feedback.
Hi @shasax ,
Sounds like an interesting project!
Question 1: Almost. Providing that
key_mapping
will give it as input to the model, but you also need to:node_features
or replace itOneHotAtomEncoding
module in the model not to override your providednode_features
The correct way to do this is to define a new "model builder": a function that builds/updates the model in
nequip
. By default,nequip
uses the following list of model builders, which I'm showing here as you would specify them in the YAML con…