Loading...
Searching...
No Matches
Incremental computation of the center of mass

SAMSON has a signals and slots mechanism on which SAMSON Extensions may rely to develop adaptive and incremental algorithms, process events (e.g. structural, visual events, etc). There are many different events emitted in SAMSON, please, refer to the Signals and slots section for more information.

In this tutorial, you will learn how to process signals in SAMSON on an example of an App that incrementaly computes the center of mass of the system when atoms move. Re-computing the center of mass of the whole system when just a few atoms moved might be computationally heavy and unnecessary, we can rather update the center of mass incrementally based only on atoms that moved by modifying their contribution into the center of mass. When an atom is moved via e.g. the point editor it emits the SBStructuralEvent::AtomPositionChanged event, it is this event which we will be using in this tutorial to incrementally update the center of mass.

The source code for an Extension created in this tutorial can be found at https://github.com/1A-OneAngstrom/SAMSON-Developer-Tutorials.

Generating an Extension

We will start by creating a new SAMSON Extension called CenterOfMass thanks to the SAMSON Extension generator (please, refer to the SAMSON Extension generator tutorial for a reminder on how to use it):

The SAMSON Extension generator generates an app class that implements the functionality of the app and is derived from the SBDApp class, and a GUI class that implements the user interface of the app and is derived from the SBGApp class.

Setting the description of the Extension

Open the header file of the app descriptor (file: SECenterOfMassAppDescriptor.hpp) and modify the class description:

SB_CLASS_DESCRIPTION("DevTutorial: Center of mass");
#define SB_CLASS_DESCRIPTION(CLASS_DESCRIPTION)
Declares the description of the class.
Definition: SBCClassProxy.hpp:202

Open the source file of the app GUI (file: SECenterOfMassAppGUI.cpp) and modify the getName function to have a user-friendly description of the app inside SAMSON:

QString SECenterOfMassAppGUI::getName() const {
return "DevTutorial: Center of mass";
}

Now, try building the Extension and when you launch SAMSON you should be able to find it in Home > Apps. For now, it has no interface and does nothing, let's now add functionality to it.

Setting up the interface

Let's start by adding the interface. Open the SEAtomShakerAppGUI.ui file. Clear the text in the label and add a button to the user interface named pushButtonComputeCenterOfMass.

Add a button

Add the following slot: onComputeCenterOfMass()

Add a slot

and connect the clicked() signal from the button to the added slot:

Connect a signal to a slot

Computing the center of mass

Let's first implement a simple computation of the center of mass of the selected atoms.

Open the header file of the GUI class (class: SECenterOfMassAppGUI, file: SECenterOfMassAppGUI.hpp) and include the SBVector3.hpp header file (necessary for SBPosition3):

#include "SBVector3.hpp"

Add to the GUI class the onComputeCenterOfMass() public slot and a function to update the label with the new center of mass:

class SECenterOfMassAppGUI : public SBGApp {
// ...
void updateCenterOfMass(const SBPosition3& centerOfMass);
public slots:
void onComputeCenterOfMass();
// ...
};
This class is the base class for App's GUI.
Definition: SBGApp.hpp:15

Implement the slot function onComputeCenterOfMass() as follows:

void SECenterOfMassAppGUI::onComputeCenterOfMass() {
getApp()->computeCenterOfMass();
}

Here, we invoke the app's function for computing the center of mass (we will implement this function later in the section). Even for simple apps, it is preferable to separate the user interface from the core functionality of the app.

Implement the updateCenterOfMass() function to show the center of mass in the interface:

void SECenterOfMassAppGUI::updateCenterOfMass(const SBPosition3& centerOfMass) {
// convert to angstrom
// (else it will be in SBQuantity::length, which is picometers)
SBQuantity::angstrom x = centerOfMass.v[0];
SBQuantity::angstrom y = centerOfMass.v[1];
SBQuantity::angstrom z = centerOfMass.v[2];
// setup the label
ui.label->setText(
QString::fromStdString(x.toStdString()) + ", " +
QString::fromStdString(y.toStdString()) + ", " +
QString::fromStdString(z.toStdString())
);
}
This template class defines physical quantity types.
Definition: SBDQuantityType.hpp:43
std::string toStdString(bool fullName=false) const
Converts the physical quantity to a string (with a full unit name when fullName is true)
Definition: SBDQuantityType.hpp:728
Quantity v[3]
The components of the physical vector.
Definition: SBDTypePhysicalVector3.hpp:768

