Moves in chemical space

ChemicalEnvironments were designed for the purpose of making moves/changes to the chemical perception of a SMIRNOFF forcefield. In this notebook we show how to use the environments to make these types of changes.

Authors

  • Christopher I. Bayly (OpenEye Scientific Software while on sabbatical with Mobley Group UC Irvine)
  • Updates and Commentary by Caitlin C. Bannan (Mobley Group UC Irvine)

In [1]:
#!/usr/bin/env python
# generic scientific/ipython header
from __future__ import print_function
from __future__ import division
import os, sys
import copy
import numpy as np

In [2]:
import openeye.oechem as oechem

In [3]:
from openforcefield.typing.chemistry import environment

Chemical groups we might use


In [4]:
chemGroups = [ ('ewg1', '[#7,#8!-1,#16!-1,F,Cl,Br,I]'),
               ('ewg1di', '[#7!X1,#8!X1,#16!X1]') ]

Section 1: Chemical Environments from SMIRKS

Here we list the smirks patterns that will be used to generate ChemicalEnvironments below.

Testing Chemical Environments API

Build test torsion, angle, bond, vdW params (ToDo: impropers)

  • These will also be used as starting points for 'moves': reasonable changes to the NetworkxMol

In [5]:
# specific torsions from SMIRKS
torsionEnv1 = environment.TorsionChemicalEnvironment( smirks = "[#6X4:1]~[#6X4:2]-[#8X2;H1;+0:3]~[*:4]" )
torsionEnv2 = environment.TorsionChemicalEnvironment( smirks = "[#8X2;H0;+0:1]~[#6X4:2]-[#8X2;H1;+0:3]~[*:4]" )
torsionEnv3 = environment.TorsionChemicalEnvironment( smirks = "[#8X2;H0;+0:1]~[#6X4:2]-[#8:3]~[*:4]" )
print( torsionEnv3.asSMIRKS() )


