Commit 5658bac6 authored by Andy Simmonett's avatar Andy Simmonett
Browse files

Add helper code to set Drude temperatures, clean up common functionality

parent 9f06b047
......@@ -120,10 +120,6 @@ protected:
* cleanup. It will also get called again if the application calls reinitialize() on the Context.
*/
virtual void cleanup() override {};
/**
* When the user modifies the state, we need to mark that the forces need to be recalculated.
*/
virtual void stateChanged(State::DataType changed) override {};
/**
* Get the names of all Kernels used by this Integrator.
*/
......
......@@ -32,7 +32,7 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "openmm/Integrator.h"
#include "openmm/DrudeIntegrator.h"
#include "openmm/Kernel.h"
#include "openmm/internal/windowsExportDrude.h"
......@@ -53,7 +53,7 @@ namespace OpenMM {
* particles.
*/
class OPENMM_EXPORT_DRUDE DrudeLangevinIntegrator : public Integrator {
class OPENMM_EXPORT_DRUDE DrudeLangevinIntegrator : public DrudeIntegrator {
public:
/**
* Create a DrudeLangevinIntegrator.
......@@ -99,22 +99,6 @@ public:
void setFriction(double coeff) {
friction = coeff;
}
/**
* Get the temperature of the heat bath applied to internal coordinates of Drude particles (in Kelvin).
*
* @return the temperature of the heat bath, measured in Kelvin
*/
double getDrudeTemperature() const {
return drudeTemperature;
}
/**
* Set the temperature of the heat bath applied to internal coordinates of Drude particles (in Kelvin).
*
* @param temp the temperature of the heat bath, measured in Kelvin
*/
void setDrudeTemperature(double temp) {
drudeTemperature = temp;
}
/**
* Get the friction coefficient which determines how strongly the internal coordinates of Drude particles
* are coupled to the heat bath (in inverse ps).
......@@ -133,79 +117,34 @@ public:
void setDrudeFriction(double coeff) {
drudeFriction = coeff;
}
/**
* Get the maximum distance a Drude particle can ever move from its parent particle, measured in nm. This is implemented
* with a hard wall constraint. If this distance is set to 0 (the default), the hard wall constraint is omitted.
*/
double getMaxDrudeDistance() const;
/**
* Set the maximum distance a Drude particle can ever move from its parent particle, measured in nm. This is implemented
* with a hard wall constraint. If this distance is set to 0 (the default), the hard wall constraint is omitted.
*/
void setMaxDrudeDistance(double distance);
/**
* Get the random number seed. See setRandomNumberSeed() for details.
*/
int getRandomNumberSeed() const {
return randomNumberSeed;
}
/**
* Set the random number seed. The precise meaning of this parameter is undefined, and is left up
* to each Platform to interpret in an appropriate way. It is guaranteed that if two simulations
* are run with different random number seeds, the sequence of random forces will be different. On
* the other hand, no guarantees are made about the behavior of simulations that use the same seed.
* In particular, Platforms are permitted to use non-deterministic algorithms which produce different
* results on successive runs, even if those runs were initialized identically.
*
* If seed is set to 0 (which is the default value assigned), a unique seed is chosen when a Context
* is created from this Force. This is done to ensure that each Context receives unique random seeds
* without you needing to set them explicitly.
*/
void setRandomNumberSeed(int seed) {
randomNumberSeed = seed;
}
/**
* Advance a simulation through time by taking a series of time steps.
*
* @param steps the number of time steps to take
*/
void step(int steps);
void step(int steps) override;
protected:
/**
* This will be called by the Context when it is created. It informs the Integrator
* of what context it will be integrating, and gives it a chance to do any necessary initialization.
* It will also get called again if the application calls reinitialize() on the Context.
*/
void initialize(ContextImpl& context);
void initialize(ContextImpl& context) override;
/**
* This will be called by the Context when it is destroyed to let the Integrator do any necessary
* cleanup. It will also get called again if the application calls reinitialize() on the Context.
*/
void cleanup();
/**
* When the user modifies the state, we need to mark that the forces need to be recalculated.
*/
void stateChanged(State::DataType changed);
void cleanup() override;
/**
* Get the names of all Kernels used by this Integrator.
*/
std::vector<std::string> getKernelNames();
std::vector<std::string> getKernelNames() override;
/**
* Compute the kinetic energy of the system at the current time.
*/
double computeKineticEnergy();
/**
* Return a list of velocities normally distributed around a target temperature, correctly
* handling center of mass velocities for the Drude pairs.
*
* @param system the system whose velocities are to be initialized.
* @param temperature the target temperature in Kelvin.
* @param randomSeed the random number seed to use when selecting velocities
*/
virtual std::vector<Vec3> getVelocitiesForTemperature(const System &system, double temperature, int randomSeed) const override;
double computeKineticEnergy() override;
private:
double temperature, friction, drudeTemperature, drudeFriction, maxDrudeDistance;
int randomNumberSeed;
double temperature, friction, drudeFriction;
Kernel kernel;
};
......
......@@ -93,6 +93,18 @@ public:
* Compute the kinetic energy of all (real and drude) particles at the current time.
*/
double computeTotalKineticEnergy();
/**
* Return a list of velocities normally distributed around a target temperature, with the Drude
* temperatures assigned according to the Drude temperature assigned to the integrator.
*
* @param system the system whose velocities are to be initialized.
* @param temperature the target temperature in Kelvin.
* @param randomSeed the random number seed to use when selecting velocities
*/
virtual std::vector<Vec3> getVelocitiesForTemperature(const System &system, double temperature,
int randomSeed) const override;
protected:
double drudeTemperature;
};
} // namespace OpenMM
......
......@@ -32,7 +32,7 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "openmm/Integrator.h"
#include "openmm/DrudeIntegrator.h"
#include "openmm/Kernel.h"
#include "openmm/internal/windowsExportDrude.h"
......@@ -47,7 +47,7 @@ namespace OpenMM {
* particles.
*/
class OPENMM_EXPORT_DRUDE DrudeSCFIntegrator : public Integrator {
class OPENMM_EXPORT_DRUDE DrudeSCFIntegrator : public DrudeIntegrator {
public:
/**
* Create a DrudeSCFIntegrator.
......@@ -78,31 +78,27 @@ public:
*
* @param steps the number of time steps to take
*/
void step(int steps);
void step(int steps) override;
protected:
/**
* This will be called by the Context when it is created. It informs the Integrator
* of what context it will be integrating, and gives it a chance to do any necessary initialization.
* It will also get called again if the application calls reinitialize() on the Context.
*/
void initialize(ContextImpl& context);
void initialize(ContextImpl& context) override;
/**
* This will be called by the Context when it is destroyed to let the Integrator do any necessary
* cleanup. It will also get called again if the application calls reinitialize() on the Context.
*/
void cleanup();
/**
* When the user modifies the state, we need to mark that the forces need to be recalculated.
*/
void stateChanged(State::DataType changed);
void cleanup() override;
/**
* Get the names of all Kernels used by this Integrator.
*/
std::vector<std::string> getKernelNames();
std::vector<std::string> getKernelNames() override;
/**
* Compute the kinetic energy of the system at the current time.
*/
double computeKineticEnergy();
double computeKineticEnergy() override;
private:
double tolerance;
Kernel kernel;
......
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2015 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "sfmt/SFMT.h"
#include "SimTKOpenMMRealType.h"
#include "openmm/DrudeForce.h"
#include "openmm/DrudeLangevinIntegrator.h"
#include "openmm/Context.h"
#include "openmm/System.h"
#include "openmm/OpenMMException.h"
#include <set>
namespace OpenMM {
std::vector<Vec3> assignDrudeVelocities(const System &system, double temperature, double drudeTemperature, int randomSeed) {
// Find the underlying Drude force object
const DrudeForce* drudeForce = NULL;
for (int i = 0; i < system.getNumForces(); i++)
if (dynamic_cast<const DrudeForce*>(&system.getForce(i)) != NULL) {
if (drudeForce == NULL)
drudeForce = dynamic_cast<const DrudeForce*>(&system.getForce(i));
else
throw OpenMMException("The System contains multiple DrudeForces");
}
if (drudeForce == NULL)
throw OpenMMException("The System does not contain a DrudeForce");
// Figure out which particles are individual and which are Drude pairs
std::set<int> particles;
std::vector<std::pair<int, int>> pairParticles;
for (int i = 0; i < system.getNumParticles(); i++) {
particles.insert(i);
}
for (int i = 0; i < drudeForce->getNumParticles(); i++) {
int p, p1, p2, p3, p4;
double charge, polarizability, aniso12, aniso34;
drudeForce->getParticleParameters(i, p, p1, p2, p3, p4, charge, polarizability, aniso12, aniso34);
particles.erase(p);
particles.erase(p1);
pairParticles.emplace_back(p, p1);
}
std::vector<int> normalParticles(particles.begin(), particles.end());
// Generate the list of Gaussian random numbers.
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(randomSeed, sfmt);
std::vector<double> randoms;
while (randoms.size() < system.getNumParticles()*3) {
double x, y, r2;
do {
x = 2.0*genrand_real2(sfmt)-1.0;
y = 2.0*genrand_real2(sfmt)-1.0;
r2 = x*x + y*y;
} while (r2 >= 1.0 || r2 == 0.0);
double multiplier = sqrt((-2.0*std::log(r2))/r2);
randoms.push_back(x*multiplier);
randoms.push_back(y*multiplier);
}
// Assign the velocities.
std::vector<Vec3> velocities(system.getNumParticles(), Vec3());
int nextRandom = 0;
// First the indivitual atoms
for (const auto &atom : normalParticles ) {
double mass = system.getParticleMass(atom);
if (mass != 0) {
double velocityScale = sqrt(BOLTZ*temperature/mass);
velocities[atom] = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*velocityScale;
}
}
// Now the particle-Drude pairs
for (const auto &pair : pairParticles ) {
const auto atom1 = pair.first;
const auto atom2 = pair.second;
double mass1 = system.getParticleMass(atom1);
double mass2 = system.getParticleMass(atom2);
if (mass1 != 0 && mass2 != 0) {
double invMass = 1.0 / (mass1 + mass2);
double redMass = mass1 * mass2 * invMass;
double fracM1 = mass1 * invMass;
double fracM2 = mass2 * invMass;
Vec3 comVelocity = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*sqrt(BOLTZ*temperature*invMass);
Vec3 relVelocity = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*sqrt(BOLTZ*drudeTemperature/redMass);
velocities[atom1] = comVelocity - fracM2 * relVelocity;
velocities[atom2] = comVelocity + fracM1 * relVelocity;
}
}
return velocities;
}
}
......@@ -29,7 +29,6 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "sfmt/SFMT.h"
#include "SimTKOpenMMRealType.h"
#include "openmm/DrudeForce.h"
#include "openmm/DrudeIntegrator.h"
......@@ -40,80 +39,12 @@
using namespace OpenMM;
std::vector<Vec3> DrudeIntegrator::getVelocitiesForTemperature(const System &system, double temperature, int randomSeedIn) const {
// Find the underlying Drude force object
const DrudeForce* drudeForce = NULL;
for (int i = 0; i < system.getNumForces(); i++)
if (dynamic_cast<const DrudeForce*>(&system.getForce(i)) != NULL) {
if (drudeForce == NULL)
drudeForce = dynamic_cast<const DrudeForce*>(&system.getForce(i));
else
throw OpenMMException("The System contains multiple DrudeForces");
}
if (drudeForce == NULL)
throw OpenMMException("The System does not contain a DrudeForce");
// Figure out which particles are individual and which are Drude pairs
std::set<int> particles;
std::vector<std::pair<int, int>> pairParticles;
for (int i = 0; i < system.getNumParticles(); i++) {
particles.insert(i);
}
for (int i = 0; i < drudeForce->getNumParticles(); i++) {
int p, p1, p2, p3, p4;
double charge, polarizability, aniso12, aniso34;
drudeForce->getParticleParameters(i, p, p1, p2, p3, p4, charge, polarizability, aniso12, aniso34);
particles.erase(p);
particles.erase(p1);
pairParticles.emplace_back(p, p1);
}
std::vector<int> normalParticles(particles.begin(), particles.end());
// Generate the list of Gaussian random numbers.
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(randomSeedIn, sfmt);
std::vector<double> randoms;
while (randoms.size() < system.getNumParticles()*3) {
double x, y, r2;
do {
x = 2.0*genrand_real2(sfmt)-1.0;
y = 2.0*genrand_real2(sfmt)-1.0;
r2 = x*x + y*y;
} while (r2 >= 1.0 || r2 == 0.0);
double multiplier = sqrt((-2.0*std::log(r2))/r2);
randoms.push_back(x*multiplier);
randoms.push_back(y*multiplier);
}
namespace OpenMM {
extern std::vector<Vec3> assignDrudeVelocities(const System &system, double temperature, double drudeTemperature, int randomSeed);
}
// Assign the velocities.
std::vector<Vec3> velocities(system.getNumParticles(), Vec3());
int nextRandom = 0;
// First the indivitual atoms
for (const auto &atom : normalParticles ) {
double mass = system.getParticleMass(atom);
if (mass != 0) {
double velocityScale = sqrt(BOLTZ*temperature/mass);
velocities[atom] = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*velocityScale;
}
}
// Now the particle-Drude pairs
for (const auto &pair : pairParticles ) {
const auto atom1 = pair.first;
const auto atom2 = pair.second;
double mass1 = system.getParticleMass(atom1);
double mass2 = system.getParticleMass(atom2);
if (mass1 != 0 && mass2 != 0) {
double invMass = 1.0 / (mass1 + mass2);
double redMass = mass1 * mass2 * invMass;
double fracM1 = mass1 * invMass;
double fracM2 = mass2 * invMass;
Vec3 comVelocity = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*sqrt(BOLTZ*temperature*invMass);
Vec3 relVelocity = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*sqrt(BOLTZ*drudeTemperature/redMass);
velocities[atom1] = comVelocity - fracM2 * relVelocity;
velocities[atom2] = comVelocity + fracM1 * relVelocity;
}
}
return velocities;
std::vector<Vec3> DrudeIntegrator::getVelocitiesForTemperature(const System &system, double temperature, int randomSeedIn) const {
return assignDrudeVelocities(system, temperature, drudeTemperature, randomSeedIn);
}
double DrudeIntegrator::getMaxDrudeDistance() const {
......
......@@ -29,8 +29,6 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "sfmt/SFMT.h"
#include "SimTKOpenMMRealType.h"
#include "openmm/DrudeLangevinIntegrator.h"
#include "openmm/Context.h"
#include "openmm/OpenMMException.h"
......@@ -45,7 +43,7 @@ using namespace OpenMM;
using std::string;
using std::vector;
DrudeLangevinIntegrator::DrudeLangevinIntegrator(double temperature, double frictionCoeff, double drudeTemperature, double drudeFrictionCoeff, double stepSize) {
DrudeLangevinIntegrator::DrudeLangevinIntegrator(double temperature, double frictionCoeff, double drudeTemperature, double drudeFrictionCoeff, double stepSize) : DrudeIntegrator(stepSize) {
setTemperature(temperature);
setFriction(frictionCoeff);
setDrudeTemperature(drudeTemperature);
......@@ -56,16 +54,6 @@ DrudeLangevinIntegrator::DrudeLangevinIntegrator(double temperature, double fric
setRandomNumberSeed(0);
}
double DrudeLangevinIntegrator::getMaxDrudeDistance() const {
return maxDrudeDistance;
}
void DrudeLangevinIntegrator::setMaxDrudeDistance(double distance) {
if (distance < 0)
throw OpenMMException("setMaxDrudeDistance: Distance cannot be negative");
maxDrudeDistance = distance;
}
void DrudeLangevinIntegrator::initialize(ContextImpl& contextRef) {
if (owner != NULL && &contextRef.getOwner() != owner)
throw OpenMMException("This Integrator is already bound to a context");
......@@ -90,9 +78,6 @@ void DrudeLangevinIntegrator::cleanup() {
kernel = Kernel();
}
void DrudeLangevinIntegrator::stateChanged(State::DataType changed) {
}
vector<string> DrudeLangevinIntegrator::getKernelNames() {
std::vector<std::string> names;
names.push_back(IntegrateDrudeLangevinStepKernel::Name());
......@@ -103,82 +88,6 @@ double DrudeLangevinIntegrator::computeKineticEnergy() {
return kernel.getAs<IntegrateDrudeLangevinStepKernel>().computeKineticEnergy(*context, *this);
}
std::vector<Vec3> DrudeLangevinIntegrator::getVelocitiesForTemperature(const System &system, double temperature, int randomSeed) const {
// Find the underlying Drude force object
const DrudeForce* drudeForce = NULL;
for (int i = 0; i < system.getNumForces(); i++)
if (dynamic_cast<const DrudeForce*>(&system.getForce(i)) != NULL) {
if (drudeForce == NULL)
drudeForce = dynamic_cast<const DrudeForce*>(&system.getForce(i));
else
throw OpenMMException("The System contains multiple DrudeForces");
}
if (drudeForce == NULL)
throw OpenMMException("The System does not contain a DrudeForce");
// Figure out which particles are individual and which are Drude pairs
std::set<int> particles;
std::vector<std::pair<int, int>> pairParticles;
for (int i = 0; i < system.getNumParticles(); i++) {
particles.insert(i);
}
for (int i = 0; i < drudeForce->getNumParticles(); i++) {
int p, p1, p2, p3, p4;
double charge, polarizability, aniso12, aniso34;
drudeForce->getParticleParameters(i, p, p1, p2, p3, p4, charge, polarizability, aniso12, aniso34);
particles.erase(p);
particles.erase(p1);
pairParticles.emplace_back(p, p1);
}
std::vector<int> normalParticles(particles.begin(), particles.end());
// Generate the list of Gaussian random numbers.
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(randomSeed, sfmt);
std::vector<double> randoms;
while (randoms.size() < system.getNumParticles()*3) {
double x, y, r2;
do {
x = 2.0*genrand_real2(sfmt)-1.0;
y = 2.0*genrand_real2(sfmt)-1.0;
r2 = x*x + y*y;
} while (r2 >= 1.0 || r2 == 0.0);
double multiplier = sqrt((-2.0*std::log(r2))/r2);
randoms.push_back(x*multiplier);
randoms.push_back(y*multiplier);
}
// Assign the velocities.
std::vector<Vec3> velocities(system.getNumParticles(), Vec3());
int nextRandom = 0;
// First the indivitual atoms
for (const auto &atom : normalParticles ) {
double mass = system.getParticleMass(atom);
if (mass != 0) {
double velocityScale = sqrt(BOLTZ*temperature/mass);
velocities[atom] = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*velocityScale;
}
}
// Now the particle-Drude pairs
for (const auto &pair : pairParticles ) {
const auto atom1 = pair.first;
const auto atom2 = pair.second;
double mass1 = system.getParticleMass(atom1);
double mass2 = system.getParticleMass(atom2);
if (mass1 != 0 && mass2 != 0) {
double invMass = 1.0 / (mass1 + mass2);
double redMass = mass1 * mass2 * invMass;
double fracM1 = mass1 * invMass;
double fracM2 = mass2 * invMass;
Vec3 comVelocity = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*sqrt(BOLTZ*temperature*invMass);
Vec3 relVelocity = Vec3(randoms[nextRandom++], randoms[nextRandom++], randoms[nextRandom++])*sqrt(BOLTZ*drudeTemperature/redMass);
velocities[atom1] = comVelocity - fracM2 * relVelocity;
velocities[atom2] = comVelocity + fracM1 * relVelocity;
}
}
return velocities;
}
void DrudeLangevinIntegrator::step(int steps) {
if (context == NULL)
throw OpenMMException("This Integrator is not bound to a context!");
......
......@@ -46,11 +46,16 @@ using namespace OpenMM;
using std::string;
using std::vector;
namespace OpenMM {
extern std::vector<Vec3> assignDrudeVelocities(const System &system, double temperature, double drudeTemperature, int randomSeed);
}
DrudeNoseHooverIntegrator::DrudeNoseHooverIntegrator(double temperature, double collisionFrequency,
double drudeTemperature, double drudeCollisionFrequency,
double stepSize, int chainLength, int numMTS, int numYoshidaSuzuki) :
NoseHooverIntegrator(stepSize) {
NoseHooverIntegrator(stepSize),
drudeTemperature(drudeTemperature)
{
setMaxDrudeDistance(0);
hasSubsystemThermostats_ = false;
addSubsystemThermostat(std::vector<int>(), std::vector<std::pair<int, int>>(), temperature,
......@@ -143,3 +148,8 @@ double DrudeNoseHooverIntegrator::computeTotalKineticEnergy() {
return vvKernel.getAs<IntegrateVelocityVerletStepKernel>().computeKineticEnergy(*context, *this);
}
std::vector<Vec3> DrudeNoseHooverIntegrator::getVelocitiesForTemperature(const System &system, double temperature,
int randomSeedIn) const {
return assignDrudeVelocities(system, temperature, drudeTemperature, randomSeedIn);
}
......@@ -42,7 +42,9 @@ using namespace OpenMM;
using std::string;
using std::vector;
DrudeSCFIntegrator::DrudeSCFIntegrator(double stepSize) {
DrudeSCFIntegrator::DrudeSCFIntegrator(double stepSize) : DrudeIntegrator(stepSize)
{
setDrudeTemperature(0.0); // This is only used to initialize velocities for this integrator
setStepSize(stepSize);
setMinimizationErrorTolerance(0.1);
setConstraintTolerance(1e-5);
......@@ -72,9 +74,6 @@ void DrudeSCFIntegrator::cleanup() {
kernel = Kernel();
}
void DrudeSCFIntegrator::stateChanged(State::DataType changed) {
}
vector<string> DrudeSCFIntegrator::getKernelNames() {
std::vector<std::string> names;
names.push_back(IntegrateDrudeSCFStepKernel::Name());
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment