mdinterface 1.0.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.
- mdinterface-1.2.0/PKG-INFO +133 -0
- mdinterface-1.2.0/README.md +111 -0
- mdinterface-1.2.0/mdinterface/__init__.py +20 -0
- mdinterface-1.2.0/mdinterface/build/__init__.py +3 -0
- mdinterface-1.2.0/mdinterface/build/box.py +254 -0
- mdinterface-1.2.0/mdinterface/build/charges.py +8 -0
- mdinterface-1.2.0/mdinterface/build/continuum2sim.py +45 -0
- mdinterface-1.2.0/mdinterface/build/polymerize.py +95 -0
- mdinterface-1.2.0/mdinterface/config.py +40 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/core/__init__.py +1 -0
- mdinterface-1.2.0/mdinterface/core/polymer.py +158 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/core/specie.py +142 -81
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/core/topology.py +31 -6
- mdinterface-1.2.0/mdinterface/database/__init__.py +12 -0
- mdinterface-1.2.0/mdinterface/database/graphene.py +35 -0
- mdinterface-1.2.0/mdinterface/database/ions.py +188 -0
- mdinterface-1.2.0/mdinterface/database/metals.py +56 -0
- mdinterface-1.2.0/mdinterface/database/molecules.py +58 -0
- mdinterface-1.2.0/mdinterface/externals/__init__.py +11 -0
- mdinterface-1.2.0/mdinterface/externals/ligpargen.py +91 -0
- mdinterface-1.2.0/mdinterface/externals/obabel.py +49 -0
- mdinterface-1.2.0/mdinterface/externals/pyscf.py +74 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/io/lammpswriter.py +1 -1
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/io/packmol.py +1 -1
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/io/read.py +53 -40
- mdinterface-1.2.0/mdinterface/simulationbox.py +438 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/utils/__init__.py +2 -1
- mdinterface-1.2.0/mdinterface/utils/auxiliary.py +193 -0
- mdinterface-1.0.0/mdinterface/utils/auxiliary.py → mdinterface-1.2.0/mdinterface/utils/graphs.py +82 -129
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/utils/map.py +18 -7
- mdinterface-1.2.0/mdinterface.egg-info/PKG-INFO +133 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface.egg-info/SOURCES.txt +17 -1
- mdinterface-1.2.0/mdinterface.egg-info/requires.txt +7 -0
- mdinterface-1.2.0/requirements.txt +7 -0
- mdinterface-1.0.0/PKG-INFO +0 -76
- mdinterface-1.0.0/README.md +0 -57
- mdinterface-1.0.0/mdinterface/__init__.py +0 -10
- mdinterface-1.0.0/mdinterface/simulationbox.py +0 -621
- mdinterface-1.0.0/mdinterface/utils/database.py +0 -111
- mdinterface-1.0.0/mdinterface.egg-info/PKG-INFO +0 -76
- mdinterface-1.0.0/mdinterface.egg-info/requires.txt +0 -4
- mdinterface-1.0.0/requirements.txt +0 -4
- {mdinterface-1.0.0 → mdinterface-1.2.0}/LICENSE +0 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/MANIFEST.in +0 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface/io/__init__.py +0 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface.egg-info/dependency_links.txt +0 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/mdinterface.egg-info/top_level.txt +0 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/pyproject.toml +0 -0
- {mdinterface-1.0.0 → mdinterface-1.2.0}/setup.cfg +0 -0
- {mdinterface-1.0.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
|
+
[](https://pypi.org/project/mdinterface/) [](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
|
+
[](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 [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()
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Tue Jan 14 10:23:57 2025
|
|
5
|
+
|
|
6
|
+
@author: roncofaber
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from mdinterface.io.packmol import header, box_place, fix_place
|
|
10
|
+
from mdinterface.build.continuum2sim import discretize_concentration
|
|
11
|
+
|
|
12
|
+
import MDAnalysis as mda
|
|
13
|
+
from ase import units
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
|
|
19
|
+
#%%
|
|
20
|
+
|
|
21
|
+
def make_solvent_box(species, solvent, ions, volume, density, nions, concentration,
|
|
22
|
+
conmodel, ion_pos):
|
|
23
|
+
|
|
24
|
+
# make sure info is sound
|
|
25
|
+
assert not( nions is not None and concentration is not None),\
|
|
26
|
+
"'nions' and 'concentration' cannot both be not None"
|
|
27
|
+
|
|
28
|
+
# convert concentration to number of ions
|
|
29
|
+
if concentration is not None:
|
|
30
|
+
nions = int(concentration*np.prod(volume)*units.mol/((units.m/10)**3))
|
|
31
|
+
|
|
32
|
+
# define instructions for packmol
|
|
33
|
+
instructions = []
|
|
34
|
+
|
|
35
|
+
# populate ions
|
|
36
|
+
if (conmodel is not None) or (nions is not None and ions is not None):
|
|
37
|
+
ion_instr = populate_with_ions(ions, nions, volume, ion_pos=ion_pos,
|
|
38
|
+
conmodel=conmodel)
|
|
39
|
+
instructions.extend(ion_instr)
|
|
40
|
+
|
|
41
|
+
# add solvent
|
|
42
|
+
if solvent is not None:
|
|
43
|
+
solvent_volume = 1e-24*np.prod(volume)
|
|
44
|
+
mass = solvent.atoms.masses.sum()
|
|
45
|
+
|
|
46
|
+
# number of solvent molecules
|
|
47
|
+
nummols = int(units.mol*density*(1.0/mass)*solvent_volume)
|
|
48
|
+
|
|
49
|
+
instructions.append([solvent, nummols, "box"])
|
|
50
|
+
|
|
51
|
+
# generate universe file
|
|
52
|
+
universe = populate_box(volume, instructions)
|
|
53
|
+
|
|
54
|
+
if universe is None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
# Create a dictionary for quick lookup of species by residue name
|
|
58
|
+
species_dict = {specie.residues.resnames[0]: specie for specie in species}
|
|
59
|
+
|
|
60
|
+
alist = []
|
|
61
|
+
for res in universe.residues:
|
|
62
|
+
resname = res.resname
|
|
63
|
+
if resname in species_dict:
|
|
64
|
+
nmol = species_dict[resname].copy()
|
|
65
|
+
nmol.atoms.positions = res.atoms.positions
|
|
66
|
+
alist.append(nmol.atoms)
|
|
67
|
+
|
|
68
|
+
solution = mda.Merge(*alist)
|
|
69
|
+
solution.dimensions = volume + [90,90,90]
|
|
70
|
+
|
|
71
|
+
return solution
|
|
72
|
+
|
|
73
|
+
# populate a box with solvent and ions
|
|
74
|
+
def populate_box(volume, instructions, input_file="input_packmol.in",
|
|
75
|
+
output_file="system.pdb"):
|
|
76
|
+
|
|
77
|
+
if not instructions:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
# check volume
|
|
81
|
+
assert len(volume) == 3, "Check volume!"
|
|
82
|
+
|
|
83
|
+
# generate box boundaries with 1 AA padding
|
|
84
|
+
box = np.concatenate(([1,1,1], np.asarray(volume)-1)).tolist()
|
|
85
|
+
|
|
86
|
+
tmp_files = ["packmol.log", "input_packmol.in", "system.pdb"]
|
|
87
|
+
with open(input_file, "w") as fout:
|
|
88
|
+
|
|
89
|
+
fout.write(header.format(output_file, np.random.randint(100000)))
|
|
90
|
+
|
|
91
|
+
for cc, instruction in enumerate(instructions):
|
|
92
|
+
|
|
93
|
+
# unpack instructions
|
|
94
|
+
mol = instruction[0]
|
|
95
|
+
rep = instruction[1]
|
|
96
|
+
typ = instruction[2]
|
|
97
|
+
|
|
98
|
+
if isinstance(rep, int):
|
|
99
|
+
if not rep:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
if typ == "box": # normal add
|
|
103
|
+
fout.write(box_place.format(cc, rep, " ".join(map(str, box))))
|
|
104
|
+
|
|
105
|
+
elif typ == "fixed": # coordinate -> fixed point
|
|
106
|
+
fout.write(fix_place.format(cc, *rep))
|
|
107
|
+
|
|
108
|
+
elif typ == "zfixed": # small bin to use in CM
|
|
109
|
+
tbox = box.copy()
|
|
110
|
+
tbox[2] = tbox[2] - mol.estimate_specie_radius()
|
|
111
|
+
tbox[5] = tbox[5] + mol.estimate_specie_radius()
|
|
112
|
+
fout.write(box_place.format(cc, 1, " ".join(map(str, tbox))))
|
|
113
|
+
mol = mol.to_universe()
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
raise "Wrong instructions"
|
|
117
|
+
|
|
118
|
+
# write tmp pdb file and store info
|
|
119
|
+
mol.atoms.write("mol_{}.pdb".format(cc))
|
|
120
|
+
tmp_files.append("mol_{}.pdb".format(cc))
|
|
121
|
+
|
|
122
|
+
# run packmol
|
|
123
|
+
try:
|
|
124
|
+
subprocess.run(['packmol < {} > packmol.log'.format(input_file)],
|
|
125
|
+
shell=True, check=True, text=True)
|
|
126
|
+
|
|
127
|
+
except:
|
|
128
|
+
print("WARNING: packmol might not have worked, check system.")
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
universe = mda.Universe(output_file)
|
|
132
|
+
except:
|
|
133
|
+
universe = None
|
|
134
|
+
|
|
135
|
+
# remove temp mol files and packmol files
|
|
136
|
+
subprocess.call(['rm'] + tmp_files)
|
|
137
|
+
|
|
138
|
+
return universe
|
|
139
|
+
|
|
140
|
+
# generate a slab from a unit cell
|
|
141
|
+
def make_interface_slab(interface_uc, xsize, ysize, layers=1):
|
|
142
|
+
|
|
143
|
+
if layers == 0 or interface_uc is None:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
xrep = int(np.round(xsize/interface_uc.atoms.get_cell()[0][0]))
|
|
147
|
+
yrep = int(np.round(ysize/interface_uc.atoms.get_cell()[1][1]))
|
|
148
|
+
|
|
149
|
+
slab = interface_uc.copy()
|
|
150
|
+
|
|
151
|
+
if not np.isclose(np.dot(slab.atoms.cell[0], [1,0,0]), slab.atoms.cell[0][0]):
|
|
152
|
+
xrep +=1
|
|
153
|
+
print("WARNING: check interface if pattern matches")
|
|
154
|
+
|
|
155
|
+
if not np.isclose(np.dot(slab.atoms.cell[1], [0,1,0]), slab.atoms.cell[1][1]):
|
|
156
|
+
yrep +=1
|
|
157
|
+
print("WARNING: check interface if pattern matches")
|
|
158
|
+
|
|
159
|
+
slab.repeat((xrep, yrep, 1), make_cubic=True)
|
|
160
|
+
|
|
161
|
+
if layers > 1: # helps with indexing
|
|
162
|
+
slab.repeat([1,1,layers])
|
|
163
|
+
|
|
164
|
+
slab.atoms.center()
|
|
165
|
+
# slab.atoms.rattle()
|
|
166
|
+
|
|
167
|
+
return slab
|
|
168
|
+
|
|
169
|
+
#THANKS CHATGPT (but mostly me tbh)
|
|
170
|
+
def populate_with_ions(ions, nions, volume, ion_pos=False, conmodel=None):
|
|
171
|
+
def place_ion(ion, volume, ion_coords, ion_radii, zpos=None, max_attempts=100):
|
|
172
|
+
ion_radius = ion.estimate_specie_radius()
|
|
173
|
+
for _ in range(max_attempts):
|
|
174
|
+
new_coord = ion_radius + 1 + np.random.rand(3) * (volume - 2 * (ion_radius + 1))
|
|
175
|
+
if zpos is not None:
|
|
176
|
+
new_coord[2] = zpos
|
|
177
|
+
|
|
178
|
+
if not ion_coords or np.all(np.linalg.norm(ion_coords - new_coord, axis=1) >= np.array(ion_radii) + ion_radius + 1):
|
|
179
|
+
return new_coord
|
|
180
|
+
print(f"Warning: Failed to place ion {ion} after {max_attempts} attempts")
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def place_ions_conmodel(ions, conmodel, volume, max_attempts=100):
|
|
184
|
+
instructions = []
|
|
185
|
+
ion_coords = []
|
|
186
|
+
ion_radii = []
|
|
187
|
+
for cc, ion in enumerate(ions):
|
|
188
|
+
z_coords, conc_profile = conmodel[cc]
|
|
189
|
+
z_positions = discretize_concentration(ion, conc_profile, z_coords, volume)
|
|
190
|
+
for zpos in z_positions:
|
|
191
|
+
new_coord = place_ion(ion, volume, ion_coords, ion_radii, zpos=zpos,
|
|
192
|
+
max_attempts=max_attempts)
|
|
193
|
+
if new_coord is not None:
|
|
194
|
+
ion_coords.append(new_coord)
|
|
195
|
+
ion_radii.append(ion.estimate_specie_radius())
|
|
196
|
+
instructions.append((ion.to_universe(), new_coord, "fixed"))
|
|
197
|
+
return instructions
|
|
198
|
+
|
|
199
|
+
def place_ions_random(ions, nions, volume, to_center, max_attempts=100):
|
|
200
|
+
instructions = []
|
|
201
|
+
ion_coords = []
|
|
202
|
+
ion_radii = []
|
|
203
|
+
for cc, ion in enumerate(ions):
|
|
204
|
+
nrep = nions if isinstance(nions, int) else nions[cc]
|
|
205
|
+
for _ in range(nrep):
|
|
206
|
+
new_coord = volume / 2 if to_center else place_ion(ion, volume,
|
|
207
|
+
ion_coords,
|
|
208
|
+
ion_radii,
|
|
209
|
+
max_attempts=max_attempts)
|
|
210
|
+
if new_coord is not None:
|
|
211
|
+
ion_coords.append(new_coord)
|
|
212
|
+
ion_radii.append(ion.estimate_specie_radius())
|
|
213
|
+
instructions.append((ion.to_universe(), new_coord, "fixed"))
|
|
214
|
+
return instructions
|
|
215
|
+
|
|
216
|
+
max_attempts = 100 # Limit placement attempts to avoid infinite loop
|
|
217
|
+
volume = np.array(volume)
|
|
218
|
+
|
|
219
|
+
if conmodel is not None:
|
|
220
|
+
assert len(conmodel) == len(ions), "Need one profile per specie"
|
|
221
|
+
return place_ions_conmodel(ions, conmodel, volume, max_attempts=max_attempts)
|
|
222
|
+
|
|
223
|
+
if ion_pos == "box":
|
|
224
|
+
return [(ion.to_universe(), nions if isinstance(nions, int) else nions[cc], "box") for cc, ion in enumerate(ions)]
|
|
225
|
+
|
|
226
|
+
to_center = ion_pos == "center"
|
|
227
|
+
if ion_pos == "left":
|
|
228
|
+
volume[2] /= 2
|
|
229
|
+
|
|
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,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Tue Jan 14 10:34:26 2025
|
|
5
|
+
|
|
6
|
+
@author: roncofaber
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ase import units
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy import integrate
|
|
12
|
+
|
|
13
|
+
#%%
|
|
14
|
+
# return a discrete concentration profile given a "continuum" conc. profile
|
|
15
|
+
# for a specie. Return positions of the bins where ion should be.
|
|
16
|
+
def discretize_concentration(specie, conc_profile, z_coords, volume):
|
|
17
|
+
|
|
18
|
+
xsize, ysize, zsize = volume
|
|
19
|
+
|
|
20
|
+
conv = units.mol/1e27
|
|
21
|
+
area = xsize*ysize
|
|
22
|
+
|
|
23
|
+
# integrate conc. prof. over area
|
|
24
|
+
cumsum = integrate.cumulative_trapezoid(conc_profile, z_coords, initial=0, axis=0)
|
|
25
|
+
cumsum_conv = cumsum*conv*area
|
|
26
|
+
|
|
27
|
+
# generate bins (1 atom in each one) for K
|
|
28
|
+
Nions = 0
|
|
29
|
+
bins = []
|
|
30
|
+
for ii, con in enumerate(cumsum_conv):
|
|
31
|
+
if con > np.maximum(cumsum_conv.max()/100, Nions):
|
|
32
|
+
Nions += 1
|
|
33
|
+
bins.append(z_coords[ii])
|
|
34
|
+
|
|
35
|
+
# now find middle point of bins
|
|
36
|
+
z_pos = []
|
|
37
|
+
for ii, pos in enumerate(bins[:-1]):
|
|
38
|
+
z_pos.append((bins[ii] + bins[ii+1])/2)
|
|
39
|
+
|
|
40
|
+
# add one last ion
|
|
41
|
+
if cumsum_conv.max() % 1 > 0.5:
|
|
42
|
+
z_pos.append((z_coords.max()+bins[-1])/2)
|
|
43
|
+
Nions += 1
|
|
44
|
+
|
|
45
|
+
return z_pos
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Tue Oct 22 17:17:30 2024
|
|
5
|
+
|
|
6
|
+
@author: roncofaber
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import ase
|
|
11
|
+
import ase.build
|
|
12
|
+
|
|
13
|
+
#%%
|
|
14
|
+
|
|
15
|
+
def build_polymer(monomer, substitute, nrep, start_end_idxs=None, target_distance=1.600):
|
|
16
|
+
"""
|
|
17
|
+
Build a polymer by replicating a monomer structure and replacing 'X' atoms with a substitute.
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
- monomer (ase.Atoms): The monomer structure as an ASE Atoms object.
|
|
21
|
+
- substitute (str): Chemical symbol to replace 'X' with after replication.
|
|
22
|
+
- nrep (int): Number of times to replicate the monomer.
|
|
23
|
+
- target_distance (float, optional): Desired bond distance at the joining points (in angstroms).
|
|
24
|
+
If None, the original distance between 'X' atoms is used.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
- sout (ase.Atoms): The replicated and modified polymer structure.
|
|
28
|
+
"""
|
|
29
|
+
|
|
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)
|
|
40
|
+
|
|
41
|
+
# Check if nrep is at least 1
|
|
42
|
+
if nrep < 1:
|
|
43
|
+
raise ValueError("nrep must be at least 1")
|
|
44
|
+
|
|
45
|
+
# Find the indices of the 'X' atoms
|
|
46
|
+
x_idxs = np.where(np.array(monomer.get_chemical_symbols()) == "X")[0]
|
|
47
|
+
if len(x_idxs) != 2:
|
|
48
|
+
raise ValueError("The monomer should contain exactly two 'X' atoms.")
|
|
49
|
+
|
|
50
|
+
# Find atoms connected to X
|
|
51
|
+
ini_idx = ase.build.connected_indices(monomer, x_idxs[0])[1]
|
|
52
|
+
end_idx = ase.build.connected_indices(monomer, x_idxs[1])[1]
|
|
53
|
+
|
|
54
|
+
bnd_vec = monomer.get_distance(end_idx, x_idxs[1], vector=True)
|
|
55
|
+
bnd_vec = target_distance * bnd_vec / np.linalg.norm(bnd_vec)
|
|
56
|
+
|
|
57
|
+
ini_pos = monomer.get_positions()[ini_idx]
|
|
58
|
+
end_pos = monomer.get_positions()[end_idx] + bnd_vec
|
|
59
|
+
|
|
60
|
+
# Get the vector between the two 'X' atoms
|
|
61
|
+
X_vec = end_pos - ini_pos
|
|
62
|
+
|
|
63
|
+
# remember connecting points
|
|
64
|
+
is_connected = np.array(len(monomer)*[False])
|
|
65
|
+
is_connected[ini_idx] = True
|
|
66
|
+
is_connected[end_idx] = True
|
|
67
|
+
monomer.new_array("is_connected", is_connected)
|
|
68
|
+
|
|
69
|
+
# Start with a copy of the monomer
|
|
70
|
+
sout = monomer.copy()
|
|
71
|
+
sout.new_array("mon_id", np.array(len(sout)*[0]))
|
|
72
|
+
|
|
73
|
+
# Replicate the monomer
|
|
74
|
+
if nrep > 1:
|
|
75
|
+
del sout[x_idxs[1]] # Remove the second 'X' atom in the first monomer
|
|
76
|
+
|
|
77
|
+
for ii in range(nrep - 1):
|
|
78
|
+
sadd = monomer.copy()
|
|
79
|
+
|
|
80
|
+
if ii < nrep - 2:
|
|
81
|
+
del sadd[x_idxs] # Remove both 'X' atoms in the intermediate monomers
|
|
82
|
+
else:
|
|
83
|
+
del sadd[x_idxs[0]] # Remove the first 'X' atom in the last monomer
|
|
84
|
+
|
|
85
|
+
sadd.translate((ii + 1) * X_vec)
|
|
86
|
+
sadd.set_array("mon_id", np.array(len(sadd)*[ii+1]))
|
|
87
|
+
sout += sadd
|
|
88
|
+
|
|
89
|
+
# Replace 'X' with the substitute
|
|
90
|
+
symbols = np.array(sout.get_chemical_symbols())
|
|
91
|
+
symbols[symbols == "X"] = substitute
|
|
92
|
+
sout.set_chemical_symbols(symbols)
|
|
93
|
+
sout.center()
|
|
94
|
+
|
|
95
|
+
return sout
|