[#8X2;H0;+0:1]~[#6X4:2]-[#8:3]~[*:4]

In [6]:
# specific angles from sublists 
angEnv1 = environment.AngleChemicalEnvironment( smirks = "[#8X2;H0;+0:1]~[#6X4:2]-[#8X2;H1;+0:3]")
angEnv2 = environment.AngleChemicalEnvironment( smirks = "[#6X4:1]~[#6X4:2]~[#6X4:3]")
angEnv3 = environment.AngleChemicalEnvironment( smirks = "[#6X4:1]~[#6X4:2]~;!@[#6X4:3]")
print( angEnv3.asSMIRKS() )


[#6X4:1]~[#6X4:2]~;!@[#6X4:3]

In [7]:
# specific bonds from sublists 
bondEnv1 = environment.BondChemicalEnvironment( smirks = "[#8X2;H0;+0:1]~[#6X4:2]" )
bondEnv2 = environment.BondChemicalEnvironment( smirks = "[#8X2;H0;+0:1]~;!@[#6X4:2]" )
bondEnv3 = environment.BondChemicalEnvironment( smirks = "[H:1]-[#6X4:2]" )
bondEnv4 = environment.ChemicalEnvironment( smirks = '[*:2]~[#8:1]-[#6X4]~[#8X2;H0;+0]' )
print( bondEnv4.asSMIRKS() )


[*:2]~[#8:1]-[#6X4]~[#8X2;H0;+0]

In [8]:
# specific vdW from sublists 
atomEnv1 = environment.AtomChemicalEnvironment( smirks = "[#8X2;H0;+0:1]")
atomEnv2 = environment.AtomChemicalEnvironment( smirks = "[#6X4:1]")
atomEnv3 = environment.AtomChemicalEnvironment( smirks = "[H:1]")
atomH3 = environment.ChemicalEnvironment( 
    smirks = '[#1:1]-[#6](-[$ewg1])(-[$ewg1])-[$ewg1]', replacements = chemGroups)
print( atomH3.asSMIRKS() )


[#1:1]-[#6](-[$ewg1])(-[$ewg1])-[$ewg1]

In [9]:
# Testing API for retrieval of atoms and bonds
# This will be used to make moves in chemical space (change the environment)
torsion = torsionEnv3
print( torsion.asSMIRKS() )
print( torsion.atom1.getORtypes() )
print( torsion.atom1.getANDtypes() )
atom4 = torsion.selectAtom( 4)
print('Selected atom : ',atom4.index, atom4.getORtypes(), atom4.getANDtypes() )
atmlist = torsion.getAtoms()
for atom in atmlist:
    print(atom.index, atom.getORtypes(), atom.getANDtypes() )
bond2 = torsion.getBond(torsion.atom2,torsion.atom3)
print( bond2.getORtypes() )
print( bond2.getANDtypes() )


[#8X2;H0;+0:1]~[#6X4:2]-[#8:3]~[*:4]
[('#8', ['X2'])]
['H0', '+0']
Selected atom :  4 [] []
1 [('#8', ['X2'])] ['H0', '+0']
2 [('#6', ['X4'])] []
3 [('#8', [])] []
4 [] []
[('-', [])]
[]

In [10]:
param = bondEnv4
print( param.asSMIRKS() )
# bond1 is the bond between indexed atoms 1 and 2.
# You can get this bond directly with selectBond:
print("Finding bond 1 directly...")
bond1 = param.selectBond(1)
print(bond1.getORtypes())

print("\nFinding bond 1 from atom1 and atom2...")
# OR you can get this bond from the atoms on either side:
firstAtom = param.selectAtom(1)
secondAtom = param.selectAtom(2)
print( 'firstAtom', firstAtom.index, firstAtom.getORtypes(), firstAtom.getANDtypes())
print( 'secondAtom', secondAtom.index, secondAtom.getORtypes(), secondAtom.getANDtypes())

bond1 = param.getBond(firstAtom, secondAtom)
print(bond1.getORtypes())


[*:2]~[#8:1]-[#6X4]~[#8X2;H0;+0]
Finding bond 1 directly...
[]

Finding bond 1 from atom1 and atom2...
firstAtom 1 [('#8', [])] []
secondAtom 2 [] []
[]

Section 2: SMIRKS chemical components for chemical moves on NetworkxMols

  • create different classes of chemical components as separate lists
  • associate odds with each component so that some moves will be preferred over overs
  • compose dicts of componentsWithOdds for atoms and bonds, each with their own classes of components
  • massage those odds componentsWithOdds dicts into weights in componentsWithWeights dicts which will be directly used by numpy's random.choice() method to make weighted random choices.

In [11]:
# Generic move lists without odds (initial guess), reformat below
ChemComponents = {}
ChemComponents['AtmBase'] = ['','#1','#5','#6','#7','#8','#9','#15','#16','#17','#35','#53']
ChemComponents['AtmORdecs'] = ['','X4','X3','X2','X1']
ChemComponents['AtmAndDecs'] = ['H0','+0',]
ChemComponents['BondBase'] = ['-',':','=','#','~']
ChemComponents['BondANDDecs'] = ['@','!@','!#']

In [12]:
# To reformat ChemComponents dict as tuples representing odds for each item,
# uncomment and run this block.
#for key in ChemComponents.keys():
#    print('ChemComponentsWithOdds[\'%s\'] = [' % key)
#    for item in ChemComponents[key]:
#        print(' (\'%s\', 1),' % item )
#    print(' ]')

For Future:

Decorators need weights, and they need to be weighted differently for different atomic elements. Guessed examples based on experience with organic chemistry:


  • C [ ('X4', 20), ('X3', 20), ('X2', 5), ('X1', 1), ]
  • N [ ('X4', 10), ('X3', 10), ('X2', 10), ('X1', 1), ]
  • O [ ('X4', 0), ('X3', 1), ('X2', 20), ('X1', 20), ]
  • H,F,Cl,Br,I [ don't use this decorator on this atom ]

  • C [ ('H0', 1), ('H1', 1), ('H2', 1), ('H3', 1) ]
  • N [ ('H0', 1), ('H1', 1), ('H2', 1), ('H3', 1) ]
  • O [ ('H0', 20), ('H1', 20), ('H2', 1), ('H3', 0) ]
  • H,F,Cl,Br,I [ don't use this decorator on this atom ]

  • C [ ('-1', 1), ('+0', 50), ('+1', 0) ]
  • N [ ('-1', 1), ('+0', 20), ('+1', 5) ]
  • O [ ('-1', 10), ('+0', 50), ('+1', 1) ]
  • H,F,Cl,Br,I [ don't use this decorator on this atom ]

In [13]:
# General
atomComponentsWithOdds = {}
atomComponentsWithOdds['Basetypes'] = [
 ('#1', 10),
 ('#5', 10),
 ('#6', 10),
 ('#7', 10),
 ('#8', 10),
 ('#9', 1),
 ('#15', 2),
 ('#16', 4),
 ('#17', 1),
 ('#35', 1),
 ('#53', 1),
 ]
atomComponentsWithOdds['ORdecs'] = [ ('', 4), 
                                       ('X4', 4), ('X3', 4), ('X2', 4), ('X1', 4), 
                                       ('H3', 1), ('H2', 1), ('H1', 1), ('H0', 1)
                                      ]
# these AtmAndDecs are dummies just to get going; real chemistry puts H-count on each atom
atomComponentsWithOdds['ANDdecs'] = [ ('H0', 1), ('+0', 1), ]

bondComponentsWithOdds = {}
bondComponentsWithOdds['Basetypes'] = [ ('-', 1), (':', 1), ('=', 1), ('#', 1), ('~', 1), ]
bondComponentsWithOdds['ORdecs'] = []
bondComponentsWithOdds['ANDdecs'] = [ ('@', 1), ('!@', 1), ('!#', 1), ]

In [14]:
# for AlkEthOH
atomComponentsWithOdds = {}
atomComponentsWithOdds['Basetypes'] = [
 ('#1', 1),
 ('#5', 0),
 ('#6', 1),
 ('#7', 0),
 ('#8', 1),
 ('#9', 0),
 ('#15', 0),
 ('#16', 0),
 ('#17', 0),
 ('#35', 0),
 ('#53', 0),
 ]
atomComponentsWithOdds['ORdecs'] = [ ('', 4), 
                                       ('X4', 4), ('X3', 4), ('X2', 4), ('X1', 4), 
                                       ('H3', 1), ('H2', 1), ('H1', 1), ('H0', 1)
                                      ]
# these AtmAndDecs are dummies just to get going; real chemistry puts H-count on each atom
atomComponentsWithOdds['ANDdecs'] = [ ('H0', 1), ('+0', 1), ]

bondComponentsWithOdds = {}
bondComponentsWithOdds['Basetypes'] = [ ('-', 1), (':', 0), ('=', 0), ('#', 0), ('~', 1), ]
# TODO: determine how to handle ~ case, do we want to have it in the list or have a None option of some kind?
bondComponentsWithOdds['ORdecs'] = []
bondComponentsWithOdds['ANDdecs'] = [ ('@', 1), ('!@', 1), ('!#', 0), ]

The following function movesWithWeightsFromOdds is copied from iPython notebook create_move_types_and_weights.ipynb:


In [15]:
# function copied from EnvMovesTypesAndWeights
def movesWithWeightsFromOdds( MovesWithOdds):
    '''Processes a dictionary of movesWithOdds (lists of string/integer tuples)
    into a dictionary of movesWithWeights usable to perform weighted
    random choices with numpy's random.choice() function.
    Argument: a MovesWithOdds dictionary of lists of string/integer tuples
    Returns: a MovesWithWeights dictionary of pairs of a moveType-list with a 
            probabilites-list, the latter used by numpy's random.choice() function.'''
    movesWithWeights = {}
    for key in MovesWithOdds.keys():
        moves = [ item[0] for item in MovesWithOdds[key] ]
        odds =  [ item[1] for item in MovesWithOdds[key] ]
        weights = odds/np.sum(odds)
        #print( key, moves, odds, weights)
        movesWithWeights[key] = ( moves, weights)
    return movesWithWeights

Generate the Weights dict from the Odds dict for each of atoms and bonds components, then combine those into a master dict that has both


In [16]:
#print( ChemComponentsWithWeights)
atomComponentsWithWeights = movesWithWeightsFromOdds(atomComponentsWithOdds)
#print( 'atomComponentsWithWeights', atomComponentsWithWeights)
bondComponentsWithWeights = movesWithWeightsFromOdds(bondComponentsWithOdds)
#print( 'bondComponentsWithWeights', bondComponentsWithWeights)
masterComponentsWithWeights = { 'atom':atomComponentsWithWeights,
                                'bond':bondComponentsWithWeights}
#print( 'masterComponentsWithWeights', masterComponentsWithWeights)

Section 3: Read file of candidate chemical moves and filter out a priori bad ones

  • a candidate chemical move is a list composed of four micro-moves, each of a different type
  • when the candidate chemical move was constructed from weighted choices, an overall probability was associated with it
  • some candidate moves are not viable, such as adding an atom to a bond. These are weeded out after the list is read in

In [17]:
def EnvMoveIsWellFormed( moveDict, msg=''):
    '''Checks moveDict (dict of proposed chem env micro-moves) to see if
    it is well-formed (i.e. before even looking at the chemical graph),
    returning True unless there is an obvious problem.
    Arguments: 
        moveDict: a dict of strings constituting proposed micro-moves of a chem env move.
        msg: an informative message about the nature of the problem.
    Returns True unless it finds a problem'''
    if moveDict['action']=='joinAtom':
        if moveDict['atomOrBond']=='bond':
            msg = 'cannot join another atom to an existing bond'
            return False
        elif moveDict['ANDorOR']=='ANDtype':
            msg = 'can only join another atom as an ORtype'
            return False
    return True

In [18]:
def getMoveDictDB(moveDictFilename):
    """
    Processes moveTrees.uniq.*.txt files
    """
    moveDictFile = open(moveDictFilename)
    moveDictdb = []
    for line in moveDictFile:
        fields = line.split()
        prob = fields[0]
        moveDict = { 'action' : fields[1],
                   'atomOrBond' : fields[2],
                   'whichAtmBnd' : fields[3],
                   'ANDorOR' : fields[4],
                  }
        if EnvMoveIsWellFormed(moveDict):
            moveDictdb.append( (moveDict, prob) )
    moveDictFile.close()    
    return moveDictdb

In [19]:
moveDictdb_byParam = dict()
for param_name in ['VdW', 'Bond', 'Angle', 'Torsion', 'Improper']:
    moveDictdb_byParam[param_name] = getMoveDictDB("moveTrees.uniq.%s.txt" % param_name)
    print(param_name, len(moveDictdb_byParam[param_name]))


VdW 20
Bond 33
Angle 46
Torsion 59
Improper 59

Section 4: API for making chemical moves

  • Chemical moves are performed on a ChemicalEnvironment (Env) representing a force field parameter for a Vdw, Bond, Angle, Improper, or Torsion parameter
  • A chemical move is a composite of four micro-moves given in a dict moveDict

The following function PickMoveItemWithProb is copied from iPython notebook EnvMovesTypesAndWeights:


In [20]:
# function copied from EnvMovesTypesAndWeights
def PickMoveItemWithProb( moveType, moveWithWeights):
    '''
    Picks a moveItem based on a moveType and a dictionary of moveTypes with associated probabilities
    
    Parameters
    ----------
        moveType: string corresponding to a key in the moveWithWeights dictionary, e.g. atomTor
        moveWithWeights: a dictionary based on moveType keys which each point to a list of probabilites
           associated with the position in the list

    Returns
    -------
        the randomly-chosen position in the list, based on the probability, together with the probability'''
    listOfIndexes = range(0, len( moveWithWeights[moveType][1]) )
    listIndex = np.random.choice(listOfIndexes, p= moveWithWeights[moveType][1])
    return moveWithWeights[moveType][0][listIndex], moveWithWeights[moveType][1][listIndex]

In [40]:
def GenerateORtype( ComponentsWithWeights):
    '''Makes a weighted random choice of a new ORtype  for an Atom or Bond from lists of candidate components.
    These ORtypes are composites of a basetype (atomic number for atom, bond order for bond) and
    an ORdec (decorator) which can associate a property (such as 'X3' meaning 3 connections on
    the atom). It also calculates and associates the cumulative probability for that composite
    ORtype based on the probabilities of each component used in making the choice.
    
    Parameters
    ----------
        ComponentsWithWeights: a MovesWithWeights dictionary of pairs of a moveType-list
            with a probabilites-list
    
    Returns
    -------
        newORtype, prob: a tuple pair with first element being a new ORtype and the second
            being the cumulative probability of that ORtype.
        None: if the attempt fails
    '''
    basetypeList = ComponentsWithWeights['Basetypes'][0]
    #print( 'Basetypes:', ComponentsWithWeights['Basetypes'][0])
    #print( 'Basetypes weights:', ComponentsWithWeights['Basetypes'][1])
    if len(basetypeList)<1:
        return None
    newBasetype, prob = PickMoveItemWithProb( 'Basetypes', ComponentsWithWeights)
    cumProb = prob
    
    ORtypeList = ComponentsWithWeights['ORdecs'][0]
    #print( 'ORdecs:', ORtypeList)
    #print( 'ORdecs weights:', ComponentsWithWeights['ORdecs'][1])
    if len(ORtypeList)<1:
        return (newBasetype, []), prob
    newORdecorator, prob = PickMoveItemWithProb( 'ORdecs', ComponentsWithWeights)
    newORtype = (newBasetype, [newORdecorator])
    cumProb *= prob
    return newORtype, prob

In [41]:
def GenerateANDtype( ComponentsWithWeights):
    '''Makes a weighted random choice of a new ANDtype for an Atom or Bond from  a list of candidate
    components.It also calculates and associates the cumulative probability for that
    composite ANDtype based on the probabilities of each component used in making the choice.
    
    Parameters
    ----------
        ComponentsWithWeights: a MovesWithWeights dictionary of pairs of a moveType-list
            with a probabilites-list
            
    Returns
    -------
        newANDtype, prob: a tuple pair with first element being a new ANDtype and the second
            being the cumulative probability of that ANDtype.
        None: if the attempt fails
    '''
    ANDtypeList = ComponentsWithWeights['ANDdecs'][0]
    #print( 'ANDdecs:', ANDtypeList)
    #print( 'ANDdecs weights:', ComponentsWithWeights['ANDdecs'][1])
    if len(ANDtypeList)<1:
        return None
    return PickMoveItemWithProb( 'ANDdecs', ComponentsWithWeights)

In [42]:
# test GenerateNwxfragORtype and GenerateNwxfragANDtype
nSamples = 3
for i in range(0,nSamples):
    print( 'atom ORtypes:', GenerateORtype( masterComponentsWithWeights['atom']) )
for i in range(0,nSamples):
    print( 'atom ANDtypes:', GenerateANDtype( masterComponentsWithWeights['atom']) )
for i in range(0,nSamples):
    print( 'bond ORtypes:', GenerateORtype( masterComponentsWithWeights['bond']) )
for i in range(0,nSamples):
    print( 'bond ANDtypes:', GenerateANDtype( masterComponentsWithWeights['bond']) )


atom ORtypes: (('#1', ['H2']), 0.041666666666666664)
atom ORtypes: (('#1', ['X2']), 0.16666666666666666)
atom ORtypes: (('#1', ['X1']), 0.16666666666666666)
atom ANDtypes: ('H0', 0.5)
atom ANDtypes: ('H0', 0.5)
atom ANDtypes: ('H0', 0.5)
bond ORtypes: (('~', []), 0.5)
bond ORtypes: (('~', []), 0.5)
bond ORtypes: (('-', []), 0.5)
bond ANDtypes: ('!@', 0.5)
bond ANDtypes: ('@', 0.5)
bond ANDtypes: ('!@', 0.5)

In [43]:
def GetEnvBondChangeling( moveDict, Env):
    '''
    Returns a Bond object from the provided ChemicalEnvironment containing ORtypes and
    ANDtypes. Whether a specific bond object or a randomly chosed one gets returned is based
    on information in the moveDict (specifically moveDict['whichAtmBnd']).
    If no bond object matching the moveDict requirements is found, it returns None.
    
    Parameters
    -----------
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves
        Env: An openforcefield chemicalEnvironment
        
    Returns
    -------
    an OpenForcefield ChemicalEnvironment bond object or None
    
    Note - when this method was originally written selecting bonds was more difficult!
    '''
    if moveDict['atomOrBond']!='bond':
        print( 'GetEnvBondChangeling: mistaken call to this function')
        return None
    # MoveDicts have two options at whichAtmBnd 'bond' or 'atom' followed by a number 
    # or the word unindexed
    try:
        index = int(moveDict['whichAtmBnd'][-1])
        return Env.selectBond(index) # returns None if there is no bond at that index
    
    except: # moveDict['whichAtmBnd'] should be the word unindexed
        return Env.selectBond(moveDict['whichAtmBnd'])

In [44]:
# get MoveDictdb for this parameter:
moveDictdb = moveDictdb_byParam['Bond']
# test GetEnvBondChangeling
startParam = environment.ChemicalEnvironment( '[*:2]!@[#8:1]-[#6X3]=[#8X1;H0;+0]' )
param = copy.deepcopy( startParam)
moveDict = moveDictdb[9][0]
print( param.asSMIRKS())
print( moveDict)

changeling = GetEnvBondChangeling( moveDict, param)
if changeling is not None:
    print( 'changeling.getORtypes():', changeling.getORtypes() )
else:
    print( "No bond found with those requirements")


[*:2]!@[#8:1]-[#6X3]=[#8X1;H0;+0]
{'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
changeling.getORtypes(): [('-', [])]

In [45]:
def GetEnvAtomChangeling( moveDict, Env):
    '''Returns an Atom object from the provided ChemicalEnvironment.
    Whether a specific atom object or a randomly chosed one gets returned is based
    on information in the moveDict (specifically moveDict['whichAtmBnd']).
    If no atom object is found, it returns None.
    
    Parameters
    -----------
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves
        Env: Openforcefield ChemicalEnvironment
        
    Returns
    -------
        Atom: an atom from the Env matching moveDict requirements 
        (or None if no atom with specifications was found)
    '''
    if moveDict['atomOrBond']!='atom':
        print( 'GetEnvAtomChangeling: mistaken call to this function')
        return None
    try:
        index = int(moveDict['whichAtmBnd'][-1])
        return Env.selectAtom( index )
    except:
        return Env.selectAtom( moveDict['whichAtmBnd'] )

In [46]:
def GetEnvChangeling( moveDict, Env):
    '''
    Parent function to broker the getting of an Atom or Bond from the specified ChemicalEnvironment
    Arguments
    ----------
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves
        Env: Openforcefield ChemicalEnvironment
    
    Returns
    --------
        changeline: an atom or bond meeting requirements in the moveDict
    '''
    atomOrBond = moveDict['atomOrBond']
    if atomOrBond=='atom':
        return GetEnvAtomChangeling( moveDict, Env)
    elif atomOrBond=='bond':
        return GetEnvBondChangeling( moveDict, Env)
    else:
        return None

In [47]:
# test GetEnvChangeling
startParam = atomH3
param = copy.deepcopy( startParam)
moveDict = moveDictdb[5][0]
print( 'Attempting to apply this proposed move:\n', moveDict )
print( 'To a ChemicalEnvironment that generates this SMIRKS string:\n', param.asSMIRKS() )
#for key in moveDict.keys():
#    print( key, ':', moveDict[key] )
changeling = GetEnvChangeling( moveDict, param)
if changeling!=None:
    print( 'changeling properties:', changeling.getORtypes(), changeling.getANDtypes() )
else:
    print( 'GetEnvChangeling returned none. Was the proposed move viable?')


Attempting to apply this proposed move:
 {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
To a ChemicalEnvironment that generates this SMIRKS string:
 [#1:1]-[#6](-[$ewg1])(-[$ewg1])-[$ewg1]
changeling properties: [('$ewg1', [])] []

In [48]:
def ProposeNewType( moveDict, ComponentsWithWeights):
    '''Proposes a new ORtype or ANDtype for a bond or atom object  
    based on a weighted random choice from  a list of
    candidate components. It also calculates and associates the cumulative probability
    for that choice based on the probabilities of the component used in making the choice.
    If the attempt to propose does not work, it returns None.
    Arguments:
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves
        ComponentsWithWeights: a MovesWithWeights dictionary of pairs of an ORtype or
            ANDtype components with a probabilites-list.
    Returns: 
        newType, prob: a tuple pair with first element being the proposed new type and
            the second being the cumulative probability of that ORtype.
        None: if the attempt fails
    '''
    #print( 'ProposeNewType moveDict:', moveDict )
    atomOrBond = moveDict['atomOrBond']
    if moveDict['ANDorOR']=='ORtype':
        newPair = GenerateORtype( ComponentsWithWeights[atomOrBond])
    elif moveDict['ANDorOR']=='ANDtype':
        newPair = GenerateANDtype( ComponentsWithWeights[atomOrBond])
    return newPair

In [49]:
# test ProposeNewType
for moveDict in moveDictdb:
    print( moveDict[0]['atomOrBond'], moveDict[0]['ANDorOR'], ':',
         ProposeNewType( moveDict[0], masterComponentsWithWeights))


atom ANDtype : ('H0', 0.5)
atom ORtype : (('#8', ['X1']), 0.16666666666666666)
atom ANDtype : ('+0', 0.5)
atom ORtype : (('#8', ['X2']), 0.16666666666666666)
atom ANDtype : ('H0', 0.5)
atom ORtype : (('#1', ['']), 0.16666666666666666)
bond ANDtype : ('@', 0.5)
bond ORtype : (('~', []), 0.5)
bond ANDtype : ('!@', 0.5)
bond ORtype : (('-', []), 0.5)
atom ANDtype : ('H0', 0.5)
atom ORtype : (('#1', ['H3']), 0.041666666666666664)
atom ANDtype : ('+0', 0.5)
atom ORtype : (('#8', ['H3']), 0.041666666666666664)
atom ANDtype : ('+0', 0.5)
atom ORtype : (('#6', ['X1']), 0.16666666666666666)
bond ANDtype : ('@', 0.5)
bond ORtype : (('~', []), 0.5)
bond ANDtype : ('!@', 0.5)
bond ORtype : (('-', []), 0.5)
atom ORtype : (('#8', ['X4']), 0.16666666666666666)
atom ORtype : (('#6', ['']), 0.16666666666666666)
atom ORtype : (('#8', ['X3']), 0.16666666666666666)
atom ANDtype : ('+0', 0.5)
atom ORtype : (('#1', ['X2']), 0.16666666666666666)
atom ANDtype : ('H0', 0.5)
atom ORtype : (('#1', ['X4']), 0.16666666666666666)
atom ANDtype : ('+0', 0.5)
atom ORtype : (('#8', ['']), 0.16666666666666666)
bond ANDtype : ('!@', 0.5)
bond ORtype : (('~', []), 0.5)
bond ANDtype : ('!@', 0.5)
bond ORtype : (('-', []), 0.5)

In [50]:
def IsActionViable( moveDict, existing, proposal, msg):
    '''Checks the viability a proposed chemical move against the existing content of
    the bond or atom in the ChemicalEnvironment the move is to
    operate upon. Depending on the nature of the move and the changeling (atom or bond) object, the
    proposed move may be a priori impossible (e.g. deleting an ANDtype when the
    ANDtype list is already empty). If such a circumstance is found, this function
    returns False with a message. Otherwise it returns true with an empty string
    for the message.
    
    Parameters
    ----------
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves.
        existing: The Atom or Bond's existing ORtype or ANDtype list of strings targeted by the move in moveDict.
        proposal: a string; the proposed change to the existing ORtype or ANDtype list.
        msg: a string; a message stating why the move is deemed not viable.'''
    action = moveDict['action']
    #print('IsActionViable:  action:',action,'; existing types:',existing,'; proposed newType:',proposal)
    if action=='add':
        if proposal=='None':
            print('proposed newType is null; willnot add')
            return False
        if proposal in existing:
            print('proposed newType is in existing list; will not add')
            return False
    elif action=='delete':
        # ToDo: check to make sure not completely deleting whole atom which connects at least two others
        # Environments already do this - unnecessary
        if len(existing)<1:
            #msg = 'existing list is already empty; will not delete'
            #print('IsActionViable: msg is', msg)
            print('existing list is already empty; will not delete')
            return False
        if len(existing)==1 and moveDict['whichAtmBnd'][4]!='d':
            # 
            print('will not delete indexed atom or bond with only one type')
            return False
        if len(existing)==1 and moveDict['atomOrBond']=='bond':
            print('will not delete last bond joining two atoms')
            return False
    elif action=='swap':
        if len(existing)<1 or proposal=='None':
            print('missing one of existing type or newType; will not swap')
            return False
        if len(existing)==1 and existing[0]==proposal:
            print('Swapping identical types is pointless; will not swap')
            return False
    elif action=='joinAtom':
        # ToDo: insert valence check (don't join to atom with full valence)
            # I think this should also be inside the environments, but it would probably treat everything 
            # with the same max valence, such as 4? 6? might need special cses? 
        # ToDo: insert subst position check (don't join to beta substituent)
            # This is also in my environment TODO list, I think it makes more sense there
        if len(existing)<1 or proposal=='None':
            print('missing one of existing atom or new atom; will not join atom')
            return False
    return True

In [51]:
def SetEnvNewList( moveDict, changeling, newList):
    '''Makes the move on the selected ChemicalEnvironment atom or bond object 
    by setting the selected ORtype or ANDtype list.
    
    Parameters
    -----------
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves.
        changeling: the Atom or Bond object targeted by the move.
        newList: the new list of strings to replace the existing one in the changeling.
        
    Returns 
    -------
    True if the move is successful, False otherwise.
        '''
    if moveDict['ANDorOR']=='ORtype':
        changeling.setORtypes( newList )
        return True
    elif moveDict['ANDorOR']=='ANDtype':
        changeling.setANDtypes( newList)
        return True
    else:
        return False

In [52]:
def EffectMoveOnEnvList( moveDict, Env, changeling, existing, proposal):
    '''Effects the action requested in the chemical move on the selected Atom or Bond
    in the provided ChemicalEnvironment.
    Arguments:
        moveDict: a dict of strings constituting a sequence of proposed chem env micro-moves.
        Env: an OpenForcefield ChemicalEnvironment (representing a force field parameter)
        changeling: the Atom or Bond object in the Env targeted by the move.
        existing: the OpenForcefield Networkx molecular changeling object's 
            existing ORtype or ANDtype list of strings targeted by the move in moveDict.
        proposal: a string; the proposed change to the existing ORtype or ANDtype list.
    Returns: True if the move is successful, False otherwise.
        '''
    action = moveDict['action']
    newList = copy.deepcopy(existing)
    #
    # begin section for action delete and swap (swap is delete followed by add)
    # begin with special case for actually removing an atom completely, ie delete the last atom ORtype
    if action=='delete' and len(newList)==1 and moveDict['atomOrBond']=='atom'and moveDict['ANDorOR']=='ORtype':
        # for time being just remove the whole thing; worry about detailed balance later
        onlyEmpty = False
        return Env.removeAtom( changeling, onlyEmpty)
    
    # now the more general case for removing the last type from a list: make an empty list
    if action=='delete' or action=='swap':
        if len(newList)==1:
            newList = []
        # now to remove a random item from a list of more than one
        else:
            listOfIndexes = range(0, len( newList) )
            idxToDelete = np.random.choice(listOfIndexes)
            del newList[idxToDelete]

    #
    # begin section for action add and swap (swap is delete followed by add)
    if action=='add' or action=='swap':
        newList.append( proposal)
        if not SetEnvNewList( moveDict, changeling, newList):
            print('EffectMoveOnEnvList newList failed:', newList)
            return False
    #
    # begin section for joinAtom
    if action=='joinAtom':
        print('EffectMoveOnEnvList: attaching [', proposal,'] to existing atom:', existing)
        Env.addAtom( changeling, None, None, [proposal], None, None)
    return True

In [53]:
def MoveEnv( Env, moveDict, weightdParts, msg=''):
    '''Modifies a ChemicalEnvironment by applying a move described by
    a moveDict (dict of proposed chem env micro-moves), based on weighted random choices of
    chemical components in weightdParts. If the proposed move is not viable, it returns False.
    Arguments:
        Env: an OpenForcefield ChemicalEnvironment (representing a force field parameter)
        moveDict: a list of strings constituting a sequence of proposed chem env micro-moves
        weightdParts: dictionary of chemical components with probabilities of being chosen
        msg: an informative message about the nature of the problem
    Returns True unless it finds a problem'''
    print( Env.asSMIRKS(), moveDict )
    
    # stage 1: get the atom or bond we are going to work on
    atomOrBond = moveDict['atomOrBond']
    changeling = GetEnvChangeling( moveDict, Env)
    if changeling==None:
        msg = 'MoveEnv: changeling is None, returning false'
        return False
    print( 'MoveEnv changeling:', changeling.getORtypes(), changeling.getANDtypes() )
    
    # stage 2: get the current list we need to modify
    ANDorOR = moveDict['ANDorOR']
    if ANDorOR=='ORtype':
        currlist = changeling.getORtypes()
        print('MoveEnv got ORtypes:', currlist)
    elif ANDorOR=='ANDtype':
        currlist = changeling.getANDtypes()
        print('MoveEnv got ANDtypes:', currlist)
    else:
        msg = 'MoveEnv: could not retrieve requested ORtypes or ANDtypes'
        return False
    
    # stage 3: get the component list and associated weights
    proposed = ProposeNewType( moveDict, masterComponentsWithWeights)
    print("Proposed list: ", proposed)
    if proposed==None:
        msg = 'MoveEnv: proposed (newType, probability) is None, returning false'
        return False
    
    #stage 4: Test viability of desired action
    mesg = ''
    IsViable = IsActionViable( moveDict, currlist, proposed[0], mesg)
    if not IsViable:
        msg = 'MoveEnv: proposal is not viable, returning false. Details:\n'+mesg
        return False

    
    #stage 5: perform action
    if not EffectMoveOnEnvList( moveDict, Env, changeling, currlist, proposed[0]):
        msg = 'MoveEnv: problem effecting the move'
        return False
    
    return True

In [54]:
# test MoveEnv
startParam = angEnv2
param = copy.deepcopy( startParam)
moveDict = moveDictdb[6][0]
#print( param.asSMIRKS(), moveDict )
msg = ''
if not MoveEnv( param, moveDict, masterComponentsWithWeights, msg):
    print('move failed, message is:', msg)
else:
    print('move worked, new SMIRKS is:', param.asSMIRKS())


[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('@', 0.5)
move worked, new SMIRKS is: [#6X4:1]~;@[#6X4:2]~[#6X4:3]

In [57]:
# test MoveEnv
startParam = angEnv2
moveDictdb = moveDictdb_byParam['Angle']
param = copy.deepcopy( startParam)
print( 'Starting with', param.asSMIRKS())
#print( len(moveDictdb) )
for i, moveDict in enumerate( moveDictdb):
    param = copy.deepcopy( startParam)
    moves =  moveDict[0]
    print( '\nworking on %2d %8s %4s %10s %7s :' % 
          (i, moves['action'], moves['atomOrBond'], moves['whichAtmBnd'], moves['ANDorOR']))
    if not MoveEnv( param, moves, masterComponentsWithWeights, msg):
        print('  Failed')
    else:
        print('  Succeeded : %s' % ( param.asSMIRKS()))


Starting with [#6X4:1]~[#6X4:2]~[#6X4:3]

working on  0      add atom      atom1 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('+0', 0.5)
  Succeeded : [#6X4;+0:1]~[#6X4:2]~[#6X4:3]

working on  1      add atom      atom1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#8', ['X3']), 0.16666666666666666)
  Succeeded : [#6X4,#8X3:1]~[#6X4:2]~[#6X4:3]

working on  2      add atom      atom2 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('H0', 0.5)
  Succeeded : [#6X4:1]~[#6X4;H0:2]~[#6X4:3]

working on  3      add atom      atom2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#8', ['']), 0.16666666666666666)
  Succeeded : [#6X4:1]~[#6X4,#8:2]~[#6X4:3]

working on  4      add atom      atom3 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('+0', 0.5)
  Succeeded : [#6X4:1]~[#6X4:2]~[#6X4;+0:3]

working on  5      add atom      atom3  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#1', ['X1']), 0.16666666666666666)
  Succeeded : [#6X4:1]~[#6X4:2]~[#6X4,#1X1:3]

working on  6      add atom  unIndexed ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ANDtype'}
  Failed

working on  7      add atom  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

working on  8      add bond      bond1 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('!@', 0.5)
  Succeeded : [#6X4:1]~;!@[#6X4:2]~[#6X4:3]

working on  9      add bond      bond1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [] []
MoveEnv got ORtypes: []
Proposed list:  (('~', []), 0.5)
  Succeeded : [#6X4:1]~[#6X4:2]~[#6X4:3]

working on 10      add bond      bond2 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond2', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('@', 0.5)
  Succeeded : [#6X4:1]~[#6X4:2]~;@[#6X4:3]

working on 11      add bond      bond2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [] []
MoveEnv got ORtypes: []
Proposed list:  (('-', []), 0.5)
  Succeeded : [#6X4:1]~[#6X4:2]-[#6X4:3]

working on 12      add bond  unIndexed ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ANDtype'}
  Failed

working on 13      add bond  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'add', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

working on 14   delete atom      atom1 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('+0', 0.5)
existing list is already empty; will not delete
  Failed

working on 15   delete atom      atom1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#1', ['H2']), 0.041666666666666664)
will not delete indexed atom or bond with only one type
  Failed

working on 16   delete atom      atom2 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('H0', 0.5)
existing list is already empty; will not delete
  Failed

working on 17   delete atom      atom2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#6', ['']), 0.16666666666666666)
will not delete indexed atom or bond with only one type
  Failed

working on 18   delete atom      atom3 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('+0', 0.5)
existing list is already empty; will not delete
  Failed

working on 19   delete atom      atom3  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#6', ['X2']), 0.16666666666666666)
will not delete indexed atom or bond with only one type
  Failed

working on 20   delete atom  unIndexed ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ANDtype'}
  Failed

working on 21   delete atom  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

working on 22   delete bond      bond1 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('@', 0.5)
existing list is already empty; will not delete
  Failed

working on 23   delete bond      bond1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [] []
MoveEnv got ORtypes: []
Proposed list:  (('~', []), 0.5)
existing list is already empty; will not delete
  Failed

working on 24   delete bond      bond2 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond2', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('!@', 0.5)
existing list is already empty; will not delete
  Failed

working on 25   delete bond      bond2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [] []
MoveEnv got ORtypes: []
Proposed list:  (('-', []), 0.5)
existing list is already empty; will not delete
  Failed

working on 26   delete bond  unIndexed ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ANDtype'}
  Failed

working on 27   delete bond  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'delete', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

working on 28 joinAtom atom      atom1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'joinAtom', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#6', ['H2']), 0.041666666666666664)
EffectMoveOnEnvList: attaching [ ('#6', ['H2']) ] to existing atom: [('#6', ['X4'])]
  Succeeded : [#6X4:1](~[#6H2])~[#6X4:2]~[#6X4:3]

working on 29 joinAtom atom      atom2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'joinAtom', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#6', ['X2']), 0.16666666666666666)
EffectMoveOnEnvList: attaching [ ('#6', ['X2']) ] to existing atom: [('#6', ['X4'])]
  Succeeded : [#6X4:1]~[#6X4:2](~[#6X2])~[#6X4:3]

working on 30 joinAtom atom      atom3  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'joinAtom', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#8', ['H0']), 0.041666666666666664)
EffectMoveOnEnvList: attaching [ ('#8', ['H0']) ] to existing atom: [('#6', ['X4'])]
  Succeeded : [#6X4:1]~[#6X4:2]~[#6X4:3]~[#8H0]

working on 31 joinAtom atom  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'joinAtom', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

working on 32     swap atom      atom1 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('H0', 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 33     swap atom      atom1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#1', ['X3']), 0.16666666666666666)
  Succeeded : [#1X3:1]~[#6X4:2]~[#6X4:3]

working on 34     swap atom      atom2 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('H0', 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 35     swap atom      atom2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#8', ['H1']), 0.041666666666666664)
  Succeeded : [#6X4:1]~[#8H1:2]~[#6X4:3]

working on 36     swap atom      atom3 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ANDtypes: []
Proposed list:  ('+0', 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 37     swap atom      atom3  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'atom3', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [('#6', ['X4'])] []
MoveEnv got ORtypes: [('#6', ['X4'])]
Proposed list:  (('#1', ['X1']), 0.16666666666666666)
  Succeeded : [#6X4:1]~[#6X4:2]~[#1X1:3]

working on 38     swap atom  unIndexed ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ANDtype'}
  Failed

working on 39     swap atom  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'atom', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

working on 40     swap bond      bond1 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('@', 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 41     swap bond      bond1  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond1', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [] []
MoveEnv got ORtypes: []
Proposed list:  (('-', []), 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 42     swap bond      bond2 ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond2', 'ANDorOR': 'ANDtype'}
MoveEnv changeling: [] []
MoveEnv got ANDtypes: []
Proposed list:  ('!@', 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 43     swap bond      bond2  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'bond', 'whichAtmBnd': 'bond2', 'ANDorOR': 'ORtype'}
MoveEnv changeling: [] []
MoveEnv got ORtypes: []
Proposed list:  (('~', []), 0.5)
missing one of existing type or newType; will not swap
  Failed

working on 44     swap bond  unIndexed ANDtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ANDtype'}
  Failed

working on 45     swap bond  unIndexed  ORtype :
[#6X4:1]~[#6X4:2]~[#6X4:3] {'action': 'swap', 'atomOrBond': 'bond', 'whichAtmBnd': 'unIndexed', 'ANDorOR': 'ORtype'}
  Failed

In [ ]: