Skip to content

Commit

Permalink
Merge pull request #281 from ReactionMechanismGenerator/dev
Browse files Browse the repository at this point in the history
Remove RMG-Java code and fix various related glitches
  • Loading branch information
jonwzheng authored Jul 25, 2024
2 parents 3f9fb1c + 21aa9a9 commit 20b7c24
Show file tree
Hide file tree
Showing 12 changed files with 40 additions and 594 deletions.
239 changes: 0 additions & 239 deletions rmgweb/database/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,245 +485,6 @@ def reactionHasReactants(reaction, reactants):
return same_species_lists(reaction.reactants, reactants, strict=False)


def getRMGJavaKineticsFromReaction(reaction):
"""
Get the kinetics for the given `reaction` (with reactants and products as :class:`Species`)
Returns a copy of the reaction, with kinetics estimated by Java.
"""
reactant_list = [species.molecule[0] for species in reaction.reactants]
product_list = [species.molecule[0] for species in reaction.products]
reaction_list = getRMGJavaKinetics(reactant_list, product_list)
# assert len(reactionList) == 1
if len(reaction_list) > 1:
print("WARNING - RMG-Java identified {0} reactions that match {1!s} instead of 1".format(len(reaction_list), reaction))
reaction_list[0].kinetics.comment += "\nWARNING - RMG-Java identified {0} reactions that match this. These kinetics are just from one of them.".format(len(reaction_list))
if len(reaction_list) == 0:
print("WARNING - RMG-Java could not find the reaction {0!s}".format(reaction))
return None
return reaction_list[0]


def getRMGJavaKinetics(reactantList, productList=None):
"""
Get the kinetics for the given `reaction` as estimated by RMG-Java. The
reactants and products of the given reaction should be :class:`Molecule`
objects.
This is done by querying a socket running RMG-Java as a service. We
construct the input file for a PopulateReactions job, pass that as input
to the RMG-Java service, then parse the output to find the kinetics of
the reaction we are interested in.
"""

def formSpecies(species):
"""
This function takes a species string from RMG-Java containing both name
and adjlist and returns them separately.
"""
lines = species.split("\n")
species_name = lines[0]
adjlist = "\n".join(lines[1:])
return species_name, adjlist

def cleanResponse(response):
"""
This function cleans up response from PopulateReactions server and gives a
species dictionary and reactions list.
"""

# Split species dictionary from reactions list
response = response.split("\n\n\n")
species_list = response[0].split("\n\n")
reactions = response[1].split("\n\n")
reactions = reactions[1]

# split species into adjacency lists with names
species_dict = [formSpecies(item) for item in species_list]

# split reactions into list of single line reactions
reactions_list = reactions.split("\n")

return species_dict, reactions_list

def searchReaction(reactionline, reactantNames, productNames):
"""
Reads reaction line and returns True if reaction occurs:
reactant1 + reactant2 --> product1 + product2
Finds both bimolecular and unimolecular reactions for only 1 reactant input, or only 1 product.
(reactants and products could be in either order 1,2 or 2,1)
"""
lines = reactionline.split("\t")
reaction_string = lines[0]
reactants, products = reaction_string.split(" --> ")
reactants = reactants.split(' + ')
products = products.split(' + ')

reactants_match = len(reactantNames) == 0
if len(reactantNames) == len(reactants):
reactants_match = sorted(reactants) == sorted(reactantNames)
elif len(reactantNames) == 1 and len(reactants) > 1:
reactants_match = all([r == reactantNames[0] for r in reactants])

products_match = len(productNames) == 0
if len(productNames) == len(products):
products_match = sorted(products) == sorted(productNames)
elif len(productNames) == 1 and len(products) > 1:
products_match = all([p == productNames[0] for p in products])

return (reactants_match and products_match)

def extractKinetics(reactionline):
"""
Takes a reaction line from RMG and creates Arrhenius object from
the kinetic data, as well as extracts names of reactants, products and comments.
Units from RMG-Java are in cm3, mol, s.
Reference Temperature T0 = 1 K.
"""
lines = reactionline.split("\t")

reaction_string = lines[0]
reactants, products = reaction_string.split(" --> ")
reactants = reactants.split(" + ")
products = products.split(" + ")

if len(reactants) == 1:
Aunits = "s^-1"
elif len(reactants) == 2:
Aunits = "cm**3/mol/s"
else: # 3 reactants?
Aunits = "cm**6/(mol^2*s)"

