Skip to content

Commit 91fd035

Browse files
authored
Merge branch 'master' into NEW
2 parents f9ce0a7 + 2558f59 commit 91fd035

9 files changed

Lines changed: 124 additions & 73 deletions

ash/interfaces/interface_DFTB.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ def write_DFTB_input(hamiltonian,xtbmethod,xyzfilename, elems,coords,charge,mult
266266
#############
267267
method1=f"Hamiltonian = {hamiltonian}" +"{"+"\n"
268268
inputlines.append(method1)
269+
inputlines.append(f"Charge = {charge}\n")
269270
if 'XTB' in hamiltonian.upper():
270271
method2=f"Method = '{xtbmethod}'"+'\n\n'
271272
inputlines.append(method2)
@@ -417,9 +418,12 @@ def grab_energy_gradient_DFTB(file, numatoms, Grad=False, PC=False, numpc=None):
417418
if 'Dipole moment:' in line:
418419
pcgrab=False
419420

420-
if Grad is True and PC is True:
421-
if np.any(pcgradient) is False:
422-
print("Error: PCgradient array from DFTB output is zero. Something went wrong.")
421+
if Grad is True:
422+
if not np.any(gradient):
423+
print("Error: gradient array from DFTB output is zero. Something went wrong (e.g. SCC not converged or DFTB+ crashed before writing forces). Check DFTB outputfile.")
424+
ashexit()
425+
if PC is True and not np.any(pcgradient):
426+
print("Error: PCgradient array from DFTB output is zero. Something went wrong (e.g. SCC not converged or DFTB+ crashed before writing forces). Check DFTB outputfile.")
423427
ashexit()
424428

425429
return energy,gradient,pcgradient

ash/interfaces/interface_Grimme_corrections.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ def calc_gcp(fragment=None, xyzfile=None, current_coords=None, elems=None, funct
129129
command_list=['mctc-gcp', xyzfile, '-l', functional]
130130
if Grad:
131131
command_list.append('--grad')
132+
# mctc-gcp appends into an existing 'gradient' file (Turbomole format) instead of
133+
# writing 'gcp_gradient'; remove it so the standalone file is always created.
134+
if os.path.exists('gradient'):
135+
os.remove('gradient')
132136

133137
print("command_list:", command_list)
134138
with open('gcp.out', 'w') as ofile:
@@ -171,7 +175,9 @@ def run(self, current_coords=None, current_MM_coords=None, MMcharges=None, qm_el
171175
elems=None, Grad=False, PC=False, numcores=None, restart=False, label=None,
172176
charge=None, mult=None):
173177

174-
frag = ash.Fragment(elems=elems, coords=current_coords,printlevel=0)
178+
if qm_elems is None:
179+
qm_elems = elems
180+
frag = ash.Fragment(elems=qm_elems, coords=current_coords,printlevel=0)
175181
eg = calc_gcp(fragment=frag, functional=self.functional, Grad=Grad, printlevel=self.printlevel)
176182
if Grad:
177183
self.energy = eg[0]

ash/interfaces/interface_OpenMM.py

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2695,8 +2695,6 @@ def OpenMM_Modeller(pdbfile=None, forcefield_object=None, forcefield=None, xmlfi
26952695
xmlfile = "amber10.xml"
26962696
elif forcefield == 'Amber14':
26972697
xmlfile = "amber14-all.xml"
2698-
elif forcefield == 'Amber96':
2699-
xmlfile = "amber96.xml"
27002698
elif forcefield == 'CHARMM36':
27012699
xmlfile = "charmm36.xml"
27022700
elif forcefield == 'CHARMM2013':
@@ -2708,53 +2706,45 @@ def OpenMM_Modeller(pdbfile=None, forcefield_object=None, forcefield=None, xmlfi
27082706
else:
27092707
print("Unknown forcefield")
27102708
exit()
2711-
elif xmlfile is not None:
2712-
print("Using xmlfile:", xmlfile)
2713-
elif forcefield_object is not None:
2714-
pass
2715-
else:
2716-
print("You must provide a forcefield name, forcefieldobject or xmlfile keywords!")
2717-
ashexit()
2718-
2719-
#Water model selection for CHARMM forcefields
2720-
if 'CHARMM' in forcefield:
2721-
# Using specific CHARMM36 version of TIP3P
2722-
if watermodel is None:
2723-
print("No watermodel selected.")
2724-
if waterxmlfile is None:
2725-
print("No waterxmlfile selected either")
2726-
print("Selecting automatically recommended CHARMM-style TIP3P")
2727-
watermodel='tip3p'
2728-
2729-
print("watermodel:", watermodel)
2730-
if watermodel.lower() == "tip3p":
2731-
modeller_solvent_name="tip3p" #Used when adding solvent
2732-
waterxmlfile = "charmm36/water.xml"
2733-
print("Waterxmlfile selected:", waterxmlfile)
2734-
2735-
2736-
#Water model selection for AMber forcefields
2737-
if 'Amber' in forcefield:
2738-
if watermodel is None:
2739-
print("No watermodel selected.")
2740-
if waterxmlfile is None:
2741-
print("No waterxmlfile selected either")
2742-
print("Selecting automatically recommended TIP3P-4B (watermodel='tip3pfb')")
2743-
print("This is a reparameterized version of TIP3P")
2744-
watermodel='tip3pfb'
2745-
print("watermodel:", watermodel)
2746-
# Using specific Amber FB version of TIP3P
2747-
if watermodel.lower() == "tip3pfb" or watermodel.lower() == "tip3p-fb":
2748-
modeller_solvent_name="tip3p" #Used when adding solvent
2749-
waterxmlfile = "amber14/tip3pfb.xml" #NOTE: this is not actually TIP3P but a reparaterized version
2750-
elif watermodel.lower () == 'tip3p':
2751-
modeller_solvent_name="tip3p"
2752-
if forcefield == 'Amber14':
2753-
waterxmlfile = "amber14/tip3p.xml"
2754-
else:
2755-
waterxmlfile = "tip3p.xml"
2756-
print("Waterxmlfile selected:", waterxmlfile)
27572709

2710+
#Water model selection for CHARMM forcefields
2711+
if 'CHARMM' in forcefield:
2712+
# Using specific CHARMM36 version of TIP3P
2713+
if watermodel is None:
2714+
print("No watermodel selected.")
2715+
if waterxmlfile is None:
2716+
print("No waterxmlfile selected either")
2717+
print("Selecting automatically recommended CHARMM-style TIP3P")
2718+
watermodel='tip3p'
2719+
2720+
print("watermodel:", watermodel)
2721+
if watermodel.lower() == "tip3p":
2722+
modeller_solvent_name="tip3p" #Used when adding solvent
2723+
waterxmlfile = "charmm36/water.xml"
2724+
print("Waterxmlfile selected:", waterxmlfile)
2725+
2726+
2727+
#Water model selection for AMber forcefields
2728+
if 'Amber' in forcefield:
2729+
if watermodel is None:
2730+
print("No watermodel selected.")
2731+
if waterxmlfile is None:
2732+
print("No waterxmlfile selected either")
2733+
print("Selecting automatically recommended TIP3P-4B (watermodel='tip3pfb')")
2734+
print("This is a reparameterized version of TIP3P")
2735+
watermodel='tip3pfb'
2736+
print("watermodel:", watermodel)
2737+
# Using specific Amber FB version of TIP3P
2738+
if watermodel.lower() == "tip3pfb" or watermodel.lower() == "tip3p-fb":
2739+
modeller_solvent_name="tip3p" #Used when adding solvent
2740+
waterxmlfile = "amber14/tip3pfb.xml" #NOTE: this is not actually TIP3P but a reparaterized version
2741+
elif watermodel.lower () == 'tip3p':
2742+
modeller_solvent_name="tip3p"
2743+
if forcefield == 'Amber14':
2744+
waterxmlfile = "amber14/tip3p.xml"
2745+
else:
2746+
waterxmlfile = "tip3p.xml"
2747+
print("Waterxmlfile selected:", waterxmlfile)
27582748

27592749
############
27602750
# Define a forcefield if using XML-files
@@ -2778,9 +2768,18 @@ def OpenMM_Modeller(pdbfile=None, forcefield_object=None, forcefield=None, xmlfi
27782768
forcefield_obj = openmm_app.forcefield.ForceField(xmlfile,waterxmlfile)
27792769
elif extraxmlfile is not None and waterxmlfile is not None:
27802770
forcefield_obj = openmm_app.forcefield.ForceField(xmlfile,extraxmlfile,waterxmlfile)
2781-
else:
2771+
2772+
elif forcefield_object is not None:
27822773
print("Using forcefield object provided")
27832774
forcefield_obj= forcefield_object
2775+
2776+
if watermodel is not None or waterxmlfile is not None:
2777+
print("Warning: watermodel/waterxmlfile ignored when forcefield_object is supplied")
2778+
2779+
2780+
else:
2781+
print("You must provide a forcefield name, forcefieldobject or xmlfile keywords!")
2782+
ashexit()
27842783

27852784
print("PDBfile:", pdbfile)
27862785
print("pH:", pH)

ash/interfaces/interface_dlfind.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ def setup_constraints_act_frozen(self):
526526
self.frozenatoms = self.frozenatoms+frozenatoms_xyz
527527
print("frozenatoms_z:", frozenatoms_z)
528528
if self.actatoms is not None:
529-
print_if_level("Actatoms provided: {self.actatoms}", self.printlevel,2)
529+
print_if_level(f"Actatoms provided: {self.actatoms}", self.printlevel, 2)
530530

531531
if self.PBC:
532532
print("PBC detected. Adding 4 dummy atoms to actatoms if not already present")
@@ -937,4 +937,4 @@ def define_residues(fragment=None, min_size=5, max_size=15):
937937
else:
938938
residues.append(current_res)
939939

940-
return residues
940+
return residues

ash/interfaces/interface_xtb.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ def __init__(self, xtbdir=None, xtbmethod='GFN1', runmode='inputfile', numcores=
121121
self.analytic_hessian=False
122122
print_line_with_mainheader(f"{self.theorynamelabel}Theory initialization")
123123

124-
# Hardness of pointcharge. GAM factor. Big number means PC behaviour
124+
# Hardness of pointcharge. GAM factor. Big number means PC behaviour.
125+
# If hardness is set to 'elements', xtb's element-specific hardness is used (see https://xtb-docs.readthedocs.io/en/latest/pcem.html).
125126
self.hardness=hardness_PC
126127

127128
# Accuracy (0.1 it quite tight)
@@ -521,7 +522,11 @@ def run(self, current_coords=None, current_MM_coords=None, MMcharges=None, qm_el
521522
print("...")
522523
# Create pcharge file if PC
523524
if PC:
524-
create_xtb_pcfile_general(current_MM_coords, MMcharges, hardness=self.hardness)
525+
if self.hardness == 'elements':
526+
hardness = mm_elems
527+
else:
528+
hardness = [self.hardness] * len(MMcharges)
529+
create_xtb_pcfile_general(current_MM_coords, MMcharges, hardness=hardness)
525530

526531
# Run xTB (note: passing PC and Grad Booleans)
527532
run_xtb_SP(self.xtbdir, self.xtbmethod, coordfile, charge, mult, printlevel=self.printlevel, PC=PC, solvent=self.solvent,
@@ -1059,8 +1064,8 @@ def create_xtb_pcfile_general(coords,pchargelist,hardness=1000):
10591064
#https://xtb-docs.readthedocs.io/en/latest/pcem.html
10601065
with open('pcharge', 'w') as pcfile:
10611066
pcfile.write(str(len(pchargelist))+'\n')
1062-
for p,c in zip(pchargelist,coords):
1063-
line = "{} {} {} {} {}".format(p, c[0], c[1], c[2], hardness)
1067+
for p,c,h in zip(pchargelist,coords,hardness):
1068+
line = "{} {} {} {} {}".format(p, c[0], c[1], c[2], h)
10641069
pcfile.write(line+'\n')
10651070

10661071

ash/modules/module_QMMM.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,8 @@ def mech_run(self, current_coords=None, elems=None, Grad=False, numcores=1, exit
985985

986986
self.MMenergy, self.MMgradient = self.mm_theory.run(current_coords=current_coords, qmatoms=self.qmatoms, Grad=True)
987987
else:
988-
print("QM/MM Grad is false")
988+
if self.printlevel >= 2:
989+
print("QM/MM Grad is false")
989990
self.MMenergy = self.mm_theory.run(current_coords=current_coords, qmatoms=self.qmatoms)
990991
else:
991992
self.MMenergy=0
@@ -1404,7 +1405,8 @@ def elstat_run(self, current_coords=None, elems=None, Grad=False, numcores=1, ex
14041405

14051406
self.MMenergy, self.MMgradient= self.mm_theory.run(current_coords=current_coords, qmatoms=self.qmatoms, Grad=True)
14061407
else:
1407-
print("QM/MM Grad is false")
1408+
if self.printlevel >= 2:
1409+
print("QM/MM Grad is false")
14081410
self.MMenergy= self.mm_theory.run(current_coords=current_coords, qmatoms=self.qmatoms)
14091411
else:
14101412
self.MMenergy=0

ash/modules/module_coords.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3940,13 +3940,15 @@ def check_charge_mult(charge, mult, theorytype, fragment, jobtype, theory=None,
39403940

39413941
#Note: theory needs to be set
39423942
if charge is None or mult is None:
3943-
print(BC.WARNING,f"Warning: Charge/mult was not provided to {jobtype}",BC.END)
3944-
print("Checking if present in QM/MM object")
3943+
if printlevel >= 1:
3944+
print(BC.WARNING,f"Warning: Charge/mult was not provided to {jobtype}",BC.END)
3945+
print("Checking if present in QM/MM object")
39453946
if theory.qm_charge != None and theory.qm_mult != None:
3946-
print("Found qm_charge and qm_mult attributes.")
39473947
charge=theory.qm_charge
39483948
mult=theory.qm_mult
3949-
print(f"Using charge={charge} and mult={mult}")
3949+
if printlevel >= 1:
3950+
print("Found qm_charge and qm_mult attributes.")
3951+
print(f"Using charge={charge} and mult={mult}")
39503952
elif fragment.charge != None and fragment.mult != None:
39513953
print(BC.WARNING,"Fragment contains charge/mult information: Charge: {} Mult: {} Using this instead".format(fragment.charge,fragment.mult), BC.END)
39523954
print(BC.WARNING,"Make sure this is what you want!", BC.END)

ash/modules/module_hybridtheory.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ def run(self, current_coords=None, current_MM_coords=None, MMcharges=None, qm_el
433433
full_dimension=current_coords.shape[0]
434434
energies=[]
435435
gradients=[]
436+
pc_gradient=None
436437
chosen_coords=current_coords
437438
chosen_elems=qm_elems
438439
for i,theory in enumerate(self.theories):
@@ -468,6 +469,8 @@ def run(self, current_coords=None, current_MM_coords=None, MMcharges=None, qm_el
468469
#print(f"Theory: {theory.theorynamelabel} gradient shape", eg_tuple[1].shape)
469470
energy = eg_tuple[0]
470471
tempgrad = eg_tuple[1]
472+
if PC and len(eg_tuple) == 3:
473+
pc_gradient = eg_tuple[2]
471474
# Assemble gradient of correct dimension
472475
if i+1 == 1 and self.theory1_atoms is not None:
473476
fullgrad=np.zeros((full_dimension,3))
@@ -539,6 +542,8 @@ def run(self, current_coords=None, current_MM_coords=None, MMcharges=None, qm_el
539542
print("Gradient (Combined):", self.gradient)
540543

541544
if Grad:
545+
if PC and pc_gradient is not None:
546+
return self.energy, self.gradient, pc_gradient
542547
return self.energy, self.gradient
543548
else:
544549
return self.energy

ash/modules/module_surface_new.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def calc_surface(
9191
'subfrctor': subfrctor,
9292
'force_noPBC': force_noPBC, 'PBC_format_option': PBC_format_option,
9393
'ActiveRegion': ActiveRegion,
94+
'actatoms': actatoms,
9495
'result_write_to_disk':False,
9596
'printlevel':printlevel,
9697
}
@@ -154,7 +155,14 @@ def calc_surface(
154155
ashexit()
155156

156157
# Build connectivity once
157-
conn = _build_connectivity(fragment.coords, fragment.elems)
158+
# Build connectivity once
159+
if ActiveRegion is True and actatoms is not None and len(actatoms) > 0:
160+
print(f"Building connectivity only inside active region: {len(actatoms)} atoms")
161+
conn = _build_connectivity(fragment.coords, fragment.elems,
162+
atom_indices=actatoms)
163+
else:
164+
print(f"Building connectivity for full fragment: {len(fragment.coords)} atoms")
165+
conn = _build_connectivity(fragment.coords, fragment.elems)
158166

159167
# Changing printlevel of fragment
160168
fragment.printlevel=printlevel
@@ -406,7 +414,7 @@ def calc_surface(
406414
optimizerobj.Hess_inv = None
407415

408416
#Running optimizer object, passing theory, fragment, constraints and possible extra kws
409-
result = optimizerobj.run(theory=theory,fragment=fragment, constraints=allconstraints, **extraoopt_run_kws)
417+
result = optimizerobj.run(theory=theory,fragment=fragment, constraints=allconstraints, charge=charge, mult=mult, **extraoopt_run_kws)
410418

411419
#if pointcount == 2:
412420
# print("2nd point optimization result:", result)
@@ -1150,21 +1158,41 @@ def _measure_dihedral(coords, i, j, k, l):
11501158
# Connectivity
11511159
# ---------------------------------------------------------------------------
11521160

1153-
def _build_connectivity(coords, elems):
1161+
def _build_connectivity(coords, elems, atom_indices=None):
11541162
coords = np.asarray(coords)
11551163
n = len(elems)
1164+
11561165
radii = np.array([
1157-
_COVALENT_RADII.get(e.capitalize(), _DEFAULT_RADIUS) for e in elems
1166+
_COVALENT_RADII.get(e.capitalize(), _DEFAULT_RADIUS)
1167+
for e in elems
11581168
])
1169+
1170+
# Keep full-length connectivity list so downstream code
1171+
# can continue using global atom indices
11591172
conn = [set() for _ in range(n)]
1160-
for i in range(n):
1161-
for j in range(i + 1, n):
1173+
1174+
# Default behaviour: full-system connectivity
1175+
if atom_indices is None:
1176+
atom_indices = range(n)
1177+
else:
1178+
atom_indices = list(atom_indices)
1179+
1180+
nsel = len(atom_indices)
1181+
1182+
for a in range(nsel):
1183+
i = atom_indices[a]
1184+
1185+
for b in range(a + 1, nsel):
1186+
j = atom_indices[b]
1187+
11621188
dist = np.linalg.norm(coords[i] - coords[j])
11631189
threshold = radii[i] + radii[j] + _CONNECTIVITY_TOLERANCE
1164-
# Ignore very short distances (e.g. same atom or ghost atoms)
1190+
1191+
# Ignore very short distances
11651192
if 0.4 < dist < threshold:
11661193
conn[i].add(j)
11671194
conn[j].add(i)
1195+
11681196
return conn
11691197

11701198
def _atoms_on_side(start, fixed, conn):
@@ -1488,7 +1516,7 @@ def _preset_geometry_restraint(fragment, RC_list, rc_values, optimizerobj,
14881516
preset_args = {k: v for k, v in opt_arguments.items()
14891517
if k not in ('constraints', 'constrainvalue')}
14901518
# Optimizing with restraint theory, passing extraconstraints as contraints if present
1491-
optimizerobj.run(theory=restraint_theory,fragment=fragment, constraints=extraconstraints, **extraoopt_run_kws)
1519+
optimizerobj.run(theory=restraint_theory,fragment=fragment, constraints=extraconstraints, charge=charge, mult=mult, **extraoopt_run_kws)
14921520

14931521
#optimizer(
14941522
# fragment=fragment, theory=restraint_theory, constraints=extraconstraints,

0 commit comments

Comments
 (0)