Skip to content

Commit 0bcbb96

Browse files
authored
Add files via upload
- Additional implementation of ASE interface for NEB method and use of NNP
1 parent 09935e9 commit 0bcbb96

File tree

5 files changed

+68
-50
lines changed

5 files changed

+68
-50
lines changed

multioptpy/ase_calculation_tools.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import re
55
import numpy as np
66

7-
from ase import Atoms
8-
from ase.vibrations import Vibrations
7+
try:
8+
from ase import Atoms
9+
from ase.vibrations import Vibrations
10+
except ImportError:
11+
print("ASE is not installed. Please install ASE to use this module.")
912

1013
from calc_tools import Calculationtools
1114
from parameter import UnitValueLib, number_element
@@ -80,9 +83,9 @@ def calc_exact_hess(self, atom_obj, positions, element_list):
8083
vib.clean()
8184
exact_hess = exact_hess / self.hartree2eV * (self.bohr2angstroms ** 2)
8285
if type(element_list[0]) is str:
83-
exact_hess = Calculationtools().project_out_hess_tr_and_rot(exact_hess, element_list, positions)
86+
exact_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(exact_hess, element_list, positions)
8487
else:
85-
exact_hess = Calculationtools().project_out_hess_tr_and_rot(exact_hess, [number_element(elem_num) for elem_num in element_list], positions)
88+
exact_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(exact_hess, [number_element(elem_num) for elem_num in element_list], positions)
8689
self.Model_hess = exact_hess
8790
return exact_hess
8891

