mdinterface 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. mdinterface-1.2.0/PKG-INFO +133 -0
  2. mdinterface-1.2.0/README.md +111 -0
  3. mdinterface-1.2.0/mdinterface/__init__.py +20 -0
  4. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/build/__init__.py +1 -1
  5. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/build/box.py +25 -1
  6. mdinterface-1.2.0/mdinterface/build/charges.py +8 -0
  7. mdinterface-1.1.0/mdinterface/build/polymer.py → mdinterface-1.2.0/mdinterface/build/polymerize.py +25 -15
  8. mdinterface-1.2.0/mdinterface/config.py +40 -0
  9. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/core/__init__.py +1 -0
  10. mdinterface-1.2.0/mdinterface/core/polymer.py +158 -0
  11. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/core/specie.py +127 -61
  12. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/core/topology.py +31 -6
  13. mdinterface-1.2.0/mdinterface/database/__init__.py +12 -0
  14. mdinterface-1.2.0/mdinterface/database/graphene.py +35 -0
  15. mdinterface-1.2.0/mdinterface/database/ions.py +188 -0
  16. mdinterface-1.2.0/mdinterface/database/metals.py +56 -0
  17. mdinterface-1.2.0/mdinterface/database/molecules.py +58 -0
  18. mdinterface-1.2.0/mdinterface/externals/__init__.py +11 -0
  19. mdinterface-1.2.0/mdinterface/externals/ligpargen.py +91 -0
  20. mdinterface-1.2.0/mdinterface/externals/obabel.py +49 -0
  21. mdinterface-1.2.0/mdinterface/externals/pyscf.py +74 -0
  22. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/io/lammpswriter.py +1 -1
  23. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/io/read.py +53 -40
  24. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/simulationbox.py +131 -100
  25. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/utils/__init__.py +1 -1
  26. mdinterface-1.2.0/mdinterface/utils/auxiliary.py +193 -0
  27. mdinterface-1.1.0/mdinterface/utils/auxiliary.py → mdinterface-1.2.0/mdinterface/utils/graphs.py +82 -129
  28. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/utils/map.py +18 -7
  29. mdinterface-1.2.0/mdinterface.egg-info/PKG-INFO +133 -0
  30. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface.egg-info/SOURCES.txt +14 -2
  31. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface.egg-info/requires.txt +2 -0
  32. mdinterface-1.2.0/requirements.txt +7 -0
  33. mdinterface-1.1.0/PKG-INFO +0 -87
  34. mdinterface-1.1.0/README.md +0 -67
  35. mdinterface-1.1.0/mdinterface/__init__.py +0 -15
  36. mdinterface-1.1.0/mdinterface/utils/database.py +0 -147
  37. mdinterface-1.1.0/mdinterface.egg-info/PKG-INFO +0 -87
  38. mdinterface-1.1.0/requirements.txt +0 -5
  39. {mdinterface-1.1.0 → mdinterface-1.2.0}/LICENSE +0 -0
  40. {mdinterface-1.1.0 → mdinterface-1.2.0}/MANIFEST.in +0 -0
  41. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/build/continuum2sim.py +0 -0
  42. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/io/__init__.py +0 -0
  43. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface/io/packmol.py +0 -0
  44. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface.egg-info/dependency_links.txt +0 -0
  45. {mdinterface-1.1.0 → mdinterface-1.2.0}/mdinterface.egg-info/top_level.txt +0 -0
  46. {mdinterface-1.1.0 → mdinterface-1.2.0}/pyproject.toml +0 -0
  47. {mdinterface-1.1.0 → mdinterface-1.2.0}/setup.cfg +0 -0
  48. {mdinterface-1.1.0 → mdinterface-1.2.0}/setup.py +0 -0
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.2
2
+ Name: mdinterface
3
+ Version: 1.2.0
4
+ Summary: mdinterface: A package for building interface systems in Molecular Dynamics simulations.
5
+ Home-page: https://gitlab.com/roncofaber/mdinterface
6
+ License: Apache-2.0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Python: >=3.6
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: mdanalysis
16
+ Requires-Dist: ase
17
+ Requires-Dist: numpy
18
+ Requires-Dist: networkx
19
+ Requires-Dist: libarvo
20
+ Requires-Dist: platformdirs
21
+ Requires-Dist: configparser
22
+
23
+ <div style="display: flex; align-items: center;">
24
+ <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
25
+ <div style="display: flex; flex-direction: column;">
26
+ <h1 style="margin: 0;">mdinterface: Build Interface Systems for Molecular Dynamics Simulations</h1>
27
+ </div>
28
+ </div>
29
+
30
+ [![PyPI version](https://badge.fury.io/py/mdinterface.svg?icon=si%3Apython)](https://pypi.org/project/mdinterface/) [![GitHub version](https://badge.fury.io/gh/roncofaber%2Fmdinterface.svg?icon=si%3Agithub)](https://github.com/roncofaber/mdinterface)
31
+
32
+ `mdinterface` is a Python package designed to build systems for Molecular Dynamics (MD) simulations. Initially developed to construct electrolyte/electrode solid-liquid interfaces, it is also well-suited for generating MD boxes of liquids, electrolyte systems, and polymer networks.
33
+
34
+ ## Features
35
+
36
+ Using `mdinterface` you can:
37
+
38
+ - Create layered simulation boxes with solvents, solutes, and interface slabs.
39
+ - Populate your system with ions and solvents using PACKMOL, and provide the starting concentration profile of the species to get the MD where you want it to be, but faster.
40
+ - Import common molecules/metals/polymers with pre-defined classical force fields parameters from the database, or automatically generate new OPLS-AA force field parameters using [https://github.com/Isra3l/ligpargen](LigParGen).
41
+ - Generate polymer chains of any length from a starting monomer.
42
+ - Estimate the RESP charges of molecules using the [PySCF electronic structure code](https://github.com/pyscf/pyscf).
43
+ - Automatically write LAMMPS data files and coefficients, so you can start making them atoms dance!
44
+
45
+ ## Requirements
46
+
47
+ Check the file [requirements.txt](requirements.txt) to see which packages are needed. Installing the package using `pip` should already take care of all mandatory dependencies.
48
+
49
+ Additionaly, you need the `packmol` utility installed to generate MD boxes. You can follow the instructions at [https://m3g.github.io/packmol/](https://m3g.github.io/packmol/) to install it. Alternatively, you can install it using `conda`:
50
+
51
+ ```bash
52
+ conda install -c conda-forge packmol
53
+ ```
54
+
55
+ ### Optional packages
56
+
57
+ #### Automatic OPLS-AA force field generator with LigParGen
58
+
59
+ If you want to use LigParGen, please follow the instructions on their [GitHub](https://github.com/Isra3l/ligpargen). To make sure that BOSS is recognized by the package, specify the environment variable `BOSSdir`.
60
+
61
+ You can do this manually every time, or just add the path to the `config.ini` file to your config directory:
62
+
63
+ ```bash
64
+ # config.ini
65
+ [settings]
66
+ BOSSdir = /path/to/your/boss/dir
67
+ ```
68
+
69
+ The config directory is found using [`platformdirs`](https://pypi.org/project/platformdirs/) and is OS dependent.
70
+
71
+ ### RESP charge analysis with PySCF
72
+
73
+ To use the RESP charge analysis feature, you need to install [PySCF](https://github.com/pyscf/pyscf) and [PyMBXAS](https://gitlab.com/roncofaber/pymbxas).
74
+
75
+ ## Installation
76
+
77
+ ### Install using `pip`
78
+
79
+ You can simply install the latest release of the package and all dependencies using:
80
+
81
+ ```bash
82
+ pip install mdinterface
83
+ ```
84
+
85
+ ### Install directly the source code
86
+
87
+ Alternatively you can obtain `mdinterface` directly from the repository by following these steps:
88
+
89
+ Clone the repository in the desired location:
90
+
91
+ ```bash
92
+ git clone git@gitlab.com:roncofaber/mdinterface.git
93
+ ```
94
+
95
+ Install the required packages:
96
+
97
+ ```bash
98
+ cd mdinterface
99
+ conda install -c conda-forge --file requirements.txt
100
+ ```
101
+
102
+ Install the package with pip:
103
+
104
+ ```bash
105
+ pip install .
106
+ ```
107
+
108
+ ### Install a development environment
109
+
110
+ If you plan of making changes, clone the package and install the requirements but then add it to your development environment with:
111
+
112
+ ```bash
113
+ pip install --no-build-isolation --no-deps -e .
114
+ ```
115
+
116
+ ## Usage
117
+
118
+ Creating a new Specie (with its topology attributes) is as simple as doing:
119
+
120
+ ```python
121
+ #%% Make a specie, and use LigParGen to estimate FF parameters
122
+ my_specie = Specie("CH3ONO", ligpargen=True) # molecule is in ASE database
123
+ ```
124
+
125
+ Please, check the files in [examples](mdinterface/examples/) to learn how to use the package. Including setting up simulation boxes, creating polymers and much more.
126
+
127
+ ## Roadmap
128
+
129
+ Since the original idea was to make a package to build MD boxes layer by layer, I am strongly debating renaming everything as "Workflow for Easy Molecular DYnamics Simulations", aka WEMDYS.
130
+
131
+ ## Questions & Issues
132
+
133
+ Sir, this is a WEMDY'S. Please contact me or open an issue, glad to talk about ideas and improvements!
@@ -0,0 +1,111 @@
1
+ <div style="display: flex; align-items: center;">
2
+ <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
3
+ <div style="display: flex; flex-direction: column;">
4
+ <h1 style="margin: 0;">mdinterface: Build Interface Systems for Molecular Dynamics Simulations</h1>
5
+ </div>
6
+ </div>
7
+
8
+ [![PyPI version](https://badge.fury.io/py/mdinterface.svg?icon=si%3Apython)](https://pypi.org/project/mdinterface/) [![GitHub version](https://badge.fury.io/gh/roncofaber%2Fmdinterface.svg?icon=si%3Agithub)](https://github.com/roncofaber/mdinterface)
9
+
10
+ `mdinterface` is a Python package designed to build systems for Molecular Dynamics (MD) simulations. Initially developed to construct electrolyte/electrode solid-liquid interfaces, it is also well-suited for generating MD boxes of liquids, electrolyte systems, and polymer networks.
11
+
12
+ ## Features
13
+
14
+ Using `mdinterface` you can:
15
+
16
+ - Create layered simulation boxes with solvents, solutes, and interface slabs.
17
+ - Populate your system with ions and solvents using PACKMOL, and provide the starting concentration profile of the species to get the MD where you want it to be, but faster.
18
+ - Import common molecules/metals/polymers with pre-defined classical force fields parameters from the database, or automatically generate new OPLS-AA force field parameters using [https://github.com/Isra3l/ligpargen](LigParGen).
19
+ - Generate polymer chains of any length from a starting monomer.
20
+ - Estimate the RESP charges of molecules using the [PySCF electronic structure code](https://github.com/pyscf/pyscf).
21
+ - Automatically write LAMMPS data files and coefficients, so you can start making them atoms dance!
22
+
23
+ ## Requirements
24
+
25
+ Check the file [requirements.txt](requirements.txt) to see which packages are needed. Installing the package using `pip` should already take care of all mandatory dependencies.
26
+
27
+ Additionaly, you need the `packmol` utility installed to generate MD boxes. You can follow the instructions at [https://m3g.github.io/packmol/](https://m3g.github.io/packmol/) to install it. Alternatively, you can install it using `conda`:
28
+
29
+ ```bash
30
+ conda install -c conda-forge packmol
31
+ ```
32
+
33
+ ### Optional packages
34
+
35
+ #### Automatic OPLS-AA force field generator with LigParGen
36
+
37
+ If you want to use LigParGen, please follow the instructions on their [GitHub](https://github.com/Isra3l/ligpargen). To make sure that BOSS is recognized by the package, specify the environment variable `BOSSdir`.
38
+
39
+ You can do this manually every time, or just add the path to the `config.ini` file to your config directory:
40
+
41
+ ```bash
42
+ # config.ini
43
+ [settings]
44
+ BOSSdir = /path/to/your/boss/dir
45
+ ```
46
+
47
+ The config directory is found using [`platformdirs`](https://pypi.org/project/platformdirs/) and is OS dependent.
48
+
49
+ ### RESP charge analysis with PySCF
50
+
51
+ To use the RESP charge analysis feature, you need to install [PySCF](https://github.com/pyscf/pyscf) and [PyMBXAS](https://gitlab.com/roncofaber/pymbxas).
52
+
53
+ ## Installation
54
+
55
+ ### Install using `pip`
56
+
57
+ You can simply install the latest release of the package and all dependencies using:
58
+
59
+ ```bash
60
+ pip install mdinterface
61
+ ```
62
+
63
+ ### Install directly the source code
64
+
65
+ Alternatively you can obtain `mdinterface` directly from the repository by following these steps:
66
+
67
+ Clone the repository in the desired location:
68
+
69
+ ```bash
70
+ git clone git@gitlab.com:roncofaber/mdinterface.git
71
+ ```
72
+
73
+ Install the required packages:
74
+
75
+ ```bash
76
+ cd mdinterface
77
+ conda install -c conda-forge --file requirements.txt
78
+ ```
79
+
80
+ Install the package with pip:
81
+
82
+ ```bash
83
+ pip install .
84
+ ```
85
+
86
+ ### Install a development environment
87
+
88
+ If you plan of making changes, clone the package and install the requirements but then add it to your development environment with:
89
+
90
+ ```bash
91
+ pip install --no-build-isolation --no-deps -e .
92
+ ```
93
+
94
+ ## Usage
95
+
96
+ Creating a new Specie (with its topology attributes) is as simple as doing:
97
+
98
+ ```python
99
+ #%% Make a specie, and use LigParGen to estimate FF parameters
100
+ my_specie = Specie("CH3ONO", ligpargen=True) # molecule is in ASE database
101
+ ```
102
+
103
+ Please, check the files in [examples](mdinterface/examples/) to learn how to use the package. Including setting up simulation boxes, creating polymers and much more.
104
+
105
+ ## Roadmap
106
+
107
+ Since the original idea was to make a package to build MD boxes layer by layer, I am strongly debating renaming everything as "Workflow for Easy Molecular DYnamics Simulations", aka WEMDYS.
108
+
109
+ ## Questions & Issues
110
+
111
+ Sir, this is a WEMDY'S. Please contact me or open an issue, glad to talk about ideas and improvements!
@@ -0,0 +1,20 @@
1
+ """
2
+ mdinterface: Build Interface Systems for Molecular Dynamics Simulations
3
+
4
+ `mdinterface` is a Python package designed to build systems for Molecular Dynamics (MD) simulations.
5
+ Initially developed to construct electrolyte/electrode interfaces, it is also well-suited for generating MD boxes of liquids, electrolyte systems, and polymer networks.
6
+
7
+ """
8
+
9
+ __version__ = '1.2.0'
10
+ __date__ = '28 Mar. 2025'
11
+ __author__ = 'Fabrice Roncoroni'
12
+ __all__ = ['SimulationBox', "Specie", "Polymer"]
13
+
14
+ from .simulationbox import SimulationBox
15
+ from .core.specie import Specie
16
+ from .core.polymer import Polymer
17
+
18
+ # load configuration file
19
+ from .config import load_config
20
+ load_config()
@@ -1,3 +1,3 @@
1
- from .polymer import *
1
+ from .polymerize import *
2
2
  from .box import *
3
3
  from .continuum2sim import *
@@ -47,7 +47,7 @@ def make_solvent_box(species, solvent, ions, volume, density, nions, concentrati
47
47
  nummols = int(units.mol*density*(1.0/mass)*solvent_volume)
48
48
 
49
49
  instructions.append([solvent, nummols, "box"])
50
-
50
+
51
51
  # generate universe file
52
52
  universe = populate_box(volume, instructions)
53
53
 
@@ -228,3 +228,27 @@ def populate_with_ions(ions, nions, volume, ion_pos=False, conmodel=None):
228
228
  volume[2] /= 2
229
229
 
230
230
  return place_ions_random(ions, nions, volume, to_center, max_attempts=max_attempts)
231
+
232
+
233
+ # add a component to the system
234
+ def add_component(system, component, zdim, padding=0):
235
+
236
+ # nothing to add here
237
+ if component is None:
238
+ return system, zdim
239
+
240
+ # ohh, let's lego the shit out of this
241
+ component = component.copy()
242
+
243
+ # component: "look at me, I am the system now."
244
+ if system is None:
245
+ component.atoms.translate([0, 0, zdim])
246
+ system = component
247
+ zdim += component.dimensions[2]
248
+
249
+ # make space and add it to the pile
250
+ else:
251
+ component.atoms.translate([0, 0, zdim + padding])
252
+ system = mda.Merge(system.atoms, component.atoms)
253
+ zdim += component.dimensions[2] + padding
254
+ return system, zdim
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Mon Feb 3 14:50:47 2025
5
+
6
+ @author: roncofaber
7
+ """
8
+
@@ -12,12 +12,12 @@ import ase.build
12
12
 
13
13
  #%%
14
14
 
15
- def build_polymer(system, substitute, nrep, target_distance=1.600):
15
+ def build_polymer(monomer, substitute, nrep, start_end_idxs=None, target_distance=1.600):
16
16
  """
17
17
  Build a polymer by replicating a monomer structure and replacing 'X' atoms with a substitute.
18
18
 
19
19
  Parameters:
20
- - system (ase.Atoms): The monomer structure as an ASE Atoms object.
20
+ - monomer (ase.Atoms): The monomer structure as an ASE Atoms object.
21
21
  - substitute (str): Chemical symbol to replace 'X' with after replication.
22
22
  - nrep (int): Number of times to replicate the monomer.
23
23
  - target_distance (float, optional): Desired bond distance at the joining points (in angstroms).
@@ -27,38 +27,47 @@ def build_polymer(system, substitute, nrep, target_distance=1.600):
27
27
  - sout (ase.Atoms): The replicated and modified polymer structure.
28
28
  """
29
29
 
30
- system = system.copy()
30
+ # make sure we don't mess nothing
31
+ monomer = monomer.copy()
32
+
33
+ # if start end is provided, do substitution here
34
+ if start_end_idxs is not None:
35
+ assert len(start_end_idxs) == 2
36
+ symbols = monomer.get_chemical_symbols()
37
+ symbols[start_end_idxs[0]] = "X"
38
+ symbols[start_end_idxs[1]] = "X"
39
+ monomer.set_chemical_symbols(symbols)
31
40
 
32
41
  # Check if nrep is at least 1
33
42
  if nrep < 1:
34
43
  raise ValueError("nrep must be at least 1")
35
44
 
36
45
  # Find the indices of the 'X' atoms
37
- x_idxs = np.where(np.array(system.get_chemical_symbols()) == "X")[0]
46
+ x_idxs = np.where(np.array(monomer.get_chemical_symbols()) == "X")[0]
38
47
  if len(x_idxs) != 2:
39
48
  raise ValueError("The monomer should contain exactly two 'X' atoms.")
40
49
 
41
50
  # Find atoms connected to X
42
- ini_idx = ase.build.connected_indices(system, x_idxs[0])[1]
43
- end_idx = ase.build.connected_indices(system, x_idxs[1])[1]
51
+ ini_idx = ase.build.connected_indices(monomer, x_idxs[0])[1]
52
+ end_idx = ase.build.connected_indices(monomer, x_idxs[1])[1]
44
53
 
45
- bnd_vec = system.get_distance(end_idx, x_idxs[1], vector=True)
54
+ bnd_vec = monomer.get_distance(end_idx, x_idxs[1], vector=True)
46
55
  bnd_vec = target_distance * bnd_vec / np.linalg.norm(bnd_vec)
47
56
 
48
- ini_pos = system.get_positions()[ini_idx]
49
- end_pos = system.get_positions()[end_idx] + bnd_vec
57
+ ini_pos = monomer.get_positions()[ini_idx]
58
+ end_pos = monomer.get_positions()[end_idx] + bnd_vec
50
59
 
51
60
  # Get the vector between the two 'X' atoms
52
61
  X_vec = end_pos - ini_pos
53
62
 
54
63
  # remember connecting points
55
- is_connected = np.array(len(system)*[False])
64
+ is_connected = np.array(len(monomer)*[False])
56
65
  is_connected[ini_idx] = True
57
66
  is_connected[end_idx] = True
58
- system.new_array("is_connected", is_connected)
67
+ monomer.new_array("is_connected", is_connected)
59
68
 
60
- # Start with a copy of the system
61
- sout = system.copy()
69
+ # Start with a copy of the monomer
70
+ sout = monomer.copy()
62
71
  sout.new_array("mon_id", np.array(len(sout)*[0]))
63
72
 
64
73
  # Replicate the monomer
@@ -66,7 +75,7 @@ def build_polymer(system, substitute, nrep, target_distance=1.600):
66
75
  del sout[x_idxs[1]] # Remove the second 'X' atom in the first monomer
67
76
 
68
77
  for ii in range(nrep - 1):
69
- sadd = system.copy()
78
+ sadd = monomer.copy()
70
79
 
71
80
  if ii < nrep - 2:
72
81
  del sadd[x_idxs] # Remove both 'X' atoms in the intermediate monomers
@@ -81,5 +90,6 @@ def build_polymer(system, substitute, nrep, target_distance=1.600):
81
90
  symbols = np.array(sout.get_chemical_symbols())
82
91
  symbols[symbols == "X"] = substitute
83
92
  sout.set_chemical_symbols(symbols)
93
+ sout.center()
84
94
 
85
- return sout#ase.build.sort(sout)
95
+ return sout
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Config file reader blatantly copied from ASE
5
+
6
+ @author: roncofaber
7
+ """
8
+
9
+ import os
10
+ import configparser
11
+ from platformdirs import user_config_dir
12
+
13
+ def load_config():
14
+ # Determine the path to the user's configuration file
15
+ MDINT_CONFIG_DIR = user_config_dir("mdinterface")
16
+ user_config_file = MDINT_CONFIG_DIR + "/config.ini"
17
+
18
+ os.environ["MDINT_CONFIG_DIR"] = MDINT_CONFIG_DIR
19
+
20
+ # Check if the user's configuration file exists
21
+ if os.path.exists(user_config_file):
22
+ config_file = user_config_file
23
+ else:
24
+ # Fall back to the default configuration file in the package directory
25
+ print(f"No config.ini file found:\n{user_config_file}")
26
+ config_file = os.path.join(os.path.dirname(__file__), 'config.ini')
27
+ print(f"Defaulting on reading file:\n{config_file}")
28
+ print("Make sure it makes sense.")
29
+
30
+ config = configparser.ConfigParser()
31
+ config.optionxform = str
32
+ config.read(config_file)
33
+
34
+ if 'settings' in config:
35
+ for key in config['settings']:
36
+ os.environ[key] = config['settings'][key]
37
+
38
+ # Load the configuration when the module is imported
39
+ if __name__ == "__main__":
40
+ load_config()
@@ -6,3 +6,4 @@ core: Core functionalities including related components.
6
6
 
7
7
  from .specie import *
8
8
  from .topology import *
9
+ from .polymer import *
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Mon Feb 3 15:00:01 2025
5
+
6
+ @author: roncofaber
7
+ """
8
+
9
+ # repo stuff
10
+ from .specie import Specie
11
+ from mdinterface.externals import run_ligpargen
12
+ from mdinterface.build.polymerize import build_polymer
13
+
14
+ # other stuff
15
+ import ase
16
+ import random
17
+ import numpy as np
18
+
19
+ #%%
20
+
21
+ class Polymer(Specie):
22
+
23
+ def __init__(self, atoms=None, charges=None, atom_types=None, bonds=None,
24
+ angles=None, dihedrals=None, impropers=None, lj={}, cutoff=1.0,
25
+ name=None, lammps_data=None, fix_missing=False, chg_scaling=1.0,
26
+ pbc=False, ligpargen=False, tot_charge=0, nrep=1, start_end_idxs=None,
27
+ target_distance=1.600, substitute=None, refine_charges=False,
28
+ offset=False):
29
+
30
+ # initialize polymer stuff
31
+ self._snippet_cache = {}
32
+
33
+ # polymerize
34
+ polymer = build_polymer(atoms, substitute, nrep, start_end_idxs=start_end_idxs,
35
+ target_distance=target_distance)
36
+
37
+ # Initialize the parent class
38
+ super().__init__(polymer, charges, atom_types, bonds, angles, dihedrals,
39
+ impropers, lj, cutoff, name, lammps_data, fix_missing,
40
+ chg_scaling, pbc, ligpargen, tot_charge)
41
+
42
+ if refine_charges:
43
+ self.refine_charges(offset=offset)
44
+
45
+ return
46
+
47
+ # snip the polymer into a smaller molecule, return as ase.Atoms
48
+ def make_snippet(self, centers, Nmax, ending="F"):
49
+
50
+ idxs = list(set(np.concatenate(self.find_relevant_distances(Nmax, centers=centers))))
51
+ edxs = list(set(np.concatenate(self.find_relevant_distances(Nmax+1, centers=centers, Nmin=Nmax))) - set([centers]))
52
+
53
+ snippet_idxs = np.array(idxs + edxs)
54
+
55
+
56
+ chain = self.atoms[idxs].copy()
57
+ term = self.atoms[edxs].copy()
58
+
59
+ term.set_chemical_symbols(len(term) * [ending])
60
+ snippet = ase.Atoms(chain + term)
61
+
62
+ return snippet, snippet_idxs
63
+
64
+ # return list of elements adjacent to a connection point
65
+ def _get_connection_elements(self):
66
+
67
+ # Get elements where there is a connection
68
+ centers = np.argwhere(self.atoms.arrays["is_connected"]).flatten()
69
+
70
+ poi = []
71
+ for center in centers:
72
+ idxs = set(self.find_relevant_distances(1, centers=center).flatten())
73
+ if not any([ii in poi for ii in idxs]):
74
+ poi.append(center)
75
+ return poi
76
+
77
+ def _update_charges(self, center, Nmax, charges, ending="F"):
78
+ """
79
+ Updates the charges for the specified species.
80
+
81
+ Parameters:
82
+ specie (ase.Atoms): The species object containing the atoms.
83
+ center (int): The center atom index.
84
+ Nmax (int): The maximum number of neighbors to consider.
85
+ charges (np.ndarray): The array of charges to be updated.
86
+ ending (str): The chemical symbol for the terminal atoms.
87
+ """
88
+
89
+ ldxs = list(set(np.concatenate(self.find_relevant_distances(3, centers=center))))
90
+
91
+ snippet, snippet_idxs = self.make_snippet(center, Nmax, ending=ending)
92
+
93
+ # Check if snippet already exists in cache
94
+ snippet_hash = ''.join(snippet.get_chemical_symbols())
95
+
96
+ if snippet_hash in self._snippet_cache:
97
+ cached_charges = self._snippet_cache[snippet_hash]
98
+ mapping = [np.argwhere(snippet_idxs == ll)[0][0] for ll in ldxs]
99
+ charges[ldxs] = cached_charges[mapping]
100
+ return
101
+
102
+ # snippet charge
103
+ if "nominal_charge" in snippet.arrays:
104
+ sn_charge = snippet.arrays["nominal_charge"].sum()
105
+ else:
106
+ sn_charge = None
107
+
108
+ # run ligpargen
109
+ output = run_ligpargen(snippet, charge=sn_charge)
110
+
111
+ # get charges
112
+ new_charges = output[0].get_initial_charges()
113
+
114
+ # find mapping
115
+ mapping = [np.argwhere(snippet_idxs == ll)[0][0] for ll in ldxs]
116
+
117
+ # update charges
118
+ charges[ldxs] = new_charges[mapping]
119
+
120
+ # Store the new snippet and its charges in cache
121
+ self._snippet_cache[snippet_hash] = new_charges
122
+
123
+ return
124
+
125
+ # main driver that refines charges across the whole polymer
126
+ def refine_charges(self, Nmax=12, offset=False, ending="F"):
127
+ """
128
+ Refines the charges for the specified species.
129
+
130
+ Parameters:
131
+ specie (ase.Atoms): The species object containing the atoms.
132
+ Nmax (int): The maximum number of neighbors to consider.
133
+ offset (bool): Whether to apply an offset to the charges.
134
+
135
+ Returns:
136
+ np.ndarray: The refined charges.
137
+ """
138
+
139
+ # get charges and connection elements
140
+ charges = self.charges
141
+ centers = self._get_connection_elements()
142
+
143
+ # get charges at every point
144
+ for center in centers:
145
+ self._update_charges(center, Nmax, charges, ending=ending)
146
+
147
+ # bring back to zero
148
+ if offset:
149
+
150
+ if "nominal_charge" in self.atoms.arrays:
151
+ target = self.atoms.arrays["nominal_charge"].sum()
152
+ else:
153
+ target = 0
154
+
155
+ charges -= ((charges.sum() - target) / len(charges))
156
+
157
+ self.atoms.set_initial_charges(charges)
158
+ return