def process_spins(trajectory_fnms): newtrajectory_fnms = [] logger.info("Pre-processing spins of trajectories") for i in range(len(trajectory_fnms)): M = Molecule(trajectory_fnms[i]) # Read in the charge and spin on the whole system. srch = lambda s: np.array([ float( re.search( '(?<=%s )[-+]?[0-9]*\.?[0-9]*([eEdD][-+]?[0-9]+)?' % s, c). group(0)) for c in M.comms if all([k in c for k in ('charge', 'sz')]) ]) Chgs = srch('charge') # An array of the net charge. SpnZs = srch('sz') # An array of the net Z-spin. chg, chgpass = extract_int(Chgs, 0.3, 1.0, label="charge", verbose=False) spn, spnpass = extract_int(abs(SpnZs), 0.3, 1.0, label="spin-z", verbose=False) nproton = sum([Elements.index(j) for j in M.elem]) nelectron = nproton + chg # Calculate the new spins if spins are inconsistent. Extracts all spin values from trajectory file, and uses # the absolute values of all spin/charge-appropriate rounded spin values. if not spnpass: logger.info("Generating new spins for " + trajectory_fnms[i]) newspins = [] for spinnew in SpnZs: spinnew = int(round(spinnew)) if ((nelectron - spinnew) / 2) * 2 != (nelectron - spinnew): continue if abs(spinnew) in newspins: continue else: newspins.append(abs(spinnew)) # Write new trajectory files for new spins and add to trajectory list for spinrpl in newspins: for j in range(len(M.comms)): M.comms[j] = re.sub( '(?<=%s )[-+]?[0-9]*\.?[0-9]*([eEdD][-+]?[0-9]+)?' % "sz", str(float(spinrpl)), M.comms[j]) newfilename = os.path.splitext( trajectory_fnms[i])[0] + "_SZ" + str( spinrpl) + os.path.splitext(trajectory_fnms[i])[1] M.write(newfilename) newtrajectory_fnms.append(newfilename) # Move original trajectory file to *.parent and exclude it from the trajectory list # os.rename(trajectory_fnms[i], trajectory_fnms[i]+".parent") else: newtrajectory_fnms.append(trajectory_fnms[i]) return newtrajectory_fnms
def load_bondorder(R, P): """ Extracts QM bond order information from a calculation. This actually reads the Mayer bond order matrix from a Q-Chem output file. This function looks funny because I implemented it after some calculations already started running. :P Therefore it checks to see if the ".bnd" file exists, and if not, it creates one. Intended to be run in a directory with transition-state.tar.bz2 obviously. Parameters ---------- R, P: Molecule Molecule objects for the reactant and product. """ for bz2 in ['transition-state.tar.bz2', 'freezing-string.tar.bz2']: extract_tar(bz2, [ 'irc_reactant.out', 'irc_product.out', 'irc_reactant.bnd', 'irc_product.bnd' ]) if not os.path.exists('irc_reactant.bnd'): if os.path.exists('irc_reactant.out'): M = Molecule('irc_reactant.out') if hasattr(M, 'qm_bondorder'): bo_reactant = Molecule('irc_reactant.out').qm_bondorder np.savetxt('irc_reactant.bnd', bo_reactant, fmt="%8.3f") else: logger.info("Warning: No QM bond order for reactant") bo_reactant = np.zeros((R.na, R.na)) else: logger.info("Warning: No QM bond order for reactant") bo_reactant = np.zeros((R.na, R.na)) else: bo_reactant = np.loadtxt('irc_reactant.bnd') if not os.path.exists('irc_product.bnd'): if os.path.exists('irc_product.out'): M = Molecule('irc_product.out') if hasattr(M, 'qm_bondorder'): bo_product = Molecule('irc_product.out').qm_bondorder np.savetxt('irc_product.bnd', bo_product, fmt="%8.3f") else: logger.info("Warning: No QM bond order for product") bo_product = np.zeros((R.na, R.na)) else: logger.info("Warning: No QM bond order for product") bo_product = np.zeros((P.na, P.na)) else: bo_product = np.loadtxt('irc_product.bnd') R.qm_bondorder = bo_reactant P.qm_bondorder = bo_product
def main(): # Parse user input. args = parse_user_input() # Load IRC coordinates. M = Molecule(args.xyz) M.load_popxyz(args.pop) # Rebuild bonds for the reactant (0th frame). R = M[0] R.build_topology() # Rebuild bonds for the product (last frame). P = M[-1] P.build_topology() # Don't draw reaction if the reactant and product are the same. if MolEqual(R, P): logger.info( "Exiting because reactant and product molecules are the same") sys.exit() # Load Mayer bond order matrices. load_bondorder(R, P) # Create SVG drawings of the reactant and product. canr, strr = make_obmol(R, "reactant") canp, strp = make_obmol(P, "product") # IRC energy. ArcE = np.loadtxt(args.energy) E = ArcE[:, 1] # "Canonicalize" the reaction direction. fwd = True if max([len(m.L()) for m in R.molecules]) > max([len(m.L()) for m in P.molecules]): # Reactions should go in the direction of increasing molecule size. fwd = False elif (max([len(m.L()) for m in R.molecules]) == max( [len(m.L()) for m in P.molecules])) and (E[0] < E[-1]): # If molecules on both sides are the same size, go in the exothermic direction. fwd = False if fwd: shutil.copy2("irc.nrg", "plot.nrg") with open("reaction.can", "w") as f: print >> f, "%s>>%s" % (canr, canp) else: ArcE = ArcE[::-1] ArcE[:, 0] *= -1 ArcE[:, 0] -= ArcE[:, 0][0] ArcE[:, 1] -= ArcE[:, 1][0] np.savetxt("plot.nrg", ArcE, fmt="% 14.6f", header="Arclength(Ang) Energy(kcal/mol)") strr, strp = strp, strr E = E[::-1] with open("reaction.can", "w") as f: print >> f, "%s>>%s" % (canp, canr) # This string looks something like C2H2 + H2O -> C2H4O. # The funny character is a Unicode entity for the "right arrow" strrxn = strr + ' ⟶ ' + strp # Create the components of the final image. # First write a text box with the chemical equation. with open("reaction_text.svg", "w") as f: print >> f, svgrect.format(text=strrxn, frgb="255,210,80", srgb="255,165,128") # Next write a text box with the charge and multiplicity. net_charge = int(round(sum(M.qm_mulliken_charges[0]))) net_mult = int(abs(round(sum(M.qm_mulliken_spins[0]))) + 1) with open("chargemult_text.svg", "w") as f: print >> f, svgrect.format(text="Charge = %i ; Multiplicity = %i" % (net_charge, net_mult), frgb="194,225,132", srgb="154,205,50") # Write a text box with the reaction energy and barrier height. with open("energy_text.svg", "w") as f: print >> f, svgrect.format( text="ΔE = %.2f kcal ; Eₐ = %.2f kcal" % (E[-1] - E[0], np.max(E)), frgb="142,200,255", srgb="30,144,255") # Run script to generate energy diagram plot (uses Gnuplot). _exec("plot-rc.sh", print_command=False) # Write a text heading with the location of the calculation on disk. with open("folder_text.svg", "w") as f: print >> f, svgtext.format(text="Path: %s" % os.getcwd().split( os.environ['HOME'])[-1].split("Refinement")[-1].strip('/')) # Print some skeleton SVG files (actually part of this script). with open("arrow.svg", "w") as f: print >> f, svgarrow with open("base.svg", "w") as f: print >> f, svgbase # Finally, compose the image. compose(fwd)