mdinterface 1.2.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 (45) hide show
  1. {mdinterface-1.2.0 → mdinterface-1.3.0}/PKG-INFO +43 -18
  2. mdinterface-1.2.0/mdinterface.egg-info/PKG-INFO → mdinterface-1.3.0/README.md +28 -35
  3. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/__init__.py +2 -2
  4. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/core/specie.py +11 -4
  5. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/database/ions.py +28 -4
  6. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/database/molecules.py +10 -3
  7. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/externals/pyscf.py +5 -2
  8. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/io/lammpswriter.py +30 -20
  9. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/simulationbox.py +16 -7
  10. mdinterface-1.3.0/mdinterface/utils/draw.py +40 -0
  11. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/utils/graphs.py +64 -40
  12. mdinterface-1.3.0/mdinterface/utils/poisson.py +239 -0
  13. mdinterface-1.3.0/mdinterface/utils/units.py +42 -0
  14. mdinterface-1.2.0/README.md → mdinterface-1.3.0/mdinterface.egg-info/PKG-INFO +60 -13
  15. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface.egg-info/SOURCES.txt +4 -1
  16. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface.egg-info/requires.txt +11 -1
  17. {mdinterface-1.2.0 → mdinterface-1.3.0}/requirements.txt +0 -1
  18. {mdinterface-1.2.0 → mdinterface-1.3.0}/setup.cfg +14 -4
  19. {mdinterface-1.2.0 → mdinterface-1.3.0}/LICENSE +0 -0
  20. {mdinterface-1.2.0 → mdinterface-1.3.0}/MANIFEST.in +0 -0
  21. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/build/__init__.py +0 -0
  22. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/build/box.py +0 -0
  23. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/build/charges.py +0 -0
  24. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/build/continuum2sim.py +0 -0
  25. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/build/polymerize.py +0 -0
  26. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/config.py +0 -0
  27. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/core/__init__.py +0 -0
  28. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/core/polymer.py +0 -0
  29. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/core/topology.py +0 -0
  30. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/database/__init__.py +0 -0
  31. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/database/graphene.py +0 -0
  32. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/database/metals.py +0 -0
  33. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/externals/__init__.py +0 -0
  34. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/externals/ligpargen.py +0 -0
  35. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/externals/obabel.py +0 -0
  36. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/io/__init__.py +0 -0
  37. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/io/packmol.py +0 -0
  38. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/io/read.py +0 -0
  39. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/utils/__init__.py +0 -0
  40. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/utils/auxiliary.py +0 -0
  41. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface/utils/map.py +0 -0
  42. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface.egg-info/dependency_links.txt +0 -0
  43. {mdinterface-1.2.0 → mdinterface-1.3.0}/mdinterface.egg-info/top_level.txt +0 -0
  44. {mdinterface-1.2.0 → mdinterface-1.3.0}/pyproject.toml +0 -0
  45. {mdinterface-1.2.0 → mdinterface-1.3.0}/setup.py +0 -0
@@ -1,24 +1,34 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mdinterface
3
- Version: 1.2.0
4
- Summary: mdinterface: A package for building interface systems in Molecular Dynamics simulations.
3
+ Version: 1.3.0
4
+ Summary: Build Interface Systems for Molecular Dynamics Simulations
5
5
  Home-page: https://gitlab.com/roncofaber/mdinterface
6
+ Author: Fabrice Roncoroni
7
+ Author-email: fabrice.roncoroni@gmail.com
6
8
  License: Apache-2.0
7
9
  Classifier: Programming Language :: Python :: 3
8
10
  Classifier: License :: OSI Approved :: Apache Software License
9
11
  Classifier: Operating System :: OS Independent
10
12
  Classifier: Topic :: Software Development
11
13
  Classifier: Topic :: Scientific/Engineering
12
- Requires-Python: >=3.6
14
+ Requires-Python: >=3.8
13
15
  Description-Content-Type: text/markdown
14
16
  License-File: LICENSE
15
17
  Requires-Dist: mdanalysis
16
18
  Requires-Dist: ase
17
19
  Requires-Dist: numpy
18
20
  Requires-Dist: networkx
19
- Requires-Dist: libarvo
20
21
  Requires-Dist: platformdirs
21
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
22
32
 
23
33
  <div style="display: flex; align-items: center;">
24
34
  <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
@@ -37,16 +47,17 @@ Using `mdinterface` you can:
37
47
 
38
48
  - Create layered simulation boxes with solvents, solutes, and interface slabs.
39
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.
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).
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).
41
51
  - Generate polymer chains of any length from a starting monomer.
42
52
  - 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!
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.
44
55
 
45
56
  ## Requirements
46
57
 
47
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.
48
59
 
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`:
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`:
50
61
 
51
62
  ```bash
52
63
  conda install -c conda-forge packmol
@@ -56,7 +67,7 @@ conda install -c conda-forge packmol
56
67
 
57
68
  #### Automatic OPLS-AA force field generator with LigParGen
58
69
 
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`.
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`.
60
71
 
61
72
  You can do this manually every time, or just add the path to the `config.ini` file to your config directory:
62
73
 
@@ -70,7 +81,7 @@ The config directory is found using [`platformdirs`](https://pypi.org/project/pl
70
81
 
71
82
  ### RESP charge analysis with PySCF
72
83
 
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).
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.
74
85
 
75
86
  ## Installation
76
87
 
@@ -92,25 +103,32 @@ Clone the repository in the desired location:
92
103
  git clone git@gitlab.com:roncofaber/mdinterface.git
93
104
  ```
94
105
 
95
- Install the required packages:
106
+ Install the package:
96
107
 
97
108
  ```bash
98
109
  cd mdinterface
99
- conda install -c conda-forge --file requirements.txt
110
+ pip install .
100
111
  ```
101
112
 
102
- Install the package with pip:
113
+ ### Install a development environment
114
+
115
+ If you plan of making changes, clone the package and add it to your development environment with:
103
116
 
104
117
  ```bash
105
- pip install .
118
+ pip install --no-build-isolation -e .
106
119
  ```
107
120
 
108
- ### Install a development environment
121
+ ### Install optional packages
109
122
 
110
- If you plan of making changes, clone the package and install the requirements but then add it to your development environment with:
123
+ You can install optional dependencies with the following commands:
111
124
 
112
125
  ```bash
113
- pip install --no-build-isolation --no-deps -e .
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]
114
132
  ```
115
133
 
116
134
  ## Usage
@@ -120,9 +138,16 @@ Creating a new Specie (with its topology attributes) is as simple as doing:
120
138
  ```python
121
139
  #%% Make a specie, and use LigParGen to estimate FF parameters
122
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
+
123
148
  ```
124
149
 
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.
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!
126
151
 
127
152
  ## Roadmap
128
153
 
@@ -1,25 +1,3 @@
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
1
  <div style="display: flex; align-items: center;">
24
2
  <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
25
3
  <div style="display: flex; flex-direction: column;">
@@ -37,16 +15,17 @@ Using `mdinterface` you can:
37
15
 
38
16
  - Create layered simulation boxes with solvents, solutes, and interface slabs.
39
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.
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).
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).
41
19
  - Generate polymer chains of any length from a starting monomer.
42
20
  - 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!
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.
44
23
 
45
24
  ## Requirements
46
25
 
47
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.
48
27
 
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`:
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`:
50
29
 
51
30
  ```bash
52
31
  conda install -c conda-forge packmol
@@ -56,7 +35,7 @@ conda install -c conda-forge packmol
56
35
 
57
36
  #### Automatic OPLS-AA force field generator with LigParGen
58
37
 
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`.
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`.
60
39
 
61
40
  You can do this manually every time, or just add the path to the `config.ini` file to your config directory:
62
41
 
@@ -70,7 +49,7 @@ The config directory is found using [`platformdirs`](https://pypi.org/project/pl
70
49
 
71
50
  ### RESP charge analysis with PySCF
72
51
 
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).
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.
74
53
 
75
54
  ## Installation
76
55
 
@@ -92,25 +71,32 @@ Clone the repository in the desired location:
92
71
  git clone git@gitlab.com:roncofaber/mdinterface.git
93
72
  ```
94
73
 
95
- Install the required packages:
74
+ Install the package:
96
75
 
97
76
  ```bash
98
77
  cd mdinterface
99
- conda install -c conda-forge --file requirements.txt
78
+ pip install .
100
79
  ```
