From 1a6e08bcc4bebd8f3752e905d73c1c72bdbd812a Mon Sep 17 00:00:00 2001
From: Chris Maffeo <cmaffeo2@illinois.edu>
Date: Tue, 28 Mar 2023 14:47:54 -0500
Subject: [PATCH] Committing things...

---
 arbdmodel/__init__.py                      | 73 ++++++++++++++++------
 arbdmodel/abstract_polymer.py              |  2 +-
 arbdmodel/coords.py                        |  4 +-
 arbdmodel/hps_polymer_model.py             |  4 +-
 arbdmodel/kh_polymer_model.py              | 68 +++++++++++++++-----
 arbdmodel/kh_polymer_model_pair_epsilon.py |  8 +++
 arbdmodel/onck_polymer_model.py            |  4 +-
 7 files changed, 121 insertions(+), 42 deletions(-)

diff --git a/arbdmodel/__init__.py b/arbdmodel/__init__.py
index db37aac..2ddc874 100644
--- a/arbdmodel/__init__.py
+++ b/arbdmodel/__init__.py
@@ -388,10 +388,10 @@ class ParticleType():
     excludedAttributes = ("idx","type_",
                           "position",
                           "children",
-                          "parent", "excludedAttributes","rigid"
+                          "parent", "excludedAttributes",
     )
 
-    def __init__(self, name, charge=0, parent=None, **kargs):
+    def __init__(self, name, charge=0, parent=None, rigid=False, rigid_body_potential=None, **kargs):
         """ Parent type is used to fall back on for nonbonded interactions if this type is not specifically referenced """
 
         if parent is not None:
@@ -402,7 +402,9 @@ class ParticleType():
         self.name   = name
         self.charge = charge
         self.parent = parent
-
+        self.rigid = rigid            
+        self.rigid_body_potential = rigid_body_potential
+        
         for key in ParticleType.excludedAttributes:
             assert( key not in kargs )
 
@@ -810,7 +812,7 @@ class ArbdModel(PdbModel):
     def __init__(self, children, origin=None, dimensions=(1000,1000,1000), temperature=291, timestep=50e-6,
                  particle_integrator = 'Brown',
                  cutoff=50, decomp_period=1000, pairlist_distance=None, nonbonded_resolution=0.1,
-                 remove_duplicate_bonded_terms=True, extra_bd_file_lines=""):
+                 remove_duplicate_bonded_terms=True, dummy_types=tuple(), extra_bd_file_lines=""):
 
         PdbModel.__init__(self, children, dimensions, remove_duplicate_bonded_terms)
         self.origin = origin
@@ -832,7 +834,8 @@ class ArbdModel(PdbModel):
         self.numParticles = 0
         self.particles = []
         self.type_counts = None
-
+        self.dummy_types = dummy_types
+        
         self.nbSchemes = []
         self._nbParamFiles = [] # This could be made more robust
         self.nbResolution = 0.1
@@ -869,7 +872,8 @@ class ArbdModel(PdbModel):
                     return s
             elif typeA.is_same_type(A) and typeB.is_same_type(B):
                 return s
-        raise Exception("No nonbonded scheme found for %s and %s" % (typeA.name, typeB.name))
+        # raise Exception("No nonbonded scheme found for %s and %s" % (typeA.name, typeB.name))
+        # print("WARNING: No nonbonded scheme found for %s and %s" % (typeA.name, typeB.name))
 
     def _countParticleTypes(self):
         ## TODO: check for modifications to particle that require
@@ -881,8 +885,11 @@ class ArbdModel(PdbModel):
                 type_counts[t] += 1
             else:
                 type_counts[t] = 1
+        for t in self.dummy_types:
+            if t not in type_counts:
+                type_counts[t] = 0
         self.type_counts = type_counts
-
+        
     def _updateParticleOrder(self):
         ## Create ordered list
         self.particles = [p for p in self]
