Submind YouTube summaries
Thumbnail for Fitting a SMIRNOFF Force Field with PyTorch - OpenFF 2026 Virtual Workshop

Fitting a SMIRNOFF Force Field with PyTorch - OpenFF 2026 Virtual Workshop

Watch on YouTube

Video summary

Dr. Ashley Mitchell introduces a modern workflow for fitting molecular dynamics force fields using the new PyTorch-based stack developed by Open Force Fields, marking a significant shift from their previous proprietary software, ForceBalance. The core of this new approach relies on PyTorch, a general-purpose machine learning library that utilizes tensors to handle computations efficiently on both CPUs and GPUs. A key advantage of this framework is its ability to automatically compute gradients through backpropagation, which eliminates the need for manual mathematical derivations when defining loss functions. This automation allows researchers to create arbitrary optimization strategies without worrying about the underlying calculus, as the system intelligently determines how to adjust force field parameters to minimize error. To demonstrate this capability, the presentation outlines a process that begins with preparing data from the QC Archive and modifying an existing SMIRNOFF force field to ensure it is compatible with differentiable computation engines like SMEI and Descent. Specific transformations include replacing semi-empirical charge calculations with the faster, conformer-independent AshGC neural network and removing non-differentiable constraints to create an unconstrained variant suitable for optimization. The data is then formatted using the Hugging Face dataset standard, which supports large-scale operations by backing data to files rather than memory. This setup allows for the efficient conversion of molecules into tensor topologies, where parameters are mapped to tensors, enabling the entire force field to be treated as a trainable object within the PyTorch ecosystem. The optimization process itself involves defining specific parameters to fit, such as bond lengths, angle force constants, and torsion terms, while setting appropriate limits and scaling factors to ensure stable training. Using an Adam optimizer, the system iterates through batches of data, computing energy and force predictions automatically via the differentiable engine. The loss function is constructed by combining errors in both energies and forces, and PyTorch's autograd mechanism calculates the precise gradient needed to update parameters. Throughout the training epochs, progress is monitored using TensorBoard, revealing how the total loss decreases over time as the force field adapts to reproduce quantum mechanical benchmarks more accurately. Finally, the optimized force field is extracted and benchmarked against the original data to validate the improvements. The results show a dramatic reduction in energy differences for alkanes and a significant improvement in reproducing specific torsion profiles, confirming that the new stack can effectively refine force fields. While the demonstration highlights successful overfitting to a specific dataset as a proof of concept, the speaker notes that real-world applications would benefit from larger datasets and better regularization techniques to prevent such issues. The entire workflow, including code and notebooks, is openly available under permissive MIT licenses, encouraging the community to adopt this new, flexible, and powerful method for developing next-generation force fields.
Read the full video transcript
Hi everyone. My name is Dr. Ashley Mitchell. I'm Open Force Fields uh science communicator. Um if you've seen a few of these before, you might have known me by a different name. Uh but it's still me. Um today we're going to talk about how to fit a force field with the uh new fitting stack that um Open FF is currently uh trying to implement. So our previous fitting stack uses a piece of software called force balance which is bespoke optimization software for molecular dynamics force fields and we're we're transitioning towards using PyTorch. So, PyTorch is a uh more generic generalpurpose machine learning uh package. Uh and the SMEI and Descent packages implement PyTorch optimization for uh for for molecular dynamics force fields. Uh so we're just going to demonstrate a really basic workflow using these new tools to fit and validate a Schmoff force field. So the tools we'll be using today are PyTorch which are just Python bindings to the torch C++ machine learning library SME and descent which is a differentiable computation platform force fields via PyTorch. So PyTorch provides this really nice abstraction called a tensor. You can think of it as kind of like a numpy array except instead of only running on your CPU, you can also put it on your GPU or on some sort of heterogeneous compute. And then uh the tensor also keeps track of all the operations you've performed on it so that a gradient can be automatically computed. And this is really the magic of everything that we're showing here. We'll be able to write an arbitrary loss function and then compute the gradient automatically. So we can write anything we want in our loss function. We don't have to do any maths ourselves. The computer will figure out how to modify the force field to improve it by uh relative to that loss function. Descent uh provides fitting machinery around SMEI which is the actual differentiable computation engine which basically just implements molecular dynamics force fields uh through tenses instead of like by hand through C++ and then for this demonstration I'll get some initial structures of QC archive but you can get initials uh you can get your fitting data from anywhere you And we'll use the smoff force field format which is our native force field format for uh producing force fields that don't rely on atom types. So instead of using atom types we directly infer chemistry using smirks patterns and smarts patterns. This notebook is accessible online. You can go to this GitHub URL and I'll put it in the description as well or you can follow this QR code. uh and there's instructions there on how to install all all of these software and run this notebook. So the plan today is that I've gathered some train and test data sets from QC archive. Uh if you want to see how I've done that uh it's all on the GitHub force uh GitHub repository. Uh so I won't show it in the in the video. Then we're going to modify an old schernnoff force field so that it can be fitted efficiently using the new stack. We're going to benchmark that force field against some alkanes and then demonstrate how to transform the data set that we gathered from QC archive into a format that can be ingested by the PyTorch SME descent stack. Then we'll configure and perform an optimization of the force field and evaluate that final force field against the original benchmarks. So in this section here that I've hidden, we're just gathering data from QCRO. So to do a force field fitting, you will need to get data from somewhere. This is an alternate. This is not an alternative to bespoke fit. Um uh you can get that that data from anywhere. Um as long as you can get it into the format we'll demonstrate in a sec. Uh and you can set up uh benchmarking however you like. These aren't really a part of the flow. So uh we're not demonstrating them. If you are looking for an alternative to to bespoke fit uh well bespoke fit is still available. Uh we've moved it into application mode rather than library mode. So we're not going to be updating its uh dependencies uh or its requirements. Uh but but the software is still available and you can go uh install it in the usual way. and the old environments will continue to work. Uh, and there's a new uh automated force field fitting regime being produced by the coal lab called Presto. Uh, they're collaborators of ours. Uh, their their lab worked substantially on bespoke fit and that's sort of the spiritual successor to bespoke pit uses a completely different um bidding methodology. a lot more AI and machine learning involved. Uh but it seems to work so pretty well so far. So have a Google that. Okay. So to get started, we're going to choose an initial force field. And just so we can demonstrate a really big improvement, we're going to be using the SMOF9 Frost version 1.1.0 force field. So this is one of the firstoff uh force fields that we produced uh or that was produced even before open force field was a thing. Um and and this is what all of our force fields are based on sort of uh the ancestor. So uh we're going to make two transformations to this just to make it easier to train. The first is that we're teaching we're ditching its toolkit AM1BC parameter handler and instead computing charges with Naggle. So Naggle is our uh graph neural network for molecules um library uh and we're using a neural network designed to compute charges uh from uh from molecules. So this uh this neural network is called ash GC which is a um a neural network that's designed to reproduce the AM1 BCC charges that we would have produced um previously. Um Ashg provides a bunch of advantages over computing AM1 BCC charges directly. First it's way faster. Uh so ash GC is actually fast enough to compute charges for an entire protein in uh in a matter of seconds less than a minute. Uh whereas for AM1 BCC that was completely intractable. It would have taken years and it would have used all the memory in the universe. Uh and secondly, ashgc is completely unaware of uh the uh the coordinates that you have attached to your molecule. So we can sort of guarantee that it's confirmer independent because it's not aware of your confidence. So uh so that's that first transformation. Instead of using a semi-impirical QM method to calculate charges, we calculate them with a message passing rap network. Uh secondly, we're going to create non-constrained variant. So non-constrained variant was never published with 99 frost, but they're really important for force field fitting uh because constraints are not differentiable. So uh to create an unconstrained variant, we simply dregister the constraints uh parameter handler and then save that. If you wanted to start with say Sage 2.3, you wouldn't have to do either of these transformations because it already works with uh Nagalashgc and it already has an unconstrained version published. So uh that would be much simpler. Okay. So next we're going to do an initial benchmark against a small group of of alkanes just to see uh whether there's room for improvement of this force field. Uh we're using some software called Yams. So Yams is our internal benchmarking tool that we're developing at the moment. Uh you're welcome to use it but it's not a priority of ours to make it super accessible to every user. it's more important to us that we can rapidly modify it. Um, so yeah, use it at your own risk, I guess. So, yams lets us just modify uh optimize uh a set of uh molecules from a benchmark uh against a force field that we pass it uh and then we can plot metrics from that. So here we have um delta delta E calculation by count. So uh this is the difference in the computed difference of energies between the uh quantum calculation which was the input data for the benchmark and the molecular mechanics calculation which was uh the parameters. And so what we're looking for here is a very sharp peak above zero. And we can see that this is sort of spread out on both sides and it has some uh more distant peaks as well. Uh this is just a simple cumulative density function of RMSD. So this is the root mean squared deviation between the quantum chemical minimum structures and the molecular mechanics minimum structures. uh a perfect force field that perfectly reproduced the structures of the quantum mechanics uh simulation would have a perfectly vertical line where the entire cumulative distribution function is focused at uh RSD of zero and then this is a torsion fingerprint deviation you still using a CDF so same idea but instead this is looking at the torsions and so we can see that there is some room for improvement uh on the torsions and again a perfect force field would look like a vertical line here. Um the more leftwards uh the the line moves the better. So uh those were one set of lipid benchmarks. We can look at a uh so so those were optimizations and these are actual torsions. And so we're now looking at this torsion here on this branched alkan. And we can see that our initial force field is repitulating the shape reasonably well of the quantum chemical baseline which is this black. And we can see the blue is the force field. But there's definitely some room for improvement. So uh this torsion is really what we're going to be focusing on in seeing what we improve. Okay. So we we know our target. We've got a force field that we want to improve. We've got some training data that we want to improve it against. And we know what we're looking for uh to to evaluate an improved force field. So the next thing to do is to get that uh data set into a format that SME and descent understand. So uh for that we're going to use the hugging face data set format. So hugging face uh the hugging face format is an open source format that's really designed for uh training on massive data. So it's really suit well suited here. The really big advantage is that can it can be backed by file rather than memory. So you can use this really painlessly for larger than memory uh data sets. We're not going to use it for a larger than memory data set today. Uh but having it backed by a file is the only way to do it. And it also gives us some safety if if everything crashes unexpectedly at some point or the container that you're running this calculation uh gets gets uh preempted or something then uh then you just have to save a file rather than the contents of memory. So uh the hugging face data set format is a lot like p a pandas data frame. So it's basically a dictionary that goes from strings which are column titles to lists. Each element in the list is an entry at that uh in that table column and then the rows are the elements of the list that all have the same index. Uh so there's four columns in a SME data set. There's smiles which is a mapped explicit hydrogen smiles string identifying the molecule. Uh so that not that's a very dense string format that not only uh uniquely defines the actual molecule, it also defines the ordering of the atoms. And so then the uh the numbers that go into the coordinates and forces uh tenses uh can um can use that atom ordering so that uh there's no ambiguity of where all the atoms go. The energies is uh a list of uh energies uh each row being a different molecule. Um and the the the each element of the energies list has a number of energies equal to the number of confidence that we have data for for the molecule in that row. then uh the coordinates of the of each single point calculation. But for these calculations, we'll we'll use the optimized single points for optimizations and the end of the uh optimization for uh torsion drives. Uh and and so each row will have a shape of the number of compliments. So that same number of energies and then the number of atoms and then there's three uh an xyz coordinate for each atom and the forces are similar but they they give you forces on each atom um at the energy minimum or wherever you are in the uh in the calculation for that confirmer um uh in in the same format. So uh n by3 numbers for each conf. So I've stored the QC archive data in pickle files. They're available at the GitHub repository. And so we just need to load them in and then formulate the data set. And so what this looks like is just going through and mapping each of the columns we've just talked about to whatever arcane identifier QC archive uses to uh represent that same information. And we can see that involves uh gener sometimes generating new smiles that we have to make sure we've got the orders of atoms right for um it involves some changing uh some coordinate changing. Uh we were only provided with the SCF gradient. So we have to multiply that by minus one to get us into forces which we do down here. And so, uh, we basically just build up this, uh, dictionary of of lists, uh, one by one as we go through each atom. And then we iterate over them to collapse all of the, um, the molecules that are then represented on multiple rows into a single row. That looks something like this. And then we create a data set from that. And for a data set of uh 2,105 confirm that takes about a second. Okay. So we now have our force field that we want to start with. We've benchmarked it and we've prepared our data set in a format that we that we like. So the next step is to parameterize. So that means going through all those smile strings and and mapping them to fully parameterized uh systems that are ready to compute a molecular mechanics energy from. So uh so this is this is very familiar if you've used force field before. uh you just convert each molecule into a topology uh by iterating over all the smiles and then in parallel we compute interchanges from them from the force field that we're using. So once we have interchanges, the interchange uses the force field to parameterize the molecule and then we have a map from okay this parameter in the molecule whether that be a torsion or an angle or a bond or whatever has this value and those values are tied back to the original force field through the interchange. And so uh these interchanges can then be converted to tenses. And so this is where SMEI starts to come into the picture. So uh because from now on we're basically going to be using uh PyTorch tensors, we need to define a device. So you could imagine changing this to CUDA if you wanted to compute on a on a CUDA GPU instead of a CPU. Um I'm demonstrating with a CPU because everyone has one. My machine's actually AMD. So, uh, getting it working in a way that would be, uh, usable by everyone else is a bit of a pain, but you're welcome to investigate what that looks like for you. Uh, and then we call this SME convert interchange for uh, function to convert them all into tensor topologies and tensor force fields. So a tensor force field is a tensor, one of these uh magical um uh numpy like buffed up numpy arrays and a tensor topology maps a parameter that needs a force field value to the actual force field in the tensor. And so the reason this exists is so that uh we can just optimize the tensor force field and we still retain uh our knowledge about which of these parameters is tied to each of the molecules that we're doing our optimization over. We just do a quick sanity check and then we create a uh dictionary from each smile string to the tensor topology. Then we make sure that all the tenses are on the device we want and we're ready to fit. So first we need to decide what we're going to fit. Uh and that looks like these parameter config options. So what we're saying here is that we want to fit the bonds angles and proper torsions. All the other parameters in in the force field will not be fit. We're not fitting any attributes. So here parameters and attributes are taking on slightly unusual terminology. They're terminology from the XML format that SMROF is built around. Um so occasionally you'll want to optimize something that's actually an represented as an attribute in that force field rather than as an uh in that XML format rather than as a parameter. And they are treated differently by descent. Uh this is quite rare. Uh but you might need it for um Leonard Jones things and stuff like that. Uh if you wanted to optimize the cutoff um or something like that. Pretty pretty obscure things in force field fitting but uh theoretically could be useful. And so for each parameter we want to fit uh we decide which columns within that parameter we want to fit. So uh for bonds bonds are represented by a hookian term. So there's obviously two parameters in a hooks in hooks law. There's the force constant of the bond and then the uh equilibrium length of the bond. And so we say we want to fit both the force constant and the length and we define their relative scale. Uh so most optimizers work better if every number they're trying to optimize is about the same size. So we just scale everything so they're the same size. Uh and then we set limits. So it doesn't make sense for the force constant or the length to be less than zero. So we set limits of uh the lower bound is zero and the upper bound is none. Angles look very similar because they're also hooking. And then the proper torsions look a bit different because we're only looking at scaling the force constant. Um uh so so we could also uh optimize the phase and the um potentially the number of of terms but that's uh that's an integer which makes it a bit more difficult. Uh so that's the magic. Uh we we decide which parameters we want to fill. And so if you're optimizing your force field yourself, you might just want to stick to the proper torsions. You might want to bring in other parameters that we haven't optimized here. Uh you might want to optimize the phase along with everything else. Um but this is where you configure that. And then once we have those parameters and attributes and the tensor tops by smiles or actually the tensor force field, we can produce this trainable object. And this trainable object is just a wrapper around the tensor force field that allows us to get out a tensor that only includes the scaled parameters that we actually want to modify. Uh okay. And then we're going to perform the actual optimization. So this is a huge amount of code. Well, it's not a huge amount of code. It's like less than 100 lines. It's but it's conceptually quite dense because this code forms the entire optimization. So we first set up some configuration. Uh we set the number of epochs. So an epoch is a basically this is going to be a loop of things that we do and an epoch is a single iteration of that loop. though. Um whatever your uh whatever process you you perform during your optimization um the number of times you do that entire process is is the number of epochs. Um our loss function that we're defining uh it can be parameterized by the learning and force weights. Uh the loss function is completely arbitrary. You can choose whatever you want. This is just a demonstration that um we could have some of that configuration outside the the loop. Uh then we set a learning rate. This hasn't been optimized. Might be completely wrong. Uh but this is just how uh how quickly the optimizer pushes the force field uh down the the ramp basically. Uh so you can get better bidding in fewer epochs by optimizing this. It doesn't like making it too big or too small can both be bad. Um this the script I'm going to show you supports uh processing data sets that are larger than memory by doing it in batches. uh because we're using the uh hugging face data format, we can use this train this data loader class that lets us do that all automatically and we can just define the size of the batches. So you'd want to tune this so that each batch fits in memory but that it's as large as possible so that uh uh you get the advantages of um of cing and stuff like that. So we're just going to set it to the length of the entire data set so that there's only one batch so that we don't have to deal with that complexity. Uh we set a directory uh to to write some logs out to. So we're using PyTorch which is uh an open-source machine learning algorithm. So its competitor from Meta is called TensorFlow. And then Meta also puts out a uh piece of software to monitor TensorFlow optimizations called Tensor Board. And because Torch is open source, there's a a compatibility layer called TensorBoard X. And so we're just defining the log directory that we're going to write our TensorBoard uh files out to so that we can read it. And then we just say okay what do we want to actually train? We want to train the data set that we produced earlier. Uh okay and then we we set up the data loader. So the data loader we're just doing things like uh maybe we need to microoptimize the memory. So we'll pin that. uh we need a function that uh that collates data from the data set, but that's not super important because we're using hugging face. So we're just using a an identity function. Uh and we set the batch size here as well. And so then we can iterate over the batch the train over the data loader to get our batches out. Then we take our trainable which remember is that wrapper around the tensor force field and the configuration of the optimizer that lets us pull out scaled uh values that we want to optimize and we convert it to those values and then put the put it on the device that we want to run our calculations on. So for this calculation will be CPU but maybe for yours it's GPU or CUDA or something else. And then finally we set up the optimizer. So, we're using Atom, which is like the optimizer everyone says to use. It's a general purpose adaptive learning rate optimizer that you can throw anything at it and it'll do okay on. Uh so, uh that that's a that's an easy choice. Uh so, all we need to give Adam once we've set this stuff up is the gradient uh with respect to the loss function and the values of the loss function. and it will and the current values of the parameters and it will push those parameters towards uh minimizing the loss function. Very cool little bit of software this this optimizer. Okay. So then we set up tensorport x we set up a progress monitor and then we start iterating through our epochs. So first thing we do is we reinitialize all our intermediate values so that uh so that we don't uh carry over any uh information from the previous epoch by accident. So we zero out our loss and our gradient and we uh get new force a new force field that we can train. Then we uh uh compute our loss function in batches. So this is where the real magic happens. So first we just go through and get the batch that we want to compute and put it on the device we want. So this looks like uh put put the values on the device uh for each sample within the batch. We're iterating over the data loader. So that'll be the batch. Uh so then we get the length of the batch because the batches are not necessarily all the same size. The last one might be smaller for instance. And then we predict the energies and forces uh for that batch. So this is the first bit of magic is descent as this autoiffiable molecular mechanics energy computation method. And so it takes in a batch of uh of of data which uh uh which gives us uh all the all the data we want to train against takes in a force field and those topologies that we created earlier. So, so we now have uh what we want to train the force field against where the force field is currently up to in its journey and uh a mapping from the molecules in the batch to uh to to definitions for which parts of the force field it should use. Then we just have uh a mean as a as an additional parameter that you probably don't need to change. uh and then it spits out both the reference energies and the computed energy and the reference force and the predicted force from all of that. So these reference energies come from the batch come from the data set that we're training against and these predicted forces and energies come from uh the topologies and the force fields and remember these predicted force fields have been computed completely uh autoiffiably. So uh as long as we use them to compute the loss function, we will retain the dependence that they have on the force field, which is where the magic happens. So that's the first bit of magic. Here's the second bit of magic. This doesn't look like magic because it's literally just a very boring um Python math like a couple of lines, right? So this works exactly the same way as a numpy array. These uh tenses are getting broadcast against each other and then um squared individually and then we sum them and divide them by the true batch size. We do that the same for the force and this computes an L2 loss for both the energy and the force. And remember because these are tenses this is still being differentiated. So we still know what these values uh what their different what their derivative is with respect to this original force field. And then we combine them with those weights that we used earlier into a total loss function. And then uh the the last bit of absolute magic is that we auto differentiate them with autograd. And so autograd takes in that loss function that we computed and then the trainable parameters the parameters that we wanted to train against and it gives us a gradient. So this gradient is another tensor that tells uh tells the optimizer which direction to push the force field in in that very high dimensional space uh to improve the force field with respect to that batch that that loss function. So you can write like any math function you can imagine in here as long as it depends on this force field through differentiable PyTorch functions which is basically all of the PyTorch maths functions. This line of code will produce a gradient that tells you how those that loss function differs with the force field. So you can put regularization in here, you can come up with some obscure scheme for incorporating I don't know anything you want. Um if you can express it as a number that gets smaller as the force field gets better, you put that number into this batch loss uh variable and you'll get a derivative out of it. So okay. So then um that's the gradient for the for the current batch. The gradient for all batches combined is just the sum of all the gradients for the batches. So uh the first time we go through a batch we set the the the epoch gradient as opposed to the batch gradient to the batch gradient. And then each other time we just add the batch gradient to the epoch gradient. Uh we do this in this weird obscure way instead of like collecting all of the batch gradients and then summing them at the end because we just want to keep the memory as free as possible. Uh and this detach function um tells it that we no longer need to track where that this derivative comes from. So again, we're never going to differentiate the gradient with respect to the force field, right? We've already computed the gradient. So we detach it so that it it knows not to keep a track of that. Um which obviously gets more and more complicated for it as it incorporates more and more information and by this stage it's incorporated quite a lot of information. Okay. So then uh we uh we compute uh the the total losses for the epoch both in terms of energy and force so that we can write it out to tensorboard. uh we're we're doing detaches there as well because we don't need to keep track of the gradient anymore. And then we write those results out to TensorBoard. And finally, we perform the actual optimization step. So here we're saying the gradient of the trainable parameters is that gradient we just computed. Optimizer take a step. You already know where to find the gradient because we gave you the trainable parameters earlier. and then uh optimizer zero out your gradient so that in the next day beat everything's clean. Okay, you can see I ran this for about an hour uh before recording this uh and we got a nice progress bar uh for all 5,000 epochs uh and our loss function is down to 20. We can actually see if that's any any improvement by looking at uh tensorboard which we can do in the notebook. You probably wouldn't necessarily do this uh for a longer running uh fit but but you can. And so from here we can see the total loss function has uh improved over the whole course of the fit. So this fit could probably go for longer and we'd still see improvement. If we break that down into energy and forces, we see that the energy actually worsens for the first couple of frames of the fit. And that's because the forces are dragging it down. And then uh once the energy has sort of reached over this pump and gotten into a new regime, uh both the forces and the energy improve together. And that looks like in aggregate a a smooth improvement of the overall loss function even though initially there was a worsening of the energy. And so you can imagine that these dynamics might be different if we modified the weights of the losses of of the different contributors to the loss function. Okay, so we've now done our optimization. We just need to get uh our force field back out. So uh the trainable still holds uh the original force field. So we just give it the optimized parameters and it will give us a tensor force field and then we just uh convert that tensor force field to shownoff force field. Uh this write shernoff uh function may eventually make its way into uh a published release of something but it's not there yet. Uh so uh it's it's in the uh GitHub repository, so you'll be able to use it if you download this notebook. Um uh but yeah, might have some bugs. We're not sure. Uh it's probably okay. Then uh we can look at this line by line. So here is each modified line next to its original line. You can see these are formatted a little bit differently, but basically we're looking at uh the lengths, you know, changed a little bit. The force constants have changed a little bit more. Units are still the same. So, this looks like a force field that's gone through an optimization. The numbers are all different. Um, we can see that the parameters that have actually changed are parameters that are relevant to alkanes, right? All of these are talking about carbons and hydrogens. Um, and we've got bonds, angles, and proper torsions, which is what we asked for it to optimize in the proper torsions. Periodicities haven't changed, phases haven't changed, just the force constants have changed. That's all what we asked for. So, it looks like we got what we wanted. So, let's benchmark it and see if we've improved things or if the loss function was lying to us. So I'm passing in both the original force field and the optimized one. Um we've done all of this with the unconstrained force field because optimizations work much better if force fields are differentiable. Uh and we use the same mole store and torsion store that we had before. Uh give it uh with the same molecules and we give it the new force field. So we can see that uh this orange um series is the optimized force field and it's moved this blue block much closer to the rest of it. Apart from that hasn't changed much. Uh here the orange is generally more to the left. So we can infer that this is a generally improved force field even though we might have some worsening of some molecules around here. Uh and then the torsions have not improved. That's interesting. Uh so there's a little bit of um stochasticity to these fits. Uh when I've run this previously, it has improved. Uh but I guess not on this one. So let's look at the actual torsion we were interested in. We see that this one torsion has improved dramatically. So uh it's now almost perfectly reproducing the the torsion profile. And that's probably because we were cheating. uh we uh have we've really overfitted this force field to alkanes, right? So uh uh this this torsion looks really good, but perhaps some other force fields uh some other molecules might perform much worse. Um but that's that's a win for us. We wanted to uh improve our force field on this one torsion and we've accomplished that. And you can imagine that the ways that we'd make it more resistant to this sort of overfitting would be add a lot more data uh think about regularization uh um uh and and fit against generally just uh just more data and uh and and use a a a really good force field as a starting point rather than the oldest one we can possibly think of. Okay, so thanks for sitting through that. What we've done is we've grabbed some data off QC archive behind the scenes and then we've processed that into a format that our new PyTorchbased fitting stack can understand. We've uh uh prepared a force field so that it can be really efficiently applied to all of our molecules and then we've applied it to all of our molecules. uh and then we've configured and uh and optimized the the force field with respect to those molecules and we've seen that our benchmarks improve. Um so all this code is freely available. Um again I'll go back up to the top and you can uh look at the description or go to these links uh uh at for open force field we we do uh open science forever. So these are all licensed under MIT very permissive licenses. Uh so uh please have a go um and please feel free to raise issues either at the repository for this notebook or at any of our other um GitHub repositories if there's any dramas any anything that uh anything that you think could be improved uh or anything that went unexpectedly wrong. We'd love to hear from you. Um, so I'm Ashley Mitchell and thanks for watching.