@@ -99,16 +102,7 @@ def single_point(self, file_directory, element_list, iter, electric_charge_and_m
99102
for num, input_file in enumerate(file_list):
100103
try:
101104
if geom_num_list is None:
102-
print("\n",input_file,"\n")
103-
104-
with open(input_file,"r") as f:
105-
input_data = f.readlines()
106-
107-
positions = []
108-
109-
for word in input_data[2:]:
110-
positions.append(word.split()[1:4])
111-
105+
positions, _, electric_charge_and_multiplicity = xyz2list(input_file, electric_charge_and_multiplicity)
112106
else:
113107
positions = geom_num_list
114108

@@ -152,7 +146,9 @@ def single_point(self, file_directory, element_list, iter, electric_charge_and_m
152146
self.FUNCTIONAL,
153147
self.BASIS_SET
154148
)
155-
149+
elif self.software_type == "uma-s-1": # Neural Network Potential
150+
atom_obj = use_FAIRCHEMNNP(atom_obj, electric_charge_and_multiplicity, self.software_path_dict["uma-s-1"])
151+
156152
elif self.software_type == "mace_mp": # Neural Network Potential
157153
atom_obj = use_MACE_MP(atom_obj, electric_charge_and_multiplicity)
158154

@@ -301,6 +297,22 @@ def use_MACE_MP(atom_obj, electric_charge_and_multiplicity):
301297
atom_obj.calc = macemp
302298
return atom_obj
303299

300+
def use_FAIRCHEMNNP(atom_obj, electric_charge_and_multiplicity, fairchem_path):
301+
try:
302+
from fairchem.core import FAIRChemCalculator
303+
from fairchem.core.units.mlip_unit import load_predict_unit
304+
except ImportError:
305+
raise ImportError("FAIRChem.core modules not found")
306+
# Load the prediction unit
307+
predict_unit = load_predict_unit(path=fairchem_path, device="cpu")
308+
309+
# Set up the FAIRChem calculator
310+
fairchem_calc = FAIRChemCalculator(predict_unit=predict_unit, task_name="omol")
311+
atom_obj.info = {"charge": int(electric_charge_and_multiplicity[0]),
312+
"spin": int(electric_charge_and_multiplicity[1])}
313+
atom_obj.calc = fairchem_calc
314+
return atom_obj
315+
304316
def use_MACE_OFF(atom_obj, electric_charge_and_multiplicity):
305317
"""Set up and return ASE atoms object with MACE_OFF calculator.
306318
@@ -373,25 +385,11 @@ class ASEEngine(CalculationEngine):
373385
This engine uses the Atomic Simulation Environment (ASE) to interface with various
374386
quantum chemistry software packages like GAMESSUS, NWChem, Gaussian, ORCA, MACE, and MOPAC.
375387
"""
376-
def __init__(self, **kwarg):
388+
def __init__(self, **kwargs):
377389
UVL = UnitValueLib()
378390

379391
self.bohr2angstroms = UVL.bohr2angstroms
380392
self.hartree2eV = UVL.hartree2eV
381-
382-
self.START_FILE = kwarg["START_FILE"]
383-
self.N_THREAD = kwarg["N_THREAD"]
384-
self.SET_MEMORY = kwarg["SET_MEMORY"]
385-
self.FUNCTIONAL = kwarg["FUNCTIONAL"]
386-
self.BASIS_SET = kwarg["BASIS_SET"]
387-
self.FC_COUNT = kwarg["FC_COUNT"]
388-
self.BPA_FOLDER_DIRECTORY = kwarg["BPA_FOLDER_DIRECTORY"]
389-
self.Model_hess = kwarg["Model_hess"]
390-
self.unrestrict = kwarg["unrestrict"]
391-
self.software_type = kwarg["software_type"]
392-
self.hessian_flag = False
393-
self.software_path_dict = read_software_path()
394-
395393

396394
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
397395
"""Calculate energy and gradients using ASE and the configured software.
@@ -426,7 +424,7 @@ def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
426424
geometry_list_tmp, element_list, _ = xyz2list(file_list[0], None)
427425

428426
hess_count = 0
429-
software_type = config.software_type
427+
software_type = config.othersoft
430428
software_path_dict = read_software_path()
431429

432430
for num, input_file in enumerate(file_list):
@@ -472,25 +470,29 @@ def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
472470
config.FUNCTIONAL,
473471
config.BASIS_SET
474472
)
473+
elif software_type == "uma-s-1": # Neural Network Potential
474+
atom_obj = use_FAIRCHEMNNP(atom_obj, electric_charge_and_multiplicity, software_path_dict["uma-s-1"])
475475
elif software_type == "mace_mp": # Neural Network Potential
476476
atom_obj = use_MACE_MP(atom_obj, electric_charge_and_multiplicity)
477477
elif software_type == "mace_off": # Neural Network Potential
478478
atom_obj = use_MACE_OFF(atom_obj, electric_charge_and_multiplicity)
479+
480+
479481
elif software_type == "mopac":
480482
atom_obj = use_MOPAC(atom_obj, electric_charge_and_multiplicity, input_file)
481483
else:
482484
print(f"Software type '{software_type}' is not available...")
483485
raise ValueError(f"Unsupported software type: {software_type}")
484486

485487
# Calculate energy and forces
486-
e = atom_obj.get_potential_energy(apply_constraint=False) / config.hartree2eV # eV to hartree
487-
g = -1 * atom_obj.get_forces(apply_constraint=False) * config.bohr2angstroms / config.hartree2eV # eV/ang. to a.u.
488+
e = atom_obj.get_potential_energy(apply_constraint=False) / self.hartree2eV # eV to hartree
489+
g = -1 * atom_obj.get_forces(apply_constraint=False) * self.bohr2angstroms / self.hartree2eV # eV/ang. to a.u.
488490

489491
# Store results
490492
energy_list.append(e)
491493
gradient_list.append(g)
492494
gradient_norm_list.append(np.sqrt(np.linalg.norm(g)**2/(len(g)*3))) # RMS
493-
geometry_num_list.append(positions / config.bohr2angstroms) # Convert to Bohr
495+
geometry_num_list.append(positions / self.bohr2angstroms) # Convert to Bohr
494496
num_list.append(num)
495497

496498
# Handle hessian calculation if needed
@@ -505,14 +507,12 @@ def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
505507
vib.clean()
506508

507509
# Convert hessian units
508-
exact_hess = exact_hess / config.hartree2eV * (config.bohr2angstroms ** 2)
509-
510+
exact_hess = exact_hess / self.hartree2eV * (self.bohr2angstroms ** 2)
511+
510512
# Project out translational and rotational modes
511513
calc_tools = Calculationtools()
512-
element_number_list = [number_element(elem) for elem in element_list]
513-
exact_hess = calc_tools.project_out_hess_tr_and_rot(
514-
exact_hess, element_number_list, positions / config.bohr2angstroms
515-
)
514+
exact_hess = calc_tools.project_out_hess_tr_and_rot_for_coord(exact_hess, element_list, positions)
515+
516516

517517
# Save hessian
518518
np.save(os.path.join(config.NEB_FOLDER_DIRECTORY, f"tmp_hessian_{hess_count}.npy"), exact_hess)

multioptpy/ieip.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,15 @@ def __init__(self, args):
9393
self.ECP = ""
9494

9595

96-
96+
self.othersoft = args.othersoft
9797
self.basic_set_and_function = args.functional+"/"+args.basisset
9898
self.force_data = force_data_parser(args)
99-
if args.usextb == "None" and args.usedxtb == "None":
99+
if self.othersoft != "None":
100+
self.iEIP_FOLDER_DIRECTORY = args.INPUT+"_iEIP_"+args.othersoft+"_"+str(datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f")[:-2])+"/"
101+
102+
103+
104+
elif args.usextb == "None" and args.usedxtb == "None":
100105
self.iEIP_FOLDER_DIRECTORY = args.INPUT+"_iEIP_"+self.basic_set_and_function.replace("/","_")+"_"+str(datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f")[:-2])+"/"
101106
else:
102107
if args.usedxtb != "None":
@@ -713,7 +718,9 @@ def close_target_force(self, L, Lt, geom_num_list_1, geom_num_list_2):
713718

714719

715720
def optimize(self):
716-
if self.args.pyscf:
721+
if self.othersoft != "None":
722+
from ase_calculation_tools import Calculation
723+
elif self.args.pyscf:
717724
from pyscf_calculation_tools import Calculation
718725
elif self.args.usextb != "None" and self.args.usedxtb == "None":
719726
from tblite_calculation_tools import Calculation
@@ -762,8 +769,9 @@ def optimize(self):
762769
spin_multiplicity = self.spin_multiplicity[i] or electric_charge_and_multiplicity_list[i][1],
763770
excited_state = self.excite_state_list[i],
764771
dft_grid=self.dft_grid,
765-
ECP = self.ECP))
766-
772+
ECP = self.ECP,
773+
software_type=self.othersoft,))
774+
767775
SP_list[i].cpcm_solv_model = self.cpcm_solv_model
768776
SP_list[i].alpb_solv_model = self.alpb_solv_model
769777

multioptpy/interface.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ def ieipparser(parser):
5858
parser.add_argument("-cpcm", "--cpcm_solv_model", type=str, default=None, help='use CPCM solvent model for xTB (Defalut setting is not using this model.) (ex.) water')
5959
parser.add_argument("-alpb", "--alpb_solv_model", type=str, default=None, help='use ALPB solvent model for xTB (Defalut setting is not using this model.) (ex.) water')#ref.: J. Chem. Theory Comput. 2021, 17, 7, 4250–4261 https://doi.org/10.1021/acs.jctc.1c00471
6060
parser.add_argument("-grid", "--dft_grid", type=int, default=3, help="fineness of grid for DFT calculation (default: 3 (0~9))")
61-
61+
parser.add_argument("-os", "--othersoft", type=str, default="None", help='use other QM software. default is not using other QM software. (require python module, ASE (Atomic Simulation Environment)) (ex.) orca, gaussian, gamessus, mace_mp etc.')
62+
6263
args = parser.parse_args()#model_function_mode
6364
args.fix_atoms = []
6465
args.gradient_fix_atoms = []
@@ -197,6 +198,7 @@ def nebparser(parser):
197198
parser.add_argument("-mem", "--SET_MEMORY", type=str, default='1GB', help='use mem(ex. 1GB)')
198199
parser.add_argument("-cineb", "--apply_CI_NEB", type=int, default='99999', help='apply CI_NEB method')
199200
parser.add_argument("-sd", "--steepest_descent", type=int, default='99999', help='apply steepest_descent method')
201+
200202

201203
class CGAction(argparse.Action):
202204
def __call__(self, parser, namespace, values, option_string=None):
@@ -235,6 +237,7 @@ def __call__(self, parser, namespace, values, option_string=None):
235237

236238
parser.add_argument('-modelhess','--use_model_hessian', nargs='?', help="use model hessian. (Default: not using model hessian If you specify only option, Improved Lindh + Grimme's D3 dispersion model hessian is used.) (ex. lindh, gfnff, gfn0xtb, fischer, fischerd3, fischerd4, schlegel, swart, lindh2007, lindh2007d3, lindh2007d4)", action=ModelhessAction, default=None)
237239
parser.add_argument("-mfc", "--calc_model_hess", type=int, default=50, help='calculate model hessian per steps (ex.) [steps per one hess calculation]')
240+
parser.add_argument("-os", "--othersoft", type=str, default="None", help='use other QM software. default is not using other QM software. (require python module, ASE (Atomic Simulation Environment)) (ex.) orca, gaussian, gamessus, mace_mp etc.')
238241

239242
parser = parser_for_biasforce(parser)
240243
args = parser.parse_args()

multioptpy/neb.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
from pyscf_calculation_tools import PySCFEngine
6868
from psi4_calculation_tools import Psi4Engine
6969
from dxtb_calculation_tools import DXTBEngine
70+
from ase_calculation_tools import ASEEngine
7071

7172
class NEBConfig:
7273
"""Configuration management class for NEB calculations"""
@@ -131,6 +132,7 @@ def __init__(self, args):
131132
self.dft_grid = int(args.dft_grid)
132133
self.spring_constant_k = 0.01
133134
self.force_const_for_cineb = 0.01
135+
self.othersoft = args.othersoft
134136

135137
# FIRE method parameters
136138
self.FIRE_dt = 0.1
@@ -213,8 +215,11 @@ def make_neb_work_directory(self, input_file):
213215
tmp_name = input_file
214216

215217
timestamp = str(datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f")[:-2])
216-
217-
if self.usextb == "None" and self.usedxtb == "None":
218+
if self.othersoft != "None":
219+
return tmp_name + "_NEB_" + self.othersoft + "_" + timestamp + "/"
220+
221+
222+
elif self.usextb == "None" and self.usedxtb == "None":
218223
return tmp_name + "_NEB_" + self.basic_set_and_function.replace("/", "_") + "_" + timestamp + "/"
219224
else:
220225
if self.usextb != "None":
@@ -229,7 +234,9 @@ class CalculationEngineFactory:
229234
@staticmethod
230235
def create_engine(config):
231236
"""Create appropriate calculation engine based on configuration"""
232-
if config.usextb != "None":
237+
if config.othersoft != "None":
238+
return ASEEngine()
239+
elif config.usextb != "None":
233240
return TBLiteEngine()
234241
elif config.usedxtb != "None":
235242
return DXTBEngine()

multioptpy/optimization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def _make_init_directory(self, file):
141141
date = datetime.datetime.now().strftime("%Y_%m_%d")
142142
base_dir = f"{date}/{self.START_FILE[:-4]}_OPT_"
143143

144-
if self.args.othersoft != "None":
144+
if self.othersoft != "None":
145145
self.BPA_FOLDER_DIRECTORY = f"{base_dir}ASE_{timestamp}/"
146146
elif self.args.usextb == "None" and self.args.usedxtb == "None":
147147
self.BPA_FOLDER_DIRECTORY = f"{base_dir}{self.FUNCTIONAL}_{self.BASIS_SET}_{timestamp}/"

0 commit comments

Comments
 (0)