Source code for htpolynet.cure.reaction
"""Handles Reaction objects.
Author: Cameron F. Abrams <cfa22@drexel.edu>
"""
import logging
from copy import deepcopy
from enum import Enum
import networkx as nx
from htpolynet.analysis.plot import network_graph
logger=logging.getLogger(__name__)
[docs]
class reaction_stage(Enum):
"""Enumerated reaction stage
"""
build=0 # only used for building molecules that use parameterized oligomers
param=1 # used to generate parameterized oligomers that are not cure reactions
cure=2 # used to generate parameterized oligormers that ARE cure reactions
cap=3 # used to generate parameterized capped monomers
unset=99
def __str__(self):
return self.name
[docs]
class Reaction:
default_directives={
'name':'',
'atoms':{},
'bonds':[],
'reactants':{},
'product':'',
'stage':reaction_stage.unset,
'procession':{},
'probability':1.0
}
def __init__(self,jsondict={}):
"""Generates a Reaction object using directives in jsondict.
Args:
jsondict (dict): directives for creating a new Reaction object, defaults to {}
"""
self.jsondict=jsondict
self.name=jsondict.get('name',self.default_directives['name'])
self.atoms=jsondict.get('atoms',self.default_directives['atoms'])
self.bonds=jsondict.get('bonds',self.default_directives['bonds'])
self.reactants=jsondict.get('reactants',self.default_directives['reactants'])
self.product=jsondict.get('product',self.default_directives['product'])
self.stage=reaction_stage[jsondict.get('stage',str(self.default_directives['stage']))]
self.procession=jsondict.get('procession',self.default_directives['procession'])
self.probability=jsondict.get('probability',self.default_directives['probability'])
for label in jsondict:
if not label in self.default_directives:
logging.debug(f'Ignoring unknown reaction directive "{label}"')
self.symmetry_versions=[]
def __str__(self):
retstr=f'Reaction "{self.name}" ({str(self.stage)})\n'
for i,r in self.reactants.items():
retstr+=f'reactant {i}: {r}\n'
retstr+=f'product {self.product}\n'
for i,a in self.atoms.items():
retstr+=f'atom {i}: {a}\n'
for b in self.bonds:
retstr+=f'bond {b}\n'
return retstr
ReactionList = list[Reaction]
[docs]
def parse_reaction_list(baselist:ReactionList):
"""Generates any new reactions inferred by procession (i.e., linear polymerization).
Args:
baselist (ReactionList): list of unprocessed reaction dicts read directly from configuration file
Returns:
ReactionList: entire set of all reactions, including new ones added here
"""
processives=[]
for i,R in enumerate(baselist):
if R.procession:
nReactions=[]
logger.debug(f'Reaction {R.name} is processive: {R.procession}')
final_product_name=R.product
R.product=f'{final_product_name}_I0'
original_name=R.name
R.name=f'{R.name}-i0'
base_reactant_key=R.procession['increment_resid']
for c in range(R.procession['count']):
nR=deepcopy(R)
nR.name=f'{original_name}-i{c+1}'
nR.product=f'{final_product_name}_I{c+1}' if c<(R.procession['count']-1) else final_product_name
nR.reactants[base_reactant_key]=f'{final_product_name}_I{c}'
for ak,arec in nR.atoms.items():
if arec['reactant']==base_reactant_key: arec['resid']+=(c+1)
nR.procession={}
nReactions.append(nR)
processives.append((i,nReactions))
lasti=0
ret_reactions=baselist.copy()
for rec in processives:
atidx,sublist=rec
atidx+=lasti
ret_reactions=ret_reactions[0:atidx+1]+sublist+ret_reactions[atidx+1:]
lasti+=len(sublist)
return ret_reactions
[docs]
def get_r(mname,RL:ReactionList):
"""Returns the Reaction that generates molecule mname as a product, if it exists, otherwise None.
Args:
mname (str): name of molecule to search
RL (ReactionList): list of all Reactions
Returns:
Reaction or None: Reaction that generates mname, or None
"""
if not RL: return None
i=0
while RL[i].product!=mname:
i+=1
if i==len(RL):
return None
return RL[i]
[docs]
def is_reactant(name:str,reaction_list:ReactionList,stage=reaction_stage.cure):
"""Tests to see if molecule 'name' appears as a reactant in any reaction in reaction_list.
Args:
name (str): name of molecule
reaction_list (ReactionList): list of all Reactions
stage (reaction_stage): stage of reactions to search, defaults to reaction_stage.cure
Returns:
bool: True if molecule is a reactant, False otherwise
"""
reactants=[]
for r in reaction_list:
if r.stage==stage:
for v in r.reactants.values():
if not v in reactants:
reactants.append(v)
return name in reactants
[docs]
def product_sequence_resnames(R:Reaction,reactions:ReactionList):
"""Recursively generates sequence of product of R by traversing network of reactions; sequence is by definition an ordered list of monomer names.
Args:
R (Reaction): a Reaction
reactions (ReactionList): list of all Reactions
Returns:
list: the sequence of R's product molecule as a list of monomer names
"""
result=[]
allProducts=[x.product for x in reactions]
for rKey,rName in R.reactants.items():
if rName in allProducts:
result.extend(product_sequence_resnames(reactions[allProducts.index(rName)],reactions))
else:
result.extend([rName])
return result
[docs]
def molname_sequence_resnames(molname:str,reactions:ReactionList):
"""Determines monomer sequence of the molecule with name molname based on a traversal of the reaction network.
Args:
molname (str): name of molecule
reactions (ReactionList): list of all Reactions
Returns:
list: monomer sequence of molecule as a list of monomer names
"""
prodnames=[x.product for x in reactions]
if molname in prodnames:
R=reactions[prodnames.index(molname)]
return product_sequence_resnames(R,reactions)
else:
return [molname] # not a product in the reaction list? must be a monomer
[docs]
def reactant_resid_to_presid(R:Reaction,reactantName:str,resid:int,reactions:ReactionList):
"""Maps the resid of a reactant monomer to its inferred resid in the associated product in reaction R, by traversing the reaction network.
Args:
R (Reaction): a Reaction
reactantName (str): the name of one of the reactants in R
resid (int): the resid in that reactant we want mapped to the product
reactions (ReactionList): list of all Reactions
Returns:
int: resid of this residue in the product of R
"""
reactants=[x for x in R.reactants.values()]
if reactantName in reactants:
ridx=reactants.index(reactantName)
logger.debug(f'reactantName {reactantName} {ridx} in {reactants} of {R.name}')
S=0
for i in range(ridx):
seq=molname_sequence_resnames(reactants[i],reactions)
logger.debug(f'{i} {seq} {S}')
S+=len(seq)
return S+resid
else:
return -1
[docs]
def generate_product_name(R:Reaction):
"""Automatically generates the name of the product based on the names of the reactants and the bonds in R.
Args:
R (Reaction): a Reaction
Returns:
str: suggested name of product
"""
pname=''
for b in R.bonds:
i,j=b['atoms']
i_arec=R.atoms[i]
j_arec=R.atoms[j]
i_reKey=i_arec['reactant']
i_reNm=R.reactants[i_reKey]
i_aNm=i_arec['atom']
j_reKey=j_arec['reactant']
j_reNm=R.reactants[j_reKey]
j_aNm=j_arec['atom']
tbNm=f'{i_reNm}~{i_aNm}-{j_aNm}~{j_reNm}'
if len(R.reactants)>1:
if len(pname)==0:
pname=tbNm
else:
pname+='---'+tbNm
return pname