Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an initial conformer property window #1837

Merged
merged 5 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions avogadro/io/xyzformat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ bool XyzFormat::read(std::istream& inStream, Core::Molecule& mol)
}

std::getline(inStream, buffer); // Skip the blank
// check for energies
if (findEnergy(buffer, energy)) {
energies.push_back(energy);
}
positions.clear();
}
}
Expand All @@ -280,9 +284,8 @@ bool XyzFormat::read(std::istream& inStream, Core::Molecule& mol)
mol.setPartialCharges("From File", chargesMatrix);
}

if (energies.size() > 1) {
if (energies.size() > 1)
mol.setData("energies", energies);
}

return true;
}
Expand Down
11 changes: 10 additions & 1 deletion avogadro/qtplugins/openbabel/openbabel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,18 @@ void OpenBabel::onGenerateConformersFinished(const QByteArray& output)
return;
}

//@todo .. multiple coordinate sets
m_molecule->undoMolecule()->setAtomPositions3d(mol.atomPositions3d(),
tr("Generate Conformers"));

// copy the coordinate sets
m_molecule->clearCoordinate3d();
for (size_t i = 0; i < mol.coordinate3dCount(); ++i)
m_molecule->setCoordinate3d(mol.coordinate3d(i), i);

// energy data too
// TODO: check if other properties are needed
m_molecule->setData("energies", mol.data("energies"));

m_molecule->emitChanged(QtGui::Molecule::Atoms | QtGui::Molecule::Modified);
m_progress->reset();
}
Expand Down
87 changes: 77 additions & 10 deletions avogadro/qtplugins/propertytables/propertymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "propertymodel.h"

#include <avogadro/calc/chargemanager.h>
#include <avogadro/core/array.h>
#include <avogadro/core/atom.h>
#include <avogadro/core/bond.h>
#include <avogadro/core/elements.h>
Expand All @@ -25,6 +26,7 @@

namespace Avogadro {

using Avogadro::Core::Array;
using Avogadro::QtGui::Molecule;
using QtGui::Molecule;
using QtGui::RWAtom;
Expand All @@ -45,8 +47,23 @@ const int AngleColumns = 5;
const int TorsionColumns = 6;
// name, number, chain, secondary structure, heterogen, color
const int ResidueColumns = 6;
// number, energy, ??
const int ConformerColumns = 2;
// number, rmsd, energy or more depending on available properties
const int ConformerColumns = 1;

// compute the RMSD between the two sets of coordinates
inline double calcRMSD(const Array<Vector3>& v1, const Array<Vector3>& v2)
{
// if they're not the same length, it's an error
if (v1.size() != v2.size())
return numeric_limits<double>::quiet_NaN();

double sum = 0.0;
for (size_t i = 0; i < v1.size(); ++i) {
Vector3 diff = v1[i] - v2[i];
sum += diff.squaredNorm();
}
return sqrt(sum / v1.size());
}

inline double distance(Vector3 v1, Vector3 v2)
{
Expand Down Expand Up @@ -119,8 +136,12 @@ int PropertyModel::columnCount(const QModelIndex& parent) const
return TorsionColumns;
case ResidueType:
return ResidueColumns;
case ConformerType:
return ConformerColumns;
case ConformerType: {
if (m_molecule->hasData("energies"))
return ConformerColumns + 1;
else
return ConformerColumns;
}
default:
return 0;
}
Expand Down Expand Up @@ -204,6 +225,9 @@ QVariant PropertyModel::data(const QModelIndex& index, int role) const
return toVariant(Qt::AlignHCenter | Qt::AlignVCenter);
} else if (m_type == ResidueType) {
return toVariant(Qt::AlignHCenter | Qt::AlignVCenter);
} else if (m_type == ConformerType) {
return toVariant(Qt::AlignRight |
Qt::AlignVCenter); // RMSD or other properties
}
}

