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.
- mdinterface-1.3.0/PKG-INFO +158 -0
- mdinterface-1.3.0/README.md +126 -0
- mdinterface-1.3.0/mdinterface/__init__.py +20 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/build/__init__.py +1 -1
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/build/box.py +25 -1
- mdinterface-1.3.0/mdinterface/build/charges.py +8 -0
- mdinterface-1.1.0/mdinterface/build/polymer.py → mdinterface-1.3.0/mdinterface/build/polymerize.py +25 -15
- mdinterface-1.3.0/mdinterface/config.py +40 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/core/__init__.py +1 -0
- mdinterface-1.3.0/mdinterface/core/polymer.py +158 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/core/specie.py +136 -63
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/core/topology.py +31 -6
- mdinterface-1.3.0/mdinterface/database/__init__.py +12 -0
- mdinterface-1.3.0/mdinterface/database/graphene.py +35 -0
- mdinterface-1.3.0/mdinterface/database/ions.py +212 -0
- mdinterface-1.3.0/mdinterface/database/metals.py +56 -0
- mdinterface-1.3.0/mdinterface/database/molecules.py +65 -0
- mdinterface-1.3.0/mdinterface/externals/__init__.py +11 -0
- mdinterface-1.3.0/mdinterface/externals/ligpargen.py +91 -0
- mdinterface-1.3.0/mdinterface/externals/obabel.py +49 -0
- mdinterface-1.3.0/mdinterface/externals/pyscf.py +77 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/lammpswriter.py +31 -21
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/read.py +53 -40
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/simulationbox.py +141 -101
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/utils/__init__.py +1 -1
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/utils/auxiliary.py +53 -122
- mdinterface-1.3.0/mdinterface/utils/draw.py +40 -0
- mdinterface-1.3.0/mdinterface/utils/graphs.py +239 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/utils/map.py +18 -7
- mdinterface-1.3.0/mdinterface/utils/poisson.py +239 -0
- mdinterface-1.3.0/mdinterface/utils/units.py +42 -0
- mdinterface-1.3.0/mdinterface.egg-info/PKG-INFO +158 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface.egg-info/SOURCES.txt +18 -3
- mdinterface-1.3.0/mdinterface.egg-info/requires.txt +17 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/requirements.txt +2 -1
- {mdinterface-1.1.0 → mdinterface-1.3.0}/setup.cfg +14 -4
- mdinterface-1.1.0/PKG-INFO +0 -87
- mdinterface-1.1.0/README.md +0 -67
- mdinterface-1.1.0/mdinterface/__init__.py +0 -15
- mdinterface-1.1.0/mdinterface/utils/database.py +0 -147
- mdinterface-1.1.0/mdinterface.egg-info/PKG-INFO +0 -87
- mdinterface-1.1.0/mdinterface.egg-info/requires.txt +0 -5
- {mdinterface-1.1.0 → mdinterface-1.3.0}/LICENSE +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/MANIFEST.in +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/build/continuum2sim.py +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/__init__.py +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface/io/packmol.py +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface.egg-info/dependency_links.txt +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/mdinterface.egg-info/top_level.txt +0 -0
- {mdinterface-1.1.0 → mdinterface-1.3.0}/pyproject.toml +0 -0
- {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
|
+
[](https://pypi.org/project/mdinterface/) [](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
|
+
[](https://pypi.org/project/mdinterface/) [](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()
|
|
@@ -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
|
mdinterface-1.1.0/mdinterface/build/polymer.py → mdinterface-1.3.0/mdinterface/build/polymerize.py
RENAMED
|
@@ -12,12 +12,12 @@ import ase.build
|
|
|
12
12
|
|
|
13
13
|
#%%
|
|
14
14
|
|
|
15
|
-
def build_polymer(
|
|
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
|
-
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
43
|
-
end_idx = ase.build.connected_indices(
|
|
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 =
|
|
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 =
|
|
49
|
-
end_pos =
|
|
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(
|
|
64
|
+
is_connected = np.array(len(monomer)*[False])
|
|
56
65
|
is_connected[ini_idx] = True
|
|
57
66
|
is_connected[end_idx] = True
|
|
58
|
-
|
|
67
|
+
monomer.new_array("is_connected", is_connected)
|
|
59
68
|
|
|
60
|
-
# Start with a copy of the
|
|
61
|
-
sout =
|
|
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 =
|
|
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
|
|
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()
|
|
@@ -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
|