101
80
 
102
- Install the package with pip:
81
+ ### Install a development environment
82
+
83
+ If you plan of making changes, clone the package and add it to your development environment with:
103
84
 
104
85
  ```bash
105
- pip install .
86
+ pip install --no-build-isolation -e .
106
87
  ```
107
88
 
108
- ### Install a development environment
89
+ ### Install optional packages
109
90
 
110
- If you plan of making changes, clone the package and install the requirements but then add it to your development environment with:
91
+ You can install optional dependencies with the following commands:
111
92
 
112
93
  ```bash
113
- pip install --no-build-isolation --no-deps -e .
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]
114
100
  ```
115
101
 
116
102
  ## Usage
@@ -120,9 +106,16 @@ Creating a new Specie (with its topology attributes) is as simple as doing:
120
106
  ```python
121
107
  #%% Make a specie, and use LigParGen to estimate FF parameters
122
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
+
123
116
  ```
124
117
 
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.
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!
126
119
 
127
120
  ## Roadmap
128
121
 
@@ -6,8 +6,8 @@ Initially developed to construct electrolyte/electrode interfaces, it is also we
6
6
 
7
7
  """
8
8
 
9
- __version__ = '1.2.0'
10
- __date__ = '28 Mar. 2025'
9
+ __version__ = '1.3.0'
10
+ __date__ = '17 Jul. 2025'
11
11
  __author__ = 'Fabrice Roncoroni'
12
12
  __all__ = ['SimulationBox', "Specie", "Polymer"]
13
13
 
@@ -20,6 +20,7 @@ from mdinterface.utils.auxiliary import as_list, find_smallest_missing, round_li
20
20
  from mdinterface.externals import run_ligpargen, run_OBChargeModel, calculate_RESP_charges
21
21
  from mdinterface.utils.graphs import molecule_to_graph, find_unique_paths_of_length,\
22
22
  find_improper_idxs,find_relevant_distances, find_atom_types
23
+ from mdinterface.utils.draw import draw_bond_markers
23
24
 
24
25
  import copy
25
26
  import numpy as np
@@ -176,7 +177,7 @@ class Specie(object):
176
177
  def _atom_types_from_lj(self, lj):
177
178
 
178
179
  # use function to retrieve IDs
179
- atom_type_ids, types_map = find_atom_types(self.atoms, max_depth=1)
180
+ atom_type_ids, types_map = find_atom_types(self.graph, max_depth=1)
180
181
 
181
182
  atom_types = []
182
183
  for atom_id in atom_type_ids:
@@ -220,7 +221,7 @@ class Specie(object):
220
221
 
221
222
  self._atoms = atoms
222
223
  self._graph = molecule_to_graph(atoms, cutoff_scale=cutoff)
223
-
224
+
224
225
  return
225
226
 
226
227
  def _update_topology(self):
@@ -507,15 +508,21 @@ class Specie(object):
507
508
  centers=centers, Ninv=Ninv)
508
509
  return unique_pairs_list
509
510
 
510
- def plot_graph(self, **kwargs):
511
+ def plot_graph(self, show_bonds=False):
511
512
 
512
513
  colors = [jmol_colors[a.number] for a in self.atoms]
513
514
 
515
+ # node_pos = nx.spring_layout(self.graph, k=1.5/np.sqrt(self.graph.order()))
516
+ node_pos = nx.kamada_kawai_layout(self.graph)
517
+
514
518
  fig, ax = plt.subplots()
515
- nx.draw(self.graph, with_labels=True, node_color=colors,
519
+ nx.draw(self.graph, pos=node_pos, with_labels=True, node_color=colors,
516
520
  node_size=1000, edge_color='black', linewidths=2, font_size=15,
517
521
  edgecolors="black", ax=ax, width=2)
518
522
 
523
+ if show_bonds:
524
+ draw_bond_markers(ax, self, node_pos, jmol_colors)
525
+
519
526
  plt.show()
520
527
 
521
528
  return
@@ -20,7 +20,8 @@ import ase
20
20
  # Jorgensen: https://pubs.acs.org/doi/10.1021/ct600252r
21
21
  # Cheatham : https://pubs.acs.org/doi/10.1021/jp8001614
22
22
  # Sengupta : https://pubs.acs.org/doi/10.1021/acs.jcim.0c01390 (12-6 HFE param)
23
- # Dang :
23
+ # Dang : https://doi.org/10.1063/1.462555, https://doi.org/10.1063/1.466363
24
+
24
25
  ions_parameters = {
25
26
  "F": {
26
27
  "charge": -1.0,
@@ -28,7 +29,7 @@ ions_parameters = {
28
29
  "jorgensen": [0.71000, 3.0500],
29
30
  "cheatham" : [0.00336, 4.1035],
30
31
  "sengupta" : [0.24140, 3.2678],
31
- "dang" : [0.18000, 3.1180]
32
+ "dang" : [0.20000, 3.1680] # was [0.18000, 3.1180]
32
33
  }
33
34
  },
34
35
  "Cl": {
@@ -107,6 +108,8 @@ ions_parameters = {
107
108
 
108
109
  def lookup_parameters(element, ffield):
109
110
  try:
111
+ if ffield.lower() == "merz":
112
+ ffield = "sengupta"
110
113
  charge = ions_parameters[element]["charge"]
111
114
  lj = ions_parameters[element]["ffield"][ffield.lower()]
112
115
  return charge, lj
@@ -125,7 +128,7 @@ class Ion(Specie):
125
128
  **kwargs: Additional keyword arguments to pass to the Specie superclass.
126
129
  """
127
130
 
128
- def __init__(self, element, ffield="Jorgensen", chg_scaling=0.8, **kwargs):
131
+ def __init__(self, element, ffield="Jorgensen", chg_scaling=1.0, **kwargs):
129
132
 
130
133
  charge, lj = lookup_parameters(element, ffield)
131
134
  lj = {element: lj}
@@ -166,7 +169,9 @@ class Perchlorate(Specie):
166
169
  return
167
170
 
168
171
 
169
- # hydronium https://pubs.acs.org/doi/pdf/10.1021/jp036842c
172
+ # hydronium parameters:
173
+ # std: https://pubs.acs.org/doi/pdf/10.1021/jp036842c
174
+ # netz: https://refubium.fu-berlin.de/bitstream/handle/fub188/15473/1.4942771.pdf
170
175
  class Hydronium(Specie):
171
176
  def __init__(self, **kwargs):
172
177
 
@@ -186,3 +191,22 @@ class Hydronium(Specie):
186
191
 
187
192
  super().__init__(hyd, charges=charges, bonds=b1, angles=a1, lj=lj, **kwargs)
188
193
  return
194
+
195
+ # hydroxide parameters:
196
+ # netz: https://refubium.fu-berlin.de/bitstream/handle/fub188/15473/1.4942771.pdf
197
+ class Hydroxide(Specie):
198
+ def __init__(self, **kwargs):
199
+
200
+ # make ion by cheating and making NH3 first
201
+ hoh = ase.build.molecule("OH")
202
+
203
+ # bond is fixed in paper #FIXME used hydronium bond with r0=1
204
+ b1 = Bond("O", "H", kr=1085.9565/2, r0=1.000)
205
+
206
+ charges = [-1.000, 0.000]
207
+
208
+ # converted J to cal
209
+ lj = {"O": [0.01195, 3.8100], "H": [0.000, 0.000]}
210
+
211
+ super().__init__(hoh, charges=charges, bonds=b1, lj=lj, **kwargs)
212
+ return
@@ -19,15 +19,22 @@ class Water(Specie):
19
19
  a1 = Angle("H", "O", "H", kr=55, theta0=104.52)
20
20
  charges = [-0.83, 0.415, 0.415]
21
21
  lj = {"O": [0.102, 3.188], "H": [0.0, 1.0]}
22
+
23
+ if model.lower() == "charmm":
24
+ b1 = Bond("O", "H", kr=450, r0=0.9572)
25
+ a1 = Angle("H", "O", "H", kr=55, theta0=104.52)
26
+ charges = [-0.834, 0.417, 0.417]
27
+ lj = {"O": [0.1521, 3.1507], "H": [0.0460, 0.4]}
22
28
 
23
29
  super().__init__("H2O", charges=charges, bonds=b1, angles=a1, lj=lj, **kwargs)
24
30
  return
25
31
 
32
+ # 22 Apr. 2025 correction: all bond terms have been divider by 2:
26
33
  #oxygen https://pubs.acs.org/doi/10.1021/acs.jctc.0c01132 /!\: divide sig by 2**(1/6)
27
34
  class Oxygen(Specie):
28
35
  def __init__(self, **kwargs):
29
36
 
30
- b1 = Bond("O", "O", kr=1640.4, r0=1.2074)
37
+ b1 = Bond("O", "O", kr=1640.4/2, r0=1.2074)
31
38
  lj = {"O" : [0.1047, 2.9373]}
32
39
 
33
40
  super().__init__("O2", charges = 0.0, lj=lj, bonds=b1, **kwargs)
@@ -37,7 +44,7 @@ class Oxygen(Specie):
37
44
  class Hydrogen(Specie):
38
45
  def __init__(self, Hset="std", **kwargs):
39
46
 
40
- b1 = Bond("H", "H", kr=700, r0=0.7414)
47
+ b1 = Bond("H", "H", kr=700/2, r0=0.7414)
41
48
 
42
49
  if Hset.lower() == "std": # standard 12-6 set
43
50
  lj = {"H" : [0.0153, 2.5996]}
@@ -51,7 +58,7 @@ class Hydrogen(Specie):
51
58
  class Nitrogen(Specie):
52
59
  def __init__(self, **kwargs):
53
60
 
54
- b1 = Bond("N", "N", kr=3190, r0=1.0977)
61
+ b1 = Bond("N", "N", kr=3190/2, r0=1.0977)
55
62
  lj = {"N" : [0.0797, 3.2197]}
56
63
 
57
64
  super().__init__("N2", charges = 0.0, lj=lj, bonds=b1, **kwargs)
