Web Analytics Made Easy - Statcounter
Skip to content

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 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):

  • launch SAMSON and run the SAMSON Extension Generator;
  • specify the path to a folder where you want to develop your SAMSON Extensions;
  • name the SAMSON Extension as CenterOfMass and add some description;
  • add an App class (called SECenterOfMassApp);
  • generate the SAMSON Extension.

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");

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 button

Add the following slot: onComputeCenterOfMass()

Add slot

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

Connect signal to 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();

    // ...

};

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())
        );

}

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;

};

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);

}

Now, try building the Extension and when you launch SAMSON you should be able to find it in Home > Apps. 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;

};

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) {

    if (event->getType() == SBStructuralEvent::AtomPositionChanged) {

        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);

    }

}

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);

}

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.

Center of mass app example