diff --git a/.github/workflows/self-hosted-build.yml b/.github/workflows/self-hosted-build.yml index ad7ad27..97088b0 100644 --- a/.github/workflows/self-hosted-build.yml +++ b/.github/workflows/self-hosted-build.yml @@ -8,22 +8,22 @@ jobs: runs-on: [self-hosted] strategy: matrix: - python-version: ["3.9"] + python-version: ["3.7", "3.8", "3.9"] steps: - uses: actions/checkout@v3 - name: Install dependencies run: | - pip install pylint pytest coverage coveralls - pip install -r requirements.txt + pip${{ matrix.python-version }} install pylint pytest coverage coveralls + pip${{ matrix.python-version }} install -r requirements.txt - name: Linting with pylint run: | - pylint $(git ls-files 'qmcblip/*.py') + python${{ matrix.python-version }} -m pylint $(git ls-files 'qmcblip/*.py') - name: Test with pytest run: | - coverage run -m --source=qmcblip pytest tests - coverage report + python${{ matrix.python-version }} -m coverage run -m --source=qmcblip pytest tests + python${{ matrix.python-version }} -m coverage report - name: Coveralls - run: coveralls --service=github + run: python${{ matrix.python-version }} -m coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.github_token }} COVERALLS_FLAG_NAME: python-${{ matrix.python-version }} diff --git a/docs/examples/C2-gamess.rst b/docs/examples/C2-gamess.rst new file mode 100644 index 0000000..7d1f1ba --- /dev/null +++ b/docs/examples/C2-gamess.rst @@ -0,0 +1,4 @@ +C2 with GAMESS +-------------- + +.. literalinclude:: ../../examples/C2-gamess/run.py \ No newline at end of file diff --git a/docs/examples/C2-quicksim.rst b/docs/examples/C2-quicksim.rst index f802737..72690f6 100644 --- a/docs/examples/C2-quicksim.rst +++ b/docs/examples/C2-quicksim.rst @@ -1,4 +1,4 @@ C2 Quick Simulation -=================== +------------------- .. literalinclude:: ../../examples/C2-quicksim/run.py \ No newline at end of file diff --git a/docs/examples/examples.rst b/docs/examples/examples.rst new file mode 100644 index 0000000..e5b32a9 --- /dev/null +++ b/docs/examples/examples.rst @@ -0,0 +1,25 @@ +Quicksim +======== +.. toctree:: + :maxdepth: 1 + :caption: Quicksim + + C2-quicksim + thio-quicksim + +FLARE++ +======= +.. toctree:: + :maxdepth: 1 + :caption: FLARE++ + + thio-300K + thio-300K-ex + +GAMESS +======== +.. toctree:: + :maxdepth: 1 + :caption: GAMESS + + C2-gamess \ No newline at end of file diff --git a/docs/examples/thio-300K-ex.rst b/docs/examples/thio-300K-ex.rst index b3b8455..031579b 100644 --- a/docs/examples/thio-300K-ex.rst +++ b/docs/examples/thio-300K-ex.rst @@ -1,13 +1,13 @@ Thiophene 300K Excited State Geometry -===================================== +------------------------------------- Running the simulation ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../../examples/thio-300K-ex/run.py Analyzing the simulation ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../../examples/thio-300K-ex/plot.py diff --git a/docs/examples/thio-300K.rst b/docs/examples/thio-300K.rst index 156d1f5..437afb0 100644 --- a/docs/examples/thio-300K.rst +++ b/docs/examples/thio-300K.rst @@ -1,13 +1,13 @@ Thiophene 300K -============== +-------------- Running the simulation ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../../examples/thio-300K/run.py Analyzing the simulation ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../../examples/thio-300K/plot.py diff --git a/docs/examples/thio-quicksim.rst b/docs/examples/thio-quicksim.rst index 60639f6..d482b92 100644 --- a/docs/examples/thio-quicksim.rst +++ b/docs/examples/thio-quicksim.rst @@ -1,4 +1,4 @@ Thiophene Quick Simulation -========================== +-------------------------- .. literalinclude:: ../../examples/thio-quicksim/run.py \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 032455b..cddc845 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,16 +8,14 @@ Quantum Monte Carlo Based Learning of Interatomic Potentials installation qmcblip/champ qmcblip/flare - qmcblip/analyze + qmcblip/quicksim + qmcblip/gamess .. toctree:: :maxdepth: 1 :caption: Examples - examples/C2-quicksim - examples/thio-quicksim - examples/thio-300K - examples/thio-300K-ex + examples/examples .. toctree:: :maxdepth: 1 diff --git a/docs/installation.rst b/docs/installation.rst index f2a86dc..b4f77a8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -23,11 +23,24 @@ To install the code * install the module ``pip install -e ./`` +Installing Dependencies +======================= + Installing CHAMP -================= +---------------- To use this software, you need a special version of CHAMP which can export the forces and energy to a file. For this you need to ase-coupling_ branch. Simply clone this and build in the usual way, as specified by their documentation. .. _ase-coupling: https://github.com/filippi-claudia/champ/tree/ase-coupling + +Installing GAMESS +----------------- + +GAMESS can be found at gamess_. To make CHAMP and GAMESS work together, we need to change some settings. +First, make sure that GAMESS is on your path, specifically the rungms file. +Secondly, in the ``gms-files.csh`` file change the ``setenv EXTBAS /dev/null`` to ``setenv EXTBAS /your/champ/location/pool/BFD/BASIS_gamess/BFD_Basis.EXTBAS``. Now GAMESS can use the BFD basis. + +.. _gamess: https://www.msg.chem.iastate.edu/gamess/ + diff --git a/docs/qmcblip/analyze.rst b/docs/qmcblip/analyze.rst deleted file mode 100644 index f518627..0000000 --- a/docs/qmcblip/analyze.rst +++ /dev/null @@ -1,23 +0,0 @@ -Analyzing results ------------------ - -Due to the custom Velocity Verlet scheme we use with FLARE, the kinetic energy is one step out of phase with the potential energy. -To make it easier to analyze the data, we included a few tools to do so (see :class:`Analyze `). -These tools also align the potential and kinetic energy: - ->>> from caf.tools import Analyze ->>> ->>> # Import the thio.out file ->>> data = Analyze('H2.out') ->>> ->>> # Create the traj.xyz file ->>> data.to_xyz() ->>> ->>> # Returns a dictionary containing the keys 'times', 'potential energy', 'kinetic energy', ->>> # 'total energy' and 'temperature' ->>> results = data.get_data() ->>> ->>> # Plot the energy and save it to energy.png ->>> data.plot_energy(filename="energy.png") - -This analyzing tool is only suitable for simulations that were performed with FLARE, and not pure QMC simulations. \ No newline at end of file diff --git a/docs/qmcblip/flare.rst b/docs/qmcblip/flare.rst index 033553e..f4d11c3 100644 --- a/docs/qmcblip/flare.rst +++ b/docs/qmcblip/flare.rst @@ -61,4 +61,28 @@ This array can be empty. >>> calculator=flare_calculator, >>> **otf_params) -See the `FLARE documentation`_ for more information about the other parts in the code on this page. \ No newline at end of file +See the `FLARE documentation`_ for more information about the other parts in the code on this page. + +Analyzing results +^^^^^^^^^^^^^^^^^ + +Due to the custom Velocity Verlet scheme we use with FLARE, the kinetic energy is one step out of phase with the potential energy. +To make it easier to analyze the data, we included a few tools to do so (see :class:`Analyze `). +These tools also align the potential and kinetic energy: + +>>> from caf.tools import Analyze +>>> +>>> # Import the thio.out file +>>> data = Analyze('H2.out') +>>> +>>> # Create the traj.xyz file +>>> data.to_xyz() +>>> +>>> # Returns a dictionary containing the keys 'times', 'potential energy', 'kinetic energy', +>>> # 'total energy' and 'temperature' +>>> results = data.get_data() +>>> +>>> # Plot the energy and save it to energy.png +>>> data.plot_energy(filename="energy.png") + +This analyzing tool is only suitable for simulations that were performed with FLARE, and not pure QMC simulations. \ No newline at end of file diff --git a/docs/qmcblip/gamess.rst b/docs/qmcblip/gamess.rst new file mode 100644 index 0000000..3d13658 --- /dev/null +++ b/docs/qmcblip/gamess.rst @@ -0,0 +1,40 @@ +Creating Wavefunction +--------------------- + +Important warning: this part is currently still experimental and only confirmed to work for C2. + +QMCblip also includes some tools to make the wavefunction for CHAMP using GAMESS. To use it, first import: + +>>> from qmcblip.gamess.utils import WavefunctionCreator + +Also create your molecular system: + +>>> from ase import Atoms +>>> atoms = Atoms('C2', [(0,0,-0.7), (0,0, 0.7)]) + +We can now initialize our :obj:`WavefunctionCreator ` object. The first argument are our atoms and the second argument is the location of the CHAMP base directory. This is not the directory containing ``vmc.mov1``, but the directory one level up. + +>>> wf = WavefunctionCreator(atoms, '../../champ') + +We can now run the GAMESS simulations. See the `GAMESS documentation`_ for more information. Using keywords and dictionaries you can supply additional GAMESS settings or change the defaults. Using the userscr keyword you can supply the location of the USERSCR directory. Usually the ASE GAMESS calculator can find it on its own, but if not you need to supply it. + +>>> wf.setup_rhf() +>>> wf.setup_cas(system=dict(mwords=500), drt=dict(nmcc=2, ndoc=2, nval=2)) +>>> wf.setup_ci(system=dict(mwords=500), cidrt=dict(nfzc=2, ndoc=2, nval=2)) + +After GAMESS is done we can convert the GAMESS output to CHAMP wavefunction. + +>>> wf.convert_to_champ() + +And we can also quickly make a CHAMP input file (``vmc.inp``). + +>>> input = wf.create_champ_input() + +Here ``input`` is of the :obj:`Settings ` type. With that we can easily run a CHAMP simulation: + +>>> from qmcblip.champ import CHAMP +>>> atoms.calc = CHAMP(champ_loc='../../champ/bin/vmc.mov1', settings = input) + +For more examples, see :doc:`../examples/examples`. + +.. _`GAMESS documentation`: https://www.msg.chem.iastate.edu/gamess/GAMESS_Manual/docs-input.txt \ No newline at end of file diff --git a/docs/qmcblip/quicksim.rst b/docs/qmcblip/quicksim.rst new file mode 100644 index 0000000..2785c6f --- /dev/null +++ b/docs/qmcblip/quicksim.rst @@ -0,0 +1,41 @@ +Quickly setup +------------- + +QMCblip comes with a quicksim function to quickly perform QMC ML FF simulations. To do a quick FLARE simulation, import: + +>>> from qmcblip.flare.quicksim import quicksim, OTFSettings + +Depending on your system, you might want to use FLARE or FLARE++. To switch between the two, make an :obj:`OTFSettings` object: + +>>> settings = OTFSettings(theory=OTFSettings.FLARE()) + +In the case you want to do a FLARE++ simulation, do: + +>>> settings = OTFSettings(theory=OTFSettings.FLAREPP()) + +You also need to provide the CHAMP calculator + +>>> from qmcblip.champ import CHAMP +>>> calc = CHAMP(champ_loc="/home/user/bin/vmc.mov1", ncore=4) + +Finally, you also need to setup the molecular system (remember to put a box around it for FLARE): + +>>> from ase import Atoms, units +>>> from ase.atoms import Cell +>>> +>>> atoms = Atoms('C2', [(0,0,-0.61385), (0,0,0.61385)]) +>>> atoms.cell = Cell.fromcellpar([50, 50, 50, 90, 90, 90]) +>>> atoms.pbc=[True, True, True] + +Finally you are ready to perform the simulation. In this case we are performing a simulation of 100 steps with 0.5fs timestep. + +>>> quicksim(atoms, 0.5, 100, calc, settings) + +You can also supply an extra argument to change the CHAMP settings during the simulation. To change the amount of optimization steps at the 10th MD timestep: + +>>> changes = [(10, {'optwf': {'nopt_iter': 10}})] +>>> quicksim(atoms, 0.5, 100, calc, settings, changes=changes) + +For more examples, see :doc:`../examples/examples`. + + diff --git a/examples/C2-gamess/run.py b/examples/C2-gamess/run.py new file mode 100644 index 0000000..87581c7 --- /dev/null +++ b/examples/C2-gamess/run.py @@ -0,0 +1,19 @@ +from ase import Atoms +from qmcblip.gamess.utils import WavefunctionCreator +from qmcblip.champ import CHAMP +from qmcblip.champio import Settings + +atoms = Atoms('C2', [(0,0,-0.7), (0,0, 0.7)]) +wf = WavefunctionCreator(atoms, '../../champ') + +wf.setup_rhf() +wf.setup_cas(system=dict(mwords=500), drt=dict(nmcc=2, ndoc=2, nval=2)) +wf.setup_ci(system=dict(mwords=500), cidrt=dict(nfzc=2, ndoc=2, nval=2)) +wf.convert_to_champ() +input = wf.create_champ_input() + +input.optwf.nopt_iter = 100 + +atoms.calc = CHAMP(champ_loc='../../champ/bin/vmc.mov1', settings = input) + +print(atoms.get_total_energy()) diff --git a/qmcblip/champio.py b/qmcblip/champio.py index 2d48371..46296e4 100644 --- a/qmcblip/champio.py +++ b/qmcblip/champio.py @@ -127,10 +127,13 @@ def write(self, filename='vmc.inp'): schema = self.schema()['properties'] for _, item in enumerate(self): if isinstance(item[1], BaseModel): - schema2 = item[1].schema()['properties'] + if callable(getattr(item[1], 'schema')): + schema2 = item[1].schema()['properties'] + else: + schema2 = [] input_file.write("\n%module " + item[0] + "\n") for _, item2 in enumerate(item[1]): - if 'postfix' in schema2[item2[0]]: + if item2[0] in schema2 and 'postfix' in schema2[item2[0]]: if item2[1] is not None: input_file.write("\t" + item2[0] + " " + str(item2[1]) +\ schema2[item2[0]]['postfix'] + "\n") @@ -139,7 +142,7 @@ def write(self, filename='vmc.inp'): input_file.write("\t" + item2[0] + " " + str(item2[1]) + "\n") input_file.write("%endmodule\n\n") else: - if 'prefix' in schema[item[0]]: + if item[0] in schema and 'prefix' in schema[item[0]]: if item[1] is not None: input_file.write(schema[item[0]]['prefix'] + item[0] + " " +\ str(item[1]) + '\n') @@ -165,9 +168,9 @@ def read(cls: Type['BaseModel'], filename: Union[str, Path]) -> 'BaseModel': path = PosixPath(filename).resolve().parent - if path.is_relative_to(Path.cwd()): + try: path = path.relative_to(Path.cwd()).as_posix() + "/" - else: + except: path = path.as_posix() + "/" output = {} diff --git a/qmcblip/gamess/utils.py b/qmcblip/gamess/utils.py index 3b487e9..914a32f 100644 --- a/qmcblip/gamess/utils.py +++ b/qmcblip/gamess/utils.py @@ -119,8 +119,12 @@ def setup_rhf(self, **kwargs): calc.calculate(self.atoms) - if calc.userscr is not None: - copyfile(Path(calc.userscr).joinpath(name + '.dat'), name + '.dat') + if calc.userscr is None: + raise RuntimeError("USERSCR is not defined") + if not Path(calc.userscr).is_dir(): + raise RuntimeError("USERSCR is not found at: " + calc.userscr) + + copyfile(Path(calc.userscr).joinpath(name + '.dat'), name + '.dat') self.vec = '\n'.join(str(subprocess.check_output( str(self.champ_path.joinpath('tools/interface/getvec.pl')) +\ @@ -164,8 +168,12 @@ def setup_cas(self, **kwargs): calc.calculate(self.atoms) - if calc.userscr is not None: - copyfile(Path(calc.userscr).joinpath(name + '.dat'), name + '.dat') + if calc.userscr is None: + raise RuntimeError("USERSCR is not defined") + if not Path(calc.userscr).is_dir(): + raise RuntimeError("USERSCR is not found at: " + calc.userscr) + + copyfile(Path(calc.userscr).joinpath(name + '.dat'), name + '.dat') self.vec = '\n'.join(str(subprocess.check_output( str(self.champ_path.joinpath('tools/interface/getvec.pl')) +\ @@ -210,8 +218,12 @@ def setup_ci(self, **kwargs): calc.calculate(self.atoms) - if calc.userscr is not None: - copyfile(Path(calc.userscr).joinpath(name + '.dat'), name + '.dat') + if calc.userscr is None: + raise RuntimeError("USERSCR is not defined") + if not Path(calc.userscr).is_dir(): + raise RuntimeError("USERSCR is not found at: " + calc.userscr) + + copyfile(Path(calc.userscr).joinpath(name + '.dat'), name + '.dat') os.chdir('..') diff --git a/setup.py b/setup.py index 5e267a3..38fdbec 100644 --- a/setup.py +++ b/setup.py @@ -29,5 +29,5 @@ test_suite='tests', packages=setuptools.find_packages(), install_requires=dependencies, - python_requires=">=3.9, <3.10", + python_requires=">=3.7, <3.10", ) \ No newline at end of file diff --git a/tests/gamess/test_gamess.py b/tests/gamess/test_gamess.py index b598533..04c34d7 100644 --- a/tests/gamess/test_gamess.py +++ b/tests/gamess/test_gamess.py @@ -12,7 +12,7 @@ found_champ = pytest.mark.skipif( not Path.home().joinpath(Path('software/champ')).is_dir(), reason="CHAMP not found." ) -found_games = pytest.mark.skipif( +found_gamess = pytest.mark.skipif( not Path.home().joinpath(Path('software/gamess')).is_dir(), reason="GAMESS not found." ) @@ -25,8 +25,21 @@ def setUp(self): os.mkdir("tests/test_data/temp") os.chdir('tests/test_data/temp') + @found_gamess @found_champ - @found_games + def test_userscrError(self): + atoms = Atoms('C2', [(0,0,-0.61385), (0,0,0.61385)]) + wf = WavefunctionCreator(atoms, str(Path.home().joinpath('software/champ'))) + with self.assertRaises(RuntimeError): + wf.setup_rhf(userscr=None) + os.chdir('..') + with self.assertRaises(FileNotFoundError): + wf.setup_rhf(userscr="not_exist") + os.chdir('..') + + + @found_champ + @found_gamess def test_gamess(self): atoms = Atoms('C2', [(0,0,-0.61385), (0,0,0.61385)]) wf = WavefunctionCreator(atoms, str(Path.home().joinpath('software/champ'))) @@ -38,8 +51,9 @@ def test_gamess(self): input.optwf.nopt_iter = 10 input.write('vmc.inp') atoms.calc = CHAMP(champ_loc=str(Path.home().joinpath('software/champ'))+"/bin/vmc.mov1", settings = input) - - self.assertAlmostEqual(atoms.get_total_energy(), -297.5, places=0) + energy = atoms.get_total_energy() + print(energy) + self.assertEqual(int(energy+297.5), 0) def tearDown(self): os.chdir("../../..") diff --git a/tests/sgdml/test_sgdml.py b/tests/sgdml/test_sgdml.py new file mode 100644 index 0000000..3633e60 --- /dev/null +++ b/tests/sgdml/test_sgdml.py @@ -0,0 +1,28 @@ +import os +import shutil +import unittest + +import pytest +from qmcblip.sgdml.utils import db_to_sgdml + + +class TestSgdml(unittest.TestCase): + + def setUp(self): + os.mkdir("tests/test_data/temp") + os.chdir('tests/test_data/temp') + + def test_db_to_sgdml(self): + shutil.copyfile("../test.db", "test.db") + try: + db_to_sgdml('test.db', 'test.npz') + except: + self.fail("Cannot convert DB to dataset.") + + def tearDown(self): + os.chdir("../../..") + shutil.rmtree('tests/test_data/temp') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_champio.py b/tests/test_champio.py index c14f8a9..a7fddfa 100644 --- a/tests/test_champio.py +++ b/tests/test_champio.py @@ -1,12 +1,32 @@ +import os +import shutil import unittest -from qmcblip.champio import Settings + from pydantic import ValidationError +from qmcblip.champio import Settings + class TestChampio(unittest.TestCase): + def setUp(self): + os.mkdir("tests/test_data/temp") + os.chdir('tests/test_data/temp') + def test_SetupError(self): with self.assertRaises(ValidationError): Settings() + def test_write(self): + settings = Settings.read('../C2_champ/vmc.inp') + settings.extra_obj = 'test' + try: + settings.write('temp.inp') + except: + self.fail("Cannot write vmc input with custom tags.") + + def tearDown(self): + os.chdir("../../..") + shutil.rmtree('tests/test_data/temp') + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_data/test.db b/tests/test_data/test.db new file mode 100644 index 0000000..d1ae688 Binary files /dev/null and b/tests/test_data/test.db differ