kinetics = Arrhenius(
A=(float(lines[1]), Aunits),
n=float(lines[2]),
Ea=(float(lines[3]), "kcal/mol"),
T0=(1, "K"),
)

comments = "\t".join(lines[4:])
kinetics.comment = "Estimated by RMG-Java:\n" + comments
entry = Entry(long_desc=comments)

return reactants, products, kinetics, entry

def identifySpecies(species_dict, molecule):
"""
Given a species_dict list and the species adjacency list, identifies
whether species is found in the list and returns its name if found.
"""
resonance_isomers = molecule.generate_resonance_structures()
for name, adjlist in species_dict:
list_molecule = Molecule().from_adjacency_list(adjlist, saturate_h=True)
for isomer in resonance_isomers:
if isomer.is_isomorphic(list_molecule):
return name
return False

product_list = productList or []
reaction_list = []

# Generate species list for Java request
pop_reactants = ''
added_reactants = set()
for index, reactant in enumerate(reactantList):
assert isinstance(reactant, Molecule)
reactant.clear_labeled_atoms()
for r in added_reactants:
if r.is_isomorphic(reactant):
break # already added this reactant
else: # exhausted the added_reactants list without finding duplicate and breaking
added_reactants.add(reactant)
pop_reactants += 'reactant{0:d} (molecule/cm3) 1\n{1}\n\n'.format(index+1, reactant.to_adjacency_list(remove_lone_pairs=True))
pop_reactants += 'END\n'

# First send search request to PopulateReactions server
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.settimeout(10)
try:
client_socket.connect(("localhost", 5000))
except IOError:
print('Unable to query RMG-Java for kinetics. (Is the RMG-Java server running?)', file=sys.stderr)
sys.stderr.flush()
return reaction_list

# Send request to server
print("SENDING REQUEST FOR RMG-JAVA SEARCH TO SERVER")
client_socket.sendall(pop_reactants)
partial_response = client_socket.recv(512)
response = partial_response
while partial_response:
partial_response = client_socket.recv(512)
response += partial_response
client_socket.close()
print("FINISHED REQUEST. CLOSED CONNECTION TO SERVER")
# Clean response from server
try:
species_dict, reactions_list = cleanResponse(response)
except:
# Return an empty reaction list if an error occurred on the java server side,
# instead of having the website crash.
print("AN ERROR OCCURRED IN THE JAVA SERVER.")
print(response)
return []

# Name the species in reaction
reactant_names = []
for reactant in reactantList:
reactant_names.append(identifySpecies(species_dict, reactant))
product_names = []
for product in product_list:
product_names.append(identifySpecies(species_dict, product))
# identifySpecies(species_dict, product) returns "False" if it can't find product
if not identifySpecies(species_dict, product):
print("Could not find this requested product in the species dictionary from RMG-Java:")
print(product)

species_dict = dict([(key, Molecule().from_adjacency_list(value, saturate_h=True)) for key, value in species_dict])

# Both products were actually found in species dictionary or were blank
reaction = None
if all(product_names):

# Constants for all entries
degeneracy = 1

# Search for da Reactions
print('Searching output for desired reaction...\n')
for reaction_line in reactions_list:
if reaction_line.strip().startswith('DUP'):
print("WARNING - DUPLICATE REACTION KINETICS ARE NOT BEING SUMMED")
# if set, the `reaction` variable should still point to the reaction from the previous reactionline iteration
if reaction:
reaction.kinetics.comment += "\nWARNING - DUPLICATE REACTION KINETICS IDENTIFIED BUT NOT SUMMED"
continue # to next reaction line.

reaction = None
# Search for both forward and backward reactions
indicator1 = searchReaction(reaction_line, reactant_names, product_names)
indicator2 = searchReaction(reaction_line, product_names, reactant_names)
if indicator1 or indicator2:
print('Found a matching reaction:')
print(reaction_line)
reactants, products, kinetics, entry = extractKinetics(reaction_line)
reaction = DepositoryReaction(
reactants=[species_dict[reactant] for reactant in reactants],
products=[species_dict[product] for product in products],
kinetics=kinetics,
degeneracy=degeneracy,
entry=entry,
)

reaction_list.append(reaction)

# Return the reactions as containing Species objects, not Molecule objects
for reaction in reaction_list:
reaction.reactants = [Species(molecule=[reactant]) for reactant in reaction.reactants]
reaction.products = [Species(molecule=[product]) for product in reaction.products]

return reaction_list


def getAbrahamAB(smiles):

Expand Down
3 changes: 0 additions & 3 deletions rmgweb/database/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@
# Load the whole database into memory
re_path(r'^load/?$', views.load, name='load'),