@@ -40,15 +40,18 @@ def calculate_RESP_charges(specie, basis='def2-svpd', xc="b3lyp", calc_type="RKS
40
40
 
41
41
  atoms = mole_to_ase(mol)
42
42
 
43
+ mol.cart = True # PySCF uses spherical basis by default
44
+
43
45
  # run calculation and calculate density matrix
44
46
  mf = make_pyscf_calculator(mol, xc=xc, calc_type=calc_type, gpu=gpu)
47
+
45
48
  mf.kernel()
46
49
  dm = mf.make_rdm1()
47
50
 
48
51
  # now, start RESP with ESP first!
49
52
 
50
53
  # ESP charge (do I need this?)
51
- q0 = esp.esp_solve(mol, dm)
54
+ # q0 = esp.esp_solve(mol, dm)
52
55
 
53
56
  # RESP charge // first stage fitting
54
57
  q1 = esp.resp_solve(mol, dm, maxit=maxit)
@@ -67,7 +70,7 @@ def calculate_RESP_charges(specie, basis='def2-svpd', xc="b3lyp", calc_type="RKS
67
70
  equal_constraints.append(tmp_idx.tolist())
68
71
 
69
72
  # RESP charge // second stage fitting
70
- q2 = esp.resp_solve(mol, dm, resp_a=1e-4,
73
+ q2 = esp.resp_solve(mol, dm, resp_a=5e-4, resp_b=0.1, tol=1e-7,
71
74
  sum_constraints=sum_constraints,
72
75
  equal_constraints=equal_constraints, maxit=maxit)
73
76
 
@@ -68,17 +68,16 @@ class DATAWriter(base.WriterBase):
68
68
  self.units['velocity'] = kwargs.pop('velocityunit',
69
69
  self.units['length']+'/'+self.units['time'])
70
70
 
71
- def _write_atoms(self, atoms, data):
71
+ def _write_atoms(self, atoms, data, atom_style):
72
72
  self.f.write('\n')
73
73
  self.f.write('Atoms\n')
74
74
  self.f.write('\n')
75
75
 
76
76
  try:
77
77
  charges = atoms.charges
78
+ has_charges = True
78
79
  except (NoDataError, AttributeError):
79
80
  has_charges = False
80
- else:
81
- has_charges = True
82
81
 
83
82
  indices = atoms.indices + 1
84
83
  i_l = len(str(indices.max()))
@@ -96,18 +95,26 @@ class DATAWriter(base.WriterBase):
96
95
  b_l = len(str(int(coordinates.max()))) + 1
97
96
  b_t = b_l + 6
98
97
 
99
- if has_charges:
100
- for index, moltag, atype, charge, coords in zip(indices, moltags,
101
- types, charges, coordinates):
98
+ if atom_style == "atomic":
99
+ for index, atype, coords in zip(indices, types, coordinates):
102
100
  x, y, z = coords
103
- self.f.write(f"{index:{i_l}d} {moltag:{m_l}d} {atype:{t_l}d} {charge: .7f}"
101
+ self.f.write(f"{index:{i_l}d} {atype:{t_l}d}"
104
102
  f" {x:> {b_t}.5f} {y:> {b_t}.5f} {z:> {b_t}.5f}\n")
105
- else:
106
- for index, moltag, atype, coords in zip(indices, moltags, types,
107
- coordinates):
108
- x, y, z = coords
109
- self.f.write(f"{index:{i_l}d} {moltag:{m_l}d} {atype:{t_l}d}"
110
- f" {x:> {b_t}.5f} {y:> {b_t}.5f} {z:> {b_t}.5f}\n")
103
+ elif atom_style == "full":
104
+
105
+ if not has_charges:
106
+ for index, moltag, atype, coords in zip(indices, moltags, types,
107
+ coordinates):
108
+ x, y, z = coords
109
+ self.f.write(f"{index:{i_l}d} {moltag:{m_l}d} {atype:{t_l}d}"
110
+ f" {x:> {b_t}.5f} {y:> {b_t}.5f} {z:> {b_t}.5f}\n")
111
+ elif has_charges:
112
+ for index, moltag, atype, charge, coords in zip(indices, moltags,
113
+ types, charges, coordinates):
114
+ x, y, z = coords
115
+ self.f.write(f"{index:{i_l}d} {moltag:{m_l}d} {atype:{t_l}d}"
116
+ f" {charge: .7f}"
117
+ f" {x:> {b_t}.5f} {y:> {b_t}.5f} {z:> {b_t}.5f}\n")
111
118
 
112
119
  def _write_velocities(self, atoms):
113
120
  self.f.write('\n')
@@ -172,7 +179,7 @@ class DATAWriter(base.WriterBase):
172
179
  self.f.write('\n')
173
180
 
174
181
  @requires('types', 'masses')
175
- def write(self, selection, frame=None):
182
+ def write(self, selection, frame=None, atom_style='full'):
176
183
  """Write selection at current trajectory frame to file.
177
184
 
178
185
  The sections for Atoms, Masses, Velocities, Bonds, Angles,
@@ -233,11 +240,14 @@ class DATAWriter(base.WriterBase):
233
240
  ('dihedral', 'dihedrals'), ('improper', 'impropers')]
234
241
 
235
242
  for btype, attr_name in attrs:
236
- features[btype] = atoms.__getattribute__(attr_name)
237
- self.f.write('{:>12d} {}\n'.format(len(features[btype]),
238
- attr_name))
239
- features[btype] = features[btype].atomgroup_intersection(
240
- atoms, strict=True)
243
+ try:
244
+ features[btype] = atoms.__getattribute__(attr_name)
245
+ self.f.write('{:>12d} {}\n'.format(len(features[btype]),
246
+ attr_name))
247
+ features[btype] = features[btype].atomgroup_intersection(
248
+ atoms, strict=True)
249
+ except:
250
+ pass
241
251
 
242
252
  self.f.write('\n')
243
253
 
@@ -251,7 +261,7 @@ class DATAWriter(base.WriterBase):
251
261
  self._write_dimensions(atoms.dimensions)
252
262
 
253
263
  self._write_masses(atoms)
254
- self._write_atoms(atoms, u.trajectory.ts.data)
264
+ self._write_atoms(atoms, u.trajectory.ts.data, atom_style)
255
265
  for attr in features.values():
256
266
  if attr is None or len(attr) == 0:
257
267
  continue
@@ -142,7 +142,7 @@ class SimulationBox():
142
142
  def make_simulation_box(self, xysize, layering, padding=1.5, to_ase=False,
143
143
  write_data=False, filename="data.lammps",
144
144
  center_electrode=False, layered=False, hijack=None,
145
- match_cell=False, remove_charges=False):
145
+ match_cell=False, atom_style="full", write_coeff=True):
146
146
 
147
147
  # define approximate cross_section
148
148
  assert len(xysize) == 2, "'xysize' should have length of 2 [xsize, ysize]"
@@ -210,7 +210,8 @@ class SimulationBox():
210
210
 
211
211
  # write data file
212
212
  if write_data:
213
- self.write_lammps_file(system, filename=filename, remove_charges=remove_charges)
213
+ self.write_lammps_file(system, filename=filename,
214
+ atom_style=atom_style, write_coeff=write_coeff)
214
215
 
215
216
  # convert to ase, or not
216
217
  if to_ase:
@@ -218,14 +219,22 @@ class SimulationBox():
218
219
  return system
219
220
 
220
221
  def write_lammps_file(self, system, write_coeff=True, filename="data.lammps",
221
- remove_charges=False):
222
+ atom_style="full"):
222
223
 
223
- if remove_charges:
224
- system.del_TopologyAttr("charges")
224
+ # just make sure we are not messing things up
225
+ system = system.copy()
226
+
227
+ # remove coefficients
228
+ if not write_coeff:
229
+ for attribute in ["bonds", "angles", "dihedrals", "impropers"]:
230
+ try:
231
+ system.del_TopologyAttr(attribute)
232
+ except:
233
+ pass
225
234
 
226
235
  # first write data file
227
236
  with DATAWriter(filename) as dt:
228
- dt.write(system.atoms)
237
+ dt.write(system.atoms, atom_style=atom_style)
229
238
 
230
239
  # now write coeff where they belong
231
240
  if write_coeff:
@@ -428,7 +437,7 @@ class SimulationBox():
428
437
  if not slabs:
429
438
  return xsize, ysize
430
439
 
431
- xsize_t, ysize_t = np.NaN, np.NaN
440
+ xsize_t, ysize_t = np.nan, np.nan
432
441
 
433
442
  for tslab in slabs:
434
443
  xi, yi, _ = self._get_size_from_slab(tslab)
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Mon Jul 14 18:55:16 2025
5
+
6
+ @author: roncofaber
7
+ """
8
+
9
+ import matplotlib.pyplot as plt
10
+
11
+ def draw_bond_markers(ax, mol, node_pos, jmol_colors):
12
+ # Assign marker and color for each bond type
13
+ bond_markers = ['o', 's', 'D', '^', 'v', '*', 'P', 'X']
14
+ bond_colors = ['red', 'blue', 'green', 'orange', 'purple', 'brown', 'cyan', 'magenta']
15
+ bondtype_to_marker = {}
16
+ bondtype_to_color = {}
17
+ for idx, bond in enumerate(mol._btype):
18
+ bondtype_to_marker[idx] = bond_markers[idx % len(bond_markers)]
19
+ bondtype_to_color[idx] = bond_colors[idx % len(bond_colors)]
20
+
21
+ # Draw markers at bond midpoints
22
+ for (a, b), btype_idx in zip(mol.bonds[0], mol.bonds[1]):
23
+ marker = bondtype_to_marker[btype_idx]
24
+ color = bondtype_to_color[btype_idx]
25
+ x0, y0 = node_pos[a]
26
+ x1, y1 = node_pos[b]
27
+ xm, ym = (x0 + x1) / 2, (y0 + y1) / 2
28
+ ax.scatter(xm, ym, marker=marker, color=color, s=100, zorder=5)
29
+
30
+ # Legend
31
+ legend_handles = []
32
+ for idx, bond in enumerate(mol._btype):
33
+ marker = bondtype_to_marker[idx]
34
+ color = bondtype_to_color[idx]
35
+ label = f"$k_r={bond.kr}$, $r_0={bond.r0}$"
36
+ h = plt.Line2D([0], [0], marker=marker, color='w', markerfacecolor=color, markersize=10, label=label, linestyle='None')
37
+ legend_handles.append(h)
38
+ ax.legend(handles=legend_handles, loc='best', fontsize=10, framealpha=1.0, edgecolor='k')
39
+
40
+ return