The purpose of this tool is to find atomic environments in crystal structur…"
42 | ]
43 | },
44 | "metadata": {},
45 | "output_type": "display_data"
46 | }
47 | ],
48 | "source": [
49 | "html_description = markdown.markdown(\"\"\"\n",
50 | "The purpose of this tool is to find atomic environments in crystal structures. These environments can then be used to define measures of similarity and collective variables for enhanced sampling simulations. The output from this tool can be used directly to create reference environments for the [EnvironmentSimilarity](https://www.plumed.org/doc-master/user-doc/html/_e_n_v_i_r_o_n_m_e_n_t_s_i_m_i_l_a_r_i_t_y.html) collective variable in [PLUMED](https://www.plumed.org/doc-master/user-doc/html/index.html). The tool can also be used to visualize chemical environments around an atom for general purposes.\n",
51 | "\"\"\")\n",
52 | "description=HTML(html_description)\n",
53 | "\n",
54 | "pre_accordion = widgets.Accordion()\n",
55 | "pre_accordion.children = [description]\n",
56 | "pre_accordion.set_title(0, 'Description')\n",
57 | "pre_accordion.selected_index = None\n",
58 | "pre_accordion"
59 | ]
60 | },
61 | {
62 | "cell_type": "markdown",
63 | "metadata": {},
64 | "source": [
65 | "Proceed through the four tabs sequentially."
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": 3,
71 | "metadata": {},
72 | "outputs": [
73 | {
74 | "data": {
75 | "application/vnd.jupyter.widget-view+json": {
76 | "model_id": "8e4a41d60b8b4350a0ec467ac5e3403d",
77 | "version_major": 2,
78 | "version_minor": 0
79 | },
80 | "text/plain": [
81 | "_ColormakerRegistry()"
82 | ]
83 | },
84 | "metadata": {},
85 | "output_type": "display_data"
86 | }
87 | ],
88 | "source": [
89 | "from environmentfinder import EnvironmentFinder\n",
90 | "# Define instance of class EnvironmentFinder\n",
91 | "MyEnvironmentFinder = EnvironmentFinder()"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": 4,
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "#############################\n",
101 | "# WIDGET 1: Choose and upload\n",
102 | "#############################\n",
103 | "\n",
104 | "# Define upload widget\n",
105 | "Widget1UploadConfiguration = widgets.FileUpload(\n",
106 | " accept='', # Accepted file extension e.g. '.txt', '.pdf', 'image/*', 'image/*,.pdf'\n",
107 | " multiple=False, # True to accept multiple files upload else False\n",
108 | " wait=True\n",
109 | ")\n",
110 | "\n",
111 | "examples = ('Ice Ih', 'examples/IceIh.pdb'),('Urea', 'examples/urea2.pdb'), ('Ga II', 'examples/Ga_II.vasp')\n",
112 | "widget_choose_filename = widgets.Dropdown(options=examples,description='Structure:')\n",
113 | "\n",
114 | "Widget1Out = widgets.Output()\n",
115 | "def chooseAndPlotConfigurationAltOutput(filename):\n",
116 | " Widget1Out.clear_output()\n",
117 | " with Widget1Out:\n",
118 | " MyEnvironmentFinder.chooseAndPlotConfiguration(filename)\n",
119 | " \n",
120 | "Widget1ExampleConfiguration = interactive(chooseAndPlotConfigurationAltOutput, filename=widget_choose_filename)\n",
121 | "\n",
122 | "# This function updates the dropdown list when a new file is uploaded\n",
123 | "def updateWidget1ExampleConfiguration(*args):\n",
124 | " uploaded_filename = Widget1UploadConfiguration.value[-1]['name']\n",
125 | " content = Widget1UploadConfiguration.value[-1]['content']\n",
126 | " upload_directory=\"upload\"\n",
127 | " if not os.path.exists(upload_directory):\n",
128 | " os.mkdir(upload_directory)\n",
129 | " with open(upload_directory + \"/\" + uploaded_filename, 'wb') as f: f.write(content)\n",
130 | " mypath=upload_directory + \"/\"\n",
131 | " found_files=[]\n",
132 | " for f in os.listdir(mypath):\n",
133 | " found_files += [(f , mypath + f)]\n",
134 | " found_files = tuple(found_files)\n",
135 | " #found_files = tuple([(f , mypath + f) for f in os.listdir(mypath)])\n",
136 | " examples = ('Ice Ih', 'examples/IceIh.pdb'),('Urea', 'examples/urea2.pdb'), ('Ga II', 'examples/Ga_II.vasp')\n",
137 | " all_files = found_files+examples\n",
138 | " widget_choose_filename.options = all_files\n",
139 | " \n",
140 | "Widget1UploadConfiguration.observe(updateWidget1ExampleConfiguration,names='value')\n",
141 | "\n",
142 | "Widget1ExamplesAndUpload = widgets.HBox([Widget1ExampleConfiguration,Widget1UploadConfiguration])\n",
143 | "Widget1Text = HTML(markdown.markdown(\"\"\"\n",
144 | "Choose a structure from the examples or upload your own in xyz, pdb, vasp, lammps-data, or [other formats](https://wiki.fysik.dtu.dk/ase/ase/io/io.html).\n",
145 | "\"\"\"))\n",
146 | "Widget1 = widgets.VBox([Widget1Text,Widget1ExamplesAndUpload,Widget1Out])"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": 5,
152 | "metadata": {},
153 | "outputs": [
154 | {
155 | "name": "stderr",
156 | "output_type": "stream",
157 | "text": [
158 | "/home/local/PRINCETON/ppiaggi/miniconda3/lib/python3.8/site-packages/traitlets/traitlets.py:714: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison\n",
159 | " silent = bool(old_value == new_value)\n"
160 | ]
161 | }
162 | ],
163 | "source": [
164 | "#############################\n",
165 | "# WIDGET 2: Define\n",
166 | "#############################\n",
167 | "\n",
168 | "MyEnvironmentFinder.chooseConfiguration('examples/IceIh.pdb') # Have to load the first example\n",
169 | "widget_atom_type_1 = widgets.Dropdown(options=MyEnvironmentFinder.atom_types, value=MyEnvironmentFinder.atom_types[1], description='Central atoms type:', style = {'description_width': 'initial'})\n",
170 | "widget_atom_type_2 = widgets.Dropdown(options=MyEnvironmentFinder.atom_types, value=MyEnvironmentFinder.atom_types[0], description='Neighbor atoms type:', style = {'description_width': 'initial'})\n",
171 | "\n",
172 | "def update_atom_types(*args):\n",
173 | " widget_atom_type_1.options = MyEnvironmentFinder.atom_types\n",
174 | " widget_atom_type_1.value = MyEnvironmentFinder.atom_types[0]\n",
175 | " widget_atom_type_2.options = MyEnvironmentFinder.atom_types\n",
176 | " widget_atom_type_2.value = MyEnvironmentFinder.atom_types[0]\n",
177 | "\n",
178 | "widget_choose_filename.observe(update_atom_types, names='value')\n",
179 | "\n",
180 | "def toggleTypeAndIndex(value):\n",
181 | " if (value=='Type'):\n",
182 | " # Call function with widgets\n",
183 | " widget_define_type_pre = interactive(MyEnvironmentFinder.calculateEnvironmentsType, \n",
184 | " atom_type_1 = widget_atom_type_1,\n",
185 | " atom_type_2 = widget_atom_type_2,\n",
186 | " cutoff = widgets.FloatText(value=2,description='Cutoff (Å):',disabled=False),\n",
187 | " tolerance = widgets.FloatText(value=0.02,description='Tolerance (Å):',disabled=False, style = {'description_width': 'initial'}) \n",
188 | " )\n",
189 | " widget_define_type_text = widgets.Label(value=\"Choose the atom type of the central and neighbor atoms, the cutoff, and the similarity tolerance.\")\n",
190 | " widget_define_type = widgets.VBox([widget_define_type_text,widget_define_type_pre]) \n",
191 | " display(widget_define_type)\n",
192 | " elif (value=='String'):\n",
193 | " # Call function with widgets\n",
194 | " widget_define_string_pre = interactive(MyEnvironmentFinder.calculateEnvironmentsString, \n",
195 | " listastring = widgets.Text(value=\"1,2,3\", description='List central atoms:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
196 | " listbstring = widgets.Text(value=\"1,2\", description='List neighbor atoms:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
197 | " cutoff = widgets.FloatText(value=2,description='Cutoff (Å):',disabled=False, style = {'description_width': 'initial'}),\n",
198 | " tolerance = widgets.FloatText(value=0.02,description='Tolerance (Å):',disabled=False, style = {'description_width': 'initial'}) \n",
199 | " )\n",
200 | " widget_define_string_text = widgets.Label(value=\"Choose central and neighbor atoms using lists, then choose the cutoff and the similarity tolerance.\")\n",
201 | " widget_define_string = widgets.VBox([widget_define_string_text,widget_define_string_pre])\n",
202 | " display(widget_define_string)\n",
203 | " elif (value=='Step'):\n",
204 | " # Call function with widgets\n",
205 | " widget_define_step_pre = interactive(MyEnvironmentFinder.calculateEnvironmentsMinMaxStride,\n",
206 | " mina = widgets.Text(value=\"1\", description='Central atoms min:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
207 | " maxa = widgets.Text(value=\"2\", description='Central atoms max:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
208 | " stridea = widgets.Text(value=\"1\", description='Central atoms step:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
209 | " minb = widgets.Text(value=\"1\", description='Neighbor atoms min:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
210 | " maxb = widgets.Text(value=\"2\", description='Neighbor atoms max:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
211 | " strideb = widgets.Text(value=\"1\", description='Neighbor atoms step:',placeholder='Type something',disabled=False, style = {'description_width': 'initial'}),\n",
212 | " cutoff = widgets.FloatText(value=2,description='Cutoff (Å):',disabled=False, style = {'description_width': 'initial'}),\n",
213 | " tolerance = widgets.FloatText(value=0.02,description='Tolerance (Å):',disabled=False, style = {'description_width': 'initial'}) \n",
214 | " )\n",
215 | " widget_define_step_text = widgets.Label(value=\"Choose central and neighbor atoms using minimum and maximum indeces and a step. Then choose the cutoff and the similarity tolerance.\")\n",
216 | " widget_define_step = widgets.VBox([widget_define_step_text,widget_define_step_pre])\n",
217 | " display(widget_define_step)\n",
218 | " else:\n",
219 | " print(\"Error\")\n",
220 | "\n",
221 | "Widget2Pre = interactive(toggleTypeAndIndex, value=widgets.ToggleButtons(options=['Type','String','Step'], description='Choose based on:', style = {'description_width': 'initial'}, disabled=False))\n",
222 | "\n",
223 | "Widget2Text = widgets.Label(value=\"Define the environments. The tool will then find the unique environments in the configuration using your definition.\")\n",
224 | "Widget2 = widgets.VBox([Widget2Text,Widget2Pre])"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "metadata": {},
231 | "outputs": [],
232 | "source": [
233 | "#############################\n",
234 | "# WIDGET 3: Analyze\n",
235 | "#############################\n",
236 | "\n",
237 | "Widget3Out = widgets.Output()\n",
238 | "\n",
239 | "def update3(*args):\n",
240 | " toggleAllVsUnique('Unique')\n",
241 | " \n",
242 | "def toggleAllVsUnique(value):\n",
243 | " Widget3Out.clear_output()\n",
244 | " if (value=='Unique' and MyEnvironmentFinder.uniqueEnvs.shape[0]>0 and MyEnvironmentFinder.uniqueFlag):\n",
245 | " plotMyEnv = interactive(MyEnvironmentFinder.chooseEnvPlotUnique, number=widgets.IntSlider(description='Environment:',min=1,max=MyEnvironmentFinder.uniqueEnvs.shape[0],step=1,value=0)) #, anglex=widgets.IntSlider(description='Angle x:',min=-90,max=90,step=5,value=0), angley=widgets.IntSlider(description='Angle y:',min=-90,max=90,step=5,value=0), anglez=widgets.IntSlider(description='Angle z:',min=-90,max=90,step=5,value=0))\n",
246 | " with Widget3Out:\n",
247 | " display(plotMyEnv)\n",
248 | " elif (value=='All' and MyEnvironmentFinder.allEnvs.shape[0]>0):\n",
249 | " plotMyEnv = interactive(MyEnvironmentFinder.chooseEnvPlotAll, number=widgets.IntSlider(description='Environment:',min=1,max=MyEnvironmentFinder.allEnvs.shape[0],step=1,value=0, style = {'description_width': 'initial'})) #, anglex=widgets.IntSlider(description='Angle x:',min=-90,max=90,step=5,value=0), angley=widgets.IntSlider(description='Angle y:',min=-90,max=90,step=5,value=0), anglez=widgets.IntSlider(description='Angle z:',min=-90,max=90,step=5,value=0))\n",
250 | " with Widget3Out:\n",
251 | " display(plotMyEnv)\n",
252 | " elif (value!='All' and value!='Unique'):\n",
253 | " with Widget3Out:\n",
254 | " print(\"Error: keyword \" + str(value) + \" not recognized!\")\n",
255 | " elif (not(MyEnvironmentFinder.uniqueFlag)):\n",
256 | " with Widget3Out:\n",
257 | " print(\"Error: unique environments not requested!\")\n",
258 | " elif (MyEnvironmentFinder.allEnvs.shape[0]==0 or MyEnvironmentFinder.uniqueEnvs.shape[0]==0):\n",
259 | " with Widget3Out:\n",
260 | " print(\"Error: empty environments!\")\n",
261 | " else:\n",
262 | " with Widget3Out:\n",
263 | " print(\"Error\")\n",
264 | " \n",
265 | "Widget3AnalyzeEnvironmentsToggle = interactive(toggleAllVsUnique, value=widgets.ToggleButtons(options=['Unique','All'], description='Choose:', disabled=False))\n",
266 | "\n",
267 | "Widget3Text = widgets.Label(value='Visualize the calculated environments. Toggle between the unique environments or all, and slide to choose environment.')\n",
268 | "\n",
269 | "Widget3AnalyzeEnvironments = widgets.VBox([Widget3Text,Widget3AnalyzeEnvironmentsToggle,Widget3Out])"
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": null,
275 | "metadata": {},
276 | "outputs": [],
277 | "source": [
278 | "#############################\n",
279 | "# WIDGET 4: Output\n",
280 | "#############################\n",
281 | "\n",
282 | "Widget4Out = widgets.Output()\n",
283 | "\n",
284 | "def update4(*args):\n",
285 | " toggleAllVsUniqueForOutput('Unique')\n",
286 | " \n",
287 | "def toggleAllVsUniqueForOutput(value):\n",
288 | " Widget4Out.clear_output()\n",
289 | " if (value=='Unique' and MyEnvironmentFinder.uniqueEnvs.shape[0]>0 and MyEnvironmentFinder.uniqueFlag):\n",
290 | " with Widget4Out:\n",
291 | " MyEnvironmentFinder.printEnvironments(MyEnvironmentFinder.uniqueEnvs)\n",
292 | " MyEnvironmentFinder.printEnvironmentsToZipFile(MyEnvironmentFinder.uniqueEnvs)\n",
293 | " elif (value=='All' and MyEnvironmentFinder.allEnvs.shape[0]>0):\n",
294 | " with Widget4Out:\n",
295 | " MyEnvironmentFinder.printEnvironments(MyEnvironmentFinder.allEnvs)\n",
296 | " MyEnvironmentFinder.printEnvironmentsToZipFile(MyEnvironmentFinder.allEnvs)\n",
297 | " elif (value!='All' and value!='Unique'):\n",
298 | " with Widget4Out:\n",
299 | " print(\"Error: keyword \" + str(value) + \" not recognized!\")\n",
300 | " elif (not(MyEnvironmentFinder.uniqueFlag)):\n",
301 | " with Widget4Out:\n",
302 | " print(\"Error: unique environments not requested!\")\n",
303 | " elif (MyEnvironmentFinder.allEnvs.shape[0]==0 or MyEnvironmentFinder.uniqueEnvs.shape[0]==0):\n",
304 | " with Widget4Out:\n",
305 | " print(\"Error: empty environments!\")\n",
306 | " else:\n",
307 | " with Widget4Out:\n",
308 | " print(\"Error\")\n",
309 | " \n",
310 | "Widget4OutputEnvironmentsToggle = interactive(toggleAllVsUniqueForOutput, value=widgets.ToggleButtons(options=['Unique','All'], description='Choose:', disabled=False))\n",
311 | "\n",
312 | "Widget4Text = widgets.Label(value='Print the environments in Protein Data Bank (PDB) format. Toggle between the unique environments or all.')\n",
313 | "\n",
314 | "Widget4TextPost = HTML(markdown.markdown(\"\"\"\n",
315 | "These environments can be used in an enhanced sampling simulation using the [EnvironmentSimilarity](https://www.plumed.org/doc-master/user-doc/html/_e_n_v_i_r_o_n_m_e_n_t_s_i_m_i_l_a_r_i_t_y.html) collective variable in [PLUMED](https://www.plumed.org/doc-master/user-doc/html/index.html).\n",
316 | "\"\"\"))\n",
317 | "\n",
318 | "#Widget4Button = widgets.Button(\n",
319 | "# description='Download',\n",
320 | "# disabled=False,\n",
321 | "# button_style='', # 'success', 'info', 'warning', 'danger' or ''\n",
322 | "# tooltip='Download',\n",
323 | "# icon='download' # (FontAwesome names without the `fa-` prefix)\n",
324 | "#)\n",
325 | "#\n",
326 | "#def zip_and_download(dummy):\n",
327 | "# MyEnvironmentFinder.printEnvironmentsToFile(MyEnvironmentFinder.uniqueEnvs)\n",
328 | "#\n",
329 | "#Widget4Button.on_click(zip_and_download)\n",
330 | "\n",
331 | "Widget4OutputEnvironments = widgets.VBox([Widget4Text,Widget4OutputEnvironmentsToggle,Widget4Out,Widget4TextPost])"
332 | ]
333 | },
334 | {
335 | "cell_type": "code",
336 | "execution_count": null,
337 | "metadata": {},
338 | "outputs": [],
339 | "source": [
340 | "#############################\n",
341 | "# Combine widgets into tabs\n",
342 | "#############################\n",
343 | "\n",
344 | "children = [Widget1,Widget2,Widget3AnalyzeEnvironments,Widget4OutputEnvironments]\n",
345 | "tab = widgets.Tab() #layout=widgets.Layout(width='800px', height='800px'))\n",
346 | "tab.children = children\n",
347 | "tab.set_title(0, 'Choose configuration')\n",
348 | "tab.set_title(1, 'Define environments')\n",
349 | "tab.set_title(2, 'Analyze environments')\n",
350 | "tab.set_title(3, 'Output environments')\n",
351 | "\n",
352 | "\n",
353 | "tab.observe(update3, names='selected_index')\n",
354 | "tab.observe(update4, names='selected_index')"
355 | ]
356 | },
357 | {
358 | "cell_type": "code",
359 | "execution_count": null,
360 | "metadata": {},
361 | "outputs": [],
362 | "source": [
363 | "display(tab)"
364 | ]
365 | },
366 | {
367 | "cell_type": "code",
368 | "execution_count": null,
369 | "metadata": {},
370 | "outputs": [],
371 | "source": [
372 | "html_acknowledgments = markdown.markdown(\"\"\"\n",
373 | "* The app uses several python libraries, for instance [ASE](https://wiki.fysik.dtu.dk/ase/), [NGLVIEW](https://github.com/arose/nglview), and [ipywidgets](https://ipywidgets.readthedocs.io/en/latest/index.html).\n",
374 | "* I am grateful to Giovanni Pizzi and Dou Du for suggesting to deploy the tool using [Binder](https://mybinder.org/)+[appmode](https://github.com/oschuett/appmode).\n",
375 | "* This tool was mainly developed with support of the Swiss National Science Foundation (SNSF) through an Early Postdoc.Mobility fellowship.\n",
376 | "* I also acknowledge funding from the NCCR MARVEL funded by the SNSF and from the CSI Computational Science Center funded by the Department of Energy of the USA.\n",
377 | "\"\"\")\n",
378 | "acknowledgments=widgets.VBox([\n",
379 | " HTML(html_acknowledgments)\n",
380 | "])\n",
381 | "\n",
382 | "html_howtocite = markdown.markdown(\"\"\"\n",
383 | "If you are using this tool to find environments for enhanced sampling simulations please read and cite:\n",
384 | "\n",
385 | "* [Pablo Piaggi and Michele Parrinello, *Calculation of phase diagrams in the multithermal-multibaric ensemble*, J. Chem. Phys. 150, 244119 (2019)](https://aip.scitation.org/doi/full/10.1063/1.5102104)\n",
386 | "\"\"\")\n",
387 | "howtocite=widgets.VBox([\n",
388 | " HTML(html_howtocite)\n",
389 | "])\n",
390 | "\n",
391 | "post_children=[acknowledgments,howtocite]\n",
392 | "post_accordion = widgets.Accordion(layout=widgets.Layout())\n",
393 | "#print(post_accordion.layout.keys)\n",
394 | "post_accordion.children = post_children\n",
395 | "post_accordion.set_title(0, 'Acknowledgments')\n",
396 | "post_accordion.set_title(1, 'How to cite')\n",
397 | "post_accordion.selected_index = None\n",
398 | "post_accordion"
399 | ]
400 | },
401 | {
402 | "cell_type": "markdown",
403 | "metadata": {},
404 | "source": [
405 | "See the project on [GitHub](https://github.com/PabloPiaggi/EnvironmentFinder)"
406 | ]
407 | },
408 | {
409 | "cell_type": "markdown",
410 | "metadata": {},
411 | "source": [
412 | "Environment Finder, Copyright © 2020-2023, Pablo Piaggi"
413 | ]
414 | },
415 | {
416 | "cell_type": "code",
417 | "execution_count": null,
418 | "metadata": {},
419 | "outputs": [],
420 | "source": []
421 | }
422 | ],
423 | "metadata": {
424 | "kernelspec": {
425 | "display_name": "Python 3 (ipykernel)",
426 | "language": "python",
427 | "name": "python3"
428 | },
429 | "language_info": {
430 | "codemirror_mode": {
431 | "name": "ipython",
432 | "version": 3
433 | },
434 | "file_extension": ".py",
435 | "mimetype": "text/x-python",
436 | "name": "python",
437 | "nbconvert_exporter": "python",
438 | "pygments_lexer": "ipython3",
439 | "version": "3.8.17"
440 | }
441 | },
442 | "nbformat": 4,
443 | "nbformat_minor": 2
444 | }
445 |
--------------------------------------------------------------------------------
/environmentfinder/__init__.py:
--------------------------------------------------------------------------------
1 | from .environmentfinder import *
2 |
--------------------------------------------------------------------------------
/environmentfinder/environmentfinder.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from ipywidgets import widgets
3 | import ase
4 | import ase.io
5 | import ase.neighborlist
6 | from itertools import permutations
7 | from tqdm.notebook import tqdm, trange
8 | import nglview
9 | from IPython.display import display, FileLink
10 | from zipfile import ZipFile
11 | import os
12 | import glob
13 |
14 | import warnings
15 | warnings.simplefilter('ignore')
16 |
17 | # Class to define and operate on environments
18 | class Environments:
19 | def __init__(self):
20 | self.myindex = -1
21 | self.indeces = np.array([],dtype=int)
22 | self.delta = np.array([])
23 | self.distance = np.array([])
24 | # degeneracy defaults to 1 and is different from 1 for unique environments
25 | self.degeneracy = 1
26 |
27 |
28 | # Class to find environments
29 | class EnvironmentFinder:
30 |
31 | def __init__(self):
32 | # self.fractionalFlag is True if the coordinates are fractional
33 | self.fractionalFlag=False
34 | # uniqueFlags is True if unique environments are to be calculated
35 | self.uniqueFlag=True
36 | # self.verboseFlag is True if verbose mode is on
37 | self.verboseFlag=True
38 | # self.allEnvs stores all calculated environments
39 | self.allEnvs=np.ndarray((0,),dtype=object)
40 | # self.uniqueEnvs stores the unique environments
41 | self.uniqueEnvs = np.ndarray((0,),dtype=object)
42 | # self.tolerance is the tolerance to determine if two elements of the
43 | # distance vectors are the same
44 | self.tolerance=0
45 | # self.atom_types contains the unique atom types
46 | self.atom_types = np.empty(1)
47 | # Max number of neighbors in environment
48 | self.max_neighbors=1
49 | self.max_neighbors_flag=False
50 | #env=Environments()
51 | #env.delta.shape = (0,3)
52 | #uniqueEnvs = np.append(uniqueEnvs,env)
53 | # self.box_layout contains options to display a box
54 | self.box_layout = widgets.Layout(display='flex',
55 | flex_flow='column',
56 | align_items='stretch',
57 | border='solid',
58 | width='90%')
59 |
60 | def plotConf(self):
61 | """ Plot configuration using nglview """
62 |
63 | v = nglview.show_ase(self.conf)
64 | v.add_representation("unitcell")
65 | boxy=widgets.Box([v],layout=self.box_layout)
66 | display(boxy)
67 |
68 | def chooseConfiguration(self,filename):
69 | """ Choose configuration by filename
70 |
71 | Args:
72 | filename (string): the file name
73 |
74 | Returns:
75 | no value
76 | """
77 |
78 | self.conf = ase.io.read(filename)
79 | self.atom_types = np.unique(self.conf.get_chemical_symbols())
80 | self.atom_types = np.append(self.atom_types,"Any")
81 |
82 | def chooseAndPlotConfiguration(self,filename):
83 | """ Choose and plot configuration by filename
84 |
85 | Args:
86 | filename (string): the file name
87 |
88 | Returns:
89 | no value
90 | """
91 |
92 | self.chooseConfiguration(filename)
93 | self.plotConf()
94 |
95 | def setMaximumNumberOfNeighbors(self,max_neighbors):
96 | self.max_neighbors_flag = True
97 | self.max_neighbors = max_neighbors
98 |
99 | def calculateEnvironments(self,lista,listb,cutoff):
100 | """ Find environments
101 |
102 | This function calculates all environments centered at the atom in
103 | lista using atoms in listb as neighbors if they are within the cutoff.
104 |
105 | Args:
106 | lista (numpy vector): list of central atoms
107 | listb (numpy vector): list of neighboring atoms
108 | cutoff (float): cut off for the calculation of neighbors
109 |
110 | Returns:
111 | no value
112 | """
113 |
114 | self.allEnvs = np.ndarray((0,),dtype=object)
115 | # Find all environments
116 | # Loop over input particles of type atom_type_1:
117 | myindeces=lista
118 | nl = ase.neighborlist.NeighborList((cutoff/2.0)*np.ones(self.conf.get_number_of_atoms()),skin=0.1,bothways=True)
119 | nl.update(self.conf)
120 | mat = self.conf.get_cell()
121 | for index in myindeces:
122 | neighbors, offsets = nl.get_neighbors(index)
123 | # Iterate over the neighbors of the current particle:
124 | Environment = Environments()
125 | Environment.myindex = index
126 | Environment.delta.shape = (0,3)
127 | for neigh, offset in zip(neighbors, offsets):
128 | if (neigh in listb): # Only if neighbor is in listb
129 | delta = self.conf.positions[neigh] + np.dot(offset, mat) - self.conf.positions[index]
130 | distance=np.linalg.norm(delta)
131 | if (distance>1.e-10):
132 | if (not self.fractionalFlag):
133 | Environment.delta = np.append(Environment.delta,np.array([[delta[0], delta[1], delta[2]]])*0.1,axis=0)
134 | else:
135 | Environment.delta = np.append(Environment.delta,np.array([[delta[0]/mat[0][0], delta[1]/mat[1][1], delta[2]/mat[2][2]]]),axis=0)
136 | Environment.distance = np.append(Environment.distance,distance)
137 | Environment.indeces = np.append(Environment.indeces,neigh)
138 | # Use maximum number of neighbors
139 | if (self.max_neighbors_flag==True):
140 | # Sort
141 | sortindeces=np.argsort(Environment.distance,kind='stable')
142 | Environment.delta = Environment.delta[sortindeces,:]
143 | Environment.distance = Environment.distance[sortindeces]
144 | Environment.indeces = Environment.indeces[sortindeces]
145 | # Choose first max_neighbors
146 | Environment.delta = Environment.delta[:self.max_neighbors,:]
147 | Environment.distance = Environment.distance[:self.max_neighbors]
148 | Environment.indeces = Environment.indeces[:self.max_neighbors]
149 | self.allEnvs = np.append(self.allEnvs,Environment)
150 |
151 |
152 | def CalculateUniqueEnvironments(self):
153 | """ Calculate unique Environments
154 |
155 | This algorithm calculates unique environments by comparing all possible
156 | permutations of the environments. However, it discards entire sets of
157 | permutations if it is found that there is no possibility of match.
158 |
159 | The number of operations scales roughly as as N^2*(\sum_{i=1}^M M+1-i)
160 | where N is the total number of environments and M is the number of i
161 | atoms in the environments.
162 |
163 | Args:
164 | none
165 |
166 | Returns:
167 | no value
168 | """
169 |
170 | num_of_templates=self.allEnvs.shape[0]
171 | flag_unique=np.ones(num_of_templates)
172 | same_as=np.linspace(0,num_of_templates-1,num_of_templates)
173 | # Disregard empty environments
174 | for i in range(num_of_templates):
175 | if (self.allEnvs[i].indeces.shape[0]==0):
176 | flag_unique[i]=0
177 | for i in trange(num_of_templates,desc="Progress", leave=False):
178 | if ( self.allEnvs[i].indeces.shape[0]>0 and flag_unique[i]==1):
179 | for j in range(i+1,num_of_templates):
180 | # Not empty, unique, and same number of neighbors
181 | if ( self.allEnvs[j].indeces.shape[0]>0 and flag_unique[j]==1 and self.allEnvs[i].indeces.shape[0]==self.allEnvs[j].indeces.shape[0]):
182 | atom_match = np.zeros(self.allEnvs[j].indeces.shape[0])
183 | for k1 in range(self.allEnvs[i].indeces.shape[0]):
184 | for k2 in range(self.allEnvs[j].indeces.shape[0]):
185 | if (np.count_nonzero(np.isclose(self.allEnvs[i].delta[k1,:],self.allEnvs[j].delta[k2,:],atol=self.tolerance))==3 ):
186 | #print(i,j,self.allEnvs[i].delta[k1,:],self.allEnvs[j].delta[k2,:],np.isclose(self.allEnvs[i].delta[k1,:],self.allEnvs[j].delta[k2,:],atol=self.tolerance))
187 | atom_match[k1] = 1
188 | break
189 | # If no match was found for a given atom the whole self.allEnvs[j] must be discarded
190 | if (atom_match[k1]==0):
191 | break
192 | # If a match was found for every atom in self.allEnvs[i]
193 | #print(i,j,atom_match)
194 | if np.all(atom_match==np.ones(atom_match.shape[0])):
195 | flag_unique[j]=0
196 | same_as[j] = i
197 | #break
198 | self.uniqueEnvs = np.ndarray((0,),dtype=object)
199 | for i in range(num_of_templates):
200 | if (flag_unique[i]==1):
201 | self.uniqueEnvs = np.append(self.uniqueEnvs,self.allEnvs[i])
202 | self.uniqueEnvs[-1].degeneracy = np.sum(np.ones(num_of_templates)[same_as==i])
203 |
204 | def printEnvironmentSummaryInfo(self,Environments):
205 | if (self.verboseFlag):
206 | num_of_templates=Environments.shape[0]
207 | if (num_of_templates>0):
208 | avg_num_neighbors = 0.0
209 | for i in range(num_of_templates):
210 | avg_num_neighbors += Environments[i].indeces.shape[0]
211 | avg_num_neighbors /= num_of_templates
212 | else:
213 | avg_num_neighbors = 0
214 | print("Found " + str(num_of_templates) + " environments each with " + str(int(avg_num_neighbors)) + " neighbors on average")
215 | if (num_of_templates>0):
216 | for i in range(num_of_templates):
217 | print("Environment " + str(int(i+1)) + ": degeneracy = " + str(int(Environments[i].degeneracy)) + " - number of neighbors = ", str(int(Environments[i].indeces.shape[0])) )
218 |
219 | def printEnvironments(self,Environments):
220 | num_of_templates=Environments.shape[0]
221 | for i in range(num_of_templates):
222 | env_atom_types = np.asarray(self.conf.get_chemical_symbols())[Environments[i].indeces.astype(int)]
223 | env_positions = Environments[i].delta*10
224 | env = ase.Atoms(env_atom_types,env_positions)
225 | ase.io.write('-', env, format='proteindatabank')
226 |
227 |
228 | def printEnvironmentsToFile(self,Environments):
229 | num_of_templates=Environments.shape[0]
230 | for i in range(num_of_templates):
231 | env_atom_types = np.asarray(self.conf.get_chemical_symbols())[Environments[i].indeces.astype(int)]
232 | env_positions = Environments[i].delta*10
233 | env = ase.Atoms(env_atom_types,env_positions)
234 | fileName="env" + str(i+1) + ".pdb"
235 | ase.io.write(fileName, env, format='proteindatabank')
236 |
237 | def printEnvironmentsToZipFile(self,Environments):
238 | num_of_templates=Environments.shape[0]
239 | download_directory="Download"
240 | if not os.path.exists(download_directory):
241 | os.mkdir(download_directory)
242 | zipObj = ZipFile(download_directory + '/download.zip', 'w')
243 | for i in range(num_of_templates):
244 | env_atom_types = np.asarray(self.conf.get_chemical_symbols())[Environments[i].indeces.astype(int)]
245 | env_positions = Environments[i].delta*10
246 | env = ase.Atoms(env_atom_types,env_positions)
247 | fileName=download_directory + "/env" + str(i+1) + ".pdb"
248 | ase.io.write(fileName, env, format='proteindatabank')
249 | zipObj.write(fileName)
250 | zipObj.close()
251 | local_file = FileLink(download_directory + '/download.zip', result_html_prefix="Click here to download the environments in PDB format: ")
252 | display(local_file)
253 | fileList = glob.glob(download_directory + '/env*')
254 | for filePath in fileList:
255 | os.remove(filePath)
256 |
257 |
258 | def plotEnv(self,myEnvironment):
259 | env_atom_types = np.asarray(self.conf.get_chemical_symbols())[myEnvironment.indeces.astype(int)] #,np.array('C')] #Template[env_number].myindex)]
260 | env_atom_types = np.append(env_atom_types,np.asarray(self.conf.get_chemical_symbols())[myEnvironment.myindex])
261 | env_positions = np.vstack((myEnvironment.delta*10,np.array([0,0,0]) ) )
262 | env = ase.Atoms(env_atom_types,env_positions)
263 | v = nglview.show_ase(env)
264 | boxy=widgets.Box([v],layout=self.box_layout)
265 | display(boxy)
266 |
267 | def chooseEnvPlotAll(self,number):
268 | if (self.allEnvs.shape[0]>0 and ((number-1)