# Export to an RMG-Java database
re_path(r'^export_(?P<type>zip|tar\.gz)/?$', views.export, name='export'),

# Thermodynamics database
re_path(r'^thermo/$', views.thermo, name='thermo'),
re_path(r'^thermo/search/$', views.moleculeSearch, name='thermo-search'),
Expand Down
54 changes: 0 additions & 54 deletions rmgweb/database/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,50 +121,6 @@ def index(request):
return render(request, 'database.html')


def export(request, type):
"""
Export the RMG database to the old RMG-Java format.
"""
# Build archive filenames from git hash and compression type
sha = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
cwd=rmgweb.settings.DATABASE_PATH)[:7]
base = 'RMG_database_{0}'.format(sha)
file_zip = '{0}.zip'.format(base)
file_tar = '{0}.tar.gz'.format(base)
if type == 'zip':
file = file_zip
elif type == 'tar.gz':
file = file_tar

# Set output path
path = os.path.join(rmgweb.settings.PROJECT_PATH, '..', 'database', 'export')
output = os.path.join(path, 'RMG_database')

# Assert archives do not already exist
if not os.path.exists(os.path.join(path, file)):

# Export old database
cmd_export = ['python', rmgweb.settings.DATABASE_PATH + '../scripts/exportOldDatabase.py', output]
subprocess.check_call(cmd_export)

# Compress database to zip
cmd_zip = ['zip', '-r', base, 'RMG_database']
subprocess.check_output(cmd_zip, cwd=path)

# Compress database to tar.gz
cmd_tar = ['tar', '-czf', file_tar, 'RMG_database']
subprocess.check_output(cmd_tar, cwd=path)

# Make compressed databases group-writable
os.chmod(os.path.join(path, file_zip), 0o664)
os.chmod(os.path.join(path, file_tar), 0o664)

# Remove exported database
shutil.rmtree(output)

# Redirect to requested compressed database
return HttpResponseRedirect('export/{0}'.format(file))

#################################################################################################################################################


Expand Down Expand Up @@ -3045,16 +3001,6 @@ def kineticsGroupEstimateEntry(request, family, estimator, reactant1, product1,
})


def kineticsJavaEntry(request, entry, reactants_fig, products_fig, kineticsParameters, kineticsModel):
section = ''
subsection = ''
database_name = 'RMG-Java Database'
reference = ''
reference_type = ''
arrow = '&hArr;'
return render(request, 'kineticsEntry.html', {'section': section, 'subsection': subsection, 'databaseName': database_name, 'entry': entry, 'reactants': reactants_fig, 'arrow': arrow, 'products': products_fig, 'reference': reference, 'referenceType': reference_type, 'kinetics': entry.data})


def kineticsSearch(request):
"""
A view of a form for specifying a set of reactants to search the database
Expand Down
23 changes: 23 additions & 0 deletions rmgweb/main/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
import urllib

import math
import os


from rmgpy.rmg.model import CoreEdgeReactionModel
from rmgpy.rmg.output import save_output_html
from rmgpy.chemkin import load_chemkin_file
from django.urls import reverse
from rmgpy.molecule.group import Group
from rmgpy.molecule.molecule import Molecule
Expand Down Expand Up @@ -182,3 +188,20 @@ def getStructureMarkup(item):
else:
structure = ''
return structure

################################################################################

def saveHtmlFile(path, read_comments=True):
"""
Save an output HTML file.
"""
chemkin_path = os.path.join(path, 'chemkin', 'chem.inp')
dictionary_path = os.path.join(path, 'RMG_Dictionary.txt')
model = CoreEdgeReactionModel()
model.core.species, model.core.reactions = load_chemkin_file(chemkin_path, dictionary_path,
read_comments=read_comments)
output_path = os.path.join(path, 'output.html')
species_path = os.path.join(path, 'species')
if not os.path.isdir(species_path):
os.makedirs(species_path)
save_output_html(output_path, model)
9 changes: 0 additions & 9 deletions rmgweb/rmg/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,6 @@ class Meta(object):
fields = '__all__'


class UploadDictionaryForm(forms.ModelForm):
"""
A Django form for uploading a RMG dictionary file.
"""
class Meta(object):
model = AdjlistConversion
fields = '__all__'


class FluxDiagramForm(forms.ModelForm):
"""
A Django form for creating a flux diagram by uploading the files required.
Expand Down
Loading

0 comments on commit 20b7c24

Please sign in to comment.