Expand All @@ -227,9 +251,6 @@ QVariant PropertyModel::data(const QModelIndex& index, int role) const
if (role != Qt::UserRole && role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();

// if (!m_validCache)
// updateCache();

if (m_type == AtomType) {
auto column = static_cast<AtomColumn>(index.column());

Expand Down Expand Up @@ -288,7 +309,7 @@ QVariant PropertyModel::data(const QModelIndex& index, int role) const
case BondDataOrder:
return bond.order();
default: // length, rounded to 4 decimals
return QString("%L1").arg(
return QString("%L1 Å").arg(
distance(atom1.position3d(), atom2.position3d()), 0, 'f', 3);
}
} else if (m_type == ResidueType) {
Expand Down Expand Up @@ -342,7 +363,7 @@ QVariant PropertyModel::data(const QModelIndex& index, int role) const
case AngleDataAtom3:
return QVariant::fromValue(std::get<2>(angle) + 1);
case AngleDataValue:
return QString("%L1").arg(calcAngle(a1, a2, a3), 0, 'f', 3);
return QString("%L1 °").arg(calcAngle(a1, a2, a3), 0, 'f', 3);
default:
return QVariant();
}
Expand Down Expand Up @@ -378,10 +399,42 @@ QVariant PropertyModel::data(const QModelIndex& index, int role) const
case TorsionDataAtom4:
return QVariant::fromValue(std::get<3>(torsion) + 1);
case TorsionDataValue:
return QString("%L1").arg(calcDihedral(a1, a2, a3, a4), 0, 'f', 3);
return QString("%L1 °").arg(calcDihedral(a1, a2, a3, a4), 0, 'f', 3);
default:
return QVariant();
}
} else if (m_type == ConformerType) {
auto column = static_cast<ConformerColumn>(index.column());
if (row >= static_cast<int>(m_molecule->coordinate3dCount()) ||
column > ConformerColumns) {
return QVariant(); // invalid index
}

switch (column) {
case ConformerDataRMSD: { // rmsd
double rmsd = 0.0;
if (row > 0) {
rmsd = calcRMSD(m_molecule->coordinate3d(row),
m_molecule->coordinate3d(0));
}

return QString("%L1 Å").arg(rmsd, 0, 'f', 3);
}
case ConformerDataEnergy: {
double energy = 0.0;
if (m_molecule->hasData("energies")) {
std::vector<double> energies = m_molecule->data("energies").toList();
// calculate the minimum
double minEnergy = std::numeric_limits<double>::max();
for (double e : energies) {
minEnergy = std::min(minEnergy, e);
}
if (row < static_cast<int>(energies.size()))
energy = energies[row] - minEnergy;
}
return QString("%L1").arg(energy, 0, 'f', 4);
}
}
}

return QVariant();
Expand Down Expand Up @@ -500,6 +553,20 @@ QVariant PropertyModel::headerData(int section, Qt::Orientation orientation,
}
} else // row headers
return QString("%L1").arg(section + 1);
} else if (m_type == ConformerType) {
// check if we have energies
bool hasEnergies = (m_molecule->hasData("energies"));
if (orientation == Qt::Horizontal) {
unsigned int column = static_cast<ConformerColumn>(section);
switch (column) {
case ConformerDataRMSD:
return tr("RMSD (Å)", "root mean squared displacement in Angstrom");
case ConformerDataEnergy:
// should only hit this if we have energies anyway
return hasEnergies ? tr("Energy (kcal/mol)") : tr("Property");
}
} else // row headers
return QString("%L1").arg(section + 1);
}

return QVariant();
Expand Down
5 changes: 2 additions & 3 deletions avogadro/qtplugins/propertytables/propertymodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ public slots:
std::vector<int> m_fragment;
Eigen::Affine3d m_transform;
bool fragmentHasAtom(int uid) const;
void buildFragment(const QtGui::RWBond& bond,
const QtGui::RWAtom& startAtom);
void buildFragment(const QtGui::RWBond& bond, const QtGui::RWAtom& startAtom);
bool fragmentRecurse(const QtGui::RWBond& bond,
const QtGui::RWAtom& startAtom,
const QtGui::RWAtom& currentAtom);
Expand Down Expand Up @@ -152,7 +151,7 @@ public slots:
// Conformer Data
enum ConformerColumn
{
ConformerDataType = 0,
ConformerDataRMSD = 0,
ConformerDataEnergy
};

Expand Down
41 changes: 34 additions & 7 deletions avogadro/qtplugins/propertytables/propertytables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ PropertyTables::PropertyTables(QObject* parent_)
action->setEnabled(false); // changed by molecule
connect(action, SIGNAL(triggered()), SLOT(showDialog()));
m_actions.append(action);

action = new QAction(this);
action->setText(tr("Conformer Properties…"));
action->setData(PropertyType::ConformerType);
action->setProperty("menu priority", 830);
action->setEnabled(false); // changed by molecule
connect(action, SIGNAL(triggered()), SLOT(showDialog()));
m_actions.append(action);
}

PropertyTables::~PropertyTables() {}
Expand Down Expand Up @@ -85,12 +93,26 @@ void PropertyTables::setMolecule(QtGui::Molecule* mol)

m_molecule = mol;

// check if there are residues
if (m_molecule->residueCount() > 0) {
for (const auto& action : m_actions) {
if (action->data().toInt() == PropertyType::ResidueType)
action->setEnabled(true);
}
updateActions();

// update if the molecule changes
connect(m_molecule, SIGNAL(changed(unsigned int)), SLOT(updateActions()));
}

void PropertyTables::updateActions()
{
if (m_molecule == nullptr)
return;

// check if we enable / disable the residue and conformer actions
bool haveResidues = (m_molecule->residueCount() > 0);
// technically coordinate sets
bool haveConformers = (m_molecule->coordinate3dCount() > 1);
for (const auto& action : m_actions) {
if (action->data().toInt() == PropertyType::ResidueType)
action->setEnabled(haveResidues);
else if (action->data().toInt() == PropertyType::ConformerType)
action->setEnabled(haveConformers);
}
}

Expand All @@ -104,6 +126,10 @@ void PropertyTables::showDialog()
m_molecule->residueCount() == 0)
return;

if (action->data().toInt() == PropertyType::ConformerType &&
m_molecule->coordinate3dCount() < 2)
return;

auto* dialog = new QDialog(qobject_cast<QWidget*>(parent()));
auto* layout = new QVBoxLayout(dialog);
dialog->setLayout(layout);
Expand All @@ -130,6 +156,7 @@ void PropertyTables::showDialog()
view->setSourceModel(model);

view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->horizontalHeader()->setStretchLastSection(true);
view->resizeColumnsToContents();
layout->addWidget(view);
dialog->setWindowTitle(view->windowTitle());
Expand All @@ -149,4 +176,4 @@ void PropertyTables::showDialog()
dialog->show();
}

} // namespace Avogadro
} // namespace Avogadro::QtPlugins
3 changes: 2 additions & 1 deletion avogadro/qtplugins/propertytables/propertytables.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ class PropertyTables : public Avogadro::QtGui::ExtensionPlugin

public slots:
void setMolecule(QtGui::Molecule* mol) override;
void updateActions();

private slots:
void showDialog();

private:
QList<QAction *> m_actions;
QList<QAction*> m_actions;
QtGui::Molecule* m_molecule;
};

Expand Down
19 changes: 18 additions & 1 deletion avogadro/qtplugins/propertytables/propertyview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "propertyview.h"

#include <avogadro/core/residue.h>
#include <avogadro/qtgui/molecule.h>

#include <QAction>
Expand Down Expand Up @@ -74,7 +75,10 @@ PropertyView::PropertyView(PropertyType type, QWidget* parent)
// You can select everything (e.g., to copy, select all, etc.)
setCornerButtonEnabled(true);
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ExtendedSelection);
if (type == ConformerType)
setSelectionMode(QAbstractItemView::SingleSelection);
else
setSelectionMode(QAbstractItemView::ExtendedSelection);
// Alternating row colors
setAlternatingRowColors(true);
// Allow sorting the table
Expand Down Expand Up @@ -137,6 +141,19 @@ void PropertyView::selectionChanged(const QItemSelection& selected,
m_molecule->undoMolecule()->setAtomSelected(std::get<2>(torsion), true);
m_molecule->undoMolecule()->setAtomSelected(std::get<3>(torsion), true);
}
} else if (m_type == PropertyType::ResidueType) {
// select all the atoms in the residue
if (m_model != nullptr) {
const auto residue = m_molecule->residue(rowNum);
auto atoms = residue.residueAtoms();
for (Index i = 0; i < atoms.size(); ++i) {
const auto atom = atoms[i];
m_molecule->undoMolecule()->setAtomSelected(atom.index(), true);
}
}
} else if (m_type == PropertyType::ConformerType) {
// selecting a row means switching to that conformer
m_molecule->setCoordinate3d(rowNum);
}
} // end loop through selected

Expand Down
Loading