Let's now implement the computation of the center of mass in the app class. Open the header file of the app class (SECenterOfMassApp.hpp) and include SBAtom.hpp and SAMSON.hpp headers:

#include "SBAtom.hpp"
#include "SAMSON.hpp"

And add the following function and variable in the app class:

class SECenterOfMassApp : public SBDApp {
// ...
public :
void computeCenterOfMass();
SBPosition3 centerOfMass;
};
This class is the base class for apps.
Definition: SBDApp.hpp:18

For now, we implement the computeCenterOfMass function without taking into account moving atoms:

void SECenterOfMassApp::computeCenterOfMass() {
// get selected nodes
SBPointerIndexer<SBNode> const* selectedNodes = SAMSON::getActiveDocument()->getSelectedNodes();
// find atoms
SBNodeIndexer temporaryIndexer;
SB_FOR(SBNode* node, *selectedNodes)
node->getNodes(temporaryIndexer, SBNode::Atom);
// compute the center of mass
if (!temporaryIndexer.size()) return;
centerOfMass.setZero();
unsigned int nAtoms = 0;
SB_FOR(SBNode* node, temporaryIndexer) {
SBPointer<SBAtom> atom = static_cast<SBAtom*>(node);
if (!atom.isValid()) continue;
centerOfMass += atom->getPosition();
nAtoms++;
}
centerOfMass /= (double)nAtoms;
temporaryIndexer.clear();
// display the center of mass
getGUI()->updateCenterOfMass(centerOfMass);
}
#define SB_FOR(VARIABLE, CONTAINER)
A macro to easily write for loops involving containers.
Definition: SBCContainerFor.hpp:83
static SBDDocument * getActiveDocument()
Returns a pointer to SAMSON's active document.
Definition: SAMSON.cpp:738
unsigned int size() const
Returns the number of indexed objects.
Definition: SBCContainerIndexer.hpp:481
void clear()
Clears the indexer.
Definition: SBCContainerIndexer.hpp:349
This class is the base class to describe a node in the data graph.
Definition: SBDDataGraphNode.hpp:33
@ Atom
Atom.
Definition: SBDDataGraphNode.hpp:67
This class describes a node indexer.
Definition: SBDDataGraphNodeIndexer.hpp:21
SBPointerIndexer< SBNode > const * getSelectedNodes() const
Returns the indexer of selected nodes.
Definition: SBDDocument.cpp:653
void setZero()
Sets all components to zero.
Definition: SBDTypePhysicalVector3.hpp:516
This class describes an atom in a structural model.
Definition: SBMStructuralModelNodeAtom.hpp:34

Now, try building the Extension and when you launch SAMSON you should be able to find it in the App menu or in the App toolbar. Load or create a molecule, select it, and compute the center of mass.

Processing signals

Let's now implement the computation of the center of mass in the incremental way thanks to the signals and slots mechanism. For that, we connect structural signal of each atom in the selected system to a slot, in which we will be listening to the SBStructuralEvent::AtomPositionChanged event emitted by an atom when its position has changed.

And add the following function and variables in the app class (SECenterOfMassApp.hpp):

class SECenterOfMassApp : public SBDApp {
// ...
public :
// ...
void onStructuralEvent(SBStructuralEvent* event);
SBPointerIndexer<SBAtom> atomIndexer;
SBPosition3* positionArray;
};
This class describe a structural event.
Definition: SBMStructuralModelEvent.hpp:15

The atomIndexer will be used for storing indices of atoms for which the center of mass should be computed and the positionArray will be used for keeping the previous positions of atoms.

Modify the constructor and destructor of the app class (SECenterOfMassApp.cpp) as follows:

SECenterOfMassApp::SECenterOfMassApp() {
// ...
positionArray = 0;
}
SECenterOfMassApp::~SECenterOfMassApp() {
// clear the position Array
if (positionArray) delete[] positionArray;
// disconnect from atoms
SB_FOR(SBAtom* atom, atomIndexer)
atom->disconnectStructuralSignalFromSlot(this, SB_SLOT(&SECenterOfMassApp::onStructuralEvent));
// ...
}

Implement the function for processing the structural event as follows:

void SECenterOfMassApp::onStructuralEvent(SBStructuralEvent* event) {
SBAtom* atom = static_cast<SBAtom*>(event->getSender());
// get the index of the atom in the atom indexer
unsigned int atomIndex = atomIndexer.getIndex(atom);
// subtract the old position
centerOfMass -= (positionArray[atomIndex] / atomIndexer.size());
// add the new position
centerOfMass += (atom->getPosition() / atomIndexer.size());
// store the new position
positionArray[atomIndex] = atom->getPosition();
// display the center of mass
getGUI()->updateCenterOfMass(centerOfMass);
}
}
Type getType() const
Returns the structural model event type.
Definition: SBMStructuralModelEvent.cpp:20
@ AtomPositionChanged
An atom position changed.
Definition: SBMStructuralModelEvent.hpp:64
SBPosition3 const & getPosition() const
Returns the atom's position.
Definition: SBMStructuralModelNodeAtom.cpp:330

Here, we update the center of mass based on an event emitted when the particle position has changed. Note that for performace reasons this event is not emitted if the particle position was changed during the simulation or through some of the editors.

First, we get an index in the atomIndexer of an atom that emitted the event, we substruct this atom's old position from the center of mass and add its new position. Then we remember the current position of the atom and we request the app's interface to update the label.

We want the following behavoir from the app: once the button in the interface is clicked, the center of mass should be computed for the currently selected system and later it is updated if any atom from the selected system changes its position. For that, we modify the implementation of the computation of the center of mass as follows:

void SECenterOfMassApp::computeCenterOfMass() {
// clear the position Array
if (positionArray) delete[] positionArray;
// disconnect from atoms
SB_FOR(SBAtom* atom, atomIndexer)
atom->disconnectStructuralSignalFromSlot(this, SB_SLOT(&SECenterOfMassApp::onStructuralEvent));
// get selected nodes and find atoms
SBPointerIndexer<SBNode> const* selectedNodes = SAMSON::getActiveDocument()->getSelectedNodes();
SBNodeIndexer temporaryIndexer;
SB_FOR(SBNode* node, *selectedNodes)
node->getNodes(temporaryIndexer, SBNode::Atom);
// store pointers to atoms
atomIndexer.clear();
SB_FOR(SBNode* node, temporaryIndexer) atomIndexer.addReferenceTarget(node);
temporaryIndexer.clear();
// connect to atoms
positionArray = new SBPosition3[atomIndexer.size()];
SB_FOR(SBAtom* atom, atomIndexer)
positionArray[atomIndexer.getIndex(atom)] = atom->getPosition();
SB_FOR(SBAtom* atom, atomIndexer)
atom->connectStructuralSignalToSlot(this, SB_SLOT(&SECenterOfMassApp::onStructuralEvent));
// compute the center of mass
if (!atomIndexer.size()) return;
centerOfMass.setZero();
SB_FOR(SBAtom* atom, atomIndexer)
centerOfMass += atom->getPosition();
centerOfMass /= (double)atomIndexer.size();
// display the center of mass
getGUI()->updateCenterOfMass(centerOfMass);
}
virtual void getNodes(SBNodeIndexer &nodeIndexer, SBDDataGraphNode::Type nodeType, bool selectedNodesOnly=false, const SBNodePredicate &visitPredicate=SBDDataGraphNode::All(), bool includeDependencies=false) const
Collects nodes into nodeIndexer, based on a nodeType, a selection status and a visitPredicate,...
Definition: SBDDataGraphNode.cpp:1797

Here, we first clean the array with old positions of atoms, and disconnect previously added atoms from the slot. Then we find atoms in the selected system, add them into the atomIndexer, and connect them to the slot. Finally, we compute the center of mass and update it in the interface of the app.

Now, you have a simple implementation of an incremental computation of the center of mass. Build the Extension and launch SAMSON. Load or create any molecule, select it, click on "Compute center of mass" button in the app, and try moving an atom. You should see that that the center of mass is updated in the app interface.

Computing center of mass