├── __init__.py ├── pesviewer ├── __init__.py └── pesviewer.py ├── conda_build_config.yaml ├── meta.yaml ├── setup.py ├── LICENSE ├── README.md └── input.txt /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pesviewer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /conda_build_config.yaml: -------------------------------------------------------------------------------- 1 | python: 2 | - 2.7 3 | - 3.6 4 | -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | # cmd: conda build . -c LCT -c rdkit -c openbabel 2 | package: 3 | name: pesviewer 4 | version: "1.0" 5 | 6 | source: 7 | path: . 8 | 9 | requirements: 10 | build: 11 | - python 12 | - numpy 13 | - matplotlib 14 | - pillow 15 | - rdkit 16 | - openbabel 17 | run: 18 | - python 19 | - numpy 20 | - matplotlib 21 | - pillow 22 | - rdkit 23 | - openbabel 24 | 25 | about: 26 | home: https://github.com/rubenvdvijver/PESViewer 27 | license: MIT 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open('README.md', 'r') as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name = "PESViewer", 8 | version = "1.0", 9 | 10 | packages = find_packages(), 11 | entry_points={'console_scripts':[ 12 | 'pesviewer = pesviewer.pesviewer:main', 13 | ]}, 14 | install_requires=['matplotlib','numpy','Pillow'], 15 | 16 | author="Ruben Van de Vijver", 17 | author_email = "vandevijver.ruben@gmail.com", 18 | 19 | long_description=long_description, 20 | long_description_type='text/markdown', 21 | description = "Depiction of Potential Energy Surfaces.", 22 | license = "MIT", 23 | url = "https://github.com/rubenvdvijver/PESViewer.git", 24 | ) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ruben Van de Vijver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PESViewer 2 | Visualize a potential energy surface 3 | 4 | ## Developer: Ruben Van de Vijver, Laboratory for Chemical Technology, Ghent University 5 | 6 | The PESViewer is a code to depict and analyze a potential energy surface 7 | characterized by wells, bimolecular products, transition states and barrierless reactions. 8 | Their energy is needed to plot the potential energy surface. 9 | Written values of the energies and 2D plots (structural formulas) of the wells and products can be added to the figure 10 | 11 | To run this code, you need python version 2.7, matplotlib, numpy and optionally OpenBabel or RDKit to create 2D plots 12 | 13 | ## INSTALL 14 | 15 | Clone the project onto your machine and go to the PESViewer directory. Type: 16 | 17 | python setup.py build 18 | python setup.py install 19 | 20 | ## INPUT 21 | 22 | A text file containing the stationary points, energies, names identifiers and options, see the input.txt as example. 23 | 24 | For wells, write each well on a separate line as follows (smiles are optional to depict the molecule, if no xyz's are available): 25 | 26 | name energy smiles 27 | 28 | For bimolecular products (again smiles are optional): 29 | 30 | name energy smiles 31 | 32 | For reactions (colors are optional, and if available the line of the reaction will be given that color): 33 | 34 | name energy reactant product color 35 | 36 | For bimolecular products (again colors are optional) 37 | 38 | name reactant product color 39 | 40 | 41 | The plotting options (to be written in the input file) are: 42 | 43 | 44 | | option | default | Description | 45 | | ------- | ------- | ------- | 46 | | title | 0 | print a title (1) or not (0) | 47 | | units | kcal/mol | energy units | 48 | | use_xyz | 1 |use xyz, put 0 to switch off | 49 | | rescale | 0 | no rescale , put the well or bimolecular name here to rescale to that value | 50 | | fh | 9. | figure height | 51 | | fw | 18. | figure width | 52 | | margin | 0.2 | margin fraction on the x and y axis | 53 | | dpi | 120 | dpi of the molecule figures | 54 | | save | 0 | does the plot need to be saved (1) or displayed (0) | 55 | | write_ts_values | 1 | booleans tell if the ts energy values should be written | 56 | | write_well_values | 1 | booleans tell if the well and bimolecular energy values should be written | 57 | | bimol_color | red | color of the energy values for the bimolecular products | 58 | | well_color | blue | color of the energy values of the wells | 59 | | ts_color | green | color or the energy values of the ts, put to 'none' to use same color as line | 60 | | show_images | 1 | boolean tells whether the molecule images should be shown on the graph | 61 | | rdkit4depict | 1 | boolean that specifies which code to use for the 2D depiction | 62 | | axes_size | 10 | font size of the axes | 63 | | text_size | 10 | font size of the energy values on the graph | 64 | | linear_lines | 0 | plot polynomials (0) or linear lines (1) between de stationary points | 65 | 66 | Optionally a folder xyz/ containing the xyz coordinates of the stationary points ($name.xyz) 67 | (for bimolecular products, use several xyz coordinates files ($name$index.xyz) ) 68 | 69 | 70 | ## RUN 71 | 72 | With the input file input.inp, type: 73 | 74 | pesviewer input.inp 75 | 76 | ## OUTPUT 77 | 78 | The output is a modifiable matplotlib figure. 79 | 80 | 2 modifications are possible 81 | - modifing the x-position of a stationary point by a 'drag and drop' of the energy value 82 | - modifing the position of 2D structure images by a 'drag and drop' of the image 83 | 84 | -------------------------------------------------------------------------------- /input.txt: -------------------------------------------------------------------------------- 1 | > 2 | This comment is not interpreted, so store any extra info here. 3 | Keywords are case insensitive. Look at the help below. 4 | IMPORTANT: avoid the use of '2d' and '3d' in the names of species, transition states and reactions 5 | (these strings are employed when generating the 2d and 3d files of the molecules) 6 | If you want to use 3D coordinates, store them in a xyz/ directory in the same directory as the python script 7 | 8 | This example is based on figure 1 in the article: 9 | J Zador et al. Phys Chem Chem Phys 11 (46), 11040-11053. 2009 Oct 13. 10 | 11 | 12 | 13 | > propene_hydroxyl 14 | 15 | > 16 | title 0 # print a title (1) or not (0) 17 | units kcal/mol # energy units 18 | use_xyz 1 # use xyz, put 0 to switch off 19 | rescale 0 # no rescale , put the well or bimolecular name here to rescale to that value 20 | fh 9. # figure height 21 | fw 18. # figure width 22 | margin 0.2 # margin fraction on the x and y axis 23 | dpi 120 # dpi of the molecule figures 24 | save 0 # does the plot need to be saved (1) or displayed (0) 25 | write_ts_values 1 # booleans tell if the ts energy values should be written 26 | write_well_values 1 # booleans tell if the well and bimolecular energy values should be written 27 | bimol_color red # color of the energy values for the bimolecular products 28 | well_color blue # color of the energy values of the wells 29 | ts_color green # color or the energy values of the ts, put to 'none' to use same color as line 30 | show_images 1 # boolean tells whether the molecule images should be shown on the graph 31 | rdkit4depict 1 # boolean that specifies which code to use for the 2D depiction 32 | axes_size 10 # font size of the axes 33 | text_size 10 # font size of the energy values on the graph 34 | linear_lines 0 # plot polynomials (0) or linear lines (1) between de stationary points 35 | 36 | > 37 | vdW -2.2 C[CH]CO 38 | b_rad -27.2 C[CH]CO 39 | a_rad -30.9 CC[CH]O 40 | o_rad -24.9 CCC[O] 41 | c_rad -20.9 [CH2]CCO 42 | 43 | > 44 | propene_oh 0. C=CC O 45 | allylalcohol_h 7.9 C=CCO [H] 46 | propenol_h 2.1 OC=CC [H] 47 | ethene_CH2OH -5.3 C=C [CH2]O 48 | propanal_h -6.8 CCC=O [H] 49 | vinylalcohol_methyl -9.0 C=CO [CH3] 50 | ethyl_formaldehyde -11.6 [CH2]C C=O 51 | oxetane_h 19.6 C1CCO1 [H] 52 | epoxypropane_h 16.1 CC1OC1 [H] 53 | cyclopropanol_h 14.4 C1CC1O [H] 54 | 55 | > 56 | entrance -1.8 vdW b_rad 57 | cycl1 31.8 b_rad epoxypropane_h 58 | isom1 12.9 b_rad a_rad 59 | beta1 6.2 b_rad propenol_h 60 | isom2 13.1 b_rad o_rad 61 | isom3 5.6 b_rad c_rad 62 | beta2 9.6 b_rad allylalcohol_h 63 | isom4 12.7 a_rad o_rad 64 | cycl2 30.4 a_rad cyclopropanol_h 65 | isom5 6.6 a_rad c_rad 66 | beta3 3.7 a_rad propenol_h 67 | beta4 3.6 a_rad propanal_h 68 | beta5 0.1 a_rad vinylalcohol_methyl 69 | isom6 -0.1 o_rad c_rad 70 | cycl3 36.7 o_rad oxetane_h 71 | cycl4 35.0 o_rad cyclopropanol_h 72 | beta6 10.9 o_rad allylalcohol_h 73 | alpha1 2.7 o_rad ethene_CH2OH 74 | cycl5 41.9 c_rad oxetane_h 75 | cycl6 31.6 c_rad epoxypropane_h 76 | beta7 -0.6 c_rad propanal_h 77 | beta8 -6.6 c_rad ethyl_formaldehyde 78 | 79 | > 80 | b1 propene_oh vdW 81 | 82 | > 83 | File follows the rules of SD file format for keywords. Keywords are case 84 | insensitive when parsed. 85 | Keywords: 86 | units: units of the energies supplied above 87 | 88 | usexyz: use the xyz coordinates of all the species and render a 2D/3D depiction 89 | 90 | rescale: energies are rescaled relative to the energy of the species given here 91 | 92 | wells: all the wells of the PES, separated by lines 93 | each line contains the name, the energy, and optionally the smiles 94 | 95 | bimolec: all the bimolecular products of the PES, separated by lines 96 | each line contains the name, the energy, and optionally the smiles of both bimolecular products 97 | 98 | ts: all the transition states of the PES, separated by lines 99 | each line contains the name, the energy, and the names of the reactant and product 100 | 101 | barrierless: all the barrierless reactions of the PES, separated by lines 102 | each line contains the name and the names of the reactant and product 103 | 104 | 105 | -------------------------------------------------------------------------------- /pesviewer/pesviewer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This code reads in an input files containing the wells, 3 | bimolecular products, transition states and 4 | barrierless reactions and creates a PES plot 5 | """ 6 | from __future__ import print_function, division 7 | import os 8 | import sys 9 | import matplotlib 10 | matplotlib.use('TkAgg') 11 | from matplotlib import pylab as plt 12 | import matplotlib.image as mpimg 13 | from PIL import Image 14 | import numpy as np 15 | import numpy.linalg as la 16 | import math 17 | # try import RDKit 18 | try: 19 | import rdkit.Chem as Chem 20 | from rdkit.Chem import Draw 21 | from rdkit.Chem import AllChem 22 | except ImportError: 23 | pass 24 | # end try 25 | 26 | # try import pybel 27 | try: 28 | import pybel 29 | except ImportError: 30 | pass 31 | try: 32 | from openbabel import pybel 33 | except ImportError: 34 | pass 35 | # end try 36 | 37 | # contains all the options for this PES 38 | options = {} 39 | 40 | # global parameters for the plot 41 | xlow = 0.0 # lowest x value on x axis 42 | xhigh = 0.0 # highest x value on x axis 43 | xmargin = 0.0 # margin on x axis 44 | ylow = 0.0 # lowest y value on x axis 45 | yhigh = 0.0 # highest y value on x axis 46 | ymargin = 0.0 # margin on y axis 47 | xlen = 1.0 # length of horizontal lines per st pt 48 | 49 | wells = [] # list of wells 50 | bimolecs = [] # list of bimolecular products 51 | tss = [] # list of transition states 52 | barrierlesss = [] # list of barrierless reactions 53 | 54 | # text dictionary: key is the chemical structure, value is the text 55 | textd = {} 56 | # lines dictionary: key is the chemical structure, 57 | # value is a list of lines for that structure 58 | linesd = {} 59 | # figures dictionary: key is the chemical structure, 60 | # value is the figure of that structure 61 | imgsd = {} 62 | # extents of the images 63 | extsd = {} 64 | 65 | 66 | class dragimage(object): 67 | """ 68 | Class to drag an image 69 | """ 70 | def __init__(self, figure=None): 71 | if figure is None: 72 | figure = plt.gcf() 73 | # simple attibute to store the dragged text object 74 | self.struct = None 75 | self.img = None 76 | # Connect events and callbacks 77 | figure.canvas.mpl_connect("button_press_event", self.pick_image) 78 | figure.canvas.mpl_connect("button_release_event", self.release_image) 79 | figure.canvas.mpl_connect("motion_notify_event", self.move_image) 80 | # end def 81 | 82 | def pick_image(self, event): 83 | for key in imgsd.keys(): 84 | self.struct = None 85 | self.img = None 86 | if (imgsd[key].get_extent()[0] < event.xdata and 87 | imgsd[key].get_extent()[1] > event.xdata and 88 | imgsd[key].get_extent()[2] < event.ydata and 89 | imgsd[key].get_extent()[3] > event.ydata): 90 | self.struct = key 91 | self.img = imgsd[key] 92 | self.current_pos = (event.xdata, event.ydata) 93 | break 94 | # end if 95 | # end for 96 | # end def 97 | 98 | def move_image(self, event): 99 | if self.img is not None: 100 | old_extent = self.img.get_extent() 101 | xchange = event.xdata-self.current_pos[0] 102 | ychange = event.ydata-self.current_pos[1] 103 | extent_change = (xchange, xchange, ychange, ychange) 104 | extent = [old_extent[i] + extent_change[i] for i in range(0, 4)] 105 | self.img.set_extent(extent=extent) 106 | self.current_pos = (event.xdata, event.ydata) 107 | plt.draw() 108 | # end def 109 | 110 | def release_image(self, event): 111 | if self.img is not None: 112 | self.struct = None 113 | self.img = None 114 | save_im_extent() 115 | # end if 116 | # end if 117 | # end def 118 | # end class 119 | 120 | 121 | class selecthandler(object): 122 | """ 123 | Class to select and move stationary points, which highlight its reactions 124 | and in the future (TODO) renders the 3D images 125 | """ 126 | def __init__(self, figure=None): 127 | if figure is None: 128 | figure = plt.gcf() 129 | self.struct = None # stationary point that is selected 130 | figure.canvas.mpl_connect("button_press_event", self.on_pick_event) 131 | figure.canvas.mpl_connect("button_release_event", 132 | self.on_release_event) 133 | figure.canvas.mpl_connect("motion_notify_event", 134 | self.motion_notify_event) 135 | # end def 136 | 137 | def on_pick_event(self, event): 138 | self.struct = None 139 | # TODO: find more efficient way to iterate 140 | # all stationary points in one loop? 141 | # create a new list? 142 | for w in wells: 143 | if self.is_close(w, event): 144 | self.struct = w 145 | highlight_structure(self.struct) 146 | # end if 147 | # end for 148 | for b in bimolecs: 149 | if self.is_close(b, event): 150 | self.struct = b 151 | highlight_structure(self.struct) 152 | # end if 153 | # end for 154 | for t in tss: 155 | if self.is_close(t, event): 156 | self.struct = t 157 | highlight_structure(self.struct) 158 | # end if 159 | # end for 160 | if self.struct is None: 161 | highlight_structure() 162 | # end if 163 | # end def 164 | 165 | def motion_notify_event(self, event): 166 | if self.struct is not None: 167 | # a stationary point got selected 168 | # current position of the stationary point 169 | old_pos = (self.struct.x, self.struct.y) 170 | self.struct.x = event.xdata # set the new position 171 | # move all the elements(image, text and lines) 172 | updateplot(self.struct, (event.xdata-old_pos[0])) 173 | self.current_pos = old_pos 174 | # end def 175 | 176 | def on_release_event(self, event): 177 | if self.struct is not None: 178 | self.struct = None 179 | # save the x-values of the startionary points to a file 180 | save_x_values() 181 | # save the image extents (x and y coordinates) to a file 182 | save_im_extent() 183 | # end if 184 | return True 185 | # end def 186 | 187 | def is_close(self, struct, event): 188 | """ 189 | An event is close if it comes within 2% of the stationary point 190 | both in the x and in the y direction 191 | """ 192 | xc = math.fabs(event.xdata - struct.x) < (xhigh-xlow)*0.02 193 | yc = math.fabs(event.ydata - struct.y) < (yhigh-ylow)*0.02 194 | return xc and yc 195 | # end class 196 | 197 | 198 | class line: 199 | """ 200 | A line contains information about the line on the graph 201 | it is either a line between a reactant and ts, between a ts and product 202 | or between a reactant and product (for barrierless reactions) 203 | the chemstructs are the reactant and ts (f orward), 204 | ts and product (reverse), or reactant and product (barrierless) 205 | """ 206 | def __init__(self, x1, y1, x2, y2, chemstruct=None, col='black'): 207 | if x1 <= x2: 208 | self.xmin = x1 209 | self.y1 = y1 # y1 corresponds to xmin 210 | self.xmax = x2 211 | self.y2 = y2 # y2 corresponds to xmax 212 | else: 213 | self.xmin = x2 214 | self.y1 = y2 # y1 corresponds to xmin 215 | self.xmax = x1 216 | self.y2 = y1 # y2 corresponds to xmax 217 | # end if 218 | 219 | 220 | if x1 == x2 or y1 == y2: 221 | self.straight_line = True 222 | self.coeffs = [] 223 | else: 224 | self.straight_line = False 225 | self.coeffs = get_polynomial(self.xmin, self.y1, 226 | self.xmax, self.y2) 227 | # end if 228 | if chemstruct is None: 229 | self.chemstruct = [] 230 | else: 231 | self.chemstruct = chemstruct 232 | self.color = col 233 | # end def 234 | # end class 235 | 236 | 237 | class well: 238 | # Well class, contains the name, smiles and energy of a well 239 | def __init__(self, name, energy, smi=None): 240 | self.name = name 241 | self.energy = energy 242 | self.smi = smi 243 | self.x = 0. 244 | self.y = 0. 245 | self.xyz_files = [] 246 | fn = 'xyz/{name}.xyz'.format(name=name) 247 | if os.path.exists(fn): 248 | self.xyz_files.append(fn) 249 | # end def 250 | # end class 251 | 252 | 253 | class bimolec: 254 | # Bimolec class, contains the name, 255 | # both smiles and energy of a bimolecular product 256 | def __init__(self, name, energy, smi=None): 257 | self.name = name 258 | self.energy = energy 259 | if smi is None: 260 | self.smi = [] 261 | else: 262 | self.smi = smi 263 | self.x = 0. 264 | self.y = 0. 265 | self.xyz_files = [] 266 | # this bimolecular product is placed on the right side of the graph 267 | self.right = False 268 | i = 1 269 | fn = 'xyz/{name}{i}.xyz'.format(name=name, i=i) 270 | while os.path.exists(fn): 271 | self.xyz_files.append(fn) 272 | i += 1 273 | fn = 'xyz/{name}{i}.xyz'.format(name=name, i=i) 274 | # end for 275 | # end def 276 | # end class 277 | 278 | 279 | class ts: 280 | """ 281 | TS class, contains the name, the names of the 282 | reactant and product and the energy of the ts 283 | """ 284 | def __init__(self, name, names, energy, col='black'): 285 | self.name = name 286 | self.energy = energy 287 | self.color = col 288 | self.xyz_files = [] 289 | fn = 'xyz/{name}.xyz'.format(name=name) 290 | if os.path.exists(fn): 291 | self.xyz_files.append(fn) 292 | self.reactant = next((w for w in wells if w.name == names[0]), None) 293 | if self.reactant is None: 294 | list = (b for b in bimolecs if b.name == names[0]) 295 | self.reactant = next(list, None) 296 | if self.reactant is None: 297 | e = exceptions.not_recognized('reactant', names[0], name) 298 | raise Exception(e) 299 | self.product = next((w for w in wells if w.name == names[1]), None) 300 | if self.product is None: 301 | list = (b for b in bimolecs if b.name == names[1]) 302 | self.product = next(list, None) 303 | if self.product is None: 304 | e = exceptions.not_recognized('product', names[1], name) 305 | raise Exception(e) 306 | self.lines = [] 307 | self.x = 0. 308 | self.y = 0. 309 | # end def 310 | # end class 311 | 312 | 313 | class barrierless: 314 | """ 315 | Barrierless class, contains the name and the 316 | names of the reactant and product 317 | """ 318 | def __init__(self, name, names, col='black'): 319 | self.name = name 320 | self.xyz_files = [] 321 | self.color = col 322 | fn = 'xyz/{name}.xyz'.format(name=name) 323 | if os.path.exists(fn): 324 | self.xyz_files.append(fn) 325 | self.reactant = next((w for w in wells if w.name == names[0]), None) 326 | if self.reactant is None: 327 | list = (b for b in bimolecs if b.name == names[0]) 328 | self.reactant = next(list, None) 329 | if self.reactant is None: 330 | e = exceptions.not_recognized('reactant', names[0], name) 331 | raise Exception(e) 332 | self.product = next((w for w in wells if w.name == names[1]), None) 333 | if self.product is None: 334 | list = (b for b in bimolecs if b.name == names[1]) 335 | self.product = next(list, None) 336 | if self.product is None: 337 | e = exceptions.not_recognized('product', names[1], name) 338 | raise Exception(e) 339 | self.line = None 340 | # end def 341 | # end class 342 | 343 | 344 | class exceptions(object): 345 | """ 346 | Class that stores the exception messages 347 | """ 348 | def not_recognized(role, ts_name, species_name): 349 | s = 'Did not recognize {role}'.format(role=role) 350 | s += ' {prod} '.format(prod=species_name) 351 | s += 'for the transition state {ts}'.format(ts=ts_name) 352 | return s 353 | 354 | 355 | def get_polynomial(x1, y1, x2, y2): 356 | """ 357 | Method fits a third order polynomial through two points as such 358 | that the derivative in both points is zero 359 | This method should only be used if x1 is not equal to x2 360 | """ 361 | if x1 == x2: 362 | print('Error, cannot fit a polynomial if x1 equals x2') 363 | sys.exit() 364 | else: 365 | y = np.matrix([[y1], [y2], [0], [0]]) 366 | x = np.matrix([[x1**3, x1**2, x1, 1], 367 | [x2**3, x2**2, x2, 1], 368 | [3*x1**2, 2*x1, 1, 0], 369 | [3*x2**2, 2*x2, 1, 0]]) 370 | xinv = la.inv(x) 371 | a = np.dot(xinv, y) 372 | return np.transpose(a).tolist()[0] 373 | # end def 374 | 375 | 376 | def read_input(fname): 377 | """ 378 | Method to read the input file 379 | """ 380 | if not os.path.exists(fname): # check if file exists 381 | raise Exception(fname + ' does not exist') 382 | # end if 383 | f = open(fname, 'r') 384 | a = f.read() 385 | f.close() 386 | a = a.replace('\r\n', '\n').replace('\r', '\n').replace('\n\n', '\n') 387 | inputs = get_sd_prop(a) 388 | options['id'] = inputs['id'][0] 389 | # by default, print the graph title 390 | options['title'] = 1 391 | # default units 392 | options['units'] = 'kJ/mol' 393 | # use xyz by default, put 0 to switch off 394 | options['use_xyz'] = 1 395 | # no rescale as default, put the well or 396 | # bimolecular name here to rescale to that value 397 | options['rescale'] = 0 398 | # default figure height 399 | options['fh'] = 9. 400 | # default figure width 401 | options['fw'] = 18. 402 | # default margin on the x and y axis 403 | options['margin'] = 0.2 404 | # default dpi of the molecule figures 405 | options['dpi'] = 120 406 | # does the plot need to be saved (1) or displayed (0) 407 | options['save'] = 0 408 | # booleans tell if the ts energy values should be written 409 | options['write_ts_values'] = 1 410 | # booleans tell if the well and bimolecular energy values should be written 411 | options['write_well_values'] = 1 412 | # color of the energy values for the bimolecular products 413 | options['bimol_color'] = 'red' 414 | # color of the energy values of the wells 415 | options['well_color'] = 'blue' 416 | # color or the energy of the ts, put to 'none' to use same color as line 417 | options['ts_color'] = 'none' 418 | # boolean tells whether the molecule images should be shown on the graph 419 | options['show_images'] = 1 420 | # boolean that specifies which code was used for the 2D depiction 421 | options['rdkit4depict'] = 1 422 | # font size of the axes 423 | options['axes_size'] = 10 424 | # font size of the energy values 425 | options['text_size'] = 10 426 | # use linear lines instead of a polynomial 427 | options['linear_lines'] = 0 428 | 429 | if 'options' in inputs: 430 | for line in inputs['options']: 431 | if line.startswith('title'): 432 | options['title'] = int(line.split()[1]) 433 | elif line.startswith('units'): 434 | options['units'] = line.split()[1] 435 | elif line.startswith('use_xyz'): 436 | options['use_xyz'] = int(line.split()[1]) 437 | elif line.startswith('rescale'): 438 | options['rescale'] = line.split()[1] 439 | elif line.startswith('fh'): 440 | options['fh'] = float(line.split()[1]) 441 | elif line.startswith('fw'): 442 | options['fw'] = float(line.split()[1]) 443 | elif line.startswith('margin'): 444 | options['margin'] = float(line.split()[1]) 445 | elif line.startswith('dpi'): 446 | options['dpi'] = int(line.split()[1]) 447 | elif line.startswith('save'): 448 | if not options['save_from_command_line']: 449 | options['save'] = int(line.split()[1]) 450 | elif line.startswith('write_ts_values'): 451 | options['write_ts_values'] = int(line.split()[1]) 452 | elif line.startswith('write_well_values'): 453 | options['write_well_values'] = int(line.split()[1]) 454 | elif line.startswith('bimol_color'): 455 | options['bimol_color'] = line.split()[1] 456 | elif line.startswith('well_color'): 457 | options['well_color'] = line.split()[1] 458 | elif line.startswith('ts_color'): 459 | options['ts_color'] = line.split()[1] 460 | elif line.startswith('show_images'): 461 | options['show_images'] = int(line.split()[1]) 462 | elif line.startswith('rdkit4depict'): 463 | options['rdkit4depict'] = int(line.split()[1]) 464 | elif line.startswith('axes_size'): 465 | options['axes_size'] = float(line.split()[1]) 466 | elif line.startswith('text_size'): 467 | options['text_size'] = float(line.split()[1]) 468 | elif line.startswith('linear_lines'): 469 | options['linear_lines'] = int(line.split()[1]) 470 | elif line.startswith('#'): 471 | # comment line, don't do anything 472 | continue 473 | else: 474 | if len(line) > 0: 475 | print('Cannot recognize input line:') 476 | print(line) 477 | # end if 478 | # end if 479 | # end for 480 | else: 481 | print('Warning, the input file arcitecture has changed,' + 482 | 'use an "options" input tag to put all the options') 483 | # end if 484 | 485 | for w in inputs['wells']: 486 | w = w.split() 487 | name = w[0] 488 | energy = eval(w[1]) 489 | smi = None 490 | if len(w) > 2: 491 | smi = w[2] 492 | # end if 493 | w = well(name, energy, smi) 494 | wells.append(w) 495 | # end for 496 | for b in inputs['bimolec']: 497 | b = b.split() 498 | name = b[0] 499 | energy = eval(b[1]) 500 | smi = [] 501 | if len(b) > 2: 502 | smi = b[2:] 503 | b = bimolec(name, energy, smi) 504 | bimolecs.append(b) 505 | # end for 506 | 507 | # it is important that the wells and bimolecular products 508 | # are read prior to the transition states and barrierless 509 | # reactions because they need to be added as reactants 510 | # and products 511 | for t in inputs['ts']: 512 | t = t.split() 513 | name = t[0] 514 | energy = eval(t[1]) 515 | names = [t[2], t[3]] 516 | col = 'black' 517 | if len(t) > 4: 518 | col = t[4] 519 | t = ts(name, names, energy, col=col) 520 | tss.append(t) 521 | # end for 522 | for b in inputs['barrierless']: 523 | b = b.split() 524 | name = b[0] 525 | names = [b[1], b[2]] 526 | col = 'black' 527 | if len(b) > 3: 528 | col = b[3] 529 | b = barrierless(name, names, col=col) 530 | barrierlesss.append(b) 531 | # end for 532 | # end def 533 | 534 | 535 | def get_sd_prop(all_lines): 536 | """ 537 | The get_sd_prop method interprets the input file 538 | which has a structure comparable to sdf molecular files 539 | """ 540 | # split all the lines according to the keywords 541 | inputs = all_lines.split('> <') 542 | # do not consider the first keyword, this contains the comments 543 | inputs = inputs[1:] 544 | ret = {} 545 | for inp in inputs: 546 | inp = inp .split('>', 1) 547 | kw = inp[0].lower() # keyword in lower case 548 | val = inp[1].strip().split('\n') # values of the keywords 549 | val = [vi.strip() for vi in val] 550 | val = [vi for vi in val if vi] 551 | ret[kw] = val 552 | # end for 553 | return ret 554 | # end def 555 | 556 | 557 | def position(): 558 | """ 559 | This method find initial position for all the wells, products and 560 | transition states. Initially, the wells are put in the middle and 561 | the products are divided on both sides. The transition states are 562 | positioned inbetween the reactants and products of the reaction 563 | """ 564 | # do the rescaling, i.e. find the y values 565 | # y0 is the energy of the species to which rescaling is done 566 | y0 = next((w.energy for w in wells if w.name == options['rescale']), 0.) 567 | if y0 == 0.: 568 | list = (b.energy for b in bimolecs if b.name == options['rescale']) 569 | y0 = next(list, 0.) 570 | for w in wells: 571 | w.y = w.energy - y0 572 | # end for 573 | for b in bimolecs: 574 | b.y = b.energy - y0 575 | # end for 576 | for t in tss: 577 | t.y = t.energy - y0 578 | # end for 579 | 580 | # find the x values 581 | file_name = '{id}_xval.txt'.format(id=options['id']) 582 | if os.path.exists(file_name): 583 | # if file exists, read the x values 584 | fi = open(file_name, 'r') 585 | a = fi.read() 586 | fi.close() 587 | a = a.split('\n') 588 | for entry in a: 589 | if len(entry) > 0: 590 | name = entry.split(' ')[0] 591 | xval = eval(entry.split(' ')[1]) 592 | for w in wells: 593 | if w.name == name: 594 | w.x = xval 595 | # end for 596 | for b in bimolecs: 597 | if b.name == name: 598 | b.x = xval 599 | # end for 600 | for t in tss: 601 | if t.name == name: 602 | t.x = xval 603 | # end for 604 | # end if 605 | # end for 606 | else: 607 | n = len(bimolecs) # n is the number of bimolecular products 608 | n2 = n // 2 609 | 610 | for i, w in enumerate(wells): 611 | # wells are put in the middle 612 | w.x = n2 + 1 + i + 0. 613 | 614 | # end for 615 | for i, b in enumerate(bimolecs): 616 | # bimolecular products are put on both sides 617 | if i < n2: 618 | b.x = 1 + i + 0. 619 | else: 620 | b.x = len(wells) + 1 + i + 0. 621 | b.right = True 622 | # end if 623 | 624 | # end for 625 | for i, t in enumerate(tss): 626 | # transition states are put inbetween the reactant and proudct 627 | x1 = t.reactant.x 628 | x2 = t.product.x 629 | x3 = (x1+x2)/2. 630 | t.x = x3 631 | # end for 632 | save_x_values() # write the x values to a file 633 | # end if 634 | # end def 635 | 636 | 637 | def generate_lines(): 638 | """ 639 | The method loops over the transition states and barrierless reactions 640 | and creates lines accordingly depending on the x and y coordinates 641 | """ 642 | for t in tss: 643 | line1 = line(t.x, 644 | t.y, 645 | t.reactant.x, 646 | t.reactant.y, 647 | [t, t.reactant], 648 | col=t.color) 649 | line2 = line(t.x, 650 | t.y, 651 | t.product.x, 652 | t.product.y, 653 | [t, t.product], 654 | col=t.color) 655 | t.lines.append(line1) 656 | t.lines.append(line2) 657 | # end for 658 | for b in barrierlesss: 659 | b.line = line(b.reactant.x, 660 | b.reactant.y, 661 | b.product.x, 662 | b.product.y, 663 | [b, b.reactant, b.product], 664 | col=b.color) 665 | # end for 666 | # end def 667 | 668 | 669 | def get_sizes(): 670 | """ 671 | Get the axis lengths and the sizes of the images 672 | """ 673 | global xlow, xhigh, xmargin, ylow, yhigh, ymargin 674 | # TODO: what if wells or bimoleculs is empty, 675 | # min and max functions will give error 676 | if len(bimolecs) > 0: 677 | # x coords of tss are always inbetween stable species 678 | xlow = min(min([w.x for w in wells]), min([b.x for b in bimolecs])) 679 | xhigh = max(max([w.x for w in wells]), max([b.x for b in bimolecs])) 680 | # all tss lie above the lowest well 681 | ylow = min(min([w.y for w in wells]), min([b.y for b in bimolecs])) 682 | else: 683 | # x coords of tss are always inbetween stable species 684 | xlow = min([w.x for w in wells]) 685 | xhigh = max([w.x for w in wells]) 686 | # all tss lie above the lowest well 687 | ylow = min([w.y for w in wells]) 688 | xmargin = options['margin']*(xhigh-xlow) 689 | try: 690 | yhigh = max([t.y for t in tss]) 691 | except ValueError: 692 | yhigh = max([b.y for b in bimolecs]) 693 | yhigh = max(yhigh, max([w.x for w in wells])) 694 | if len(bimolecs) > 0: 695 | yhigh = max(yhigh, max([b.y for b in bimolecs])) 696 | ymargin = options['margin']*(yhigh-ylow) 697 | # end def 698 | 699 | 700 | def plot(): 701 | """ 702 | Plotter method takes all the lines and plots them in one graph 703 | """ 704 | global xlow, xhigh, xmargin, ylow, yhigh, ymargin, xlen 705 | 706 | def showimage(s): 707 | """ 708 | Get the extent and show the image on the plot 709 | s is the structure of which the image is to be 710 | plotted 711 | """ 712 | global ymargin 713 | fn = '{id}_2d/{name}_2d.png'.format(id=options['id'], name=s.name) 714 | if os.path.exists(fn): 715 | img = mpimg.imread(fn) 716 | extent = None 717 | if s.name in extsd: 718 | extent = extsd[s.name] 719 | else: 720 | if not options['rdkit4depict']: 721 | options['dpi'] = 120 722 | # end if 723 | 724 | imy = len(img) + 0. 725 | imx = len(img[0]) + 0. 726 | imw = (xhigh-xlow+0.)/(options['fw']+0.)*imx/options['dpi'] 727 | imh = (yhigh-ylow+0.)/(options['fh']+0.)*imy/options['dpi'] 728 | 729 | if isinstance(s, bimolec): 730 | if s.x > (xhigh-xlow)/2.: 731 | extent = (s.x + xmargin/5., 732 | s.x + xmargin/5. + imw, 733 | s.y-imh/2., 734 | s.y+imh/2.) 735 | else: 736 | extent = (s.x - xmargin/5. - imw, 737 | s.x - xmargin/5., 738 | s.y-imh/2., 739 | s.y+imh/2.) 740 | # end if 741 | else: 742 | extent = (s.x - imw/2., 743 | s.x + imw/2., 744 | s.y-ymargin/5. - imh, 745 | s.y-ymargin/5.) 746 | # end if 747 | # end if 748 | im = ax.imshow(img, aspect='auto', extent=extent, zorder=-1) 749 | # add to dictionary with the well it belongs to as key 750 | imgsd[s] = im 751 | # end if 752 | # end def 753 | lines = [] 754 | for t in tss: 755 | lines.append(t.lines[0]) 756 | lines.append(t.lines[1]) 757 | # end for 758 | for b in barrierlesss: 759 | lines.append(b.line) 760 | # end for 761 | get_sizes() 762 | plt.rcParams["figure.figsize"] = [options['fw'], options['fh']] 763 | plt.rcParams["font.size"] = options['axes_size'] 764 | 765 | matplotlib.rc("figure", facecolor="white") 766 | fig, ax = plt.subplots() 767 | ax.spines['top'].set_visible(False) 768 | ax.spines['bottom'].set_visible(False) 769 | ax.spines['right'].set_visible(False) 770 | 771 | ax.xaxis.set_ticks_position('none') 772 | ax.yaxis.set_ticks_position('left') 773 | ax.set_xticklabels([]) 774 | 775 | if options['show_images']: 776 | for w in wells: 777 | showimage(w) 778 | # end for 779 | for b in bimolecs: 780 | showimage(b) 781 | # end for 782 | save_im_extent() # save the positions of the images to a file 783 | 784 | # draw the lines 785 | # in case of linear lines, calculate the distance of the horizontal pieces 786 | if options['linear_lines']: 787 | xlen = (len(wells) + len(bimolecs)) / (4 * (xhigh - xlow)) 788 | 789 | for i, line in enumerate(lines): 790 | lw = 1.5 791 | alpha = 1.0 792 | ls = 'solid' 793 | if line.color == 'gray': 794 | ls = 'dotted' 795 | elif line.color == 'blue' or line.color == 'b': 796 | ls = 'dashed' 797 | if line.straight_line: 798 | if line.xmin == line.xmax: # plot a vertical line 799 | ymin = min(line.y1, line.y2) 800 | ymax = max(line.y1, line.y2) 801 | a = ax.vlines(x=line.xmin, 802 | ymin=ymin, 803 | ymax=ymax, 804 | color=line.color, 805 | ls=ls, 806 | linewidth=lw, 807 | alpha=alpha) 808 | for struct in line.chemstruct: 809 | # add to the lines dictionary 810 | linesd[struct].append(a) 811 | # end for 812 | else: # plot a horizontal line 813 | a = ax.hlines(y=line.y1, 814 | xmin=line.xmin, 815 | xmax=line.xmax, 816 | color=line.color, 817 | linewidth=lw, 818 | alpha=alpha) 819 | for struct in line.chemstruct: 820 | # add to the lines dictionary 821 | linesd[struct].append(a) 822 | # end for 823 | # end if 824 | else: 825 | if options['linear_lines']: 826 | xlist = [line.xmin, line.xmin + xlen / 2, 827 | line.xmax - xlen / 2, line.xmax] 828 | y = [line.y1, line.y1, line.y2, line.y2] 829 | else: 830 | xlist = np.arange(line.xmin, 831 | line.xmax, 832 | (line.xmax-line.xmin) / 1000) 833 | a = line.coeffs 834 | y = a[0]*xlist**3 + a[1]*xlist**2 + a[2]*xlist + a[3] 835 | pl = ax.plot(xlist, 836 | y, 837 | color=line.color, 838 | ls=ls, 839 | linewidth=lw, 840 | alpha=alpha) 841 | for struct in line.chemstruct: 842 | # add to the lines dictionary 843 | linesd[struct].append(pl) 844 | # end for 845 | # end if 846 | # end for 847 | ax.set_xlim([xlow-xmargin, xhigh+xmargin]) 848 | ax.set_ylim([ylow-ymargin, yhigh+ymargin]) 849 | 850 | # write the name and energies to the plot 851 | for w in wells: 852 | if options['write_well_values']: 853 | t = ax.text(w.x, 854 | w.y-ymargin/10, 855 | '{:.1f}'.format(w.y), 856 | fontdict={'size': options['text_size']}, 857 | ha='center', va='top', 858 | color=options['well_color'], 859 | picker=True) 860 | # add to dictionary with the well it belongs to as key 861 | textd[w] = t 862 | # end if 863 | # end for 864 | for b in bimolecs: 865 | if options['write_well_values']: 866 | # write the text values below the line: 867 | t = ax.text(b.x, 868 | b.y-ymargin/10, 869 | '{:.1f}'.format(b.y), 870 | fontdict={'size': options['text_size']}, 871 | ha='center', 872 | va='top', 873 | color=options['bimol_color'], 874 | picker=True) 875 | # add to dictionary with the well it belongs to as key 876 | textd[b] = t 877 | # end if 878 | # end for 879 | for t in tss: 880 | if options['write_ts_values']: 881 | color = t.color 882 | if options['ts_color'] != 'none': 883 | color = options['ts_color'] 884 | te = ax.text(t.x, 885 | t.y+ymargin/30, 886 | '{:.1f}'.format(t.y), 887 | fontdict={'size': options['text_size']}, 888 | ha='center', 889 | va='bottom', 890 | color=color, 891 | picker=True) 892 | # add to dictionary with the well it belongs to as key 893 | textd[t] = te 894 | # end if 895 | # end for 896 | 897 | sel = selecthandler() 898 | dr = dragimage() 899 | 900 | if options['title']: 901 | plt.title('Potential energy surface of {id}'.format(id=options['id'])) 902 | plt.ylabel('Energy ({units})'.format(units=options['units'])) 903 | if options['save']: 904 | plt.savefig('{id}_pes_plot.png'.format(id=options['id']), 905 | bbox_inches='tight') 906 | else: 907 | plt.show() 908 | # end def 909 | 910 | 911 | def generate_2d_depiction(): 912 | """ 913 | 2D depiction is generated (if not yet available) and 914 | stored in the directory join(input_id, '_2d') 915 | This is only done for the wells and bimolecular products, 916 | 2D of tss need to be supplied by the user 917 | """ 918 | def get_smis(m, smis, files): 919 | # name and path of png file 920 | if len(smis) > 0: 921 | return smis 922 | elif all([os.path.exists(f) for f in files]): 923 | smis = [] 924 | for f in files: 925 | try: 926 | # weird syntax to allow python2 and python3 927 | # python2: obmol = pybel.readfile('xyz', f).next() 928 | # python3: obmol = pybel.readfile('xyz', f).__next__() 929 | obmol = [mol for mol in pybel.readfile('xyz', f)][-1] 930 | smis.append(obmol.write("smi").split()[0]) 931 | except NameError: 932 | print('Could not generate smiles for {n}'.format(n=m.name)) 933 | # end try 934 | # end for 935 | return smis 936 | # end def 937 | 938 | def generate_2d(m, smis): 939 | # name and path of png file 940 | png = '{id}_2d/{name}_2d.png'.format(id=options['id'], name=m.name) 941 | if not os.path.exists(png): 942 | if len(smis) > 0: 943 | smi = '.'.join(smis) 944 | try: 945 | options['rdkit4depict'] = 0 946 | obmol = pybel.readstring("smi", smi) 947 | obmol.draw(show=False, filename=png) 948 | except NameError: 949 | try: 950 | mol = Chem.MolFromSmiles(smi) 951 | AllChem.Compute2DCoords(mol) 952 | cc = mol.GetConformer() 953 | xx = [] 954 | yy = [] 955 | for i in range(cc.GetNumAtoms()): 956 | pos = cc.GetAtomPosition(i) 957 | xx.append(pos.x) 958 | yy.append(pos.y) 959 | # end for 960 | sc = 50 961 | dx = (max(xx)-min(xx))*sc+30 962 | dy = (max(yy)-min(yy))*sc+30 963 | if not isinstance(dx, int): 964 | dx = int(dx) 965 | if not isinstance(dy, int): 966 | dy = int(dy) 967 | Draw.MolToFile(mol, png, size=(dx, dy), kekulize=False) 968 | except NameError: 969 | print('Could not generate 2d for {n}'.format(n=m.name)) 970 | return 971 | # end try 972 | # end try 973 | 974 | im = Image.open(png) 975 | im.load() 976 | ix_low = im.getbbox()[0] 977 | ix_high = im.getbbox()[2] 978 | iy_low = im.getbbox()[1] 979 | iy_high = im.getbbox()[3] 980 | 981 | ar = np.asarray(im) 982 | 983 | for i, row in enumerate(ar): 984 | if not all([all([ci == 255 for ci in c]) for c in row]): 985 | if i > 10: 986 | iy_low = i-10 987 | else: 988 | iy_low = i 989 | # end if 990 | break 991 | # end if 992 | # end for 993 | for j, col in enumerate(ar.swapaxes(0, 1)): 994 | if not all([all([ci == 255 for ci in c]) for c in col]): 995 | if j > 10: 996 | ix_low = j-10 997 | else: 998 | ix_low = j 999 | # end if 1000 | break 1001 | # end if 1002 | # end for 1003 | for k, revrow in reversed(list(enumerate(ar))): 1004 | if not all([all([ci == 255 for ci in c]) for c in revrow]): 1005 | if k < iy_high - 10: 1006 | iy_high = k+10 1007 | else: 1008 | iy_high = k 1009 | # end if 1010 | break 1011 | # end if 1012 | # end for 1013 | for l, revcol in reversed(list(enumerate(ar.swapaxes(0, 1)))): 1014 | if not all([all([ci == 255 for ci in c]) for c in revcol]): 1015 | if k < ix_high - 10: 1016 | ix_high = l+10 1017 | else: 1018 | ix_high = l 1019 | # end if 1020 | break 1021 | # end if 1022 | # end for 1023 | im.crop((ix_low, iy_low, ix_high, iy_high)).save(png) 1024 | else: 1025 | # (TODO) add warning messages 1026 | print('Could not generate 2d for {name}'.format(name=m.name)) 1027 | return 1028 | # end if 1029 | # end if 1030 | # end def 1031 | # make the directory with the 2d depictions, if not yet available 1032 | dir = options['id'] + '_2d' 1033 | try: 1034 | os.stat(dir) 1035 | except: 1036 | os.mkdir(dir) 1037 | for w in wells: 1038 | s = [] 1039 | if w.smi is not None: 1040 | s.append(w.smi) 1041 | generate_2d(w, get_smis(w, s, w.xyz_files)) 1042 | # end for 1043 | for b in bimolecs: 1044 | generate_2d(b, get_smis(b, b.smi, b.xyz_files)) 1045 | # end for 1046 | # end def 1047 | 1048 | 1049 | def updateplot(struct, x_change): 1050 | """ 1051 | Update the plot after the drag event of a stationary point (struct), 1052 | move all related objects by x_change in the x direction, 1053 | and regenerate the corresponding lines 1054 | """ 1055 | global xlow, xhigh, xmargin, ylow, yhigh, ymargin, xlen 1056 | # set the new sizes of the figure 1057 | get_sizes() 1058 | plt.gca().set_xlim([xlow-xmargin, xhigh+xmargin]) 1059 | plt.gca().set_ylim([ylow-ymargin, yhigh+ymargin]) 1060 | # generate new coordinates for the images 1061 | if struct in imgsd: 1062 | old_extent = imgsd[struct].get_extent() 1063 | extent_change = (x_change, x_change, 0, 0) 1064 | extent = [old_extent[i] + extent_change[i] for i in range(0, 4)] 1065 | imgsd[struct].set_extent(extent=extent) 1066 | # end if 1067 | # generate new coordinates for the text 1068 | if struct in textd: 1069 | old_pos = textd[struct].get_position() 1070 | new_pos = (old_pos[0]+x_change, old_pos[1]) 1071 | textd[struct].set_position(new_pos) 1072 | # generate new coordinates for the lines 1073 | for t in tss: 1074 | if (struct == t or struct == t.reactant or struct == t.product): 1075 | t.lines[0] = line(t.x, 1076 | t.y, 1077 | t.reactant.x, 1078 | t.reactant.y, 1079 | [t, t.reactant], 1080 | col=t.color) 1081 | t.lines[1] = line(t.x, 1082 | t.y, 1083 | t.product.x, 1084 | t.product.y, 1085 | [t, t.product], 1086 | col=t.color) 1087 | for i in range(0, 2): 1088 | li = t.lines[i] 1089 | if li.straight_line: 1090 | print('straight line') 1091 | else: 1092 | if options['linear_lines']: 1093 | xlist = [li.xmin, li.xmin + xlen / 2, 1094 | li.xmax - xlen / 2, li.xmax] 1095 | y = [li.y1, li.y1, li.y2, li.y2] 1096 | else: 1097 | xlist = np.arange(li.xmin, 1098 | li.xmax, 1099 | (li.xmax-li.xmin) / 1000) 1100 | a = li.coeffs 1101 | y = a[0]*xlist**3 + a[1]*xlist**2 + a[2]*xlist + a[3] 1102 | linesd[t][i][0].set_xdata(xlist) 1103 | linesd[t][i][0].set_ydata(y) 1104 | # end if 1105 | # end for 1106 | # end if 1107 | # end for 1108 | for b in barrierlesss: 1109 | if (struct == b.reactant or struct == b.product): 1110 | b.line = line(b.reactant.x, 1111 | b.reactant.y, 1112 | b.product.x, 1113 | b.product.y, 1114 | [b.reactant, b.product], 1115 | col=b.color) 1116 | li = b.line 1117 | if li.straight_line: 1118 | print('straight line') 1119 | else: 1120 | if options['linear_lines']: 1121 | xlist = [li.xmin, li.xmin + xlen / 2, 1122 | li.xmax - xlen / 2, li.xmax] 1123 | y = [li.y1, li.y1, li.y2, li.y2] 1124 | else: 1125 | xlist = np.arange(li.xmin, li.xmax, (li.xmax-li.xmin) / 1000) 1126 | a = li.coeffs 1127 | y = a[0]*xlist**3 + a[1]*xlist**2 + a[2]*xlist + a[3] 1128 | linesd[b][0][0].set_xdata(xlist) 1129 | linesd[b][0][0].set_ydata(y) 1130 | # end if 1131 | # end if 1132 | # end for 1133 | plt.draw() 1134 | # end def 1135 | 1136 | 1137 | def highlight_structure(struct=None): 1138 | """ 1139 | for all the lines, text and structures that are not 1140 | directly connected to struct, set alpha to 0.15 1141 | """ 1142 | if struct is None: 1143 | alpha = 1. 1144 | else: 1145 | alpha = 0.15 1146 | # end if 1147 | # get all the tss and barrierlesss with this struct 1148 | highlight = [] 1149 | lines = [] 1150 | for t in tss: 1151 | if t == struct or t.reactant == struct or t.product == struct: 1152 | highlight.append(t) 1153 | if t.reactant not in highlight: 1154 | highlight.append(t.reactant) 1155 | if t.product not in highlight: 1156 | highlight.append(t.product) 1157 | lines = lines + linesd[t] 1158 | # end if 1159 | # end for 1160 | for b in barrierlesss: 1161 | if b.reactant == struct or b.product == struct: 1162 | highlight.append(b) 1163 | if b.reactant not in highlight: 1164 | highlight.append(b.reactant) 1165 | if b.product not in highlight: 1166 | highlight.append(b.product) 1167 | lines = lines + linesd[b] 1168 | # end if 1169 | # end for 1170 | for struct in linesd: 1171 | for li in linesd[struct]: 1172 | if li in lines: 1173 | li[0].set_alpha(1.) 1174 | else: 1175 | li[0].set_alpha(alpha) 1176 | # end if 1177 | # end for 1178 | # end for 1179 | for struct in textd: 1180 | if struct in highlight: 1181 | textd[struct].set_alpha(1.) 1182 | else: 1183 | textd[struct].set_alpha(alpha) 1184 | # end if 1185 | # end for 1186 | for struct in imgsd: 1187 | if struct in highlight: 1188 | imgsd[struct].set_alpha(1.) 1189 | else: 1190 | imgsd[struct].set_alpha(alpha) 1191 | # end if 1192 | # end for 1193 | plt.draw() 1194 | # end def 1195 | 1196 | 1197 | def save_x_values(): 1198 | """ 1199 | save the x values of the stationary points to an external file 1200 | """ 1201 | fi = open('{id}_xval.txt'.format(id=options['id']), 'w') 1202 | if len(wells) > 0: 1203 | lines = ['{n} {v:.2f}'.format(n=w.name, v=w.x) for w in wells] 1204 | fi.write('\n'.join(lines)+'\n') 1205 | if len(bimolecs) > 0: 1206 | lines = ['{n} {v:.2f}'.format(n=b.name, v=b.x) for b in bimolecs] 1207 | fi.write('\n'.join(lines)+'\n') 1208 | if len(tss) > 0: 1209 | lines = ['{n} {v:.2f}'.format(n=t.name, v=t.x) for t in tss] 1210 | fi.write('\n'.join(lines)) 1211 | fi.close() 1212 | # end def 1213 | 1214 | 1215 | def save_im_extent(): 1216 | """ 1217 | save the x values of the stationary points to an external file 1218 | """ 1219 | fi = open('{id}_im_extent.txt'.format(id=options['id']), 'w') 1220 | for key in imgsd: 1221 | e = imgsd[key].get_extent() 1222 | vals = '{:.2f} {:.2f} {:.2f} {:.2f}'.format(e[0], e[1], e[2], e[3]) 1223 | fi.write('{name} {vals}\n'.format(name=key.name, vals=vals)) 1224 | fi.close() 1225 | # end def 1226 | 1227 | 1228 | def read_im_extent(): 1229 | """ 1230 | Read the extents of the images if they are present in a file_name 1231 | """ 1232 | fname = '{id}_im_extent.txt'.format(id=options['id']) 1233 | if os.path.exists(fname): 1234 | fi = open(fname, 'r') 1235 | a = fi.read() 1236 | fi.close() 1237 | a = a.split('\n') 1238 | for entry in a: 1239 | pieces = entry.split(' ') 1240 | if len(pieces) == 5: 1241 | extsd[pieces[0]] = [eval(pieces[i]) for i in range(1, 5)] 1242 | # end if 1243 | # end for 1244 | # end if 1245 | # end def 1246 | 1247 | 1248 | def main(argv): 1249 | """ 1250 | Main method to run the PESViewer 1251 | """ 1252 | if len(argv) > 1: # read the arguments 1253 | fname = argv[1] 1254 | options['save'] = 0 1255 | options['save_from_command_line'] = 0 1256 | if len(argv) > 2: 1257 | # argument to specify whether plot needs to be saved or displayed 1258 | if argv[2] == 'save': 1259 | options['save'] = 1 1260 | options['save_from_command_line'] = 1 1261 | elif len(argv) == 1: 1262 | print('To use the pesviewer, supply an input file as argument.') 1263 | sys.exit(-1) 1264 | # end if 1265 | read_input(fname) # read the input file 1266 | # initialize the dictionaries 1267 | for w in wells: 1268 | linesd[w] = [] 1269 | # end for 1270 | for b in bimolecs: 1271 | linesd[b] = [] 1272 | # end for 1273 | for t in tss: 1274 | linesd[t] = [] 1275 | # end for 1276 | for b in barrierlesss: 1277 | linesd[b] = [] 1278 | # end for 1279 | read_im_extent() # read the position of the images, if known 1280 | position() # find initial positions for all the species on the graph 1281 | generate_lines() # generate all the line 1282 | # generate 2d depiction from the smiles or 3D structure, 1283 | # store them in join(input_id, '_2d') 1284 | generate_2d_depiction() 1285 | plot() # plot the graph 1286 | # end def 1287 | 1288 | 1289 | main(sys.argv) 1290 | --------------------------------------------------------------------------------