@@ -906,7 +913,7 @@ class ArbdModel(PdbModel):
         if typeA != typeB:
             self.nbSchemes.append( (nonbonded_scheme, typeB, typeA) )
 
-    def simulate(self, output_name, output_directory='output', num_steps=100000000, timestep=None, gpu=0, output_period=1e4, arbd=None, directory='.', restart_file=None, replicas=1, write_pqr=False, log_file=None, dry_run = False):
+    def simulate(self, output_name, output_directory='output', num_steps=100000000, timestep=None, gpu=0, output_period=1e4, arbd=None, directory='.', restart_file=None, replicas=1, random_seed=None, write_pqr=False, log_file=None, dry_run = False):
         assert(type(gpu) is int)
         num_steps = int(num_steps)
 
@@ -955,7 +962,7 @@ class ArbdModel(PdbModel):
             self.writePdb( output_name + ".pdb" )
             if write_pqr: self.write_pqr( output_name + ".pqr" )
             self.writePsf( output_name + ".psf" )
-            self.writeArbdFiles( output_name, numSteps=num_steps, outputPeriod=output_period, restart_file=restart_file )
+            self.writeArbdFiles( output_name, numSteps=num_steps, outputPeriod=output_period, restart_file=restart_file, random_seed=random_seed )
             # os.sync()
 
             ## http://stackoverflow.com/questions/18421757/live-output-from-subprocess-command
@@ -994,7 +1001,7 @@ class ArbdModel(PdbModel):
     # Methods for printing model #
     # -------------------------- #
 
-    def writeArbdFiles(self, prefix, numSteps=100000000, outputPeriod=10000, restart_file=None):
+    def writeArbdFiles(self, prefix, numSteps=100000000, outputPeriod=10000, restart_file=None, random_seed=None):
         ## TODO: save and reference directories and prefixes using member data
         d = self.potential_directory = "potentials"
         if not os.path.exists(d):
@@ -1019,7 +1026,7 @@ class ArbdModel(PdbModel):
         self._writeArbdProductPotentialFile()
         self._writeArbdGroupSitesFile()
         self._writeArbdPotentialFiles( prefix, directory = d )
-        self._writeArbdConf( prefix, numSteps=numSteps, outputPeriod=outputPeriod, restart_file=restart_file )
+        self._writeArbdConf( prefix, numSteps=numSteps, outputPeriod=outputPeriod, restart_file=restart_file, random_seed=random_seed )
         
     # def _writeArbdCoordFile(self, filename):
     #     with open(filename,'w') as fh:
@@ -1070,17 +1077,17 @@ class ArbdModel(PdbModel):
                     fh.write(" ".join(str(p.idx) for p in c) + "\n")
 
 
-    def _writeArbdConf(self, prefix, randomSeed=None, numSteps=100000000, outputPeriod=10000, restart_file=None):
+    def _writeArbdConf(self, prefix, random_seed=None, numSteps=100000000, outputPeriod=10000, restart_file=None):
         ## TODO: raise exception if _writeArbdPotentialFiles has not been called
         filename = "%s.bd" % prefix
 
         ## Prepare a dictionary to fill in placeholders in the configuration file
         params = self.__dict__.copy() # get parameters from System object
 
-        if randomSeed is None:
+        if random_seed is None:
             params['randomSeed']     = ""
         else:
-            params['randomSeed'] = "seed %s" % randomSeed
+            params['randomSeed'] = "seed %s" % random_seed
         params['numSteps']       = int(numSteps)
 
         # params['coordinateFile'] = "%s.coord.txt" % prefix
@@ -1174,10 +1181,15 @@ transDamping {g} {g} {g}
 """.format(mass=pt.mass, g=gamma)
                 else:
                     raise ValueError("Unrecognized particle integrator '{}'".format(self.particle_integrator))
+                if pt.rigid_body_potential is not None:
+                    particleParams['rigid_potential'] = "rigidBodyPotential {}".format(pt.rigid_body_potential)
+                else:
+                    particleParams['rigid_potential'] = ''
                 fh.write("""
 particle {name}
 num {num}
 {dynamics}
+{rigid_potential}
 """.format(**particleParams))
                 if 'grid' in particleParams:
                     grids = []
@@ -1192,9 +1204,26 @@ num {num}
 
                     fh.write("gridFile {}\n".format(" ".join(grids)))
                     fh.write("gridFileScale {}\n".format(" ".join(scales)))
-
                 else:
                     fh.write("gridFile {}/null.dx\n".format(self.potential_directory))
+                if 'rb_grid' in particleParams:
+                    grids = []
+                    scales = []
+                    for entry in pt.rb_grid:
+                        try:
+                            g, s = entry
+                        except:
+                            g = entry
+                            s = 1
+                        ## TODO, use Path.relative_to?
+                        try:
+                            grids.append(str( g.relative_to(os.getcwd()) ))
+                        except:
+                            grids.append(str(g))
+                        scales.append(str(s))
+                    fh.write("rigidBodyPotential {}\n".format(" ".join(grids)))
+                    if any([s!=1 for s in scales]):
+                        raise NotImplementedError
 
             ## Write coordinates and interactions
             fh.write("""
@@ -1207,8 +1236,9 @@ tabulatedPotential  1
 ## The i@j@file syntax means particle type i will have NB interactions with particle type j using the potential in file
 """.format(**params))
             for pair,f in zip(self._particleTypePairIter(), self._nbParamFiles):
-                i,j,t1,t2 = pair
-                fh.write("tabulatedFile %d@%d@%s\n" % (i,j,f))
+                if f is not None:
+                    i,j,t1,t2 = pair
+                    fh.write("tabulatedFile %d@%d@%s\n" % (i,j,f))
 
             ## Bonded interactions
             restraints = self.get_restraints()
@@ -1305,7 +1335,14 @@ component "data" value 3
             scheme = self._getNbScheme(t1,t2)
             scheme.write_file(f, t1, t2, rMax = self.cutoff)
             self._nbParamFiles.append(f)
-
+            # try:
+            #     scheme = self._getNbScheme(t1,t2)
+            #     scheme.write_file(f, t1, t2, rMax = self.cutoff)
+            #     self._nbParamFiles.append(f)
+            # except:
+            #     self._nbParamFiles.append(None)
+                
+                
     def _getNonbondedPotential(self,x,a,b):
         return a*(np.exp(-x/b))    
 
diff --git a/arbdmodel/abstract_polymer.py b/arbdmodel/abstract_polymer.py
index 732a956..9c80f61 100644
--- a/arbdmodel/abstract_polymer.py
+++ b/arbdmodel/abstract_polymer.py
@@ -195,7 +195,7 @@ class PolymerSection(ConnectableElement):
         # self.sequence = None
 
     def __repr__(self):
-        return "<{} {}[{:d}]>".format( type(self), self.name, self.num_monomers )
+        return "<{} {}[{:d}]>".format( type(self), self.segname, self.num_monomers )
 
     def set_splines(self, contours, coords):
         tck, u = interpolate.splprep( coords.T, u=contours, s=0, k=1)
diff --git a/arbdmodel/coords.py b/arbdmodel/coords.py
index 6c9515f..52b8f89 100644
--- a/arbdmodel/coords.py
+++ b/arbdmodel/coords.py
@@ -29,9 +29,9 @@ def minimizeRmsd(coordsB, coordsA, weights=None, maxIter=100):
 
 
 def minimizeRmsd(coordsB, coordsA, weights=None):
-    q,comA,comB = _minimizeRmsd(coordsB, coordsA, weights)
+    q,comB,comA = _minimizeRmsd(coordsB, coordsA, weights)
     assert( np.all(np.isreal( q )) )
-    return quaternion_to_matrix(q),comA,comB
+    return quaternion_to_matrix(q),comB,comA
 
 
 ## http://onlinelibrary.wiley.com/doi/10.1002/jcc.21439/full
diff --git a/arbdmodel/hps_polymer_model.py b/arbdmodel/hps_polymer_model.py
index f56d43b..1d246b9 100644
--- a/arbdmodel/hps_polymer_model.py
+++ b/arbdmodel/hps_polymer_model.py
@@ -256,8 +256,8 @@ class HpsModel(ArbdModel):
         kwargs['timestep'] = 10e-6
         kwargs['cutoff'] = max(4*debye_length,20)
 
-        if 'decompPeriod' not in kwargs:
-            kwargs['decompPeriod'] = 1000
+        if 'decomp_period' not in kwargs:
+            kwargs['decomp_period'] = 1000
 
         """ Assign sequences """
         if sequences is None:
diff --git a/arbdmodel/kh_polymer_model.py b/arbdmodel/kh_polymer_model.py
index 4d732a0..3d56f2b 100644
--- a/arbdmodel/kh_polymer_model.py
+++ b/arbdmodel/kh_polymer_model.py
@@ -114,9 +114,15 @@ _types = dict(
                      sigma = 5.86,
                  )
 )
-for k,t in _types.items():
-    t.resname = t.name
 
+for k,t in list(_types.items()):
+    t.resname = t.name
+    t.is_idp = False
+    
+    ## Add types for IDPs
+    _types[k+'IDP'] = ParticleType(t.name+'IDP', mass=t.mass, charge=t.charge, sigma=t.sigma, is_idp=True, resname=t.resname)
+    
+    
 class KhNonbonded(NonbondedScheme):
     def __init__(self, debye_length=10, resolution=0.1, rMin=0):
         NonbondedScheme.__init__(self, typesA=None, typesB=None, resolution=resolution, rMin=rMin)
@@ -134,13 +140,24 @@ class KhNonbonded(NonbondedScheme):
         u_elec = (A*q1*q2/D)*np.exp(-r/ld) / r 
         
         """ KH scale model """
-        # alpha = 0.228
-        # epsilon0 = -1.0
-        # e_mj = epsilon_mj[(typeA.name,typeB.name)]        
-        # epsilon = alpha * np.abs( e_mj - epsilon0 )
-        epsilon = epsilon_mj[(typeA.name,typeB.name)] 
-        lambda_ = -1 if epsilon > 0 else 1
-        epsilon = np.abs(epsilon)
+        A_is_idp = B_is_idp = False
+        try:
+            A_is_idp = A.is_idp
+        except:
+            pass
+        try:
+            B_is_idp = B.is_idp
+        except:
+            pass
+
+        _idp_scale = (int(A_is_idp)+int(A_is_idp)) * 0.5
+        alpha = 0.159 + _idp_scale * (0.228 - 0.159)
+        epsilon0 = -1.0 + _idp_scale * (-1.36 + 1.0)
+
+        e_mj = epsilon_mj[(typeA.name,typeB.name)]        
+        epsilon = alpha * np.abs( e_mj - epsilon0 )
+        # epsilon = epsilon_mj[(typeA.name,typeB.name)] 
+        lambda_ = -1 if epsilon0 > e_mj else 1
 
         sigma = 0.5 * (typeA.sigma + typeB.sigma)
         
@@ -193,6 +210,16 @@ class KhBeadsFromPolymer(Group):
 
         if len(sequence) != polymer.num_monomers:
             raise ValueError("Length of sequence does not match length of polymer")
+
+        try:
+            polymer.idp_array
+            if len(polymer.idp_array) != polymer.num_monomers:
+                raise Exception
+            self.idp_array = polymer.idp_array
+        except:
+            print("Warning: KhBeadsFromPolymer processing a polymer without 'idp_array' set; assuming IDP")
+            self.idp_array = np.ones(len(polymer.num_monomers),dtype=np.bool)
+
         Group.__init__(self, **kwargs)
         
     def _clear_beads(self):
@@ -205,6 +232,8 @@ class KhBeadsFromPolymer(Group):
             c = self.polymer.monomer_index_to_contour(i)
             r = self.polymer.contour_to_position(c)
             s = self.sequence[i]
+            if self.idp_array[i]:
+                s = s + 'IDP'
 
             bead = PointParticle(_types[s], r,
                                  name = s,
@@ -232,6 +261,7 @@ class KhModel(ArbdModel):
                  sequences = None,
                  debye_length = 10,
                  damping_coefficient = 10,
+                 idp_array = None,
                  DEBUG=False,
                  **kwargs):
 
@@ -242,8 +272,8 @@ class KhModel(ArbdModel):
         kwargs['timestep'] = 10e-6
         kwargs['cutoff'] = max(4*debye_length,20)
 
-        if 'decompPeriod' not in kwargs:
-            kwargs['decompPeriod'] = 1000
+        if 'decomp_period' not in kwargs:
+            kwargs['decomp_period'] = 1000
 
         """ Assign sequences """
         if sequences is None:
@@ -253,21 +283,25 @@ class KhModel(ArbdModel):
         self.sequences = sequences
         ArbdModel.__init__(self, [], **kwargs)
 
+
         """ Update type diffusion coefficients """
         self.types = all_types = [t for key,t in _types.items()]
         self.set_damping_coefficient( damping_coefficient )
 
         """ Set up nonbonded interactions """
-        nonbonded = KhNonbonded(debye_length)
-        for i in range(len(all_types)):
-            t1 = all_types[i]
-            for j in range(i,len(all_types)):
-                t2 = all_types[j]
-                self.useNonbondedScheme( nonbonded, typeA=t1, typeB=t2 )
+        self.kh_nonbonded = KhNonbonded(debye_length)
+        for t in all_types:
+            self._add_nonbonded_interaction(t)
                 
         """ Generate beads """
         self.generate_beads()
 
+    def _add_nonbonded_interaction(self, type_):
+        i = self.types.index(type_) if type_ in self.types else 0
+        for j in range(i,len(self.types)):
+            t = self.types[j]
+            self.useNonbondedScheme( self.kh_nonbonded, typeA=type_, typeB=t )
+
     def update_splines(self, coords):
         i = 0
         for p in self.polymer_group.polymers:
diff --git a/arbdmodel/kh_polymer_model_pair_epsilon.py b/arbdmodel/kh_polymer_model_pair_epsilon.py
index e696b2c..e84acd9 100644
--- a/arbdmodel/kh_polymer_model_pair_epsilon.py
+++ b/arbdmodel/kh_polymer_model_pair_epsilon.py
@@ -224,6 +224,14 @@ for line in _txtdata.split('\n'):
     
     __add_value((n1,n2), v)
     __add_value((n2,n1), v)
+
+    __add_value((n1+'IDP',n2), v)
+    __add_value((n2,n1+'IDP'), v)
+    __add_value((n2+'IDP',n1), v)
+    __add_value((n1,n2+'IDP'), v)
+    __add_value((n2+'IDP',n1+'IDP'), v)
+    __add_value((n1+'IDP',n2+'IDP'), v)
+
     
 if __name__ == '__main__':
     print(epsilon_mj)
diff --git a/arbdmodel/onck_polymer_model.py b/arbdmodel/onck_polymer_model.py
index 861f90d..a63429d 100644
--- a/arbdmodel/onck_polymer_model.py
+++ b/arbdmodel/onck_polymer_model.py
@@ -270,8 +270,8 @@ class OnckModel(ArbdModel):
         kwargs['timestep'] = 20e-6
         kwargs['cutoff'] = max(5*debye_length,25)
 
-        if 'decompPeriod' not in kwargs:
-            kwargs['decompPeriod'] = 1000
+        if 'decomp_period' not in kwargs:
+            kwargs['decomp_period'] = 1000
 
         """ Assign sequences """
         if sequences is None:
-- 
GitLab