mdinterface 1.1.0__tar.gz → 1.3.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 (51) hide show
  1. mdinterface-1.3.0/PKG-INFO +158 -0
  2. mdinterface-1.3.0/README.md +126 -0
  3. mdinterface-1.3.0/mdinterface/__init__.py +20 -0
  4. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/build/__init__.py +1 -1
  5. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/build/box.py +25 -1
  6. mdinterface-1.3.0/mdinterface/build/charges.py +8 -0
  7. mdinterface-1.1.0/mdinterface/build/polymer.py → mdinterface-1.3.0/mdinterface/build/polymerize.py +25 -15
  8. mdinterface-1.3.0/mdinterface/config.py +40 -0
  9. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/core/__init__.py +1 -0
  10. mdinterface-1.3.0/mdinterface/core/polymer.py +158 -0
  11. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/core/specie.py +136 -63
  12. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/core/topology.py +31 -6
  13. mdinterface-1.3.0/mdinterface/database/__init__.py +12 -0
  14. mdinterface-1.3.0/mdinterface/database/graphene.py +35 -0
  15. mdinterface-1.3.0/mdinterface/database/ions.py +212 -0
  16. mdinterface-1.3.0/mdinterface/database/metals.py +56 -0
  17. mdinterface-1.3.0/mdinterface/database/molecules.py +65 -0
  18. mdinterface-1.3.0/mdinterface/externals/__init__.py +11 -0
  19. mdinterface-1.3.0/mdinterface/externals/ligpargen.py +91 -0
  20. mdinterface-1.3.0/mdinterface/externals/obabel.py +49 -0
  21. mdinterface-1.3.0/mdinterface/externals/pyscf.py +77 -0
  22. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/lammpswriter.py +31 -21
  23. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/read.py +53 -40
  24. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/simulationbox.py +141 -101
  25. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/utils/__init__.py +1 -1
  26. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/utils/auxiliary.py +53 -122
  27. mdinterface-1.3.0/mdinterface/utils/draw.py +40 -0
  28. mdinterface-1.3.0/mdinterface/utils/graphs.py +239 -0
  29. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/utils/map.py +18 -7
  30. mdinterface-1.3.0/mdinterface/utils/poisson.py +239 -0
  31. mdinterface-1.3.0/mdinterface/utils/units.py +42 -0
  32. mdinterface-1.3.0/mdinterface.egg-info/PKG-INFO +158 -0
  33. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface.egg-info/SOURCES.txt +18 -3
  34. mdinterface-1.3.0/mdinterface.egg-info/requires.txt +17 -0
  35. {mdinterface-1.1.0 → mdinterface-1.3.0}/requirements.txt +2 -1
  36. {mdinterface-1.1.0 → mdinterface-1.3.0}/setup.cfg +14 -4
  37. mdinterface-1.1.0/PKG-INFO +0 -87
  38. mdinterface-1.1.0/README.md +0 -67
  39. mdinterface-1.1.0/mdinterface/__init__.py +0 -15
  40. mdinterface-1.1.0/mdinterface/utils/database.py +0 -147
  41. mdinterface-1.1.0/mdinterface.egg-info/PKG-INFO +0 -87
  42. mdinterface-1.1.0/mdinterface.egg-info/requires.txt +0 -5
  43. {mdinterface-1.1.0 → mdinterface-1.3.0}/LICENSE +0 -0
  44. {mdinterface-1.1.0 → mdinterface-1.3.0}/MANIFEST.in +0 -0
  45. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/build/continuum2sim.py +0 -0
  46. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/__init__.py +0 -0
  47. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/packmol.py +0 -0
  48. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface.egg-info/dependency_links.txt +0 -0
  49. {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface.egg-info/top_level.txt +0 -0
  50. {mdinterface-1.1.0 → mdinterface-1.3.0}/pyproject.toml +0 -0
  51. {mdinterface-1.1.0 → mdinterface-1.3.0}/setup.py +0 -0
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: mdinterface
3
+ Version: 1.3.0
4
+ Summary: Build Interface Systems for Molecular Dynamics Simulations
5
+ Home-page: https://gitlab.com/roncofaber/mdinterface
6
+ Author: Fabrice Roncoroni
7
+ Author-email: fabrice.roncoroni@gmail.com
8
+ License: Apache-2.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Software Development
13
+ Classifier: Topic :: Scientific/Engineering
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: mdanalysis
18
+ Requires-Dist: ase
19
+ Requires-Dist: numpy
20
+ Requires-Dist: networkx
21
+ Requires-Dist: platformdirs
22
+ Requires-Dist: configparser
23
+ Provides-Extra: volume
24
+ Requires-Dist: libarvo; extra == "volume"
25
+ Provides-Extra: resp
26
+ Requires-Dist: pyscf; extra == "resp"
27
+ Requires-Dist: pymbxas; extra == "resp"
28
+ Provides-Extra: all
29
+ Requires-Dist: libarvo; extra == "all"
30
+ Requires-Dist: pyscf; extra == "all"
31
+ Dynamic: license-file
32
+
33
+ <div style="display: flex; align-items: center;">
34
+ <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
35
+ <div style="display: flex; flex-direction: column;">
36
+ <h1 style="margin: 0;">mdinterface: Build Interface Systems for Molecular Dynamics Simulations</h1>
37
+ </div>
38
+ </div>
39
+
40
+ [![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)
41
+
42
+ `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.
43
+
44
+ ## Features
45
+
46
+ Using `mdinterface` you can:
47
+
48
+ - Create layered simulation boxes with solvents, solutes, and interface slabs.
49
+ - 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.
50
+ - 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 [LigParGen](https://github.com/Isra3l/ligpargen).
51
+ - Generate polymer chains of any length from a starting monomer.
52
+ - Estimate the RESP charges of molecules using the [PySCF electronic structure code](https://github.com/pyscf/pyscf).
53
+ - Automatically write LAMMPS data files and coefficients, so you can start making them atoms dance as soon as possible!
54
+ - Integrate your workflow with [MDAnalysis](https://github.com/MDAnalysis/mdanalysis): create your molecules with `mdinterface` and convert them to `mda.Universe` objects with a simple interface.
55
+
56
+ ## Requirements
57
+
58
+ 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.
59
+
60
+ Additionally, 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`:
61
+
62
+ ```bash
63
+ conda install -c conda-forge packmol
64
+ ```
65
+
66
+ ### Optional packages
67
+
68
+ #### Automatic OPLS-AA force field generator with LigParGen
69
+
70
+ If you want to use LigParGen, please follow the instructions on their [GitHub](https://github.com/Isra3l/ligpargen). If you are having trouble installing, you can try my own [fork](https://github.com/roncofaber/ligpargen) of the original repo. To make sure that BOSS is recognized by the package, specify the environment variable `BOSSdir`.
71
+
72
+ You can do this manually every time, or just add the path to the `config.ini` file to your config directory:
73
+
74
+ ```bash
75
+ # config.ini
76
+ [settings]
77
+ BOSSdir = /path/to/your/boss/dir
78
+ ```
79
+
80
+ The config directory is found using [`platformdirs`](https://pypi.org/project/platformdirs/) and is OS dependent.
81
+
82
+ ### RESP charge analysis with PySCF
83
+
84
+ To use the RESP charge analysis feature, you need to install [PySCF](https://github.com/pyscf/pyscf) and [PyMBXAS](https://gitlab.com/roncofaber/pymbxas). To my knowledge, the RESP feature is only implemented in [gpu4pyscf](https://github.com/pyscf/gpu4pyscf) at the moment, so follow the repo instructions on how to install it.
85
+
86
+ ## Installation
87
+
88
+ ### Install using `pip`
89
+
90
+ You can simply install the latest release of the package and all dependencies using:
91
+
92
+ ```bash
93
+ pip install mdinterface
94
+ ```
95
+
96
+ ### Install directly the source code
97
+
98
+ Alternatively you can obtain `mdinterface` directly from the repository by following these steps:
99
+
100
+ Clone the repository in the desired location:
101
+
102
+ ```bash
103
+ git clone git@gitlab.com:roncofaber/mdinterface.git
104
+ ```
105
+
106
+ Install the package:
107
+
108
+ ```bash
109
+ cd mdinterface
110
+ pip install .
111
+ ```
112
+
113
+ ### Install a development environment
114
+
115
+ If you plan of making changes, clone the package and add it to your development environment with:
116
+
117
+ ```bash
118
+ pip install --no-build-isolation -e .
119
+ ```
120
+
121
+ ### Install optional packages
122
+
123
+ You can install optional dependencies with the following commands:
124
+
125
+ ```bash
126
+ # install libarvo to estimate species:
127
+ pip install mdinterface[volume] volumes
128
+ # install pyscf and pymbxas (you still need gpu4pyscf):
129
+ pip install mdinterface[resp]
130
+ # install all of the above options:
131
+ pip install mdinterface[all]
132
+ ```
133
+
134
+ ## Usage
135
+
136
+ Creating a new Specie (with its topology attributes) is as simple as doing:
137
+
138
+ ```python
139
+ #%% Make a specie, and use LigParGen to estimate FF parameters
140
+ my_specie = Specie("CH3ONO", ligpargen=True) # molecule is in ASE database
141
+
142
+ # make a specie from any ASE readable file
143
+ my_specie = Specie("methylnitrite.xyz", ligpargen=True)
144
+
145
+ # convert specie to mdanalysis universe (and all the attributes!)
146
+ my_specie.to_universe()
147
+
148
+ ```
149
+
150
+ Please, check the files in [examples](mdinterface/examples/) to learn how to use the package. Including setting up simulation boxes, creating polymers, perform RESP analysis and much more!
151
+
152
+ ## Roadmap
153
+
154
+ 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.
155
+
156
+ ## Questions & Issues
157
+
158
+ Sir, this is a WEMDY'S. Please contact me or open an issue, glad to talk about ideas and improvements!
@@ -0,0 +1,126 @@
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 [LigParGen](https://github.com/Isra3l/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 as soon as possible!
22
+ - Integrate your workflow with [MDAnalysis](https://github.com/MDAnalysis/mdanalysis): create your molecules with `mdinterface` and convert them to `mda.Universe` objects with a simple interface.
23
+
24
+ ## Requirements
25
+
26
+ 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.
27
+
28
+ Additionally, 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`:
29
+
30
+ ```bash
31
+ conda install -c conda-forge packmol
32
+ ```
33
+
34
+ ### Optional packages
35
+
36
+ #### Automatic OPLS-AA force field generator with LigParGen
37
+
38
+ If you want to use LigParGen, please follow the instructions on their [GitHub](https://github.com/Isra3l/ligpargen). If you are having trouble installing, you can try my own [fork](https://github.com/roncofaber/ligpargen) of the original repo. To make sure that BOSS is recognized by the package, specify the environment variable `BOSSdir`.
39
+
40
+ You can do this manually every time, or just add the path to the `config.ini` file to your config directory:
41
+
42
+ ```bash
43
+ # config.ini
44
+ [settings]
45
+ BOSSdir = /path/to/your/boss/dir
46
+ ```
47
+
48
+ The config directory is found using [`platformdirs`](https://pypi.org/project/platformdirs/) and is OS dependent.
49
+
50
+ ### RESP charge analysis with PySCF
51
+
52
+ To use the RESP charge analysis feature, you need to install [PySCF](https://github.com/pyscf/pyscf) and [PyMBXAS](https://gitlab.com/roncofaber/pymbxas). To my knowledge, the RESP feature is only implemented in [gpu4pyscf](https://github.com/pyscf/gpu4pyscf) at the moment, so follow the repo instructions on how to install it.
53
+
54
+ ## Installation
55
+
56
+ ### Install using `pip`
57
+
58
+ You can simply install the latest release of the package and all dependencies using:
59
+
60
+ ```bash
61
+ pip install mdinterface
62
+ ```
63
+
64
+ ### Install directly the source code
65
+
66
+ Alternatively you can obtain `mdinterface` directly from the repository by following these steps:
67
+
68
+ Clone the repository in the desired location:
69
+
70
+ ```bash
71
+ git clone git@gitlab.com:roncofaber/mdinterface.git
72
+ ```
73
+
74
+ Install the package:
75
+
76
+ ```bash
77
+ cd mdinterface
78
+ pip install .
79
+ ```
80
+
81
+ ### Install a development environment
82
+
83
+ If you plan of making changes, clone the package and add it to your development environment with:
84
+
85
+ ```bash
86
+ pip install --no-build-isolation -e .
87
+ ```
88
+
89
+ ### Install optional packages
90
+
91
+ You can install optional dependencies with the following commands:
92
+
93
+ ```bash
94
+ # install libarvo to estimate species:
95
+ pip install mdinterface[volume] volumes
96
+ # install pyscf and pymbxas (you still need gpu4pyscf):
97
+ pip install mdinterface[resp]
98
+ # install all of the above options:
99
+ pip install mdinterface[all]
100
+ ```
101
+
102
+ ## Usage
103
+
104
+ Creating a new Specie (with its topology attributes) is as simple as doing:
105
+
106
+ ```python
107
+ #%% Make a specie, and use LigParGen to estimate FF parameters
108
+ my_specie = Specie("CH3ONO", ligpargen=True) # molecule is in ASE database
109
+
110
+ # make a specie from any ASE readable file
111
+ my_specie = Specie("methylnitrite.xyz", ligpargen=True)
112
+
113
+ # convert specie to mdanalysis universe (and all the attributes!)
114
+ my_specie.to_universe()
115
+
116
+ ```
117
+
118
+ Please, check the files in [examples](mdinterface/examples/) to learn how to use the package. Including setting up simulation boxes, creating polymers, perform RESP analysis and much more!
119
+
120
+ ## Roadmap
121
+
122
+ 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.
123
+
124
+ ## Questions & Issues
125
+
126
+ 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.3.0'
10
+ __date__ = '17 Jul. 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