diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index d24eb6b540..0000000000 --- a/.coveragerc +++ /dev/null @@ -1,22 +0,0 @@ -# .coveragerc to control coverage.py -[run] -branch = True -source = - rmgpy - rmg.py -omit = *Test.py - -[report] -show_missing = False -exclude_lines = - pragma: no cover - def __repr__ - if self.debug: - if settings.DEBUG - raise AssertionError - raise NotImplementedError - if 0: - if __name__ == .__main__.: - -[html] -directory = testing/coverage diff --git a/examples/rmg/1,3-hexadiene/input.py b/examples/rmg/1,3-hexadiene/input.py index ff5955127f..b6dbf0ca14 100644 --- a/examples/rmg/1,3-hexadiene/input.py +++ b/examples/rmg/1,3-hexadiene/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], # 'all', 'default'==['training'], [], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/TEOS/input.py b/examples/rmg/TEOS/input.py index 0677bf7685..44fecc5b47 100644 --- a/examples/rmg/TEOS/input.py +++ b/examples/rmg/TEOS/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/c3h4/input.py b/examples/rmg/c3h4/input.py index 1af1e146fd..ffc50b4d3b 100644 --- a/examples/rmg/c3h4/input.py +++ b/examples/rmg/c3h4/input.py @@ -1,10 +1,10 @@ # Data sources database( - thermoLibraries = ['primaryThermoLibrary', 'GRI-Mech3.0'], + thermoLibraries = ['primaryThermoLibrary', 'GRI-Mech3.0-N'], reactionLibraries = [], - seedMechanisms = [], + seedMechanisms = ['GRI-Mech3.0-N'], kineticsDepositories = ['training'], # 'all', 'default'==['training'], [], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/ch3no2/input.py b/examples/rmg/ch3no2/input.py new file mode 100644 index 0000000000..6490f928b2 --- /dev/null +++ b/examples/rmg/ch3no2/input.py @@ -0,0 +1,86 @@ +# Data sources +database( + thermoLibraries = ['KlippensteinH2O2', 'primaryThermoLibrary','DFT_QCI_thermo','CH','CHN','CHO','CHON','CN','NISTThermoLibrary','thermo_DFT_CCSDTF12_BAC','GRI-Mech3.0-N'], + reactionLibraries = [('Nitrogen_Dean_and_Bozelli',False)], + seedMechanisms = ['ERC-FoundationFuelv0.9'], + kineticsDepositories = ['training'], + kineticsFamilies = ['!Intra_Disproportionation', '!Substitution_O'], + kineticsEstimator = 'rate rules', +) + +# Constraints on generated species +generatedSpeciesConstraints( + #maximumCarbonAtoms = 7, + #maximumHydrogenAtoms = 8, + #maximumOxygenAtoms = 5, + maximumNitrogenAtoms = 2, + #maximumSiliconAtoms = 0, + #maximumSulfurAtoms = 0, + #maximumHeavyAtoms = 3, + maximumRadicalElectrons = 2, +) + +# List of species +species( + label='CH3NO2', + reactive=True, + structure=adjacencyList( + """ + 1 C 0 0 {2,S} {3,S} {4,S} {5,S} + 2 H 0 0 {1,S} + 3 H 0 0 {1,S} + 4 H 0 0 {1,S} + 5 N 0 0 {1,S} {6,D} {7,S} + 6 O 0 2 {5,D} + 7 O 0 3 {5,S} + """), +) + +species( + label='O2', + reactive=True, + structure=adjacencyList( + """ + 1 O 1 2 {2,S} + 2 O 1 2 {1,S} + """), +) + +species( + label='N2', + reactive=True, + structure=adjacencyList( + """ + 1 N 1 1 {2,T} + 2 N 1 1 {1,T} + """), +) + +# Reaction systems + +simpleReactor( + temperature=(1500,'K'), + pressure=(10.0,'bar'), + initialMoleFractions={ + "CH3NO2": 0.1, + "O2": 0.21, + "N2": 0.69, + }, + terminationConversion={ + 'CH3NO2': 0.1, + }, +) + +model( + toleranceKeepInEdge=1e-5, + toleranceMoveToCore=0.1, + toleranceInterruptSimulation=0.1, + maximumEdgeSpecies=10000 +) + +options( + units='si', + saveRestartPeriod=None, + drawMolecules=False, + generatePlots=False, +) diff --git a/examples/rmg/diesel/input.py b/examples/rmg/diesel/input.py index 43389bc19f..1b3a010db4 100644 --- a/examples/rmg/diesel/input.py +++ b/examples/rmg/diesel/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], # 'all', 'default'==['training'], [], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/e85/input.py b/examples/rmg/e85/input.py index 95371e2951..6ffb04c275 100644 --- a/examples/rmg/e85/input.py +++ b/examples/rmg/e85/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = ['GRI-Mech3.0'], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/liquid_phase/input.py b/examples/rmg/liquid_phase/input.py index 236771e8a4..6e9f22839b 100644 --- a/examples/rmg/liquid_phase/input.py +++ b/examples/rmg/liquid_phase/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/methylformate/input.py b/examples/rmg/methylformate/input.py index 9ffd0bdb38..ccd3989236 100644 --- a/examples/rmg/methylformate/input.py +++ b/examples/rmg/methylformate/input.py @@ -4,7 +4,7 @@ reactionLibraries = [('Methylformate',False),('Glarborg/highP',False)], seedMechanisms = ['Glarborg/C2'], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/minimal/input.py b/examples/rmg/minimal/input.py index 10839f4cba..5e5065847b 100644 --- a/examples/rmg/minimal/input.py +++ b/examples/rmg/minimal/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/examples/rmg/minimal_sensitivity/input.py b/examples/rmg/minimal_sensitivity/input.py index bd62ddf77a..c1c57f0936 100644 --- a/examples/rmg/minimal_sensitivity/input.py +++ b/examples/rmg/minimal_sensitivity/input.py @@ -4,7 +4,7 @@ reactionLibraries = [], seedMechanisms = [], kineticsDepositories = ['training'], - kineticsFamilies = ['!Intra_Disproportionation'], + kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'], kineticsEstimator = 'rate rules', ) diff --git a/external/cclib/LICENSE b/external/cclib/LICENSE index 5faba9d48c..5ab7695ab8 100644 --- a/external/cclib/LICENSE +++ b/external/cclib/LICENSE @@ -1,504 +1,504 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/external/cclib/__init__.py b/external/cclib/__init__.py index a3b4673dcd..1c94f946c3 100644 --- a/external/cclib/__init__.py +++ b/external/cclib/__init__.py @@ -1,18 +1,18 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006-2010, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 888 $" -__version__ = "1.0" - -import parser -import progress -import method -import bridge - -# The test module can be imported if it was installed with cclib. -try: - import test -except: - pass +""" +cclib (http://cclib.sf.net) is (c) 2006-2010, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 888 $" +__version__ = "1.0" + +import parser +import progress +import method +import bridge + +# The test module can be imported if it was installed with cclib. +try: + import test +except: + pass diff --git a/external/cclib/bridge/__init__.py b/external/cclib/bridge/__init__.py index abad492fe1..109ce6fb7f 100644 --- a/external/cclib/bridge/__init__.py +++ b/external/cclib/bridge/__init__.py @@ -1,25 +1,25 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 839 $" - -try: - import openbabel -except Exception: - pass -else: - from cclib2openbabel import makeopenbabel - -try: - import PyQuante -except ImportError: - pass -else: - from cclib2pyquante import makepyquante - -try: - from cclib2biopython import makebiopython -except ImportError: - pass +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 839 $" + +try: + import openbabel +except Exception: + pass +else: + from cclib2openbabel import makeopenbabel + +try: + import PyQuante +except ImportError: + pass +else: + from cclib2pyquante import makepyquante + +try: + from cclib2biopython import makebiopython +except ImportError: + pass diff --git a/external/cclib/bridge/cclib2biopython.py b/external/cclib/bridge/cclib2biopython.py index 55cf4d0850..a4ff08dc16 100644 --- a/external/cclib/bridge/cclib2biopython.py +++ b/external/cclib/bridge/cclib2biopython.py @@ -1,35 +1,35 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 709 $" - -from Bio.PDB.Atom import Atom -from cclib.parser.utils import PeriodicTable - -def makebiopython(atomcoords, atomnos): - """Create a list of BioPython Atoms. - - This creates a list of BioPython Atoms suitable for use - by Bio.PDB.Superimposer, for example. - - >>> import numpy - >>> from Bio.PDB.Superimposer import Superimposer - >>> atomnos = numpy.array([1,8,1],"i") - >>> a = numpy.array([[-1,1,0],[0,0,0],[1,1,0]],"f") - >>> b = numpy.array([[1.1,2,0],[1,1,0],[2,1,0]],"f") - >>> si = Superimposer() - >>> si.set_atoms(makebiopython(a,atomnos),makebiopython(b,atomnos)) - >>> print si.rms - 0.29337859596 - """ - pt = PeriodicTable() - bioatoms = [] - for coords, atomno in zip(atomcoords, atomnos): - bioatoms.append(Atom(pt.element[atomno], coords, 0, 0, 0, 0, 0)) - return bioatoms - -if __name__ == "__main__": - import doctest - doctest.testmod() +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 709 $" + +from Bio.PDB.Atom import Atom +from cclib.parser.utils import PeriodicTable + +def makebiopython(atomcoords, atomnos): + """Create a list of BioPython Atoms. + + This creates a list of BioPython Atoms suitable for use + by Bio.PDB.Superimposer, for example. + + >>> import numpy + >>> from Bio.PDB.Superimposer import Superimposer + >>> atomnos = numpy.array([1,8,1],"i") + >>> a = numpy.array([[-1,1,0],[0,0,0],[1,1,0]],"f") + >>> b = numpy.array([[1.1,2,0],[1,1,0],[2,1,0]],"f") + >>> si = Superimposer() + >>> si.set_atoms(makebiopython(a,atomnos),makebiopython(b,atomnos)) + >>> print si.rms + 0.29337859596 + """ + pt = PeriodicTable() + bioatoms = [] + for coords, atomno in zip(atomcoords, atomnos): + bioatoms.append(Atom(pt.element[atomno], coords, 0, 0, 0, 0, 0)) + return bioatoms + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/external/cclib/bridge/cclib2openbabel.py b/external/cclib/bridge/cclib2openbabel.py index 50ae264c13..fd645ea5a6 100644 --- a/external/cclib/bridge/cclib2openbabel.py +++ b/external/cclib/bridge/cclib2openbabel.py @@ -1,39 +1,39 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 867 $" - -import openbabel as ob - -def makeopenbabel(atomcoords, atomnos, charge=0, mult=1): - """Create an Open Babel molecule. - - >>> import numpy, openbabel - >>> atomnos = numpy.array([1,8,1],"i") - >>> coords = numpy.array([[-1.,1.,0.],[0.,0.,0.],[1.,1.,0.]]) - >>> obmol = makeopenbabel(coords, atomnos) - >>> obconversion = openbabel.OBConversion() - >>> formatok = obconversion.SetOutFormat("inchi") - >>> print obconversion.WriteString(obmol).strip() - InChI=1/H2O/h1H2 - """ - obmol = ob.OBMol() - for i in range(len(atomnos)): - # Note that list(atomcoords[i]) is not equivalent!!! - coords = atomcoords[i].tolist() - atomno = int(atomnos[i]) - obatom = ob.OBAtom() - obatom.SetAtomicNum(atomno) - obatom.SetVector(*coords) - obmol.AddAtom(obatom) - obmol.ConnectTheDots() - obmol.PerceiveBondOrders() - obmol.SetTotalSpinMultiplicity(mult) - obmol.SetTotalCharge(charge) - return obmol - -if __name__ == "__main__": - import doctest - doctest.testmod() +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 867 $" + +import openbabel as ob + +def makeopenbabel(atomcoords, atomnos, charge=0, mult=1): + """Create an Open Babel molecule. + + >>> import numpy, openbabel + >>> atomnos = numpy.array([1,8,1],"i") + >>> coords = numpy.array([[-1.,1.,0.],[0.,0.,0.],[1.,1.,0.]]) + >>> obmol = makeopenbabel(coords, atomnos) + >>> obconversion = openbabel.OBConversion() + >>> formatok = obconversion.SetOutFormat("inchi") + >>> print obconversion.WriteString(obmol).strip() + InChI=1/H2O/h1H2 + """ + obmol = ob.OBMol() + for i in range(len(atomnos)): + # Note that list(atomcoords[i]) is not equivalent!!! + coords = atomcoords[i].tolist() + atomno = int(atomnos[i]) + obatom = ob.OBAtom() + obatom.SetAtomicNum(atomno) + obatom.SetVector(*coords) + obmol.AddAtom(obatom) + obmol.ConnectTheDots() + obmol.PerceiveBondOrders() + obmol.SetTotalSpinMultiplicity(mult) + obmol.SetTotalCharge(charge) + return obmol + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/external/cclib/bridge/cclib2pyquante.py b/external/cclib/bridge/cclib2pyquante.py index 40de057a1d..fc24f84cff 100644 --- a/external/cclib/bridge/cclib2pyquante.py +++ b/external/cclib/bridge/cclib2pyquante.py @@ -1,27 +1,27 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 737 $" - -from PyQuante.Molecule import Molecule - -def makepyquante(atomcoords, atomnos, charge=0, mult=1): - """Create a PyQuante Molecule. - - >>> import numpy - >>> from PyQuante.hartree_fock import hf - >>> atomnos = numpy.array([1,8,1],"i") - >>> a = numpy.array([[-1,1,0],[0,0,0],[1,1,0]],"f") - >>> pyqmol = makepyquante(a,atomnos) - >>> en,orbe,orbs = hf(pyqmol) - >>> print int(en * 10) / 10. # Should be around -73.8 - -73.8 - """ - return Molecule("notitle", zip(atomnos, atomcoords), units="Angstrom", - charge=charge, multiplicity=mult) - -if __name__ == "__main__": - import doctest - doctest.testmod() +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 737 $" + +from PyQuante.Molecule import Molecule + +def makepyquante(atomcoords, atomnos, charge=0, mult=1): + """Create a PyQuante Molecule. + + >>> import numpy + >>> from PyQuante.hartree_fock import hf + >>> atomnos = numpy.array([1,8,1],"i") + >>> a = numpy.array([[-1,1,0],[0,0,0],[1,1,0]],"f") + >>> pyqmol = makepyquante(a,atomnos) + >>> en,orbe,orbs = hf(pyqmol) + >>> print int(en * 10) / 10. # Should be around -73.8 + -73.8 + """ + return Molecule("notitle", zip(atomnos, atomcoords), units="Angstrom", + charge=charge, multiplicity=mult) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/external/cclib/method/__init__.py b/external/cclib/method/__init__.py index dae41a8d68..b8f2d2f0e4 100644 --- a/external/cclib/method/__init__.py +++ b/external/cclib/method/__init__.py @@ -1,15 +1,15 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 631 $" - -from density import Density -from cspa import CSPA -from mpa import MPA -from lpa import LPA -from opa import OPA -from mbo import MBO -from fragments import FragmentAnalysis -from cda import CDA +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 631 $" + +from density import Density +from cspa import CSPA +from mpa import MPA +from lpa import LPA +from opa import OPA +from mbo import MBO +from fragments import FragmentAnalysis +from cda import CDA diff --git a/external/cclib/method/calculationmethod.py b/external/cclib/method/calculationmethod.py index b0e08dd52f..914d88b26e 100644 --- a/external/cclib/method/calculationmethod.py +++ b/external/cclib/method/calculationmethod.py @@ -1,44 +1,44 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 733 $" - -import logging -import sys - - -class Method(object): - """Abstract class for logfile objects. - - Subclasses defined by cclib: - Density, Fragments, OPA, Population - - Attributes: - data - ccData source data object - """ - def __init__(self, data, progress=None, - loglevel=logging.INFO, logname="Log"): - """Initialise the Logfile object. - - Typically called by subclasses in their own __init__ methods. - """ - - self.data = data - self.progress = progress - self.loglevel = loglevel - self.logname = logname - - # Set up the logger. - self.logger = logging.getLogger('%s %s' % (self.logname, self.data)) - self.logger.setLevel(self.loglevel) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(logging.Formatter( - "[%(name)s %(levelname)s] %(message)s")) - self.logger.addHandler(handler) - - -if __name__ == "__main__": - import doctest, calculationmethod - doctest.testmod(calculationmethod, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 733 $" + +import logging +import sys + + +class Method(object): + """Abstract class for logfile objects. + + Subclasses defined by cclib: + Density, Fragments, OPA, Population + + Attributes: + data - ccData source data object + """ + def __init__(self, data, progress=None, + loglevel=logging.INFO, logname="Log"): + """Initialise the Logfile object. + + Typically called by subclasses in their own __init__ methods. + """ + + self.data = data + self.progress = progress + self.loglevel = loglevel + self.logname = logname + + # Set up the logger. + self.logger = logging.getLogger('%s %s' % (self.logname, self.data)) + self.logger.setLevel(self.loglevel) + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter( + "[%(name)s %(levelname)s] %(message)s")) + self.logger.addHandler(handler) + + +if __name__ == "__main__": + import doctest, calculationmethod + doctest.testmod(calculationmethod, verbose=False) diff --git a/external/cclib/method/cda.py b/external/cclib/method/cda.py index 96b80e2300..bcc0d61c8a 100644 --- a/external/cclib/method/cda.py +++ b/external/cclib/method/cda.py @@ -1,123 +1,123 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 453 $" - -import random # For sometimes running the progress updater - -import numpy - -from fragments import FragmentAnalysis - - -class CDA(FragmentAnalysis): - """Charge Decomposition Analysis (CDA)""" - - def __init__(self, *args): - - # Call the __init__ method of the superclass. - super(FragmentAnalysis, self).__init__(logname="CDA", *args) - - def __str__(self): - """Return a string representation of the object.""" - return "CDA of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'CDA("%s")' % (self.data) - - def calculate(self, fragments, cupdate=0.05): - """Perform the charge decomposition analysis. - - Inputs: - fragments - list of ccData data objects - """ - - - retval = super(CDA, self).calculate(fragments, cupdate) - if not retval: - return False - - # At this point, there should be a mocoeffs and fooverlaps - # in analogy to a ccData object. - - donations = [] - bdonations = [] - repulsions = [] - residuals = [] - - if len(self.mocoeffs) == 2: - occs = 1 - else: - occs = 2 - - # Intialize progress if available. - nstep = self.data.homos[0] - if len(self.data.homos) == 2: - nstep += self.data.homos[1] - if self.progress: - self.progress.initialize(nstep) - - # Begin the actual method. - step = 0 - for spin in range(len(self.mocoeffs)): - - size = len(self.mocoeffs[spin]) - homo = self.data.homos[spin] - - if len(fragments[0].homos) == 2: - homoa = fragments[0].homos[spin] - else: - homoa = fragments[0].homos[0] - - if len(fragments[1].homos) == 2: - homob = fragments[1].homos[spin] - else: - homob = fragments[1].homos[0] - - offset = fragments[0].nbasis - - self.logger.info("Creating donations, bdonations, and repulsions: array[]") - donations.append(numpy.zeros(size, "d")) - bdonations.append(numpy.zeros(size, "d")) - repulsions.append(numpy.zeros(size, "d")) - residuals.append(numpy.zeros(size, "d")) - - for i in range(self.data.homos[spin] + 1): - - # Calculate donation for each MO. - for k in range(0, homoa + 1): - for n in range(offset + homob + 1, self.data.nbasis): - donations[spin][i] += 2 * occs * self.mocoeffs[spin][i,k] \ - * self.mocoeffs[spin][i,n] * self.fooverlaps[k][n] - - for l in range(offset, offset + homob + 1): - for m in range(homoa + 1, offset): - bdonations[spin][i] += 2 * occs * self.mocoeffs[spin][i,l] \ - * self.mocoeffs[spin][i,m] * self.fooverlaps[l][m] - - for k in range(0, homoa + 1): - for m in range(offset, offset+homob + 1): - repulsions[spin][i] += 2 * occs * self.mocoeffs[spin][i,k] \ - * self.mocoeffs[spin][i, m] * self.fooverlaps[k][m] - - for m in range(homoa + 1, offset): - for n in range(offset + homob + 1, self.data.nbasis): - residuals[spin][i] += 2 * occs * self.mocoeffs[spin][i,m] \ - * self.mocoeffs[spin][i, n] * self.fooverlaps[m][n] - - step += 1 - if self.progress and random.random() < cupdate: - self.progress.update(step, "Charge Decomposition Analysis...") - - if self.progress: - self.progress.update(nstep, "Done.") - - self.donations = donations - self.bdonations = bdonations - self.repulsions = repulsions - self.residuals = residuals - - return True +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 453 $" + +import random # For sometimes running the progress updater + +import numpy + +from fragments import FragmentAnalysis + + +class CDA(FragmentAnalysis): + """Charge Decomposition Analysis (CDA)""" + + def __init__(self, *args): + + # Call the __init__ method of the superclass. + super(FragmentAnalysis, self).__init__(logname="CDA", *args) + + def __str__(self): + """Return a string representation of the object.""" + return "CDA of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'CDA("%s")' % (self.data) + + def calculate(self, fragments, cupdate=0.05): + """Perform the charge decomposition analysis. + + Inputs: + fragments - list of ccData data objects + """ + + + retval = super(CDA, self).calculate(fragments, cupdate) + if not retval: + return False + + # At this point, there should be a mocoeffs and fooverlaps + # in analogy to a ccData object. + + donations = [] + bdonations = [] + repulsions = [] + residuals = [] + + if len(self.mocoeffs) == 2: + occs = 1 + else: + occs = 2 + + # Intialize progress if available. + nstep = self.data.homos[0] + if len(self.data.homos) == 2: + nstep += self.data.homos[1] + if self.progress: + self.progress.initialize(nstep) + + # Begin the actual method. + step = 0 + for spin in range(len(self.mocoeffs)): + + size = len(self.mocoeffs[spin]) + homo = self.data.homos[spin] + + if len(fragments[0].homos) == 2: + homoa = fragments[0].homos[spin] + else: + homoa = fragments[0].homos[0] + + if len(fragments[1].homos) == 2: + homob = fragments[1].homos[spin] + else: + homob = fragments[1].homos[0] + + offset = fragments[0].nbasis + + self.logger.info("Creating donations, bdonations, and repulsions: array[]") + donations.append(numpy.zeros(size, "d")) + bdonations.append(numpy.zeros(size, "d")) + repulsions.append(numpy.zeros(size, "d")) + residuals.append(numpy.zeros(size, "d")) + + for i in range(self.data.homos[spin] + 1): + + # Calculate donation for each MO. + for k in range(0, homoa + 1): + for n in range(offset + homob + 1, self.data.nbasis): + donations[spin][i] += 2 * occs * self.mocoeffs[spin][i,k] \ + * self.mocoeffs[spin][i,n] * self.fooverlaps[k][n] + + for l in range(offset, offset + homob + 1): + for m in range(homoa + 1, offset): + bdonations[spin][i] += 2 * occs * self.mocoeffs[spin][i,l] \ + * self.mocoeffs[spin][i,m] * self.fooverlaps[l][m] + + for k in range(0, homoa + 1): + for m in range(offset, offset+homob + 1): + repulsions[spin][i] += 2 * occs * self.mocoeffs[spin][i,k] \ + * self.mocoeffs[spin][i, m] * self.fooverlaps[k][m] + + for m in range(homoa + 1, offset): + for n in range(offset + homob + 1, self.data.nbasis): + residuals[spin][i] += 2 * occs * self.mocoeffs[spin][i,m] \ + * self.mocoeffs[spin][i, n] * self.fooverlaps[m][n] + + step += 1 + if self.progress and random.random() < cupdate: + self.progress.update(step, "Charge Decomposition Analysis...") + + if self.progress: + self.progress.update(nstep, "Done.") + + self.donations = donations + self.bdonations = bdonations + self.repulsions = repulsions + self.residuals = residuals + + return True diff --git a/external/cclib/method/cspa.py b/external/cclib/method/cspa.py index 5166b6b82c..d457002c5e 100644 --- a/external/cclib/method/cspa.py +++ b/external/cclib/method/cspa.py @@ -1,111 +1,111 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 733 $" - -import random # For sometimes running the progress updater - -import numpy - -from population import Population - - -class CSPA(Population): - """The C-squared population analysis.""" - - def __init__(self, *args): - - # Call the __init__ method of the superclass. - super(CSPA, self).__init__(logname="CSPA", *args) - - def __str__(self): - """Return a string representation of the object.""" - return "CSPA of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'CSPA("%s")' % (self.data) - - def calculate(self, indices=None, fupdate=0.05): - """Perform the C squared population analysis. - - Inputs: - indices - list of lists containing atomic orbital indices of fragments - """ - - # Do we have the needed info in the parser? - if not hasattr(self.data, "mocoeffs"): - self.logger.error("Missing mocoeffs") - return False - if not hasattr(self.data, "nbasis"): - self.logger.error("Missing nbasis") - return False - if not hasattr(self.data, "homos"): - self.logger.error("Missing homos") - return False - - self.logger.info("Creating attribute aoresults: array[3]") - - # Determine number of steps, and whether process involves beta orbitals. - unrestricted = (len(self.data.mocoeffs)==2) - nbasis = self.data.nbasis - self.aoresults = [] - alpha = len(self.data.mocoeffs[0]) - self.aoresults.append(numpy.zeros([alpha, nbasis], "d")) - nstep = alpha - if unrestricted: - beta = len(self.data.mocoeffs[1]) - self.aoresults.append(numpy.zeros([beta, nbasis], "d")) - nstep += beta - - # Intialize progress if available. - if self.progress: - self.progress.initialize(nstep) - - step = 0 - for spin in range(len(self.data.mocoeffs)): - - for i in range(len(self.data.mocoeffs[spin])): - - if self.progress and random.random() < fupdate: - self.progress.update(step, "C^2 Population Analysis") - - submocoeffs = self.data.mocoeffs[spin][i] - scale = numpy.inner(submocoeffs, submocoeffs) - tempcoeffs = numpy.multiply(submocoeffs, submocoeffs) - tempvec = tempcoeffs/scale - self.aoresults[spin][i] = numpy.divide(tempcoeffs, scale).astype("d") - - step += 1 - - if self.progress: - self.progress.update(nstep, "Done") - - retval = super(CSPA, self).partition(indices) - - if not retval: - self.logger.error("Error in partitioning results") - return False - - self.logger.info("Creating fragcharges: array[1]") - size = len(self.fragresults[0][0]) - self.fragcharges = numpy.zeros([size], "d") - - for spin in range(len(self.fragresults)): - - for i in range(self.data.homos[spin] + 1): - - temp = numpy.reshape(self.fragresults[spin][i], (size,)) - self.fragcharges = numpy.add(self.fragcharges, temp) - - if not unrestricted: - self.fragcharges = numpy.multiply(self.fragcharges, 2) - - return True - - -if __name__ == "__main__": - import doctest, cspa - doctest.testmod(cspa, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 733 $" + +import random # For sometimes running the progress updater + +import numpy + +from population import Population + + +class CSPA(Population): + """The C-squared population analysis.""" + + def __init__(self, *args): + + # Call the __init__ method of the superclass. + super(CSPA, self).__init__(logname="CSPA", *args) + + def __str__(self): + """Return a string representation of the object.""" + return "CSPA of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'CSPA("%s")' % (self.data) + + def calculate(self, indices=None, fupdate=0.05): + """Perform the C squared population analysis. + + Inputs: + indices - list of lists containing atomic orbital indices of fragments + """ + + # Do we have the needed info in the parser? + if not hasattr(self.data, "mocoeffs"): + self.logger.error("Missing mocoeffs") + return False + if not hasattr(self.data, "nbasis"): + self.logger.error("Missing nbasis") + return False + if not hasattr(self.data, "homos"): + self.logger.error("Missing homos") + return False + + self.logger.info("Creating attribute aoresults: array[3]") + + # Determine number of steps, and whether process involves beta orbitals. + unrestricted = (len(self.data.mocoeffs)==2) + nbasis = self.data.nbasis + self.aoresults = [] + alpha = len(self.data.mocoeffs[0]) + self.aoresults.append(numpy.zeros([alpha, nbasis], "d")) + nstep = alpha + if unrestricted: + beta = len(self.data.mocoeffs[1]) + self.aoresults.append(numpy.zeros([beta, nbasis], "d")) + nstep += beta + + # Intialize progress if available. + if self.progress: + self.progress.initialize(nstep) + + step = 0 + for spin in range(len(self.data.mocoeffs)): + + for i in range(len(self.data.mocoeffs[spin])): + + if self.progress and random.random() < fupdate: + self.progress.update(step, "C^2 Population Analysis") + + submocoeffs = self.data.mocoeffs[spin][i] + scale = numpy.inner(submocoeffs, submocoeffs) + tempcoeffs = numpy.multiply(submocoeffs, submocoeffs) + tempvec = tempcoeffs/scale + self.aoresults[spin][i] = numpy.divide(tempcoeffs, scale).astype("d") + + step += 1 + + if self.progress: + self.progress.update(nstep, "Done") + + retval = super(CSPA, self).partition(indices) + + if not retval: + self.logger.error("Error in partitioning results") + return False + + self.logger.info("Creating fragcharges: array[1]") + size = len(self.fragresults[0][0]) + self.fragcharges = numpy.zeros([size], "d") + + for spin in range(len(self.fragresults)): + + for i in range(self.data.homos[spin] + 1): + + temp = numpy.reshape(self.fragresults[spin][i], (size,)) + self.fragcharges = numpy.add(self.fragcharges, temp) + + if not unrestricted: + self.fragcharges = numpy.multiply(self.fragcharges, 2) + + return True + + +if __name__ == "__main__": + import doctest, cspa + doctest.testmod(cspa, verbose=False) diff --git a/external/cclib/method/density.py b/external/cclib/method/density.py index 3b5e16fb82..7def358f1c 100644 --- a/external/cclib/method/density.py +++ b/external/cclib/method/density.py @@ -1,85 +1,85 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 733 $" - -import random # For sometimes running the progress updater -import logging - -import numpy - -from calculationmethod import Method - - -class Density(Method): - """Calculate the density matrix""" - def __init__(self, data, progress=None, loglevel=logging.INFO, - logname="Density"): - - # Call the __init__ method of the superclass. - super(Density, self).__init__(data, progress, loglevel, logname) - - def __str__(self): - """Return a string representation of the object.""" - return "Density matrix of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'Density matrix("%s")' % (self.data) - - def calculate(self, fupdate=0.05): - """Calculate the density matrix.""" - - # Do we have the needed info in the data object? - if not hasattr(self.data, "mocoeffs"): - self.logger.error("Missing mocoeffs") - return False - if not hasattr(self.data,"nbasis"): - self.logger.error("Missing nbasis") - return False - if not hasattr(self.data,"homos"): - self.logger.error("Missing homos") - return False - - self.logger.info("Creating attribute density: array[3]") - size = self.data.nbasis - unrestricted = (len(self.data.mocoeffs) == 2) - - #determine number of steps, and whether process involves beta orbitals - nstep = self.data.homos[0] + 1 - if unrestricted: - self.density = numpy.zeros([2, size, size], "d") - nstep += self.data.homos[1] + 1 - else: - self.density = numpy.zeros([1, size, size], "d") - - #intialize progress if available - if self.progress: - self.progress.initialize(nstep) - - step = 0 - for spin in range(len(self.data.mocoeffs)): - - for i in range(self.data.homos[spin] + 1): - - if self.progress and random.random() < fupdate: - self.progress.update(step, "Density Matrix") - - col = numpy.reshape(self.data.mocoeffs[spin][i], (size, 1)) - colt = numpy.reshape(col, (1, size)) - - tempdensity = numpy.dot(col, colt) - self.density[spin] = numpy.add(self.density[spin], - tempdensity) - - step += 1 - - if not unrestricted: #multiply by two to account for second electron - self.density[0] = numpy.add(self.density[0], self.density[0]) - - if self.progress: - self.progress.update(nstep, "Done") - - return True #let caller know we finished density +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 733 $" + +import random # For sometimes running the progress updater +import logging + +import numpy + +from calculationmethod import Method + + +class Density(Method): + """Calculate the density matrix""" + def __init__(self, data, progress=None, loglevel=logging.INFO, + logname="Density"): + + # Call the __init__ method of the superclass. + super(Density, self).__init__(data, progress, loglevel, logname) + + def __str__(self): + """Return a string representation of the object.""" + return "Density matrix of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'Density matrix("%s")' % (self.data) + + def calculate(self, fupdate=0.05): + """Calculate the density matrix.""" + + # Do we have the needed info in the data object? + if not hasattr(self.data, "mocoeffs"): + self.logger.error("Missing mocoeffs") + return False + if not hasattr(self.data,"nbasis"): + self.logger.error("Missing nbasis") + return False + if not hasattr(self.data,"homos"): + self.logger.error("Missing homos") + return False + + self.logger.info("Creating attribute density: array[3]") + size = self.data.nbasis + unrestricted = (len(self.data.mocoeffs) == 2) + + #determine number of steps, and whether process involves beta orbitals + nstep = self.data.homos[0] + 1 + if unrestricted: + self.density = numpy.zeros([2, size, size], "d") + nstep += self.data.homos[1] + 1 + else: + self.density = numpy.zeros([1, size, size], "d") + + #intialize progress if available + if self.progress: + self.progress.initialize(nstep) + + step = 0 + for spin in range(len(self.data.mocoeffs)): + + for i in range(self.data.homos[spin] + 1): + + if self.progress and random.random() < fupdate: + self.progress.update(step, "Density Matrix") + + col = numpy.reshape(self.data.mocoeffs[spin][i], (size, 1)) + colt = numpy.reshape(col, (1, size)) + + tempdensity = numpy.dot(col, colt) + self.density[spin] = numpy.add(self.density[spin], + tempdensity) + + step += 1 + + if not unrestricted: #multiply by two to account for second electron + self.density[0] = numpy.add(self.density[0], self.density[0]) + + if self.progress: + self.progress.update(nstep, "Done") + + return True #let caller know we finished density diff --git a/external/cclib/method/fragments.py b/external/cclib/method/fragments.py index f55b5e5831..87517016cf 100644 --- a/external/cclib/method/fragments.py +++ b/external/cclib/method/fragments.py @@ -1,134 +1,134 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 238 $" - -import random # For sometimes running the progress updater - -import numpy -numpy.inv = numpy.linalg.inv - -from calculationmethod import * - - -class FragmentAnalysis(Method): - """Convert a molecule's basis functions from atomic-based to fragment MO-based""" - def __init__(self, data, progress=None, loglevel=logging.INFO, - logname="FragmentAnalysis of"): - - # Call the __init__ method of the superclass. - super(FragmentAnalysis, self).__init__(data, progress, loglevel, logname) - self.parsed = False - - def __str__(self): - """Return a string representation of the object.""" - return "Fragment molecule basis of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'Fragment molecular basis("%s")' % (self.data) - - def calculate(self, fragments, cupdate=0.05): - - nFragBasis = 0 - nFragAlpha = 0 - nFragBeta = 0 - self.fonames = [] - - unrestricted = ( len(self.data.mocoeffs) == 2 ) - - self.logger.info("Creating attribute fonames[]") - - # Collect basis info on the fragments. - for j in range(len(fragments)): - nFragBasis += fragments[j].nbasis - nFragAlpha += fragments[j].homos[0] + 1 - if unrestricted and len(fragments[j].homos) == 1: - nFragBeta += fragments[j].homos[0] + 1 #assume restricted fragment - elif unrestricted and len(fragments[j].homos) == 2: - nFragBeta += fragments[j].homos[1] + 1 #assume unrestricted fragment - - #assign fonames based on fragment name and MO number - for i in range(fragments[j].nbasis): - if hasattr(fragments[j],"name"): - self.fonames.append("%s_%i"%(fragments[j].name,i+1)) - else: - self.fonames.append("noname%i_%i"%(j,i+1)) - - nBasis = self.data.nbasis - nAlpha = self.data.homos[0] + 1 - if unrestricted: - nBeta = self.data.homos[1] + 1 - - # Check to make sure calcs have the right properties. - if nBasis != nFragBasis: - self.logger.error("Basis functions don't match") - return False - - if nAlpha != nFragAlpha: - self.logger.error("Alpha electrons don't match") - return False - - if unrestricted and nBeta != nFragBeta: - self.logger.error("Beta electrons don't match") - return False - - if len(self.data.atomcoords) != 1: - self.logger.warning("Molecule calc appears to be an optimization") - - for frag in fragments: - if len(frag.atomcoords) != 1: - self.logger.warning("One or more fragment appears to be an optimization") - break - - last = 0 - for frag in fragments: - size = frag.natom - if self.data.atomcoords[0][last:last+size].tolist() != frag.atomcoords[0].tolist(): - self.logger.error("Atom coordinates aren't aligned") - return False - - last += size - - # And let's begin! - self.mocoeffs = [] - self.logger.info("Creating mocoeffs in new fragment MO basis: mocoeffs[]") - - for spin in range(len(self.data.mocoeffs)): - blockMatrix = numpy.zeros((nBasis,nBasis), "d") - pos = 0 - - # Build up block-diagonal matrix from fragment mocoeffs. - # Need to switch ordering from [mo,ao] to [ao,mo]. - for i in range(len(fragments)): - size = fragments[i].nbasis - if len(fragments[i].mocoeffs) == 1: - blockMatrix[pos:pos+size,pos:pos+size] = numpy.transpose(fragments[i].mocoeffs[0]) - else: - blockMatrix[pos:pos+size,pos:pos+size] = numpy.transpose(fragments[i].mocoeffs[spin]) - pos += size - - # Invert and mutliply to result in fragment MOs as basis. - iBlockMatrix = numpy.inv(blockMatrix) - results = numpy.transpose(numpy.dot(iBlockMatrix, numpy.transpose(self.data.mocoeffs[spin]))) - self.mocoeffs.append(results) - - if hasattr(self.data, "aooverlaps"): - tempMatrix = numpy.dot(self.data.aooverlaps, blockMatrix) - tBlockMatrix = numpy.transpose(blockMatrix) - if spin == 0: - self.fooverlaps = numpy.dot(tBlockMatrix, tempMatrix) - self.logger.info("Creating fooverlaps: array[x,y]") - elif spin == 1: - self.fooverlaps2 = numpy.dot(tBlockMatrix, tempMatrix) - self.logger.info("Creating fooverlaps (beta): array[x,y]") - else: - self.logger.warning("Overlap matrix missing") - - self.parsed = True - self.nbasis = nBasis - self.homos = self.data.homos - - return True +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 238 $" + +import random # For sometimes running the progress updater + +import numpy +numpy.inv = numpy.linalg.inv + +from calculationmethod import * + + +class FragmentAnalysis(Method): + """Convert a molecule's basis functions from atomic-based to fragment MO-based""" + def __init__(self, data, progress=None, loglevel=logging.INFO, + logname="FragmentAnalysis of"): + + # Call the __init__ method of the superclass. + super(FragmentAnalysis, self).__init__(data, progress, loglevel, logname) + self.parsed = False + + def __str__(self): + """Return a string representation of the object.""" + return "Fragment molecule basis of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'Fragment molecular basis("%s")' % (self.data) + + def calculate(self, fragments, cupdate=0.05): + + nFragBasis = 0 + nFragAlpha = 0 + nFragBeta = 0 + self.fonames = [] + + unrestricted = ( len(self.data.mocoeffs) == 2 ) + + self.logger.info("Creating attribute fonames[]") + + # Collect basis info on the fragments. + for j in range(len(fragments)): + nFragBasis += fragments[j].nbasis + nFragAlpha += fragments[j].homos[0] + 1 + if unrestricted and len(fragments[j].homos) == 1: + nFragBeta += fragments[j].homos[0] + 1 #assume restricted fragment + elif unrestricted and len(fragments[j].homos) == 2: + nFragBeta += fragments[j].homos[1] + 1 #assume unrestricted fragment + + #assign fonames based on fragment name and MO number + for i in range(fragments[j].nbasis): + if hasattr(fragments[j],"name"): + self.fonames.append("%s_%i"%(fragments[j].name,i+1)) + else: + self.fonames.append("noname%i_%i"%(j,i+1)) + + nBasis = self.data.nbasis + nAlpha = self.data.homos[0] + 1 + if unrestricted: + nBeta = self.data.homos[1] + 1 + + # Check to make sure calcs have the right properties. + if nBasis != nFragBasis: + self.logger.error("Basis functions don't match") + return False + + if nAlpha != nFragAlpha: + self.logger.error("Alpha electrons don't match") + return False + + if unrestricted and nBeta != nFragBeta: + self.logger.error("Beta electrons don't match") + return False + + if len(self.data.atomcoords) != 1: + self.logger.warning("Molecule calc appears to be an optimization") + + for frag in fragments: + if len(frag.atomcoords) != 1: + self.logger.warning("One or more fragment appears to be an optimization") + break + + last = 0 + for frag in fragments: + size = frag.natom + if self.data.atomcoords[0][last:last+size].tolist() != frag.atomcoords[0].tolist(): + self.logger.error("Atom coordinates aren't aligned") + return False + + last += size + + # And let's begin! + self.mocoeffs = [] + self.logger.info("Creating mocoeffs in new fragment MO basis: mocoeffs[]") + + for spin in range(len(self.data.mocoeffs)): + blockMatrix = numpy.zeros((nBasis,nBasis), "d") + pos = 0 + + # Build up block-diagonal matrix from fragment mocoeffs. + # Need to switch ordering from [mo,ao] to [ao,mo]. + for i in range(len(fragments)): + size = fragments[i].nbasis + if len(fragments[i].mocoeffs) == 1: + blockMatrix[pos:pos+size,pos:pos+size] = numpy.transpose(fragments[i].mocoeffs[0]) + else: + blockMatrix[pos:pos+size,pos:pos+size] = numpy.transpose(fragments[i].mocoeffs[spin]) + pos += size + + # Invert and mutliply to result in fragment MOs as basis. + iBlockMatrix = numpy.inv(blockMatrix) + results = numpy.transpose(numpy.dot(iBlockMatrix, numpy.transpose(self.data.mocoeffs[spin]))) + self.mocoeffs.append(results) + + if hasattr(self.data, "aooverlaps"): + tempMatrix = numpy.dot(self.data.aooverlaps, blockMatrix) + tBlockMatrix = numpy.transpose(blockMatrix) + if spin == 0: + self.fooverlaps = numpy.dot(tBlockMatrix, tempMatrix) + self.logger.info("Creating fooverlaps: array[x,y]") + elif spin == 1: + self.fooverlaps2 = numpy.dot(tBlockMatrix, tempMatrix) + self.logger.info("Creating fooverlaps (beta): array[x,y]") + else: + self.logger.warning("Overlap matrix missing") + + self.parsed = True + self.nbasis = nBasis + self.homos = self.data.homos + + return True diff --git a/external/cclib/method/lpa.py b/external/cclib/method/lpa.py index 428b968c0e..56c0af93bc 100644 --- a/external/cclib/method/lpa.py +++ b/external/cclib/method/lpa.py @@ -1,132 +1,132 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 837 $" - -import random - -import numpy - -from population import Population - - -class LPA(Population): - """The Lowdin population analysis""" - def __init__(self, *args): - - # Call the __init__ method of the superclass. - super(LPA, self).__init__(logname="LPA", *args) - - def __str__(self): - """Return a string representation of the object.""" - return "LPA of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'LPA("%s")' % (self.data) - - def calculate(self, indices=None, x=0.5, fupdate=0.05): - """Perform a calculation of Lowdin population analysis. - - Inputs: - indices - list of lists containing atomic orbital indices of fragments - x - overlap matrix exponent in wavefunxtion projection (x=0.5 for Lowdin) - """ - - # Do we have the needed info in the parser? - if not hasattr(self.data,"mocoeffs"): - self.logger.error("Missing mocoeffs") - return False - if not (hasattr(self.data, "aooverlaps") \ - or hasattr(self.data, "fooverlaps") ): - self.logger.error("Missing overlap matrix") - return False - if not hasattr(self.data, "nbasis"): - self.logger.error("Missing nbasis") - return False - if not hasattr(self.data, "homos"): - self.logger.error("Missing homos") - return False - - unrestricted = (len(self.data.mocoeffs) == 2) - nbasis = self.data.nbasis - - # Determine number of steps, and whether process involves beta orbitals. - self.logger.info("Creating attribute aoresults: [array[2]]") - alpha = len(self.data.mocoeffs[0]) - self.aoresults = [ numpy.zeros([alpha, nbasis], "d") ] - nstep = alpha - - if unrestricted: - beta = len(self.data.mocoeffs[1]) - self.aoresults.append(numpy.zeros([beta, nbasis], "d")) - nstep += beta - - #intialize progress if available - if self.progress: - self.progress.initialize(nstep) - - if hasattr(self.data, "aooverlaps"): - S = self.data.aooverlaps - elif hasattr(self.data, "fooverlaps"): - S = self.data.fooverlaps - - # Get eigenvalues and matrix of eigenvectors for transformation decomposition (U). - # Find roots of diagonal elements, and transform backwards using eigevectors. - # We need two matrices here, one for S^x, another for S^(1-x). - # We don't need to invert U, since S is symmetrical. - eigenvalues, U = numpy.linalg.eig(S) - UI = U.transpose() - Sdiagroot1 = numpy.identity(len(S))*numpy.power(eigenvalues, x) - Sdiagroot2 = numpy.identity(len(S))*numpy.power(eigenvalues, 1-x) - Sroot1 = numpy.dot(U, numpy.dot(Sdiagroot1, UI)) - Sroot2 = numpy.dot(U, numpy.dot(Sdiagroot2, UI)) - - step = 0 - for spin in range(len(self.data.mocoeffs)): - - for i in range(len(self.data.mocoeffs[spin])): - - if self.progress and random.random() < fupdate: - self.progress.update(step, "Lowdin Population Analysis") - - ci = self.data.mocoeffs[spin][i] - - temp1 = numpy.dot(ci, Sroot1) - temp2 = numpy.dot(ci, Sroot2) - self.aoresults[spin][i] = numpy.multiply(temp1, temp2).astype("d") - - step += 1 - - if self.progress: - self.progress.update(nstep, "Done") - - retval = super(LPA, self).partition(indices) - - if not retval: - self.logger.error("Error in partitioning results") - return False - - # Create array for charges. - self.logger.info("Creating fragcharges: array[1]") - size = len(self.fragresults[0][0]) - self.fragcharges = numpy.zeros([size], "d") - - for spin in range(len(self.fragresults)): - - for i in range(self.data.homos[spin] + 1): - - temp = numpy.reshape(self.fragresults[spin][i], (size,)) - self.fragcharges = numpy.add(self.fragcharges, temp) - - if not unrestricted: - self.fragcharges = numpy.multiply(self.fragcharges, 2) - - return True - - -if __name__ == "__main__": - import doctest, lpa - doctest.testmod(lpa, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 837 $" + +import random + +import numpy + +from population import Population + + +class LPA(Population): + """The Lowdin population analysis""" + def __init__(self, *args): + + # Call the __init__ method of the superclass. + super(LPA, self).__init__(logname="LPA", *args) + + def __str__(self): + """Return a string representation of the object.""" + return "LPA of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'LPA("%s")' % (self.data) + + def calculate(self, indices=None, x=0.5, fupdate=0.05): + """Perform a calculation of Lowdin population analysis. + + Inputs: + indices - list of lists containing atomic orbital indices of fragments + x - overlap matrix exponent in wavefunxtion projection (x=0.5 for Lowdin) + """ + + # Do we have the needed info in the parser? + if not hasattr(self.data,"mocoeffs"): + self.logger.error("Missing mocoeffs") + return False + if not (hasattr(self.data, "aooverlaps") \ + or hasattr(self.data, "fooverlaps") ): + self.logger.error("Missing overlap matrix") + return False + if not hasattr(self.data, "nbasis"): + self.logger.error("Missing nbasis") + return False + if not hasattr(self.data, "homos"): + self.logger.error("Missing homos") + return False + + unrestricted = (len(self.data.mocoeffs) == 2) + nbasis = self.data.nbasis + + # Determine number of steps, and whether process involves beta orbitals. + self.logger.info("Creating attribute aoresults: [array[2]]") + alpha = len(self.data.mocoeffs[0]) + self.aoresults = [ numpy.zeros([alpha, nbasis], "d") ] + nstep = alpha + + if unrestricted: + beta = len(self.data.mocoeffs[1]) + self.aoresults.append(numpy.zeros([beta, nbasis], "d")) + nstep += beta + + #intialize progress if available + if self.progress: + self.progress.initialize(nstep) + + if hasattr(self.data, "aooverlaps"): + S = self.data.aooverlaps + elif hasattr(self.data, "fooverlaps"): + S = self.data.fooverlaps + + # Get eigenvalues and matrix of eigenvectors for transformation decomposition (U). + # Find roots of diagonal elements, and transform backwards using eigevectors. + # We need two matrices here, one for S^x, another for S^(1-x). + # We don't need to invert U, since S is symmetrical. + eigenvalues, U = numpy.linalg.eig(S) + UI = U.transpose() + Sdiagroot1 = numpy.identity(len(S))*numpy.power(eigenvalues, x) + Sdiagroot2 = numpy.identity(len(S))*numpy.power(eigenvalues, 1-x) + Sroot1 = numpy.dot(U, numpy.dot(Sdiagroot1, UI)) + Sroot2 = numpy.dot(U, numpy.dot(Sdiagroot2, UI)) + + step = 0 + for spin in range(len(self.data.mocoeffs)): + + for i in range(len(self.data.mocoeffs[spin])): + + if self.progress and random.random() < fupdate: + self.progress.update(step, "Lowdin Population Analysis") + + ci = self.data.mocoeffs[spin][i] + + temp1 = numpy.dot(ci, Sroot1) + temp2 = numpy.dot(ci, Sroot2) + self.aoresults[spin][i] = numpy.multiply(temp1, temp2).astype("d") + + step += 1 + + if self.progress: + self.progress.update(nstep, "Done") + + retval = super(LPA, self).partition(indices) + + if not retval: + self.logger.error("Error in partitioning results") + return False + + # Create array for charges. + self.logger.info("Creating fragcharges: array[1]") + size = len(self.fragresults[0][0]) + self.fragcharges = numpy.zeros([size], "d") + + for spin in range(len(self.fragresults)): + + for i in range(self.data.homos[spin] + 1): + + temp = numpy.reshape(self.fragresults[spin][i], (size,)) + self.fragcharges = numpy.add(self.fragcharges, temp) + + if not unrestricted: + self.fragcharges = numpy.multiply(self.fragcharges, 2) + + return True + + +if __name__ == "__main__": + import doctest, lpa + doctest.testmod(lpa, verbose=False) diff --git a/external/cclib/method/mbo.py b/external/cclib/method/mbo.py index d28f3d0a53..f3ea5346a9 100644 --- a/external/cclib/method/mbo.py +++ b/external/cclib/method/mbo.py @@ -1,121 +1,121 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 733 $" - -import random # For sometimes running the progress updater - -import numpy - -from density import Density - - -class MBO(Density): - """Calculate the density matrix.""" - - def __init__(self, *args): - - # Call the __init__ method of the superclass. - super(MBO, self).__init__(logname="MBO", *args) - - def __str__(self): - """Return a string representation of the object.""" - return "Mayer's bond order of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'Mayer\'s bond order("%s")' % (self.data) - - def calculate(self, indices=None, fupdate=0.05): - """Calculate Mayer's bond orders.""" - - retval = super(MBO, self).calculate(fupdate) - if not retval: #making density didn't work - return False - - # Do we have the needed info in the ccData object? - if not (hasattr(self.data, "aooverlaps") - or hasattr(self.data, "fooverlaps")): - self.logger.error("Missing overlap matrix") - return False #let the caller of function know we didn't finish - - if not indices: - - # Build list of groups of orbitals in each atom for atomresults. - if hasattr(self.data, "aonames"): - names = self.data.aonames - overlaps = self.data.aooverlaps - elif hasattr(self.data, "fonames"): - names = self.data.fonames - overlaps = self.data.fooverlaps - else: - self.logger.error("Missing aonames or fonames") - return False - - atoms = [] - indices = [] - - name = names[0].split('_')[0] - atoms.append(name) - indices.append([0]) - - for i in range(1, len(names)): - name = names[i].split('_')[0] - try: - index = atoms.index(name) - except ValueError: #not found in atom list - atoms.append(name) - indices.append([i]) - else: - indices[index].append(i) - - self.logger.info("Creating attribute fragresults: array[3]") - size = len(indices) - - # Determine number of steps, and whether process involves beta orbitals. - PS = [] - PS.append(numpy.dot(self.density[0], overlaps)) - nstep = size**2 #approximately quadratic in size - unrestricted = (len(self.data.mocoeffs) == 2) - if unrestricted: - self.fragresults = numpy.zeros([2, size, size], "d") - PS.append(numpy.dot(self.density[1], overlaps)) - else: - self.fragresults = numpy.zeros([1, size, size], "d") - - # Intialize progress if available. - if self.progress: - self.progress.initialize(nstep) - - step = 0 - for i in range(len(indices)): - - if self.progress and random.random() < fupdate: - self.progress.update(step, "Mayer's Bond Order") - - for j in range(i+1, len(indices)): - - tempsumA = 0 - tempsumB = 0 - - for a in indices[i]: - - for b in indices[j]: - - tempsumA += 2 * PS[0][a][b] * PS[0][b][a] - if unrestricted: - tempsumB += 2 * PS[1][a][b] * PS[1][b][a] - - self.fragresults[0][i, j] = tempsumA - self.fragresults[0][j, i] = tempsumA - - if unrestricted: - self.fragresults[1][i, j] = tempsumB - self.fragresults[1][j, i] = tempsumB - - if self.progress: - self.progress.update(nstep, "Done") - - return True +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 733 $" + +import random # For sometimes running the progress updater + +import numpy + +from density import Density + + +class MBO(Density): + """Calculate the density matrix.""" + + def __init__(self, *args): + + # Call the __init__ method of the superclass. + super(MBO, self).__init__(logname="MBO", *args) + + def __str__(self): + """Return a string representation of the object.""" + return "Mayer's bond order of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'Mayer\'s bond order("%s")' % (self.data) + + def calculate(self, indices=None, fupdate=0.05): + """Calculate Mayer's bond orders.""" + + retval = super(MBO, self).calculate(fupdate) + if not retval: #making density didn't work + return False + + # Do we have the needed info in the ccData object? + if not (hasattr(self.data, "aooverlaps") + or hasattr(self.data, "fooverlaps")): + self.logger.error("Missing overlap matrix") + return False #let the caller of function know we didn't finish + + if not indices: + + # Build list of groups of orbitals in each atom for atomresults. + if hasattr(self.data, "aonames"): + names = self.data.aonames + overlaps = self.data.aooverlaps + elif hasattr(self.data, "fonames"): + names = self.data.fonames + overlaps = self.data.fooverlaps + else: + self.logger.error("Missing aonames or fonames") + return False + + atoms = [] + indices = [] + + name = names[0].split('_')[0] + atoms.append(name) + indices.append([0]) + + for i in range(1, len(names)): + name = names[i].split('_')[0] + try: + index = atoms.index(name) + except ValueError: #not found in atom list + atoms.append(name) + indices.append([i]) + else: + indices[index].append(i) + + self.logger.info("Creating attribute fragresults: array[3]") + size = len(indices) + + # Determine number of steps, and whether process involves beta orbitals. + PS = [] + PS.append(numpy.dot(self.density[0], overlaps)) + nstep = size**2 #approximately quadratic in size + unrestricted = (len(self.data.mocoeffs) == 2) + if unrestricted: + self.fragresults = numpy.zeros([2, size, size], "d") + PS.append(numpy.dot(self.density[1], overlaps)) + else: + self.fragresults = numpy.zeros([1, size, size], "d") + + # Intialize progress if available. + if self.progress: + self.progress.initialize(nstep) + + step = 0 + for i in range(len(indices)): + + if self.progress and random.random() < fupdate: + self.progress.update(step, "Mayer's Bond Order") + + for j in range(i+1, len(indices)): + + tempsumA = 0 + tempsumB = 0 + + for a in indices[i]: + + for b in indices[j]: + + tempsumA += 2 * PS[0][a][b] * PS[0][b][a] + if unrestricted: + tempsumB += 2 * PS[1][a][b] * PS[1][b][a] + + self.fragresults[0][i, j] = tempsumA + self.fragresults[0][j, i] = tempsumA + + if unrestricted: + self.fragresults[1][i, j] = tempsumB + self.fragresults[1][j, i] = tempsumB + + if self.progress: + self.progress.update(nstep, "Done") + + return True diff --git a/external/cclib/method/mpa.py b/external/cclib/method/mpa.py index a300c1d75c..0690c5f47f 100644 --- a/external/cclib/method/mpa.py +++ b/external/cclib/method/mpa.py @@ -1,118 +1,118 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 733 $" - -import random - -import numpy - -from population import Population - - -class MPA(Population): - """The Mulliken population analysis.""" - - def __init__(self, *args): - - # Call the __init__ method of the superclass. - super(MPA, self).__init__(logname="MPA", *args) - - def __str__(self): - """Return a string representation of the object.""" - return "MPA of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'MPA("%s")' % (self.data) - - def calculate(self, indices=None, fupdate=0.05): - """Perform a Mulliken population analysis.""" - - # Do we have the needed attributes in the data object? - if not hasattr(self.data, "mocoeffs"): - self.logger.error("Missing mocoeffs") - return False - if not (hasattr(self.data, "aooverlaps") \ - or hasattr(self.data, "fooverlaps") ): - self.logger.error("Missing overlap matrix") - return False - if not hasattr(self.data, "nbasis"): - self.logger.error("Missing nbasis") - return False - if not hasattr(self.data, "homos"): - self.logger.error("Missing homos") - return False - - - # Determine number of steps, and whether process involves beta orbitals. - self.logger.info("Creating attribute aoresults: [array[2]]") - nbasis = self.data.nbasis - alpha = len(self.data.mocoeffs[0]) - self.aoresults = [ numpy.zeros([alpha, nbasis], "d") ] - nstep = alpha - unrestricted = (len(self.data.mocoeffs) == 2) - if unrestricted: - beta = len(self.data.mocoeffs[1]) - self.aoresults.append(numpy.zeros([beta, nbasis], "d")) - nstep += beta - - # Intialize progress if available. - if self.progress: - self.progress.initialize(nstep) - - step = 0 - for spin in range(len(self.data.mocoeffs)): - - for i in range(len(self.data.mocoeffs[spin])): - - if self.progress and random.random() < fupdate: - self.progress.update(step, "Mulliken Population Analysis") - - #X_{ai} = \sum_b c_{ai} c_{bi} S_{ab} - # = c_{ai} \sum_b c_{bi} S_{ab} - # = c_{ai} C(i) \cdot S(a) - # X = C(i) * [C(i) \cdot S] - # C(i) is 1xn and S is nxn, result of matrix mult is 1xn - - ci = self.data.mocoeffs[spin][i] - if hasattr(self.data, "aooverlaps"): - temp = numpy.dot(ci, self.data.aooverlaps) - elif hasattr(self.data, "fooverlaps"): - temp = numpy.dot(ci, self.data.fooverlaps) - - self.aoresults[spin][i] = numpy.multiply(ci, temp).astype("d") - - step += 1 - - if self.progress: - self.progress.update(nstep, "Done") - - retval = super(MPA, self).partition(indices) - - if not retval: - self.logger.error("Error in partitioning results") - return False - - # Create array for mulliken charges. - self.logger.info("Creating fragcharges: array[1]") - size = len(self.fragresults[0][0]) - self.fragcharges = numpy.zeros([size], "d") - - for spin in range(len(self.fragresults)): - - for i in range(self.data.homos[spin] + 1): - - temp = numpy.reshape(self.fragresults[spin][i], (size,)) - self.fragcharges = numpy.add(self.fragcharges, temp) - - if not unrestricted: - self.fragcharges = numpy.multiply(self.fragcharges, 2) - - return True - -if __name__ == "__main__": - import doctest, mpa - doctest.testmod(mpa, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 733 $" + +import random + +import numpy + +from population import Population + + +class MPA(Population): + """The Mulliken population analysis.""" + + def __init__(self, *args): + + # Call the __init__ method of the superclass. + super(MPA, self).__init__(logname="MPA", *args) + + def __str__(self): + """Return a string representation of the object.""" + return "MPA of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'MPA("%s")' % (self.data) + + def calculate(self, indices=None, fupdate=0.05): + """Perform a Mulliken population analysis.""" + + # Do we have the needed attributes in the data object? + if not hasattr(self.data, "mocoeffs"): + self.logger.error("Missing mocoeffs") + return False + if not (hasattr(self.data, "aooverlaps") \ + or hasattr(self.data, "fooverlaps") ): + self.logger.error("Missing overlap matrix") + return False + if not hasattr(self.data, "nbasis"): + self.logger.error("Missing nbasis") + return False + if not hasattr(self.data, "homos"): + self.logger.error("Missing homos") + return False + + + # Determine number of steps, and whether process involves beta orbitals. + self.logger.info("Creating attribute aoresults: [array[2]]") + nbasis = self.data.nbasis + alpha = len(self.data.mocoeffs[0]) + self.aoresults = [ numpy.zeros([alpha, nbasis], "d") ] + nstep = alpha + unrestricted = (len(self.data.mocoeffs) == 2) + if unrestricted: + beta = len(self.data.mocoeffs[1]) + self.aoresults.append(numpy.zeros([beta, nbasis], "d")) + nstep += beta + + # Intialize progress if available. + if self.progress: + self.progress.initialize(nstep) + + step = 0 + for spin in range(len(self.data.mocoeffs)): + + for i in range(len(self.data.mocoeffs[spin])): + + if self.progress and random.random() < fupdate: + self.progress.update(step, "Mulliken Population Analysis") + + #X_{ai} = \sum_b c_{ai} c_{bi} S_{ab} + # = c_{ai} \sum_b c_{bi} S_{ab} + # = c_{ai} C(i) \cdot S(a) + # X = C(i) * [C(i) \cdot S] + # C(i) is 1xn and S is nxn, result of matrix mult is 1xn + + ci = self.data.mocoeffs[spin][i] + if hasattr(self.data, "aooverlaps"): + temp = numpy.dot(ci, self.data.aooverlaps) + elif hasattr(self.data, "fooverlaps"): + temp = numpy.dot(ci, self.data.fooverlaps) + + self.aoresults[spin][i] = numpy.multiply(ci, temp).astype("d") + + step += 1 + + if self.progress: + self.progress.update(nstep, "Done") + + retval = super(MPA, self).partition(indices) + + if not retval: + self.logger.error("Error in partitioning results") + return False + + # Create array for mulliken charges. + self.logger.info("Creating fragcharges: array[1]") + size = len(self.fragresults[0][0]) + self.fragcharges = numpy.zeros([size], "d") + + for spin in range(len(self.fragresults)): + + for i in range(self.data.homos[spin] + 1): + + temp = numpy.reshape(self.fragresults[spin][i], (size,)) + self.fragcharges = numpy.add(self.fragcharges, temp) + + if not unrestricted: + self.fragcharges = numpy.multiply(self.fragcharges, 2) + + return True + +if __name__ == "__main__": + import doctest, mpa + doctest.testmod(mpa, verbose=False) diff --git a/external/cclib/method/opa.py b/external/cclib/method/opa.py index a4bf1fecd8..14a6e22e10 100644 --- a/external/cclib/method/opa.py +++ b/external/cclib/method/opa.py @@ -1,141 +1,141 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 739 $" - -import random - -import numpy - -from calculationmethod import Method - - -def func(x): - if x==1: - return 1 - else: - return x+func(x-1) - - -class OPA(Method): - """The overlap population analysis.""" - - def __init__(self, *args): - - # Call the __init__ method of the superclass. - super(OPA, self).__init__(logname="OPA", *args) - - def __str__(self): - """Return a string representation of the object.""" - return "OPA of" % (self.data) - - def __repr__(self): - """Return a representation of the object.""" - return 'OPA("%s")' % (self.data) - - def calculate(self, indices=None, fupdate=0.05): - """Perform an overlap population analysis given the results of a parser""" - - # Do we have the needed info in the ccData object? - if not hasattr(self.data, "mocoeffs") \ - and not ( hasattr(self.data, "aooverlaps") \ - or hasattr(self.data, "fooverlaps") ) \ - and not hasattr(self.data, "nbasis"): - self.logger.error("Missing mocoeffs, aooverlaps/fooverlaps or nbasis") - return False #let the caller of function know we didn't finish - - if not indices: - - # Build list of groups of orbitals in each atom for atomresults. - if hasattr(self.data, "aonames"): - names = self.data.aonames - elif hasattr(self.data, "foonames"): - names = self.data.fonames - - atoms = [] - indices = [] - - name = names[0].split('_')[0] - atoms.append(name) - indices.append([0]) - - for i in range(1, len(names)): - name = names[i].split('_')[0] - try: - index = atoms.index(name) - except ValueError: #not found in atom list - atoms.append(name) - indices.append([i]) - else: - indices[index].append(i) - - # Determine number of steps, and whether process involves beta orbitals. - nfrag = len(indices) #nfrag - nstep = func(nfrag - 1) - unrestricted = (len(self.data.mocoeffs) == 2) - alpha = len(self.data.mocoeffs[0]) - nbasis = self.data.nbasis - - self.logger.info("Creating attribute results: array[4]") - results= [ numpy.zeros([nfrag, nfrag, alpha], "d") ] - if unrestricted: - beta = len(self.data.mocoeffs[1]) - results.append(numpy.zeros([nfrag, nfrag, beta], "d")) - nstep *= 2 - - if hasattr(self.data, "aooverlaps"): - overlap = self.data.aooverlaps - elif hasattr(self.data,"fooverlaps"): - overlap = self.data.fooverlaps - - #intialize progress if available - if self.progress: - self.progress.initialize(nstep) - - size = len(self.data.mocoeffs[0]) - step = 0 - - preresults = [] - for spin in range(len(self.data.mocoeffs)): - two = numpy.array([2.0]*len(self.data.mocoeffs[spin]),"d") - - - # OP_{AB,i} = \sum_{a in A} \sum_{b in B} 2 c_{ai} c_{bi} S_{ab} - - for A in range(len(indices)-1): - - for B in range(A+1, len(indices)): - - if self.progress: #usually only a handful of updates, so remove random part - self.progress.update(step, "Overlap Population Analysis") - - for a in indices[A]: - - ca = self.data.mocoeffs[spin][:,a] - - for b in indices[B]: - - cb = self.data.mocoeffs[spin][:,b] - temp = ca * cb * two *overlap[a,b] - results[spin][A,B] = numpy.add(results[spin][A,B],temp) - results[spin][B,A] = numpy.add(results[spin][B,A],temp) - - step += 1 - - temparray2 = numpy.swapaxes(results[0],1,2) - self.results = [ numpy.swapaxes(temparray2,0,1) ] - if unrestricted: - temparray2 = numpy.swapaxes(results[1],1,2) - self.results.append(numpy.swapaxes(temparray2, 0, 1)) - - if self.progress: - self.progress.update(nstep, "Done") - - return True - - -if __name__ == "__main__": - import doctest, opa - doctest.testmod(opa, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 739 $" + +import random + +import numpy + +from calculationmethod import Method + + +def func(x): + if x==1: + return 1 + else: + return x+func(x-1) + + +class OPA(Method): + """The overlap population analysis.""" + + def __init__(self, *args): + + # Call the __init__ method of the superclass. + super(OPA, self).__init__(logname="OPA", *args) + + def __str__(self): + """Return a string representation of the object.""" + return "OPA of" % (self.data) + + def __repr__(self): + """Return a representation of the object.""" + return 'OPA("%s")' % (self.data) + + def calculate(self, indices=None, fupdate=0.05): + """Perform an overlap population analysis given the results of a parser""" + + # Do we have the needed info in the ccData object? + if not hasattr(self.data, "mocoeffs") \ + and not ( hasattr(self.data, "aooverlaps") \ + or hasattr(self.data, "fooverlaps") ) \ + and not hasattr(self.data, "nbasis"): + self.logger.error("Missing mocoeffs, aooverlaps/fooverlaps or nbasis") + return False #let the caller of function know we didn't finish + + if not indices: + + # Build list of groups of orbitals in each atom for atomresults. + if hasattr(self.data, "aonames"): + names = self.data.aonames + elif hasattr(self.data, "foonames"): + names = self.data.fonames + + atoms = [] + indices = [] + + name = names[0].split('_')[0] + atoms.append(name) + indices.append([0]) + + for i in range(1, len(names)): + name = names[i].split('_')[0] + try: + index = atoms.index(name) + except ValueError: #not found in atom list + atoms.append(name) + indices.append([i]) + else: + indices[index].append(i) + + # Determine number of steps, and whether process involves beta orbitals. + nfrag = len(indices) #nfrag + nstep = func(nfrag - 1) + unrestricted = (len(self.data.mocoeffs) == 2) + alpha = len(self.data.mocoeffs[0]) + nbasis = self.data.nbasis + + self.logger.info("Creating attribute results: array[4]") + results= [ numpy.zeros([nfrag, nfrag, alpha], "d") ] + if unrestricted: + beta = len(self.data.mocoeffs[1]) + results.append(numpy.zeros([nfrag, nfrag, beta], "d")) + nstep *= 2 + + if hasattr(self.data, "aooverlaps"): + overlap = self.data.aooverlaps + elif hasattr(self.data,"fooverlaps"): + overlap = self.data.fooverlaps + + #intialize progress if available + if self.progress: + self.progress.initialize(nstep) + + size = len(self.data.mocoeffs[0]) + step = 0 + + preresults = [] + for spin in range(len(self.data.mocoeffs)): + two = numpy.array([2.0]*len(self.data.mocoeffs[spin]),"d") + + + # OP_{AB,i} = \sum_{a in A} \sum_{b in B} 2 c_{ai} c_{bi} S_{ab} + + for A in range(len(indices)-1): + + for B in range(A+1, len(indices)): + + if self.progress: #usually only a handful of updates, so remove random part + self.progress.update(step, "Overlap Population Analysis") + + for a in indices[A]: + + ca = self.data.mocoeffs[spin][:,a] + + for b in indices[B]: + + cb = self.data.mocoeffs[spin][:,b] + temp = ca * cb * two *overlap[a,b] + results[spin][A,B] = numpy.add(results[spin][A,B],temp) + results[spin][B,A] = numpy.add(results[spin][B,A],temp) + + step += 1 + + temparray2 = numpy.swapaxes(results[0],1,2) + self.results = [ numpy.swapaxes(temparray2,0,1) ] + if unrestricted: + temparray2 = numpy.swapaxes(results[1],1,2) + self.results.append(numpy.swapaxes(temparray2, 0, 1)) + + if self.progress: + self.progress.update(nstep, "Done") + + return True + + +if __name__ == "__main__": + import doctest, opa + doctest.testmod(opa, verbose=False) diff --git a/external/cclib/method/population.py b/external/cclib/method/population.py index 82438f697e..7964a6bafb 100644 --- a/external/cclib/method/population.py +++ b/external/cclib/method/population.py @@ -1,94 +1,94 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 733 $" - -import logging - -import numpy - -from calculationmethod import Method - - -class Population(Method): - """A base class for all population-type methods.""" - - def __init__(self, data, progress=None, \ - loglevel=logging.INFO, logname="Log"): - - # Call the __init__ method of the superclass. - super(Population, self).__init__(data, progress, loglevel, logname) - self.fragresults = None - - def __str__(self): - """Return a string representation of the object.""" - return "Population" - - def __repr__(self): - """Return a representation of the object.""" - return "Population" - - def partition(self, indices=None): - - if not hasattr(self, "aoresults"): - self.calculate() - - if not indices: - - # Build list of groups of orbitals in each atom for atomresults. - if hasattr(self.data, "aonames"): - names = self.data.aonames - elif hasattr(self.data, "fonames"): - names = self.data.fonames - - atoms = [] - indices = [] - - name = names[0].split('_')[0] - atoms.append(name) - indices.append([0]) - - for i in range(1, len(names)): - name = names[i].split('_')[0] - try: - index = atoms.index(name) - except ValueError: #not found in atom list - atoms.append(name) - indices.append([i]) - else: - indices[index].append(i) - - natoms = len(indices) - nmocoeffs = len(self.aoresults[0]) - - # Build results numpy array[3]. - alpha = len(self.aoresults[0]) - results = [] - results.append(numpy.zeros([alpha, natoms], "d")) - - if len(self.aoresults) == 2: - beta = len(self.aoresults[1]) - results.append(numpy.zeros([beta, natoms], "d")) - - # For each spin, splice numpy array at ao index, - # and add to correct result row. - for spin in range(len(results)): - - for i in range(natoms): # Number of groups. - - for j in range(len(indices[i])): # For each group. - - temp = self.aoresults[spin][:, indices[i][j]] - results[spin][:, i] = numpy.add(results[spin][:, i], temp) - - self.logger.info("Saving partitioned results in fragresults: [array[2]]") - self.fragresults = results - - return True - - -if __name__ == "__main__": - import doctest, population - doctest.testmod(population, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 733 $" + +import logging + +import numpy + +from calculationmethod import Method + + +class Population(Method): + """A base class for all population-type methods.""" + + def __init__(self, data, progress=None, \ + loglevel=logging.INFO, logname="Log"): + + # Call the __init__ method of the superclass. + super(Population, self).__init__(data, progress, loglevel, logname) + self.fragresults = None + + def __str__(self): + """Return a string representation of the object.""" + return "Population" + + def __repr__(self): + """Return a representation of the object.""" + return "Population" + + def partition(self, indices=None): + + if not hasattr(self, "aoresults"): + self.calculate() + + if not indices: + + # Build list of groups of orbitals in each atom for atomresults. + if hasattr(self.data, "aonames"): + names = self.data.aonames + elif hasattr(self.data, "fonames"): + names = self.data.fonames + + atoms = [] + indices = [] + + name = names[0].split('_')[0] + atoms.append(name) + indices.append([0]) + + for i in range(1, len(names)): + name = names[i].split('_')[0] + try: + index = atoms.index(name) + except ValueError: #not found in atom list + atoms.append(name) + indices.append([i]) + else: + indices[index].append(i) + + natoms = len(indices) + nmocoeffs = len(self.aoresults[0]) + + # Build results numpy array[3]. + alpha = len(self.aoresults[0]) + results = [] + results.append(numpy.zeros([alpha, natoms], "d")) + + if len(self.aoresults) == 2: + beta = len(self.aoresults[1]) + results.append(numpy.zeros([beta, natoms], "d")) + + # For each spin, splice numpy array at ao index, + # and add to correct result row. + for spin in range(len(results)): + + for i in range(natoms): # Number of groups. + + for j in range(len(indices[i])): # For each group. + + temp = self.aoresults[spin][:, indices[i][j]] + results[spin][:, i] = numpy.add(results[spin][:, i], temp) + + self.logger.info("Saving partitioned results in fragresults: [array[2]]") + self.fragresults = results + + return True + + +if __name__ == "__main__": + import doctest, population + doctest.testmod(population, verbose=False) diff --git a/external/cclib/method/volume.py b/external/cclib/method/volume.py index 0e8ca8aa58..08cc2d7c4e 100644 --- a/external/cclib/method/volume.py +++ b/external/cclib/method/volume.py @@ -1,264 +1,264 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 742 $" - -import copy - -import numpy - -try: - from PyQuante.CGBF import CGBF - module_pyq = True -except: - module_pyq = False - -try: - from pyvtk import * - from pyvtk.DataSetAttr import * - module_pyvtk = True -except: - module_pyvtk = False - -from cclib.bridge import makepyquante -from cclib.parser.utils import convertor - - -class Volume(object): - """Represent a volume in space. - - Required parameters: - origin -- the bottom left hand corner of the volume - topcorner -- the top right hand corner - spacing -- the distance between the points in the cube - - Attributes: - data -- a numpy array of values for each point in the volume - (set to zero at initialisation) - numpts -- the numbers of points in the (x,y,z) directions - - """ - - def __init__(self, origin, topcorner, spacing): - - self.origin = origin - self.spacing = spacing - self.topcorner = topcorner - self.numpts = [] - for i in range(3): - self.numpts.append(int((self.topcorner[i]-self.origin[i])/self.spacing[i] + 1) ) - self.data = numpy.zeros( tuple(self.numpts), "d") - - def __str__(self): - """Return a string representation.""" - return "Volume %s to %s (density: %s)" % (self.origin, self.topcorner, - self.spacing) - - def write(self, filename, format="Cube"): - """Write the volume to file.""" - - format = format.upper() - - if format.upper() not in ["VTK", "CUBE"]: - raise "Format must be either VTK or Cube" - elif format=="VTK": - self.writeasvtk(filename) - else: - self.writeascube(filename) - - def writeasvtk(self, filename): - if not module_pyvtk: - raise Exception, "You need to have pyvtk installed" - ranges = (numpy.arange(self.data.shape[2]), - numpy.arange(self.data.shape[1]), - numpy.arange(self.data.shape[0])) - v = VtkData(RectilinearGrid(*ranges), "Test", - PointData(Scalars(self.data.ravel(), "from cclib", "default"))) - v.tofile(filename) - - def integrate(self): - boxvol = (self.spacing[0] * self.spacing[1] * self.spacing[2] * - convertor(1, "Angstrom", "bohr")**3) - return sum(self.data.ravel()) * boxvol - - def integrate_square(self): - boxvol = (self.spacing[0] * self.spacing[1] * self.spacing[2] * - convertor(1, "Angstrom", "bohr")**3) - return sum(self.data.ravel()**2) * boxvol - - def writeascube(self, filename): - # Remember that the units are bohr, not Angstroms - convert = lambda x : convertor(x, "Angstrom", "bohr") - ans = [] - ans.append("Cube file generated by cclib") - ans.append("") - format = "%4d%12.6f%12.6f%12.6f" - origin = [convert(x) for x in self.origin] - ans.append(format % (0, origin[0], origin[1], origin[2])) - ans.append(format % (self.data.shape[0], convert(self.spacing[0]), 0.0, 0.0)) - ans.append(format % (self.data.shape[1], 0.0, convert(self.spacing[1]), 0.0)) - ans.append(format % (self.data.shape[2], 0.0, 0.0, convert(self.spacing[2]))) - line = [] - for i in range(self.data.shape[0]): - for j in range(self.data.shape[1]): - for k in range(self.data.shape[2]): - line.append(scinotation(self.data[i][j][k])) - if len(line)==6: - ans.append(" ".join(line)) - line = [] - if line: - ans.append(" ".join(line)) - line = [] - outputfile = open(filename, "w") - outputfile.write("\n".join(ans)) - outputfile.close() - -def scinotation(num): - """Write in scientific notation - - >>> scinotation(1./654) - ' 1.52905E-03' - >>> scinotation(-1./654) - '-1.52905E-03' - """ - ans = "%10.5E" % num - broken = ans.split("E") - exponent = int(broken[1]) - if exponent<-99: - return " 0.000E+00" - if exponent<0: - sign="-" - else: - sign="+" - return ("%sE%s%s" % (broken[0],sign,broken[1][-2:])).rjust(12) - -def getbfs(coords, gbasis): - """Convenience function for both wavefunction and density based on PyQuante Ints.py.""" - mymol = makepyquante(coords, [0 for x in coords]) - - sym2powerlist = { - 'S' : [(0,0,0)], - 'P' : [(1,0,0),(0,1,0),(0,0,1)], - 'D' : [(2,0,0),(0,2,0),(0,0,2),(1,1,0),(0,1,1),(1,0,1)], - 'F' : [(3,0,0),(2,1,0),(2,0,1),(1,2,0),(1,1,1),(1,0,2), - (0,3,0),(0,2,1),(0,1,2), (0,0,3)] - } - - bfs = [] - for i,atom in enumerate(mymol): - bs = gbasis[i] - for sym,prims in bs: - for power in sym2powerlist[sym]: - bf = CGBF(atom.pos(),power) - for expnt,coef in prims: - bf.add_primitive(expnt,coef) - bf.normalize() - bfs.append(bf) - - return bfs - -def wavefunction(coords, mocoeffs, gbasis, volume): - """Calculate the magnitude of the wavefunction at every point in a volume. - - Attributes: - coords -- the coordinates of the atoms - mocoeffs -- mocoeffs for one eigenvalue - gbasis -- gbasis from a parser object - volume -- a template Volume object (will not be altered) - """ - bfs = getbfs(coords, gbasis) - - wavefn = copy.copy(volume) - wavefn.data = numpy.zeros( wavefn.data.shape, "d") - - conversion = convertor(1,"bohr","Angstrom") - x = numpy.arange(wavefn.origin[0], wavefn.topcorner[0]+wavefn.spacing[0], wavefn.spacing[0]) / conversion - y = numpy.arange(wavefn.origin[1], wavefn.topcorner[1]+wavefn.spacing[1], wavefn.spacing[1]) / conversion - z = numpy.arange(wavefn.origin[2], wavefn.topcorner[2]+wavefn.spacing[2], wavefn.spacing[2]) / conversion - - for bs in range(len(bfs)): - data = numpy.zeros( wavefn.data.shape, "d") - for i,xval in enumerate(x): - for j,yval in enumerate(y): - for k,zval in enumerate(z): - data[i, j, k] = bfs[bs].amp(xval,yval,zval) - numpy.multiply(data, mocoeffs[bs], data) - numpy.add(wavefn.data, data, wavefn.data) - - return wavefn - -def electrondensity(coords, mocoeffslist, gbasis, volume): - """Calculate the magnitude of the electron density at every point in a volume. - - Attributes: - coords -- the coordinates of the atoms - mocoeffs -- mocoeffs for all of the occupied eigenvalues - gbasis -- gbasis from a parser object - volume -- a template Volume object (will not be altered) - - Note: mocoeffs is a list of numpy arrays. The list will be of length 1 - for restricted calculations, and length 2 for unrestricted. - """ - bfs = getbfs(coords, gbasis) - - density = copy.copy(volume) - density.data = numpy.zeros( density.data.shape, "d") - - conversion = convertor(1,"bohr","Angstrom") - x = numpy.arange(density.origin[0], density.topcorner[0]+density.spacing[0], density.spacing[0]) / conversion - y = numpy.arange(density.origin[1], density.topcorner[1]+density.spacing[1], density.spacing[1]) / conversion - z = numpy.arange(density.origin[2], density.topcorner[2]+density.spacing[2], density.spacing[2]) / conversion - - for mocoeffs in mocoeffslist: - for mocoeff in mocoeffs: - wavefn = numpy.zeros( density.data.shape, "d") - for bs in range(len(bfs)): - data = numpy.zeros( density.data.shape, "d") - for i,xval in enumerate(x): - for j,yval in enumerate(y): - tmp = [] - for k,zval in enumerate(z): - tmp.append(bfs[bs].amp(xval, yval, zval)) - data[i,j,:] = tmp - numpy.multiply(data, mocoeff[bs], data) - numpy.add(wavefn, data, wavefn) - density.data += wavefn**2 - - if len(mocoeffslist) == 1: - density.data = density.data*2. # doubly-occupied - - return density - - -if __name__=="__main__": - - try: - import psyco - psyco.full() - except ImportError: - pass - - from cclib.parser import ccopen - import logging - a = ccopen("../../../data/Gaussian/basicGaussian03/dvb_sp_basis.log") - a.logger.setLevel(logging.ERROR) - c = a.parse() - - b = ccopen("../../../data/Gaussian/basicGaussian03/dvb_sp.out") - b.logger.setLevel(logging.ERROR) - d = b.parse() - - vol = Volume( (-3.0,-6,-2.0), (3.0, 6, 2.0), spacing=(0.25,0.25,0.25) ) - wavefn = wavefunction(d.atomcoords[0], d.mocoeffs[0][d.homos[0]], - c.gbasis, vol) - assert abs(wavefn.integrate())<1E-6 # not necessarily true for all wavefns - assert abs(wavefn.integrate_square() - 1.00)<1E-3 # true for all wavefns - print wavefn.integrate(), wavefn.integrate_square() - - vol = Volume( (-3.0,-6,-2.0), (3.0, 6, 2.0), spacing=(0.25,0.25,0.25) ) - frontierorbs = [d.mocoeffs[0][(d.homos[0]-3):(d.homos[0]+1)]] - density = electrondensity(d.atomcoords[0], frontierorbs, c.gbasis, vol) - assert abs(density.integrate()-8.00)<1E-2 - print "Combined Density of 4 Frontier orbitals=",density.integrate() +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 742 $" + +import copy + +import numpy + +try: + from PyQuante.CGBF import CGBF + module_pyq = True +except: + module_pyq = False + +try: + from pyvtk import * + from pyvtk.DataSetAttr import * + module_pyvtk = True +except: + module_pyvtk = False + +from cclib.bridge import makepyquante +from cclib.parser.utils import convertor + + +class Volume(object): + """Represent a volume in space. + + Required parameters: + origin -- the bottom left hand corner of the volume + topcorner -- the top right hand corner + spacing -- the distance between the points in the cube + + Attributes: + data -- a numpy array of values for each point in the volume + (set to zero at initialisation) + numpts -- the numbers of points in the (x,y,z) directions + + """ + + def __init__(self, origin, topcorner, spacing): + + self.origin = origin + self.spacing = spacing + self.topcorner = topcorner + self.numpts = [] + for i in range(3): + self.numpts.append(int((self.topcorner[i]-self.origin[i])/self.spacing[i] + 1) ) + self.data = numpy.zeros( tuple(self.numpts), "d") + + def __str__(self): + """Return a string representation.""" + return "Volume %s to %s (density: %s)" % (self.origin, self.topcorner, + self.spacing) + + def write(self, filename, format="Cube"): + """Write the volume to file.""" + + format = format.upper() + + if format.upper() not in ["VTK", "CUBE"]: + raise "Format must be either VTK or Cube" + elif format=="VTK": + self.writeasvtk(filename) + else: + self.writeascube(filename) + + def writeasvtk(self, filename): + if not module_pyvtk: + raise Exception, "You need to have pyvtk installed" + ranges = (numpy.arange(self.data.shape[2]), + numpy.arange(self.data.shape[1]), + numpy.arange(self.data.shape[0])) + v = VtkData(RectilinearGrid(*ranges), "Test", + PointData(Scalars(self.data.ravel(), "from cclib", "default"))) + v.tofile(filename) + + def integrate(self): + boxvol = (self.spacing[0] * self.spacing[1] * self.spacing[2] * + convertor(1, "Angstrom", "bohr")**3) + return sum(self.data.ravel()) * boxvol + + def integrate_square(self): + boxvol = (self.spacing[0] * self.spacing[1] * self.spacing[2] * + convertor(1, "Angstrom", "bohr")**3) + return sum(self.data.ravel()**2) * boxvol + + def writeascube(self, filename): + # Remember that the units are bohr, not Angstroms + convert = lambda x : convertor(x, "Angstrom", "bohr") + ans = [] + ans.append("Cube file generated by cclib") + ans.append("") + format = "%4d%12.6f%12.6f%12.6f" + origin = [convert(x) for x in self.origin] + ans.append(format % (0, origin[0], origin[1], origin[2])) + ans.append(format % (self.data.shape[0], convert(self.spacing[0]), 0.0, 0.0)) + ans.append(format % (self.data.shape[1], 0.0, convert(self.spacing[1]), 0.0)) + ans.append(format % (self.data.shape[2], 0.0, 0.0, convert(self.spacing[2]))) + line = [] + for i in range(self.data.shape[0]): + for j in range(self.data.shape[1]): + for k in range(self.data.shape[2]): + line.append(scinotation(self.data[i][j][k])) + if len(line)==6: + ans.append(" ".join(line)) + line = [] + if line: + ans.append(" ".join(line)) + line = [] + outputfile = open(filename, "w") + outputfile.write("\n".join(ans)) + outputfile.close() + +def scinotation(num): + """Write in scientific notation + + >>> scinotation(1./654) + ' 1.52905E-03' + >>> scinotation(-1./654) + '-1.52905E-03' + """ + ans = "%10.5E" % num + broken = ans.split("E") + exponent = int(broken[1]) + if exponent<-99: + return " 0.000E+00" + if exponent<0: + sign="-" + else: + sign="+" + return ("%sE%s%s" % (broken[0],sign,broken[1][-2:])).rjust(12) + +def getbfs(coords, gbasis): + """Convenience function for both wavefunction and density based on PyQuante Ints.py.""" + mymol = makepyquante(coords, [0 for x in coords]) + + sym2powerlist = { + 'S' : [(0,0,0)], + 'P' : [(1,0,0),(0,1,0),(0,0,1)], + 'D' : [(2,0,0),(0,2,0),(0,0,2),(1,1,0),(0,1,1),(1,0,1)], + 'F' : [(3,0,0),(2,1,0),(2,0,1),(1,2,0),(1,1,1),(1,0,2), + (0,3,0),(0,2,1),(0,1,2), (0,0,3)] + } + + bfs = [] + for i,atom in enumerate(mymol): + bs = gbasis[i] + for sym,prims in bs: + for power in sym2powerlist[sym]: + bf = CGBF(atom.pos(),power) + for expnt,coef in prims: + bf.add_primitive(expnt,coef) + bf.normalize() + bfs.append(bf) + + return bfs + +def wavefunction(coords, mocoeffs, gbasis, volume): + """Calculate the magnitude of the wavefunction at every point in a volume. + + Attributes: + coords -- the coordinates of the atoms + mocoeffs -- mocoeffs for one eigenvalue + gbasis -- gbasis from a parser object + volume -- a template Volume object (will not be altered) + """ + bfs = getbfs(coords, gbasis) + + wavefn = copy.copy(volume) + wavefn.data = numpy.zeros( wavefn.data.shape, "d") + + conversion = convertor(1,"bohr","Angstrom") + x = numpy.arange(wavefn.origin[0], wavefn.topcorner[0]+wavefn.spacing[0], wavefn.spacing[0]) / conversion + y = numpy.arange(wavefn.origin[1], wavefn.topcorner[1]+wavefn.spacing[1], wavefn.spacing[1]) / conversion + z = numpy.arange(wavefn.origin[2], wavefn.topcorner[2]+wavefn.spacing[2], wavefn.spacing[2]) / conversion + + for bs in range(len(bfs)): + data = numpy.zeros( wavefn.data.shape, "d") + for i,xval in enumerate(x): + for j,yval in enumerate(y): + for k,zval in enumerate(z): + data[i, j, k] = bfs[bs].amp(xval,yval,zval) + numpy.multiply(data, mocoeffs[bs], data) + numpy.add(wavefn.data, data, wavefn.data) + + return wavefn + +def electrondensity(coords, mocoeffslist, gbasis, volume): + """Calculate the magnitude of the electron density at every point in a volume. + + Attributes: + coords -- the coordinates of the atoms + mocoeffs -- mocoeffs for all of the occupied eigenvalues + gbasis -- gbasis from a parser object + volume -- a template Volume object (will not be altered) + + Note: mocoeffs is a list of numpy arrays. The list will be of length 1 + for restricted calculations, and length 2 for unrestricted. + """ + bfs = getbfs(coords, gbasis) + + density = copy.copy(volume) + density.data = numpy.zeros( density.data.shape, "d") + + conversion = convertor(1,"bohr","Angstrom") + x = numpy.arange(density.origin[0], density.topcorner[0]+density.spacing[0], density.spacing[0]) / conversion + y = numpy.arange(density.origin[1], density.topcorner[1]+density.spacing[1], density.spacing[1]) / conversion + z = numpy.arange(density.origin[2], density.topcorner[2]+density.spacing[2], density.spacing[2]) / conversion + + for mocoeffs in mocoeffslist: + for mocoeff in mocoeffs: + wavefn = numpy.zeros( density.data.shape, "d") + for bs in range(len(bfs)): + data = numpy.zeros( density.data.shape, "d") + for i,xval in enumerate(x): + for j,yval in enumerate(y): + tmp = [] + for k,zval in enumerate(z): + tmp.append(bfs[bs].amp(xval, yval, zval)) + data[i,j,:] = tmp + numpy.multiply(data, mocoeff[bs], data) + numpy.add(wavefn, data, wavefn) + density.data += wavefn**2 + + if len(mocoeffslist) == 1: + density.data = density.data*2. # doubly-occupied + + return density + + +if __name__=="__main__": + + try: + import psyco + psyco.full() + except ImportError: + pass + + from cclib.parser import ccopen + import logging + a = ccopen("../../../data/Gaussian/basicGaussian03/dvb_sp_basis.log") + a.logger.setLevel(logging.ERROR) + c = a.parse() + + b = ccopen("../../../data/Gaussian/basicGaussian03/dvb_sp.out") + b.logger.setLevel(logging.ERROR) + d = b.parse() + + vol = Volume( (-3.0,-6,-2.0), (3.0, 6, 2.0), spacing=(0.25,0.25,0.25) ) + wavefn = wavefunction(d.atomcoords[0], d.mocoeffs[0][d.homos[0]], + c.gbasis, vol) + assert abs(wavefn.integrate())<1E-6 # not necessarily true for all wavefns + assert abs(wavefn.integrate_square() - 1.00)<1E-3 # true for all wavefns + print wavefn.integrate(), wavefn.integrate_square() + + vol = Volume( (-3.0,-6,-2.0), (3.0, 6, 2.0), spacing=(0.25,0.25,0.25) ) + frontierorbs = [d.mocoeffs[0][(d.homos[0]-3):(d.homos[0]+1)]] + density = electrondensity(d.atomcoords[0], frontierorbs, c.gbasis, vol) + assert abs(density.integrate()-8.00)<1E-2 + print "Combined Density of 4 Frontier orbitals=",density.integrate() diff --git a/external/cclib/parser/__init__.py b/external/cclib/parser/__init__.py index 8ebbfcf82b..8b4d5224a5 100644 --- a/external/cclib/parser/__init__.py +++ b/external/cclib/parser/__init__.py @@ -1,29 +1,29 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -gmagoon 4/5/10-4/6/10 (this notice added 4/29/10): Gregory Magoon modified this file from cclib 1.0 -""" - -__revision__ = "$Revision: 863 $" - -# These import statements are added for the convenience of users... - -# Rather than having to type: -# from cclib.parser.gaussianparser import Gaussian -# they can use: -# from cclib.parser import Gaussian - -from adfparser import ADF -from gamessparser import GAMESS -from gamessukparser import GAMESSUK -from gaussianparser import Gaussian -from jaguarparser import Jaguar -from molproparser import Molpro -from orcaparser import ORCA -from mopacparser import Mopac -from mm4parser import MM4 - -# This allow users to type: -# from cclib.parser import ccopen - -from ccopen import ccopen +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +gmagoon 4/5/10-4/6/10 (this notice added 4/29/10): Gregory Magoon modified this file from cclib 1.0 +""" + +__revision__ = "$Revision: 863 $" + +# These import statements are added for the convenience of users... + +# Rather than having to type: +# from cclib.parser.gaussianparser import Gaussian +# they can use: +# from cclib.parser import Gaussian + +from adfparser import ADF +from gamessparser import GAMESS +from gamessukparser import GAMESSUK +from gaussianparser import Gaussian +from jaguarparser import Jaguar +from molproparser import Molpro +from orcaparser import ORCA +from mopacparser import Mopac +from mm4parser import MM4 + +# This allow users to type: +# from cclib.parser import ccopen + +from ccopen import ccopen diff --git a/external/cclib/parser/adfparser.py b/external/cclib/parser/adfparser.py index c636dfd093..237ee879db 100644 --- a/external/cclib/parser/adfparser.py +++ b/external/cclib/parser/adfparser.py @@ -1,882 +1,882 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 861 $" - - -import numpy - -import logfileparser -import utils - - -class ADF(logfileparser.Logfile): - """An ADF log file""" - - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(ADF, self).__init__(logname="ADF", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "ADF log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'ADF("%s")' % (self.filename) - - def normalisesym(self, label): - """Use standard symmetry labels instead of ADF labels. - - To normalise: - (1) any periods are removed (except in the case of greek letters) - (2) XXX is replaced by X, and a " added. - (3) XX is replaced by X, and a ' added. - (4) The greek letters Sigma, Pi, Delta and Phi are replaced by - their lowercase equivalent. - - >>> sym = ADF("dummyfile").normalisesym - >>> labels = ['A','s','A1','A1.g','Sigma','Pi','Delta','Phi','Sigma.g','A.g','AA','AAA','EE1','EEE1'] - >>> map(sym,labels) - ['A', 's', 'A1', 'A1g', 'sigma', 'pi', 'delta', 'phi', 'sigma.g', 'Ag', "A'", 'A"', "E1'", 'E1"'] - """ - greeks = ['Sigma', 'Pi', 'Delta', 'Phi'] - for greek in greeks: - if label.startswith(greek): - return label.lower() - - ans = label.replace(".", "") - if ans[1:3] == "''": - temp = ans[0] + '"' - ans = temp - - l = len(ans) - if l > 1 and ans[0] == ans[1]: # Python only tests the second condition if the first is true - if l > 2 and ans[1] == ans[2]: - ans = ans.replace(ans[0]*3, ans[0]) + '"' - else: - ans = ans.replace(ans[0]*2, ans[0]) + "'" - return ans - - def normalisedegenerates(self, label, num, ndict=None): - """Generate a string used for matching degenerate orbital labels - - To normalise: - (1) if label is E or T, return label:num - (2) if label is P or D, look up in dict, and return answer - """ - - if not ndict: - ndict = { 'P': {0:"P:x", 1:"P:y", 2:"P:z"},\ - 'D': {0:"D:z2", 1:"D:x2-y2", 2:"D:xy", 3:"D:xz", 4:"D:yz"}} - - if ndict.has_key(label): - if ndict[label].has_key(num): - return ndict[label][num] - else: - return "%s:%i"%(label,num+1) - else: - return "%s:%i"%(label,num+1) - - def before_parsing(self): - - # Used to avoid extracting the final geometry twice in a GeoOpt - self.NOTFOUND, self.GETLAST, self.NOMORE = range(3) - self.finalgeometry = self.NOTFOUND - - # Used for calculating the scftarget (variables names taken from the ADF manual) - self.accint = self.SCFconv = self.sconv2 = None - - # keep track of nosym and unrestricted case to parse Energies since it doens't have an all Irreps section - self.nosymflag = False - self.unrestrictedflag = False - - SCFCNV, SCFCNV2 = range(2) #used to index self.scftargets[] - maxelem, norm = range(2) # used to index scf.values - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - if line.find("INPUT FILE") >= 0: - #check to make sure we aren't parsing Create jobs - while line: - - self.updateprogress(inputfile, "Unsupported Information", self.fupdate) - - if line.find("INPUT FILE") >=0 and hasattr(self,"scftargets"): - #does this file contain multiple calculations? - #if so, print a warning and skip to end of file - self.logger.warning("Skipping remaining calculations") - inputfile.seek(0,2) - break - - if line.find("INPUT FILE") >= 0: - line2 = inputfile.next() - else: - line2 = None - - if line2 and len(line2) <= 2: - #make sure that it's not blank like in the NiCO4 regression - line2 = inputfile.next() - - if line2 and (line2.find("Create") < 0 and line2.find("create") < 0): - break - - line = inputfile.next() - - if line[1:10] == "Symmetry:": - info = line.split() - if info[1] == "NOSYM": - self.nosymflag = True - - # Use this to read the subspecies of irreducible representations. - # It will be a list, with each element representing one irrep. - if line.strip() == "Irreducible Representations, including subspecies": - dashes = inputfile.next() - self.irreps = [] - line = inputfile.next() - while line.strip() != "": - self.irreps.append(line.split()) - line = inputfile.next() - - if line[4:13] == 'Molecule:': - info = line.split() - if info[1] == 'UNrestricted': - self.unrestrictedflag = True - - if line[1:6] == "ATOMS": - # Find the number of atoms and their atomic numbers - # Also extract the starting coordinates (for a GeoOpt anyway) - self.updateprogress(inputfile, "Attributes", self.cupdate) - - self.atomnos = [] - self.atomcoords = [] - self.coreelectrons = [] - - underline = inputfile.next() #clear pointless lines - label1 = inputfile.next() # - label2 = inputfile.next() # - line = inputfile.next() - atomcoords = [] - while len(line)>2: #ensure that we are reading no blank lines - info = line.split() - element = info[1].split('.')[0] - self.atomnos.append(self.table.number[element]) - atomcoords.append(map(float, info[2:5])) - self.coreelectrons.append(int(float(info[5]) - float(info[6]))) - line = inputfile.next() - self.atomcoords.append(atomcoords) - - self.natom = len(self.atomnos) - self.atomnos = numpy.array(self.atomnos, "i") - - if line[1:10] == "FRAGMENTS": - header = inputfile.next() - - self.frags = [] - self.fragnames = [] - - line = inputfile.next() - while len(line) > 2: #ensure that we are reading no blank lines - info = line.split() - - if len(info) == 7: #fragment name is listed here - self.fragnames.append("%s_%s"%(info[1],info[0])) - self.frags.append([]) - self.frags[-1].append(int(info[2]) - 1) - - elif len(info) == 5: #add atoms into last fragment - self.frags[-1].append(int(info[0]) - 1) - - line = inputfile.next() - - # Extract charge - if line[1:11] == "Net Charge": - self.charge = int(line.split()[2]) - line = inputfile.next() - if len(line.strip()): - # Spin polar: 1 (Spin_A minus Spin_B electrons) - self.mult = int(line.split()[2]) + 1 - # (Not sure about this for higher multiplicities) - else: - self.mult = 1 - - if line[1:22] == "S C F U P D A T E S": - # find targets for SCF convergence - - if not hasattr(self,"scftargets"): - self.scftargets = [] - - #underline, blank, nr - for i in range(3): - inputfile.next() - - line = inputfile.next() - self.SCFconv = float(line.split()[-1]) - line = inputfile.next() - self.sconv2 = float(line.split()[-1]) - - if line[1:11] == "CYCLE 1": - - self.updateprogress(inputfile, "QM convergence", self.fupdate) - - newlist = [] - line = inputfile.next() - - if not hasattr(self,"geovalues"): - # This is the first SCF cycle - self.scftargets.append([self.sconv2*10, self.sconv2]) - elif self.finalgeometry in [self.GETLAST, self.NOMORE]: - # This is the final SCF cycle - self.scftargets.append([self.SCFconv*10, self.SCFconv]) - else: - # This is an intermediate SCF cycle - oldscftst = self.scftargets[-1][1] - grdmax = self.geovalues[-1][1] - scftst = max(self.SCFconv, min(oldscftst, grdmax/30, 10**(-self.accint))) - self.scftargets.append([scftst*10, scftst]) - - while line.find("SCF CONVERGED") == -1 and line.find("SCF not fully converged, result acceptable") == -1 and line.find("SCF NOT CONVERGED") == -1: - if line[4:12] == "SCF test": - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - - info = line.split() - newlist.append([float(info[4]), abs(float(info[6]))]) - try: - line = inputfile.next() - except StopIteration: #EOF reached? - self.logger.warning("SCF did not converge, so attributes may be missing") - break - - if line.find("SCF not fully converged, result acceptable") > 0: - self.logger.warning("SCF not fully converged, results acceptable") - - if line.find("SCF NOT CONVERGED") > 0: - self.logger.warning("SCF did not converge! moenergies and mocoeffs are unreliable") - - if hasattr(self, "scfvalues"): - self.scfvalues.append(newlist) - - # Parse SCF energy for SP calcs from bonding energy decomposition section. - # It seems ADF does not print it earlier for SP calcualtions. - # If it does (does it?), parse that instead. - # Check that scfenergies does not exist, becuase gopt runs also print this, - # repeating the values in the last "Geometry Convergence Tests" section. - if "Total Bonding Energy:" in line: - if not hasattr(self, "scfenergies"): - energy = utils.convertor(float(line.split()[3]), "hartree", "eV") - self.scfenergies = [energy] - - if line[51:65] == "Final Geometry": - self.finalgeometry = self.GETLAST - - if line[1:24] == "Coordinates (Cartesian)" and self.finalgeometry in [self.NOTFOUND, self.GETLAST]: - # Get the coordinates from each step of the GeoOpt - if not hasattr(self, "atomcoords"): - self.atomcoords = [] - equals = inputfile.next() - blank = inputfile.next() - title = inputfile.next() - title = inputfile.next() - hyphens = inputfile.next() - - atomcoords = [] - line = inputfile.next() - while line != hyphens: - atomcoords.append(map(float, line.split()[5:8])) - line = inputfile.next() - self.atomcoords.append(atomcoords) - if self.finalgeometry == self.GETLAST: # Don't get any more coordinates - self.finalgeometry = self.NOMORE - - if line[1:27] == 'Geometry Convergence Tests': - # Extract Geometry convergence information - if not hasattr(self, "geotargets"): - self.geovalues = [] - self.geotargets = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0], "d") - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - equals = inputfile.next() - blank = inputfile.next() - line = inputfile.next() - temp = inputfile.next().strip().split() - self.scfenergies.append(utils.convertor(float(temp[-1]), "hartree", "eV")) - for i in range(6): - line = inputfile.next() - values = [] - for i in range(5): - temp = inputfile.next().split() - self.geotargets[i] = float(temp[-3]) - values.append(float(temp[-4])) - self.geovalues.append(values) - - if line[1:27] == 'General Accuracy Parameter': - # Need to know the accuracy of the integration grid to - # calculate the scftarget...note that it changes with time - self.accint = float(line.split()[-1]) - - if line.find('Orbital Energies, per Irrep and Spin') > 0 and not hasattr(self, "mosyms") and self.nosymflag and not self.unrestrictedflag: - #Extracting orbital symmetries and energies, homos for nosym case - #Should only be for restricted case because there is a better text block for unrestricted and nosym - - self.mosyms = [[]] - - self.moenergies = [[]] - - underline = inputfile.next() - header = inputfile.next() - underline = inputfile.next() - label = inputfile.next() - line = inputfile.next() - - info = line.split() - - if not info[0] == '1': - self.logger.warning("MO info up to #%s is missing" % info[0]) - - #handle case where MO information up to a certain orbital are missing - while int(info[0]) - 1 != len(self.moenergies[0]): - self.moenergies[0].append(99999) - self.mosyms[0].append('A') - - homoA = None - - while len(line) > 10: - info = line.split() - self.mosyms[0].append('A') - self.moenergies[0].append(utils.convertor(float(info[2]), 'hartree', 'eV')) - if info[1] == '0.000' and not hasattr(self, 'homos'): - self.homos = [len(self.moenergies[0]) - 2] - line = inputfile.next() - - self.moenergies = [numpy.array(self.moenergies[0], "d")] - self.homos = numpy.array(self.homos, "i") - - if line[1:29] == 'Orbital Energies, both Spins' and not hasattr(self, "mosyms") and self.nosymflag and self.unrestrictedflag: - #Extracting orbital symmetries and energies, homos for nosym case - #should only be here if unrestricted and nosym - - self.mosyms = [[], []] - - moenergies = [[], []] - - underline = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - underline = inputfile.next() - line = inputfile.next() - - homoa = 0 - homob = None - - while len(line) > 5: - info = line.split() - if info[2] == 'A': - self.mosyms[0].append('A') - moenergies[0].append(utils.convertor(float(info[4]), 'hartree', 'eV')) - if info[3] != '0.00': - homoa = len(moenergies[0]) - 1 - elif info[2] == 'B': - self.mosyms[1].append('A') - moenergies[1].append(utils.convertor(float(info[4]), 'hartree', 'eV')) - if info[3] != '0.00': - homob = len(moenergies[1]) - 1 - else: - print "Error reading line: %s" % line - - line = inputfile.next() - - self.moenergies = [numpy.array(x, "d") for x in moenergies] - self.homos = numpy.array([homoa, homob], "i") - - - if line[1:29] == 'Orbital Energies, all Irreps' and not hasattr(self, "mosyms"): - #Extracting orbital symmetries and energies, homos - self.mosyms = [[]] - self.symlist = {} - - self.moenergies = [[]] - - underline = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - underline2 = inputfile.next() - line = inputfile.next() - - homoa = None - homob = None - - #multiple = {'E':2, 'T':3, 'P':3, 'D':5} - # The above is set if there are no special irreps - names = [irrep[0].split(':')[0] for irrep in self.irreps] - counts = [len(irrep) for irrep in self.irreps] - multiple = dict(zip(names, counts)) - irrepspecies = {} - for n in range(len(names)): - indices = range(counts[n]) - subspecies = self.irreps[n] - irrepspecies[names[n]] = dict(zip(indices, subspecies)) - - while line.strip(): - info = line.split() - if len(info) == 5: #this is restricted - #count = multiple.get(info[0][0],1) - count = multiple.get(info[0],1) - for repeat in range(count): # i.e. add E's twice, T's thrice - self.mosyms[0].append(self.normalisesym(info[0])) - self.moenergies[0].append(utils.convertor(float(info[3]), 'hartree', 'eV')) - - sym = info[0] - if count > 1: # add additional sym label - sym = self.normalisedegenerates(info[0],repeat,ndict=irrepspecies) - - try: - self.symlist[sym][0].append(len(self.moenergies[0])-1) - except KeyError: - self.symlist[sym]=[[]] - self.symlist[sym][0].append(len(self.moenergies[0])-1) - - if info[2] == '0.00' and not hasattr(self, 'homos'): - self.homos = [len(self.moenergies[0]) - (count + 1)] #count, because need to handle degenerate cases - line = inputfile.next() - elif len(info) == 6: #this is unrestricted - if len(self.moenergies) < 2: #if we don't have space, create it - self.moenergies.append([]) - self.mosyms.append([]) -# count = multiple.get(info[0][0], 1) - count = multiple.get(info[0], 1) - if info[2] == 'A': - for repeat in range(count): # i.e. add E's twice, T's thrice - self.mosyms[0].append(self.normalisesym(info[0])) - self.moenergies[0].append(utils.convertor(float(info[4]), 'hartree', 'eV')) - - sym = info[0] - if count > 1: #add additional sym label - sym = self.normalisedegenerates(info[0],repeat) - - try: - self.symlist[sym][0].append(len(self.moenergies[0])-1) - except KeyError: - self.symlist[sym]=[[],[]] - self.symlist[sym][0].append(len(self.moenergies[0])-1) - - if info[3] == '0.00' and homoa == None: - homoa = len(self.moenergies[0]) - (count + 1) #count because degenerate cases need to be handled - - if info[2] == 'B': - for repeat in range(count): # i.e. add E's twice, T's thrice - self.mosyms[1].append(self.normalisesym(info[0])) - self.moenergies[1].append(utils.convertor(float(info[4]), 'hartree', 'eV')) - - sym = info[0] - if count > 1: #add additional sym label - sym = self.normalisedegenerates(info[0],repeat) - - try: - self.symlist[sym][1].append(len(self.moenergies[1])-1) - except KeyError: - self.symlist[sym]=[[],[]] - self.symlist[sym][1].append(len(self.moenergies[1])-1) - - if info[3] == '0.00' and homob == None: - homob = len(self.moenergies[1]) - (count + 1) - - line = inputfile.next() - - else: #different number of lines - print "Error", info - - if len(info) == 6: #still unrestricted, despite being out of loop - self.homos = [homoa, homob] - - self.moenergies = [numpy.array(x, "d") for x in self.moenergies] - self.homos = numpy.array(self.homos, "i") - - if line[1:28] == "Vibrations and Normal Modes": - # Section on extracting vibdisps - # Also contains vibfreqs, but these are extracted in the - # following section (see below) - self.vibdisps = [] - equals = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - header = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - - freqs = inputfile.next() - while freqs.strip()!="": - minus = inputfile.next() - p = [ [], [], [] ] - for i in range(len(self.atomnos)): - broken = map(float, inputfile.next().split()[1:]) - for j in range(0, len(broken), 3): - p[j/3].append(broken[j:j+3]) - self.vibdisps.extend(p[:(len(broken)/3)]) - blank = inputfile.next() - blank = inputfile.next() - freqs = inputfile.next() - self.vibdisps = numpy.array(self.vibdisps, "d") - - if line[1:24] == "List of All Frequencies": - # Start of the IR/Raman frequency section - self.updateprogress(inputfile, "Frequency information", self.fupdate) - - # self.vibsyms = [] # Need to look into this a bit more - self.vibirs = [] - self.vibfreqs = [] - for i in range(8): - line = inputfile.next() - line = inputfile.next().strip() - while line: - temp = line.split() - self.vibfreqs.append(float(temp[0])) - self.vibirs.append(float(temp[2])) # or is it temp[1]? - line = inputfile.next().strip() - self.vibfreqs = numpy.array(self.vibfreqs, "d") - self.vibirs = numpy.array(self.vibirs, "d") - if hasattr(self, "vibramans"): - self.vibramans = numpy.array(self.vibramans, "d") - - - #******************************************************************************************************************8 - #delete this after new implementation using smat, eigvec print,eprint? - if line[1:49] == "Total nr. of (C)SFOs (summation over all irreps)": - # Extract the number of basis sets - self.nbasis = int(line.split(":")[1].split()[0]) - - # now that we're here, let's extract aonames - - self.fonames = [] - self.start_indeces = {} - - blank = inputfile.next() - note = inputfile.next() - symoffset = 0 - - blank = inputfile.next() - blank = inputfile.next() - if len(blank) > 2: #fix for ADF2006.01 as it has another note - blank = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - - self.nosymreps = [] - while len(self.fonames) < self.nbasis: - - symline = inputfile.next() - sym = symline.split()[1] - line = inputfile.next() - num = int(line.split(':')[1].split()[0]) - self.nosymreps.append(num) - - #read until line "--------..." is found - while line.find('-----') < 0: - line = inputfile.next() - - line = inputfile.next() # the start of the first SFO - - while len(self.fonames) < symoffset + num: - info = line.split() - - #index0 index1 occ2 energy3/4 fragname5 coeff6 orbnum7 orbname8 fragname9 - if not sym in self.start_indeces.keys(): - #have we already set the start index for this symmetry? - self.start_indeces[sym] = int(info[1]) - - orbname = info[8] - orbital = info[7] + orbname.replace(":", "") - - fragname = info[5] - frag = fragname + info[9] - - coeff = float(info[6]) - - line = inputfile.next() - while line.strip() and not line[:7].strip(): # while it's the same SFO - # i.e. while not completely blank, but blank at the start - info = line[43:].split() - if len(info)>0: # len(info)==0 for the second line of dvb_ir.adfout - frag += "+" + fragname + info[-1] - coeff = float(info[-4]) - if coeff < 0: - orbital += '-' + info[-3] + info[-2].replace(":", "") - else: - orbital += '+' + info[-3] + info[-2].replace(":", "") - line = inputfile.next() - # At this point, we are either at the start of the next SFO or at - # a blank line...the end - - self.fonames.append("%s_%s" % (frag, orbital)) - symoffset += num - - # blankline blankline - inputfile.next(); inputfile.next() - - if line[1:32] == "S F O P O P U L A T I O N S ,": - #Extract overlap matrix - - self.fooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") - - symoffset = 0 - - for nosymrep in self.nosymreps: - - line = inputfile.next() - while line.find('===') < 10: #look for the symmetry labels - line = inputfile.next() - #blank blank text blank col row - for i in range(6): - inputfile.next() - - base = 0 - while base < nosymrep: #have we read all the columns? - - for i in range(nosymrep - base): - - self.updateprogress(inputfile, "Overlap", self.fupdate) - line = inputfile.next() - parts = line.split()[1:] - for j in range(len(parts)): - k = float(parts[j]) - self.fooverlaps[base + symoffset + j, base + symoffset +i] = k - self.fooverlaps[base + symoffset + i, base + symoffset + j] = k - - #blank, blank, column - for i in range(3): - inputfile.next() - - base += 4 - - symoffset += nosymrep - base = 0 - -# The commented code below makes the atombasis attribute based on the BAS function in ADF, -# but this is probably not so useful, since SFOs are used to build MOs in ADF. -# if line[1:54] == "BAS: List of all Elementary Cartesian Basis Functions": -# -# self.atombasis = [] -# -# # There will be some text, followed by a line: -# # (power of) X Y Z R Alpha on Atom -# while not line[1:11] == "(power of)": -# line = inputfile.next() -# dashes = inputfile.next() -# blank = inputfile.next() -# line = inputfile.next() -# # There will be two blank lines when there are no more atom types. -# while line.strip() != "": -# atoms = [int(i)-1 for i in line.split()[1:]] -# for n in range(len(atoms)): -# self.atombasis.append([]) -# dashes = inputfile.next() -# line = inputfile.next() -# while line.strip() != "": -# indices = [int(i)-1 for i in line.split()[5:]] -# for i in range(len(indices)): -# self.atombasis[atoms[i]].append(indices[i]) -# line = inputfile.next() -# line = inputfile.next() - - if line[48:67] == "SFO MO coefficients": - - self.mocoeffs = [numpy.zeros((self.nbasis, self.nbasis), "d")] - spin = 0 - symoffset = 0 - lastrow = 0 - - # Section ends with "1" at beggining of a line. - while line[0] != "1": - line = inputfile.next() - - # If spin is specified, then there will be two coefficient matrices. - if line.strip() == "***** SPIN 1 *****": - self.mocoeffs = [numpy.zeros((self.nbasis, self.nbasis), "d"), - numpy.zeros((self.nbasis, self.nbasis), "d")] - - # Bump up the spin. - if line.strip() == "***** SPIN 2 *****": - spin = 1 - symoffset = 0 - lastrow = 0 - - # Next symmetry. - if line.strip()[:4] == "=== ": - sym = line.split()[1] - if self.nosymflag: - aolist = range(self.nbasis) - else: - aolist = self.symlist[sym][spin] - # Add to the symmetry offset of AO ordering. - symoffset += lastrow - - # Blocks with coefficient always start with "MOs :". - if line[1:6] == "MOs :": - # Next line has the MO index contributed to. - monumbers = [int(n) for n in line[6:].split()] - occup = inputfile.next() - label = inputfile.next() - line = inputfile.next() - # The table can end with a blank line or "1". - row = 0 - while not line.strip() in ["", "1"]: - info = line.split() - - if int(info[0]) < self.start_indeces[sym]: - #check to make sure we aren't parsing CFs - line = inputfile.next() - continue - - self.updateprogress(inputfile, "Coefficients", self.fupdate) - row += 1 - coeffs = [float(x) for x in info[1:]] - moindices = [aolist[n-1] for n in monumbers] - # The AO index is 1 less than the row. - aoindex = symoffset + row - 1 - for i in range(len(monumbers)): - self.mocoeffs[spin][moindices[i],aoindex] = coeffs[i] - line = inputfile.next() - lastrow = row - - if line[4:53] == "Final excitation energies from Davidson algorithm": - - # move forward in file past some various algorthm info - - # * Final excitation energies from Davidson algorithm * - # * * - # ************************************************************************** - - # Number of loops in Davidson routine = 20 - # Number of matrix-vector multiplications = 24 - # Type of excitations = SINGLET-SINGLET - - inputfile.next(); inputfile.next(); inputfile.next() - inputfile.next(); inputfile.next(); inputfile.next() - inputfile.next(); inputfile.next() - - symm = self.normalisesym(inputfile.next().split()[1]) - - # move forward in file past some more txt and header info - - # Excitation energies E in a.u. and eV, dE wrt prev. cycle, - # oscillator strengths f in a.u. - - # no. E/a.u. E/eV f dE/a.u. - # ----------------------------------------------------- - - inputfile.next(); inputfile.next(); inputfile.next() - inputfile.next(); inputfile.next(); inputfile.next() - - # now start parsing etenergies and etoscs - - etenergies = [] - etoscs = [] - etsyms = [] - - line = inputfile.next() - while len(line) > 2: - info = line.split() - etenergies.append(utils.convertor(float(info[2]), "eV", "cm-1")) - etoscs.append(float(info[3])) - etsyms.append(symm) - line = inputfile.next() - - # move past next section - while line[1:53] != "Major MO -> MO transitions for the above excitations": - line = inputfile.next() - - # move past headers - - # Excitation Occupied to virtual Contribution - # Nr. orbitals weight contribibutions to - # (sum=1) transition dipole moment - # x y z - - inputfile.next(), inputfile.next(), inputfile.next() - inputfile.next(), inputfile.next(), inputfile.next() - - # before we start handeling transitions, we need - # to create mosyms with indices - # only restricted calcs are possible in ADF - - counts = {} - syms = [] - for mosym in self.mosyms[0]: - if counts.keys().count(mosym) == 0: - counts[mosym] = 1 - else: - counts[mosym] += 1 - - syms.append(str(counts[mosym]) + mosym) - - import re - etsecs = [] - printed_warning = False - - for i in range(len(etenergies)): - etsec = [] - line = inputfile.next() - info = line.split() - while len(info) > 0: - - match = re.search('[^0-9]', info[1]) - index1 = int(info[1][:match.start(0)]) - text = info[1][match.start(0):] - symtext = text[0].upper() + text[1:] - sym1 = str(index1) + self.normalisesym(symtext) - - match = re.search('[^0-9]', info[3]) - index2 = int(info[3][:match.start(0)]) - text = info[3][match.start(0):] - symtext = text[0].upper() + text[1:] - sym2 = str(index2) + self.normalisesym(symtext) - - try: - index1 = syms.index(sym1) - except ValueError: - if not printed_warning: - self.logger.warning("Etsecs are not accurate!") - printed_warning = True - - try: - index2 = syms.index(sym2) - except ValueError: - if not printed_warning: - self.logger.warning("Etsecs are not accurate!") - printed_warning = True - - etsec.append([(index1, 0), (index2, 0), float(info[4])]) - - line = inputfile.next() - info = line.split() - - etsecs.append(etsec) - - - if not hasattr(self, "etenergies"): - self.etenergies = etenergies - else: - self.etenergies += etenergies - - if not hasattr(self, "etoscs"): - self.etoscs = etoscs - else: - self.etoscs += etoscs - - if not hasattr(self, "etsyms"): - self.etsyms = etsyms - else: - self.etsyms += etsyms - - if not hasattr(self, "etsecs"): - self.etsecs = etsecs - else: - self.etsecs += etsecs - -if __name__ == "__main__": - import doctest, adfparser - doctest.testmod(adfparser, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 861 $" + + +import numpy + +import logfileparser +import utils + + +class ADF(logfileparser.Logfile): + """An ADF log file""" + + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(ADF, self).__init__(logname="ADF", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "ADF log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'ADF("%s")' % (self.filename) + + def normalisesym(self, label): + """Use standard symmetry labels instead of ADF labels. + + To normalise: + (1) any periods are removed (except in the case of greek letters) + (2) XXX is replaced by X, and a " added. + (3) XX is replaced by X, and a ' added. + (4) The greek letters Sigma, Pi, Delta and Phi are replaced by + their lowercase equivalent. + + >>> sym = ADF("dummyfile").normalisesym + >>> labels = ['A','s','A1','A1.g','Sigma','Pi','Delta','Phi','Sigma.g','A.g','AA','AAA','EE1','EEE1'] + >>> map(sym,labels) + ['A', 's', 'A1', 'A1g', 'sigma', 'pi', 'delta', 'phi', 'sigma.g', 'Ag', "A'", 'A"', "E1'", 'E1"'] + """ + greeks = ['Sigma', 'Pi', 'Delta', 'Phi'] + for greek in greeks: + if label.startswith(greek): + return label.lower() + + ans = label.replace(".", "") + if ans[1:3] == "''": + temp = ans[0] + '"' + ans = temp + + l = len(ans) + if l > 1 and ans[0] == ans[1]: # Python only tests the second condition if the first is true + if l > 2 and ans[1] == ans[2]: + ans = ans.replace(ans[0]*3, ans[0]) + '"' + else: + ans = ans.replace(ans[0]*2, ans[0]) + "'" + return ans + + def normalisedegenerates(self, label, num, ndict=None): + """Generate a string used for matching degenerate orbital labels + + To normalise: + (1) if label is E or T, return label:num + (2) if label is P or D, look up in dict, and return answer + """ + + if not ndict: + ndict = { 'P': {0:"P:x", 1:"P:y", 2:"P:z"},\ + 'D': {0:"D:z2", 1:"D:x2-y2", 2:"D:xy", 3:"D:xz", 4:"D:yz"}} + + if ndict.has_key(label): + if ndict[label].has_key(num): + return ndict[label][num] + else: + return "%s:%i"%(label,num+1) + else: + return "%s:%i"%(label,num+1) + + def before_parsing(self): + + # Used to avoid extracting the final geometry twice in a GeoOpt + self.NOTFOUND, self.GETLAST, self.NOMORE = range(3) + self.finalgeometry = self.NOTFOUND + + # Used for calculating the scftarget (variables names taken from the ADF manual) + self.accint = self.SCFconv = self.sconv2 = None + + # keep track of nosym and unrestricted case to parse Energies since it doens't have an all Irreps section + self.nosymflag = False + self.unrestrictedflag = False + + SCFCNV, SCFCNV2 = range(2) #used to index self.scftargets[] + maxelem, norm = range(2) # used to index scf.values + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + if line.find("INPUT FILE") >= 0: + #check to make sure we aren't parsing Create jobs + while line: + + self.updateprogress(inputfile, "Unsupported Information", self.fupdate) + + if line.find("INPUT FILE") >=0 and hasattr(self,"scftargets"): + #does this file contain multiple calculations? + #if so, print a warning and skip to end of file + self.logger.warning("Skipping remaining calculations") + inputfile.seek(0,2) + break + + if line.find("INPUT FILE") >= 0: + line2 = inputfile.next() + else: + line2 = None + + if line2 and len(line2) <= 2: + #make sure that it's not blank like in the NiCO4 regression + line2 = inputfile.next() + + if line2 and (line2.find("Create") < 0 and line2.find("create") < 0): + break + + line = inputfile.next() + + if line[1:10] == "Symmetry:": + info = line.split() + if info[1] == "NOSYM": + self.nosymflag = True + + # Use this to read the subspecies of irreducible representations. + # It will be a list, with each element representing one irrep. + if line.strip() == "Irreducible Representations, including subspecies": + dashes = inputfile.next() + self.irreps = [] + line = inputfile.next() + while line.strip() != "": + self.irreps.append(line.split()) + line = inputfile.next() + + if line[4:13] == 'Molecule:': + info = line.split() + if info[1] == 'UNrestricted': + self.unrestrictedflag = True + + if line[1:6] == "ATOMS": + # Find the number of atoms and their atomic numbers + # Also extract the starting coordinates (for a GeoOpt anyway) + self.updateprogress(inputfile, "Attributes", self.cupdate) + + self.atomnos = [] + self.atomcoords = [] + self.coreelectrons = [] + + underline = inputfile.next() #clear pointless lines + label1 = inputfile.next() # + label2 = inputfile.next() # + line = inputfile.next() + atomcoords = [] + while len(line)>2: #ensure that we are reading no blank lines + info = line.split() + element = info[1].split('.')[0] + self.atomnos.append(self.table.number[element]) + atomcoords.append(map(float, info[2:5])) + self.coreelectrons.append(int(float(info[5]) - float(info[6]))) + line = inputfile.next() + self.atomcoords.append(atomcoords) + + self.natom = len(self.atomnos) + self.atomnos = numpy.array(self.atomnos, "i") + + if line[1:10] == "FRAGMENTS": + header = inputfile.next() + + self.frags = [] + self.fragnames = [] + + line = inputfile.next() + while len(line) > 2: #ensure that we are reading no blank lines + info = line.split() + + if len(info) == 7: #fragment name is listed here + self.fragnames.append("%s_%s"%(info[1],info[0])) + self.frags.append([]) + self.frags[-1].append(int(info[2]) - 1) + + elif len(info) == 5: #add atoms into last fragment + self.frags[-1].append(int(info[0]) - 1) + + line = inputfile.next() + + # Extract charge + if line[1:11] == "Net Charge": + self.charge = int(line.split()[2]) + line = inputfile.next() + if len(line.strip()): + # Spin polar: 1 (Spin_A minus Spin_B electrons) + self.mult = int(line.split()[2]) + 1 + # (Not sure about this for higher multiplicities) + else: + self.mult = 1 + + if line[1:22] == "S C F U P D A T E S": + # find targets for SCF convergence + + if not hasattr(self,"scftargets"): + self.scftargets = [] + + #underline, blank, nr + for i in range(3): + inputfile.next() + + line = inputfile.next() + self.SCFconv = float(line.split()[-1]) + line = inputfile.next() + self.sconv2 = float(line.split()[-1]) + + if line[1:11] == "CYCLE 1": + + self.updateprogress(inputfile, "QM convergence", self.fupdate) + + newlist = [] + line = inputfile.next() + + if not hasattr(self,"geovalues"): + # This is the first SCF cycle + self.scftargets.append([self.sconv2*10, self.sconv2]) + elif self.finalgeometry in [self.GETLAST, self.NOMORE]: + # This is the final SCF cycle + self.scftargets.append([self.SCFconv*10, self.SCFconv]) + else: + # This is an intermediate SCF cycle + oldscftst = self.scftargets[-1][1] + grdmax = self.geovalues[-1][1] + scftst = max(self.SCFconv, min(oldscftst, grdmax/30, 10**(-self.accint))) + self.scftargets.append([scftst*10, scftst]) + + while line.find("SCF CONVERGED") == -1 and line.find("SCF not fully converged, result acceptable") == -1 and line.find("SCF NOT CONVERGED") == -1: + if line[4:12] == "SCF test": + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + + info = line.split() + newlist.append([float(info[4]), abs(float(info[6]))]) + try: + line = inputfile.next() + except StopIteration: #EOF reached? + self.logger.warning("SCF did not converge, so attributes may be missing") + break + + if line.find("SCF not fully converged, result acceptable") > 0: + self.logger.warning("SCF not fully converged, results acceptable") + + if line.find("SCF NOT CONVERGED") > 0: + self.logger.warning("SCF did not converge! moenergies and mocoeffs are unreliable") + + if hasattr(self, "scfvalues"): + self.scfvalues.append(newlist) + + # Parse SCF energy for SP calcs from bonding energy decomposition section. + # It seems ADF does not print it earlier for SP calcualtions. + # If it does (does it?), parse that instead. + # Check that scfenergies does not exist, becuase gopt runs also print this, + # repeating the values in the last "Geometry Convergence Tests" section. + if "Total Bonding Energy:" in line: + if not hasattr(self, "scfenergies"): + energy = utils.convertor(float(line.split()[3]), "hartree", "eV") + self.scfenergies = [energy] + + if line[51:65] == "Final Geometry": + self.finalgeometry = self.GETLAST + + if line[1:24] == "Coordinates (Cartesian)" and self.finalgeometry in [self.NOTFOUND, self.GETLAST]: + # Get the coordinates from each step of the GeoOpt + if not hasattr(self, "atomcoords"): + self.atomcoords = [] + equals = inputfile.next() + blank = inputfile.next() + title = inputfile.next() + title = inputfile.next() + hyphens = inputfile.next() + + atomcoords = [] + line = inputfile.next() + while line != hyphens: + atomcoords.append(map(float, line.split()[5:8])) + line = inputfile.next() + self.atomcoords.append(atomcoords) + if self.finalgeometry == self.GETLAST: # Don't get any more coordinates + self.finalgeometry = self.NOMORE + + if line[1:27] == 'Geometry Convergence Tests': + # Extract Geometry convergence information + if not hasattr(self, "geotargets"): + self.geovalues = [] + self.geotargets = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0], "d") + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + equals = inputfile.next() + blank = inputfile.next() + line = inputfile.next() + temp = inputfile.next().strip().split() + self.scfenergies.append(utils.convertor(float(temp[-1]), "hartree", "eV")) + for i in range(6): + line = inputfile.next() + values = [] + for i in range(5): + temp = inputfile.next().split() + self.geotargets[i] = float(temp[-3]) + values.append(float(temp[-4])) + self.geovalues.append(values) + + if line[1:27] == 'General Accuracy Parameter': + # Need to know the accuracy of the integration grid to + # calculate the scftarget...note that it changes with time + self.accint = float(line.split()[-1]) + + if line.find('Orbital Energies, per Irrep and Spin') > 0 and not hasattr(self, "mosyms") and self.nosymflag and not self.unrestrictedflag: + #Extracting orbital symmetries and energies, homos for nosym case + #Should only be for restricted case because there is a better text block for unrestricted and nosym + + self.mosyms = [[]] + + self.moenergies = [[]] + + underline = inputfile.next() + header = inputfile.next() + underline = inputfile.next() + label = inputfile.next() + line = inputfile.next() + + info = line.split() + + if not info[0] == '1': + self.logger.warning("MO info up to #%s is missing" % info[0]) + + #handle case where MO information up to a certain orbital are missing + while int(info[0]) - 1 != len(self.moenergies[0]): + self.moenergies[0].append(99999) + self.mosyms[0].append('A') + + homoA = None + + while len(line) > 10: + info = line.split() + self.mosyms[0].append('A') + self.moenergies[0].append(utils.convertor(float(info[2]), 'hartree', 'eV')) + if info[1] == '0.000' and not hasattr(self, 'homos'): + self.homos = [len(self.moenergies[0]) - 2] + line = inputfile.next() + + self.moenergies = [numpy.array(self.moenergies[0], "d")] + self.homos = numpy.array(self.homos, "i") + + if line[1:29] == 'Orbital Energies, both Spins' and not hasattr(self, "mosyms") and self.nosymflag and self.unrestrictedflag: + #Extracting orbital symmetries and energies, homos for nosym case + #should only be here if unrestricted and nosym + + self.mosyms = [[], []] + + moenergies = [[], []] + + underline = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + underline = inputfile.next() + line = inputfile.next() + + homoa = 0 + homob = None + + while len(line) > 5: + info = line.split() + if info[2] == 'A': + self.mosyms[0].append('A') + moenergies[0].append(utils.convertor(float(info[4]), 'hartree', 'eV')) + if info[3] != '0.00': + homoa = len(moenergies[0]) - 1 + elif info[2] == 'B': + self.mosyms[1].append('A') + moenergies[1].append(utils.convertor(float(info[4]), 'hartree', 'eV')) + if info[3] != '0.00': + homob = len(moenergies[1]) - 1 + else: + print "Error reading line: %s" % line + + line = inputfile.next() + + self.moenergies = [numpy.array(x, "d") for x in moenergies] + self.homos = numpy.array([homoa, homob], "i") + + + if line[1:29] == 'Orbital Energies, all Irreps' and not hasattr(self, "mosyms"): + #Extracting orbital symmetries and energies, homos + self.mosyms = [[]] + self.symlist = {} + + self.moenergies = [[]] + + underline = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + underline2 = inputfile.next() + line = inputfile.next() + + homoa = None + homob = None + + #multiple = {'E':2, 'T':3, 'P':3, 'D':5} + # The above is set if there are no special irreps + names = [irrep[0].split(':')[0] for irrep in self.irreps] + counts = [len(irrep) for irrep in self.irreps] + multiple = dict(zip(names, counts)) + irrepspecies = {} + for n in range(len(names)): + indices = range(counts[n]) + subspecies = self.irreps[n] + irrepspecies[names[n]] = dict(zip(indices, subspecies)) + + while line.strip(): + info = line.split() + if len(info) == 5: #this is restricted + #count = multiple.get(info[0][0],1) + count = multiple.get(info[0],1) + for repeat in range(count): # i.e. add E's twice, T's thrice + self.mosyms[0].append(self.normalisesym(info[0])) + self.moenergies[0].append(utils.convertor(float(info[3]), 'hartree', 'eV')) + + sym = info[0] + if count > 1: # add additional sym label + sym = self.normalisedegenerates(info[0],repeat,ndict=irrepspecies) + + try: + self.symlist[sym][0].append(len(self.moenergies[0])-1) + except KeyError: + self.symlist[sym]=[[]] + self.symlist[sym][0].append(len(self.moenergies[0])-1) + + if info[2] == '0.00' and not hasattr(self, 'homos'): + self.homos = [len(self.moenergies[0]) - (count + 1)] #count, because need to handle degenerate cases + line = inputfile.next() + elif len(info) == 6: #this is unrestricted + if len(self.moenergies) < 2: #if we don't have space, create it + self.moenergies.append([]) + self.mosyms.append([]) +# count = multiple.get(info[0][0], 1) + count = multiple.get(info[0], 1) + if info[2] == 'A': + for repeat in range(count): # i.e. add E's twice, T's thrice + self.mosyms[0].append(self.normalisesym(info[0])) + self.moenergies[0].append(utils.convertor(float(info[4]), 'hartree', 'eV')) + + sym = info[0] + if count > 1: #add additional sym label + sym = self.normalisedegenerates(info[0],repeat) + + try: + self.symlist[sym][0].append(len(self.moenergies[0])-1) + except KeyError: + self.symlist[sym]=[[],[]] + self.symlist[sym][0].append(len(self.moenergies[0])-1) + + if info[3] == '0.00' and homoa == None: + homoa = len(self.moenergies[0]) - (count + 1) #count because degenerate cases need to be handled + + if info[2] == 'B': + for repeat in range(count): # i.e. add E's twice, T's thrice + self.mosyms[1].append(self.normalisesym(info[0])) + self.moenergies[1].append(utils.convertor(float(info[4]), 'hartree', 'eV')) + + sym = info[0] + if count > 1: #add additional sym label + sym = self.normalisedegenerates(info[0],repeat) + + try: + self.symlist[sym][1].append(len(self.moenergies[1])-1) + except KeyError: + self.symlist[sym]=[[],[]] + self.symlist[sym][1].append(len(self.moenergies[1])-1) + + if info[3] == '0.00' and homob == None: + homob = len(self.moenergies[1]) - (count + 1) + + line = inputfile.next() + + else: #different number of lines + print "Error", info + + if len(info) == 6: #still unrestricted, despite being out of loop + self.homos = [homoa, homob] + + self.moenergies = [numpy.array(x, "d") for x in self.moenergies] + self.homos = numpy.array(self.homos, "i") + + if line[1:28] == "Vibrations and Normal Modes": + # Section on extracting vibdisps + # Also contains vibfreqs, but these are extracted in the + # following section (see below) + self.vibdisps = [] + equals = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + header = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + + freqs = inputfile.next() + while freqs.strip()!="": + minus = inputfile.next() + p = [ [], [], [] ] + for i in range(len(self.atomnos)): + broken = map(float, inputfile.next().split()[1:]) + for j in range(0, len(broken), 3): + p[j/3].append(broken[j:j+3]) + self.vibdisps.extend(p[:(len(broken)/3)]) + blank = inputfile.next() + blank = inputfile.next() + freqs = inputfile.next() + self.vibdisps = numpy.array(self.vibdisps, "d") + + if line[1:24] == "List of All Frequencies": + # Start of the IR/Raman frequency section + self.updateprogress(inputfile, "Frequency information", self.fupdate) + + # self.vibsyms = [] # Need to look into this a bit more + self.vibirs = [] + self.vibfreqs = [] + for i in range(8): + line = inputfile.next() + line = inputfile.next().strip() + while line: + temp = line.split() + self.vibfreqs.append(float(temp[0])) + self.vibirs.append(float(temp[2])) # or is it temp[1]? + line = inputfile.next().strip() + self.vibfreqs = numpy.array(self.vibfreqs, "d") + self.vibirs = numpy.array(self.vibirs, "d") + if hasattr(self, "vibramans"): + self.vibramans = numpy.array(self.vibramans, "d") + + + #******************************************************************************************************************8 + #delete this after new implementation using smat, eigvec print,eprint? + if line[1:49] == "Total nr. of (C)SFOs (summation over all irreps)": + # Extract the number of basis sets + self.nbasis = int(line.split(":")[1].split()[0]) + + # now that we're here, let's extract aonames + + self.fonames = [] + self.start_indeces = {} + + blank = inputfile.next() + note = inputfile.next() + symoffset = 0 + + blank = inputfile.next() + blank = inputfile.next() + if len(blank) > 2: #fix for ADF2006.01 as it has another note + blank = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + + self.nosymreps = [] + while len(self.fonames) < self.nbasis: + + symline = inputfile.next() + sym = symline.split()[1] + line = inputfile.next() + num = int(line.split(':')[1].split()[0]) + self.nosymreps.append(num) + + #read until line "--------..." is found + while line.find('-----') < 0: + line = inputfile.next() + + line = inputfile.next() # the start of the first SFO + + while len(self.fonames) < symoffset + num: + info = line.split() + + #index0 index1 occ2 energy3/4 fragname5 coeff6 orbnum7 orbname8 fragname9 + if not sym in self.start_indeces.keys(): + #have we already set the start index for this symmetry? + self.start_indeces[sym] = int(info[1]) + + orbname = info[8] + orbital = info[7] + orbname.replace(":", "") + + fragname = info[5] + frag = fragname + info[9] + + coeff = float(info[6]) + + line = inputfile.next() + while line.strip() and not line[:7].strip(): # while it's the same SFO + # i.e. while not completely blank, but blank at the start + info = line[43:].split() + if len(info)>0: # len(info)==0 for the second line of dvb_ir.adfout + frag += "+" + fragname + info[-1] + coeff = float(info[-4]) + if coeff < 0: + orbital += '-' + info[-3] + info[-2].replace(":", "") + else: + orbital += '+' + info[-3] + info[-2].replace(":", "") + line = inputfile.next() + # At this point, we are either at the start of the next SFO or at + # a blank line...the end + + self.fonames.append("%s_%s" % (frag, orbital)) + symoffset += num + + # blankline blankline + inputfile.next(); inputfile.next() + + if line[1:32] == "S F O P O P U L A T I O N S ,": + #Extract overlap matrix + + self.fooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") + + symoffset = 0 + + for nosymrep in self.nosymreps: + + line = inputfile.next() + while line.find('===') < 10: #look for the symmetry labels + line = inputfile.next() + #blank blank text blank col row + for i in range(6): + inputfile.next() + + base = 0 + while base < nosymrep: #have we read all the columns? + + for i in range(nosymrep - base): + + self.updateprogress(inputfile, "Overlap", self.fupdate) + line = inputfile.next() + parts = line.split()[1:] + for j in range(len(parts)): + k = float(parts[j]) + self.fooverlaps[base + symoffset + j, base + symoffset +i] = k + self.fooverlaps[base + symoffset + i, base + symoffset + j] = k + + #blank, blank, column + for i in range(3): + inputfile.next() + + base += 4 + + symoffset += nosymrep + base = 0 + +# The commented code below makes the atombasis attribute based on the BAS function in ADF, +# but this is probably not so useful, since SFOs are used to build MOs in ADF. +# if line[1:54] == "BAS: List of all Elementary Cartesian Basis Functions": +# +# self.atombasis = [] +# +# # There will be some text, followed by a line: +# # (power of) X Y Z R Alpha on Atom +# while not line[1:11] == "(power of)": +# line = inputfile.next() +# dashes = inputfile.next() +# blank = inputfile.next() +# line = inputfile.next() +# # There will be two blank lines when there are no more atom types. +# while line.strip() != "": +# atoms = [int(i)-1 for i in line.split()[1:]] +# for n in range(len(atoms)): +# self.atombasis.append([]) +# dashes = inputfile.next() +# line = inputfile.next() +# while line.strip() != "": +# indices = [int(i)-1 for i in line.split()[5:]] +# for i in range(len(indices)): +# self.atombasis[atoms[i]].append(indices[i]) +# line = inputfile.next() +# line = inputfile.next() + + if line[48:67] == "SFO MO coefficients": + + self.mocoeffs = [numpy.zeros((self.nbasis, self.nbasis), "d")] + spin = 0 + symoffset = 0 + lastrow = 0 + + # Section ends with "1" at beggining of a line. + while line[0] != "1": + line = inputfile.next() + + # If spin is specified, then there will be two coefficient matrices. + if line.strip() == "***** SPIN 1 *****": + self.mocoeffs = [numpy.zeros((self.nbasis, self.nbasis), "d"), + numpy.zeros((self.nbasis, self.nbasis), "d")] + + # Bump up the spin. + if line.strip() == "***** SPIN 2 *****": + spin = 1 + symoffset = 0 + lastrow = 0 + + # Next symmetry. + if line.strip()[:4] == "=== ": + sym = line.split()[1] + if self.nosymflag: + aolist = range(self.nbasis) + else: + aolist = self.symlist[sym][spin] + # Add to the symmetry offset of AO ordering. + symoffset += lastrow + + # Blocks with coefficient always start with "MOs :". + if line[1:6] == "MOs :": + # Next line has the MO index contributed to. + monumbers = [int(n) for n in line[6:].split()] + occup = inputfile.next() + label = inputfile.next() + line = inputfile.next() + # The table can end with a blank line or "1". + row = 0 + while not line.strip() in ["", "1"]: + info = line.split() + + if int(info[0]) < self.start_indeces[sym]: + #check to make sure we aren't parsing CFs + line = inputfile.next() + continue + + self.updateprogress(inputfile, "Coefficients", self.fupdate) + row += 1 + coeffs = [float(x) for x in info[1:]] + moindices = [aolist[n-1] for n in monumbers] + # The AO index is 1 less than the row. + aoindex = symoffset + row - 1 + for i in range(len(monumbers)): + self.mocoeffs[spin][moindices[i],aoindex] = coeffs[i] + line = inputfile.next() + lastrow = row + + if line[4:53] == "Final excitation energies from Davidson algorithm": + + # move forward in file past some various algorthm info + + # * Final excitation energies from Davidson algorithm * + # * * + # ************************************************************************** + + # Number of loops in Davidson routine = 20 + # Number of matrix-vector multiplications = 24 + # Type of excitations = SINGLET-SINGLET + + inputfile.next(); inputfile.next(); inputfile.next() + inputfile.next(); inputfile.next(); inputfile.next() + inputfile.next(); inputfile.next() + + symm = self.normalisesym(inputfile.next().split()[1]) + + # move forward in file past some more txt and header info + + # Excitation energies E in a.u. and eV, dE wrt prev. cycle, + # oscillator strengths f in a.u. + + # no. E/a.u. E/eV f dE/a.u. + # ----------------------------------------------------- + + inputfile.next(); inputfile.next(); inputfile.next() + inputfile.next(); inputfile.next(); inputfile.next() + + # now start parsing etenergies and etoscs + + etenergies = [] + etoscs = [] + etsyms = [] + + line = inputfile.next() + while len(line) > 2: + info = line.split() + etenergies.append(utils.convertor(float(info[2]), "eV", "cm-1")) + etoscs.append(float(info[3])) + etsyms.append(symm) + line = inputfile.next() + + # move past next section + while line[1:53] != "Major MO -> MO transitions for the above excitations": + line = inputfile.next() + + # move past headers + + # Excitation Occupied to virtual Contribution + # Nr. orbitals weight contribibutions to + # (sum=1) transition dipole moment + # x y z + + inputfile.next(), inputfile.next(), inputfile.next() + inputfile.next(), inputfile.next(), inputfile.next() + + # before we start handeling transitions, we need + # to create mosyms with indices + # only restricted calcs are possible in ADF + + counts = {} + syms = [] + for mosym in self.mosyms[0]: + if counts.keys().count(mosym) == 0: + counts[mosym] = 1 + else: + counts[mosym] += 1 + + syms.append(str(counts[mosym]) + mosym) + + import re + etsecs = [] + printed_warning = False + + for i in range(len(etenergies)): + etsec = [] + line = inputfile.next() + info = line.split() + while len(info) > 0: + + match = re.search('[^0-9]', info[1]) + index1 = int(info[1][:match.start(0)]) + text = info[1][match.start(0):] + symtext = text[0].upper() + text[1:] + sym1 = str(index1) + self.normalisesym(symtext) + + match = re.search('[^0-9]', info[3]) + index2 = int(info[3][:match.start(0)]) + text = info[3][match.start(0):] + symtext = text[0].upper() + text[1:] + sym2 = str(index2) + self.normalisesym(symtext) + + try: + index1 = syms.index(sym1) + except ValueError: + if not printed_warning: + self.logger.warning("Etsecs are not accurate!") + printed_warning = True + + try: + index2 = syms.index(sym2) + except ValueError: + if not printed_warning: + self.logger.warning("Etsecs are not accurate!") + printed_warning = True + + etsec.append([(index1, 0), (index2, 0), float(info[4])]) + + line = inputfile.next() + info = line.split() + + etsecs.append(etsec) + + + if not hasattr(self, "etenergies"): + self.etenergies = etenergies + else: + self.etenergies += etenergies + + if not hasattr(self, "etoscs"): + self.etoscs = etoscs + else: + self.etoscs += etoscs + + if not hasattr(self, "etsyms"): + self.etsyms = etsyms + else: + self.etsyms += etsyms + + if not hasattr(self, "etsecs"): + self.etsecs = etsecs + else: + self.etsecs += etsecs + +if __name__ == "__main__": + import doctest, adfparser + doctest.testmod(adfparser, verbose=False) diff --git a/external/cclib/parser/ccopen.py b/external/cclib/parser/ccopen.py index 758048eedb..edcef85bad 100644 --- a/external/cclib/parser/ccopen.py +++ b/external/cclib/parser/ccopen.py @@ -1,101 +1,101 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 860 $" - - -import types - -import logfileparser - -import adfparser -import gamessparser -import gamessukparser -import gaussianparser -import jaguarparser -import molproparser -import orcaparser - - -def ccopen(source, *args, **kargs): - """Guess the identity of a particular log file and return an instance of it. - - Inputs: - source - a single logfile, a list of logfiles, or an input stream - - Returns: - one of ADF, GAMESS, GAMESS UK, Gaussian, Jaguar, Molpro, ORCA, or - None (if it cannot figure it out or the file does not exist). - """ - - filetype = None - - # Try to open the logfile(s), using openlogfile. - if isinstance(source,types.StringTypes) or \ - isinstance(source,list) and all([isinstance(s,types.StringTypes) for s in source]): - try: - inputfile = logfileparser.openlogfile(source) - except IOError, (errno, strerror): - print "I/O error %s (%s): %s" %(errno, source, strerror) - return None - isstream = False - elif hasattr(source, "read"): - inputfile = source - isstream = True - else: - raise ValueError - - # Read through the logfile(s) and search for a clue. - for line in inputfile: - - if line.find("Amsterdam Density Functional") >= 0: - filetype = adfparser.ADF - break - - # Don't break in this case as it may be a GAMESS-UK file. - elif line.find("GAMESS") >= 0: - filetype = gamessparser.GAMESS - - # This can break, since it is non-GAMESS-UK specific. - elif line.find("GAMESS VERSION") >= 0: - filetype = gamessparser.GAMESS - break - - elif line.find("G A M E S S - U K") >= 0: - filetype = gamessukparser.GAMESSUK - break - - elif line.find("Gaussian, Inc.") >= 0: - filetype = gaussianparser.Gaussian - break - - elif line.find("Jaguar") >= 0: - filetype = jaguarparser.Jaguar - break - - elif line.find("PROGRAM SYSTEM MOLPRO") >= 0: - filetype = molproparser.Molpro - break - - # Molpro log files don't have the line above. Set this only if - # nothing else is detected, and notice it can be overwritten, - # since it does not break the loop. - elif line[0:8] == "1PROGRAM" and not filetype: - filetype = molproparser.Molpro - - elif line.find("O R C A") >= 0: - filetype = orcaparser.ORCA - break - - # Need to close file before creating a instance. - if not isstream: - inputfile.close() - - # Return an instance of the chosen class. - try: - return filetype(source, *args, **kargs) - except TypeError: - print "Log file type not identified." - raise +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 860 $" + + +import types + +import logfileparser + +import adfparser +import gamessparser +import gamessukparser +import gaussianparser +import jaguarparser +import molproparser +import orcaparser + + +def ccopen(source, *args, **kargs): + """Guess the identity of a particular log file and return an instance of it. + + Inputs: + source - a single logfile, a list of logfiles, or an input stream + + Returns: + one of ADF, GAMESS, GAMESS UK, Gaussian, Jaguar, Molpro, ORCA, or + None (if it cannot figure it out or the file does not exist). + """ + + filetype = None + + # Try to open the logfile(s), using openlogfile. + if isinstance(source,types.StringTypes) or \ + isinstance(source,list) and all([isinstance(s,types.StringTypes) for s in source]): + try: + inputfile = logfileparser.openlogfile(source) + except IOError, (errno, strerror): + print "I/O error %s (%s): %s" %(errno, source, strerror) + return None + isstream = False + elif hasattr(source, "read"): + inputfile = source + isstream = True + else: + raise ValueError + + # Read through the logfile(s) and search for a clue. + for line in inputfile: + + if line.find("Amsterdam Density Functional") >= 0: + filetype = adfparser.ADF + break + + # Don't break in this case as it may be a GAMESS-UK file. + elif line.find("GAMESS") >= 0: + filetype = gamessparser.GAMESS + + # This can break, since it is non-GAMESS-UK specific. + elif line.find("GAMESS VERSION") >= 0: + filetype = gamessparser.GAMESS + break + + elif line.find("G A M E S S - U K") >= 0: + filetype = gamessukparser.GAMESSUK + break + + elif line.find("Gaussian, Inc.") >= 0: + filetype = gaussianparser.Gaussian + break + + elif line.find("Jaguar") >= 0: + filetype = jaguarparser.Jaguar + break + + elif line.find("PROGRAM SYSTEM MOLPRO") >= 0: + filetype = molproparser.Molpro + break + + # Molpro log files don't have the line above. Set this only if + # nothing else is detected, and notice it can be overwritten, + # since it does not break the loop. + elif line[0:8] == "1PROGRAM" and not filetype: + filetype = molproparser.Molpro + + elif line.find("O R C A") >= 0: + filetype = orcaparser.ORCA + break + + # Need to close file before creating a instance. + if not isstream: + inputfile.close() + + # Return an instance of the chosen class. + try: + return filetype(source, *args, **kargs) + except TypeError: + print "Log file type not identified." + raise diff --git a/external/cclib/parser/data.py b/external/cclib/parser/data.py index 301a304bda..33fc963196 100644 --- a/external/cclib/parser/data.py +++ b/external/cclib/parser/data.py @@ -1,199 +1,199 @@ -""" -cclib (http://cclib.sf.net) is (c) 2007, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -gmagoon 4/5/10-4/6/10 (this notice added 4/29/10): Gregory Magoon modified this file from cclib 1.0 -""" - - -import cPickle as pickle -import os -import sys - -import numpy - - -class ccData(object): - """Class for objects containing data from cclib parsers and methods. - - Description of cclib attributes: - aonames -- atomic orbital names (list) - aooverlaps -- atomic orbital overlap matrix (array[2]) - atombasis -- indices of atomic orbitals on each atom (list of lists) - atomcoords -- atom coordinates (array[3], angstroms) - atomnos -- atomic numbers (array[1]) - charge -- net charge of the system (integer) - ccenergies -- molecular energies with Coupled-Cluster corrections (array[2], eV) - coreelectrons -- number of core electrons in atom pseudopotentials (array[1]) - etenergies -- energies of electronic transitions (array[1], 1/cm) - etoscs -- oscillator strengths of electronic transitions (array[1]) - etrotats -- rotatory strengths of electronic transitions (array[1], ??) - etsecs -- singly-excited configurations for electronic transitions (list of lists) - etsyms -- symmetries of electronic transitions (list) - fonames -- fragment orbital names (list) - fooverlaps -- fragment orbital overlap matrix (array[2]) - fragnames -- names of fragments (list) - frags -- indices of atoms in a fragment (list of lists) - gbasis -- coefficients and exponents of Gaussian basis functions (PyQuante format) - geotargets -- targets for convergence of geometry optimization (array[1]) - geovalues -- current values for convergence of geometry optmization (array[1]) - homos -- molecular orbital indices of HOMO(s) (array[1]) - mocoeffs -- molecular orbital coefficients (list of arrays[2]) - moenergies -- molecular orbital energies (list of arrays[1], eV) - mosyms -- orbital symmetries (list of lists) - mpenergies -- molecular electronic energies with Moller-Plesset corrections (array[2], eV) - mult -- multiplicity of the system (integer) - natom -- number of atoms (integer) - nbasis -- number of basis functions (integer) - nmo -- number of molecular orbitals (integer) - nocoeffs -- natural orbital coefficients (array[2]) - scfenergies -- molecular electronic energies after SCF (Hartree-Fock, DFT) (array[1], eV) - scftargets -- targets for convergence of the SCF (array[2]) - scfvalues -- current values for convergence of the SCF (list of arrays[2]) - stericenergy -- final steric energy (for MM4 calculations) - vibdisps -- cartesian displacement vectors (array[3], delta angstrom) - vibfreqs -- vibrational frequencies (array[1], 1/cm) - vibirs -- IR intensities (array[1], km/mol) - vibramans -- Raman intensities (array[1], A^4/Da) - vibsyms -- symmetries of vibrations (list) - (1) The term 'array' refers to a numpy array - (2) The number of dimensions of an array is given in square brackets - (3) Python indexes arrays/lists starting at zero, so if homos==[10], then - the 11th molecular orbital is the HOMO - """ - - def __init__(self, attributes=None): - """Initialize the cclibData object. - - Normally called in the parse() method of a Logfile subclass. - - Inputs: - attributes - dictionary of attributes to load - """ - - # Names of all supported attributes. - self._attrlist = ['aonames', 'aooverlaps', 'atombasis', - 'atomcoords', 'atomnos', - 'ccenergies', 'charge', 'coreelectrons', - 'etenergies', 'etoscs', 'etrotats', 'etsecs', 'etsyms', - 'fonames', 'fooverlaps', 'fragnames', 'frags', - 'gbasis', 'geotargets', 'geovalues', 'grads', - 'hessian', 'homos', - 'mocoeffs', 'moenergies', 'molmass', 'mosyms', 'mpenergies', 'mult', - 'natom', 'nbasis', 'nmo', 'nocoeffs', 'rotcons', 'rotsymm', - 'scfenergies', 'scftargets', 'scfvalues', 'stericenergy', - 'vibdisps', 'vibfreqs', 'vibirs', 'vibramans', 'vibsyms'] - - # The expected types for all supported attributes. - #gmagoon 5/27/09: added rotsymm type above and below - #gmagoon 6/8/09: added molmass (previously (maybe 5/28) I had added rotcons) - self._attrtypes = { "aonames": list, - "aooverlaps": numpy.ndarray, - "atombasis": list, - "atomcoords": numpy.ndarray, - "atomnos": numpy.ndarray, - "charge": int, - "coreelectrons": numpy.ndarray, - "etenergies": numpy.ndarray, - "etoscs": numpy.ndarray, - "etrotats": numpy.ndarray, - "etsecs": list, - "etsyms": list, - 'gbasis': list, - "geotargets": numpy.ndarray, - "geovalues": numpy.ndarray, - "grads": numpy.ndarray, - "hessian": numpy.ndarray, - "homos": numpy.ndarray, - "mocoeffs": list, - "moenergies": list, - "molmass": float, - "mosyms": list, - "mpenergies": numpy.ndarray, - "mult": int, - "natom": int, - "nbasis": int, - "nmo": int, - "nocoeffs": numpy.ndarray, - "rotcons": list, - "rotsymm": int, - "scfenergies": numpy.ndarray, - "scftargets": numpy.ndarray, - "scfvalues": list, - "stericenergy": float, - "vibdisps": numpy.ndarray, - "vibfreqs": numpy.ndarray, - "vibirs": numpy.ndarray, - "vibramans": numpy.ndarray, - "vibsyms": list, - } - - # Arrays are double precision by default, but these will be integer arrays. - self._intarrays = ['atomnos', 'coreelectrons', 'homos'] - - # Attributes that should be lists of arrays (double precision). - self._listsofarrays = ['mocoeffs', 'moenergies', 'scfvalues', 'rotcons']#gmagoon 5/28/09: added rotcons - - if attributes: - self.setattributes(attributes) - - def listify(self): - """Converts all attributes that are arrays or lists of arrays to lists.""" - - for k, v in self._attrtypes.iteritems(): - if hasattr(self, k): - if v == numpy.ndarray: - setattr(self, k, getattr(self, k).tolist()) - elif v == list and k in self._listsofarrays: - setattr(self, k, [x.tolist() for x in getattr(self, k)]) - - def arrayify(self): - """Converts appropriate attributes to arrays or lists of arrays.""" - - for k, v in self._attrtypes.iteritems(): - if hasattr(self, k): - precision = 'd' - if k in self._intarrays: - precision = 'i' - if v == numpy.ndarray: - setattr(self, k, numpy.array(getattr(self, k), precision)) - elif v == list and k in self._listsofarrays: - setattr(self, k, [numpy.array(x, precision) - for x in getattr(self, k)]) - - def getattributes(self, tolists=False): - """Returns a dictionary of existing data attributes. - - Inputs: - tolists - flag to convert attributes to lists where applicable - """ - - if tolists: - self.listify() - attributes = {} - for attr in self._attrlist: - if hasattr(self, attr): - attributes[attr] = getattr(self,attr) - if tolists: - self.arrayofy() - return attributes - - def setattributes(self, attributes): - """Sets data attributes given in a dictionary. - - Inputs: - attributes - dictionary of attributes to set - Outputs: - invalid - list of attributes names that were not set, which - means they are not specified in self._attrlist - """ - - if type(attributes) is not dict: - raise TypeError, "attributes must be in a dictionary" - - valid = [a for a in attributes if a in self._attrlist] - invalid = [a for a in attributes if a not in self._attrlist] - - for attr in valid: - setattr(self, attr, attributes[attr]) - self.arrayify() - return invalid +""" +cclib (http://cclib.sf.net) is (c) 2007, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +gmagoon 4/5/10-4/6/10 (this notice added 4/29/10): Gregory Magoon modified this file from cclib 1.0 +""" + + +import cPickle as pickle +import os +import sys + +import numpy + + +class ccData(object): + """Class for objects containing data from cclib parsers and methods. + + Description of cclib attributes: + aonames -- atomic orbital names (list) + aooverlaps -- atomic orbital overlap matrix (array[2]) + atombasis -- indices of atomic orbitals on each atom (list of lists) + atomcoords -- atom coordinates (array[3], angstroms) + atomnos -- atomic numbers (array[1]) + charge -- net charge of the system (integer) + ccenergies -- molecular energies with Coupled-Cluster corrections (array[2], eV) + coreelectrons -- number of core electrons in atom pseudopotentials (array[1]) + etenergies -- energies of electronic transitions (array[1], 1/cm) + etoscs -- oscillator strengths of electronic transitions (array[1]) + etrotats -- rotatory strengths of electronic transitions (array[1], ??) + etsecs -- singly-excited configurations for electronic transitions (list of lists) + etsyms -- symmetries of electronic transitions (list) + fonames -- fragment orbital names (list) + fooverlaps -- fragment orbital overlap matrix (array[2]) + fragnames -- names of fragments (list) + frags -- indices of atoms in a fragment (list of lists) + gbasis -- coefficients and exponents of Gaussian basis functions (PyQuante format) + geotargets -- targets for convergence of geometry optimization (array[1]) + geovalues -- current values for convergence of geometry optmization (array[1]) + homos -- molecular orbital indices of HOMO(s) (array[1]) + mocoeffs -- molecular orbital coefficients (list of arrays[2]) + moenergies -- molecular orbital energies (list of arrays[1], eV) + mosyms -- orbital symmetries (list of lists) + mpenergies -- molecular electronic energies with Moller-Plesset corrections (array[2], eV) + mult -- multiplicity of the system (integer) + natom -- number of atoms (integer) + nbasis -- number of basis functions (integer) + nmo -- number of molecular orbitals (integer) + nocoeffs -- natural orbital coefficients (array[2]) + scfenergies -- molecular electronic energies after SCF (Hartree-Fock, DFT) (array[1], eV) + scftargets -- targets for convergence of the SCF (array[2]) + scfvalues -- current values for convergence of the SCF (list of arrays[2]) + stericenergy -- final steric energy (for MM4 calculations) + vibdisps -- cartesian displacement vectors (array[3], delta angstrom) + vibfreqs -- vibrational frequencies (array[1], 1/cm) + vibirs -- IR intensities (array[1], km/mol) + vibramans -- Raman intensities (array[1], A^4/Da) + vibsyms -- symmetries of vibrations (list) + (1) The term 'array' refers to a numpy array + (2) The number of dimensions of an array is given in square brackets + (3) Python indexes arrays/lists starting at zero, so if homos==[10], then + the 11th molecular orbital is the HOMO + """ + + def __init__(self, attributes=None): + """Initialize the cclibData object. + + Normally called in the parse() method of a Logfile subclass. + + Inputs: + attributes - dictionary of attributes to load + """ + + # Names of all supported attributes. + self._attrlist = ['aonames', 'aooverlaps', 'atombasis', + 'atomcoords', 'atomnos', + 'ccenergies', 'charge', 'coreelectrons', + 'etenergies', 'etoscs', 'etrotats', 'etsecs', 'etsyms', + 'fonames', 'fooverlaps', 'fragnames', 'frags', + 'gbasis', 'geotargets', 'geovalues', 'grads', + 'hessian', 'homos', + 'mocoeffs', 'moenergies', 'molmass', 'mosyms', 'mpenergies', 'mult', + 'natom', 'nbasis', 'nmo', 'nocoeffs', 'rotcons', 'rotsymm', + 'scfenergies', 'scftargets', 'scfvalues', 'stericenergy', + 'vibdisps', 'vibfreqs', 'vibirs', 'vibramans', 'vibsyms'] + + # The expected types for all supported attributes. + #gmagoon 5/27/09: added rotsymm type above and below + #gmagoon 6/8/09: added molmass (previously (maybe 5/28) I had added rotcons) + self._attrtypes = { "aonames": list, + "aooverlaps": numpy.ndarray, + "atombasis": list, + "atomcoords": numpy.ndarray, + "atomnos": numpy.ndarray, + "charge": int, + "coreelectrons": numpy.ndarray, + "etenergies": numpy.ndarray, + "etoscs": numpy.ndarray, + "etrotats": numpy.ndarray, + "etsecs": list, + "etsyms": list, + 'gbasis': list, + "geotargets": numpy.ndarray, + "geovalues": numpy.ndarray, + "grads": numpy.ndarray, + "hessian": numpy.ndarray, + "homos": numpy.ndarray, + "mocoeffs": list, + "moenergies": list, + "molmass": float, + "mosyms": list, + "mpenergies": numpy.ndarray, + "mult": int, + "natom": int, + "nbasis": int, + "nmo": int, + "nocoeffs": numpy.ndarray, + "rotcons": list, + "rotsymm": int, + "scfenergies": numpy.ndarray, + "scftargets": numpy.ndarray, + "scfvalues": list, + "stericenergy": float, + "vibdisps": numpy.ndarray, + "vibfreqs": numpy.ndarray, + "vibirs": numpy.ndarray, + "vibramans": numpy.ndarray, + "vibsyms": list, + } + + # Arrays are double precision by default, but these will be integer arrays. + self._intarrays = ['atomnos', 'coreelectrons', 'homos'] + + # Attributes that should be lists of arrays (double precision). + self._listsofarrays = ['mocoeffs', 'moenergies', 'scfvalues', 'rotcons']#gmagoon 5/28/09: added rotcons + + if attributes: + self.setattributes(attributes) + + def listify(self): + """Converts all attributes that are arrays or lists of arrays to lists.""" + + for k, v in self._attrtypes.iteritems(): + if hasattr(self, k): + if v == numpy.ndarray: + setattr(self, k, getattr(self, k).tolist()) + elif v == list and k in self._listsofarrays: + setattr(self, k, [x.tolist() for x in getattr(self, k)]) + + def arrayify(self): + """Converts appropriate attributes to arrays or lists of arrays.""" + + for k, v in self._attrtypes.iteritems(): + if hasattr(self, k): + precision = 'd' + if k in self._intarrays: + precision = 'i' + if v == numpy.ndarray: + setattr(self, k, numpy.array(getattr(self, k), precision)) + elif v == list and k in self._listsofarrays: + setattr(self, k, [numpy.array(x, precision) + for x in getattr(self, k)]) + + def getattributes(self, tolists=False): + """Returns a dictionary of existing data attributes. + + Inputs: + tolists - flag to convert attributes to lists where applicable + """ + + if tolists: + self.listify() + attributes = {} + for attr in self._attrlist: + if hasattr(self, attr): + attributes[attr] = getattr(self,attr) + if tolists: + self.arrayofy() + return attributes + + def setattributes(self, attributes): + """Sets data attributes given in a dictionary. + + Inputs: + attributes - dictionary of attributes to set + Outputs: + invalid - list of attributes names that were not set, which + means they are not specified in self._attrlist + """ + + if type(attributes) is not dict: + raise TypeError, "attributes must be in a dictionary" + + valid = [a for a in attributes if a in self._attrlist] + invalid = [a for a in attributes if a not in self._attrlist] + + for attr in valid: + setattr(self, attr, attributes[attr]) + self.arrayify() + return invalid diff --git a/external/cclib/parser/gamessparser.py b/external/cclib/parser/gamessparser.py index 3d667ce063..2d0d6a70e9 100644 --- a/external/cclib/parser/gamessparser.py +++ b/external/cclib/parser/gamessparser.py @@ -1,912 +1,912 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 892 $" - - -import re - -import numpy - -import logfileparser -import utils - - -class GAMESS(logfileparser.Logfile): - """A GAMESS log file.""" - SCFRMS, SCFMAX, SCFENERGY = range(3) # Used to index self.scftargets[] - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(GAMESS, self).__init__(logname="GAMESS", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "GAMESS log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'GAMESS("%s")' % (self.filename) - - def normalisesym(self, label): - """Normalise the symmetries used by GAMESS. - - To normalise, two rules need to be applied: - (1) Occurences of U/G in the 2/3 position of the label - must be lower-cased - (2) Two single quotation marks must be replaced by a double - - >>> t = GAMESS("dummyfile").normalisesym - >>> labels = ['A', 'A1', 'A1G', "A'", "A''", "AG"] - >>> answers = map(t, labels) - >>> print answers - ['A', 'A1', 'A1g', "A'", 'A"', 'Ag'] - """ - if label[1:] == "''": - end = '"' - else: - end = label[1:].replace("U", "u").replace("G", "g") - return label[0] + end - - def before_parsing(self): - - self.firststdorient = True # Used to decide whether to wipe the atomcoords clean - self.geooptfinished = False # Used to avoid extracting the final geometry twice - self.cihamtyp = "none" # Type of CI Hamiltonian: saps or dets. - self.scftype = "none" # Type of SCF calculation: BLYP, RHF, ROHF, etc. - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - if line [1:12] == "INPUT CARD>": - return - - # We are looking for this line: - # PARAMETERS CONTROLLING GEOMETRY SEARCH ARE - # ... - # OPTTOL = 1.000E-04 RMIN = 1.500E-03 - if line[10:18] == "OPTTOL =": - if not hasattr(self, "geotargets"): - opttol = float(line.split()[2]) - self.geotargets = numpy.array([opttol, 3. / opttol], "d") - - if line.find("FINAL") == 1: - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - # Has to deal with such lines as: - # FINAL R-B3LYP ENERGY IS -382.0507446475 AFTER 10 ITERATIONS - # FINAL ENERGY IS -379.7594673378 AFTER 9 ITERATIONS - # ...so take the number after the "IS" - temp = line.split() - self.scfenergies.append(utils.convertor(float(temp[temp.index("IS") + 1]), "hartree", "eV")) - - # Total energies after Moller-Plesset corrections - if (line.find("RESULTS OF MOLLER-PLESSET") >= 0 or - line[6:37] == "SCHWARZ INEQUALITY TEST SKIPPED"): - # Output looks something like this: - # RESULTS OF MOLLER-PLESSET 2ND ORDER CORRECTION ARE - # E(0)= -285.7568061536 - # E(1)= 0.0 - # E(2)= -0.9679419329 - # E(MP2)= -286.7247480864 - # where E(MP2) = E(0) + E(2) - # - # with GAMESS-US 12 Jan 2009 (R3) the preceding text is different: - ## DIRECT 4-INDEX TRANSFORMATION - ## SCHWARZ INEQUALITY TEST SKIPPED 0 INTEGRAL BLOCKS - ## E(SCF)= -76.0088477471 - ## E(2)= -0.1403745370 - ## E(MP2)= -76.1492222841 - if not hasattr(self, "mpenergies"): - self.mpenergies = [] - # Each iteration has a new print-out - self.mpenergies.append([]) - # GAMESS-US presently supports only second order corrections (MP2) - # PC GAMESS also has higher levels (3rd and 4th), with different output - # Only the highest level MP4 energy is gathered (SDQ or SDTQ) - while re.search("DONE WITH MP(\d) ENERGY", line) is None: - line = inputfile.next() - if len(line.split()) > 0: - # Only up to MP2 correction - if line.split()[0] == "E(MP2)=": - mp2energy = float(line.split()[1]) - self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) - # MP2 before higher order calculations - if line.split()[0] == "E(MP2)": - mp2energy = float(line.split()[2]) - self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) - if line.split()[0] == "E(MP3)": - mp3energy = float(line.split()[2]) - self.mpenergies[-1].append(utils.convertor(mp3energy, "hartree", "eV")) - if line.split()[0] in ["E(MP4-SDQ)", "E(MP4-SDTQ)"]: - mp4energy = float(line.split()[2]) - self.mpenergies[-1].append(utils.convertor(mp4energy, "hartree", "eV")) - - # Total energies after Coupled Cluster calculations - # Only the highest Coupled Cluster level result is gathered - if line[12:23] == "CCD ENERGY:": - if not hasattr(self, "ccenergies"): - self.ccenergies = [] - ccenergy = float(line.split()[2]) - self.ccenergies.append(utils.convertor(ccenergy, "hartree", "eV")) - if line.find("CCSD") >= 0 and line.split()[0:2] == ["CCSD", "ENERGY:"]: - if not hasattr(self, "ccenergies"): - self.ccenergies = [] - ccenergy = float(line.split()[2]) - line = inputfile.next() - if line[8:23] == "CCSD[T] ENERGY:": - ccenergy = float(line.split()[2]) - line = inputfile.next() - if line[8:23] == "CCSD(T) ENERGY:": - ccenergy = float(line.split()[2]) - self.ccenergies.append(utils.convertor(ccenergy, "hartree", "eV")) - # Also collect MP2 energies, which are always calculated before CC - if line [8:23] == "MBPT(2) ENERGY:": - if not hasattr(self, "mpenergies"): - self.mpenergies = [] - self.mpenergies.append([]) - mp2energy = float(line.split()[2]) - self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) - - # Extract charge and multiplicity - if line[1:19] == "CHARGE OF MOLECULE": - self.charge = int(line.split()[-1]) - self.mult = int(inputfile.next().split()[-1]) - - # etenergies (used only for CIS runs now) - if "EXCITATION ENERGIES" in line and line.find("DONE WITH") < 0: - if not hasattr(self, "etenergies"): - self.etenergies = [] - header = inputfile.next().rstrip() - get_etosc = False - if header.endswith("OSC. STR."): - # water_cis_dets.out does not have the oscillator strength - # in this table...it is extracted from a different section below - get_etosc = True - self.etoscs = [] - dashes = inputfile.next() - line = inputfile.next() - broken = line.split() - while len(broken) > 0: - # Take hartree value with more numbers, and convert. - # Note that the values listed after this are also less exact! - etenergy = float(broken[1]) - self.etenergies.append(utils.convertor(etenergy, "hartree", "cm-1")) - if get_etosc: - etosc = float(broken[-1]) - self.etoscs.append(etosc) - broken = inputfile.next().split() - - # Detect the CI hamiltonian type, if applicable. - # Should always be detected if CIS is done. - if line[8:64] == "RESULTS FROM SPIN-ADAPTED ANTISYMMETRIZED PRODUCT (SAPS)": - self.cihamtyp = "saps" - if line[8:64] == "RESULTS FROM DETERMINANT BASED ATOMIC ORBITAL CI-SINGLES": - self.cihamtyp = "dets" - - # etsecs (used only for CIS runs for now) - if line[1:14] == "EXCITED STATE": - if not hasattr(self, 'etsecs'): - self.etsecs = [] - if not hasattr(self, 'etsyms'): - self.etsyms = [] - statenumber = int(line.split()[2]) - spin = int(float(line.split()[7])) - if spin == 0: - sym = "Singlet" - if spin == 1: - sym = "Triplet" - sym += '-' + line.split()[-1] - self.etsyms.append(sym) - # skip 5 lines - for i in range(5): - line = inputfile.next() - line = inputfile.next() - CIScontribs = [] - while line.strip()[0] != "-": - MOtype = 0 - # alpha/beta are specified for hamtyp=dets - if self.cihamtyp == "dets": - if line.split()[0] == "BETA": - MOtype = 1 - fromMO = int(line.split()[-3])-1 - toMO = int(line.split()[-2])-1 - coeff = float(line.split()[-1]) - # With the SAPS hamiltonian, the coefficients are multiplied - # by sqrt(2) so that they normalize to 1. - # With DETS, both alpha and beta excitations are printed. - # if self.cihamtyp == "saps": - # coeff /= numpy.sqrt(2.0) - CIScontribs.append([(fromMO,MOtype),(toMO,MOtype),coeff]) - line = inputfile.next() - self.etsecs.append(CIScontribs) - - # etoscs (used only for CIS runs now) - if line[1:50] == "TRANSITION FROM THE GROUND STATE TO EXCITED STATE": - if not hasattr(self, "etoscs"): - self.etoscs = [] - statenumber = int(line.split()[-1]) - # skip 7 lines - for i in range(8): - line = inputfile.next() - strength = float(line.split()[3]) - self.etoscs.append(strength) - - # TD-DFT for GAMESS-US - if line[14:29] == "LET EXCITATIONS": # TRIPLET and SINGLET - self.etenergies = [] - self.etoscs = [] - self.etsecs = [] - etsyms = [] - minus = inputfile.next() - blank = inputfile.next() - line = inputfile.next() - # Loop starts on the STATE line - while line.find("STATE") >= 0: - broken = line.split() - self.etenergies.append(utils.convertor(float(broken[-2]), "eV", "cm-1")) - broken = inputfile.next().split() - self.etoscs.append(float(broken[-1])) - sym = inputfile.next() # Not always present - if sym.find("SYMMETRY")>=0: - etsyms.append(sym.split()[-1]) - header = inputfile.next() - minus = inputfile.next() - CIScontribs = [] - line = inputfile.next() - while line.strip(): - broken = line.split() - fromMO, toMO = [int(broken[x]) - 1 for x in [2, 4]] - CIScontribs.append([(fromMO, 0), (toMO, 0), float(broken[1])]) - line = inputfile.next() - self.etsecs.append(CIScontribs) - line = inputfile.next() - if etsyms: # Not always present - self.etsyms = etsyms - - # Maximum and RMS gradients. - if "MAXIMUM GRADIENT" in line or "RMS GRADIENT" in line: - - if not hasattr(self, "geovalues"): - self.geovalues = [] - - parts = line.split() - - # Newer versions (around 2006) have both maximum and RMS on one line: - # MAXIMUM GRADIENT = 0.0531540 RMS GRADIENT = 0.0189223 - if len(parts) == 8: - maximum = float(parts[3]) - rms = float(parts[7]) - - # In older versions of GAMESS, this spanned two lines, like this: - # MAXIMUM GRADIENT = 0.057578167 - # RMS GRADIENT = 0.027589766 - if len(parts) == 4: - maximum = float(parts[3]) - line = inputfile.next() - parts = line.split() - rms = float(parts[3]) - - - # FMO also prints two final one- and two-body gradients (see exam37): - # (1) MAXIMUM GRADIENT = 0.0531540 RMS GRADIENT = 0.0189223 - if len(parts) == 9: - maximum = float(parts[4]) - rms = float(parts[8]) - - self.geovalues.append([maximum, rms]) - - if line[11:50] == "ATOMIC COORDINATES": - # This is the input orientation, which is the only data available for - # SP calcs, but which should be overwritten by the standard orientation - # values, which is the only information available for all geoopt cycles. - if not hasattr(self, "atomcoords"): - self.atomcoords = [] - self.atomnos = [] - line = inputfile.next() - atomcoords = [] - atomnos = [] - line = inputfile.next() - while line.strip(): - temp = line.strip().split() - atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[2:5]]) - atomnos.append(int(round(float(temp[1])))) # Don't use the atom name as this is arbitary - line = inputfile.next() - self.atomnos = numpy.array(atomnos, "i") - self.atomcoords.append(atomcoords) - - if line[12:40] == "EQUILIBRIUM GEOMETRY LOCATED": - # Prevent extraction of the final geometry twice - self.geooptfinished = True - - if line[1:29] == "COORDINATES OF ALL ATOMS ARE" and not self.geooptfinished: - # This is the standard orientation, which is the only coordinate - # information available for all geometry optimisation cycles. - # The input orientation will be overwritten if this is a geometry optimisation - # We assume that a previous Input Orientation has been found and - # used to extract the atomnos - if self.firststdorient: - self.firststdorient = False - # Wipes out the single input coordinate at the start of the file - self.atomcoords = [] - - line = inputfile.next() - hyphens = inputfile.next() - - atomcoords = [] - line = inputfile.next() - - for i in range(self.natom): - temp = line.strip().split() - atomcoords.append(map(float, temp[2:5])) - line = inputfile.next() - self.atomcoords.append(atomcoords) - - # Section with SCF information. - # - # The space at the start of the search string is to differentiate from MCSCF. - # Everything before the search string is stored as the type of SCF. - # SCF types may include: BLYP, RHF, ROHF, UHF, etc. - # - # For example, in exam17 the section looks like this (note that this is GVB): - # ------------------------ - # ROHF-GVB SCF CALCULATION - # ------------------------ - # GVB STEP WILL USE 119875 WORDS OF MEMORY. - # - # MAXIT= 30 NPUNCH= 2 SQCDF TOL=1.0000E-05 - # NUCLEAR ENERGY= 6.1597411978 - # EXTRAP=T DAMP=F SHIFT=F RSTRCT=F DIIS=F SOSCF=F - # - # ITER EX TOTAL ENERGY E CHANGE SQCDF DIIS ERROR - # 0 0 -38.298939963 -38.298939963 0.131784454 0.000000000 - # 1 1 -38.332044339 -0.033104376 0.026019716 0.000000000 - # ... and will be terminated by a blank line. - if line.rstrip()[-16:] == " SCF CALCULATION": - - # Remember the type of SCF. - self.scftype = line.strip()[:-16] - - dashes = inputfile.next() - - while line [:5] != " ITER": - - # GVB uses SQCDF for checking convergence (for example in exam17). - if "GVB" in self.scftype and "SQCDF TOL=" in line: - scftarget = float(line.split("=")[-1]) - - # Normally however the density is used as the convergence criterium. - # Deal with various versions: - # (GAMESS VERSION = 12 DEC 2003) - # DENSITY MATRIX CONV= 2.00E-05 DFT GRID SWITCH THRESHOLD= 3.00E-04 - # (GAMESS VERSION = 22 FEB 2006) - # DENSITY MATRIX CONV= 1.00E-05 - # (PC GAMESS version 6.2, Not DFT?) - # DENSITY CONV= 1.00E-05 - elif "DENSITY CONV" in line or "DENSITY MATRIX CONV" in line: - scftarget = float(line.split()[-1]) - - line = inputfile.next() - - if not hasattr(self, "scftargets"): - self.scftargets = [] - - self.scftargets.append([scftarget]) - - if not hasattr(self,"scfvalues"): - self.scfvalues = [] - - line = inputfile.next() - - # Normally the iteration print in 6 columns. - # For ROHF, however, it is 5 columns, thus this extra parameter. - if "ROHF" in self.scftype: - valcol = 4 - else: - valcol = 5 - - # SCF iterations are terminated by a blank line. - # The first four characters usually contains the step number. - # However, lines can also contain messages, including: - # * * * INITIATING DIIS PROCEDURE * * * - # CONVERGED TO SWOFF, SO DFT CALCULATION IS NOW SWITCHED ON - # DFT CODE IS SWITCHING BACK TO THE FINER GRID - values = [] - while line.strip(): - try: - temp = int(line[0:4]) - except ValueError: - pass - else: - values.append([float(line.split()[valcol])]) - line = inputfile.next() - self.scfvalues.append(values) - - if line.find("NORMAL COORDINATE ANALYSIS IN THE HARMONIC APPROXIMATION") >= 0: - # GAMESS has... - # MODES 1 TO 6 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. - # - # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2, - # REDUCED MASSES IN AMU. - # - # 1 2 3 4 5 - # FREQUENCY: 52.49 41.45 17.61 9.23 10.61 - # REDUCED MASS: 3.92418 3.77048 5.43419 6.44636 5.50693 - # IR INTENSITY: 0.00013 0.00001 0.00004 0.00000 0.00003 - - # ...or in the case of a numerical Hessian job... - - # MODES 1 TO 5 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. - # - # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2, - # REDUCED MASSES IN AMU. - # - # 1 2 3 4 5 - # FREQUENCY: 0.05 0.03 0.03 30.89 30.94 - # REDUCED MASS: 8.50125 8.50137 8.50136 1.06709 1.06709 - - - # whereas PC-GAMESS has... - # MODES 1 TO 6 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. - # - # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2 - # - # 1 2 3 4 5 - # FREQUENCY: 5.89 1.46 0.01 0.01 0.01 - # IR INTENSITY: 0.00000 0.00000 0.00000 0.00000 0.00000 - - # If Raman is present we have (for PC-GAMESS)... - # MODES 1 TO 6 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. - # - # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2 - # RAMAN INTENSITIES IN ANGSTROM**4/AMU, DEPOLARIZATIONS ARE DIMENSIONLESS - # - # 1 2 3 4 5 - # FREQUENCY: 5.89 1.46 0.04 0.03 0.01 - # IR INTENSITY: 0.00000 0.00000 0.00000 0.00000 0.00000 - # RAMAN INTENSITY: 12.675 1.828 0.000 0.000 0.000 - # DEPOLARIZATION: 0.750 0.750 0.124 0.009 0.750 - - # If PC-GAMESS has not reached the stationary point we have - # MODES 1 TO 5 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. - # - # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2 - # - # ******************************************************* - # * THIS IS NOT A STATIONARY POINT ON THE MOLECULAR PES * - # * THE VIBRATIONAL ANALYSIS IS NOT VALID !!! * - # ******************************************************* - # - # 1 2 3 4 5 - - # MODES 2 TO 7 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. - - self.vibfreqs = [] - self.vibirs = [] - self.vibdisps = [] - - # Need to get to the modes line - warning = False - while line.find("MODES") == -1: - line = inputfile.next() - if line.find("THIS IS NOT A STATIONARY POINT")>=0: - warning = True - startrot = int(line.split()[1]) - endrot = int(line.split()[3]) - blank = inputfile.next() - - line = inputfile.next() # FREQUENCIES, etc. - while line != blank: - line = inputfile.next() - if warning: # Get past the second warning - line = inputfile.next() - while line!= blank: - line = inputfile.next() - self.logger.warning("This is not a stationary point on the molecular" - "PES. The vibrational analysis is not valid.") - - freqNo = inputfile.next() - while freqNo.find("SAYVETZ") == -1: - freq = inputfile.next().strip().split()[1:] - # May include imaginary frequencies - # FREQUENCY: 825.18 I 111.53 12.62 10.70 0.89 - newfreq = [] - for i, x in enumerate(freq): - if x!="I": - newfreq.append(float(x)) - else: - newfreq[-1] = -newfreq[-1] - self.vibfreqs.extend(newfreq) - line = inputfile.next() - if line.find("REDUCED") >= 0: # skip the reduced mass (not always present) - line = inputfile.next() - if line.find("IR INTENSITY") >= 0: - # Not present if a numerical Hessian calculation - irIntensity = map(float, line.strip().split()[2:]) - self.vibirs.extend([utils.convertor(x, "Debye^2/amu-Angstrom^2", "km/mol") for x in irIntensity]) - line = inputfile.next() - if line.find("RAMAN") >= 0: - if not hasattr(self,"vibramans"): - self.vibramans = [] - ramanIntensity = line.strip().split() - self.vibramans.extend(map(float, ramanIntensity[2:])) - depolar = inputfile.next() - line = inputfile.next() - assert line == blank - - # Extract the Cartesian displacement vectors - p = [ [], [], [], [], [] ] - for j in range(len(self.atomnos)): - q = [ [], [], [], [], [] ] - for k in range(3): # x, y, z - line = inputfile.next()[21:] - broken = map(float, line.split()) - for l in range(len(broken)): - q[l].append(broken[l]) - for k in range(len(broken)): - p[k].append(q[k]) - self.vibdisps.extend(p[:len(broken)]) - - # Skip the Sayvetz stuff at the end - for j in range(10): - line = inputfile.next() - blank = inputfile.next() - freqNo = inputfile.next() - # Exclude rotations and translations - self.vibfreqs = numpy.array(self.vibfreqs[:startrot-1]+self.vibfreqs[endrot:], "d") - self.vibirs = numpy.array(self.vibirs[:startrot-1]+self.vibirs[endrot:], "d") - self.vibdisps = numpy.array(self.vibdisps[:startrot-1]+self.vibdisps[endrot:], "d") - if hasattr(self, "vibramans"): - self.vibramans = numpy.array(self.vibramans[:startrot-1]+self.vibramans[endrot:], "d") - - if line[5:21] == "ATOMIC BASIS SET": - self.gbasis = [] - line = inputfile.next() - while line.find("SHELL")<0: - line = inputfile.next() - blank = inputfile.next() - atomname = inputfile.next() - # shellcounter stores the shell no of the last shell - # in the previous set of primitives - shellcounter = 1 - while line.find("TOTAL NUMBER")<0: - blank = inputfile.next() - line = inputfile.next() - shellno = int(line.split()[0]) - shellgap = shellno - shellcounter - gbasis = [] # Stores basis sets on one atom - shellsize = 0 - while len(line.split())!=1 and line.find("TOTAL NUMBER")<0: - shellsize += 1 - coeff = {} - # coefficients and symmetries for a block of rows - while line.strip(): - temp = line.strip().split() - sym = temp[1] - assert sym in ['S', 'P', 'D', 'F', 'G', 'L'] - if sym == "L": # L refers to SP - if len(temp)==6: # GAMESS US - coeff.setdefault("S", []).append( (float(temp[3]), float(temp[4])) ) - coeff.setdefault("P", []).append( (float(temp[3]), float(temp[5])) ) - else: # PC GAMESS - assert temp[6][-1] == temp[9][-1] == ')' - coeff.setdefault("S", []).append( (float(temp[3]), float(temp[6][:-1])) ) - coeff.setdefault("P", []).append( (float(temp[3]), float(temp[9][:-1])) ) - else: - if len(temp)==5: # GAMESS US - coeff.setdefault(sym, []).append( (float(temp[3]), float(temp[4])) ) - else: # PC GAMESS - assert temp[6][-1] == ')' - coeff.setdefault(sym, []).append( (float(temp[3]), float(temp[6][:-1])) ) - line = inputfile.next() - # either a blank or a continuation of the block - if sym == "L": - gbasis.append( ('S', coeff['S'])) - gbasis.append( ('P', coeff['P'])) - else: - gbasis.append( (sym, coeff[sym])) - line = inputfile.next() - # either the start of the next block or the start of a new atom or - # the end of the basis function section - - numtoadd = 1 + (shellgap / shellsize) - shellcounter = shellno + shellsize - for x in range(numtoadd): - self.gbasis.append(gbasis) - - if line.find("EIGENVECTORS") == 10 or line.find("MOLECULAR OBRITALS") == 10: - # The details returned come from the *final* report of evalues and - # the last list of symmetries in the log file. - # Should be followed by lines like this: - # ------------ - # EIGENVECTORS - # ------------ - # - # 1 2 3 4 5 - # -10.0162 -10.0161 -10.0039 -10.0039 -10.0029 - # BU AG BU AG AG - # 1 C 1 S 0.699293 0.699290 -0.027566 0.027799 0.002412 - # 2 C 1 S 0.031569 0.031361 0.004097 -0.004054 -0.000605 - # 3 C 1 X 0.000908 0.000632 -0.004163 0.004132 0.000619 - # 4 C 1 Y -0.000019 0.000033 0.000668 -0.000651 0.005256 - # 5 C 1 Z 0.000000 0.000000 0.000000 0.000000 0.000000 - # 6 C 2 S -0.699293 0.699290 0.027566 0.027799 0.002412 - # 7 C 2 S -0.031569 0.031361 -0.004097 -0.004054 -0.000605 - # 8 C 2 X 0.000908 -0.000632 -0.004163 -0.004132 -0.000619 - # 9 C 2 Y -0.000019 -0.000033 0.000668 0.000651 -0.005256 - # 10 C 2 Z 0.000000 0.000000 0.000000 0.000000 0.000000 - # 11 C 3 S -0.018967 -0.019439 0.011799 -0.014884 -0.452328 - # 12 C 3 S -0.007748 -0.006932 0.000680 -0.000695 -0.024917 - # 13 C 3 X 0.002628 0.002997 0.000018 0.000061 -0.003608 - # and so forth... with blanks lines between blocks of 5 orbitals each. - # Warning! There are subtle differences between GAMESS-US and PC-GAMES - # in the formatting of the first four columns. - # - # Watch out for F orbitals... - # PC GAMESS - # 19 C 1 YZ 0.000000 0.000000 0.000000 0.000000 0.000000 - # 20 C XXX 0.000000 0.000000 0.000000 0.000000 0.002249 - # 21 C YYY 0.000000 0.000000 -0.025555 0.000000 0.000000 - # 22 C ZZZ 0.000000 0.000000 0.000000 0.002249 0.000000 - # 23 C XXY 0.000000 0.000000 0.001343 0.000000 0.000000 - # GAMESS US - # 55 C 1 XYZ 0.000000 0.000000 0.000000 0.000000 0.000000 - # 56 C 1XXXX -0.000014 -0.000067 0.000000 0.000000 0.000000 - # - # This is fine for GeoOpt and SP, but may be weird for TD and Freq. - - # This is the stuff that we can read from these blocks. - self.moenergies = [[]] - self.mosyms = [[]] - if not hasattr(self, "nmo"): - self.nmo = self.nbasis - self.mocoeffs = [numpy.zeros((self.nmo, self.nbasis), "d")] - readatombasis = False - if not hasattr(self, "atombasis"): - self.atombasis = [] - self.aonames = [] - for i in range(self.natom): - self.atombasis.append([]) - self.aonames = [] - readatombasis = True - - dashes = inputfile.next() - for base in range(0, self.nmo, 5): - - line = inputfile.next() - # Make sure that this section does not end prematurely - checked by regression test 2CO.ccsd.aug-cc-pVDZ.out. - if line.strip() != "": - break; - - numbers = inputfile.next() # Eigenvector numbers. - - # Sometimes there are some blank lines here. - while not line.strip(): - line = inputfile.next() - - # Eigenvalues for these orbitals (in hartrees). - try: - self.moenergies[0].extend([utils.convertor(float(x), "hartree", "eV") for x in line.split()]) - except: - self.logger.warning('MO section found but could not be parsed!') - break; - - # Orbital symmetries. - line = inputfile.next() - if line.strip(): - self.mosyms[0].extend(map(self.normalisesym, line.split())) - - # Now we have nbasis lines. - # Going to use the same method as for normalise_aonames() - # to extract basis set information. - p = re.compile("(\d+)\s*([A-Z][A-Z]?)\s*(\d+)\s*([A-Z]+)") - oldatom ='0' - for i in range(self.nbasis): - line = inputfile.next() - - # If line is empty, break (ex. for FMO in exam37). - if not line.strip(): break - - # Fill atombasis and aonames only first time around - if readatombasis and base == 0: - aonames = [] - start = line[:17].strip() - m = p.search(start) - if m: - g = m.groups() - aoname = "%s%s_%s" % (g[1].capitalize(), g[2], g[3]) - oldatom = g[2] - atomno = int(g[2])-1 - orbno = int(g[0])-1 - else: # For F orbitals, as shown above - g = [x.strip() for x in line.split()] - aoname = "%s%s_%s" % (g[1].capitalize(), oldatom, g[2]) - atomno = int(oldatom)-1 - orbno = int(g[0])-1 - self.atombasis[atomno].append(orbno) - self.aonames.append(aoname) - coeffs = line[15:] # Strip off the crud at the start. - j = 0 - while j*11+4 < len(coeffs): - self.mocoeffs[0][base+j, i] = float(coeffs[j * 11:(j + 1) * 11]) - j += 1 - - line = inputfile.next() - # If it's restricted and no more properties: - # ...... END OF RHF/DFT CALCULATION ...... - # If there are more properties (DENSITY MATRIX): - # -------------- - # - # If it's unrestricted we have: - # - # ----- BETA SET ----- - # - # ------------ - # EIGENVECTORS - # ------------ - # - # 1 2 3 4 5 - # ... and so forth. - line = inputfile.next() - if line[2:22] == "----- BETA SET -----": - self.mocoeffs.append(numpy.zeros((self.nmo, self.nbasis), "d")) - self.moenergies.append([]) - self.mosyms.append([]) - for i in range(4): - line = inputfile.next() - for base in range(0, self.nmo, 5): - blank = inputfile.next() - line = inputfile.next() # Eigenvector no - line = inputfile.next() - self.moenergies[1].extend([utils.convertor(float(x), "hartree", "eV") for x in line.split()]) - line = inputfile.next() - self.mosyms[1].extend(map(self.normalisesym, line.split())) - for i in range(self.nbasis): - line = inputfile.next() - temp = line[15:] # Strip off the crud at the start - j = 0 - while j * 11 + 4 < len(temp): - self.mocoeffs[1][base+j, i] = float(temp[j * 11:(j + 1) * 11]) - j += 1 - line = inputfile.next() - self.moenergies = [numpy.array(x, "d") for x in self.moenergies] - - # Natural orbitals - presently support only CIS. - # Looks basically the same as eigenvectors, without symmetry labels. - if line[10:30] == "CIS NATURAL ORBITALS": - - self.nocoeffs = numpy.zeros((self.nmo, self.nbasis), "d") - - dashes = inputfile.next() - for base in range(0, self.nmo, 5): - - blank = inputfile.next() - numbers = inputfile.next() # Eigenvector numbers. - - # Eigenvalues for these natural orbitals (not in hartrees!). - # Sometimes there are some blank lines before it. - line = inputfile.next() - while not line.strip(): - line = inputfile.next() - eigenvalues = line - - # Orbital symemtry labels are normally here for MO coefficients. - line = inputfile.next() - - # Now we have nbasis lines with the coefficients. - for i in range(self.nbasis): - - line = inputfile.next() - coeffs = line[15:] - j = 0 - while j*11+4 < len(coeffs): - self.nocoeffs[base+j, i] = float(coeffs[j * 11:(j + 1) * 11]) - j += 1 - - # We cannot trust this self.homos until we come to the phrase: - # SYMMETRIES FOR INITAL GUESS ORBITALS FOLLOW - # which either is followed by "ALPHA" or "BOTH" at which point we can say - # for certain that it is an un/restricted calculations. - # Note that MCSCF calcs also print this search string, so make sure - # that self.homos does not exist yet. - if line[1:28] == "NUMBER OF OCCUPIED ORBITALS" and not hasattr(self,'homos'): - homos = [int(line.split()[-1])-1] - line = inputfile.next() - homos.append(int(line.split()[-1])-1) - self.homos = numpy.array(homos, "i") - - - if line.find("SYMMETRIES FOR INITIAL GUESS ORBITALS FOLLOW") >= 0: - # Not unrestricted, so lop off the second index. - # In case the search string above was not used (ex. FMO in exam38), - # we can try to use the next line which should also contain the - # number of occupied orbitals. - if line.find("BOTH SET(S)") >= 0: - nextline = inputfile.next() - if "ORBITALS ARE OCCUPIED" in nextline: - homos = int(nextline.split()[0])-1 - if hasattr(self,"homos"): - try: - assert self.homos[0] == homos - except AssertionError: - self.logger.warning("Number of occupied orbitals not consistent. This is normal for ECP and FMO jobs.") - else: - self.homos = [homos] - self.homos = numpy.resize(self.homos, [1]) - - # Set the total number of atoms, only once. - # Normally GAMESS print TOTAL NUMBER OF ATOMS, however in some cases - # this is slightly different (ex. lower case for FMO in exam37). - if not hasattr(self,"natom") and "NUMBER OF ATOMS" in line.upper(): - self.natom = int(line.split()[-1]) - - if line.find("NUMBER OF CARTESIAN GAUSSIAN BASIS") == 1 or line.find("TOTAL NUMBER OF BASIS FUNCTIONS") == 1: - # The first is from Julien's Example and the second is from Alexander's - # I think it happens if you use a polar basis function instead of a cartesian one - self.nbasis = int(line.strip().split()[-1]) - - elif line.find("SPHERICAL HARMONICS KEPT IN THE VARIATION SPACE") >= 0: - # Note that this line is present if ISPHER=1, e.g. for C_bigbasis - self.nmo = int(line.strip().split()[-1]) - - elif line.find("TOTAL NUMBER OF MOS IN VARIATION SPACE") == 1: - # Note that this line is not always present, so by default - # NBsUse is set equal to NBasis (see below). - self.nmo = int(line.split()[-1]) - - elif line.find("OVERLAP MATRIX") == 0 or line.find("OVERLAP MATRIX") == 1: - # The first is for PC-GAMESS, the second for GAMESS - # Read 1-electron overlap matrix - if not hasattr(self, "aooverlaps"): - self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") - else: - self.logger.info("Reading additional aooverlaps...") - base = 0 - while base < self.nbasis: - blank = inputfile.next() - line = inputfile.next() # Basis fn number - blank = inputfile.next() - for i in range(self.nbasis - base): # Fewer lines each time - line = inputfile.next() - temp = line.split() - for j in range(4, len(temp)): - self.aooverlaps[base+j-4, i+base] = float(temp[j]) - self.aooverlaps[i+base, base+j-4] = float(temp[j]) - base += 5 - - # ECP Pseudopotential information - if "ECP POTENTIALS" in line: - if not hasattr(self, "coreelectrons"): - self.coreelectrons = [0]*self.natom - dashes = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - while header.split()[0] == "PARAMETERS": - name = header[17:25] - atomnum = int(header[34:40]) - # The pseudopotnetial is given explicitely - if header[40:50] == "WITH ZCORE": - zcore = int(header[50:55]) - lmax = int(header[63:67]) - self.coreelectrons[atomnum-1] = zcore - # The pseudopotnetial is copied from another atom - if header[40:55] == "ARE THE SAME AS": - atomcopy = int(header[60:]) - self.coreelectrons[atomnum-1] = self.coreelectrons[atomcopy-1] - line = inputfile.next() - while line.split() <> []: - line = inputfile.next() - header = inputfile.next() - - # This was used before refactoring the parser, geotargets was set here after parsing. - #if not hasattr(self, "geotargets"): - # opttol = 1e-4 - # self.geotargets = numpy.array([opttol, 3. / opttol], "d") - #if hasattr(self,"geovalues"): self.geovalues = numpy.array(self.geovalues, "d") - - -if __name__ == "__main__": - import doctest, gamessparser - doctest.testmod(gamessparser, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 892 $" + + +import re + +import numpy + +import logfileparser +import utils + + +class GAMESS(logfileparser.Logfile): + """A GAMESS log file.""" + SCFRMS, SCFMAX, SCFENERGY = range(3) # Used to index self.scftargets[] + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(GAMESS, self).__init__(logname="GAMESS", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "GAMESS log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'GAMESS("%s")' % (self.filename) + + def normalisesym(self, label): + """Normalise the symmetries used by GAMESS. + + To normalise, two rules need to be applied: + (1) Occurences of U/G in the 2/3 position of the label + must be lower-cased + (2) Two single quotation marks must be replaced by a double + + >>> t = GAMESS("dummyfile").normalisesym + >>> labels = ['A', 'A1', 'A1G', "A'", "A''", "AG"] + >>> answers = map(t, labels) + >>> print answers + ['A', 'A1', 'A1g', "A'", 'A"', 'Ag'] + """ + if label[1:] == "''": + end = '"' + else: + end = label[1:].replace("U", "u").replace("G", "g") + return label[0] + end + + def before_parsing(self): + + self.firststdorient = True # Used to decide whether to wipe the atomcoords clean + self.geooptfinished = False # Used to avoid extracting the final geometry twice + self.cihamtyp = "none" # Type of CI Hamiltonian: saps or dets. + self.scftype = "none" # Type of SCF calculation: BLYP, RHF, ROHF, etc. + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + if line [1:12] == "INPUT CARD>": + return + + # We are looking for this line: + # PARAMETERS CONTROLLING GEOMETRY SEARCH ARE + # ... + # OPTTOL = 1.000E-04 RMIN = 1.500E-03 + if line[10:18] == "OPTTOL =": + if not hasattr(self, "geotargets"): + opttol = float(line.split()[2]) + self.geotargets = numpy.array([opttol, 3. / opttol], "d") + + if line.find("FINAL") == 1: + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + # Has to deal with such lines as: + # FINAL R-B3LYP ENERGY IS -382.0507446475 AFTER 10 ITERATIONS + # FINAL ENERGY IS -379.7594673378 AFTER 9 ITERATIONS + # ...so take the number after the "IS" + temp = line.split() + self.scfenergies.append(utils.convertor(float(temp[temp.index("IS") + 1]), "hartree", "eV")) + + # Total energies after Moller-Plesset corrections + if (line.find("RESULTS OF MOLLER-PLESSET") >= 0 or + line[6:37] == "SCHWARZ INEQUALITY TEST SKIPPED"): + # Output looks something like this: + # RESULTS OF MOLLER-PLESSET 2ND ORDER CORRECTION ARE + # E(0)= -285.7568061536 + # E(1)= 0.0 + # E(2)= -0.9679419329 + # E(MP2)= -286.7247480864 + # where E(MP2) = E(0) + E(2) + # + # with GAMESS-US 12 Jan 2009 (R3) the preceding text is different: + ## DIRECT 4-INDEX TRANSFORMATION + ## SCHWARZ INEQUALITY TEST SKIPPED 0 INTEGRAL BLOCKS + ## E(SCF)= -76.0088477471 + ## E(2)= -0.1403745370 + ## E(MP2)= -76.1492222841 + if not hasattr(self, "mpenergies"): + self.mpenergies = [] + # Each iteration has a new print-out + self.mpenergies.append([]) + # GAMESS-US presently supports only second order corrections (MP2) + # PC GAMESS also has higher levels (3rd and 4th), with different output + # Only the highest level MP4 energy is gathered (SDQ or SDTQ) + while re.search("DONE WITH MP(\d) ENERGY", line) is None: + line = inputfile.next() + if len(line.split()) > 0: + # Only up to MP2 correction + if line.split()[0] == "E(MP2)=": + mp2energy = float(line.split()[1]) + self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) + # MP2 before higher order calculations + if line.split()[0] == "E(MP2)": + mp2energy = float(line.split()[2]) + self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) + if line.split()[0] == "E(MP3)": + mp3energy = float(line.split()[2]) + self.mpenergies[-1].append(utils.convertor(mp3energy, "hartree", "eV")) + if line.split()[0] in ["E(MP4-SDQ)", "E(MP4-SDTQ)"]: + mp4energy = float(line.split()[2]) + self.mpenergies[-1].append(utils.convertor(mp4energy, "hartree", "eV")) + + # Total energies after Coupled Cluster calculations + # Only the highest Coupled Cluster level result is gathered + if line[12:23] == "CCD ENERGY:": + if not hasattr(self, "ccenergies"): + self.ccenergies = [] + ccenergy = float(line.split()[2]) + self.ccenergies.append(utils.convertor(ccenergy, "hartree", "eV")) + if line.find("CCSD") >= 0 and line.split()[0:2] == ["CCSD", "ENERGY:"]: + if not hasattr(self, "ccenergies"): + self.ccenergies = [] + ccenergy = float(line.split()[2]) + line = inputfile.next() + if line[8:23] == "CCSD[T] ENERGY:": + ccenergy = float(line.split()[2]) + line = inputfile.next() + if line[8:23] == "CCSD(T) ENERGY:": + ccenergy = float(line.split()[2]) + self.ccenergies.append(utils.convertor(ccenergy, "hartree", "eV")) + # Also collect MP2 energies, which are always calculated before CC + if line [8:23] == "MBPT(2) ENERGY:": + if not hasattr(self, "mpenergies"): + self.mpenergies = [] + self.mpenergies.append([]) + mp2energy = float(line.split()[2]) + self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) + + # Extract charge and multiplicity + if line[1:19] == "CHARGE OF MOLECULE": + self.charge = int(line.split()[-1]) + self.mult = int(inputfile.next().split()[-1]) + + # etenergies (used only for CIS runs now) + if "EXCITATION ENERGIES" in line and line.find("DONE WITH") < 0: + if not hasattr(self, "etenergies"): + self.etenergies = [] + header = inputfile.next().rstrip() + get_etosc = False + if header.endswith("OSC. STR."): + # water_cis_dets.out does not have the oscillator strength + # in this table...it is extracted from a different section below + get_etosc = True + self.etoscs = [] + dashes = inputfile.next() + line = inputfile.next() + broken = line.split() + while len(broken) > 0: + # Take hartree value with more numbers, and convert. + # Note that the values listed after this are also less exact! + etenergy = float(broken[1]) + self.etenergies.append(utils.convertor(etenergy, "hartree", "cm-1")) + if get_etosc: + etosc = float(broken[-1]) + self.etoscs.append(etosc) + broken = inputfile.next().split() + + # Detect the CI hamiltonian type, if applicable. + # Should always be detected if CIS is done. + if line[8:64] == "RESULTS FROM SPIN-ADAPTED ANTISYMMETRIZED PRODUCT (SAPS)": + self.cihamtyp = "saps" + if line[8:64] == "RESULTS FROM DETERMINANT BASED ATOMIC ORBITAL CI-SINGLES": + self.cihamtyp = "dets" + + # etsecs (used only for CIS runs for now) + if line[1:14] == "EXCITED STATE": + if not hasattr(self, 'etsecs'): + self.etsecs = [] + if not hasattr(self, 'etsyms'): + self.etsyms = [] + statenumber = int(line.split()[2]) + spin = int(float(line.split()[7])) + if spin == 0: + sym = "Singlet" + if spin == 1: + sym = "Triplet" + sym += '-' + line.split()[-1] + self.etsyms.append(sym) + # skip 5 lines + for i in range(5): + line = inputfile.next() + line = inputfile.next() + CIScontribs = [] + while line.strip()[0] != "-": + MOtype = 0 + # alpha/beta are specified for hamtyp=dets + if self.cihamtyp == "dets": + if line.split()[0] == "BETA": + MOtype = 1 + fromMO = int(line.split()[-3])-1 + toMO = int(line.split()[-2])-1 + coeff = float(line.split()[-1]) + # With the SAPS hamiltonian, the coefficients are multiplied + # by sqrt(2) so that they normalize to 1. + # With DETS, both alpha and beta excitations are printed. + # if self.cihamtyp == "saps": + # coeff /= numpy.sqrt(2.0) + CIScontribs.append([(fromMO,MOtype),(toMO,MOtype),coeff]) + line = inputfile.next() + self.etsecs.append(CIScontribs) + + # etoscs (used only for CIS runs now) + if line[1:50] == "TRANSITION FROM THE GROUND STATE TO EXCITED STATE": + if not hasattr(self, "etoscs"): + self.etoscs = [] + statenumber = int(line.split()[-1]) + # skip 7 lines + for i in range(8): + line = inputfile.next() + strength = float(line.split()[3]) + self.etoscs.append(strength) + + # TD-DFT for GAMESS-US + if line[14:29] == "LET EXCITATIONS": # TRIPLET and SINGLET + self.etenergies = [] + self.etoscs = [] + self.etsecs = [] + etsyms = [] + minus = inputfile.next() + blank = inputfile.next() + line = inputfile.next() + # Loop starts on the STATE line + while line.find("STATE") >= 0: + broken = line.split() + self.etenergies.append(utils.convertor(float(broken[-2]), "eV", "cm-1")) + broken = inputfile.next().split() + self.etoscs.append(float(broken[-1])) + sym = inputfile.next() # Not always present + if sym.find("SYMMETRY")>=0: + etsyms.append(sym.split()[-1]) + header = inputfile.next() + minus = inputfile.next() + CIScontribs = [] + line = inputfile.next() + while line.strip(): + broken = line.split() + fromMO, toMO = [int(broken[x]) - 1 for x in [2, 4]] + CIScontribs.append([(fromMO, 0), (toMO, 0), float(broken[1])]) + line = inputfile.next() + self.etsecs.append(CIScontribs) + line = inputfile.next() + if etsyms: # Not always present + self.etsyms = etsyms + + # Maximum and RMS gradients. + if "MAXIMUM GRADIENT" in line or "RMS GRADIENT" in line: + + if not hasattr(self, "geovalues"): + self.geovalues = [] + + parts = line.split() + + # Newer versions (around 2006) have both maximum and RMS on one line: + # MAXIMUM GRADIENT = 0.0531540 RMS GRADIENT = 0.0189223 + if len(parts) == 8: + maximum = float(parts[3]) + rms = float(parts[7]) + + # In older versions of GAMESS, this spanned two lines, like this: + # MAXIMUM GRADIENT = 0.057578167 + # RMS GRADIENT = 0.027589766 + if len(parts) == 4: + maximum = float(parts[3]) + line = inputfile.next() + parts = line.split() + rms = float(parts[3]) + + + # FMO also prints two final one- and two-body gradients (see exam37): + # (1) MAXIMUM GRADIENT = 0.0531540 RMS GRADIENT = 0.0189223 + if len(parts) == 9: + maximum = float(parts[4]) + rms = float(parts[8]) + + self.geovalues.append([maximum, rms]) + + if line[11:50] == "ATOMIC COORDINATES": + # This is the input orientation, which is the only data available for + # SP calcs, but which should be overwritten by the standard orientation + # values, which is the only information available for all geoopt cycles. + if not hasattr(self, "atomcoords"): + self.atomcoords = [] + self.atomnos = [] + line = inputfile.next() + atomcoords = [] + atomnos = [] + line = inputfile.next() + while line.strip(): + temp = line.strip().split() + atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[2:5]]) + atomnos.append(int(round(float(temp[1])))) # Don't use the atom name as this is arbitary + line = inputfile.next() + self.atomnos = numpy.array(atomnos, "i") + self.atomcoords.append(atomcoords) + + if line[12:40] == "EQUILIBRIUM GEOMETRY LOCATED": + # Prevent extraction of the final geometry twice + self.geooptfinished = True + + if line[1:29] == "COORDINATES OF ALL ATOMS ARE" and not self.geooptfinished: + # This is the standard orientation, which is the only coordinate + # information available for all geometry optimisation cycles. + # The input orientation will be overwritten if this is a geometry optimisation + # We assume that a previous Input Orientation has been found and + # used to extract the atomnos + if self.firststdorient: + self.firststdorient = False + # Wipes out the single input coordinate at the start of the file + self.atomcoords = [] + + line = inputfile.next() + hyphens = inputfile.next() + + atomcoords = [] + line = inputfile.next() + + for i in range(self.natom): + temp = line.strip().split() + atomcoords.append(map(float, temp[2:5])) + line = inputfile.next() + self.atomcoords.append(atomcoords) + + # Section with SCF information. + # + # The space at the start of the search string is to differentiate from MCSCF. + # Everything before the search string is stored as the type of SCF. + # SCF types may include: BLYP, RHF, ROHF, UHF, etc. + # + # For example, in exam17 the section looks like this (note that this is GVB): + # ------------------------ + # ROHF-GVB SCF CALCULATION + # ------------------------ + # GVB STEP WILL USE 119875 WORDS OF MEMORY. + # + # MAXIT= 30 NPUNCH= 2 SQCDF TOL=1.0000E-05 + # NUCLEAR ENERGY= 6.1597411978 + # EXTRAP=T DAMP=F SHIFT=F RSTRCT=F DIIS=F SOSCF=F + # + # ITER EX TOTAL ENERGY E CHANGE SQCDF DIIS ERROR + # 0 0 -38.298939963 -38.298939963 0.131784454 0.000000000 + # 1 1 -38.332044339 -0.033104376 0.026019716 0.000000000 + # ... and will be terminated by a blank line. + if line.rstrip()[-16:] == " SCF CALCULATION": + + # Remember the type of SCF. + self.scftype = line.strip()[:-16] + + dashes = inputfile.next() + + while line [:5] != " ITER": + + # GVB uses SQCDF for checking convergence (for example in exam17). + if "GVB" in self.scftype and "SQCDF TOL=" in line: + scftarget = float(line.split("=")[-1]) + + # Normally however the density is used as the convergence criterium. + # Deal with various versions: + # (GAMESS VERSION = 12 DEC 2003) + # DENSITY MATRIX CONV= 2.00E-05 DFT GRID SWITCH THRESHOLD= 3.00E-04 + # (GAMESS VERSION = 22 FEB 2006) + # DENSITY MATRIX CONV= 1.00E-05 + # (PC GAMESS version 6.2, Not DFT?) + # DENSITY CONV= 1.00E-05 + elif "DENSITY CONV" in line or "DENSITY MATRIX CONV" in line: + scftarget = float(line.split()[-1]) + + line = inputfile.next() + + if not hasattr(self, "scftargets"): + self.scftargets = [] + + self.scftargets.append([scftarget]) + + if not hasattr(self,"scfvalues"): + self.scfvalues = [] + + line = inputfile.next() + + # Normally the iteration print in 6 columns. + # For ROHF, however, it is 5 columns, thus this extra parameter. + if "ROHF" in self.scftype: + valcol = 4 + else: + valcol = 5 + + # SCF iterations are terminated by a blank line. + # The first four characters usually contains the step number. + # However, lines can also contain messages, including: + # * * * INITIATING DIIS PROCEDURE * * * + # CONVERGED TO SWOFF, SO DFT CALCULATION IS NOW SWITCHED ON + # DFT CODE IS SWITCHING BACK TO THE FINER GRID + values = [] + while line.strip(): + try: + temp = int(line[0:4]) + except ValueError: + pass + else: + values.append([float(line.split()[valcol])]) + line = inputfile.next() + self.scfvalues.append(values) + + if line.find("NORMAL COORDINATE ANALYSIS IN THE HARMONIC APPROXIMATION") >= 0: + # GAMESS has... + # MODES 1 TO 6 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. + # + # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2, + # REDUCED MASSES IN AMU. + # + # 1 2 3 4 5 + # FREQUENCY: 52.49 41.45 17.61 9.23 10.61 + # REDUCED MASS: 3.92418 3.77048 5.43419 6.44636 5.50693 + # IR INTENSITY: 0.00013 0.00001 0.00004 0.00000 0.00003 + + # ...or in the case of a numerical Hessian job... + + # MODES 1 TO 5 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. + # + # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2, + # REDUCED MASSES IN AMU. + # + # 1 2 3 4 5 + # FREQUENCY: 0.05 0.03 0.03 30.89 30.94 + # REDUCED MASS: 8.50125 8.50137 8.50136 1.06709 1.06709 + + + # whereas PC-GAMESS has... + # MODES 1 TO 6 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. + # + # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2 + # + # 1 2 3 4 5 + # FREQUENCY: 5.89 1.46 0.01 0.01 0.01 + # IR INTENSITY: 0.00000 0.00000 0.00000 0.00000 0.00000 + + # If Raman is present we have (for PC-GAMESS)... + # MODES 1 TO 6 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. + # + # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2 + # RAMAN INTENSITIES IN ANGSTROM**4/AMU, DEPOLARIZATIONS ARE DIMENSIONLESS + # + # 1 2 3 4 5 + # FREQUENCY: 5.89 1.46 0.04 0.03 0.01 + # IR INTENSITY: 0.00000 0.00000 0.00000 0.00000 0.00000 + # RAMAN INTENSITY: 12.675 1.828 0.000 0.000 0.000 + # DEPOLARIZATION: 0.750 0.750 0.124 0.009 0.750 + + # If PC-GAMESS has not reached the stationary point we have + # MODES 1 TO 5 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. + # + # FREQUENCIES IN CM**-1, IR INTENSITIES IN DEBYE**2/AMU-ANGSTROM**2 + # + # ******************************************************* + # * THIS IS NOT A STATIONARY POINT ON THE MOLECULAR PES * + # * THE VIBRATIONAL ANALYSIS IS NOT VALID !!! * + # ******************************************************* + # + # 1 2 3 4 5 + + # MODES 2 TO 7 ARE TAKEN AS ROTATIONS AND TRANSLATIONS. + + self.vibfreqs = [] + self.vibirs = [] + self.vibdisps = [] + + # Need to get to the modes line + warning = False + while line.find("MODES") == -1: + line = inputfile.next() + if line.find("THIS IS NOT A STATIONARY POINT")>=0: + warning = True + startrot = int(line.split()[1]) + endrot = int(line.split()[3]) + blank = inputfile.next() + + line = inputfile.next() # FREQUENCIES, etc. + while line != blank: + line = inputfile.next() + if warning: # Get past the second warning + line = inputfile.next() + while line!= blank: + line = inputfile.next() + self.logger.warning("This is not a stationary point on the molecular" + "PES. The vibrational analysis is not valid.") + + freqNo = inputfile.next() + while freqNo.find("SAYVETZ") == -1: + freq = inputfile.next().strip().split()[1:] + # May include imaginary frequencies + # FREQUENCY: 825.18 I 111.53 12.62 10.70 0.89 + newfreq = [] + for i, x in enumerate(freq): + if x!="I": + newfreq.append(float(x)) + else: + newfreq[-1] = -newfreq[-1] + self.vibfreqs.extend(newfreq) + line = inputfile.next() + if line.find("REDUCED") >= 0: # skip the reduced mass (not always present) + line = inputfile.next() + if line.find("IR INTENSITY") >= 0: + # Not present if a numerical Hessian calculation + irIntensity = map(float, line.strip().split()[2:]) + self.vibirs.extend([utils.convertor(x, "Debye^2/amu-Angstrom^2", "km/mol") for x in irIntensity]) + line = inputfile.next() + if line.find("RAMAN") >= 0: + if not hasattr(self,"vibramans"): + self.vibramans = [] + ramanIntensity = line.strip().split() + self.vibramans.extend(map(float, ramanIntensity[2:])) + depolar = inputfile.next() + line = inputfile.next() + assert line == blank + + # Extract the Cartesian displacement vectors + p = [ [], [], [], [], [] ] + for j in range(len(self.atomnos)): + q = [ [], [], [], [], [] ] + for k in range(3): # x, y, z + line = inputfile.next()[21:] + broken = map(float, line.split()) + for l in range(len(broken)): + q[l].append(broken[l]) + for k in range(len(broken)): + p[k].append(q[k]) + self.vibdisps.extend(p[:len(broken)]) + + # Skip the Sayvetz stuff at the end + for j in range(10): + line = inputfile.next() + blank = inputfile.next() + freqNo = inputfile.next() + # Exclude rotations and translations + self.vibfreqs = numpy.array(self.vibfreqs[:startrot-1]+self.vibfreqs[endrot:], "d") + self.vibirs = numpy.array(self.vibirs[:startrot-1]+self.vibirs[endrot:], "d") + self.vibdisps = numpy.array(self.vibdisps[:startrot-1]+self.vibdisps[endrot:], "d") + if hasattr(self, "vibramans"): + self.vibramans = numpy.array(self.vibramans[:startrot-1]+self.vibramans[endrot:], "d") + + if line[5:21] == "ATOMIC BASIS SET": + self.gbasis = [] + line = inputfile.next() + while line.find("SHELL")<0: + line = inputfile.next() + blank = inputfile.next() + atomname = inputfile.next() + # shellcounter stores the shell no of the last shell + # in the previous set of primitives + shellcounter = 1 + while line.find("TOTAL NUMBER")<0: + blank = inputfile.next() + line = inputfile.next() + shellno = int(line.split()[0]) + shellgap = shellno - shellcounter + gbasis = [] # Stores basis sets on one atom + shellsize = 0 + while len(line.split())!=1 and line.find("TOTAL NUMBER")<0: + shellsize += 1 + coeff = {} + # coefficients and symmetries for a block of rows + while line.strip(): + temp = line.strip().split() + sym = temp[1] + assert sym in ['S', 'P', 'D', 'F', 'G', 'L'] + if sym == "L": # L refers to SP + if len(temp)==6: # GAMESS US + coeff.setdefault("S", []).append( (float(temp[3]), float(temp[4])) ) + coeff.setdefault("P", []).append( (float(temp[3]), float(temp[5])) ) + else: # PC GAMESS + assert temp[6][-1] == temp[9][-1] == ')' + coeff.setdefault("S", []).append( (float(temp[3]), float(temp[6][:-1])) ) + coeff.setdefault("P", []).append( (float(temp[3]), float(temp[9][:-1])) ) + else: + if len(temp)==5: # GAMESS US + coeff.setdefault(sym, []).append( (float(temp[3]), float(temp[4])) ) + else: # PC GAMESS + assert temp[6][-1] == ')' + coeff.setdefault(sym, []).append( (float(temp[3]), float(temp[6][:-1])) ) + line = inputfile.next() + # either a blank or a continuation of the block + if sym == "L": + gbasis.append( ('S', coeff['S'])) + gbasis.append( ('P', coeff['P'])) + else: + gbasis.append( (sym, coeff[sym])) + line = inputfile.next() + # either the start of the next block or the start of a new atom or + # the end of the basis function section + + numtoadd = 1 + (shellgap / shellsize) + shellcounter = shellno + shellsize + for x in range(numtoadd): + self.gbasis.append(gbasis) + + if line.find("EIGENVECTORS") == 10 or line.find("MOLECULAR OBRITALS") == 10: + # The details returned come from the *final* report of evalues and + # the last list of symmetries in the log file. + # Should be followed by lines like this: + # ------------ + # EIGENVECTORS + # ------------ + # + # 1 2 3 4 5 + # -10.0162 -10.0161 -10.0039 -10.0039 -10.0029 + # BU AG BU AG AG + # 1 C 1 S 0.699293 0.699290 -0.027566 0.027799 0.002412 + # 2 C 1 S 0.031569 0.031361 0.004097 -0.004054 -0.000605 + # 3 C 1 X 0.000908 0.000632 -0.004163 0.004132 0.000619 + # 4 C 1 Y -0.000019 0.000033 0.000668 -0.000651 0.005256 + # 5 C 1 Z 0.000000 0.000000 0.000000 0.000000 0.000000 + # 6 C 2 S -0.699293 0.699290 0.027566 0.027799 0.002412 + # 7 C 2 S -0.031569 0.031361 -0.004097 -0.004054 -0.000605 + # 8 C 2 X 0.000908 -0.000632 -0.004163 -0.004132 -0.000619 + # 9 C 2 Y -0.000019 -0.000033 0.000668 0.000651 -0.005256 + # 10 C 2 Z 0.000000 0.000000 0.000000 0.000000 0.000000 + # 11 C 3 S -0.018967 -0.019439 0.011799 -0.014884 -0.452328 + # 12 C 3 S -0.007748 -0.006932 0.000680 -0.000695 -0.024917 + # 13 C 3 X 0.002628 0.002997 0.000018 0.000061 -0.003608 + # and so forth... with blanks lines between blocks of 5 orbitals each. + # Warning! There are subtle differences between GAMESS-US and PC-GAMES + # in the formatting of the first four columns. + # + # Watch out for F orbitals... + # PC GAMESS + # 19 C 1 YZ 0.000000 0.000000 0.000000 0.000000 0.000000 + # 20 C XXX 0.000000 0.000000 0.000000 0.000000 0.002249 + # 21 C YYY 0.000000 0.000000 -0.025555 0.000000 0.000000 + # 22 C ZZZ 0.000000 0.000000 0.000000 0.002249 0.000000 + # 23 C XXY 0.000000 0.000000 0.001343 0.000000 0.000000 + # GAMESS US + # 55 C 1 XYZ 0.000000 0.000000 0.000000 0.000000 0.000000 + # 56 C 1XXXX -0.000014 -0.000067 0.000000 0.000000 0.000000 + # + # This is fine for GeoOpt and SP, but may be weird for TD and Freq. + + # This is the stuff that we can read from these blocks. + self.moenergies = [[]] + self.mosyms = [[]] + if not hasattr(self, "nmo"): + self.nmo = self.nbasis + self.mocoeffs = [numpy.zeros((self.nmo, self.nbasis), "d")] + readatombasis = False + if not hasattr(self, "atombasis"): + self.atombasis = [] + self.aonames = [] + for i in range(self.natom): + self.atombasis.append([]) + self.aonames = [] + readatombasis = True + + dashes = inputfile.next() + for base in range(0, self.nmo, 5): + + line = inputfile.next() + # Make sure that this section does not end prematurely - checked by regression test 2CO.ccsd.aug-cc-pVDZ.out. + if line.strip() != "": + break; + + numbers = inputfile.next() # Eigenvector numbers. + + # Sometimes there are some blank lines here. + while not line.strip(): + line = inputfile.next() + + # Eigenvalues for these orbitals (in hartrees). + try: + self.moenergies[0].extend([utils.convertor(float(x), "hartree", "eV") for x in line.split()]) + except: + self.logger.warning('MO section found but could not be parsed!') + break; + + # Orbital symmetries. + line = inputfile.next() + if line.strip(): + self.mosyms[0].extend(map(self.normalisesym, line.split())) + + # Now we have nbasis lines. + # Going to use the same method as for normalise_aonames() + # to extract basis set information. + p = re.compile("(\d+)\s*([A-Z][A-Z]?)\s*(\d+)\s*([A-Z]+)") + oldatom ='0' + for i in range(self.nbasis): + line = inputfile.next() + + # If line is empty, break (ex. for FMO in exam37). + if not line.strip(): break + + # Fill atombasis and aonames only first time around + if readatombasis and base == 0: + aonames = [] + start = line[:17].strip() + m = p.search(start) + if m: + g = m.groups() + aoname = "%s%s_%s" % (g[1].capitalize(), g[2], g[3]) + oldatom = g[2] + atomno = int(g[2])-1 + orbno = int(g[0])-1 + else: # For F orbitals, as shown above + g = [x.strip() for x in line.split()] + aoname = "%s%s_%s" % (g[1].capitalize(), oldatom, g[2]) + atomno = int(oldatom)-1 + orbno = int(g[0])-1 + self.atombasis[atomno].append(orbno) + self.aonames.append(aoname) + coeffs = line[15:] # Strip off the crud at the start. + j = 0 + while j*11+4 < len(coeffs): + self.mocoeffs[0][base+j, i] = float(coeffs[j * 11:(j + 1) * 11]) + j += 1 + + line = inputfile.next() + # If it's restricted and no more properties: + # ...... END OF RHF/DFT CALCULATION ...... + # If there are more properties (DENSITY MATRIX): + # -------------- + # + # If it's unrestricted we have: + # + # ----- BETA SET ----- + # + # ------------ + # EIGENVECTORS + # ------------ + # + # 1 2 3 4 5 + # ... and so forth. + line = inputfile.next() + if line[2:22] == "----- BETA SET -----": + self.mocoeffs.append(numpy.zeros((self.nmo, self.nbasis), "d")) + self.moenergies.append([]) + self.mosyms.append([]) + for i in range(4): + line = inputfile.next() + for base in range(0, self.nmo, 5): + blank = inputfile.next() + line = inputfile.next() # Eigenvector no + line = inputfile.next() + self.moenergies[1].extend([utils.convertor(float(x), "hartree", "eV") for x in line.split()]) + line = inputfile.next() + self.mosyms[1].extend(map(self.normalisesym, line.split())) + for i in range(self.nbasis): + line = inputfile.next() + temp = line[15:] # Strip off the crud at the start + j = 0 + while j * 11 + 4 < len(temp): + self.mocoeffs[1][base+j, i] = float(temp[j * 11:(j + 1) * 11]) + j += 1 + line = inputfile.next() + self.moenergies = [numpy.array(x, "d") for x in self.moenergies] + + # Natural orbitals - presently support only CIS. + # Looks basically the same as eigenvectors, without symmetry labels. + if line[10:30] == "CIS NATURAL ORBITALS": + + self.nocoeffs = numpy.zeros((self.nmo, self.nbasis), "d") + + dashes = inputfile.next() + for base in range(0, self.nmo, 5): + + blank = inputfile.next() + numbers = inputfile.next() # Eigenvector numbers. + + # Eigenvalues for these natural orbitals (not in hartrees!). + # Sometimes there are some blank lines before it. + line = inputfile.next() + while not line.strip(): + line = inputfile.next() + eigenvalues = line + + # Orbital symemtry labels are normally here for MO coefficients. + line = inputfile.next() + + # Now we have nbasis lines with the coefficients. + for i in range(self.nbasis): + + line = inputfile.next() + coeffs = line[15:] + j = 0 + while j*11+4 < len(coeffs): + self.nocoeffs[base+j, i] = float(coeffs[j * 11:(j + 1) * 11]) + j += 1 + + # We cannot trust this self.homos until we come to the phrase: + # SYMMETRIES FOR INITAL GUESS ORBITALS FOLLOW + # which either is followed by "ALPHA" or "BOTH" at which point we can say + # for certain that it is an un/restricted calculations. + # Note that MCSCF calcs also print this search string, so make sure + # that self.homos does not exist yet. + if line[1:28] == "NUMBER OF OCCUPIED ORBITALS" and not hasattr(self,'homos'): + homos = [int(line.split()[-1])-1] + line = inputfile.next() + homos.append(int(line.split()[-1])-1) + self.homos = numpy.array(homos, "i") + + + if line.find("SYMMETRIES FOR INITIAL GUESS ORBITALS FOLLOW") >= 0: + # Not unrestricted, so lop off the second index. + # In case the search string above was not used (ex. FMO in exam38), + # we can try to use the next line which should also contain the + # number of occupied orbitals. + if line.find("BOTH SET(S)") >= 0: + nextline = inputfile.next() + if "ORBITALS ARE OCCUPIED" in nextline: + homos = int(nextline.split()[0])-1 + if hasattr(self,"homos"): + try: + assert self.homos[0] == homos + except AssertionError: + self.logger.warning("Number of occupied orbitals not consistent. This is normal for ECP and FMO jobs.") + else: + self.homos = [homos] + self.homos = numpy.resize(self.homos, [1]) + + # Set the total number of atoms, only once. + # Normally GAMESS print TOTAL NUMBER OF ATOMS, however in some cases + # this is slightly different (ex. lower case for FMO in exam37). + if not hasattr(self,"natom") and "NUMBER OF ATOMS" in line.upper(): + self.natom = int(line.split()[-1]) + + if line.find("NUMBER OF CARTESIAN GAUSSIAN BASIS") == 1 or line.find("TOTAL NUMBER OF BASIS FUNCTIONS") == 1: + # The first is from Julien's Example and the second is from Alexander's + # I think it happens if you use a polar basis function instead of a cartesian one + self.nbasis = int(line.strip().split()[-1]) + + elif line.find("SPHERICAL HARMONICS KEPT IN THE VARIATION SPACE") >= 0: + # Note that this line is present if ISPHER=1, e.g. for C_bigbasis + self.nmo = int(line.strip().split()[-1]) + + elif line.find("TOTAL NUMBER OF MOS IN VARIATION SPACE") == 1: + # Note that this line is not always present, so by default + # NBsUse is set equal to NBasis (see below). + self.nmo = int(line.split()[-1]) + + elif line.find("OVERLAP MATRIX") == 0 or line.find("OVERLAP MATRIX") == 1: + # The first is for PC-GAMESS, the second for GAMESS + # Read 1-electron overlap matrix + if not hasattr(self, "aooverlaps"): + self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") + else: + self.logger.info("Reading additional aooverlaps...") + base = 0 + while base < self.nbasis: + blank = inputfile.next() + line = inputfile.next() # Basis fn number + blank = inputfile.next() + for i in range(self.nbasis - base): # Fewer lines each time + line = inputfile.next() + temp = line.split() + for j in range(4, len(temp)): + self.aooverlaps[base+j-4, i+base] = float(temp[j]) + self.aooverlaps[i+base, base+j-4] = float(temp[j]) + base += 5 + + # ECP Pseudopotential information + if "ECP POTENTIALS" in line: + if not hasattr(self, "coreelectrons"): + self.coreelectrons = [0]*self.natom + dashes = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + while header.split()[0] == "PARAMETERS": + name = header[17:25] + atomnum = int(header[34:40]) + # The pseudopotnetial is given explicitely + if header[40:50] == "WITH ZCORE": + zcore = int(header[50:55]) + lmax = int(header[63:67]) + self.coreelectrons[atomnum-1] = zcore + # The pseudopotnetial is copied from another atom + if header[40:55] == "ARE THE SAME AS": + atomcopy = int(header[60:]) + self.coreelectrons[atomnum-1] = self.coreelectrons[atomcopy-1] + line = inputfile.next() + while line.split() <> []: + line = inputfile.next() + header = inputfile.next() + + # This was used before refactoring the parser, geotargets was set here after parsing. + #if not hasattr(self, "geotargets"): + # opttol = 1e-4 + # self.geotargets = numpy.array([opttol, 3. / opttol], "d") + #if hasattr(self,"geovalues"): self.geovalues = numpy.array(self.geovalues, "d") + + +if __name__ == "__main__": + import doctest, gamessparser + doctest.testmod(gamessparser, verbose=False) diff --git a/external/cclib/parser/gamessukparser.py b/external/cclib/parser/gamessukparser.py index 3120be8585..3b54a82129 100644 --- a/external/cclib/parser/gamessukparser.py +++ b/external/cclib/parser/gamessukparser.py @@ -1,524 +1,524 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 861 $" - - -import re - -import numpy - -import logfileparser -import utils - - -class GAMESSUK(logfileparser.Logfile): - """A GAMESS UK log file""" - SCFRMS, SCFMAX, SCFENERGY = range(3) # Used to index self.scftargets[] - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(GAMESSUK, self).__init__(logname="GAMESSUK", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "GAMESS UK log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'GAMESSUK("%s")' % (self.filename) - - def normalisesym(self, label): - """Use standard symmetry labels instead of GAMESS UK labels. - - >>> t = GAMESSUK("dummyfile.txt") - >>> labels = ['a', 'a1', 'ag', "a'", 'a"', "a''", "a1''", 'a1"'] - >>> labels.extend(["e1+", "e1-"]) - >>> answer = [t.normalisesym(x) for x in labels] - >>> answer - ['A', 'A1', 'Ag', "A'", 'A"', 'A"', 'A1"', 'A1"', 'E1', 'E1'] - """ - label = label.replace("''", '"').replace("+", "").replace("-", "") - ans = label[0].upper() + label[1:] - - return ans - - def before_parsing(self): - - # This will be used to detect the first set of "nuclear coordinates" in - # a geometry-optimization - self.firstnuccoords = True - - # used for determining whether to add a second mosyms, etc. - self.betamosyms = self.betamoenergies = self.betamocoeffs = False - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - if line[1:22] == "total number of atoms": - if not hasattr(self, "natom"): - self.natom = int(line.split()[-1]) - - if line[3:44] == "convergence threshold in optimization run": - # Assuming that this is only found in the case of OPTXYZ - # (i.e. an optimization in Cartesian coordinates) - self.geotargets = [float(line.split()[-2])] - - if line[32:61] == "largest component of gradient": - # This is the geotarget in the case of OPTXYZ - if not hasattr(self, "geovalues"): - self.geovalues = [] - self.geovalues.append([float(line.split()[4])]) - - if line[37:49] == "convergence?": - # Get the geovalues and geotargets for OPTIMIZE - if not hasattr(self, "geovalues"): - self.geovalues = [] - self.geotargets = [] - geotargets = [] - geovalues = [] - for i in range(4): - temp = line.split() - geovalues.append(float(temp[2])) - if not self.geotargets: - geotargets.append(float(temp[-2])) - line = inputfile.next() - self.geovalues.append(geovalues) - if not self.geotargets: - self.geotargets = geotargets - - if line[40:58] == "molecular geometry": - # Only one set of atomcoords is taken from this section - # For geo-opts, more coordinates are taken from the "nuclear coordinates" - if not hasattr(self, "atomcoords"): - self.atomcoords = [] - self.atomnos = [] - - stop = " "*9 + "*"*79 - line = inputfile.next() - while not line.startswith(stop): - line = inputfile.next() - line = inputfile.next() - while not line.startswith(stop): - line = inputfile.next() - empty = inputfile.next() - - atomcoords = [] - empty = inputfile.next() - while not empty.startswith(stop): - line = inputfile.next().split() # the coordinate data - atomcoords.append(map(float,line[3:6])) - self.atomnos.append(int(round(float(line[2])))) - while line!=empty: - line = inputfile.next() - # at this point, line is an empty line, right after - # 1 or more lines containing basis set information - empty = inputfile.next() - # empty is either a row of asterisks or the empty line - # before the row of coordinate data - - self.atomcoords.append(atomcoords) - self.atomnos = numpy.array(self.atomnos, "i") - - if line[40:59] == "nuclear coordinates": - # We need not remember the first geometry in the geo-opt as this will - # be recorded already, in the "molecular geometry" section - # (note: single-point calculations have no "nuclear coordinates" only - # "molecular geometry") - if self.firstnuccoords: - self.firstnuccoords = False - return - # This was continue (in loop) before parser refactoring. - # continue - if not hasattr(self, "atomcoords"): - self.atomcoords = [] - self.atomnos = [] - - asterisk = inputfile.next() - blank = inputfile.next() - colmname = inputfile.next() - equals = inputfile.next() - - atomcoords = [] - atomnos = [] - line = inputfile.next() - while line != equals: - temp = line.strip().split() - atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[0:3]]) - if not hasattr(self, "atomnos") or len(self.atomnos) == 0: - atomnos.append(int(float(temp[3]))) - - line = inputfile.next() - - self.atomcoords.append(atomcoords) - if not hasattr(self, "atomnos") or len(self.atomnos) == 0: - self.atomnos = atomnos - - if line[1:32] == "total number of basis functions": - self.nbasis = int(line.split()[-1]) - while line.find("charge of molecule")<0: - line = inputfile.next() - self.charge = int(line.split()[-1]) - self.mult = int(inputfile.next().split()[-1]) - - alpha = int(inputfile.next().split()[-1])-1 - beta = int(inputfile.next().split()[-1])-1 - if self.mult==1: - self.homos = numpy.array([alpha], "i") - else: - self.homos = numpy.array([alpha,beta], "i") - - if line[37:69] == "s-matrix over gaussian basis set": - self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") - - minus = inputfile.next() - blank = inputfile.next() - i = 0 - while i < self.nbasis: - blank = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - - for j in range(self.nbasis): - temp = map(float, inputfile.next().split()[1:]) - self.aooverlaps[j,(0+i):(len(temp)+i)] = temp - - i += len(temp) - - if line[18:43] == 'EFFECTIVE CORE POTENTIALS': - self.coreelectrons = numpy.zeros(self.natom, 'i') - asterisk = inputfile.next() - line = inputfile.next() - while line[15:46]!="*"*31: - if line.find("for atoms ...")>=0: - atomindex = [] - line = inputfile.next() - while line.find("core charge")<0: - broken = line.split() - atomindex.extend([int(x.split("-")[0]) for x in broken]) - line = inputfile.next() - charge = float(line.split()[4]) - for idx in atomindex: - self.coreelectrons[idx-1] = self.atomnos[idx-1] - charge - line = inputfile.next() - - if line[3:27] == "Wavefunction convergence": - self.scftarget = float(line.split()[-2]) - self.scftargets = [] - - if line[11:22] == "normal mode": - if not hasattr(self, "vibfreqs"): - self.vibfreqs = [] - self.vibirs = [] - - units = inputfile.next() - xyz = inputfile.next() - equals = inputfile.next() - line = inputfile.next() - while line!=equals: - temp = line.split() - self.vibfreqs.append(float(temp[1])) - self.vibirs.append(float(temp[-2])) - line = inputfile.next() - # Use the length of the vibdisps to figure out - # how many rotations and translations to remove - self.vibfreqs = self.vibfreqs[-len(self.vibdisps):] - self.vibirs = self.vibirs[-len(self.vibdisps):] - - if line[44:73] == "normalised normal coordinates": - self.vibdisps = [] - equals = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - freqnum = inputfile.next() - while freqnum.find("=")<0: - blank = inputfile.next() - equals = inputfile.next() - freqs = inputfile.next() - equals = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - equals = inputfile.next() - p = [ [] for x in range(9) ] - for i in range(len(self.atomnos)): - brokenx = map(float, inputfile.next()[25:].split()) - brokeny = map(float, inputfile.next()[25:].split()) - brokenz = map(float, inputfile.next()[25:].split()) - for j,x in enumerate(zip(brokenx, brokeny, brokenz)): - p[j].append(x) - self.vibdisps.extend(p) - - blank = inputfile.next() - blank = inputfile.next() - freqnum = inputfile.next() - - if line[26:36] == "raman data": - self.vibramans = [] - - stars = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - - blank = inputfile.next() - line = inputfile.next() - while line[1]!="*": - self.vibramans.append(float(line.split()[3])) - blank = inputfile.next() - line = inputfile.next() - # Use the length of the vibdisps to figure out - # how many rotations and translations to remove - self.vibramans = self.vibramans[-len(self.vibdisps):] - - if line[3:11] == "SCF TYPE": - self.scftype = line.split()[-2] - assert self.scftype in ['rhf', 'uhf', 'gvb'], "%s not one of 'rhf', 'uhf' or 'gvb'" % self.scftype - - if line[15:31] == "convergence data": - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - self.scftargets.append([self.scftarget]) # Assuming it does not change over time - while line[1:10] != "="*9: - line = inputfile.next() - line = inputfile.next() - tester = line.find("tester") # Can be in a different place depending - assert tester>=0 - while line[1:10] != "="*9: # May be two or three lines (unres) - line = inputfile.next() - - scfvalues = [] - line = inputfile.next() - while line.strip(): - if line[2:6]!="****": - # e.g. **** recalulation of fock matrix on iteration 4 (examples/chap12/pyridine.out) - scfvalues.append([float(line[tester-5:tester+6])]) - line = inputfile.next() - self.scfvalues.append(scfvalues) - - if line[10:22] == "total energy" and len(line.split()) == 3: - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - scfenergy = utils.convertor(float(line.split()[-1]), "hartree", "eV") - self.scfenergies.append(scfenergy) - - # Total energies after Moller-Plesset corrections - # Second order correction is always first, so its first occurance - # triggers creation of mpenergies (list of lists of energies) - # Further corrections are appended as found - # Note: GAMESS-UK sometimes prints only the corrections, - # so they must be added to the last value of scfenergies - if line[10:32] == "mp2 correlation energy" or \ - line[10:42] == "second order perturbation energy": - if not hasattr(self, "mpenergies"): - self.mpenergies = [] - self.mpenergies.append([]) - self.mp2correction = self.float(line.split()[-1]) - self.mp2energy = self.scfenergies[-1] + self.mp2correction - self.mpenergies[-1].append(utils.convertor(self.mp2energy, "hartree", "eV")) - if line[10:41] == "third order perturbation energy": - self.mp3correction = self.float(line.split()[-1]) - self.mp3energy = self.mp2energy + self.mp3correction - self.mpenergies[-1].append(utils.convertor(self.mp3energy, "hartree", "eV")) - - if line[40:59] == "molecular basis set": - self.gbasis = [] - line = inputfile.next() - while line.find("contraction coefficients")<0: - line = inputfile.next() - equals = inputfile.next() - blank = inputfile.next() - atomname = inputfile.next() - basisregexp = re.compile("\d*(\D+)") # Get everything after any digits - shellcounter = 1 - while line!=equals: - gbasis = [] # Stores basis sets on one atom - blank = inputfile.next() - blank = inputfile.next() - line = inputfile.next() - shellno = int(line.split()[0]) - shellgap = shellno - shellcounter - shellsize = 0 - while len(line.split())!=1 and line!=equals: - if line.split(): - shellsize += 1 - coeff = {} - # coefficients and symmetries for a block of rows - while line.strip() and line!=equals: - temp = line.strip().split() - # temp[1] may be either like (a) "1s" and "1sp", or (b) "s" and "sp" - # See GAMESS-UK 7.0 distribution/examples/chap12/pyridine2_21m10r.out - # for an example of the latter - sym = basisregexp.match(temp[1]).groups()[0] - assert sym in ['s', 'p', 'd', 'f', 'sp'], "'%s' not a recognized symmetry" % sym - if sym == "sp": - coeff.setdefault("S", []).append( (float(temp[3]), float(temp[6])) ) - coeff.setdefault("P", []).append( (float(temp[3]), float(temp[10])) ) - else: - coeff.setdefault(sym.upper(), []).append( (float(temp[3]), float(temp[6])) ) - line = inputfile.next() - # either a blank or a continuation of the block - if coeff: - if sym == "sp": - gbasis.append( ('S', coeff['S'])) - gbasis.append( ('P', coeff['P'])) - else: - gbasis.append( (sym.upper(), coeff[sym.upper()])) - if line==equals: - continue - line = inputfile.next() - # either the start of the next block or the start of a new atom or - # the end of the basis function section (signified by a line of equals) - numtoadd = 1 + (shellgap / shellsize) - shellcounter = shellno + shellsize - for x in range(numtoadd): - self.gbasis.append(gbasis) - - if line[50:70] == "----- beta set -----": - self.betamosyms = True - self.betamoenergies = True - self.betamocoeffs = True - # betamosyms will be turned off in the next - # SYMMETRY ASSIGNMENT section - - if line[31:50] == "SYMMETRY ASSIGNMENT": - if not hasattr(self, "mosyms"): - self.mosyms = [] - - multiple = {'a':1, 'b':1, 'e':2, 't':3, 'g':4, 'h':5} - - equals = inputfile.next() - line = inputfile.next() - while line != equals: # There may be one or two lines of title (compare mg10.out and duhf_1.out) - line = inputfile.next() - - mosyms = [] - line = inputfile.next() - while line != equals: - temp = line[25:30].strip() - if temp[-1]=='?': - # e.g. e? or t? or g? (see example/chap12/na7mg_uhf.out) - # for two As, an A and an E, and two Es of the same energy respectively. - t = line[91:].strip().split() - for i in range(1,len(t),2): - for j in range(multiple[t[i][0]]): # add twice for 'e', etc. - mosyms.append(self.normalisesym(t[i])) - else: - for j in range(multiple[temp[0]]): - mosyms.append(self.normalisesym(temp)) # add twice for 'e', etc. - line = inputfile.next() - assert len(mosyms) == self.nmo, "mosyms: %d but nmo: %d" % (len(mosyms), self.nmo) - if self.betamosyms: - # Only append if beta (otherwise with IPRINT SCF - # it will add mosyms for every step of a geo opt) - self.mosyms.append(mosyms) - self.betamosyms = False - elif self.scftype=='gvb': - # gvb has alpha and beta orbitals but they are identical - self.mosysms = [mosyms, mosyms] - else: - self.mosyms = [mosyms] - - if line[50:62] == "eigenvectors": - # Mocoeffs...can get evalues from here too - # (only if using FORMAT HIGH though will they all be present) - if not hasattr(self, "mocoeffs"): - self.aonames = [] - aonames = [] - minus = inputfile.next() - - mocoeffs = numpy.zeros( (self.nmo, self.nbasis), "d") - readatombasis = False - if not hasattr(self, "atombasis"): - self.atombasis = [] - for i in range(self.natom): - self.atombasis.append([]) - readatombasis = True - - blank = inputfile.next() - blank = inputfile.next() - evalues = inputfile.next() - - p = re.compile(r"\d+\s+(\d+)\s*(\w+) (\w+)") - oldatomname = "DUMMY VALUE" - - mo = 0 - while mo < self.nmo: - blank = inputfile.next() - blank = inputfile.next() - nums = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - for basis in range(self.nbasis): - line = inputfile.next() - # Fill atombasis only first time around. - if readatombasis: - orbno = int(line[1:5])-1 - atomno = int(line[6:9])-1 - self.atombasis[atomno].append(orbno) - if not self.aonames: - pg = p.match(line[:18].strip()).groups() - atomname = "%s%s%s" % (pg[1][0].upper(), pg[1][1:], pg[0]) - if atomname!=oldatomname: - aonum = 1 - oldatomname = atomname - name = "%s_%d%s" % (atomname, aonum, pg[2].upper()) - if name in aonames: - aonum += 1 - name = "%s_%d%s" % (atomname, aonum, pg[2].upper()) - aonames.append(name) - temp = map(float, line[19:].split()) - mocoeffs[mo:(mo+len(temp)), basis] = temp - # Fill atombasis only first time around. - readatombasis = False - if not self.aonames: - self.aonames = aonames - - line = inputfile.next() # blank line - while line==blank: - line = inputfile.next() - evalues = line - if evalues[:17].strip(): # i.e. if these aren't evalues - break # Not all the MOs are present - mo += len(temp) - mocoeffs = mocoeffs[0:(mo+len(temp)), :] # In case some aren't present - if self.betamocoeffs: - self.mocoeffs.append(mocoeffs) - else: - self.mocoeffs = [mocoeffs] - - if line[7:12] == "irrep": - ########## eigenvalues ########### - # This section appears once at the start of a geo-opt and once at the end - # unless IPRINT SCF is used (when it appears at every step in addition) - if not hasattr(self, "moenergies"): - self.moenergies = [] - - equals = inputfile.next() - while equals[1:5] != "====": # May be one or two lines of title (compare duhf_1.out and mg10.out) - equals = inputfile.next() - - moenergies = [] - line = inputfile.next() - if not line.strip(): # May be a blank line here (compare duhf_1.out and mg10.out) - line = inputfile.next() - - while line.strip() and line != equals: # May end with a blank or equals - temp = line.strip().split() - moenergies.append(utils.convertor(float(temp[2]), "hartree", "eV")) - line = inputfile.next() - self.nmo = len(moenergies) - if self.betamoenergies: - self.moenergies.append(moenergies) - self.betamoenergies = False - elif self.scftype=='gvb': - self.moenergies = [moenergies, moenergies] - else: - self.moenergies = [moenergies] - - -if __name__ == "__main__": - import doctest - doctest.testmod() +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 861 $" + + +import re + +import numpy + +import logfileparser +import utils + + +class GAMESSUK(logfileparser.Logfile): + """A GAMESS UK log file""" + SCFRMS, SCFMAX, SCFENERGY = range(3) # Used to index self.scftargets[] + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(GAMESSUK, self).__init__(logname="GAMESSUK", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "GAMESS UK log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'GAMESSUK("%s")' % (self.filename) + + def normalisesym(self, label): + """Use standard symmetry labels instead of GAMESS UK labels. + + >>> t = GAMESSUK("dummyfile.txt") + >>> labels = ['a', 'a1', 'ag', "a'", 'a"', "a''", "a1''", 'a1"'] + >>> labels.extend(["e1+", "e1-"]) + >>> answer = [t.normalisesym(x) for x in labels] + >>> answer + ['A', 'A1', 'Ag', "A'", 'A"', 'A"', 'A1"', 'A1"', 'E1', 'E1'] + """ + label = label.replace("''", '"').replace("+", "").replace("-", "") + ans = label[0].upper() + label[1:] + + return ans + + def before_parsing(self): + + # This will be used to detect the first set of "nuclear coordinates" in + # a geometry-optimization + self.firstnuccoords = True + + # used for determining whether to add a second mosyms, etc. + self.betamosyms = self.betamoenergies = self.betamocoeffs = False + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + if line[1:22] == "total number of atoms": + if not hasattr(self, "natom"): + self.natom = int(line.split()[-1]) + + if line[3:44] == "convergence threshold in optimization run": + # Assuming that this is only found in the case of OPTXYZ + # (i.e. an optimization in Cartesian coordinates) + self.geotargets = [float(line.split()[-2])] + + if line[32:61] == "largest component of gradient": + # This is the geotarget in the case of OPTXYZ + if not hasattr(self, "geovalues"): + self.geovalues = [] + self.geovalues.append([float(line.split()[4])]) + + if line[37:49] == "convergence?": + # Get the geovalues and geotargets for OPTIMIZE + if not hasattr(self, "geovalues"): + self.geovalues = [] + self.geotargets = [] + geotargets = [] + geovalues = [] + for i in range(4): + temp = line.split() + geovalues.append(float(temp[2])) + if not self.geotargets: + geotargets.append(float(temp[-2])) + line = inputfile.next() + self.geovalues.append(geovalues) + if not self.geotargets: + self.geotargets = geotargets + + if line[40:58] == "molecular geometry": + # Only one set of atomcoords is taken from this section + # For geo-opts, more coordinates are taken from the "nuclear coordinates" + if not hasattr(self, "atomcoords"): + self.atomcoords = [] + self.atomnos = [] + + stop = " "*9 + "*"*79 + line = inputfile.next() + while not line.startswith(stop): + line = inputfile.next() + line = inputfile.next() + while not line.startswith(stop): + line = inputfile.next() + empty = inputfile.next() + + atomcoords = [] + empty = inputfile.next() + while not empty.startswith(stop): + line = inputfile.next().split() # the coordinate data + atomcoords.append(map(float,line[3:6])) + self.atomnos.append(int(round(float(line[2])))) + while line!=empty: + line = inputfile.next() + # at this point, line is an empty line, right after + # 1 or more lines containing basis set information + empty = inputfile.next() + # empty is either a row of asterisks or the empty line + # before the row of coordinate data + + self.atomcoords.append(atomcoords) + self.atomnos = numpy.array(self.atomnos, "i") + + if line[40:59] == "nuclear coordinates": + # We need not remember the first geometry in the geo-opt as this will + # be recorded already, in the "molecular geometry" section + # (note: single-point calculations have no "nuclear coordinates" only + # "molecular geometry") + if self.firstnuccoords: + self.firstnuccoords = False + return + # This was continue (in loop) before parser refactoring. + # continue + if not hasattr(self, "atomcoords"): + self.atomcoords = [] + self.atomnos = [] + + asterisk = inputfile.next() + blank = inputfile.next() + colmname = inputfile.next() + equals = inputfile.next() + + atomcoords = [] + atomnos = [] + line = inputfile.next() + while line != equals: + temp = line.strip().split() + atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[0:3]]) + if not hasattr(self, "atomnos") or len(self.atomnos) == 0: + atomnos.append(int(float(temp[3]))) + + line = inputfile.next() + + self.atomcoords.append(atomcoords) + if not hasattr(self, "atomnos") or len(self.atomnos) == 0: + self.atomnos = atomnos + + if line[1:32] == "total number of basis functions": + self.nbasis = int(line.split()[-1]) + while line.find("charge of molecule")<0: + line = inputfile.next() + self.charge = int(line.split()[-1]) + self.mult = int(inputfile.next().split()[-1]) + + alpha = int(inputfile.next().split()[-1])-1 + beta = int(inputfile.next().split()[-1])-1 + if self.mult==1: + self.homos = numpy.array([alpha], "i") + else: + self.homos = numpy.array([alpha,beta], "i") + + if line[37:69] == "s-matrix over gaussian basis set": + self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") + + minus = inputfile.next() + blank = inputfile.next() + i = 0 + while i < self.nbasis: + blank = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + + for j in range(self.nbasis): + temp = map(float, inputfile.next().split()[1:]) + self.aooverlaps[j,(0+i):(len(temp)+i)] = temp + + i += len(temp) + + if line[18:43] == 'EFFECTIVE CORE POTENTIALS': + self.coreelectrons = numpy.zeros(self.natom, 'i') + asterisk = inputfile.next() + line = inputfile.next() + while line[15:46]!="*"*31: + if line.find("for atoms ...")>=0: + atomindex = [] + line = inputfile.next() + while line.find("core charge")<0: + broken = line.split() + atomindex.extend([int(x.split("-")[0]) for x in broken]) + line = inputfile.next() + charge = float(line.split()[4]) + for idx in atomindex: + self.coreelectrons[idx-1] = self.atomnos[idx-1] - charge + line = inputfile.next() + + if line[3:27] == "Wavefunction convergence": + self.scftarget = float(line.split()[-2]) + self.scftargets = [] + + if line[11:22] == "normal mode": + if not hasattr(self, "vibfreqs"): + self.vibfreqs = [] + self.vibirs = [] + + units = inputfile.next() + xyz = inputfile.next() + equals = inputfile.next() + line = inputfile.next() + while line!=equals: + temp = line.split() + self.vibfreqs.append(float(temp[1])) + self.vibirs.append(float(temp[-2])) + line = inputfile.next() + # Use the length of the vibdisps to figure out + # how many rotations and translations to remove + self.vibfreqs = self.vibfreqs[-len(self.vibdisps):] + self.vibirs = self.vibirs[-len(self.vibdisps):] + + if line[44:73] == "normalised normal coordinates": + self.vibdisps = [] + equals = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + freqnum = inputfile.next() + while freqnum.find("=")<0: + blank = inputfile.next() + equals = inputfile.next() + freqs = inputfile.next() + equals = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + equals = inputfile.next() + p = [ [] for x in range(9) ] + for i in range(len(self.atomnos)): + brokenx = map(float, inputfile.next()[25:].split()) + brokeny = map(float, inputfile.next()[25:].split()) + brokenz = map(float, inputfile.next()[25:].split()) + for j,x in enumerate(zip(brokenx, brokeny, brokenz)): + p[j].append(x) + self.vibdisps.extend(p) + + blank = inputfile.next() + blank = inputfile.next() + freqnum = inputfile.next() + + if line[26:36] == "raman data": + self.vibramans = [] + + stars = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + + blank = inputfile.next() + line = inputfile.next() + while line[1]!="*": + self.vibramans.append(float(line.split()[3])) + blank = inputfile.next() + line = inputfile.next() + # Use the length of the vibdisps to figure out + # how many rotations and translations to remove + self.vibramans = self.vibramans[-len(self.vibdisps):] + + if line[3:11] == "SCF TYPE": + self.scftype = line.split()[-2] + assert self.scftype in ['rhf', 'uhf', 'gvb'], "%s not one of 'rhf', 'uhf' or 'gvb'" % self.scftype + + if line[15:31] == "convergence data": + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + self.scftargets.append([self.scftarget]) # Assuming it does not change over time + while line[1:10] != "="*9: + line = inputfile.next() + line = inputfile.next() + tester = line.find("tester") # Can be in a different place depending + assert tester>=0 + while line[1:10] != "="*9: # May be two or three lines (unres) + line = inputfile.next() + + scfvalues = [] + line = inputfile.next() + while line.strip(): + if line[2:6]!="****": + # e.g. **** recalulation of fock matrix on iteration 4 (examples/chap12/pyridine.out) + scfvalues.append([float(line[tester-5:tester+6])]) + line = inputfile.next() + self.scfvalues.append(scfvalues) + + if line[10:22] == "total energy" and len(line.split()) == 3: + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + scfenergy = utils.convertor(float(line.split()[-1]), "hartree", "eV") + self.scfenergies.append(scfenergy) + + # Total energies after Moller-Plesset corrections + # Second order correction is always first, so its first occurance + # triggers creation of mpenergies (list of lists of energies) + # Further corrections are appended as found + # Note: GAMESS-UK sometimes prints only the corrections, + # so they must be added to the last value of scfenergies + if line[10:32] == "mp2 correlation energy" or \ + line[10:42] == "second order perturbation energy": + if not hasattr(self, "mpenergies"): + self.mpenergies = [] + self.mpenergies.append([]) + self.mp2correction = self.float(line.split()[-1]) + self.mp2energy = self.scfenergies[-1] + self.mp2correction + self.mpenergies[-1].append(utils.convertor(self.mp2energy, "hartree", "eV")) + if line[10:41] == "third order perturbation energy": + self.mp3correction = self.float(line.split()[-1]) + self.mp3energy = self.mp2energy + self.mp3correction + self.mpenergies[-1].append(utils.convertor(self.mp3energy, "hartree", "eV")) + + if line[40:59] == "molecular basis set": + self.gbasis = [] + line = inputfile.next() + while line.find("contraction coefficients")<0: + line = inputfile.next() + equals = inputfile.next() + blank = inputfile.next() + atomname = inputfile.next() + basisregexp = re.compile("\d*(\D+)") # Get everything after any digits + shellcounter = 1 + while line!=equals: + gbasis = [] # Stores basis sets on one atom + blank = inputfile.next() + blank = inputfile.next() + line = inputfile.next() + shellno = int(line.split()[0]) + shellgap = shellno - shellcounter + shellsize = 0 + while len(line.split())!=1 and line!=equals: + if line.split(): + shellsize += 1 + coeff = {} + # coefficients and symmetries for a block of rows + while line.strip() and line!=equals: + temp = line.strip().split() + # temp[1] may be either like (a) "1s" and "1sp", or (b) "s" and "sp" + # See GAMESS-UK 7.0 distribution/examples/chap12/pyridine2_21m10r.out + # for an example of the latter + sym = basisregexp.match(temp[1]).groups()[0] + assert sym in ['s', 'p', 'd', 'f', 'sp'], "'%s' not a recognized symmetry" % sym + if sym == "sp": + coeff.setdefault("S", []).append( (float(temp[3]), float(temp[6])) ) + coeff.setdefault("P", []).append( (float(temp[3]), float(temp[10])) ) + else: + coeff.setdefault(sym.upper(), []).append( (float(temp[3]), float(temp[6])) ) + line = inputfile.next() + # either a blank or a continuation of the block + if coeff: + if sym == "sp": + gbasis.append( ('S', coeff['S'])) + gbasis.append( ('P', coeff['P'])) + else: + gbasis.append( (sym.upper(), coeff[sym.upper()])) + if line==equals: + continue + line = inputfile.next() + # either the start of the next block or the start of a new atom or + # the end of the basis function section (signified by a line of equals) + numtoadd = 1 + (shellgap / shellsize) + shellcounter = shellno + shellsize + for x in range(numtoadd): + self.gbasis.append(gbasis) + + if line[50:70] == "----- beta set -----": + self.betamosyms = True + self.betamoenergies = True + self.betamocoeffs = True + # betamosyms will be turned off in the next + # SYMMETRY ASSIGNMENT section + + if line[31:50] == "SYMMETRY ASSIGNMENT": + if not hasattr(self, "mosyms"): + self.mosyms = [] + + multiple = {'a':1, 'b':1, 'e':2, 't':3, 'g':4, 'h':5} + + equals = inputfile.next() + line = inputfile.next() + while line != equals: # There may be one or two lines of title (compare mg10.out and duhf_1.out) + line = inputfile.next() + + mosyms = [] + line = inputfile.next() + while line != equals: + temp = line[25:30].strip() + if temp[-1]=='?': + # e.g. e? or t? or g? (see example/chap12/na7mg_uhf.out) + # for two As, an A and an E, and two Es of the same energy respectively. + t = line[91:].strip().split() + for i in range(1,len(t),2): + for j in range(multiple[t[i][0]]): # add twice for 'e', etc. + mosyms.append(self.normalisesym(t[i])) + else: + for j in range(multiple[temp[0]]): + mosyms.append(self.normalisesym(temp)) # add twice for 'e', etc. + line = inputfile.next() + assert len(mosyms) == self.nmo, "mosyms: %d but nmo: %d" % (len(mosyms), self.nmo) + if self.betamosyms: + # Only append if beta (otherwise with IPRINT SCF + # it will add mosyms for every step of a geo opt) + self.mosyms.append(mosyms) + self.betamosyms = False + elif self.scftype=='gvb': + # gvb has alpha and beta orbitals but they are identical + self.mosysms = [mosyms, mosyms] + else: + self.mosyms = [mosyms] + + if line[50:62] == "eigenvectors": + # Mocoeffs...can get evalues from here too + # (only if using FORMAT HIGH though will they all be present) + if not hasattr(self, "mocoeffs"): + self.aonames = [] + aonames = [] + minus = inputfile.next() + + mocoeffs = numpy.zeros( (self.nmo, self.nbasis), "d") + readatombasis = False + if not hasattr(self, "atombasis"): + self.atombasis = [] + for i in range(self.natom): + self.atombasis.append([]) + readatombasis = True + + blank = inputfile.next() + blank = inputfile.next() + evalues = inputfile.next() + + p = re.compile(r"\d+\s+(\d+)\s*(\w+) (\w+)") + oldatomname = "DUMMY VALUE" + + mo = 0 + while mo < self.nmo: + blank = inputfile.next() + blank = inputfile.next() + nums = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + for basis in range(self.nbasis): + line = inputfile.next() + # Fill atombasis only first time around. + if readatombasis: + orbno = int(line[1:5])-1 + atomno = int(line[6:9])-1 + self.atombasis[atomno].append(orbno) + if not self.aonames: + pg = p.match(line[:18].strip()).groups() + atomname = "%s%s%s" % (pg[1][0].upper(), pg[1][1:], pg[0]) + if atomname!=oldatomname: + aonum = 1 + oldatomname = atomname + name = "%s_%d%s" % (atomname, aonum, pg[2].upper()) + if name in aonames: + aonum += 1 + name = "%s_%d%s" % (atomname, aonum, pg[2].upper()) + aonames.append(name) + temp = map(float, line[19:].split()) + mocoeffs[mo:(mo+len(temp)), basis] = temp + # Fill atombasis only first time around. + readatombasis = False + if not self.aonames: + self.aonames = aonames + + line = inputfile.next() # blank line + while line==blank: + line = inputfile.next() + evalues = line + if evalues[:17].strip(): # i.e. if these aren't evalues + break # Not all the MOs are present + mo += len(temp) + mocoeffs = mocoeffs[0:(mo+len(temp)), :] # In case some aren't present + if self.betamocoeffs: + self.mocoeffs.append(mocoeffs) + else: + self.mocoeffs = [mocoeffs] + + if line[7:12] == "irrep": + ########## eigenvalues ########### + # This section appears once at the start of a geo-opt and once at the end + # unless IPRINT SCF is used (when it appears at every step in addition) + if not hasattr(self, "moenergies"): + self.moenergies = [] + + equals = inputfile.next() + while equals[1:5] != "====": # May be one or two lines of title (compare duhf_1.out and mg10.out) + equals = inputfile.next() + + moenergies = [] + line = inputfile.next() + if not line.strip(): # May be a blank line here (compare duhf_1.out and mg10.out) + line = inputfile.next() + + while line.strip() and line != equals: # May end with a blank or equals + temp = line.strip().split() + moenergies.append(utils.convertor(float(temp[2]), "hartree", "eV")) + line = inputfile.next() + self.nmo = len(moenergies) + if self.betamoenergies: + self.moenergies.append(moenergies) + self.betamoenergies = False + elif self.scftype=='gvb': + self.moenergies = [moenergies, moenergies] + else: + self.moenergies = [moenergies] + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/external/cclib/parser/gaussianparser.py b/external/cclib/parser/gaussianparser.py index 0cde81b6b1..834c5fe28f 100644 --- a/external/cclib/parser/gaussianparser.py +++ b/external/cclib/parser/gaussianparser.py @@ -1,1026 +1,1026 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -gmagoon 4/5/10-4/6/10 (this notice added 4/29/10): Gregory Magoon modified this file from cclib 1.0 -""" - -__revision__ = "$Revision: 882 $" - - -import re - -import numpy - -import logfileparser -import utils - - -class Gaussian(logfileparser.Logfile): - """A Gaussian 98/03 log file.""" - - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(Gaussian, self).__init__(logname="Gaussian", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "Gaussian log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'Gaussian("%s")' % (self.filename) - - def normalisesym(self, label): - """Use standard symmetry labels instead of Gaussian labels. - - To normalise: - (1) If label is one of [SG, PI, PHI, DLTA], replace by [sigma, pi, phi, delta] - (2) replace any G or U by their lowercase equivalent - - >>> sym = Gaussian("dummyfile").normalisesym - >>> labels = ['A1', 'AG', 'A1G', "SG", "PI", "PHI", "DLTA", 'DLTU', 'SGG'] - >>> map(sym, labels) - ['A1', 'Ag', 'A1g', 'sigma', 'pi', 'phi', 'delta', 'delta.u', 'sigma.g'] - """ - # note: DLT must come after DLTA - greek = [('SG', 'sigma'), ('PI', 'pi'), ('PHI', 'phi'), - ('DLTA', 'delta'), ('DLT', 'delta')] - for k,v in greek: - if label.startswith(k): - tmp = label[len(k):] - label = v - if tmp: - label = v + "." + tmp - - ans = label.replace("U", "u").replace("G", "g") - return ans - - def before_parsing(self): - - # Used to index self.scftargets[]. - SCFRMS, SCFMAX, SCFENERGY = range(3) - - # Flag that indicates whether it has reached the end of a geoopt. - self.optfinished = False - - # Flag for identifying Coupled Cluster runs. - self.coupledcluster = False - - # Fragment number for counterpoise calculations (normally zero). - self.counterpoise = 0 - - # Flag for identifying ONIOM calculations. - self.oniom = False - - def after_parsing(self): - - # Correct the percent values in the etsecs in the case of - # a restricted calculation. The following has the - # effect of including each transition twice. - if hasattr(self, "etsecs") and len(self.homos) == 1: - new_etsecs = [[(x[0], x[1], x[2] * numpy.sqrt(2)) for x in etsec] - for etsec in self.etsecs] - self.etsecs = new_etsecs - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - # Number of atoms. - if line[1:8] == "NAtoms=": - - self.updateprogress(inputfile, "Attributes", self.fupdate) - - natom = int(line.split()[1]) - if not hasattr(self, "natom"): - self.natom = natom - - # Catch message about completed optimization. - if line[1:23] == "Optimization completed": - self.optfinished = True - - # Extract the atomic numbers and coordinates from the input orientation, - # in the event the standard orientation isn't available. - if not self.optfinished and line.find("Input orientation") > -1 or line.find("Z-Matrix orientation") > -1: - - # If this is a counterpoise calculation, this output means that - # the supermolecule is now being considered, so we can set: - self.counterpoise = 0 - - self.updateprogress(inputfile, "Attributes", self.cupdate) - - if not hasattr(self, "inputcoords"): - self.inputcoords = [] - self.inputatoms = [] - - hyphens = inputfile.next() - colmNames = inputfile.next() - colmNames = inputfile.next() - hyphens = inputfile.next() - - atomcoords = [] - line = inputfile.next() - while line != hyphens: - broken = line.split() - self.inputatoms.append(int(broken[1])) - atomcoords.append(map(float, broken[3:6])) - line = inputfile.next() - - self.inputcoords.append(atomcoords) - - if not hasattr(self, "natom"): - self.atomnos = numpy.array(self.inputatoms, 'i') - self.natom = len(self.atomnos) - - # Extract the atomic numbers and coordinates of the atoms. - if not self.optfinished and line.strip() == "Standard orientation:": - - self.updateprogress(inputfile, "Attributes", self.cupdate) - - # If this is a counterpoise calculation, this output means that - # the supermolecule is now being considered, so we can set: - self.counterpoise = 0 - - if not hasattr(self, "atomcoords"): - self.atomcoords = [] - - hyphens = inputfile.next() - colmNames = inputfile.next() - colmNames = inputfile.next() - hyphens = inputfile.next() - - atomnos = [] - atomcoords = [] - line = inputfile.next() - while line != hyphens: - broken = line.split() - atomnos.append(int(broken[1])) - atomcoords.append(map(float, broken[-3:])) - line = inputfile.next() - self.atomcoords.append(atomcoords) - if not hasattr(self, "natom"): - self.atomnos = numpy.array(atomnos, 'i') - self.natom = len(self.atomnos) - - # Find the targets for SCF convergence (QM calcs). - if line[1:44] == 'Requested convergence on RMS density matrix': - - if not hasattr(self, "scftargets"): - self.scftargets = [] - - scftargets = [] - # The RMS density matrix. - scftargets.append(self.float(line.split('=')[1].split()[0])) - line = inputfile.next() - # The MAX density matrix. - scftargets.append(self.float(line.strip().split('=')[1][:-1])) - line = inputfile.next() - # For G03, there's also the energy (not for G98). - if line[1:10] == "Requested": - scftargets.append(self.float(line.strip().split('=')[1][:-1])) - - self.scftargets.append(scftargets) - - # Extract SCF convergence information (QM calcs). - if line[1:10] == 'Cycle 1': - - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - - scfvalues = [] - line = inputfile.next() - while line.find("SCF Done") == -1: - - self.updateprogress(inputfile, "QM convergence", self.fupdate) - - if line.find(' E=') == 0: - self.logger.debug(line) - - # RMSDP=3.74D-06 MaxDP=7.27D-05 DE=-1.73D-07 OVMax= 3.67D-05 - # or - # RMSDP=1.13D-05 MaxDP=1.08D-04 OVMax= 1.66D-04 - if line.find(" RMSDP") == 0: - - parts = line.split() - newlist = [self.float(x.split('=')[1]) for x in parts[0:2]] - energy = 1.0 - if len(parts) > 4: - energy = parts[2].split('=')[1] - if energy == "": - energy = self.float(parts[3]) - else: - energy = self.float(energy) - if len(self.scftargets[0]) == 3: # Only add the energy if it's a target criteria - newlist.append(energy) - scfvalues.append(newlist) - - try: - line = inputfile.next() - # May be interupted by EOF. - except StopIteration: - break - - self.scfvalues.append(scfvalues) - - # Extract SCF convergence information (AM1 calcs). - if line[1:4] == 'It=': - - self.scftargets = numpy.array([1E-7], "d") # This is the target value for the rms - self.scfvalues = [[]] - - line = inputfile.next() - while line.find(" Energy") == -1: - - if self.progress: - step = inputfile.tell() - if step != oldstep: - self.progress.update(step, "AM1 Convergence") - oldstep = step - - if line[1:4] == "It=": - parts = line.strip().split() - self.scfvalues[0].append(self.float(parts[-1][:-1])) - line = inputfile.next() - - # Note: this needs to follow the section where 'SCF Done' is used - # to terminate a loop when extracting SCF convergence information. - if line[1:9] == 'SCF Done': - - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - - self.scfenergies.append(utils.convertor(self.float(line.split()[4]), "hartree", "eV")) - #gmagoon 5/27/09: added scfenergies reading for PM3 case where line begins with Energy= - #example line: " Energy= -0.077520562724 NIter= 14." - if line[1:8] == 'Energy=': - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - self.scfenergies.append(utils.convertor(self.float(line.split()[1]), "hartree", "eV")) - #gmagoon 6/8/09: added molecular mass parsing (units will be amu) - #example line: " Molecular mass: 208.11309 amu." - if line[1:16] == 'Molecular mass:': - self.molmass = self.float(line.split()[2]) - - #gmagoon 5/27/09: added rotsymm for reading rotational symmetry number - #it would probably be better to read in point group (or calculate separately with OpenBabel, and I probably won't end up using this - #example line: " Rotational symmetry number 1." - if line[1:27] == 'Rotational symmetry number': - self.rotsymm = int(self.float(line.split()[3])) - - #gmagoon 5/28/09: added rotcons for rotational constants (at each step) in GHZ - #example line: Rotational constants (GHZ): 17.0009421 5.8016756 4.5717439 - #could also read in moment of inertia, but this should just differ by a constant: rot cons= h/(8*Pi^2*I) - #note that the last occurence of this in the thermochemistry section has reduced precision, so we will want to use the 2nd to last instance - if line[1:28] == 'Rotational constants (GHZ):': - if not hasattr(self, "rotcons"): - self.rotcons = [] - - #some linear cases (e.g. if linearity is not recognized) can have asterisks ****... for the first rotational constant; e.g.: - # Rotational constants (GHZ): ************ 12.73690 12.73690 - # or: - # Rotational constants (GHZ):*************** 10.4988228 10.4988223 - # if this is the case, replace the asterisks with a 0.0 - #we can also have cases like this: - # Rotational constants (GHZ):6983905.3278703 11.8051382 11.8051183 - #if line[28:29] == '*' or line.split()[3].startswith('*'): - if line[37:38] == '*': - self.rotcons.append([0.0]+map(float, line[28:].split()[-2:])) #record last 0.0 and last 2 numbers (words) in the string following the prefix - else: - self.rotcons.append(map(float, line[28:].split()[-3:])) #record last 3 numbers (words) in the string following the prefix - - # Total energies after Moller-Plesset corrections. - # Second order correction is always first, so its first occurance - # triggers creation of mpenergies (list of lists of energies). - # Further MP2 corrections are appended as found. - # - # Example MP2 output line: - # E2 = -0.9505918144D+00 EUMP2 = -0.28670924198852D+03 - # Warning! this output line is subtly different for MP3/4/5 runs - if "EUMP2" in line[27:34]: - - if not hasattr(self, "mpenergies"): - self.mpenergies = [] - self.mpenergies.append([]) - mp2energy = self.float(line.split("=")[2]) - self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) - - # Example MP3 output line: - # E3= -0.10518801D-01 EUMP3= -0.75012800924D+02 - if line[34:39] == "EUMP3": - - mp3energy = self.float(line.split("=")[2]) - self.mpenergies[-1].append(utils.convertor(mp3energy, "hartree", "eV")) - - # Example MP4 output lines: - # E4(DQ)= -0.31002157D-02 UMP4(DQ)= -0.75015901139D+02 - # E4(SDQ)= -0.32127241D-02 UMP4(SDQ)= -0.75016013648D+02 - # E4(SDTQ)= -0.32671209D-02 UMP4(SDTQ)= -0.75016068045D+02 - # Energy for most substitutions is used only (SDTQ by default) - if line[34:42] == "UMP4(DQ)": - - mp4energy = self.float(line.split("=")[2]) - line = inputfile.next() - if line[34:43] == "UMP4(SDQ)": - mp4energy = self.float(line.split("=")[2]) - line = inputfile.next() - if line[34:44] == "UMP4(SDTQ)": - mp4energy = self.float(line.split("=")[2]) - self.mpenergies[-1].append(utils.convertor(mp4energy, "hartree", "eV")) - - # Example MP5 output line: - # DEMP5 = -0.11048812312D-02 MP5 = -0.75017172926D+02 - if line[29:32] == "MP5": - mp5energy = self.float(line.split("=")[2]) - self.mpenergies[-1].append(utils.convertor(mp5energy, "hartree", "eV")) - - # Total energies after Coupled Cluster corrections. - # Second order MBPT energies (MP2) are also calculated for these runs, - # but the output is the same as when parsing for mpenergies. - # First turn on flag for Coupled Cluster runs. - if line[1:23] == "Coupled Cluster theory" or line[1:8] == "CCSD(T)": - - self.coupledcluster = True - if not hasattr(self, "ccenergies"): - self.ccenergies = [] - - # Now read the consecutive correlated energies when , - # but append only the last one to ccenergies. - # Only the highest level energy is appended - ex. CCSD(T), not CCSD. - if self.coupledcluster and line[27:35] == "E(CORR)=": - self.ccenergy = self.float(line.split()[3]) - if self.coupledcluster and line[1:9] == "CCSD(T)=": - self.ccenergy = self.float(line.split()[1]) - # Append when leaving link 913 - if self.coupledcluster and line[1:16] == "Leave Link 913": - self.ccenergies.append(utils.convertor(self.ccenergy, "hartree", "eV")) - - # Geometry convergence information. - if line[49:59] == 'Converged?': - - if not hasattr(self, "geotargets"): - self.geovalues = [] - self.geotargets = numpy.array([0.0, 0.0, 0.0, 0.0], "d") - - newlist = [0]*4 - for i in range(4): - line = inputfile.next() - self.logger.debug(line) - parts = line.split() - try: - value = self.float(parts[2]) - except ValueError: - value = -1.0 - #self.logger.error("Problem parsing the value for geometry optimisation: %s is not a number." % parts[2]) - #gmagoon 20111202: because the value can become **** (as shown below, I'm changing this to not report an error, and instead just set the value to -1.0 -# Item Value Threshold Converged? -# Maximum Force ******** 0.000015 NO -# RMS Force 1.813626 0.000010 NO -# Maximum Displacement 0.915407 0.000060 NO -# RMS Displacement 0.280831 0.000040 NO - else: - newlist[i] = value - self.geotargets[i] = self.float(parts[3]) - - self.geovalues.append(newlist) - - # Gradients. - # Read in the cartesian energy gradients (forces) from a block like this: - # ------------------------------------------------------------------- - # Center Atomic Forces (Hartrees/Bohr) - # Number Number X Y Z - # ------------------------------------------------------------------- - # 1 1 -0.012534744 -0.021754635 -0.008346094 - # 2 6 0.018984731 0.032948887 -0.038003451 - # 3 1 -0.002133484 -0.006226040 0.023174772 - # 4 1 -0.004316502 -0.004968213 0.023174772 - # -2 -0.001830728 -0.000743108 -0.000196625 - # ------------------------------------------------------------------ - # - # The "-2" line is for a dummy atom - # - # Then optimization is done in internal coordinates, Gaussian also - # print the forces in internal coordinates, which can be produced from - # the above. This block looks like this: - # Variable Old X -DE/DX Delta X Delta X Delta X New X - # (Linear) (Quad) (Total) - # ch 2.05980 0.01260 0.00000 0.01134 0.01134 2.07114 - # hch 1.75406 0.09547 0.00000 0.24861 0.24861 2.00267 - # hchh 2.09614 0.01261 0.00000 0.16875 0.16875 2.26489 - # Item Value Threshold Converged? - if line[37:43] == "Forces": - - if not hasattr(self, "grads"): - self.grads = [] - - header = inputfile.next() - dashes = inputfile.next() - line = inputfile.next() - forces = [] - while line != dashes: - broken = line.split() - Fx, Fy, Fz = broken[-3:] - forces.append([float(Fx),float(Fy),float(Fz)]) - line = inputfile.next() - self.grads.append(forces) - - # Charge and multiplicity. - # If counterpoise correction is used, multiple lines match. - # The first one contains charge/multiplicity of the whole molecule.: - # Charge = 0 Multiplicity = 1 in supermolecule - # Charge = 0 Multiplicity = 1 in fragment 1. - # Charge = 0 Multiplicity = 1 in fragment 2. - if line[1:7] == 'Charge' and line.find("Multiplicity")>=0: - - regex = ".*=(.*)Mul.*=\s*(\d+).*" - match = re.match(regex, line) - assert match, "Something unusual about the line: '%s'" % line - - self.charge = int(match.groups()[0]) - self.mult = int(match.groups()[1]) - - # Orbital symmetries. - if line[1:20] == 'Orbital symmetries:' and not hasattr(self, "mosyms"): - - # For counterpoise fragments, skip these lines. - if self.counterpoise != 0: return - - self.updateprogress(inputfile, "MO Symmetries", self.fupdate) - - self.mosyms = [[]] - line = inputfile.next() - unres = False - if line.find("Alpha Orbitals") == 1: - unres = True - line = inputfile.next() - i = 0 - while len(line) > 18 and line[17] == '(': - if line.find('Virtual') >= 0: - self.homos = numpy.array([i-1], "i") # 'HOMO' indexes the HOMO in the arrays - parts = line[17:].split() - for x in parts: - self.mosyms[0].append(self.normalisesym(x.strip('()'))) - i += 1 - line = inputfile.next() - if unres: - line = inputfile.next() - # Repeat with beta orbital information - i = 0 - self.mosyms.append([]) - while len(line) > 18 and line[17] == '(': - if line.find('Virtual')>=0: - if (hasattr(self, "homos")):#if there was also an alpha virtual orbital (here we consider beta) we will store two indices in the array - self.homos.resize([2]) # Extend the array to two elements - self.homos[1] = i-1 # 'HOMO' indexes the HOMO in the arrays - else:#otherwise (e.g. for O triplet) there is no alpha virtual orbital, only beta virtual orbitals, and we initialize the array with one element - self.homos = numpy.array([i-1], "i") # 'HOMO' indexes the HOMO in the arrays - parts = line[17:].split() - for x in parts: - self.mosyms[1].append(self.normalisesym(x.strip('()'))) - i += 1 - line = inputfile.next() - - # Alpha/Beta electron eigenvalues. - if line[1:6] == "Alpha" and line.find("eigenvalues") >= 0: - - # For counterpoise fragments, skip these lines. - if self.counterpoise != 0: return - - # For ONIOM calcs, ignore this section in order to bypass assertion failure. - if self.oniom: return - - self.updateprogress(inputfile, "Eigenvalues", self.fupdate) - self.moenergies = [[]] - HOMO = -2 - - while line.find('Alpha') == 1: - if line.split()[1] == "virt." and HOMO == -2: - - # If there aren't any symmetries, this is a good way to find the HOMO. - # Also, check for consistency if homos was already parsed. - HOMO = len(self.moenergies[0])-1 - if hasattr(self, "homos"): - assert HOMO == self.homos[0] - else: - self.homos = numpy.array([HOMO], "i") - - part = line[28:] - i = 0 - while i*10+4 < len(part): - x = part[i*10:(i+1)*10] - self.moenergies[0].append(utils.convertor(self.float(x), "hartree", "eV")) - i += 1 - line = inputfile.next() - # If, at this point, self.homos is unset, then there were not - # any alpha virtual orbitals - if not hasattr(self, "homos"): - HOMO = len(self.moenergies[0])-1 - self.homos = numpy.array([HOMO], "i") - - - if line.find('Beta') == 2: - self.moenergies.append([]) - - HOMO = -2 - while line.find('Beta') == 2: - if line.split()[1] == "virt." and HOMO == -2: - - # If there aren't any symmetries, this is a good way to find the HOMO. - # Also, check for consistency if homos was already parsed. - HOMO = len(self.moenergies[1])-1 - if len(self.homos) == 2: - assert HOMO == self.homos[1] - else: - self.homos.resize([2]) - self.homos[1] = HOMO - - part = line[28:] - i = 0 - while i*10+4 < len(part): - x = part[i*10:(i+1)*10] - self.moenergies[1].append(utils.convertor(self.float(x), "hartree", "eV")) - i += 1 - line = inputfile.next() - - self.moenergies = [numpy.array(x, "d") for x in self.moenergies] - - # Gaussian Rev <= B.0.3 (?) - # AO basis set in the form of general basis input: - # 1 0 - # S 3 1.00 0.000000000000 - # 0.7161683735D+02 0.1543289673D+00 - # 0.1304509632D+02 0.5353281423D+00 - # 0.3530512160D+01 0.4446345422D+00 - # SP 3 1.00 0.000000000000 - # 0.2941249355D+01 -0.9996722919D-01 0.1559162750D+00 - # 0.6834830964D+00 0.3995128261D+00 0.6076837186D+00 - # 0.2222899159D+00 0.7001154689D+00 0.3919573931D+00 - if line[1:16] == "AO basis set in": - - # For counterpoise fragment calcualtions, skip these lines. - if self.counterpoise != 0: return - - self.gbasis = [] - line = inputfile.next() - while line.strip(): - gbasis = [] - line = inputfile.next() - while line.find("*")<0: - temp = line.split() - symtype = temp[0] - numgau = int(temp[1]) - gau = [] - for i in range(numgau): - temp = map(self.float, inputfile.next().split()) - gau.append(temp) - - for i,x in enumerate(symtype): - newgau = [(z[0],z[i+1]) for z in gau] - gbasis.append( (x,newgau) ) - line = inputfile.next() # i.e. "****" or "SP ...." - self.gbasis.append(gbasis) - line = inputfile.next() # i.e. "20 0" or blank line - - # Start of the IR/Raman frequency section. - # Caution is advised here, as additional frequency blocks - # can be printed by Gaussian (with slightly different formats), - # often doubling the information printed. - # See, for a non-standard exmaple, regression Gaussian98/test_H2.log - if line[1:14] == "Harmonic freq": - - self.updateprogress(inputfile, "Frequency Information", self.fupdate) - - # The whole block should not have any blank lines. - while line.strip() != "": - - # Lines with symmetries and symm. indices begin with whitespace. - if line[1:15].strip() == "" and not line[15:22].strip().isdigit(): - - if not hasattr(self, 'vibsyms'): - self.vibsyms = [] - syms = line.split() - self.vibsyms.extend(syms) - - if line[1:15] == "Frequencies --": - - if not hasattr(self, 'vibfreqs'): - self.vibfreqs = [] - freqs = [self.float(f) for f in line[15:].split()] - self.vibfreqs.extend(freqs) - - if line[1:15] == "IR Inten --": - - if not hasattr(self, 'vibirs'): - self.vibirs = [] - irs = [self.float(f) for f in line[15:].split()] - self.vibirs.extend(irs) - - if line[1:15] == "Raman Activ --": - - if not hasattr(self, 'vibramans'): - self.vibramans = [] - ramans = [self.float(f) for f in line[15:].split()] - self.vibramans.extend(ramans) - - # Block with displacement should start with this. - # Remember, it is possible to have less than three columns! - # There should be as many lines as there are atoms. - if line[1:29] == "Atom AN X Y Z": - - if not hasattr(self, 'vibdisps'): - self.vibdisps = [] - disps = [] - for n in range(self.natom): - line = inputfile.next() - numbers = [float(s) for s in line[10:].split()] - N = len(numbers) / 3 - if not disps: - for n in range(N): - disps.append([]) - for n in range(N): - disps[n].append(numbers[3*n:3*n+3]) - self.vibdisps.extend(disps) - - line = inputfile.next() - -# Below is the old code for the IR/Raman frequency block, can probably be removed. -# while len(line[:15].split()) == 0: -# self.logger.debug(line) -# self.vibsyms.extend(line.split()) # Adding new symmetry -# line = inputfile.next() -# # Read in frequencies. -# freqs = [self.float(f) for f in line.split()[2:]] -# self.vibfreqs.extend(freqs) -# line = inputfile.next() -# line = inputfile.next() -# line = inputfile.next() -# irs = [self.float(f) for f in line.split()[3:]] -# self.vibirs.extend(irs) -# line = inputfile.next() # Either the header or a Raman line -# if line.find("Raman") >= 0: -# if not hasattr(self, "vibramans"): -# self.vibramans = [] -# ramans = [self.float(f) for f in line.split()[3:]] -# self.vibramans.extend(ramans) -# line = inputfile.next() # Depolar (P) -# line = inputfile.next() # Depolar (U) -# line = inputfile.next() # Header -# line = inputfile.next() # First line of cartesian displacement vectors -# p = [[], [], []] -# while len(line[:15].split()) > 0: -# # Store the cartesian displacement vectors -# broken = map(float, line.strip().split()[2:]) -# for i in range(0, len(broken), 3): -# p[i/3].append(broken[i:i+3]) -# line = inputfile.next() -# self.vibdisps.extend(p[0:len(broken)/3]) -# line = inputfile.next() # Should be the line with symmetries -# self.vibfreqs = numpy.array(self.vibfreqs, "d") -# self.vibirs = numpy.array(self.vibirs, "d") -# self.vibdisps = numpy.array(self.vibdisps, "d") -# if hasattr(self, "vibramans"): -# self.vibramans = numpy.array(self.vibramans, "d") - - # Electronic transitions. - if line[1:14] == "Excited State": - - if not hasattr(self, "etenergies"): - self.etenergies = [] - self.etoscs = [] - self.etsyms = [] - self.etsecs = [] - # Need to deal with lines like: - # (restricted calc) - # Excited State 1: Singlet-BU 5.3351 eV 232.39 nm f=0.1695 - # (unrestricted calc) (first excited state is 2!) - # Excited State 2: ?Spin -A 0.1222 eV 10148.75 nm f=0.0000 - # (Gaussian 09 ZINDO) - # Excited State 1: Singlet-?Sym 2.5938 eV 478.01 nm f=0.0000 =0.000 - p = re.compile(":(?P.*?)(?P-?\d*\.\d*) eV") - groups = p.search(line).groups() - self.etenergies.append(utils.convertor(self.float(groups[1]), "eV", "cm-1")) - self.etoscs.append(self.float(line.split("f=")[-1].split()[0])) - self.etsyms.append(groups[0].strip()) - - line = inputfile.next() - - p = re.compile("(\d+)") - CIScontrib = [] - while line.find(" ->") >= 0: # This is a contribution to the transition - parts = line.split("->") - self.logger.debug(parts) - # Has to deal with lines like: - # 32 -> 38 0.04990 - # 35A -> 45A 0.01921 - frommoindex = 0 # For restricted or alpha unrestricted - fromMO = parts[0].strip() - if fromMO[-1] == "B": - frommoindex = 1 # For beta unrestricted - fromMO = int(p.match(fromMO).group())-1 # subtract 1 so that it is an index into moenergies - - t = parts[1].split() - tomoindex = 0 - toMO = t[0] - if toMO[-1] == "B": - tomoindex = 1 - toMO = int(p.match(toMO).group())-1 # subtract 1 so that it is an index into moenergies - - percent = self.float(t[1]) - # For restricted calculations, the percentage will be corrected - # after parsing (see after_parsing() above). - CIScontrib.append([(fromMO, frommoindex), (toMO, tomoindex), percent]) - line = inputfile.next() - self.etsecs.append(CIScontrib) - -# Circular dichroism data (different for G03 vs G09) - -# G03 - -## <0|r|b> * (Au), Rotatory Strengths (R) in -## cgs (10**-40 erg-esu-cm/Gauss) -## state X Y Z R(length) -## 1 0.0006 0.0096 -0.0082 -0.4568 -## 2 0.0251 -0.0025 0.0002 -5.3846 -## 3 0.0168 0.4204 -0.3707 -15.6580 -## 4 0.0721 0.9196 -0.9775 -3.3553 - -# G09 - -## 1/2[<0|r|b>* + (<0|rxdel|b>*)*] -## Rotatory Strengths (R) in cgs (10**-40 erg-esu-cm/Gauss) -## state XX YY ZZ R(length) R(au) -## 1 -0.3893 -6.7546 5.7736 -0.4568 -0.0010 -## 2 -17.7437 1.7335 -0.1435 -5.3845 -0.0114 -## 3 -11.8655 -297.2604 262.1519 -15.6580 -0.0332 - - if (line[1:52] == "<0|r|b> * (Au), Rotatory Strengths (R)" or - line[1:50] == "1/2[<0|r|b>* + (<0|rxdel|b>*)*]"): - - self.etrotats = [] - inputfile.next() # Units - headers = inputfile.next() # Headers - Ncolms = len(headers.split()) - line = inputfile.next() - parts = line.strip().split() - while len(parts) == Ncolms: - try: - R = self.float(parts[4]) - except ValueError: - # nan or -nan if there is no first excited state - # (for unrestricted calculations) - pass - else: - self.etrotats.append(R) - line = inputfile.next() - temp = line.strip().split() - parts = line.strip().split() - self.etrotats = numpy.array(self.etrotats, "d") - - # Number of basis sets functions. - # Has to deal with lines like: - # NBasis = 434 NAE= 97 NBE= 97 NFC= 34 NFV= 0 - # and... - # NBasis = 148 MinDer = 0 MaxDer = 0 - # Although the former is in every file, it doesn't occur before - # the overlap matrix is printed. - if line[1:7] == "NBasis" or line[4:10] == "NBasis": - - # For counterpoise fragment, skip these lines. - if self.counterpoise != 0: return - - # For ONIOM calcs, ignore this section in order to bypass assertion failure. - if self.oniom: return - - # If nbasis was already parsed, check if it changed. - nbasis = int(line.split('=')[1].split()[0]) - if hasattr(self, "nbasis"): - assert nbasis == self.nbasis - else: - self.nbasis = nbasis - - # Number of linearly-independent basis sets. - if line[1:7] == "NBsUse": - - # For counterpoise fragment, skip these lines. - if self.counterpoise != 0: return - - # For ONIOM calcs, ignore this section in order to bypass assertion failure. - if self.oniom: return - - # If nmo was already parsed, check if it changed. - nmo = int(line.split('=')[1].split()[0]) - if hasattr(self, "nmo"): - assert nmo == self.nmo - else: - self.nmo = nmo - - # For AM1 calculations, set nbasis by a second method, - # as nmo may not always be explicitly stated. - if line[7:22] == "basis functions, ": - - nbasis = int(line.split()[0]) - if hasattr(self, "nbasis"): - assert nbasis == self.nbasis - else: - self.nbasis = nbasis - - # Molecular orbital overlap matrix. - # Has to deal with lines such as: - # *** Overlap *** - # ****** Overlap ****** - if line[1:4] == "***" and (line[5:12] == "Overlap" - or line[8:15] == "Overlap"): - - self.aooverlaps = numpy.zeros( (self.nbasis, self.nbasis), "d") - # Overlap integrals for basis fn#1 are in aooverlaps[0] - base = 0 - colmNames = inputfile.next() - while base < self.nbasis: - - self.updateprogress(inputfile, "Overlap", self.fupdate) - - for i in range(self.nbasis-base): # Fewer lines this time - line = inputfile.next() - parts = line.split() - for j in range(len(parts)-1): # Some lines are longer than others - k = float(parts[j+1].replace("D", "E")) - self.aooverlaps[base+j, i+base] = k - self.aooverlaps[i+base, base+j] = k - base += 5 - colmNames = inputfile.next() - self.aooverlaps = numpy.array(self.aooverlaps, "d") - - # Molecular orbital coefficients (mocoeffs). - # Essentially only produced for SCF calculations. - # This is also the place where aonames and atombasis are parsed. - if line[5:35] == "Molecular Orbital Coefficients" or line[5:41] == "Alpha Molecular Orbital Coefficients" or line[5:40] == "Beta Molecular Orbital Coefficients": - - if line[5:40] == "Beta Molecular Orbital Coefficients": - beta = True - if self.popregular: - return - # This was continue before refactoring the parsers. - #continue # Not going to extract mocoeffs - # Need to add an extra array to self.mocoeffs - self.mocoeffs.append(numpy.zeros((self.nmo, self.nbasis), "d")) - else: - beta = False - self.aonames = [] - self.atombasis = [] - mocoeffs = [numpy.zeros((self.nmo, self.nbasis), "d")] - - base = 0 - self.popregular = False - for base in range(0, self.nmo, 5): - - self.updateprogress(inputfile, "Coefficients", self.fupdate) - - colmNames = inputfile.next() - - if not colmNames.split(): - self.logger.warning("Molecular coefficients header found but no coefficients.") - break; - - if base==0 and int(colmNames.split()[0])!=1: - # Implies that this is a POP=REGULAR calculation - # and so, only aonames (not mocoeffs) will be extracted - self.popregular = True - symmetries = inputfile.next() - eigenvalues = inputfile.next() - for i in range(self.nbasis): - - line = inputfile.next() - if base == 0 and not beta: # Just do this the first time 'round - # Changed below from :12 to :11 to deal with Elmar Neumann's example - parts = line[:11].split() - if len(parts) > 1: # New atom - if i>0: - self.atombasis.append(atombasis) - atombasis = [] - atomname = "%s%s" % (parts[2], parts[1]) - orbital = line[11:20].strip() - self.aonames.append("%s_%s" % (atomname, orbital)) - atombasis.append(i) - - part = line[21:].replace("D", "E").rstrip() - temp = [] - for j in range(0, len(part), 10): - temp.append(float(part[j:j+10])) - if beta: - self.mocoeffs[1][base:base + len(part) / 10, i] = temp - else: - mocoeffs[0][base:base + len(part) / 10, i] = temp - if base == 0 and not beta: # Do the last update of atombasis - self.atombasis.append(atombasis) - if self.popregular: - # We now have aonames, so no need to continue - break - if not self.popregular and not beta: - self.mocoeffs = mocoeffs - - # Natural Orbital Coefficients (nocoeffs) - alternative for mocoeffs. - # Most extensively formed after CI calculations, but not only. - # Like for mocoeffs, this is also where aonames and atombasis are parsed. - if line[5:33] == "Natural Orbital Coefficients": - - self.aonames = [] - self.atombasis = [] - nocoeffs = numpy.zeros((self.nmo, self.nbasis), "d") - - base = 0 - self.popregular = False - for base in range(0, self.nmo, 5): - - self.updateprogress(inputfile, "Coefficients", self.fupdate) - - colmNames = inputfile.next() - if base==0 and int(colmNames.split()[0])!=1: - # Implies that this is a POP=REGULAR calculation - # and so, only aonames (not mocoeffs) will be extracted - self.popregular = True - - # No symmetry line for natural orbitals. - # symmetries = inputfile.next() - eigenvalues = inputfile.next() - - for i in range(self.nbasis): - - line = inputfile.next() - - # Just do this the first time 'round. - if base == 0: - - # Changed below from :12 to :11 to deal with Elmar Neumann's example. - parts = line[:11].split() - # New atom. - if len(parts) > 1: - if i>0: - self.atombasis.append(atombasis) - atombasis = [] - atomname = "%s%s" % (parts[2], parts[1]) - orbital = line[11:20].strip() - self.aonames.append("%s_%s" % (atomname, orbital)) - atombasis.append(i) - - part = line[21:].replace("D", "E").rstrip() - temp = [] - - for j in range(0, len(part), 10): - temp.append(float(part[j:j+10])) - - nocoeffs[base:base + len(part) / 10, i] = temp - - # Do the last update of atombasis. - if base == 0: - self.atombasis.append(atombasis) - - # We now have aonames, so no need to continue. - if self.popregular: - break - - if not self.popregular: - self.nocoeffs = nocoeffs - - # Pseudopotential charges. - if line.find("Pseudopotential Parameters") > -1: - - dashes = inputfile.next() - label1 = inputfile.next() - label2 = inputfile.next() - dashes = inputfile.next() - - line = inputfile.next() - if line.find("Centers:") < 0: - return - # This was continue before parser refactoring. - # continue - - centers = map(int, line.split()[1:]) - centers.sort() # Not always in increasing order - - self.coreelectrons = numpy.zeros(self.natom, "i") - - for center in centers: - line = inputfile.next() - front = line[:10].strip() - while not (front and int(front) == center): - line = inputfile.next() - front = line[:10].strip() - info = line.split() - self.coreelectrons[center-1] = int(info[1]) - int(info[2]) - - # This will be printed for counterpoise calcualtions only. - # To prevent crashing, we need to know which fragment is being considered. - # Other information is also printed in lines that start like this. - if line[1:14] == 'Counterpoise:': - - if line[42:50] == "fragment": - self.counterpoise = int(line[51:54]) - - # This will be printed only during ONIOM calcs; use it to set a flag - # that will allow assertion failures to be bypassed in the code. - if line[1:7] == "ONIOM:": - self.oniom = True - -if __name__ == "__main__": - import doctest, gaussianparser - doctest.testmod(gaussianparser, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +gmagoon 4/5/10-4/6/10 (this notice added 4/29/10): Gregory Magoon modified this file from cclib 1.0 +""" + +__revision__ = "$Revision: 882 $" + + +import re + +import numpy + +import logfileparser +import utils + + +class Gaussian(logfileparser.Logfile): + """A Gaussian 98/03 log file.""" + + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(Gaussian, self).__init__(logname="Gaussian", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "Gaussian log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'Gaussian("%s")' % (self.filename) + + def normalisesym(self, label): + """Use standard symmetry labels instead of Gaussian labels. + + To normalise: + (1) If label is one of [SG, PI, PHI, DLTA], replace by [sigma, pi, phi, delta] + (2) replace any G or U by their lowercase equivalent + + >>> sym = Gaussian("dummyfile").normalisesym + >>> labels = ['A1', 'AG', 'A1G', "SG", "PI", "PHI", "DLTA", 'DLTU', 'SGG'] + >>> map(sym, labels) + ['A1', 'Ag', 'A1g', 'sigma', 'pi', 'phi', 'delta', 'delta.u', 'sigma.g'] + """ + # note: DLT must come after DLTA + greek = [('SG', 'sigma'), ('PI', 'pi'), ('PHI', 'phi'), + ('DLTA', 'delta'), ('DLT', 'delta')] + for k,v in greek: + if label.startswith(k): + tmp = label[len(k):] + label = v + if tmp: + label = v + "." + tmp + + ans = label.replace("U", "u").replace("G", "g") + return ans + + def before_parsing(self): + + # Used to index self.scftargets[]. + SCFRMS, SCFMAX, SCFENERGY = range(3) + + # Flag that indicates whether it has reached the end of a geoopt. + self.optfinished = False + + # Flag for identifying Coupled Cluster runs. + self.coupledcluster = False + + # Fragment number for counterpoise calculations (normally zero). + self.counterpoise = 0 + + # Flag for identifying ONIOM calculations. + self.oniom = False + + def after_parsing(self): + + # Correct the percent values in the etsecs in the case of + # a restricted calculation. The following has the + # effect of including each transition twice. + if hasattr(self, "etsecs") and len(self.homos) == 1: + new_etsecs = [[(x[0], x[1], x[2] * numpy.sqrt(2)) for x in etsec] + for etsec in self.etsecs] + self.etsecs = new_etsecs + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + # Number of atoms. + if line[1:8] == "NAtoms=": + + self.updateprogress(inputfile, "Attributes", self.fupdate) + + natom = int(line.split()[1]) + if not hasattr(self, "natom"): + self.natom = natom + + # Catch message about completed optimization. + if line[1:23] == "Optimization completed": + self.optfinished = True + + # Extract the atomic numbers and coordinates from the input orientation, + # in the event the standard orientation isn't available. + if not self.optfinished and line.find("Input orientation") > -1 or line.find("Z-Matrix orientation") > -1: + + # If this is a counterpoise calculation, this output means that + # the supermolecule is now being considered, so we can set: + self.counterpoise = 0 + + self.updateprogress(inputfile, "Attributes", self.cupdate) + + if not hasattr(self, "inputcoords"): + self.inputcoords = [] + self.inputatoms = [] + + hyphens = inputfile.next() + colmNames = inputfile.next() + colmNames = inputfile.next() + hyphens = inputfile.next() + + atomcoords = [] + line = inputfile.next() + while line != hyphens: + broken = line.split() + self.inputatoms.append(int(broken[1])) + atomcoords.append(map(float, broken[3:6])) + line = inputfile.next() + + self.inputcoords.append(atomcoords) + + if not hasattr(self, "natom"): + self.atomnos = numpy.array(self.inputatoms, 'i') + self.natom = len(self.atomnos) + + # Extract the atomic numbers and coordinates of the atoms. + if not self.optfinished and line.strip() == "Standard orientation:": + + self.updateprogress(inputfile, "Attributes", self.cupdate) + + # If this is a counterpoise calculation, this output means that + # the supermolecule is now being considered, so we can set: + self.counterpoise = 0 + + if not hasattr(self, "atomcoords"): + self.atomcoords = [] + + hyphens = inputfile.next() + colmNames = inputfile.next() + colmNames = inputfile.next() + hyphens = inputfile.next() + + atomnos = [] + atomcoords = [] + line = inputfile.next() + while line != hyphens: + broken = line.split() + atomnos.append(int(broken[1])) + atomcoords.append(map(float, broken[-3:])) + line = inputfile.next() + self.atomcoords.append(atomcoords) + if not hasattr(self, "natom"): + self.atomnos = numpy.array(atomnos, 'i') + self.natom = len(self.atomnos) + + # Find the targets for SCF convergence (QM calcs). + if line[1:44] == 'Requested convergence on RMS density matrix': + + if not hasattr(self, "scftargets"): + self.scftargets = [] + + scftargets = [] + # The RMS density matrix. + scftargets.append(self.float(line.split('=')[1].split()[0])) + line = inputfile.next() + # The MAX density matrix. + scftargets.append(self.float(line.strip().split('=')[1][:-1])) + line = inputfile.next() + # For G03, there's also the energy (not for G98). + if line[1:10] == "Requested": + scftargets.append(self.float(line.strip().split('=')[1][:-1])) + + self.scftargets.append(scftargets) + + # Extract SCF convergence information (QM calcs). + if line[1:10] == 'Cycle 1': + + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + + scfvalues = [] + line = inputfile.next() + while line.find("SCF Done") == -1: + + self.updateprogress(inputfile, "QM convergence", self.fupdate) + + if line.find(' E=') == 0: + self.logger.debug(line) + + # RMSDP=3.74D-06 MaxDP=7.27D-05 DE=-1.73D-07 OVMax= 3.67D-05 + # or + # RMSDP=1.13D-05 MaxDP=1.08D-04 OVMax= 1.66D-04 + if line.find(" RMSDP") == 0: + + parts = line.split() + newlist = [self.float(x.split('=')[1]) for x in parts[0:2]] + energy = 1.0 + if len(parts) > 4: + energy = parts[2].split('=')[1] + if energy == "": + energy = self.float(parts[3]) + else: + energy = self.float(energy) + if len(self.scftargets[0]) == 3: # Only add the energy if it's a target criteria + newlist.append(energy) + scfvalues.append(newlist) + + try: + line = inputfile.next() + # May be interupted by EOF. + except StopIteration: + break + + self.scfvalues.append(scfvalues) + + # Extract SCF convergence information (AM1 calcs). + if line[1:4] == 'It=': + + self.scftargets = numpy.array([1E-7], "d") # This is the target value for the rms + self.scfvalues = [[]] + + line = inputfile.next() + while line.find(" Energy") == -1: + + if self.progress: + step = inputfile.tell() + if step != oldstep: + self.progress.update(step, "AM1 Convergence") + oldstep = step + + if line[1:4] == "It=": + parts = line.strip().split() + self.scfvalues[0].append(self.float(parts[-1][:-1])) + line = inputfile.next() + + # Note: this needs to follow the section where 'SCF Done' is used + # to terminate a loop when extracting SCF convergence information. + if line[1:9] == 'SCF Done': + + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + + self.scfenergies.append(utils.convertor(self.float(line.split()[4]), "hartree", "eV")) + #gmagoon 5/27/09: added scfenergies reading for PM3 case where line begins with Energy= + #example line: " Energy= -0.077520562724 NIter= 14." + if line[1:8] == 'Energy=': + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + self.scfenergies.append(utils.convertor(self.float(line.split()[1]), "hartree", "eV")) + #gmagoon 6/8/09: added molecular mass parsing (units will be amu) + #example line: " Molecular mass: 208.11309 amu." + if line[1:16] == 'Molecular mass:': + self.molmass = self.float(line.split()[2]) + + #gmagoon 5/27/09: added rotsymm for reading rotational symmetry number + #it would probably be better to read in point group (or calculate separately with OpenBabel, and I probably won't end up using this + #example line: " Rotational symmetry number 1." + if line[1:27] == 'Rotational symmetry number': + self.rotsymm = int(self.float(line.split()[3])) + + #gmagoon 5/28/09: added rotcons for rotational constants (at each step) in GHZ + #example line: Rotational constants (GHZ): 17.0009421 5.8016756 4.5717439 + #could also read in moment of inertia, but this should just differ by a constant: rot cons= h/(8*Pi^2*I) + #note that the last occurence of this in the thermochemistry section has reduced precision, so we will want to use the 2nd to last instance + if line[1:28] == 'Rotational constants (GHZ):': + if not hasattr(self, "rotcons"): + self.rotcons = [] + + #some linear cases (e.g. if linearity is not recognized) can have asterisks ****... for the first rotational constant; e.g.: + # Rotational constants (GHZ): ************ 12.73690 12.73690 + # or: + # Rotational constants (GHZ):*************** 10.4988228 10.4988223 + # if this is the case, replace the asterisks with a 0.0 + #we can also have cases like this: + # Rotational constants (GHZ):6983905.3278703 11.8051382 11.8051183 + #if line[28:29] == '*' or line.split()[3].startswith('*'): + if line[37:38] == '*': + self.rotcons.append([0.0]+map(float, line[28:].split()[-2:])) #record last 0.0 and last 2 numbers (words) in the string following the prefix + else: + self.rotcons.append(map(float, line[28:].split()[-3:])) #record last 3 numbers (words) in the string following the prefix + + # Total energies after Moller-Plesset corrections. + # Second order correction is always first, so its first occurance + # triggers creation of mpenergies (list of lists of energies). + # Further MP2 corrections are appended as found. + # + # Example MP2 output line: + # E2 = -0.9505918144D+00 EUMP2 = -0.28670924198852D+03 + # Warning! this output line is subtly different for MP3/4/5 runs + if "EUMP2" in line[27:34]: + + if not hasattr(self, "mpenergies"): + self.mpenergies = [] + self.mpenergies.append([]) + mp2energy = self.float(line.split("=")[2]) + self.mpenergies[-1].append(utils.convertor(mp2energy, "hartree", "eV")) + + # Example MP3 output line: + # E3= -0.10518801D-01 EUMP3= -0.75012800924D+02 + if line[34:39] == "EUMP3": + + mp3energy = self.float(line.split("=")[2]) + self.mpenergies[-1].append(utils.convertor(mp3energy, "hartree", "eV")) + + # Example MP4 output lines: + # E4(DQ)= -0.31002157D-02 UMP4(DQ)= -0.75015901139D+02 + # E4(SDQ)= -0.32127241D-02 UMP4(SDQ)= -0.75016013648D+02 + # E4(SDTQ)= -0.32671209D-02 UMP4(SDTQ)= -0.75016068045D+02 + # Energy for most substitutions is used only (SDTQ by default) + if line[34:42] == "UMP4(DQ)": + + mp4energy = self.float(line.split("=")[2]) + line = inputfile.next() + if line[34:43] == "UMP4(SDQ)": + mp4energy = self.float(line.split("=")[2]) + line = inputfile.next() + if line[34:44] == "UMP4(SDTQ)": + mp4energy = self.float(line.split("=")[2]) + self.mpenergies[-1].append(utils.convertor(mp4energy, "hartree", "eV")) + + # Example MP5 output line: + # DEMP5 = -0.11048812312D-02 MP5 = -0.75017172926D+02 + if line[29:32] == "MP5": + mp5energy = self.float(line.split("=")[2]) + self.mpenergies[-1].append(utils.convertor(mp5energy, "hartree", "eV")) + + # Total energies after Coupled Cluster corrections. + # Second order MBPT energies (MP2) are also calculated for these runs, + # but the output is the same as when parsing for mpenergies. + # First turn on flag for Coupled Cluster runs. + if line[1:23] == "Coupled Cluster theory" or line[1:8] == "CCSD(T)": + + self.coupledcluster = True + if not hasattr(self, "ccenergies"): + self.ccenergies = [] + + # Now read the consecutive correlated energies when , + # but append only the last one to ccenergies. + # Only the highest level energy is appended - ex. CCSD(T), not CCSD. + if self.coupledcluster and line[27:35] == "E(CORR)=": + self.ccenergy = self.float(line.split()[3]) + if self.coupledcluster and line[1:9] == "CCSD(T)=": + self.ccenergy = self.float(line.split()[1]) + # Append when leaving link 913 + if self.coupledcluster and line[1:16] == "Leave Link 913": + self.ccenergies.append(utils.convertor(self.ccenergy, "hartree", "eV")) + + # Geometry convergence information. + if line[49:59] == 'Converged?': + + if not hasattr(self, "geotargets"): + self.geovalues = [] + self.geotargets = numpy.array([0.0, 0.0, 0.0, 0.0], "d") + + newlist = [0]*4 + for i in range(4): + line = inputfile.next() + self.logger.debug(line) + parts = line.split() + try: + value = self.float(parts[2]) + except ValueError: + value = -1.0 + #self.logger.error("Problem parsing the value for geometry optimisation: %s is not a number." % parts[2]) + #gmagoon 20111202: because the value can become **** (as shown below, I'm changing this to not report an error, and instead just set the value to -1.0 +# Item Value Threshold Converged? +# Maximum Force ******** 0.000015 NO +# RMS Force 1.813626 0.000010 NO +# Maximum Displacement 0.915407 0.000060 NO +# RMS Displacement 0.280831 0.000040 NO + else: + newlist[i] = value + self.geotargets[i] = self.float(parts[3]) + + self.geovalues.append(newlist) + + # Gradients. + # Read in the cartesian energy gradients (forces) from a block like this: + # ------------------------------------------------------------------- + # Center Atomic Forces (Hartrees/Bohr) + # Number Number X Y Z + # ------------------------------------------------------------------- + # 1 1 -0.012534744 -0.021754635 -0.008346094 + # 2 6 0.018984731 0.032948887 -0.038003451 + # 3 1 -0.002133484 -0.006226040 0.023174772 + # 4 1 -0.004316502 -0.004968213 0.023174772 + # -2 -0.001830728 -0.000743108 -0.000196625 + # ------------------------------------------------------------------ + # + # The "-2" line is for a dummy atom + # + # Then optimization is done in internal coordinates, Gaussian also + # print the forces in internal coordinates, which can be produced from + # the above. This block looks like this: + # Variable Old X -DE/DX Delta X Delta X Delta X New X + # (Linear) (Quad) (Total) + # ch 2.05980 0.01260 0.00000 0.01134 0.01134 2.07114 + # hch 1.75406 0.09547 0.00000 0.24861 0.24861 2.00267 + # hchh 2.09614 0.01261 0.00000 0.16875 0.16875 2.26489 + # Item Value Threshold Converged? + if line[37:43] == "Forces": + + if not hasattr(self, "grads"): + self.grads = [] + + header = inputfile.next() + dashes = inputfile.next() + line = inputfile.next() + forces = [] + while line != dashes: + broken = line.split() + Fx, Fy, Fz = broken[-3:] + forces.append([float(Fx),float(Fy),float(Fz)]) + line = inputfile.next() + self.grads.append(forces) + + # Charge and multiplicity. + # If counterpoise correction is used, multiple lines match. + # The first one contains charge/multiplicity of the whole molecule.: + # Charge = 0 Multiplicity = 1 in supermolecule + # Charge = 0 Multiplicity = 1 in fragment 1. + # Charge = 0 Multiplicity = 1 in fragment 2. + if line[1:7] == 'Charge' and line.find("Multiplicity")>=0: + + regex = ".*=(.*)Mul.*=\s*(\d+).*" + match = re.match(regex, line) + assert match, "Something unusual about the line: '%s'" % line + + self.charge = int(match.groups()[0]) + self.mult = int(match.groups()[1]) + + # Orbital symmetries. + if line[1:20] == 'Orbital symmetries:' and not hasattr(self, "mosyms"): + + # For counterpoise fragments, skip these lines. + if self.counterpoise != 0: return + + self.updateprogress(inputfile, "MO Symmetries", self.fupdate) + + self.mosyms = [[]] + line = inputfile.next() + unres = False + if line.find("Alpha Orbitals") == 1: + unres = True + line = inputfile.next() + i = 0 + while len(line) > 18 and line[17] == '(': + if line.find('Virtual') >= 0: + self.homos = numpy.array([i-1], "i") # 'HOMO' indexes the HOMO in the arrays + parts = line[17:].split() + for x in parts: + self.mosyms[0].append(self.normalisesym(x.strip('()'))) + i += 1 + line = inputfile.next() + if unres: + line = inputfile.next() + # Repeat with beta orbital information + i = 0 + self.mosyms.append([]) + while len(line) > 18 and line[17] == '(': + if line.find('Virtual')>=0: + if (hasattr(self, "homos")):#if there was also an alpha virtual orbital (here we consider beta) we will store two indices in the array + self.homos.resize([2]) # Extend the array to two elements + self.homos[1] = i-1 # 'HOMO' indexes the HOMO in the arrays + else:#otherwise (e.g. for O triplet) there is no alpha virtual orbital, only beta virtual orbitals, and we initialize the array with one element + self.homos = numpy.array([i-1], "i") # 'HOMO' indexes the HOMO in the arrays + parts = line[17:].split() + for x in parts: + self.mosyms[1].append(self.normalisesym(x.strip('()'))) + i += 1 + line = inputfile.next() + + # Alpha/Beta electron eigenvalues. + if line[1:6] == "Alpha" and line.find("eigenvalues") >= 0: + + # For counterpoise fragments, skip these lines. + if self.counterpoise != 0: return + + # For ONIOM calcs, ignore this section in order to bypass assertion failure. + if self.oniom: return + + self.updateprogress(inputfile, "Eigenvalues", self.fupdate) + self.moenergies = [[]] + HOMO = -2 + + while line.find('Alpha') == 1: + if line.split()[1] == "virt." and HOMO == -2: + + # If there aren't any symmetries, this is a good way to find the HOMO. + # Also, check for consistency if homos was already parsed. + HOMO = len(self.moenergies[0])-1 + if hasattr(self, "homos"): + assert HOMO == self.homos[0] + else: + self.homos = numpy.array([HOMO], "i") + + part = line[28:] + i = 0 + while i*10+4 < len(part): + x = part[i*10:(i+1)*10] + self.moenergies[0].append(utils.convertor(self.float(x), "hartree", "eV")) + i += 1 + line = inputfile.next() + # If, at this point, self.homos is unset, then there were not + # any alpha virtual orbitals + if not hasattr(self, "homos"): + HOMO = len(self.moenergies[0])-1 + self.homos = numpy.array([HOMO], "i") + + + if line.find('Beta') == 2: + self.moenergies.append([]) + + HOMO = -2 + while line.find('Beta') == 2: + if line.split()[1] == "virt." and HOMO == -2: + + # If there aren't any symmetries, this is a good way to find the HOMO. + # Also, check for consistency if homos was already parsed. + HOMO = len(self.moenergies[1])-1 + if len(self.homos) == 2: + assert HOMO == self.homos[1] + else: + self.homos.resize([2]) + self.homos[1] = HOMO + + part = line[28:] + i = 0 + while i*10+4 < len(part): + x = part[i*10:(i+1)*10] + self.moenergies[1].append(utils.convertor(self.float(x), "hartree", "eV")) + i += 1 + line = inputfile.next() + + self.moenergies = [numpy.array(x, "d") for x in self.moenergies] + + # Gaussian Rev <= B.0.3 (?) + # AO basis set in the form of general basis input: + # 1 0 + # S 3 1.00 0.000000000000 + # 0.7161683735D+02 0.1543289673D+00 + # 0.1304509632D+02 0.5353281423D+00 + # 0.3530512160D+01 0.4446345422D+00 + # SP 3 1.00 0.000000000000 + # 0.2941249355D+01 -0.9996722919D-01 0.1559162750D+00 + # 0.6834830964D+00 0.3995128261D+00 0.6076837186D+00 + # 0.2222899159D+00 0.7001154689D+00 0.3919573931D+00 + if line[1:16] == "AO basis set in": + + # For counterpoise fragment calcualtions, skip these lines. + if self.counterpoise != 0: return + + self.gbasis = [] + line = inputfile.next() + while line.strip(): + gbasis = [] + line = inputfile.next() + while line.find("*")<0: + temp = line.split() + symtype = temp[0] + numgau = int(temp[1]) + gau = [] + for i in range(numgau): + temp = map(self.float, inputfile.next().split()) + gau.append(temp) + + for i,x in enumerate(symtype): + newgau = [(z[0],z[i+1]) for z in gau] + gbasis.append( (x,newgau) ) + line = inputfile.next() # i.e. "****" or "SP ...." + self.gbasis.append(gbasis) + line = inputfile.next() # i.e. "20 0" or blank line + + # Start of the IR/Raman frequency section. + # Caution is advised here, as additional frequency blocks + # can be printed by Gaussian (with slightly different formats), + # often doubling the information printed. + # See, for a non-standard exmaple, regression Gaussian98/test_H2.log + if line[1:14] == "Harmonic freq": + + self.updateprogress(inputfile, "Frequency Information", self.fupdate) + + # The whole block should not have any blank lines. + while line.strip() != "": + + # Lines with symmetries and symm. indices begin with whitespace. + if line[1:15].strip() == "" and not line[15:22].strip().isdigit(): + + if not hasattr(self, 'vibsyms'): + self.vibsyms = [] + syms = line.split() + self.vibsyms.extend(syms) + + if line[1:15] == "Frequencies --": + + if not hasattr(self, 'vibfreqs'): + self.vibfreqs = [] + freqs = [self.float(f) for f in line[15:].split()] + self.vibfreqs.extend(freqs) + + if line[1:15] == "IR Inten --": + + if not hasattr(self, 'vibirs'): + self.vibirs = [] + irs = [self.float(f) for f in line[15:].split()] + self.vibirs.extend(irs) + + if line[1:15] == "Raman Activ --": + + if not hasattr(self, 'vibramans'): + self.vibramans = [] + ramans = [self.float(f) for f in line[15:].split()] + self.vibramans.extend(ramans) + + # Block with displacement should start with this. + # Remember, it is possible to have less than three columns! + # There should be as many lines as there are atoms. + if line[1:29] == "Atom AN X Y Z": + + if not hasattr(self, 'vibdisps'): + self.vibdisps = [] + disps = [] + for n in range(self.natom): + line = inputfile.next() + numbers = [float(s) for s in line[10:].split()] + N = len(numbers) / 3 + if not disps: + for n in range(N): + disps.append([]) + for n in range(N): + disps[n].append(numbers[3*n:3*n+3]) + self.vibdisps.extend(disps) + + line = inputfile.next() + +# Below is the old code for the IR/Raman frequency block, can probably be removed. +# while len(line[:15].split()) == 0: +# self.logger.debug(line) +# self.vibsyms.extend(line.split()) # Adding new symmetry +# line = inputfile.next() +# # Read in frequencies. +# freqs = [self.float(f) for f in line.split()[2:]] +# self.vibfreqs.extend(freqs) +# line = inputfile.next() +# line = inputfile.next() +# line = inputfile.next() +# irs = [self.float(f) for f in line.split()[3:]] +# self.vibirs.extend(irs) +# line = inputfile.next() # Either the header or a Raman line +# if line.find("Raman") >= 0: +# if not hasattr(self, "vibramans"): +# self.vibramans = [] +# ramans = [self.float(f) for f in line.split()[3:]] +# self.vibramans.extend(ramans) +# line = inputfile.next() # Depolar (P) +# line = inputfile.next() # Depolar (U) +# line = inputfile.next() # Header +# line = inputfile.next() # First line of cartesian displacement vectors +# p = [[], [], []] +# while len(line[:15].split()) > 0: +# # Store the cartesian displacement vectors +# broken = map(float, line.strip().split()[2:]) +# for i in range(0, len(broken), 3): +# p[i/3].append(broken[i:i+3]) +# line = inputfile.next() +# self.vibdisps.extend(p[0:len(broken)/3]) +# line = inputfile.next() # Should be the line with symmetries +# self.vibfreqs = numpy.array(self.vibfreqs, "d") +# self.vibirs = numpy.array(self.vibirs, "d") +# self.vibdisps = numpy.array(self.vibdisps, "d") +# if hasattr(self, "vibramans"): +# self.vibramans = numpy.array(self.vibramans, "d") + + # Electronic transitions. + if line[1:14] == "Excited State": + + if not hasattr(self, "etenergies"): + self.etenergies = [] + self.etoscs = [] + self.etsyms = [] + self.etsecs = [] + # Need to deal with lines like: + # (restricted calc) + # Excited State 1: Singlet-BU 5.3351 eV 232.39 nm f=0.1695 + # (unrestricted calc) (first excited state is 2!) + # Excited State 2: ?Spin -A 0.1222 eV 10148.75 nm f=0.0000 + # (Gaussian 09 ZINDO) + # Excited State 1: Singlet-?Sym 2.5938 eV 478.01 nm f=0.0000 =0.000 + p = re.compile(":(?P.*?)(?P-?\d*\.\d*) eV") + groups = p.search(line).groups() + self.etenergies.append(utils.convertor(self.float(groups[1]), "eV", "cm-1")) + self.etoscs.append(self.float(line.split("f=")[-1].split()[0])) + self.etsyms.append(groups[0].strip()) + + line = inputfile.next() + + p = re.compile("(\d+)") + CIScontrib = [] + while line.find(" ->") >= 0: # This is a contribution to the transition + parts = line.split("->") + self.logger.debug(parts) + # Has to deal with lines like: + # 32 -> 38 0.04990 + # 35A -> 45A 0.01921 + frommoindex = 0 # For restricted or alpha unrestricted + fromMO = parts[0].strip() + if fromMO[-1] == "B": + frommoindex = 1 # For beta unrestricted + fromMO = int(p.match(fromMO).group())-1 # subtract 1 so that it is an index into moenergies + + t = parts[1].split() + tomoindex = 0 + toMO = t[0] + if toMO[-1] == "B": + tomoindex = 1 + toMO = int(p.match(toMO).group())-1 # subtract 1 so that it is an index into moenergies + + percent = self.float(t[1]) + # For restricted calculations, the percentage will be corrected + # after parsing (see after_parsing() above). + CIScontrib.append([(fromMO, frommoindex), (toMO, tomoindex), percent]) + line = inputfile.next() + self.etsecs.append(CIScontrib) + +# Circular dichroism data (different for G03 vs G09) + +# G03 + +## <0|r|b> * (Au), Rotatory Strengths (R) in +## cgs (10**-40 erg-esu-cm/Gauss) +## state X Y Z R(length) +## 1 0.0006 0.0096 -0.0082 -0.4568 +## 2 0.0251 -0.0025 0.0002 -5.3846 +## 3 0.0168 0.4204 -0.3707 -15.6580 +## 4 0.0721 0.9196 -0.9775 -3.3553 + +# G09 + +## 1/2[<0|r|b>* + (<0|rxdel|b>*)*] +## Rotatory Strengths (R) in cgs (10**-40 erg-esu-cm/Gauss) +## state XX YY ZZ R(length) R(au) +## 1 -0.3893 -6.7546 5.7736 -0.4568 -0.0010 +## 2 -17.7437 1.7335 -0.1435 -5.3845 -0.0114 +## 3 -11.8655 -297.2604 262.1519 -15.6580 -0.0332 + + if (line[1:52] == "<0|r|b> * (Au), Rotatory Strengths (R)" or + line[1:50] == "1/2[<0|r|b>* + (<0|rxdel|b>*)*]"): + + self.etrotats = [] + inputfile.next() # Units + headers = inputfile.next() # Headers + Ncolms = len(headers.split()) + line = inputfile.next() + parts = line.strip().split() + while len(parts) == Ncolms: + try: + R = self.float(parts[4]) + except ValueError: + # nan or -nan if there is no first excited state + # (for unrestricted calculations) + pass + else: + self.etrotats.append(R) + line = inputfile.next() + temp = line.strip().split() + parts = line.strip().split() + self.etrotats = numpy.array(self.etrotats, "d") + + # Number of basis sets functions. + # Has to deal with lines like: + # NBasis = 434 NAE= 97 NBE= 97 NFC= 34 NFV= 0 + # and... + # NBasis = 148 MinDer = 0 MaxDer = 0 + # Although the former is in every file, it doesn't occur before + # the overlap matrix is printed. + if line[1:7] == "NBasis" or line[4:10] == "NBasis": + + # For counterpoise fragment, skip these lines. + if self.counterpoise != 0: return + + # For ONIOM calcs, ignore this section in order to bypass assertion failure. + if self.oniom: return + + # If nbasis was already parsed, check if it changed. + nbasis = int(line.split('=')[1].split()[0]) + if hasattr(self, "nbasis"): + assert nbasis == self.nbasis + else: + self.nbasis = nbasis + + # Number of linearly-independent basis sets. + if line[1:7] == "NBsUse": + + # For counterpoise fragment, skip these lines. + if self.counterpoise != 0: return + + # For ONIOM calcs, ignore this section in order to bypass assertion failure. + if self.oniom: return + + # If nmo was already parsed, check if it changed. + nmo = int(line.split('=')[1].split()[0]) + if hasattr(self, "nmo"): + assert nmo == self.nmo + else: + self.nmo = nmo + + # For AM1 calculations, set nbasis by a second method, + # as nmo may not always be explicitly stated. + if line[7:22] == "basis functions, ": + + nbasis = int(line.split()[0]) + if hasattr(self, "nbasis"): + assert nbasis == self.nbasis + else: + self.nbasis = nbasis + + # Molecular orbital overlap matrix. + # Has to deal with lines such as: + # *** Overlap *** + # ****** Overlap ****** + if line[1:4] == "***" and (line[5:12] == "Overlap" + or line[8:15] == "Overlap"): + + self.aooverlaps = numpy.zeros( (self.nbasis, self.nbasis), "d") + # Overlap integrals for basis fn#1 are in aooverlaps[0] + base = 0 + colmNames = inputfile.next() + while base < self.nbasis: + + self.updateprogress(inputfile, "Overlap", self.fupdate) + + for i in range(self.nbasis-base): # Fewer lines this time + line = inputfile.next() + parts = line.split() + for j in range(len(parts)-1): # Some lines are longer than others + k = float(parts[j+1].replace("D", "E")) + self.aooverlaps[base+j, i+base] = k + self.aooverlaps[i+base, base+j] = k + base += 5 + colmNames = inputfile.next() + self.aooverlaps = numpy.array(self.aooverlaps, "d") + + # Molecular orbital coefficients (mocoeffs). + # Essentially only produced for SCF calculations. + # This is also the place where aonames and atombasis are parsed. + if line[5:35] == "Molecular Orbital Coefficients" or line[5:41] == "Alpha Molecular Orbital Coefficients" or line[5:40] == "Beta Molecular Orbital Coefficients": + + if line[5:40] == "Beta Molecular Orbital Coefficients": + beta = True + if self.popregular: + return + # This was continue before refactoring the parsers. + #continue # Not going to extract mocoeffs + # Need to add an extra array to self.mocoeffs + self.mocoeffs.append(numpy.zeros((self.nmo, self.nbasis), "d")) + else: + beta = False + self.aonames = [] + self.atombasis = [] + mocoeffs = [numpy.zeros((self.nmo, self.nbasis), "d")] + + base = 0 + self.popregular = False + for base in range(0, self.nmo, 5): + + self.updateprogress(inputfile, "Coefficients", self.fupdate) + + colmNames = inputfile.next() + + if not colmNames.split(): + self.logger.warning("Molecular coefficients header found but no coefficients.") + break; + + if base==0 and int(colmNames.split()[0])!=1: + # Implies that this is a POP=REGULAR calculation + # and so, only aonames (not mocoeffs) will be extracted + self.popregular = True + symmetries = inputfile.next() + eigenvalues = inputfile.next() + for i in range(self.nbasis): + + line = inputfile.next() + if base == 0 and not beta: # Just do this the first time 'round + # Changed below from :12 to :11 to deal with Elmar Neumann's example + parts = line[:11].split() + if len(parts) > 1: # New atom + if i>0: + self.atombasis.append(atombasis) + atombasis = [] + atomname = "%s%s" % (parts[2], parts[1]) + orbital = line[11:20].strip() + self.aonames.append("%s_%s" % (atomname, orbital)) + atombasis.append(i) + + part = line[21:].replace("D", "E").rstrip() + temp = [] + for j in range(0, len(part), 10): + temp.append(float(part[j:j+10])) + if beta: + self.mocoeffs[1][base:base + len(part) / 10, i] = temp + else: + mocoeffs[0][base:base + len(part) / 10, i] = temp + if base == 0 and not beta: # Do the last update of atombasis + self.atombasis.append(atombasis) + if self.popregular: + # We now have aonames, so no need to continue + break + if not self.popregular and not beta: + self.mocoeffs = mocoeffs + + # Natural Orbital Coefficients (nocoeffs) - alternative for mocoeffs. + # Most extensively formed after CI calculations, but not only. + # Like for mocoeffs, this is also where aonames and atombasis are parsed. + if line[5:33] == "Natural Orbital Coefficients": + + self.aonames = [] + self.atombasis = [] + nocoeffs = numpy.zeros((self.nmo, self.nbasis), "d") + + base = 0 + self.popregular = False + for base in range(0, self.nmo, 5): + + self.updateprogress(inputfile, "Coefficients", self.fupdate) + + colmNames = inputfile.next() + if base==0 and int(colmNames.split()[0])!=1: + # Implies that this is a POP=REGULAR calculation + # and so, only aonames (not mocoeffs) will be extracted + self.popregular = True + + # No symmetry line for natural orbitals. + # symmetries = inputfile.next() + eigenvalues = inputfile.next() + + for i in range(self.nbasis): + + line = inputfile.next() + + # Just do this the first time 'round. + if base == 0: + + # Changed below from :12 to :11 to deal with Elmar Neumann's example. + parts = line[:11].split() + # New atom. + if len(parts) > 1: + if i>0: + self.atombasis.append(atombasis) + atombasis = [] + atomname = "%s%s" % (parts[2], parts[1]) + orbital = line[11:20].strip() + self.aonames.append("%s_%s" % (atomname, orbital)) + atombasis.append(i) + + part = line[21:].replace("D", "E").rstrip() + temp = [] + + for j in range(0, len(part), 10): + temp.append(float(part[j:j+10])) + + nocoeffs[base:base + len(part) / 10, i] = temp + + # Do the last update of atombasis. + if base == 0: + self.atombasis.append(atombasis) + + # We now have aonames, so no need to continue. + if self.popregular: + break + + if not self.popregular: + self.nocoeffs = nocoeffs + + # Pseudopotential charges. + if line.find("Pseudopotential Parameters") > -1: + + dashes = inputfile.next() + label1 = inputfile.next() + label2 = inputfile.next() + dashes = inputfile.next() + + line = inputfile.next() + if line.find("Centers:") < 0: + return + # This was continue before parser refactoring. + # continue + + centers = map(int, line.split()[1:]) + centers.sort() # Not always in increasing order + + self.coreelectrons = numpy.zeros(self.natom, "i") + + for center in centers: + line = inputfile.next() + front = line[:10].strip() + while not (front and int(front) == center): + line = inputfile.next() + front = line[:10].strip() + info = line.split() + self.coreelectrons[center-1] = int(info[1]) - int(info[2]) + + # This will be printed for counterpoise calcualtions only. + # To prevent crashing, we need to know which fragment is being considered. + # Other information is also printed in lines that start like this. + if line[1:14] == 'Counterpoise:': + + if line[42:50] == "fragment": + self.counterpoise = int(line[51:54]) + + # This will be printed only during ONIOM calcs; use it to set a flag + # that will allow assertion failures to be bypassed in the code. + if line[1:7] == "ONIOM:": + self.oniom = True + +if __name__ == "__main__": + import doctest, gaussianparser + doctest.testmod(gaussianparser, verbose=False) diff --git a/external/cclib/parser/jaguarparser.py b/external/cclib/parser/jaguarparser.py index 7fbf16c57b..50ea95200b 100644 --- a/external/cclib/parser/jaguarparser.py +++ b/external/cclib/parser/jaguarparser.py @@ -1,474 +1,474 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 861 $" - - -import re - -import numpy - -import logfileparser -import utils - - -class Jaguar(logfileparser.Logfile): - """A Jaguar output file""" - - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(Jaguar, self).__init__(logname="Jaguar", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "Jaguar output file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'Jaguar("%s")' % (self.filename) - - def normalisesym(self, label): - """Normalise the symmetries used by Jaguar. - - To normalise, three rules need to be applied: - (1) To handle orbitals of E symmetry, retain everything before the / - (2) Replace two p's by " - (2) Replace any remaining single p's by ' - - >>> t = Jaguar("dummyfile").normalisesym - >>> labels = ['A', 'A1', 'Ag', 'Ap', 'App', "A1p", "A1pp", "E1pp/Ap"] - >>> answers = map(t, labels) - >>> print answers - ['A', 'A1', 'Ag', "A'", 'A"', "A1'", 'A1"', 'E1"'] - """ - ans = label.split("/")[0].replace("pp", '"').replace("p", "'") - return ans - - def before_parsing(self): - - self.geoopt = False # Is this a GeoOpt? Needed for SCF targets/values. - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - if line[0:4] == "etot": - # Get SCF convergence information - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - self.scftargets = [[5E-5, 5E-6]] - values = [] - while line[0:4] == "etot": - # Jaguar 4.2 - # etot 1 N N 0 N -382.08751886450 2.3E-03 1.4E-01 - # etot 2 Y Y 0 N -382.27486023153 1.9E-01 1.4E-03 5.7E-02 - # Jaguar 6.5 - # etot 1 N N 0 N -382.08751881733 2.3E-03 1.4E-01 - # etot 2 Y Y 0 N -382.27486018708 1.9E-01 1.4E-03 5.7E-02 - temp = line.split()[7:] - if len(temp)==3: - denergy = float(temp[0]) - else: - denergy = 0 # Should really be greater than target value - # or should we just ignore the values in this line - ddensity = float(temp[-2]) - maxdiiserr = float(temp[-1]) - if not self.geoopt: - values.append([denergy, ddensity]) - else: - values.append([ddensity]) - line = inputfile.next() - self.scfvalues.append(values) - - # Hartree-Fock energy after SCF - if line[1:18] == "SCFE: SCF energy:": - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - temp = line.strip().split() - scfenergy = float(temp[temp.index("hartrees") - 1]) - scfenergy = utils.convertor(scfenergy, "hartree", "eV") - self.scfenergies.append(scfenergy) - - # Energy after LMP2 correction - if line[1:18] == "Total LMP2 Energy": - if not hasattr(self, "mpenergies"): - self.mpenergies = [[]] - lmp2energy = float(line.split()[-1]) - lmp2energy = utils.convertor(lmp2energy, "hartree", "eV") - self.mpenergies[-1].append(lmp2energy) - - if line[2:14] == "new geometry" or line[1:21] == "Symmetrized geometry" or line.find("Input geometry") > 0: - # Get the atom coordinates - if not hasattr(self, "atomcoords") or line[1:21] == "Symmetrized geometry": - # Wipe the "Input geometry" if "Symmetrized geometry" present - self.atomcoords = [] - p = re.compile("(\D+)\d+") # One/more letters followed by a number - atomcoords = [] - atomnos = [] - angstrom = inputfile.next() - title = inputfile.next() - line = inputfile.next() - while line.strip(): - temp = line.split() - element = p.findall(temp[0])[0] - atomnos.append(self.table.number[element]) - atomcoords.append(map(float, temp[1:])) - line = inputfile.next() - self.atomcoords.append(atomcoords) - self.atomnos = numpy.array(atomnos, "i") - self.natom = len(atomcoords) - - # Extract charge and multiplicity - if line[2:22] == "net molecular charge": - self.charge = int(line.split()[-1]) - self.mult = int(inputfile.next().split()[-1]) - - if line[2:24] == "start of program geopt": - if not self.geoopt: - # Need to keep only the RMS density change info - # if this is a geoopt - self.scftargets = [[self.scftargets[0][0]]] - if hasattr(self, "scfvalues"): - self.scfvalues[0] = [[x[0]] for x in self.scfvalues[0]] - self.geoopt = True - else: - self.scftargets.append([5E-5]) - - if line[2:28] == "geometry optimization step": - # Get Geometry Opt convergence information - if not hasattr(self, "geovalues"): - self.geovalues = [] - self.geotargets = numpy.zeros(5, "d") - gopt_step = int(line.split()[-1]) - energy = inputfile.next() - # quick hack for messages of the sort: - # ** restarting optimization from step 2 ** - # as found in regression file ptnh3_2_H2O_2_2plus.out - if inputfile.next().strip(): - blank = inputfile.next() - line = inputfile.next() - values = [] - target_index = 0 - if gopt_step == 1: - # The first optimization step does not produce an energy change - values.append(0.0) - target_index = 1 - while line.strip(): - if len(line) > 40 and line[41] == "(": - # A new geo convergence value - values.append(float(line[26:37])) - self.geotargets[target_index] = float(line[43:54]) - target_index += 1 - line = inputfile.next() - self.geovalues.append(values) - - if line.find("number of occupied orbitals") > 0: - # Get number of MOs - occs = int(line.split()[-1]) - line = inputfile.next() - virts = int(line.split()[-1]) - self.nmo = occs + virts - self.homos = numpy.array([occs-1], "i") - - self.unrestrictedflag = False - - if line.find("number of alpha occupied orb") > 0: - # Get number of MOs for an unrestricted calc - - aoccs = int(line.split()[-1]) - line = inputfile.next() - avirts = int(line.split()[-1]) - line = inputfile.next() - boccs = int(line.split()[-1]) - line = inputfile.next() - bvirt = int(line.split()[-1]) - - self.nmo = aoccs + avirts - self.homos = numpy.array([aoccs-1,boccs-1], "i") - self.unrestrictedflag = True - - # MO energies and symmetries. - # Jaguar 7.0: provides energies and symmetries for both - # restricted and unrestricted calculations, like this: - # Alpha Orbital energies/symmetry label: - # -10.25358 Bu -10.25353 Ag -10.21931 Bu -10.21927 Ag - # -10.21792 Bu -10.21782 Ag -10.21773 Bu -10.21772 Ag - # ... - # Jaguar 6.5: prints both only for restricted calculations, - # so for unrestricted calculations the output it looks like this: - # Alpha Orbital energies: - # -10.25358 -10.25353 -10.21931 -10.21927 -10.21792 -10.21782 - # -10.21773 -10.21772 -10.21537 -10.21537 -1.02078 -0.96193 - # ... - # Presence of 'Orbital energies' is enough to catch all versions. - if "Orbital energies" in line: - - # Parsing results is identical for restricted/unrestricted - # calculations, just assert later that alpha/beta order is OK. - spin = int(line[2:6] == "Beta") - - # Check if symmetries are printed also. - issyms = "symmetry label" in line - - if not hasattr(self, "moenergies"): - self.moenergies = [] - if issyms and not hasattr(self, "mosyms"): - self.mosyms = [] - - # Grow moeneriges/mosyms and make sure they are empty when - # parsed multiple times - currently cclib returns only - # the final output (ex. in a geomtry optimization). - if len(self.moenergies) < spin+1: - self.moenergies.append([]) - self.moenergies[spin] = [] - if issyms: - if len(self.mosyms) < spin+1: - self.mosyms.append([]) - self.mosyms[spin] = [] - - line = inputfile.next().split() - while len(line) > 0: - if issyms: - energies = [float(line[2*i]) for i in range(len(line)/2)] - syms = [line[2*i+1] for i in range(len(line)/2)] - else: - energies = [float(e) for e in line] - energies = [utils.convertor(e, "hartree", "eV") for e in energies] - self.moenergies[spin].extend(energies) - if issyms: - syms = [self.normalisesym(s) for s in syms] - self.mosyms[spin].extend(syms) - line = inputfile.next().split() - - # There should always be an extra blank line after all this. - line = inputfile.next() - - if line.find("Occupied + virtual Orbitals- final wvfn") > 0: - - blank = inputfile.next() - stars = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - - if not hasattr(self,"mocoeffs"): - if self.unrestrictedflag: - spin = 2 - else: - spin = 1 - - self.mocoeffs = [] - - - aonames = [] - lastatom = "X" - - readatombasis = False - if not hasattr(self, "atombasis"): - self.atombasis = [] - for i in range(self.natom): - self.atombasis.append([]) - readatombasis = True - - offset = 0 - - for s in range(spin): - mocoeffs = numpy.zeros((len(self.moenergies[s]), self.nbasis), "d") - - if s == 1: #beta case - stars = inputfile.next() - blank = inputfile.next() - title = inputfile.next() - blank = inputfile.next() - stars = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - - for k in range(0,len(self.moenergies[s]),5): - - numbers = inputfile.next() - eigens = inputfile.next() - line = inputfile.next() - - for i in range(self.nbasis): - - info = line.split() - - # Fill atombasis only first time around. - if readatombasis and k == 0: - orbno = int(info[0]) - atom = info[1] - if atom[1].isalpha(): - atomno = int(atom[2:]) - else: - atomno = int(atom[1:]) - self.atombasis[atomno-1].append(orbno-1) - - if not hasattr(self,"aonames"): - if lastatom != info[1]: - scount = 1 - pcount = 3 - dcount = 6 #six d orbitals in Jaguar - - if info[2] == 'S': - aonames.append("%s_%i%s"%(info[1], scount, info[2])) - scount += 1 - - if info[2] == 'X' or info[2] == 'Y' or info[2] == 'Z': - aonames.append("%s_%iP%s"%(info[1], pcount / 3, info[2])) - pcount += 1 - - if info[2] == 'XX' or info[2] == 'YY' or info[2] == 'ZZ' or \ - info[2] == 'XY' or info[2] == 'XZ' or info[2] == 'YZ': - - aonames.append("%s_%iD%s"%(info[1], dcount / 6, info[2])) - dcount += 1 - - lastatom = info[1] - - for j in range(len(info[3:])): - mocoeffs[j+k,i] = float(info[3+j]) - - line = inputfile.next() - - if not hasattr(self,"aonames"): - self.aonames = aonames - - offset += 5 - self.mocoeffs.append(mocoeffs) - - - if line[2:6] == "olap": - if line[6]=="-": - return - # This was continue (in loop) before parser refactoring. - # continue # avoid "olap-dev" - self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") - - for i in range(0, self.nbasis, 5): - blank = inputfile.next() - header = inputfile.next() - for j in range(i, self.nbasis): - temp = map(float, inputfile.next().split()[1:]) - self.aooverlaps[j, i:(i+len(temp))] = temp - self.aooverlaps[i:(i+len(temp)), j] = temp - - if line[1:28] == "number of occupied orbitals": - self.homos = numpy.array([float(line.strip().split()[-1])-1], "i") - - if line[2:27] == "number of basis functions": - self.nbasis = int(line.strip().split()[-1]) - - # IR output looks like this: - # frequencies 72.45 113.25 176.88 183.76 267.60 312.06 - # symmetries Au Bg Au Bu Ag Bg - # intensities 0.07 0.00 0.28 0.52 0.00 0.00 - # reduc. mass 1.90 0.74 1.06 1.42 1.19 0.85 - # force const 0.01 0.01 0.02 0.03 0.05 0.05 - # C1 X 0.00000 0.00000 0.00000 -0.05707 -0.06716 0.00000 - # C1 Y 0.00000 0.00000 0.00000 0.00909 -0.02529 0.00000 - # C1 Z 0.04792 -0.06032 -0.01192 0.00000 0.00000 0.11613 - # C2 X 0.00000 0.00000 0.00000 -0.06094 -0.04635 0.00000 - # ... etc. ... - # This is a complete ouput, some files will not have intensities, - # and older Jaguar versions sometimes skip the symmetries. - if line[2:23] == "start of program freq": - - self.vibfreqs = [] - self.vibdisps = [] - forceconstants = False - intensities = False - blank = inputfile.next() - line = inputfile.next() - while line.strip(): - if "force const" in line: - forceconstants = True - if "intensities" in line: - intensities = True - line = inputfile.next() - freqs = inputfile.next() - - # The last block has an extra blank line after it - catch it. - while freqs.strip(): - - # Number of modes (columns printed in this block). - nmodes = len(freqs.split())-1 - - # Append the frequencies. - self.vibfreqs.extend(map(float, freqs.split()[1:])) - line = inputfile.next().split() - - # May skip symmetries (older Jaguar versions). - if line[0] == "symmetries": - if not hasattr(self, "vibsyms"): - self.vibsyms = [] - self.vibsyms.extend(map(self.normalisesym, line[1:])) - line = inputfile.next().split() - if intensities: - if not hasattr(self, "vibirs"): - self.vibirs = [] - self.vibirs.extend(map(float, line[1:])) - line = inputfile.next().split() - if forceconstants: - line = inputfile.next() - - # Start parsing the displacements. - # Variable 'q' holds up to 7 lists of triplets. - q = [ [] for i in range(7) ] - for n in range(self.natom): - # Variable 'p' holds up to 7 triplets. - p = [ [] for i in range(7) ] - for i in range(3): - line = inputfile.next() - disps = [float(disp) for disp in line.split()[2:]] - for j in range(nmodes): - p[j].append(disps[j]) - for i in range(nmodes): - q[i].append(p[i]) - - self.vibdisps.extend(q[:nmodes]) - blank = inputfile.next() - freqs = inputfile.next() - - # Convert new data to arrays. - self.vibfreqs = numpy.array(self.vibfreqs, "d") - self.vibdisps = numpy.array(self.vibdisps, "d") - if hasattr(self, "vibirs"): - self.vibirs = numpy.array(self.vibirs, "d") - - # Parse excited state output (for CIS calculations). - # Jaguar calculates only singlet states. - if line[2:15] == "Excited State": - if not hasattr(self, "etenergies"): - self.etenergies = [] - if not hasattr(self, "etoscs"): - self.etoscs = [] - if not hasattr(self, "etsecs"): - self.etsecs = [] - self.etsyms = [] - etenergy = float(line.split()[3]) - etenergy = utils.convertor(etenergy, "eV", "cm-1") - self.etenergies.append(etenergy) - # Skip 4 lines - for i in range(5): - line = inputfile.next() - self.etsecs.append([]) - # Jaguar calculates only singlet states. - self.etsyms.append('Singlet-A') - while line.strip() != "": - fromMO = int(line.split()[0])-1 - toMO = int(line.split()[2])-1 - coeff = float(line.split()[-1]) - self.etsecs[-1].append([(fromMO,0),(toMO,0),coeff]) - line = inputfile.next() - # Skip 3 lines - for i in range(4): - line = inputfile.next() - strength = float(line.split()[-1]) - self.etoscs.append(strength) - - -if __name__ == "__main__": - import doctest, jaguarparser - doctest.testmod(jaguarparser, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 861 $" + + +import re + +import numpy + +import logfileparser +import utils + + +class Jaguar(logfileparser.Logfile): + """A Jaguar output file""" + + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(Jaguar, self).__init__(logname="Jaguar", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "Jaguar output file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'Jaguar("%s")' % (self.filename) + + def normalisesym(self, label): + """Normalise the symmetries used by Jaguar. + + To normalise, three rules need to be applied: + (1) To handle orbitals of E symmetry, retain everything before the / + (2) Replace two p's by " + (2) Replace any remaining single p's by ' + + >>> t = Jaguar("dummyfile").normalisesym + >>> labels = ['A', 'A1', 'Ag', 'Ap', 'App', "A1p", "A1pp", "E1pp/Ap"] + >>> answers = map(t, labels) + >>> print answers + ['A', 'A1', 'Ag', "A'", 'A"', "A1'", 'A1"', 'E1"'] + """ + ans = label.split("/")[0].replace("pp", '"').replace("p", "'") + return ans + + def before_parsing(self): + + self.geoopt = False # Is this a GeoOpt? Needed for SCF targets/values. + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + if line[0:4] == "etot": + # Get SCF convergence information + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + self.scftargets = [[5E-5, 5E-6]] + values = [] + while line[0:4] == "etot": + # Jaguar 4.2 + # etot 1 N N 0 N -382.08751886450 2.3E-03 1.4E-01 + # etot 2 Y Y 0 N -382.27486023153 1.9E-01 1.4E-03 5.7E-02 + # Jaguar 6.5 + # etot 1 N N 0 N -382.08751881733 2.3E-03 1.4E-01 + # etot 2 Y Y 0 N -382.27486018708 1.9E-01 1.4E-03 5.7E-02 + temp = line.split()[7:] + if len(temp)==3: + denergy = float(temp[0]) + else: + denergy = 0 # Should really be greater than target value + # or should we just ignore the values in this line + ddensity = float(temp[-2]) + maxdiiserr = float(temp[-1]) + if not self.geoopt: + values.append([denergy, ddensity]) + else: + values.append([ddensity]) + line = inputfile.next() + self.scfvalues.append(values) + + # Hartree-Fock energy after SCF + if line[1:18] == "SCFE: SCF energy:": + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + temp = line.strip().split() + scfenergy = float(temp[temp.index("hartrees") - 1]) + scfenergy = utils.convertor(scfenergy, "hartree", "eV") + self.scfenergies.append(scfenergy) + + # Energy after LMP2 correction + if line[1:18] == "Total LMP2 Energy": + if not hasattr(self, "mpenergies"): + self.mpenergies = [[]] + lmp2energy = float(line.split()[-1]) + lmp2energy = utils.convertor(lmp2energy, "hartree", "eV") + self.mpenergies[-1].append(lmp2energy) + + if line[2:14] == "new geometry" or line[1:21] == "Symmetrized geometry" or line.find("Input geometry") > 0: + # Get the atom coordinates + if not hasattr(self, "atomcoords") or line[1:21] == "Symmetrized geometry": + # Wipe the "Input geometry" if "Symmetrized geometry" present + self.atomcoords = [] + p = re.compile("(\D+)\d+") # One/more letters followed by a number + atomcoords = [] + atomnos = [] + angstrom = inputfile.next() + title = inputfile.next() + line = inputfile.next() + while line.strip(): + temp = line.split() + element = p.findall(temp[0])[0] + atomnos.append(self.table.number[element]) + atomcoords.append(map(float, temp[1:])) + line = inputfile.next() + self.atomcoords.append(atomcoords) + self.atomnos = numpy.array(atomnos, "i") + self.natom = len(atomcoords) + + # Extract charge and multiplicity + if line[2:22] == "net molecular charge": + self.charge = int(line.split()[-1]) + self.mult = int(inputfile.next().split()[-1]) + + if line[2:24] == "start of program geopt": + if not self.geoopt: + # Need to keep only the RMS density change info + # if this is a geoopt + self.scftargets = [[self.scftargets[0][0]]] + if hasattr(self, "scfvalues"): + self.scfvalues[0] = [[x[0]] for x in self.scfvalues[0]] + self.geoopt = True + else: + self.scftargets.append([5E-5]) + + if line[2:28] == "geometry optimization step": + # Get Geometry Opt convergence information + if not hasattr(self, "geovalues"): + self.geovalues = [] + self.geotargets = numpy.zeros(5, "d") + gopt_step = int(line.split()[-1]) + energy = inputfile.next() + # quick hack for messages of the sort: + # ** restarting optimization from step 2 ** + # as found in regression file ptnh3_2_H2O_2_2plus.out + if inputfile.next().strip(): + blank = inputfile.next() + line = inputfile.next() + values = [] + target_index = 0 + if gopt_step == 1: + # The first optimization step does not produce an energy change + values.append(0.0) + target_index = 1 + while line.strip(): + if len(line) > 40 and line[41] == "(": + # A new geo convergence value + values.append(float(line[26:37])) + self.geotargets[target_index] = float(line[43:54]) + target_index += 1 + line = inputfile.next() + self.geovalues.append(values) + + if line.find("number of occupied orbitals") > 0: + # Get number of MOs + occs = int(line.split()[-1]) + line = inputfile.next() + virts = int(line.split()[-1]) + self.nmo = occs + virts + self.homos = numpy.array([occs-1], "i") + + self.unrestrictedflag = False + + if line.find("number of alpha occupied orb") > 0: + # Get number of MOs for an unrestricted calc + + aoccs = int(line.split()[-1]) + line = inputfile.next() + avirts = int(line.split()[-1]) + line = inputfile.next() + boccs = int(line.split()[-1]) + line = inputfile.next() + bvirt = int(line.split()[-1]) + + self.nmo = aoccs + avirts + self.homos = numpy.array([aoccs-1,boccs-1], "i") + self.unrestrictedflag = True + + # MO energies and symmetries. + # Jaguar 7.0: provides energies and symmetries for both + # restricted and unrestricted calculations, like this: + # Alpha Orbital energies/symmetry label: + # -10.25358 Bu -10.25353 Ag -10.21931 Bu -10.21927 Ag + # -10.21792 Bu -10.21782 Ag -10.21773 Bu -10.21772 Ag + # ... + # Jaguar 6.5: prints both only for restricted calculations, + # so for unrestricted calculations the output it looks like this: + # Alpha Orbital energies: + # -10.25358 -10.25353 -10.21931 -10.21927 -10.21792 -10.21782 + # -10.21773 -10.21772 -10.21537 -10.21537 -1.02078 -0.96193 + # ... + # Presence of 'Orbital energies' is enough to catch all versions. + if "Orbital energies" in line: + + # Parsing results is identical for restricted/unrestricted + # calculations, just assert later that alpha/beta order is OK. + spin = int(line[2:6] == "Beta") + + # Check if symmetries are printed also. + issyms = "symmetry label" in line + + if not hasattr(self, "moenergies"): + self.moenergies = [] + if issyms and not hasattr(self, "mosyms"): + self.mosyms = [] + + # Grow moeneriges/mosyms and make sure they are empty when + # parsed multiple times - currently cclib returns only + # the final output (ex. in a geomtry optimization). + if len(self.moenergies) < spin+1: + self.moenergies.append([]) + self.moenergies[spin] = [] + if issyms: + if len(self.mosyms) < spin+1: + self.mosyms.append([]) + self.mosyms[spin] = [] + + line = inputfile.next().split() + while len(line) > 0: + if issyms: + energies = [float(line[2*i]) for i in range(len(line)/2)] + syms = [line[2*i+1] for i in range(len(line)/2)] + else: + energies = [float(e) for e in line] + energies = [utils.convertor(e, "hartree", "eV") for e in energies] + self.moenergies[spin].extend(energies) + if issyms: + syms = [self.normalisesym(s) for s in syms] + self.mosyms[spin].extend(syms) + line = inputfile.next().split() + + # There should always be an extra blank line after all this. + line = inputfile.next() + + if line.find("Occupied + virtual Orbitals- final wvfn") > 0: + + blank = inputfile.next() + stars = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + + if not hasattr(self,"mocoeffs"): + if self.unrestrictedflag: + spin = 2 + else: + spin = 1 + + self.mocoeffs = [] + + + aonames = [] + lastatom = "X" + + readatombasis = False + if not hasattr(self, "atombasis"): + self.atombasis = [] + for i in range(self.natom): + self.atombasis.append([]) + readatombasis = True + + offset = 0 + + for s in range(spin): + mocoeffs = numpy.zeros((len(self.moenergies[s]), self.nbasis), "d") + + if s == 1: #beta case + stars = inputfile.next() + blank = inputfile.next() + title = inputfile.next() + blank = inputfile.next() + stars = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + + for k in range(0,len(self.moenergies[s]),5): + + numbers = inputfile.next() + eigens = inputfile.next() + line = inputfile.next() + + for i in range(self.nbasis): + + info = line.split() + + # Fill atombasis only first time around. + if readatombasis and k == 0: + orbno = int(info[0]) + atom = info[1] + if atom[1].isalpha(): + atomno = int(atom[2:]) + else: + atomno = int(atom[1:]) + self.atombasis[atomno-1].append(orbno-1) + + if not hasattr(self,"aonames"): + if lastatom != info[1]: + scount = 1 + pcount = 3 + dcount = 6 #six d orbitals in Jaguar + + if info[2] == 'S': + aonames.append("%s_%i%s"%(info[1], scount, info[2])) + scount += 1 + + if info[2] == 'X' or info[2] == 'Y' or info[2] == 'Z': + aonames.append("%s_%iP%s"%(info[1], pcount / 3, info[2])) + pcount += 1 + + if info[2] == 'XX' or info[2] == 'YY' or info[2] == 'ZZ' or \ + info[2] == 'XY' or info[2] == 'XZ' or info[2] == 'YZ': + + aonames.append("%s_%iD%s"%(info[1], dcount / 6, info[2])) + dcount += 1 + + lastatom = info[1] + + for j in range(len(info[3:])): + mocoeffs[j+k,i] = float(info[3+j]) + + line = inputfile.next() + + if not hasattr(self,"aonames"): + self.aonames = aonames + + offset += 5 + self.mocoeffs.append(mocoeffs) + + + if line[2:6] == "olap": + if line[6]=="-": + return + # This was continue (in loop) before parser refactoring. + # continue # avoid "olap-dev" + self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") + + for i in range(0, self.nbasis, 5): + blank = inputfile.next() + header = inputfile.next() + for j in range(i, self.nbasis): + temp = map(float, inputfile.next().split()[1:]) + self.aooverlaps[j, i:(i+len(temp))] = temp + self.aooverlaps[i:(i+len(temp)), j] = temp + + if line[1:28] == "number of occupied orbitals": + self.homos = numpy.array([float(line.strip().split()[-1])-1], "i") + + if line[2:27] == "number of basis functions": + self.nbasis = int(line.strip().split()[-1]) + + # IR output looks like this: + # frequencies 72.45 113.25 176.88 183.76 267.60 312.06 + # symmetries Au Bg Au Bu Ag Bg + # intensities 0.07 0.00 0.28 0.52 0.00 0.00 + # reduc. mass 1.90 0.74 1.06 1.42 1.19 0.85 + # force const 0.01 0.01 0.02 0.03 0.05 0.05 + # C1 X 0.00000 0.00000 0.00000 -0.05707 -0.06716 0.00000 + # C1 Y 0.00000 0.00000 0.00000 0.00909 -0.02529 0.00000 + # C1 Z 0.04792 -0.06032 -0.01192 0.00000 0.00000 0.11613 + # C2 X 0.00000 0.00000 0.00000 -0.06094 -0.04635 0.00000 + # ... etc. ... + # This is a complete ouput, some files will not have intensities, + # and older Jaguar versions sometimes skip the symmetries. + if line[2:23] == "start of program freq": + + self.vibfreqs = [] + self.vibdisps = [] + forceconstants = False + intensities = False + blank = inputfile.next() + line = inputfile.next() + while line.strip(): + if "force const" in line: + forceconstants = True + if "intensities" in line: + intensities = True + line = inputfile.next() + freqs = inputfile.next() + + # The last block has an extra blank line after it - catch it. + while freqs.strip(): + + # Number of modes (columns printed in this block). + nmodes = len(freqs.split())-1 + + # Append the frequencies. + self.vibfreqs.extend(map(float, freqs.split()[1:])) + line = inputfile.next().split() + + # May skip symmetries (older Jaguar versions). + if line[0] == "symmetries": + if not hasattr(self, "vibsyms"): + self.vibsyms = [] + self.vibsyms.extend(map(self.normalisesym, line[1:])) + line = inputfile.next().split() + if intensities: + if not hasattr(self, "vibirs"): + self.vibirs = [] + self.vibirs.extend(map(float, line[1:])) + line = inputfile.next().split() + if forceconstants: + line = inputfile.next() + + # Start parsing the displacements. + # Variable 'q' holds up to 7 lists of triplets. + q = [ [] for i in range(7) ] + for n in range(self.natom): + # Variable 'p' holds up to 7 triplets. + p = [ [] for i in range(7) ] + for i in range(3): + line = inputfile.next() + disps = [float(disp) for disp in line.split()[2:]] + for j in range(nmodes): + p[j].append(disps[j]) + for i in range(nmodes): + q[i].append(p[i]) + + self.vibdisps.extend(q[:nmodes]) + blank = inputfile.next() + freqs = inputfile.next() + + # Convert new data to arrays. + self.vibfreqs = numpy.array(self.vibfreqs, "d") + self.vibdisps = numpy.array(self.vibdisps, "d") + if hasattr(self, "vibirs"): + self.vibirs = numpy.array(self.vibirs, "d") + + # Parse excited state output (for CIS calculations). + # Jaguar calculates only singlet states. + if line[2:15] == "Excited State": + if not hasattr(self, "etenergies"): + self.etenergies = [] + if not hasattr(self, "etoscs"): + self.etoscs = [] + if not hasattr(self, "etsecs"): + self.etsecs = [] + self.etsyms = [] + etenergy = float(line.split()[3]) + etenergy = utils.convertor(etenergy, "eV", "cm-1") + self.etenergies.append(etenergy) + # Skip 4 lines + for i in range(5): + line = inputfile.next() + self.etsecs.append([]) + # Jaguar calculates only singlet states. + self.etsyms.append('Singlet-A') + while line.strip() != "": + fromMO = int(line.split()[0])-1 + toMO = int(line.split()[2])-1 + coeff = float(line.split()[-1]) + self.etsecs[-1].append([(fromMO,0),(toMO,0),coeff]) + line = inputfile.next() + # Skip 3 lines + for i in range(4): + line = inputfile.next() + strength = float(line.split()[-1]) + self.etoscs.append(strength) + + +if __name__ == "__main__": + import doctest, jaguarparser + doctest.testmod(jaguarparser, verbose=False) diff --git a/external/cclib/parser/logfileparser.py b/external/cclib/parser/logfileparser.py index cdd3ba9577..b7d8bfd5d2 100644 --- a/external/cclib/parser/logfileparser.py +++ b/external/cclib/parser/logfileparser.py @@ -1,300 +1,300 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 879 $" - - -import StringIO - -try: - import bz2 # New in Python 2.3. -except ImportError: - bz2 = None -import fileinput -import gzip -import inspect -import logging -logging.logMultiprocessing = 0 # To avoid a problem with Avogadro -import os -import random -try: - set # Standard type from Python 2.4+. -except NameError: - from sets import Set as set -import sys -import types -import zipfile - -import numpy - -import utils -from data import ccData - - -def openlogfile(filename): - """Return a file object given a filename. - - Given the filename of a log file or a gzipped, zipped, or bzipped - log file, this function returns a regular Python file object. - - Given an address starting with http://, this function retrieves the url - and returns a file object using a temporary file. - - Given a list of filenames, this function returns a FileInput object, - which can be used for seamless iteration without concatenation. - """ - - # If there is a single string argument given. - if type(filename) in [str, unicode]: - - extension = os.path.splitext(filename)[1] - - if extension == ".gz": - fileobject = gzip.open(filename, "r") - - elif extension == ".zip": - zip = zipfile.ZipFile(filename, "r") - assert len(zip.namelist()) == 1, "ERROR: Zip file contains more than 1 file" - fileobject = StringIO.StringIO(zip.read(zip.namelist()[0])) - - elif extension in ['.bz', '.bz2']: - # Module 'bz2' is not always importable. - assert bz2 != None, "ERROR: module bz2 cannot be imported" - fileobject = bz2.BZ2File(filename, "r") - - else: - fileobject = open(filename, "r") - - return fileobject - - elif hasattr(filename, "__iter__"): - - # Compression (gzip and bzip) is supported as of Python 2.5. - if sys.version_info[0] >= 2 and sys.version_info[1] >= 5: - fileobject = fileinput.input(filename, openhook=fileinput.hook_compressed) - else: - fileobject = fileinput.input(filename) - - return fileobject - - -class Logfile(object): - """Abstract class for logfile objects. - - Subclasses defined by cclib: - ADF, GAMESS, GAMESSUK, Gaussian, Jaguar, Molpro, ORCA - - """ - - def __init__(self, source, progress=None, - loglevel=logging.INFO, logname="Log", logstream=sys.stdout, - fupdate=0.05, cupdate=0.002, - datatype=ccData): - """Initialise the Logfile object. - - This should be called by a ubclass in its own __init__ method. - - Inputs: - source - a single logfile, a list of logfiles, or input stream - """ - - # Set the filename to source if it is a string or a list of filenames. - # In the case of an input stream, set some arbitrary name and the stream. - # Elsewise, raise an Exception. - if isinstance(source,types.StringTypes): - self.filename = source - self.isstream = False - elif isinstance(source,list) and all([isinstance(s,types.StringTypes) for s in source]): - self.filename = source - self.isstream = False - elif hasattr(source, "read"): - self.filename = "stream %s" %str(type(source)) - self.isstream = True - self.stream = source - else: - raise ValueError - - # Progress indicator. - self.progress = progress - self.fupdate = fupdate - self.cupdate = cupdate - - # Set up the logger. - # Note that calling logging.getLogger() with one name always returns the same instance. - # Presently in cclib, all parser instances of the same class use the same logger, - # which means that care needs to be taken not to duplicate handlers. - self.loglevel = loglevel - self.logname = logname - self.logger = logging.getLogger('%s %s' % (self.logname,self.filename)) - self.logger.setLevel(self.loglevel) - if len(self.logger.handlers) == 0: - handler = logging.StreamHandler(logstream) - handler.setFormatter(logging.Formatter("[%(name)s %(levelname)s] %(message)s")) - self.logger.addHandler(handler) - - # Periodic table of elements. - self.table = utils.PeriodicTable() - - # This is the class that will be used in the data object returned by parse(), - # and should normally be ccData or a subclass. - self.datatype = datatype - - def __setattr__(self, name, value): - - # Send info to logger if the attribute is in the list self._attrlist. - if name in getattr(self, "_attrlist", {}) and hasattr(self, "logger"): - - # Call logger.info() only if the attribute is new. - if not hasattr(self, name): - if type(value) in [numpy.ndarray, list]: - self.logger.info("Creating attribute %s[]" %name) - else: - self.logger.info("Creating attribute %s: %s" %(name, str(value))) - - # Set the attribute. - object.__setattr__(self, name, value) - - def parse(self, fupdate=None, cupdate=None): - """Parse the logfile, using the assumed extract method of the child.""" - - # Check that the sub-class has an extract attribute, - # that is callable with the proper number of arguemnts. - if not hasattr(self, "extract"): - raise AttributeError, "Class %s has no extract() method." %self.__class__.__name__ - return -1 - if not callable(self.extract): - raise AttributeError, "Method %s._extract not callable." %self.__class__.__name__ - return -1 - if len(inspect.getargspec(self.extract)[0]) != 3: - raise AttributeError, "Method %s._extract takes wrong number of arguments." %self.__class__.__name__ - return -1 - - # Save the current list of attributes to keep after parsing. - # The dict of self should be the same after parsing. - _nodelete = list(set(self.__dict__.keys())) - - # Initiate the FileInput object for the input files. - # Remember that self.filename can be a list of files. - if not self.isstream: - inputfile = openlogfile(self.filename) - else: - inputfile = self.stream - - # Intialize self.progress. - if self.progress: - inputfile.seek(0,2) - nstep = inputfile.tell() - inputfile.seek(0) - self.progress.initialize(nstep) - self.progress.step = 0 - if fupdate: - self.fupdate = fupdate - if cupdate: - self.cupdate = cupdate - - # Initialize the ccData object that will be returned. - # This is normally ccData, but can be changed by passing - # the datatype argument to __init__(). - data = self.datatype() - - # Copy the attribute list, so that the parser knows what to expect, - # specifically in __setattr__(). - # The class self.datatype (normally ccData) must have this attribute. - self._attrlist = data._attrlist - - # Maybe the sub-class has something to do before parsing. - if hasattr(self, "before_parsing"): - self.before_parsing() - - # Loop over lines in the file object and call extract(). - # This is where the actual parsing is done. - for line in inputfile: - - self.updateprogress(inputfile, "Unsupported information", cupdate) - - # This call should check if the line begins a section of extracted data. - # If it does, it parses some lines and sets the relevant attributes (to self). - # Any attributes can be freely set and used across calls, however only those - # in data._attrlist will be moved to final data object that is returned. - self.extract(inputfile, line) - - # Close input file object. - if not self.isstream: - inputfile.close() - - # Maybe the sub-class has something to do after parsing. - if hasattr(self, "after_parsing"): - self.after_parsing() - - # If atomcoords were not parsed, but some input coordinates were ("inputcoords"). - # This is originally from the Gaussian parser, a regression fix. - if not hasattr(self, "atomcoords") and hasattr(self, "inputcoords"): - self.atomcoords = numpy.array(self.inputcoords, 'd') - - # Set nmo if not set already - to nbasis. - if not hasattr(self, "nmo") and hasattr(self, "nbasis"): - self.nmo = self.nbasis - - # Creating deafult coreelectrons array. - if not hasattr(self, "coreelectrons") and hasattr(self, "natom"): - self.coreelectrons = numpy.zeros(self.natom, "i") - - # Move all cclib attributes to the ccData object. - # To be moved, an attribute must be in data._attrlist. - for attr in data._attrlist: - if hasattr(self, attr): - setattr(data, attr, getattr(self, attr)) - - # Now make sure that the cclib attributes in the data object - # are all the correct type (including arrays and lists of arrays). - data.arrayify() - - # Delete all temporary attributes (including cclib attributes). - # All attributes should have been moved to a data object, - # which will be returned. - for attr in self.__dict__.keys(): - if not attr in _nodelete: - self.__delattr__(attr) - - # Update self.progress as done. - if self.progress: - self.progress.update(nstep, "Done") - - # Return the ccData object that was generated. - return data - - def updateprogress(self, inputfile, msg, xupdate=0.05): - """Update progress.""" - - if self.progress and random.random() < xupdate: - newstep = inputfile.tell() - if newstep != self.progress.step: - self.progress.update(newstep, msg) - self.progress.step = newstep - - def normalisesym(self,symlabel): - """Standardise the symmetry labels between parsers. - - This method should be overwritten by individual parsers, and should - contain appropriate doctests. If is not overwritten, this is detected - as an error by unit tests. - """ - return "ERROR: This should be overwritten by this subclass" - - def float(self,number): - """Convert a string to a float avoiding the problem with Ds. - - >>> t = Logfile("dummyfile") - >>> t.float("123.2323E+02") - 12323.23 - >>> t.float("123.2323D+02") - 12323.23 - """ - number = number.replace("D","E") - return float(number) - -if __name__=="__main__": - import doctest - doctest.testmod() +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 879 $" + + +import StringIO + +try: + import bz2 # New in Python 2.3. +except ImportError: + bz2 = None +import fileinput +import gzip +import inspect +import logging +logging.logMultiprocessing = 0 # To avoid a problem with Avogadro +import os +import random +try: + set # Standard type from Python 2.4+. +except NameError: + from sets import Set as set +import sys +import types +import zipfile + +import numpy + +import utils +from data import ccData + + +def openlogfile(filename): + """Return a file object given a filename. + + Given the filename of a log file or a gzipped, zipped, or bzipped + log file, this function returns a regular Python file object. + + Given an address starting with http://, this function retrieves the url + and returns a file object using a temporary file. + + Given a list of filenames, this function returns a FileInput object, + which can be used for seamless iteration without concatenation. + """ + + # If there is a single string argument given. + if type(filename) in [str, unicode]: + + extension = os.path.splitext(filename)[1] + + if extension == ".gz": + fileobject = gzip.open(filename, "r") + + elif extension == ".zip": + zip = zipfile.ZipFile(filename, "r") + assert len(zip.namelist()) == 1, "ERROR: Zip file contains more than 1 file" + fileobject = StringIO.StringIO(zip.read(zip.namelist()[0])) + + elif extension in ['.bz', '.bz2']: + # Module 'bz2' is not always importable. + assert bz2 != None, "ERROR: module bz2 cannot be imported" + fileobject = bz2.BZ2File(filename, "r") + + else: + fileobject = open(filename, "r") + + return fileobject + + elif hasattr(filename, "__iter__"): + + # Compression (gzip and bzip) is supported as of Python 2.5. + if sys.version_info[0] >= 2 and sys.version_info[1] >= 5: + fileobject = fileinput.input(filename, openhook=fileinput.hook_compressed) + else: + fileobject = fileinput.input(filename) + + return fileobject + + +class Logfile(object): + """Abstract class for logfile objects. + + Subclasses defined by cclib: + ADF, GAMESS, GAMESSUK, Gaussian, Jaguar, Molpro, ORCA + + """ + + def __init__(self, source, progress=None, + loglevel=logging.INFO, logname="Log", logstream=sys.stdout, + fupdate=0.05, cupdate=0.002, + datatype=ccData): + """Initialise the Logfile object. + + This should be called by a ubclass in its own __init__ method. + + Inputs: + source - a single logfile, a list of logfiles, or input stream + """ + + # Set the filename to source if it is a string or a list of filenames. + # In the case of an input stream, set some arbitrary name and the stream. + # Elsewise, raise an Exception. + if isinstance(source,types.StringTypes): + self.filename = source + self.isstream = False + elif isinstance(source,list) and all([isinstance(s,types.StringTypes) for s in source]): + self.filename = source + self.isstream = False + elif hasattr(source, "read"): + self.filename = "stream %s" %str(type(source)) + self.isstream = True + self.stream = source + else: + raise ValueError + + # Progress indicator. + self.progress = progress + self.fupdate = fupdate + self.cupdate = cupdate + + # Set up the logger. + # Note that calling logging.getLogger() with one name always returns the same instance. + # Presently in cclib, all parser instances of the same class use the same logger, + # which means that care needs to be taken not to duplicate handlers. + self.loglevel = loglevel + self.logname = logname + self.logger = logging.getLogger('%s %s' % (self.logname,self.filename)) + self.logger.setLevel(self.loglevel) + if len(self.logger.handlers) == 0: + handler = logging.StreamHandler(logstream) + handler.setFormatter(logging.Formatter("[%(name)s %(levelname)s] %(message)s")) + self.logger.addHandler(handler) + + # Periodic table of elements. + self.table = utils.PeriodicTable() + + # This is the class that will be used in the data object returned by parse(), + # and should normally be ccData or a subclass. + self.datatype = datatype + + def __setattr__(self, name, value): + + # Send info to logger if the attribute is in the list self._attrlist. + if name in getattr(self, "_attrlist", {}) and hasattr(self, "logger"): + + # Call logger.info() only if the attribute is new. + if not hasattr(self, name): + if type(value) in [numpy.ndarray, list]: + self.logger.info("Creating attribute %s[]" %name) + else: + self.logger.info("Creating attribute %s: %s" %(name, str(value))) + + # Set the attribute. + object.__setattr__(self, name, value) + + def parse(self, fupdate=None, cupdate=None): + """Parse the logfile, using the assumed extract method of the child.""" + + # Check that the sub-class has an extract attribute, + # that is callable with the proper number of arguemnts. + if not hasattr(self, "extract"): + raise AttributeError, "Class %s has no extract() method." %self.__class__.__name__ + return -1 + if not callable(self.extract): + raise AttributeError, "Method %s._extract not callable." %self.__class__.__name__ + return -1 + if len(inspect.getargspec(self.extract)[0]) != 3: + raise AttributeError, "Method %s._extract takes wrong number of arguments." %self.__class__.__name__ + return -1 + + # Save the current list of attributes to keep after parsing. + # The dict of self should be the same after parsing. + _nodelete = list(set(self.__dict__.keys())) + + # Initiate the FileInput object for the input files. + # Remember that self.filename can be a list of files. + if not self.isstream: + inputfile = openlogfile(self.filename) + else: + inputfile = self.stream + + # Intialize self.progress. + if self.progress: + inputfile.seek(0,2) + nstep = inputfile.tell() + inputfile.seek(0) + self.progress.initialize(nstep) + self.progress.step = 0 + if fupdate: + self.fupdate = fupdate + if cupdate: + self.cupdate = cupdate + + # Initialize the ccData object that will be returned. + # This is normally ccData, but can be changed by passing + # the datatype argument to __init__(). + data = self.datatype() + + # Copy the attribute list, so that the parser knows what to expect, + # specifically in __setattr__(). + # The class self.datatype (normally ccData) must have this attribute. + self._attrlist = data._attrlist + + # Maybe the sub-class has something to do before parsing. + if hasattr(self, "before_parsing"): + self.before_parsing() + + # Loop over lines in the file object and call extract(). + # This is where the actual parsing is done. + for line in inputfile: + + self.updateprogress(inputfile, "Unsupported information", cupdate) + + # This call should check if the line begins a section of extracted data. + # If it does, it parses some lines and sets the relevant attributes (to self). + # Any attributes can be freely set and used across calls, however only those + # in data._attrlist will be moved to final data object that is returned. + self.extract(inputfile, line) + + # Close input file object. + if not self.isstream: + inputfile.close() + + # Maybe the sub-class has something to do after parsing. + if hasattr(self, "after_parsing"): + self.after_parsing() + + # If atomcoords were not parsed, but some input coordinates were ("inputcoords"). + # This is originally from the Gaussian parser, a regression fix. + if not hasattr(self, "atomcoords") and hasattr(self, "inputcoords"): + self.atomcoords = numpy.array(self.inputcoords, 'd') + + # Set nmo if not set already - to nbasis. + if not hasattr(self, "nmo") and hasattr(self, "nbasis"): + self.nmo = self.nbasis + + # Creating deafult coreelectrons array. + if not hasattr(self, "coreelectrons") and hasattr(self, "natom"): + self.coreelectrons = numpy.zeros(self.natom, "i") + + # Move all cclib attributes to the ccData object. + # To be moved, an attribute must be in data._attrlist. + for attr in data._attrlist: + if hasattr(self, attr): + setattr(data, attr, getattr(self, attr)) + + # Now make sure that the cclib attributes in the data object + # are all the correct type (including arrays and lists of arrays). + data.arrayify() + + # Delete all temporary attributes (including cclib attributes). + # All attributes should have been moved to a data object, + # which will be returned. + for attr in self.__dict__.keys(): + if not attr in _nodelete: + self.__delattr__(attr) + + # Update self.progress as done. + if self.progress: + self.progress.update(nstep, "Done") + + # Return the ccData object that was generated. + return data + + def updateprogress(self, inputfile, msg, xupdate=0.05): + """Update progress.""" + + if self.progress and random.random() < xupdate: + newstep = inputfile.tell() + if newstep != self.progress.step: + self.progress.update(newstep, msg) + self.progress.step = newstep + + def normalisesym(self,symlabel): + """Standardise the symmetry labels between parsers. + + This method should be overwritten by individual parsers, and should + contain appropriate doctests. If is not overwritten, this is detected + as an error by unit tests. + """ + return "ERROR: This should be overwritten by this subclass" + + def float(self,number): + """Convert a string to a float avoiding the problem with Ds. + + >>> t = Logfile("dummyfile") + >>> t.float("123.2323E+02") + 12323.23 + >>> t.float("123.2323D+02") + 12323.23 + """ + number = number.replace("D","E") + return float(number) + +if __name__=="__main__": + import doctest + doctest.testmod() diff --git a/external/cclib/parser/mm4parser.py b/external/cclib/parser/mm4parser.py index 4c6459a761..f431ef07e1 100644 --- a/external/cclib/parser/mm4parser.py +++ b/external/cclib/parser/mm4parser.py @@ -1,260 +1,260 @@ -""" -gmagoon 05/03/10: new class for MM4 parsing, based on mopacparser.py, which, in turn, is based on gaussianparser.py from cclib, described below: -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 814 $" - - -#import re - -import numpy -import math -import utils -import logfileparser - - -def symbol2int(symbol): - t = utils.PeriodicTable() - return t.number[symbol] - -class MM4(logfileparser.Logfile): - """An MM4 output file.""" - - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(MM4, self).__init__(logname="MM4", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "MM4 log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'MM4("%s")' % (self.filename) - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - # Number of atoms. - # Example: THE COORDINATES OF 20 ATOMS ARE READ IN. - if line[0:28] == ' THE COORDINATES OF': - - self.updateprogress(inputfile, "Attributes", self.fupdate) - natom = int(line.split()[-5]) #fifth to last component should be number of atoms - if hasattr(self, "natom"): - assert self.natom == natom - else: - self.natom = natom - - # Extract the atomic numbers and coordinates from the optimized (final) geometry - - # Example: -# FINAL ATOMIC COORDINATE -# ATOM X Y Z TYPE -# C( 1) -3.21470 -0.22058 0.00000 ( 1) -# H( 2) -3.30991 -0.87175 0.89724 ( 5) -# H( 3) -3.30991 -0.87174 -0.89724 ( 5) -# H( 4) -4.08456 0.47380 0.00000 ( 5) -# C( 5) -1.88672 0.54893 0.00000 ( 1) -# H( 6) -1.84759 1.21197 -0.89488 ( 5) -# H( 7) -1.84759 1.21197 0.89488 ( 5) -# C( 8) -0.66560 -0.38447 0.00000 ( 1) -# H( 9) -0.70910 -1.04707 -0.89471 ( 5) -# H( 10) -0.70910 -1.04707 0.89471 ( 5) -# C( 11) 0.66560 0.38447 0.00000 ( 1) -# H( 12) 0.70910 1.04707 0.89471 ( 5) -# H( 13) 0.70910 1.04707 -0.89471 ( 5) -# C( 14) 1.88672 -0.54893 0.00000 ( 1) -# H( 15) 1.84759 -1.21197 -0.89488 ( 5) -# H( 16) 1.84759 -1.21197 0.89488 ( 5) -# C( 17) 3.21470 0.22058 0.00000 ( 1) -# H( 18) 3.30991 0.87174 0.89724 ( 5) -# H( 19) 4.08456 -0.47380 0.00000 ( 5) -# H( 20) 3.30991 0.87175 -0.89724 ( 5) - - if line[0:29] == ' FINAL ATOMIC COORDINATE': - - - self.updateprogress(inputfile, "Attributes", self.cupdate) - - self.inputcoords = [] - self.inputatoms = [] - - headerline = inputfile.next() - - atomcoords = [] - line = inputfile.next() - while len(line.split()) > 0: - broken = line.split() - self.inputatoms.append(symbol2int(line[0:10].strip())) - xc = float(line[17:29]) - yc = float(line[29:41]) - zc = float(line[41:53]) - atomcoords.append([xc,yc,zc]) - line = inputfile.next() - - self.inputcoords.append(atomcoords) - - if not hasattr(self, "atomnos"): - self.atomnos = numpy.array(self.inputatoms, 'i') - if not hasattr(self, "natom"): - self.natom = len(self.atomnos) - - -#read energy (in kcal/mol, converted to eV) -# Example: HEAT OF FORMATION (HFN) AT 298.2 K = -42.51 KCAL/MOLE - if line[0:31] == ' HEAT OF FORMATION (HFN) AT': - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - self.scfenergies.append(utils.convertor(self.float(line.split()[-2])/627.5095, "hartree", "eV")) #note conversion from kcal/mol to hartree - - #molecular mass parsing (units will be amu); note that this can occur multiple times in the file, but all values should be the same - #Example: FORMULA WEIGHT : 86.112 - if line[0:33] == ' FORMULA WEIGHT :': - self.updateprogress(inputfile, "Attributes", self.fupdate) - molmass = self.float(line.split()[-1]) - if hasattr(self, "molmass"): - assert self.molmass == molmass #check that subsequent occurences match the original value - else: - self.molmass = molmass - - #rotational constants (converted to GHZ) - #Example: -# THE MOMENTS OF INERTIA CALCULATED FROM R(g), R(z) VALUES -# (also from R(e), R(alpha), R(s) VALUES) -# -# Note: (1) All calculations are based on principle isotopes. -# (2) R(z) values include harmonic vibration (Coriolis) -# contribution indicated in parentheses. -# -# -# (1) UNIT = 10**(-39) GM*CM**2 -# -# IX IY IZ -# -# R(e) 5.7724 73.4297 76.0735 -# R(z) 5.7221(-0.0518) 74.0311(-0.0285) 76.7102(-0.0064) -# -# (2) UNIT = AU A**2 -# -# IX IY IZ -# -# R(e) 34.7661 442.2527 458.1757 -# R(z) 34.4633(-0.3117) 445.8746(-0.1714) 462.0104(-0.0385) - #moments of inertia converted into rotational constants via rot cons= h/(8*Pi^2*I) - #we will use the equilibrium values (R(e)) in units of 10**-39 GM*CM**2 (these units are less precise (fewer digits) than AU A**2 units but it is simpler as it doesn't require use of Avogadro's number - #***even R(e) may include temperature dependent effects, though, and maybe the one I actually want is r(mm4) (not reported) - if line[0:33] == ' (1) UNIT = 10**(-39) GM*CM**2': - dummyline = inputfile.next(); - dummyline = inputfile.next(); - dummyline = inputfile.next(); - rotinfo=inputfile.next(); - if not hasattr(self, "rotcons"): - self.rotcons = [] - broken = rotinfo.split() - h = 6.62606896E3 #Planck constant in 10^-37 J-s = 10^-37 kg m^2/s cf. http://physics.nist.gov/cgi-bin/cuu/Value?h#mid - a = h/(8*math.pi*math.pi*float(broken[1])) - b = h/(8*math.pi*math.pi*float(broken[2])) - c = h/(8*math.pi*math.pi*float(broken[3])) - self.rotcons.append([a, b, c]) - - # Start of the IR/Raman frequency section. -#Example: -#0 FUNDAMENTAL NORMAL VIBRATIONAL FREQUENCIES -# ( THEORETICALLY 54 VALUES ) -# -# Frequency : in 1/cm -# A(i) : IR intensity (vs,s,m,w,vw,-- or in 10**6 cm/mole) -# A(i) = -- : IR inactive -# -# -# no Frequency Symmetry A(i) -# -# 1. 2969.6 (Bu ) s -# 2. 2969.6 (Bu ) w -# 3. 2967.6 (Bu ) w -# 4. 2967.6 (Bu ) s -# 5. 2931.2 (Au ) vs -# 6. 2927.8 (Bg ) -- -# 7. 2924.9 (Au ) m -# 8. 2923.6 (Bg ) -- -# 9. 2885.8 (Ag ) -- -# 10. 2883.9 (Bu ) w -# 11. 2879.8 (Ag ) -- -# 12. 2874.6 (Bu ) w -# 13. 2869.6 (Ag ) -- -# 14. 2869.2 (Bu ) s -# 15. 1554.4 (Ag ) -- -# 16. 1494.3 (Bu ) w -# 17. 1449.7 (Bg ) -- -# 18. 1449.5 (Au ) w -# 19. 1444.8 (Ag ) -- -# 20. 1438.5 (Bu ) w -# 21. 1421.5 (Ag ) -- -# 22. 1419.3 (Ag ) -- -# 23. 1416.5 (Bu ) w -# 24. 1398.8 (Bu ) w -# 25. 1383.9 (Ag ) -- -# 26. 1363.7 (Bu ) m -# 27. 1346.3 (Ag ) -- -# 28. 1300.2 (Au ) vw -# 29. 1298.7 (Bg ) -- -# 30. 1283.4 (Bu ) m -# 31. 1267.4 (Bg ) -- -# 32. 1209.6 (Au ) w -# 33. 1132.2 (Bg ) -- -# 34. 1094.4 (Ag ) -- -# 35. 1063.4 (Bu ) w -# 36. 1017.8 (Bu ) w -# 37. 1011.6 (Ag ) -- -# 38. 1004.2 (Au ) w -# 39. 990.2 (Ag ) -- -# 40. 901.8 (Ag ) -- -# 41. 898.4 (Bg ) -- -# 42. 875.9 (Bu ) w -# 43. 795.4 (Au ) w -# 44. 725.0 (Bg ) -- -# 45. 699.6 (Au ) w -# 46. 453.4 (Bu ) w -# 47. 352.1 (Ag ) -- -# 48. 291.1 (Ag ) -- -# 49. 235.9 (Au ) vw -# 50. 225.2 (Bg ) -- -# 51. 151.6 (Bg ) -- -# 52. 147.7 (Bu ) w -# 53. 108.0 (Au ) vw -# 54. 77.1 (Au ) vw -# 55. ( 0.0) (t/r ) -# 56. ( 0.0) (t/r ) -# 57. ( 0.0) (t/r ) -# 58. ( 0.0) (t/r ) -# 59. ( 0.0) (t/r ) -# 60. ( 0.0) (t/r ) - - if line[0:52] == ' no Frequency Symmetry A(i)': - blankline = inputfile.next() - self.updateprogress(inputfile, "Frequency Information", self.fupdate) - - if not hasattr(self, 'vibfreqs'): - self.vibfreqs = [] - line = inputfile.next() - while(line[15:31].find('(') < 0):#terminate once we reach zero frequencies (which include parentheses) - freq = self.float(line[15:31]) - self.vibfreqs.append(freq) - line = inputfile.next() - #parsing of final steric energy in eV (for purposes of providing a baseline for possible subsequent hindered rotor calculations) - #example line:" FINAL STERIC ENERGY IS 0.8063 KCAL/MOL." - if line[6:28] == 'FINAL STERIC ENERGY IS': - stericenergy = utils.convertor(self.float(line.split()[4])/627.5095, "hartree", "eV") #note conversion from kcal/mol to hartree - if hasattr(self, "stericenergy"): - assert self.stericenergy == stericenergy #check that subsequent occurences match the original value - else: - self.stericenergy = stericenergy - - -if __name__ == "__main__": - import doctest, mm4parser - doctest.testmod(mm4parser, verbose=False) +""" +gmagoon 05/03/10: new class for MM4 parsing, based on mopacparser.py, which, in turn, is based on gaussianparser.py from cclib, described below: +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 814 $" + + +#import re + +import numpy +import math +import utils +import logfileparser + + +def symbol2int(symbol): + t = utils.PeriodicTable() + return t.number[symbol] + +class MM4(logfileparser.Logfile): + """An MM4 output file.""" + + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(MM4, self).__init__(logname="MM4", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "MM4 log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'MM4("%s")' % (self.filename) + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + # Number of atoms. + # Example: THE COORDINATES OF 20 ATOMS ARE READ IN. + if line[0:28] == ' THE COORDINATES OF': + + self.updateprogress(inputfile, "Attributes", self.fupdate) + natom = int(line.split()[-5]) #fifth to last component should be number of atoms + if hasattr(self, "natom"): + assert self.natom == natom + else: + self.natom = natom + + # Extract the atomic numbers and coordinates from the optimized (final) geometry + + # Example: +# FINAL ATOMIC COORDINATE +# ATOM X Y Z TYPE +# C( 1) -3.21470 -0.22058 0.00000 ( 1) +# H( 2) -3.30991 -0.87175 0.89724 ( 5) +# H( 3) -3.30991 -0.87174 -0.89724 ( 5) +# H( 4) -4.08456 0.47380 0.00000 ( 5) +# C( 5) -1.88672 0.54893 0.00000 ( 1) +# H( 6) -1.84759 1.21197 -0.89488 ( 5) +# H( 7) -1.84759 1.21197 0.89488 ( 5) +# C( 8) -0.66560 -0.38447 0.00000 ( 1) +# H( 9) -0.70910 -1.04707 -0.89471 ( 5) +# H( 10) -0.70910 -1.04707 0.89471 ( 5) +# C( 11) 0.66560 0.38447 0.00000 ( 1) +# H( 12) 0.70910 1.04707 0.89471 ( 5) +# H( 13) 0.70910 1.04707 -0.89471 ( 5) +# C( 14) 1.88672 -0.54893 0.00000 ( 1) +# H( 15) 1.84759 -1.21197 -0.89488 ( 5) +# H( 16) 1.84759 -1.21197 0.89488 ( 5) +# C( 17) 3.21470 0.22058 0.00000 ( 1) +# H( 18) 3.30991 0.87174 0.89724 ( 5) +# H( 19) 4.08456 -0.47380 0.00000 ( 5) +# H( 20) 3.30991 0.87175 -0.89724 ( 5) + + if line[0:29] == ' FINAL ATOMIC COORDINATE': + + + self.updateprogress(inputfile, "Attributes", self.cupdate) + + self.inputcoords = [] + self.inputatoms = [] + + headerline = inputfile.next() + + atomcoords = [] + line = inputfile.next() + while len(line.split()) > 0: + broken = line.split() + self.inputatoms.append(symbol2int(line[0:10].strip())) + xc = float(line[17:29]) + yc = float(line[29:41]) + zc = float(line[41:53]) + atomcoords.append([xc,yc,zc]) + line = inputfile.next() + + self.inputcoords.append(atomcoords) + + if not hasattr(self, "atomnos"): + self.atomnos = numpy.array(self.inputatoms, 'i') + if not hasattr(self, "natom"): + self.natom = len(self.atomnos) + + +#read energy (in kcal/mol, converted to eV) +# Example: HEAT OF FORMATION (HFN) AT 298.2 K = -42.51 KCAL/MOLE + if line[0:31] == ' HEAT OF FORMATION (HFN) AT': + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + self.scfenergies.append(utils.convertor(self.float(line.split()[-2])/627.5095, "hartree", "eV")) #note conversion from kcal/mol to hartree + + #molecular mass parsing (units will be amu); note that this can occur multiple times in the file, but all values should be the same + #Example: FORMULA WEIGHT : 86.112 + if line[0:33] == ' FORMULA WEIGHT :': + self.updateprogress(inputfile, "Attributes", self.fupdate) + molmass = self.float(line.split()[-1]) + if hasattr(self, "molmass"): + assert self.molmass == molmass #check that subsequent occurences match the original value + else: + self.molmass = molmass + + #rotational constants (converted to GHZ) + #Example: +# THE MOMENTS OF INERTIA CALCULATED FROM R(g), R(z) VALUES +# (also from R(e), R(alpha), R(s) VALUES) +# +# Note: (1) All calculations are based on principle isotopes. +# (2) R(z) values include harmonic vibration (Coriolis) +# contribution indicated in parentheses. +# +# +# (1) UNIT = 10**(-39) GM*CM**2 +# +# IX IY IZ +# +# R(e) 5.7724 73.4297 76.0735 +# R(z) 5.7221(-0.0518) 74.0311(-0.0285) 76.7102(-0.0064) +# +# (2) UNIT = AU A**2 +# +# IX IY IZ +# +# R(e) 34.7661 442.2527 458.1757 +# R(z) 34.4633(-0.3117) 445.8746(-0.1714) 462.0104(-0.0385) + #moments of inertia converted into rotational constants via rot cons= h/(8*Pi^2*I) + #we will use the equilibrium values (R(e)) in units of 10**-39 GM*CM**2 (these units are less precise (fewer digits) than AU A**2 units but it is simpler as it doesn't require use of Avogadro's number + #***even R(e) may include temperature dependent effects, though, and maybe the one I actually want is r(mm4) (not reported) + if line[0:33] == ' (1) UNIT = 10**(-39) GM*CM**2': + dummyline = inputfile.next(); + dummyline = inputfile.next(); + dummyline = inputfile.next(); + rotinfo=inputfile.next(); + if not hasattr(self, "rotcons"): + self.rotcons = [] + broken = rotinfo.split() + h = 6.62606896E3 #Planck constant in 10^-37 J-s = 10^-37 kg m^2/s cf. http://physics.nist.gov/cgi-bin/cuu/Value?h#mid + a = h/(8*math.pi*math.pi*float(broken[1])) + b = h/(8*math.pi*math.pi*float(broken[2])) + c = h/(8*math.pi*math.pi*float(broken[3])) + self.rotcons.append([a, b, c]) + + # Start of the IR/Raman frequency section. +#Example: +#0 FUNDAMENTAL NORMAL VIBRATIONAL FREQUENCIES +# ( THEORETICALLY 54 VALUES ) +# +# Frequency : in 1/cm +# A(i) : IR intensity (vs,s,m,w,vw,-- or in 10**6 cm/mole) +# A(i) = -- : IR inactive +# +# +# no Frequency Symmetry A(i) +# +# 1. 2969.6 (Bu ) s +# 2. 2969.6 (Bu ) w +# 3. 2967.6 (Bu ) w +# 4. 2967.6 (Bu ) s +# 5. 2931.2 (Au ) vs +# 6. 2927.8 (Bg ) -- +# 7. 2924.9 (Au ) m +# 8. 2923.6 (Bg ) -- +# 9. 2885.8 (Ag ) -- +# 10. 2883.9 (Bu ) w +# 11. 2879.8 (Ag ) -- +# 12. 2874.6 (Bu ) w +# 13. 2869.6 (Ag ) -- +# 14. 2869.2 (Bu ) s +# 15. 1554.4 (Ag ) -- +# 16. 1494.3 (Bu ) w +# 17. 1449.7 (Bg ) -- +# 18. 1449.5 (Au ) w +# 19. 1444.8 (Ag ) -- +# 20. 1438.5 (Bu ) w +# 21. 1421.5 (Ag ) -- +# 22. 1419.3 (Ag ) -- +# 23. 1416.5 (Bu ) w +# 24. 1398.8 (Bu ) w +# 25. 1383.9 (Ag ) -- +# 26. 1363.7 (Bu ) m +# 27. 1346.3 (Ag ) -- +# 28. 1300.2 (Au ) vw +# 29. 1298.7 (Bg ) -- +# 30. 1283.4 (Bu ) m +# 31. 1267.4 (Bg ) -- +# 32. 1209.6 (Au ) w +# 33. 1132.2 (Bg ) -- +# 34. 1094.4 (Ag ) -- +# 35. 1063.4 (Bu ) w +# 36. 1017.8 (Bu ) w +# 37. 1011.6 (Ag ) -- +# 38. 1004.2 (Au ) w +# 39. 990.2 (Ag ) -- +# 40. 901.8 (Ag ) -- +# 41. 898.4 (Bg ) -- +# 42. 875.9 (Bu ) w +# 43. 795.4 (Au ) w +# 44. 725.0 (Bg ) -- +# 45. 699.6 (Au ) w +# 46. 453.4 (Bu ) w +# 47. 352.1 (Ag ) -- +# 48. 291.1 (Ag ) -- +# 49. 235.9 (Au ) vw +# 50. 225.2 (Bg ) -- +# 51. 151.6 (Bg ) -- +# 52. 147.7 (Bu ) w +# 53. 108.0 (Au ) vw +# 54. 77.1 (Au ) vw +# 55. ( 0.0) (t/r ) +# 56. ( 0.0) (t/r ) +# 57. ( 0.0) (t/r ) +# 58. ( 0.0) (t/r ) +# 59. ( 0.0) (t/r ) +# 60. ( 0.0) (t/r ) + + if line[0:52] == ' no Frequency Symmetry A(i)': + blankline = inputfile.next() + self.updateprogress(inputfile, "Frequency Information", self.fupdate) + + if not hasattr(self, 'vibfreqs'): + self.vibfreqs = [] + line = inputfile.next() + while(line[15:31].find('(') < 0):#terminate once we reach zero frequencies (which include parentheses) + freq = self.float(line[15:31]) + self.vibfreqs.append(freq) + line = inputfile.next() + #parsing of final steric energy in eV (for purposes of providing a baseline for possible subsequent hindered rotor calculations) + #example line:" FINAL STERIC ENERGY IS 0.8063 KCAL/MOL." + if line[6:28] == 'FINAL STERIC ENERGY IS': + stericenergy = utils.convertor(self.float(line.split()[4])/627.5095, "hartree", "eV") #note conversion from kcal/mol to hartree + if hasattr(self, "stericenergy"): + assert self.stericenergy == stericenergy #check that subsequent occurences match the original value + else: + self.stericenergy = stericenergy + + +if __name__ == "__main__": + import doctest, mm4parser + doctest.testmod(mm4parser, verbose=False) diff --git a/external/cclib/parser/molproparser.py b/external/cclib/parser/molproparser.py index 4dd3a1905f..7f4f1fab1f 100644 --- a/external/cclib/parser/molproparser.py +++ b/external/cclib/parser/molproparser.py @@ -1,644 +1,644 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 661 $" - - -import re - -import numpy - -import logfileparser -import utils - - -class Molpro(logfileparser.Logfile): - """Molpro file parser""" - - def __init__(self, *args, **kwargs): - # Call the __init__ method of the superclass - super(Molpro, self).__init__(logname="Molpro", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "Molpro log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'Molpro("%s")' % (self.filename) - - def normalisesym(self, label): - """Normalise the symmetries used by Molpro.""" - ans = label.replace("`", "'").replace("``", "''") - return ans - - def before_parsing(self): - - self.electronorbitals = "" - self.insidescf = False - - def after_parsing(self): - - # If optimization thresholds are default, they are normally not printed. - if not hasattr(self, "geotargets"): - self.geotargets = [] - # Default THRGRAD (required accuracy of the optimized gradient). - self.geotargets.append(3E-4) - # Default THRENERG (required accuracy of the optimized energy). - self.geotargets.append(1E-6) - # Default THRSTEP (convergence threshold for the geometry optimization step). - self.geotargets.append(3E-4) - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - if line[1:19] == "ATOMIC COORDINATES": - - if not hasattr(self,"atomcoords"): - self.atomcoords = [] - self.atomnos = [] - line = inputfile.next() - line = inputfile.next() - line = inputfile.next() - atomcoords = [] - atomnos = [] - - line = inputfile.next() - while line.strip(): - temp = line.strip().split() - atomcoords.append([utils.convertor(float(x),"bohr","Angstrom") for x in temp[3:6]]) #bohrs to angs - atomnos.append(int(round(float(temp[2])))) - line = inputfile.next() - - self.atomnos = numpy.array(atomnos, "i") - self.atomcoords.append(atomcoords) - self.natom = len(self.atomnos) - - # Use BASIS DATA to parse input for aonames and atombasis. - # This is always the first place this information is printed, so no attribute check is needed. - if line[1:11] == "BASIS DATA": - - blank = inputfile.next() - header = inputfile.next() - blank = inputfile.next() - self.aonames = [] - self.atombasis = [] - self.gbasis = [] - for i in range(self.natom): - self.atombasis.append([]) - self.gbasis.append([]) - - line = "dummy" - while line.strip() != "": - line = inputfile.next() - funcnr = line[1:6] - funcsym = line[7:9] - funcatom_ = line[11:14] - functype_ = line[16:22] - funcexp = line[25:38] - funccoeffs = line[38:] - - # If a new function type is printed or the BASIS DATA block ends, - # then the previous functions can be added to gbasis. - # When translating the Molpro function type name into a gbasis code, - # note that Molpro prints all components, and we want to add - # only one to gbasis, with the proper code (S,P,D,F,G). - # Warning! The function types differ for cartesian/spherical functions. - # Skip the first printed function type, however (line[3] != '1'). - if (functype_.strip() and line[1:4] != ' 1') or line.strip() == "": - funcbasis = None - if functype in ['1s', 's']: - funcbasis = 'S' - if functype in ['x', '2px']: - funcbasis = 'P' - if functype in ['xx', '3d0']: - funcbasis = 'D' - if functype in ['xxx', '4f0']: - funcbasis = 'F' - if functype in ['xxxx', '5g0']: - funcbasis = 'G' - if funcbasis: - - # The function is split into as many columns as there are. - for i in range(len(coefficients[0])): - func = (funcbasis, []) - for j in range(len(exponents)): - func[1].append((exponents[j],coefficients[j][i])) - self.gbasis[funcatom-1].append(func) - - # If it is a new type, set up the variables for the next shell(s). - if functype_.strip(): - exponents = [] - coefficients = [] - functype = functype_.strip() - funcatom = int(funcatom_.strip()) - - # Add exponents and coefficients to lists. - if line.strip(): - funcexp = float(funcexp) - funccoeffs = [float(s) for s in funccoeffs.split()] - exponents.append(funcexp) - coefficients.append(funccoeffs) - - # If the function number is there, add to atombasis and aonames. - if funcnr.strip(): - funcnr = int(funcnr.split('.')[0]) - self.atombasis[funcatom-1].append(funcnr-1) - element = self.table.element[self.atomnos[funcatom-1]] - aoname = "%s%i_%s" %(element, funcatom, functype) - self.aonames.append(aoname) - - if line[1:23] == "NUMBER OF CONTRACTIONS": - - nbasis = int(line.split()[3]) - if hasattr(self, "nbasis"): - assert nbasis == self.nbasis - else: - self.nbasis = nbasis - - # This is used to signalize whether we are inside an SCF calculation. - if line[1:8] == "PROGRAM" and line[14:18] == "-SCF": - - self.insidescf = True - - # Use this information instead of 'SETTING ...', in case the defaults are standard. - # Note that this is sometimes printed in each geometry optimization step. - if line[1:20] == "NUMBER OF ELECTRONS": - - spinup = int(line.split()[3][:-1]) - spindown = int(line.split()[4][:-1]) - # Nuclear charges (atomnos) should be parsed by now. - nuclear = numpy.sum(self.atomnos) - charge = nuclear - spinup - spindown - mult = spinup - spindown + 1 - - # Copy charge, or assert for exceptions if already exists. - if not hasattr(self, "charge"): - self.charge = charge - else: - assert self.charge == charge - - # Copy multiplicity, or assert for exceptions if already exists. - if not hasattr(self, "mult"): - self.mult = mult - else: - assert self.mult == mult - - # Convergenve thresholds for SCF cycle, should be contained in a line such as: - # CONVERGENCE THRESHOLDS: 1.00E-05 (Density) 1.40E-07 (Energy) - if self.insidescf and line[1:24] == "CONVERGENCE THRESHOLDS:": - - if not hasattr(self, "scftargets"): - self.scftargets = [] - - scftargets = map(float, line.split()[2::2]) - self.scftargets.append(scftargets) - # Usually two criteria, but save the names this just in case. - self.scftargetnames = line.split()[3::2] - - # Read in the print out of the SCF cycle - for scfvalues. For RHF looks like: - # ITERATION DDIFF GRAD ENERGY 2-EL.EN. DIPOLE MOMENTS DIIS - # 1 0.000D+00 0.000D+00 -379.71523700 1159.621171 0.000000 0.000000 0.000000 0 - # 2 0.000D+00 0.898D-02 -379.74469736 1162.389787 0.000000 0.000000 0.000000 1 - # 3 0.817D-02 0.144D-02 -379.74635529 1162.041033 0.000000 0.000000 0.000000 2 - # 4 0.213D-02 0.571D-03 -379.74658063 1162.159929 0.000000 0.000000 0.000000 3 - # 5 0.799D-03 0.166D-03 -379.74660889 1162.144256 0.000000 0.000000 0.000000 4 - if self.insidescf and line[1:10] == "ITERATION": - - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - - line = inputfile.next() - energy = 0.0 - scfvalues = [] - while line.strip() != "": - if line.split()[0].isdigit(): - - ddiff = float(line.split()[1].replace('D','E')) - newenergy = float(line.split()[3]) - ediff = newenergy - energy - energy = newenergy - - # The convergence thresholds must have been read above. - # Presently, we recognize MAX DENSITY and MAX ENERGY thresholds. - numtargets = len(self.scftargetnames) - values = [numpy.nan]*numtargets - for n,name in zip(range(numtargets),self.scftargetnames): - if "ENERGY" in name.upper(): - values[n] = ediff - elif "DENSITY" in name.upper(): - values[n] = ddiff - scfvalues.append(values) - - line = inputfile.next() - self.scfvalues.append(numpy.array(scfvalues)) - - # SCF result - RHF/UHF and DFT (RKS) energies. - if line[1:5] in ["!RHF", "!UHF", "!RKS"] and line[16:22] == "ENERGY": - - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - scfenergy = float(line.split()[4]) - self.scfenergies.append(utils.convertor(scfenergy, "hartree", "eV")) - - # We are now done with SCF cycle (after a few lines). - self.insidescf = False - - # MP2 energies. - if line[1:5] == "!MP2": - - if not hasattr(self, 'mpenergies'): - self.mpenergies = [] - mp2energy = float(line.split()[-1]) - mp2energy = utils.convertor(mp2energy, "hartree", "eV") - self.mpenergies.append([mp2energy]) - - # MP2 energies if MP3 or MP4 is also calculated. - if line[1:5] == "MP2:": - - if not hasattr(self, 'mpenergies'): - self.mpenergies = [] - mp2energy = float(line.split()[2]) - mp2energy = utils.convertor(mp2energy, "hartree", "eV") - self.mpenergies.append([mp2energy]) - - # MP3 (D) and MP4 (DQ or SDQ) energies. - if line[1:8] == "MP3(D):": - - mp3energy = float(line.split()[2]) - mp2energy = utils.convertor(mp3energy, "hartree", "eV") - line = inputfile.next() - self.mpenergies[-1].append(mp2energy) - if line[1:9] == "MP4(DQ):": - mp4energy = float(line.split()[2]) - line = inputfile.next() - if line[1:10] == "MP4(SDQ):": - mp4energy = float(line.split()[2]) - mp4energy = utils.convertor(mp4energy, "hartree", "eV") - self.mpenergies[-1].append(mp4energy) - - # The CCSD program operates all closed-shel coupled cluster runs. - if line[1:15] == "PROGRAM * CCSD": - - if not hasattr(self, "ccenergies"): - self.ccenergies = [] - while line[1:20] != "Program statistics:": - # The last energy (most exact) will be read last and thus saved. - if line[1:5] == "!CCD" or line[1:6] == "!CCSD" or line[1:9] == "!CCSD(T)": - ccenergy = float(line.split()[-1]) - ccenergy = utils.convertor(ccenergy, "hartree", "eV") - line = inputfile.next() - self.ccenergies.append(ccenergy) - - # Read the occupancy (index of HOMO s). - # For restricted calculations, there is one line here. For unrestricted, two: - # Final alpha occupancy: ... - # Final beta occupancy: ... - if line[1:17] == "Final occupancy:": - self.homos = [int(line.split()[-1])-1] - if line[1:23] == "Final alpha occupancy:": - self.homos = [int(line.split()[-1])-1] - line = inputfile.next() - self.homos.append(int(line.split()[-1])-1) - - # From this block atombasis, moenergies, and mocoeffs can be parsed. - # Note that Molpro does not print this by default, you must add this in the input: - # GPRINT,ORBITALS - # What's more, this prints only the occupied orbitals. To get virtuals, add also: - # ORBPTIN,NVIRT - # where NVIRT is how many to print (can be some large number, like 99999, to print all). - # The block is in general flipped when compared to other programs (GAMESS, Gaussian), and - # MOs in the rows. Also, it does not cut the table into parts, rather each MO row has - # as many lines as it takes to print all the coefficients, as shown below: - # - # ELECTRON ORBITALS - # ================= - # - # - # Orb Occ Energy Couls-En Coefficients - # - # 1 1s 1 1s 1 2px 1 2py 1 2pz 2 1s (...) - # 3 1s 3 1s 3 2px 3 2py 3 2pz 4 1s (...) - # (...) - # - # 1.1 2 -11.0351 -43.4915 0.701460 0.025696 -0.000365 -0.000006 0.000000 0.006922 (...) - # -0.006450 0.004742 -0.001028 -0.002955 0.000000 -0.701460 (...) - # (...) - # - # For unrestricted calcualtions, ELECTRON ORBITALS is followed on the same line - # by FOR POSITIVE SPIN or FOR NEGATIVE SPIN. - # For examples, see data/Molpro/basicMolpro2006/dvb_sp*. - if line[1:18] == "ELECTRON ORBITALS" or self.electronorbitals: - # Detect if we are reading beta (negative spin) orbitals. - spin = 0 - if line[19:36] == "FOR NEGATIVE SPIN" or self.electronorbitals[19:36] == "FOR NEGATIVE SPIN": - spin = 1 - - if not self.electronorbitals: - dashes = inputfile.next() - blank = inputfile.next() - blank = inputfile.next() - headers = inputfile.next() - blank = inputfile.next() - - # Parse the list of atomic orbitals if atombasis or aonames is missing. - line = inputfile.next() - if not hasattr(self, "atombasis") or not hasattr(self, "aonames"): - self.atombasis = [] - for i in range(self.natom): - self.atombasis.append([]) - self.aonames = [] - aonum = 0 - while line.strip(): - for s in line.split(): - if s.isdigit(): - atomno = int(s) - self.atombasis[atomno-1].append(aonum) - aonum += 1 - else: - functype = s - element = self.table.element[self.atomnos[atomno-1]] - aoname = "%s%i_%s" %(element, atomno, functype) - self.aonames.append(aoname) - line = inputfile.next() - else: - while line.strip(): - line = inputfile.next() - - # Now there can be one or two blank lines. - while not line.strip(): - line = inputfile.next() - - # Create empty moenergies and mocoeffs if they don't exist. - if not hasattr(self, "moenergies"): - self.moenergies = [[]] - self.mocoeffs = [[]] - # Do the same if they exist and are being read again (spin=0), - # this means only the last print-out of these data are saved, - # which consistent with current cclib practices. - elif len(self.moenergies) == 1 and spin == 0: - self.moenergies = [[]] - self.mocoeffs = [[]] - else: - self.moenergies.append([]) - self.mocoeffs.append([]) - - while line.strip() and not "ORBITALS" in line: - coeffs = [] - while line.strip() != "": - if line[:30].strip(): - moenergy = float(line.split()[2]) - moenergy = utils.convertor(moenergy, "hartree", "eV") - self.moenergies[spin].append(moenergy) - line = line[31:] - # Each line has 10 coefficients in 10.6f format. - num = len(line)/10 - for i in range(num): - try: - coeff = float(line[10*i:10*(i+1)]) - # Molpro prints stars when coefficients are huge. - except ValueError, detail: - self.logger.warn("Set coefficient to zero: %s" %detail) - coeff = 0.0 - coeffs.append(coeff) - line = inputfile.next() - self.mocoeffs[spin].append(coeffs) - line = inputfile.next() - - # Check if last line begins the next ELECTRON ORBITALS section. - if line[1:18] == "ELECTRON ORBITALS": - self.electronorbitals = line - else: - self.electronorbitals = "" - - # If the MATROP program was called appropriately, - # the atomic obital overlap matrix S is printed. - # The matrix is printed straight-out, ten elements in each row, both halves. - # Note that is the entire matrix is not printed, then aooverlaps - # will not have dimensions nbasis x nbasis. - if line[1:9] == "MATRIX S": - - blank = inputfile.next() - symblocklabel = inputfile.next() - if not hasattr(self, "aooverlaps"): - self.aooverlaps = [[]] - line = inputfile.next() - while line.strip() != "": - elements = [float(s) for s in line.split()] - if len(self.aooverlaps[-1]) + len(elements) <= self.nbasis: - self.aooverlaps[-1] += elements - else: - n = len(self.aooverlaps[-1]) + len(elements) - self.nbasis - self.aooverlaps[-1] += elements[:-n] - self.aooverlaps.append([]) - self.aooverlaps[-1] += elements[-n:] - line = inputfile.next() - - # Thresholds are printed only if the defaults are changed with GTHRESH. - # In that case, we can fill geotargets with non-default values. - # The block should look like this as of Molpro 2006.1: - # THRESHOLDS: - - # ZERO = 1.00D-12 ONEINT = 1.00D-12 TWOINT = 1.00D-11 PREFAC = 1.00D-14 LOCALI = 1.00D-09 EORDER = 1.00D-04 - # ENERGY = 0.00D+00 ETEST = 0.00D+00 EDENS = 0.00D+00 THRDEDEF= 1.00D-06 GRADIENT= 1.00D-02 STEP = 1.00D-03 - # ORBITAL = 1.00D-05 CIVEC = 1.00D-05 COEFF = 1.00D-04 PRINTCI = 5.00D-02 PUNCHCI = 9.90D+01 OPTGRAD = 3.00D-04 - # OPTENERG= 1.00D-06 OPTSTEP = 3.00D-04 THRGRAD = 2.00D-04 COMPRESS= 1.00D-11 VARMIN = 1.00D-07 VARMAX = 1.00D-03 - # THRDOUB = 0.00D+00 THRDIV = 1.00D-05 THRRED = 1.00D-07 THRPSP = 1.00D+00 THRDC = 1.00D-10 THRCS = 1.00D-10 - # THRNRM = 1.00D-08 THREQ = 0.00D+00 THRDE = 1.00D+00 THRREF = 1.00D-05 SPARFAC = 1.00D+00 THRDLP = 1.00D-07 - # THRDIA = 1.00D-10 THRDLS = 1.00D-07 THRGPS = 0.00D+00 THRKEX = 0.00D+00 THRDIS = 2.00D-01 THRVAR = 1.00D-10 - # THRLOC = 1.00D-06 THRGAP = 1.00D-06 THRLOCT = -1.00D+00 THRGAPT = -1.00D+00 THRORB = 1.00D-06 THRMLTP = 0.00D+00 - # THRCPQCI= 1.00D-10 KEXTA = 0.00D+00 THRCOARS= 0.00D+00 SYMTOL = 1.00D-06 GRADTOL = 1.00D-06 THROVL = 1.00D-08 - # THRORTH = 1.00D-08 GRID = 1.00D-06 GRIDMAX = 1.00D-03 DTMAX = 0.00D+00 - if line [1:12] == "THRESHOLDS": - - blank = inputfile.next() - line = inputfile.next() - while line.strip(): - - if "OPTENERG" in line: - start = line.find("OPTENERG") - optenerg = line[start+10:start+20] - if "OPTGRAD" in line: - start = line.find("OPTGRAD") - optgrad = line[start+10:start+20] - if "OPTSTEP" in line: - start = line.find("OPTSTEP") - optstep = line[start+10:start+20] - line = inputfile.next() - - self.geotargets = [optenerg, optgrad, optstep] - - # The optimization history is the source for geovlues: - # END OF GEOMETRY OPTIMIZATION. TOTAL CPU: 246.9 SEC - # - # ITER. ENERGY(OLD) ENERGY(NEW) DE GRADMAX GRADNORM GRADRMS STEPMAX STEPLEN STEPRMS - # 1 -382.02936898 -382.04914450 -0.01977552 0.11354875 0.20127947 0.01183997 0.12972761 0.20171740 0.01186573 - # 2 -382.04914450 -382.05059234 -0.00144784 0.03299860 0.03963339 0.00233138 0.05577169 0.06687650 0.00393391 - # 3 -382.05059234 -382.05069136 -0.00009902 0.00694359 0.01069889 0.00062935 0.01654549 0.02016307 0.00118606 - # 4 -382.05069136 -382.05069130 0.00000006 0.00295497 0.00363023 0.00021354 0.00234307 0.00443525 0.00026090 - # 5 -382.05069130 -382.05069206 -0.00000075 0.00098220 0.00121031 0.00007119 0.00116863 0.00140452 0.00008262 - # 6 -382.05069206 -382.05069209 -0.00000003 0.00011350 0.00022306 0.00001312 0.00013321 0.00024526 0.00001443 - if line[1:30] == "END OF GEOMETRY OPTIMIZATION.": - - blank = inputfile.next() - headers = inputfile.next() - - # Although criteria can be changed, the printed format should not change. - # In case it does, retrieve the columns for each parameter. - headers = headers.split() - index_THRENERG = headers.index('DE') - index_THRGRAD = headers.index('GRADMAX') - index_THRSTEP = headers.index('STEPMAX') - - line = inputfile.next() - self.geovalues = [] - while line.strip() != "": - - line = line.split() - geovalues = [] - geovalues.append(float(line[index_THRENERG])) - geovalues.append(float(line[index_THRGRAD])) - geovalues.append(float(line[index_THRSTEP])) - self.geovalues.append(geovalues) - line = inputfile.next() - - # This block should look like this: - # Normal Modes - # - # 1 Au 2 Bu 3 Ag 4 Bg 5 Ag - # Wavenumbers [cm-1] 151.81 190.88 271.17 299.59 407.86 - # Intensities [km/mol] 0.33 0.28 0.00 0.00 0.00 - # Intensities [relative] 0.34 0.28 0.00 0.00 0.00 - # CX1 0.00000 -0.01009 0.02577 0.00000 0.06008 - # CY1 0.00000 -0.05723 -0.06696 0.00000 0.06349 - # CZ1 -0.02021 0.00000 0.00000 0.11848 0.00000 - # CX2 0.00000 -0.01344 0.05582 0.00000 -0.02513 - # CY2 0.00000 -0.06288 -0.03618 0.00000 0.00349 - # CZ2 -0.05565 0.00000 0.00000 0.07815 0.00000 - # ... - # Molpro prints low frequency modes in a subsequent section with the same format, - # which also contains zero frequency modes, with the title: - # Normal Modes of low/zero frequencies - if line[1:13] == "Normal Modes": - - if line[1:37] == "Normal Modes of low/zero frequencies": - islow = True - else: - islow = False - - blank = inputfile.next() - - # Each portion of five modes is followed by a single blank line. - # The whole block is followed by an additional blank line. - line = inputfile.next() - while line.strip(): - - if line[1:25].isspace(): - numbers = map(int, line.split()[::2]) - vibsyms = line.split()[1::2] - - if line[1:12] == "Wavenumbers": - vibfreqs = map(float, line.strip().split()[2:]) - - if line[1:21] == "Intensities [km/mol]": - vibirs = map(float, line.strip().split()[2:]) - - # There should always by 3xnatom displacement rows. - if line[1:11].isspace() and line[13:25].strip().isdigit(): - - # There are a maximum of 5 modes per line. - nmodes = len(line.split())-1 - - vibdisps = [] - for i in range(nmodes): - vibdisps.append([]) - for n in range(self.natom): - vibdisps[i].append([]) - for i in range(nmodes): - disp = float(line.split()[i+1]) - vibdisps[i][0].append(disp) - for i in range(self.natom*3 - 1): - line = inputfile.next() - iatom = (i+1)/3 - for i in range(nmodes): - disp = float(line.split()[i+1]) - vibdisps[i][iatom].append(disp) - - line = inputfile.next() - if not line.strip(): - - if not hasattr(self, "vibfreqs"): - self.vibfreqs = [] - if not hasattr(self, "vibsyms"): - self.vibsyms = [] - if not hasattr(self, "vibirs") and "vibirs" in dir(): - self.vibirs = [] - if not hasattr(self, "vibdisps") and "vibdisps" in dir(): - self.vibdisps = [] - - if not islow: - self.vibfreqs.extend(vibfreqs) - self.vibsyms.extend(vibsyms) - if "vibirs" in dir(): - self.vibirs.extend(vibirs) - if "vibdisps" in dir(): - self.vibdisps.extend(vibdisps) - else: - nonzero = [f > 0 for f in vibfreqs] - vibfreqs = [f for f in vibfreqs if f > 0] - self.vibfreqs = vibfreqs + self.vibfreqs - vibsyms = [vibsyms[i] for i in range(len(vibsyms)) if nonzero[i]] - self.vibsyms = vibsyms + self.vibsyms - if "vibirs" in dir(): - vibirs = [vibirs[i] for i in range(len(vibirs)) if nonzero[i]] - self.vibirs = vibirs + self.vibirs - if "vibdisps" in dir(): - vibdisps = [vibdisps[i] for i in range(len(vibdisps)) if nonzero[i]] - self.vibdisps = vibdisps + self.vibdisps - - line = inputfile.next() - - if line[1:16] == "Force Constants": - - self.logger.info("Creating attribute hessian") - self.hessian = [] - line = inputfile.next() - hess = [] - tmp = [] - - while line.strip(): - try: map(float, line.strip().split()[2:]) - except: - line = inputfile.next() - line.strip().split()[1:] - hess.extend([map(float,line.strip().split()[1:])]) - line = inputfile.next() - lig = 0 - - while (lig==0) or (len(hess[0]) > 1): - tmp.append(hess.pop(0)) - lig += 1 - k = 5 - - while len(hess) != 0: - tmp[k] += hess.pop(0) - k += 1 - if (len(tmp[k-1]) == lig): break - if k >= lig: k = len(tmp[-1]) - for l in tmp: self.hessian += l - - if line[1:14] == "Atomic Masses" and hasattr(self,"hessian"): - - line = inputfile.next() - self.amass = map(float, line.strip().split()[2:]) - - while line.strip(): - line = inputfile.next() - self.amass += map(float, line.strip().split()[2:]) - - -if __name__ == "__main__": - import doctest, molproparser - doctest.testmod(molproparser, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 661 $" + + +import re + +import numpy + +import logfileparser +import utils + + +class Molpro(logfileparser.Logfile): + """Molpro file parser""" + + def __init__(self, *args, **kwargs): + # Call the __init__ method of the superclass + super(Molpro, self).__init__(logname="Molpro", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "Molpro log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'Molpro("%s")' % (self.filename) + + def normalisesym(self, label): + """Normalise the symmetries used by Molpro.""" + ans = label.replace("`", "'").replace("``", "''") + return ans + + def before_parsing(self): + + self.electronorbitals = "" + self.insidescf = False + + def after_parsing(self): + + # If optimization thresholds are default, they are normally not printed. + if not hasattr(self, "geotargets"): + self.geotargets = [] + # Default THRGRAD (required accuracy of the optimized gradient). + self.geotargets.append(3E-4) + # Default THRENERG (required accuracy of the optimized energy). + self.geotargets.append(1E-6) + # Default THRSTEP (convergence threshold for the geometry optimization step). + self.geotargets.append(3E-4) + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + if line[1:19] == "ATOMIC COORDINATES": + + if not hasattr(self,"atomcoords"): + self.atomcoords = [] + self.atomnos = [] + line = inputfile.next() + line = inputfile.next() + line = inputfile.next() + atomcoords = [] + atomnos = [] + + line = inputfile.next() + while line.strip(): + temp = line.strip().split() + atomcoords.append([utils.convertor(float(x),"bohr","Angstrom") for x in temp[3:6]]) #bohrs to angs + atomnos.append(int(round(float(temp[2])))) + line = inputfile.next() + + self.atomnos = numpy.array(atomnos, "i") + self.atomcoords.append(atomcoords) + self.natom = len(self.atomnos) + + # Use BASIS DATA to parse input for aonames and atombasis. + # This is always the first place this information is printed, so no attribute check is needed. + if line[1:11] == "BASIS DATA": + + blank = inputfile.next() + header = inputfile.next() + blank = inputfile.next() + self.aonames = [] + self.atombasis = [] + self.gbasis = [] + for i in range(self.natom): + self.atombasis.append([]) + self.gbasis.append([]) + + line = "dummy" + while line.strip() != "": + line = inputfile.next() + funcnr = line[1:6] + funcsym = line[7:9] + funcatom_ = line[11:14] + functype_ = line[16:22] + funcexp = line[25:38] + funccoeffs = line[38:] + + # If a new function type is printed or the BASIS DATA block ends, + # then the previous functions can be added to gbasis. + # When translating the Molpro function type name into a gbasis code, + # note that Molpro prints all components, and we want to add + # only one to gbasis, with the proper code (S,P,D,F,G). + # Warning! The function types differ for cartesian/spherical functions. + # Skip the first printed function type, however (line[3] != '1'). + if (functype_.strip() and line[1:4] != ' 1') or line.strip() == "": + funcbasis = None + if functype in ['1s', 's']: + funcbasis = 'S' + if functype in ['x', '2px']: + funcbasis = 'P' + if functype in ['xx', '3d0']: + funcbasis = 'D' + if functype in ['xxx', '4f0']: + funcbasis = 'F' + if functype in ['xxxx', '5g0']: + funcbasis = 'G' + if funcbasis: + + # The function is split into as many columns as there are. + for i in range(len(coefficients[0])): + func = (funcbasis, []) + for j in range(len(exponents)): + func[1].append((exponents[j],coefficients[j][i])) + self.gbasis[funcatom-1].append(func) + + # If it is a new type, set up the variables for the next shell(s). + if functype_.strip(): + exponents = [] + coefficients = [] + functype = functype_.strip() + funcatom = int(funcatom_.strip()) + + # Add exponents and coefficients to lists. + if line.strip(): + funcexp = float(funcexp) + funccoeffs = [float(s) for s in funccoeffs.split()] + exponents.append(funcexp) + coefficients.append(funccoeffs) + + # If the function number is there, add to atombasis and aonames. + if funcnr.strip(): + funcnr = int(funcnr.split('.')[0]) + self.atombasis[funcatom-1].append(funcnr-1) + element = self.table.element[self.atomnos[funcatom-1]] + aoname = "%s%i_%s" %(element, funcatom, functype) + self.aonames.append(aoname) + + if line[1:23] == "NUMBER OF CONTRACTIONS": + + nbasis = int(line.split()[3]) + if hasattr(self, "nbasis"): + assert nbasis == self.nbasis + else: + self.nbasis = nbasis + + # This is used to signalize whether we are inside an SCF calculation. + if line[1:8] == "PROGRAM" and line[14:18] == "-SCF": + + self.insidescf = True + + # Use this information instead of 'SETTING ...', in case the defaults are standard. + # Note that this is sometimes printed in each geometry optimization step. + if line[1:20] == "NUMBER OF ELECTRONS": + + spinup = int(line.split()[3][:-1]) + spindown = int(line.split()[4][:-1]) + # Nuclear charges (atomnos) should be parsed by now. + nuclear = numpy.sum(self.atomnos) + charge = nuclear - spinup - spindown + mult = spinup - spindown + 1 + + # Copy charge, or assert for exceptions if already exists. + if not hasattr(self, "charge"): + self.charge = charge + else: + assert self.charge == charge + + # Copy multiplicity, or assert for exceptions if already exists. + if not hasattr(self, "mult"): + self.mult = mult + else: + assert self.mult == mult + + # Convergenve thresholds for SCF cycle, should be contained in a line such as: + # CONVERGENCE THRESHOLDS: 1.00E-05 (Density) 1.40E-07 (Energy) + if self.insidescf and line[1:24] == "CONVERGENCE THRESHOLDS:": + + if not hasattr(self, "scftargets"): + self.scftargets = [] + + scftargets = map(float, line.split()[2::2]) + self.scftargets.append(scftargets) + # Usually two criteria, but save the names this just in case. + self.scftargetnames = line.split()[3::2] + + # Read in the print out of the SCF cycle - for scfvalues. For RHF looks like: + # ITERATION DDIFF GRAD ENERGY 2-EL.EN. DIPOLE MOMENTS DIIS + # 1 0.000D+00 0.000D+00 -379.71523700 1159.621171 0.000000 0.000000 0.000000 0 + # 2 0.000D+00 0.898D-02 -379.74469736 1162.389787 0.000000 0.000000 0.000000 1 + # 3 0.817D-02 0.144D-02 -379.74635529 1162.041033 0.000000 0.000000 0.000000 2 + # 4 0.213D-02 0.571D-03 -379.74658063 1162.159929 0.000000 0.000000 0.000000 3 + # 5 0.799D-03 0.166D-03 -379.74660889 1162.144256 0.000000 0.000000 0.000000 4 + if self.insidescf and line[1:10] == "ITERATION": + + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + + line = inputfile.next() + energy = 0.0 + scfvalues = [] + while line.strip() != "": + if line.split()[0].isdigit(): + + ddiff = float(line.split()[1].replace('D','E')) + newenergy = float(line.split()[3]) + ediff = newenergy - energy + energy = newenergy + + # The convergence thresholds must have been read above. + # Presently, we recognize MAX DENSITY and MAX ENERGY thresholds. + numtargets = len(self.scftargetnames) + values = [numpy.nan]*numtargets + for n,name in zip(range(numtargets),self.scftargetnames): + if "ENERGY" in name.upper(): + values[n] = ediff + elif "DENSITY" in name.upper(): + values[n] = ddiff + scfvalues.append(values) + + line = inputfile.next() + self.scfvalues.append(numpy.array(scfvalues)) + + # SCF result - RHF/UHF and DFT (RKS) energies. + if line[1:5] in ["!RHF", "!UHF", "!RKS"] and line[16:22] == "ENERGY": + + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + scfenergy = float(line.split()[4]) + self.scfenergies.append(utils.convertor(scfenergy, "hartree", "eV")) + + # We are now done with SCF cycle (after a few lines). + self.insidescf = False + + # MP2 energies. + if line[1:5] == "!MP2": + + if not hasattr(self, 'mpenergies'): + self.mpenergies = [] + mp2energy = float(line.split()[-1]) + mp2energy = utils.convertor(mp2energy, "hartree", "eV") + self.mpenergies.append([mp2energy]) + + # MP2 energies if MP3 or MP4 is also calculated. + if line[1:5] == "MP2:": + + if not hasattr(self, 'mpenergies'): + self.mpenergies = [] + mp2energy = float(line.split()[2]) + mp2energy = utils.convertor(mp2energy, "hartree", "eV") + self.mpenergies.append([mp2energy]) + + # MP3 (D) and MP4 (DQ or SDQ) energies. + if line[1:8] == "MP3(D):": + + mp3energy = float(line.split()[2]) + mp2energy = utils.convertor(mp3energy, "hartree", "eV") + line = inputfile.next() + self.mpenergies[-1].append(mp2energy) + if line[1:9] == "MP4(DQ):": + mp4energy = float(line.split()[2]) + line = inputfile.next() + if line[1:10] == "MP4(SDQ):": + mp4energy = float(line.split()[2]) + mp4energy = utils.convertor(mp4energy, "hartree", "eV") + self.mpenergies[-1].append(mp4energy) + + # The CCSD program operates all closed-shel coupled cluster runs. + if line[1:15] == "PROGRAM * CCSD": + + if not hasattr(self, "ccenergies"): + self.ccenergies = [] + while line[1:20] != "Program statistics:": + # The last energy (most exact) will be read last and thus saved. + if line[1:5] == "!CCD" or line[1:6] == "!CCSD" or line[1:9] == "!CCSD(T)": + ccenergy = float(line.split()[-1]) + ccenergy = utils.convertor(ccenergy, "hartree", "eV") + line = inputfile.next() + self.ccenergies.append(ccenergy) + + # Read the occupancy (index of HOMO s). + # For restricted calculations, there is one line here. For unrestricted, two: + # Final alpha occupancy: ... + # Final beta occupancy: ... + if line[1:17] == "Final occupancy:": + self.homos = [int(line.split()[-1])-1] + if line[1:23] == "Final alpha occupancy:": + self.homos = [int(line.split()[-1])-1] + line = inputfile.next() + self.homos.append(int(line.split()[-1])-1) + + # From this block atombasis, moenergies, and mocoeffs can be parsed. + # Note that Molpro does not print this by default, you must add this in the input: + # GPRINT,ORBITALS + # What's more, this prints only the occupied orbitals. To get virtuals, add also: + # ORBPTIN,NVIRT + # where NVIRT is how many to print (can be some large number, like 99999, to print all). + # The block is in general flipped when compared to other programs (GAMESS, Gaussian), and + # MOs in the rows. Also, it does not cut the table into parts, rather each MO row has + # as many lines as it takes to print all the coefficients, as shown below: + # + # ELECTRON ORBITALS + # ================= + # + # + # Orb Occ Energy Couls-En Coefficients + # + # 1 1s 1 1s 1 2px 1 2py 1 2pz 2 1s (...) + # 3 1s 3 1s 3 2px 3 2py 3 2pz 4 1s (...) + # (...) + # + # 1.1 2 -11.0351 -43.4915 0.701460 0.025696 -0.000365 -0.000006 0.000000 0.006922 (...) + # -0.006450 0.004742 -0.001028 -0.002955 0.000000 -0.701460 (...) + # (...) + # + # For unrestricted calcualtions, ELECTRON ORBITALS is followed on the same line + # by FOR POSITIVE SPIN or FOR NEGATIVE SPIN. + # For examples, see data/Molpro/basicMolpro2006/dvb_sp*. + if line[1:18] == "ELECTRON ORBITALS" or self.electronorbitals: + # Detect if we are reading beta (negative spin) orbitals. + spin = 0 + if line[19:36] == "FOR NEGATIVE SPIN" or self.electronorbitals[19:36] == "FOR NEGATIVE SPIN": + spin = 1 + + if not self.electronorbitals: + dashes = inputfile.next() + blank = inputfile.next() + blank = inputfile.next() + headers = inputfile.next() + blank = inputfile.next() + + # Parse the list of atomic orbitals if atombasis or aonames is missing. + line = inputfile.next() + if not hasattr(self, "atombasis") or not hasattr(self, "aonames"): + self.atombasis = [] + for i in range(self.natom): + self.atombasis.append([]) + self.aonames = [] + aonum = 0 + while line.strip(): + for s in line.split(): + if s.isdigit(): + atomno = int(s) + self.atombasis[atomno-1].append(aonum) + aonum += 1 + else: + functype = s + element = self.table.element[self.atomnos[atomno-1]] + aoname = "%s%i_%s" %(element, atomno, functype) + self.aonames.append(aoname) + line = inputfile.next() + else: + while line.strip(): + line = inputfile.next() + + # Now there can be one or two blank lines. + while not line.strip(): + line = inputfile.next() + + # Create empty moenergies and mocoeffs if they don't exist. + if not hasattr(self, "moenergies"): + self.moenergies = [[]] + self.mocoeffs = [[]] + # Do the same if they exist and are being read again (spin=0), + # this means only the last print-out of these data are saved, + # which consistent with current cclib practices. + elif len(self.moenergies) == 1 and spin == 0: + self.moenergies = [[]] + self.mocoeffs = [[]] + else: + self.moenergies.append([]) + self.mocoeffs.append([]) + + while line.strip() and not "ORBITALS" in line: + coeffs = [] + while line.strip() != "": + if line[:30].strip(): + moenergy = float(line.split()[2]) + moenergy = utils.convertor(moenergy, "hartree", "eV") + self.moenergies[spin].append(moenergy) + line = line[31:] + # Each line has 10 coefficients in 10.6f format. + num = len(line)/10 + for i in range(num): + try: + coeff = float(line[10*i:10*(i+1)]) + # Molpro prints stars when coefficients are huge. + except ValueError, detail: + self.logger.warn("Set coefficient to zero: %s" %detail) + coeff = 0.0 + coeffs.append(coeff) + line = inputfile.next() + self.mocoeffs[spin].append(coeffs) + line = inputfile.next() + + # Check if last line begins the next ELECTRON ORBITALS section. + if line[1:18] == "ELECTRON ORBITALS": + self.electronorbitals = line + else: + self.electronorbitals = "" + + # If the MATROP program was called appropriately, + # the atomic obital overlap matrix S is printed. + # The matrix is printed straight-out, ten elements in each row, both halves. + # Note that is the entire matrix is not printed, then aooverlaps + # will not have dimensions nbasis x nbasis. + if line[1:9] == "MATRIX S": + + blank = inputfile.next() + symblocklabel = inputfile.next() + if not hasattr(self, "aooverlaps"): + self.aooverlaps = [[]] + line = inputfile.next() + while line.strip() != "": + elements = [float(s) for s in line.split()] + if len(self.aooverlaps[-1]) + len(elements) <= self.nbasis: + self.aooverlaps[-1] += elements + else: + n = len(self.aooverlaps[-1]) + len(elements) - self.nbasis + self.aooverlaps[-1] += elements[:-n] + self.aooverlaps.append([]) + self.aooverlaps[-1] += elements[-n:] + line = inputfile.next() + + # Thresholds are printed only if the defaults are changed with GTHRESH. + # In that case, we can fill geotargets with non-default values. + # The block should look like this as of Molpro 2006.1: + # THRESHOLDS: + + # ZERO = 1.00D-12 ONEINT = 1.00D-12 TWOINT = 1.00D-11 PREFAC = 1.00D-14 LOCALI = 1.00D-09 EORDER = 1.00D-04 + # ENERGY = 0.00D+00 ETEST = 0.00D+00 EDENS = 0.00D+00 THRDEDEF= 1.00D-06 GRADIENT= 1.00D-02 STEP = 1.00D-03 + # ORBITAL = 1.00D-05 CIVEC = 1.00D-05 COEFF = 1.00D-04 PRINTCI = 5.00D-02 PUNCHCI = 9.90D+01 OPTGRAD = 3.00D-04 + # OPTENERG= 1.00D-06 OPTSTEP = 3.00D-04 THRGRAD = 2.00D-04 COMPRESS= 1.00D-11 VARMIN = 1.00D-07 VARMAX = 1.00D-03 + # THRDOUB = 0.00D+00 THRDIV = 1.00D-05 THRRED = 1.00D-07 THRPSP = 1.00D+00 THRDC = 1.00D-10 THRCS = 1.00D-10 + # THRNRM = 1.00D-08 THREQ = 0.00D+00 THRDE = 1.00D+00 THRREF = 1.00D-05 SPARFAC = 1.00D+00 THRDLP = 1.00D-07 + # THRDIA = 1.00D-10 THRDLS = 1.00D-07 THRGPS = 0.00D+00 THRKEX = 0.00D+00 THRDIS = 2.00D-01 THRVAR = 1.00D-10 + # THRLOC = 1.00D-06 THRGAP = 1.00D-06 THRLOCT = -1.00D+00 THRGAPT = -1.00D+00 THRORB = 1.00D-06 THRMLTP = 0.00D+00 + # THRCPQCI= 1.00D-10 KEXTA = 0.00D+00 THRCOARS= 0.00D+00 SYMTOL = 1.00D-06 GRADTOL = 1.00D-06 THROVL = 1.00D-08 + # THRORTH = 1.00D-08 GRID = 1.00D-06 GRIDMAX = 1.00D-03 DTMAX = 0.00D+00 + if line [1:12] == "THRESHOLDS": + + blank = inputfile.next() + line = inputfile.next() + while line.strip(): + + if "OPTENERG" in line: + start = line.find("OPTENERG") + optenerg = line[start+10:start+20] + if "OPTGRAD" in line: + start = line.find("OPTGRAD") + optgrad = line[start+10:start+20] + if "OPTSTEP" in line: + start = line.find("OPTSTEP") + optstep = line[start+10:start+20] + line = inputfile.next() + + self.geotargets = [optenerg, optgrad, optstep] + + # The optimization history is the source for geovlues: + # END OF GEOMETRY OPTIMIZATION. TOTAL CPU: 246.9 SEC + # + # ITER. ENERGY(OLD) ENERGY(NEW) DE GRADMAX GRADNORM GRADRMS STEPMAX STEPLEN STEPRMS + # 1 -382.02936898 -382.04914450 -0.01977552 0.11354875 0.20127947 0.01183997 0.12972761 0.20171740 0.01186573 + # 2 -382.04914450 -382.05059234 -0.00144784 0.03299860 0.03963339 0.00233138 0.05577169 0.06687650 0.00393391 + # 3 -382.05059234 -382.05069136 -0.00009902 0.00694359 0.01069889 0.00062935 0.01654549 0.02016307 0.00118606 + # 4 -382.05069136 -382.05069130 0.00000006 0.00295497 0.00363023 0.00021354 0.00234307 0.00443525 0.00026090 + # 5 -382.05069130 -382.05069206 -0.00000075 0.00098220 0.00121031 0.00007119 0.00116863 0.00140452 0.00008262 + # 6 -382.05069206 -382.05069209 -0.00000003 0.00011350 0.00022306 0.00001312 0.00013321 0.00024526 0.00001443 + if line[1:30] == "END OF GEOMETRY OPTIMIZATION.": + + blank = inputfile.next() + headers = inputfile.next() + + # Although criteria can be changed, the printed format should not change. + # In case it does, retrieve the columns for each parameter. + headers = headers.split() + index_THRENERG = headers.index('DE') + index_THRGRAD = headers.index('GRADMAX') + index_THRSTEP = headers.index('STEPMAX') + + line = inputfile.next() + self.geovalues = [] + while line.strip() != "": + + line = line.split() + geovalues = [] + geovalues.append(float(line[index_THRENERG])) + geovalues.append(float(line[index_THRGRAD])) + geovalues.append(float(line[index_THRSTEP])) + self.geovalues.append(geovalues) + line = inputfile.next() + + # This block should look like this: + # Normal Modes + # + # 1 Au 2 Bu 3 Ag 4 Bg 5 Ag + # Wavenumbers [cm-1] 151.81 190.88 271.17 299.59 407.86 + # Intensities [km/mol] 0.33 0.28 0.00 0.00 0.00 + # Intensities [relative] 0.34 0.28 0.00 0.00 0.00 + # CX1 0.00000 -0.01009 0.02577 0.00000 0.06008 + # CY1 0.00000 -0.05723 -0.06696 0.00000 0.06349 + # CZ1 -0.02021 0.00000 0.00000 0.11848 0.00000 + # CX2 0.00000 -0.01344 0.05582 0.00000 -0.02513 + # CY2 0.00000 -0.06288 -0.03618 0.00000 0.00349 + # CZ2 -0.05565 0.00000 0.00000 0.07815 0.00000 + # ... + # Molpro prints low frequency modes in a subsequent section with the same format, + # which also contains zero frequency modes, with the title: + # Normal Modes of low/zero frequencies + if line[1:13] == "Normal Modes": + + if line[1:37] == "Normal Modes of low/zero frequencies": + islow = True + else: + islow = False + + blank = inputfile.next() + + # Each portion of five modes is followed by a single blank line. + # The whole block is followed by an additional blank line. + line = inputfile.next() + while line.strip(): + + if line[1:25].isspace(): + numbers = map(int, line.split()[::2]) + vibsyms = line.split()[1::2] + + if line[1:12] == "Wavenumbers": + vibfreqs = map(float, line.strip().split()[2:]) + + if line[1:21] == "Intensities [km/mol]": + vibirs = map(float, line.strip().split()[2:]) + + # There should always by 3xnatom displacement rows. + if line[1:11].isspace() and line[13:25].strip().isdigit(): + + # There are a maximum of 5 modes per line. + nmodes = len(line.split())-1 + + vibdisps = [] + for i in range(nmodes): + vibdisps.append([]) + for n in range(self.natom): + vibdisps[i].append([]) + for i in range(nmodes): + disp = float(line.split()[i+1]) + vibdisps[i][0].append(disp) + for i in range(self.natom*3 - 1): + line = inputfile.next() + iatom = (i+1)/3 + for i in range(nmodes): + disp = float(line.split()[i+1]) + vibdisps[i][iatom].append(disp) + + line = inputfile.next() + if not line.strip(): + + if not hasattr(self, "vibfreqs"): + self.vibfreqs = [] + if not hasattr(self, "vibsyms"): + self.vibsyms = [] + if not hasattr(self, "vibirs") and "vibirs" in dir(): + self.vibirs = [] + if not hasattr(self, "vibdisps") and "vibdisps" in dir(): + self.vibdisps = [] + + if not islow: + self.vibfreqs.extend(vibfreqs) + self.vibsyms.extend(vibsyms) + if "vibirs" in dir(): + self.vibirs.extend(vibirs) + if "vibdisps" in dir(): + self.vibdisps.extend(vibdisps) + else: + nonzero = [f > 0 for f in vibfreqs] + vibfreqs = [f for f in vibfreqs if f > 0] + self.vibfreqs = vibfreqs + self.vibfreqs + vibsyms = [vibsyms[i] for i in range(len(vibsyms)) if nonzero[i]] + self.vibsyms = vibsyms + self.vibsyms + if "vibirs" in dir(): + vibirs = [vibirs[i] for i in range(len(vibirs)) if nonzero[i]] + self.vibirs = vibirs + self.vibirs + if "vibdisps" in dir(): + vibdisps = [vibdisps[i] for i in range(len(vibdisps)) if nonzero[i]] + self.vibdisps = vibdisps + self.vibdisps + + line = inputfile.next() + + if line[1:16] == "Force Constants": + + self.logger.info("Creating attribute hessian") + self.hessian = [] + line = inputfile.next() + hess = [] + tmp = [] + + while line.strip(): + try: map(float, line.strip().split()[2:]) + except: + line = inputfile.next() + line.strip().split()[1:] + hess.extend([map(float,line.strip().split()[1:])]) + line = inputfile.next() + lig = 0 + + while (lig==0) or (len(hess[0]) > 1): + tmp.append(hess.pop(0)) + lig += 1 + k = 5 + + while len(hess) != 0: + tmp[k] += hess.pop(0) + k += 1 + if (len(tmp[k-1]) == lig): break + if k >= lig: k = len(tmp[-1]) + for l in tmp: self.hessian += l + + if line[1:14] == "Atomic Masses" and hasattr(self,"hessian"): + + line = inputfile.next() + self.amass = map(float, line.strip().split()[2:]) + + while line.strip(): + line = inputfile.next() + self.amass += map(float, line.strip().split()[2:]) + + +if __name__ == "__main__": + import doctest, molproparser + doctest.testmod(molproparser, verbose=False) diff --git a/external/cclib/parser/mopacparser.py b/external/cclib/parser/mopacparser.py index a27a8c1a02..13ee5c0a71 100644 --- a/external/cclib/parser/mopacparser.py +++ b/external/cclib/parser/mopacparser.py @@ -1,215 +1,215 @@ -""" -gmagoon 07/06/09: new class for MOPAC parsing, based on gaussianparser.py from cclib, described below: -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 814 $" - - -#import re - -import numpy - -import utils -import logfileparser - - -def symbol2int(symbol): - t = utils.PeriodicTable() - return t.number[symbol] - #if symbol == 'C': return 6 - #elif symbol == 'H': return 1 - #elif symbol == 'O': return 8 - #elif symbol == 'Si': return 14 - #else: return -1 - -class Mopac(logfileparser.Logfile): - """A MOPAC2009 output file.""" - - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(Mopac, self).__init__(logname="Mopac", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "Mopac log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'Mopac("%s")' % (self.filename) - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - # Number of atoms. (I think this section of code may be redundant and not needed) - # Example: Empirical Formula: C H2 O = 4 atoms - if line.find("Empirical Formula:") > -1: - - self.updateprogress(inputfile, "Attributes", self.fupdate) - #locate the component that beg - natom = int(line.split()[-2]) #second to last component should be number of atoms (last element is "atoms" (or possibly "atom"?)) - if hasattr(self, "natom"): - assert self.natom == natom - else: - self.natom = natom - - # Extract the atomic numbers and coordinates from the optimized geometry - # note that cartesian coordinates section occurs multiple times in the file, and we want to end up using the last instance - # also, note that the section labeled cartesian coordinates doesn't have as many decimal places as the one used here - # Example 1 (not used): -# CARTESIAN COORDINATES -# -# NO. ATOM X Y Z -# -# 1 O 4.7928 -0.8461 0.3641 -# 2 O 5.8977 -0.3171 0.0092 -# 3 C 3.8616 0.0654 0.8629 -# 4 O 2.9135 0.0549 -0.0719 -# 5 Si -0.6125 -0.0271 0.0487 -# 6 O 0.9200 0.2818 -0.6180 -# 7 O -1.3453 -1.2462 -0.8684 -# 8 O -1.4046 1.4708 0.0167 -# 9 O -0.5716 -0.5263 1.6651 -# 10 C 1.8529 1.0175 0.0716 -# 11 C -1.5193 -1.0359 -2.2416 -# 12 C -2.7764 1.5044 0.2897 -# 13 C -0.0136 -1.7640 2.0001 -# 14 C 2.1985 2.3297 -0.6413 -# 15 C -2.2972 -2.2169 -2.8050 -# 16 C -3.2205 2.9603 0.3151 -# 17 C 1.2114 -1.5689 2.8841 -# 18 H 4.1028 0.8832 1.5483 -# ... - # Example 2 (used): -# ATOM CHEMICAL X Y Z -# NUMBER SYMBOL (ANGSTROMS) (ANGSTROMS) (ANGSTROMS) -# -# 1 O 4.79280259 * -0.84610232 * 0.36409474 * -# 2 O 5.89768035 * -0.31706418 * 0.00917035 * -# 3 C 3.86164836 * 0.06535206 * 0.86290800 * -# 4 O 2.91352871 * 0.05485130 * -0.07194851 * -# 5 Si -0.61245484 * -0.02707117 * 0.04871188 * -# 6 O 0.91999240 * 0.28181302 * -0.61800545 * -# 7 O -1.34526429 * -1.24617340 * -0.86844046 * -# 8 O -1.40457125 * 1.47080489 * 0.01671181 * -# 9 O -0.57162101 * -0.52628027 * 1.66508989 * -# 10 C 1.85290140 * 1.01752620 * 0.07159039 * -# 11 C -1.51932072 * -1.03592573 * -2.24160046 * -# 12 C -2.77644395 * 1.50443941 * 0.28973441 * -# 13 C -0.01360776 * -1.76397803 * 2.00010724 * -# 14 C 2.19854080 * 2.32966388 * -0.64131311 * -# 15 C -2.29721668 * -2.21688022 * -2.80495545 * -# 16 C -3.22047132 * 2.96028967 * 0.31511890 * -# 17 C 1.21142471 * -1.56886315 * 2.88414255 * -# 18 H 4.10284938 * 0.88318846 * 1.54829483 * -# 19 H 1.60266809 * 1.19314394 * 1.14931859 * -# 20 H -2.06992519 * -0.08909329 * -2.41564011 * -# 21 H -0.53396028 * -0.94280520 * -2.73816125 * -# 22 H -2.99280631 * 1.01386560 * 1.25905636 * -# 23 H -3.32412961 * 0.94305635 * -0.49427315 * -# 24 H -0.81149878 * -2.30331548 * 2.54543351 * -# 25 H 0.24486568 * -2.37041735 * 1.10943219 * -# 26 H 2.46163770 * 2.17667287 * -1.69615441 * -# 27 H 1.34364456 * 3.01690600 * -0.61108044 * -# 28 H 3.04795301 * 2.82487051 * -0.15380555 * -# 29 H -1.76804185 * -3.16646015 * -2.65234745 * -# 30 H -3.28543199 * -2.31880074 * -2.33789659 * -# 31 H -2.45109195 * -2.09228197 * -3.88420787 * -# 32 H -3.02567427 * 3.46605770 * -0.63952294 * -# 33 H -4.29770055 * 3.02763638 * 0.51281387 * -# 34 H -2.70317481 * 3.53302115 * 1.09570604 * -# 35 H 2.01935375 * -1.03805729 * 2.35810565 * -# 36 H 1.60901654 * -2.53904354 * 3.20705714 * -# 37 H 0.97814118 * -0.98964976 * 3.78695207 * - if (line.find("NUMBER SYMBOL (ANGSTROMS) (ANGSTROMS) (ANGSTROMS)") > -1 or line.find("NUMBER SYMBOL (ANGSTROMS) (ANGSTROMS) (ANGSTROMS)") > -1): - - - - self.updateprogress(inputfile, "Attributes", self.cupdate) - - self.inputcoords = [] - self.inputatoms = [] - - blankline = inputfile.next() - - atomcoords = [] - line = inputfile.next() - # while line != blankline: - while len(line.split()) > 0: - broken = line.split() - self.inputatoms.append(symbol2int(broken[1])) - xc = float(broken[2]) - yc = float(broken[4]) - zc = float(broken[6]) - atomcoords.append([xc,yc,zc]) - line = inputfile.next() - - self.inputcoords.append(atomcoords) - - if not hasattr(self, "natom"): - self.atomnos = numpy.array(self.inputatoms, 'i') - self.natom = len(self.atomnos) - -#read energy (in kcal/mol, converted to eV) -# Example: FINAL HEAT OF FORMATION = -333.88606 KCAL = -1396.97927 KJ - if line[0:35] == ' FINAL HEAT OF FORMATION =': - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - self.scfenergies.append(utils.convertor(self.float(line.split()[5])/627.5095, "hartree", "eV")) #note conversion from kcal/mol to hartree - - #molecular mass parsing (units will be amu) - #Example: MOLECULAR WEIGHT = - if line[0:35] == ' MOLECULAR WEIGHT =': - self.molmass = self.float(line.split()[3]) - - #rotational constants (converted to GHZ) - #Example: - -# ROTATIONAL CONSTANTS IN CM(-1) -# -# A = 0.01757641 B = 0.00739763 C = 0.00712013 - #could also read in moment of inertia, but this should just differ by a constant: rot cons= h/(8*Pi^2*I) - #note that the last occurence of this in the thermochemistry section has reduced precision, so we will want to use the 2nd to last instance - if line[0:40] == ' ROTATIONAL CONSTANTS IN CM(-1)': - blankline = inputfile.next(); - rotinfo=inputfile.next(); - if not hasattr(self, "rotcons"): - self.rotcons = [] - broken = rotinfo.split() - sol = 29.9792458 #speed of light in vacuum in 10^9 cm/s, cf. http://physics.nist.gov/cgi-bin/cuu/Value?c|search_for=universal_in! - a = float(broken[2])*sol - b = float(broken[5])*sol - c = float(broken[8])*sol - self.rotcons.append([a, b, c]) - - # Start of the IR/Raman frequency section. -#Example: -# VIBRATION 1 1A ATOM PAIR ENERGY CONTRIBUTION RADIAL -# FREQ. 15.08 C 12 -- C 16 +7.9% (999.0%) 0.0% -# T-DIPOLE 0.2028 C 16 -- H 34 +5.8% (999.0%) 28.0% -# TRAVEL 0.0240 C 16 -- H 32 +5.6% (999.0%) 35.0% -# RED. MASS 1.7712 O 1 -- O 4 +5.2% (999.0%) 0.4% -# EFF. MASS7752.8338 -# -# VIBRATION 2 2A ATOM PAIR ENERGY CONTRIBUTION RADIAL -# FREQ. 42.22 C 11 -- C 15 +9.0% (985.8%) 0.0% -# T-DIPOLE 0.1675 C 15 -- H 31 +6.6% (843.6%) 3.3% -# TRAVEL 0.0359 C 15 -- H 29 +6.0% (802.8%) 24.5% -# RED. MASS 1.7417 C 13 -- C 17 +5.8% (792.7%) 0.0% -# EFF. MASS1242.2114 - if line[1:10] == 'VIBRATION': - line = inputfile.next() - self.updateprogress(inputfile, "Frequency Information", self.fupdate) - - if not hasattr(self, 'vibfreqs'): - self.vibfreqs = [] - freq = self.float(line.split()[1]) - #self.vibfreqs.extend(freqs) - self.vibfreqs.append(freq) - - -if __name__ == "__main__": - import doctest, mopacparser - doctest.testmod(mopacparser, verbose=False) +""" +gmagoon 07/06/09: new class for MOPAC parsing, based on gaussianparser.py from cclib, described below: +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 814 $" + + +#import re + +import numpy + +import utils +import logfileparser + + +def symbol2int(symbol): + t = utils.PeriodicTable() + return t.number[symbol] + #if symbol == 'C': return 6 + #elif symbol == 'H': return 1 + #elif symbol == 'O': return 8 + #elif symbol == 'Si': return 14 + #else: return -1 + +class Mopac(logfileparser.Logfile): + """A MOPAC2009 output file.""" + + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(Mopac, self).__init__(logname="Mopac", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "Mopac log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'Mopac("%s")' % (self.filename) + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + # Number of atoms. (I think this section of code may be redundant and not needed) + # Example: Empirical Formula: C H2 O = 4 atoms + if line.find("Empirical Formula:") > -1: + + self.updateprogress(inputfile, "Attributes", self.fupdate) + #locate the component that beg + natom = int(line.split()[-2]) #second to last component should be number of atoms (last element is "atoms" (or possibly "atom"?)) + if hasattr(self, "natom"): + assert self.natom == natom + else: + self.natom = natom + + # Extract the atomic numbers and coordinates from the optimized geometry + # note that cartesian coordinates section occurs multiple times in the file, and we want to end up using the last instance + # also, note that the section labeled cartesian coordinates doesn't have as many decimal places as the one used here + # Example 1 (not used): +# CARTESIAN COORDINATES +# +# NO. ATOM X Y Z +# +# 1 O 4.7928 -0.8461 0.3641 +# 2 O 5.8977 -0.3171 0.0092 +# 3 C 3.8616 0.0654 0.8629 +# 4 O 2.9135 0.0549 -0.0719 +# 5 Si -0.6125 -0.0271 0.0487 +# 6 O 0.9200 0.2818 -0.6180 +# 7 O -1.3453 -1.2462 -0.8684 +# 8 O -1.4046 1.4708 0.0167 +# 9 O -0.5716 -0.5263 1.6651 +# 10 C 1.8529 1.0175 0.0716 +# 11 C -1.5193 -1.0359 -2.2416 +# 12 C -2.7764 1.5044 0.2897 +# 13 C -0.0136 -1.7640 2.0001 +# 14 C 2.1985 2.3297 -0.6413 +# 15 C -2.2972 -2.2169 -2.8050 +# 16 C -3.2205 2.9603 0.3151 +# 17 C 1.2114 -1.5689 2.8841 +# 18 H 4.1028 0.8832 1.5483 +# ... + # Example 2 (used): +# ATOM CHEMICAL X Y Z +# NUMBER SYMBOL (ANGSTROMS) (ANGSTROMS) (ANGSTROMS) +# +# 1 O 4.79280259 * -0.84610232 * 0.36409474 * +# 2 O 5.89768035 * -0.31706418 * 0.00917035 * +# 3 C 3.86164836 * 0.06535206 * 0.86290800 * +# 4 O 2.91352871 * 0.05485130 * -0.07194851 * +# 5 Si -0.61245484 * -0.02707117 * 0.04871188 * +# 6 O 0.91999240 * 0.28181302 * -0.61800545 * +# 7 O -1.34526429 * -1.24617340 * -0.86844046 * +# 8 O -1.40457125 * 1.47080489 * 0.01671181 * +# 9 O -0.57162101 * -0.52628027 * 1.66508989 * +# 10 C 1.85290140 * 1.01752620 * 0.07159039 * +# 11 C -1.51932072 * -1.03592573 * -2.24160046 * +# 12 C -2.77644395 * 1.50443941 * 0.28973441 * +# 13 C -0.01360776 * -1.76397803 * 2.00010724 * +# 14 C 2.19854080 * 2.32966388 * -0.64131311 * +# 15 C -2.29721668 * -2.21688022 * -2.80495545 * +# 16 C -3.22047132 * 2.96028967 * 0.31511890 * +# 17 C 1.21142471 * -1.56886315 * 2.88414255 * +# 18 H 4.10284938 * 0.88318846 * 1.54829483 * +# 19 H 1.60266809 * 1.19314394 * 1.14931859 * +# 20 H -2.06992519 * -0.08909329 * -2.41564011 * +# 21 H -0.53396028 * -0.94280520 * -2.73816125 * +# 22 H -2.99280631 * 1.01386560 * 1.25905636 * +# 23 H -3.32412961 * 0.94305635 * -0.49427315 * +# 24 H -0.81149878 * -2.30331548 * 2.54543351 * +# 25 H 0.24486568 * -2.37041735 * 1.10943219 * +# 26 H 2.46163770 * 2.17667287 * -1.69615441 * +# 27 H 1.34364456 * 3.01690600 * -0.61108044 * +# 28 H 3.04795301 * 2.82487051 * -0.15380555 * +# 29 H -1.76804185 * -3.16646015 * -2.65234745 * +# 30 H -3.28543199 * -2.31880074 * -2.33789659 * +# 31 H -2.45109195 * -2.09228197 * -3.88420787 * +# 32 H -3.02567427 * 3.46605770 * -0.63952294 * +# 33 H -4.29770055 * 3.02763638 * 0.51281387 * +# 34 H -2.70317481 * 3.53302115 * 1.09570604 * +# 35 H 2.01935375 * -1.03805729 * 2.35810565 * +# 36 H 1.60901654 * -2.53904354 * 3.20705714 * +# 37 H 0.97814118 * -0.98964976 * 3.78695207 * + if (line.find("NUMBER SYMBOL (ANGSTROMS) (ANGSTROMS) (ANGSTROMS)") > -1 or line.find("NUMBER SYMBOL (ANGSTROMS) (ANGSTROMS) (ANGSTROMS)") > -1): + + + + self.updateprogress(inputfile, "Attributes", self.cupdate) + + self.inputcoords = [] + self.inputatoms = [] + + blankline = inputfile.next() + + atomcoords = [] + line = inputfile.next() + # while line != blankline: + while len(line.split()) > 0: + broken = line.split() + self.inputatoms.append(symbol2int(broken[1])) + xc = float(broken[2]) + yc = float(broken[4]) + zc = float(broken[6]) + atomcoords.append([xc,yc,zc]) + line = inputfile.next() + + self.inputcoords.append(atomcoords) + + if not hasattr(self, "natom"): + self.atomnos = numpy.array(self.inputatoms, 'i') + self.natom = len(self.atomnos) + +#read energy (in kcal/mol, converted to eV) +# Example: FINAL HEAT OF FORMATION = -333.88606 KCAL = -1396.97927 KJ + if line[0:35] == ' FINAL HEAT OF FORMATION =': + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + self.scfenergies.append(utils.convertor(self.float(line.split()[5])/627.5095, "hartree", "eV")) #note conversion from kcal/mol to hartree + + #molecular mass parsing (units will be amu) + #Example: MOLECULAR WEIGHT = + if line[0:35] == ' MOLECULAR WEIGHT =': + self.molmass = self.float(line.split()[3]) + + #rotational constants (converted to GHZ) + #Example: + +# ROTATIONAL CONSTANTS IN CM(-1) +# +# A = 0.01757641 B = 0.00739763 C = 0.00712013 + #could also read in moment of inertia, but this should just differ by a constant: rot cons= h/(8*Pi^2*I) + #note that the last occurence of this in the thermochemistry section has reduced precision, so we will want to use the 2nd to last instance + if line[0:40] == ' ROTATIONAL CONSTANTS IN CM(-1)': + blankline = inputfile.next(); + rotinfo=inputfile.next(); + if not hasattr(self, "rotcons"): + self.rotcons = [] + broken = rotinfo.split() + sol = 29.9792458 #speed of light in vacuum in 10^9 cm/s, cf. http://physics.nist.gov/cgi-bin/cuu/Value?c|search_for=universal_in! + a = float(broken[2])*sol + b = float(broken[5])*sol + c = float(broken[8])*sol + self.rotcons.append([a, b, c]) + + # Start of the IR/Raman frequency section. +#Example: +# VIBRATION 1 1A ATOM PAIR ENERGY CONTRIBUTION RADIAL +# FREQ. 15.08 C 12 -- C 16 +7.9% (999.0%) 0.0% +# T-DIPOLE 0.2028 C 16 -- H 34 +5.8% (999.0%) 28.0% +# TRAVEL 0.0240 C 16 -- H 32 +5.6% (999.0%) 35.0% +# RED. MASS 1.7712 O 1 -- O 4 +5.2% (999.0%) 0.4% +# EFF. MASS7752.8338 +# +# VIBRATION 2 2A ATOM PAIR ENERGY CONTRIBUTION RADIAL +# FREQ. 42.22 C 11 -- C 15 +9.0% (985.8%) 0.0% +# T-DIPOLE 0.1675 C 15 -- H 31 +6.6% (843.6%) 3.3% +# TRAVEL 0.0359 C 15 -- H 29 +6.0% (802.8%) 24.5% +# RED. MASS 1.7417 C 13 -- C 17 +5.8% (792.7%) 0.0% +# EFF. MASS1242.2114 + if line[1:10] == 'VIBRATION': + line = inputfile.next() + self.updateprogress(inputfile, "Frequency Information", self.fupdate) + + if not hasattr(self, 'vibfreqs'): + self.vibfreqs = [] + freq = self.float(line.split()[1]) + #self.vibfreqs.extend(freqs) + self.vibfreqs.append(freq) + + +if __name__ == "__main__": + import doctest, mopacparser + doctest.testmod(mopacparser, verbose=False) diff --git a/external/cclib/parser/orcaparser.py b/external/cclib/parser/orcaparser.py index 4af2fb86f8..6e80ac0b32 100644 --- a/external/cclib/parser/orcaparser.py +++ b/external/cclib/parser/orcaparser.py @@ -1,407 +1,407 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 668 $" - - -import re - -import numpy - -import logfileparser -import utils - - -class ORCA(logfileparser.Logfile): - """An ORCA log file.""" - - def __init__(self, *args, **kwargs): - - # Call the __init__ method of the superclass - super(ORCA, self).__init__(logname="ORCA", *args, **kwargs) - - def __str__(self): - """Return a string representation of the object.""" - return "ORCA log file %s" % (self.filename) - - def __repr__(self): - """Return a representation of the object.""" - return 'ORCA("%s")' % (self.filename) - - def normalisesym(self, label): - """Use standard symmetry labels instead of Gaussian labels. - - To normalise: - (1) If label is one of [SG, PI, PHI, DLTA], replace by [sigma, pi, phi, delta] - (2) replace any G or U by their lowercase equivalent - - >>> sym = Gaussian("dummyfile").normalisesym - >>> labels = ['A1', 'AG', 'A1G', "SG", "PI", "PHI", "DLTA", 'DLTU', 'SGG'] - >>> map(sym, labels) - ['A1', 'Ag', 'A1g', 'sigma', 'pi', 'phi', 'delta', 'delta.u', 'sigma.g'] - """ - - def before_parsing(self): - - # Used to index self.scftargets[]. - SCFRMS, SCFMAX, SCFENERGY = range(3) - # Flag that indicates whether it has reached the end of a geoopt. - self.optfinished = False - # Flag for identifying Coupled Cluster runs. - self.coupledcluster = False - - def extract(self, inputfile, line): - """Extract information from the file object inputfile.""" - - if line[0:15] == "Number of atoms": - - natom = int(line.split()[-1]) - if hasattr(self, "natom"): - # I wonder whether this code will ever be executed. - assert self.natom == natom - else: - self.natom = natom - - if line[1:13] == "Total Charge": -#get charge and multiplicity info - self.charge = int(line.split()[-1]) - line = inputfile.next() - self.mult = int(line.split()[-1]) - - if line[25:50] == "Geometry Optimization Run": -#get geotarget info - line = inputfile.next() - while line[0:23] != "Convergence Tolerances:": - line = inputfile.next() - - self.geotargets = numpy.zeros((5,), "d") - for i in range(5): - line = inputfile.next() - self.geotargets[i] = float(line.split()[-2]) - - # Read in scfvalues. - if line [:14] == "SCF ITERATIONS": - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - dashes = inputfile.next() - line = inputfile.next().split() - assert line[1] == "Energy" - assert line[2] == "Delta-E" - assert line[3] == "Max-DP" - self.scfvalues.append([]) - while line != []: - if line[0].isdigit(): - energy = float(line[1]) - deltaE = float(line[2]) - maxDP = float(line[3]) - rmsDP = float(line[4]) - self.scfvalues[-1].append([deltaE, maxDP, rmsDP]) - line = inputfile.next().split() - - # Read in values for last SCF iteration and scftargets. - if line[:15] == "SCF CONVERGENCE": - if not hasattr(self, "scfvalues"): - self.scfvalues = [] - if not hasattr(self, "scftargets"): - self.scftargets = [] - dashes = inputfile.next() - blank = inputfile.next() - line = inputfile.next() - assert line[:29].strip() == "Last Energy change" - deltaE_value = float(line[33:46]) - deltaE_target = float(line[60:72]) - line = inputfile.next() - assert line[:29].strip() == "Last MAX-Density change" - maxDP_value = float(line[33:46]) - maxDP_target = float(line[60:72]) - line = inputfile.next() - assert line[:29].strip() == "Last RMS-Density change" - rmsDP_value = float(line[33:46]) - rmsDP_target = float(line[60:72]) - line = inputfile.next() - assert line[:29].strip() == "Last DIIS Error" - self.scfvalues[-1].append([deltaE_value,maxDP_value,rmsDP_value]) - self.scftargets.append([deltaE_target,maxDP_target,rmsDP_target]) - - # Read in SCF energy, at least in SP calculation. - if line [:16] == "TOTAL SCF ENERGY": - if not hasattr(self, "scfenergies"): - self.scfenergies = [] - dashes = inputfile.next() - blank = inputfile.next() - line = inputfile.next() - if line[:12] == "Total Energy": - energy = float(line[50:67]) - self.scfenergies.append(energy) - - if line[33:53] == "Geometry convergence": -#get geometry convergence criteria - if not hasattr(self, "geovalues"): - self.geovalues = [ ] - - newlist = [] - headers = inputfile.next() - dashes = inputfile.next() - - #check if energy change is present (steps > 1) - line = inputfile.next() - if line.find("Energy change") > 0: - newlist.append(float(line.split()[2])) - line = inputfile.next() - else: - newlist.append(0.0) - - #get rest of info - for i in range(4): - newlist.append(float(line.split()[2])) - line = inputfile.next() - - self.geovalues.append(newlist) - - if line[0:21] == "CARTESIAN COORDINATES" and not hasattr(self, "atomcoords"): -#if not an optimization, determine structure used - dashes = inputfile.next() - - atomnos = [] - atomcoords = [] - line = inputfile.next() - while len(line) > 1: - broken = line.split() - atomnos.append(self.table.number[broken[0]]) - atomcoords.append(map(float, broken[1:4])) - line = inputfile.next() - - self.atomcoords = [atomcoords] - if not hasattr(self, "atomnos"): - self.atomnos = atomnos - self.natom = len(atomnos) - - if line[26:53] == "GEOMETRY OPTIMIZATION CYCLE": -#parse geometry coords - stars = inputfile.next() - dashes = inputfile.next() - text = inputfile.next() - dashes = inputfile.next() - - if not hasattr(self,"atomcoords"): - self.atomcoords = [] - - atomnos = [] - atomcoords = [] - for i in range(self.natom): - line = inputfile.next() - broken = line.split() - atomnos.append(self.table.number[broken[0]]) - atomcoords.append(map(float, broken[1:4])) - - self.atomcoords.append(atomcoords) - if not hasattr(self, "atomnos"): - self.atomnos = numpy.array(atomnos,'i') - - if line[21:68] == "FINAL ENERGY EVALUATION AT THE STATIONARY POINT": - text = inputfile.next() - broken = text.split() - assert int(broken[2]) == len(self.atomcoords) - stars = inputfile.next() - dashes = inputfile.next() - text = inputfile.next() - dashes = inputfile.next() - - atomcoords = [] - for i in range(self.natom): - line = inputfile.next() - broken = line.split() - atomcoords.append(map(float, broken[1:4])) - - self.atomcoords.append(atomcoords) - - if line[0:16] == "ORBITAL ENERGIES": -#parser orbial energy information - dashes = inputfile.next() - text = inputfile.next() - text = inputfile.next() - - self.moenergies = [[]] - self.homos = [[0]] - - line = inputfile.next() - while len(line) > 20: #restricted calcs are terminated by ------ - info = line.split() - self.moenergies[0].append(float(info[3])) - if float(info[1]) > 0.00: #might be 1 or 2, depending on restricted-ness - self.homos[0] = int(info[0]) - line = inputfile.next() - - line = inputfile.next() - - #handle beta orbitals - if line[17:35] == "SPIN DOWN ORBITALS": - text = inputfile.next() - - self.moenergies.append([]) - self.homos.append(0) - - line = inputfile.next() - while len(line) > 20: #actually terminated by ------ - info = line.split() - self.moenergies[1].append(float(info[3])) - if float(info[1]) == 1.00: - self.homos[1] = int(info[0]) - line = inputfile.next() - - if line[1:32] == "# of contracted basis functions": - self.nbasis = int(line.split()[-1]) - - if line[0:14] == "OVERLAP MATRIX": -#parser the overlap matrix - dashes = inputfile.next() - - self.aooverlaps = numpy.zeros( (self.nbasis, self.nbasis), "d") - for i in range(0, self.nbasis, 6): - header = inputfile.next() - size = len(header.split()) - - for j in range(self.nbasis): - line = inputfile.next() - broken = line.split() - self.aooverlaps[j, i:i+size] = map(float, broken[1:size+1]) - - # Molecular orbital coefficients. - # This is also where atombasis is parsed. - if line[0:18] == "MOLECULAR ORBITALS": - - dashses = inputfile.next() - - mocoeffs = [ numpy.zeros((self.nbasis, self.nbasis), "d") ] - self.aonames = [] - self.atombasis = [] - for n in range(self.natom): - self.atombasis.append([]) - - for spin in range(len(self.moenergies)): - - if spin == 1: - blank = inputfile.next() - mocoeffs.append(numpy.zeros((self.nbasis, self.nbasis), "d")) - - for i in range(0, self.nbasis, 6): - numbers = inputfile.next() - energies = inputfile.next() - occs = inputfile.next() - dashes = inputfile.next() - broken = dashes.split() - size = len(broken) - - for j in range(self.nbasis): - line = inputfile.next() - broken = line.split() - - #only need this on the first time through - if spin == 0 and i == 0: - atomname = line[3:5].split()[0] - num = int(line[0:3]) - orbital = broken[1].upper() - - self.aonames.append("%s%i_%s"%(atomname, num+1, orbital)) - self.atombasis[num].append(j) - - temp = [] - vals = line[16:-1] #-1 to remove the last blank space - for k in range(0, len(vals), 10): - temp.append(float(vals[k:k+10])) - mocoeffs[spin][i:i+size, j] = temp - - self.mocoeffs = mocoeffs - - if line[0:18] == "TD-DFT/TDA EXCITED": - sym = "Triplet" # Could be singlets or triplets - if line.find("SINGLETS") >= 0: - sym = "Singlet" - self.etsecs = [] - self.etenergies = [] - self.etsyms = [] - lookup = {'a':0, 'b':1} - line = inputfile.next() - while line.find("STATE") < 0: - line = inputfile.next() - # Contains STATE or is blank - while line.find("STATE") >= 0: - broken = line.split() - self.etenergies.append(float(broken[-2])) - self.etsyms.append(sym) - line = inputfile.next() - sec = [] - # Contains SEC or is blank - while line.strip(): - start = line[0:8].strip() - start = (int(start[:-1]), lookup[start[-1]]) - end = line[10:17].strip() - end = (int(end[:-1]), lookup[end[-1]]) - contrib = float(line[35:47].strip()) - sec.append([start, end, contrib]) - line = inputfile.next() - self.etsecs.append(sec) - line = inputfile.next() - - if line[25:44] == "ABSORPTION SPECTRUM": - minus = inputfile.next() - header = inputfile.next() - header = inputfile.next() - minus = inputfile.next() - self.etoscs = [] - for x in self.etsyms: - osc = inputfile.next().split()[3] - if osc == "spin": # "spin forbidden" - osc = 0 - else: - osc = float(osc) - self.etoscs.append(osc) - - if line[0:23] == "VIBRATIONAL FREQUENCIES": -#parse the vibrational frequencies - dashes = inputfile.next() - blank = inputfile.next() - - self.vibfreqs = numpy.zeros((3 * self.natom,),"d") - - for i in range(3 * self.natom): - line = inputfile.next() - self.vibfreqs[i] = float(line.split()[1]) - - if line[0:11] == "IR SPECTRUM": -#parse ir intensities - dashes = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - dashes = inputfile.next() - - self.vibirs = numpy.zeros((3 * self.natom,),"d") - - line = inputfile.next() - while len(line) > 2: - num = int(line[0:4]) - self.vibirs[num] = float(line.split()[2]) - line = inputfile.next() - - if line[0:14] == "RAMAN SPECTRUM": -#parser raman intensities - dashes = inputfile.next() - blank = inputfile.next() - header = inputfile.next() - dashes = inputfile.next() - - self.vibramans = numpy.zeros((3 * self.natom,),"d") - - line = inputfile.next() - while len(line) > 2: - num = int(line[0:4]) - self.vibramans[num] = float(line.split()[2]) - line = inputfile.next() - - - -if __name__ == "__main__": - import doctest, orcaparser - doctest.testmod(orcaparser, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 668 $" + + +import re + +import numpy + +import logfileparser +import utils + + +class ORCA(logfileparser.Logfile): + """An ORCA log file.""" + + def __init__(self, *args, **kwargs): + + # Call the __init__ method of the superclass + super(ORCA, self).__init__(logname="ORCA", *args, **kwargs) + + def __str__(self): + """Return a string representation of the object.""" + return "ORCA log file %s" % (self.filename) + + def __repr__(self): + """Return a representation of the object.""" + return 'ORCA("%s")' % (self.filename) + + def normalisesym(self, label): + """Use standard symmetry labels instead of Gaussian labels. + + To normalise: + (1) If label is one of [SG, PI, PHI, DLTA], replace by [sigma, pi, phi, delta] + (2) replace any G or U by their lowercase equivalent + + >>> sym = Gaussian("dummyfile").normalisesym + >>> labels = ['A1', 'AG', 'A1G', "SG", "PI", "PHI", "DLTA", 'DLTU', 'SGG'] + >>> map(sym, labels) + ['A1', 'Ag', 'A1g', 'sigma', 'pi', 'phi', 'delta', 'delta.u', 'sigma.g'] + """ + + def before_parsing(self): + + # Used to index self.scftargets[]. + SCFRMS, SCFMAX, SCFENERGY = range(3) + # Flag that indicates whether it has reached the end of a geoopt. + self.optfinished = False + # Flag for identifying Coupled Cluster runs. + self.coupledcluster = False + + def extract(self, inputfile, line): + """Extract information from the file object inputfile.""" + + if line[0:15] == "Number of atoms": + + natom = int(line.split()[-1]) + if hasattr(self, "natom"): + # I wonder whether this code will ever be executed. + assert self.natom == natom + else: + self.natom = natom + + if line[1:13] == "Total Charge": +#get charge and multiplicity info + self.charge = int(line.split()[-1]) + line = inputfile.next() + self.mult = int(line.split()[-1]) + + if line[25:50] == "Geometry Optimization Run": +#get geotarget info + line = inputfile.next() + while line[0:23] != "Convergence Tolerances:": + line = inputfile.next() + + self.geotargets = numpy.zeros((5,), "d") + for i in range(5): + line = inputfile.next() + self.geotargets[i] = float(line.split()[-2]) + + # Read in scfvalues. + if line [:14] == "SCF ITERATIONS": + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + dashes = inputfile.next() + line = inputfile.next().split() + assert line[1] == "Energy" + assert line[2] == "Delta-E" + assert line[3] == "Max-DP" + self.scfvalues.append([]) + while line != []: + if line[0].isdigit(): + energy = float(line[1]) + deltaE = float(line[2]) + maxDP = float(line[3]) + rmsDP = float(line[4]) + self.scfvalues[-1].append([deltaE, maxDP, rmsDP]) + line = inputfile.next().split() + + # Read in values for last SCF iteration and scftargets. + if line[:15] == "SCF CONVERGENCE": + if not hasattr(self, "scfvalues"): + self.scfvalues = [] + if not hasattr(self, "scftargets"): + self.scftargets = [] + dashes = inputfile.next() + blank = inputfile.next() + line = inputfile.next() + assert line[:29].strip() == "Last Energy change" + deltaE_value = float(line[33:46]) + deltaE_target = float(line[60:72]) + line = inputfile.next() + assert line[:29].strip() == "Last MAX-Density change" + maxDP_value = float(line[33:46]) + maxDP_target = float(line[60:72]) + line = inputfile.next() + assert line[:29].strip() == "Last RMS-Density change" + rmsDP_value = float(line[33:46]) + rmsDP_target = float(line[60:72]) + line = inputfile.next() + assert line[:29].strip() == "Last DIIS Error" + self.scfvalues[-1].append([deltaE_value,maxDP_value,rmsDP_value]) + self.scftargets.append([deltaE_target,maxDP_target,rmsDP_target]) + + # Read in SCF energy, at least in SP calculation. + if line [:16] == "TOTAL SCF ENERGY": + if not hasattr(self, "scfenergies"): + self.scfenergies = [] + dashes = inputfile.next() + blank = inputfile.next() + line = inputfile.next() + if line[:12] == "Total Energy": + energy = float(line[50:67]) + self.scfenergies.append(energy) + + if line[33:53] == "Geometry convergence": +#get geometry convergence criteria + if not hasattr(self, "geovalues"): + self.geovalues = [ ] + + newlist = [] + headers = inputfile.next() + dashes = inputfile.next() + + #check if energy change is present (steps > 1) + line = inputfile.next() + if line.find("Energy change") > 0: + newlist.append(float(line.split()[2])) + line = inputfile.next() + else: + newlist.append(0.0) + + #get rest of info + for i in range(4): + newlist.append(float(line.split()[2])) + line = inputfile.next() + + self.geovalues.append(newlist) + + if line[0:21] == "CARTESIAN COORDINATES" and not hasattr(self, "atomcoords"): +#if not an optimization, determine structure used + dashes = inputfile.next() + + atomnos = [] + atomcoords = [] + line = inputfile.next() + while len(line) > 1: + broken = line.split() + atomnos.append(self.table.number[broken[0]]) + atomcoords.append(map(float, broken[1:4])) + line = inputfile.next() + + self.atomcoords = [atomcoords] + if not hasattr(self, "atomnos"): + self.atomnos = atomnos + self.natom = len(atomnos) + + if line[26:53] == "GEOMETRY OPTIMIZATION CYCLE": +#parse geometry coords + stars = inputfile.next() + dashes = inputfile.next() + text = inputfile.next() + dashes = inputfile.next() + + if not hasattr(self,"atomcoords"): + self.atomcoords = [] + + atomnos = [] + atomcoords = [] + for i in range(self.natom): + line = inputfile.next() + broken = line.split() + atomnos.append(self.table.number[broken[0]]) + atomcoords.append(map(float, broken[1:4])) + + self.atomcoords.append(atomcoords) + if not hasattr(self, "atomnos"): + self.atomnos = numpy.array(atomnos,'i') + + if line[21:68] == "FINAL ENERGY EVALUATION AT THE STATIONARY POINT": + text = inputfile.next() + broken = text.split() + assert int(broken[2]) == len(self.atomcoords) + stars = inputfile.next() + dashes = inputfile.next() + text = inputfile.next() + dashes = inputfile.next() + + atomcoords = [] + for i in range(self.natom): + line = inputfile.next() + broken = line.split() + atomcoords.append(map(float, broken[1:4])) + + self.atomcoords.append(atomcoords) + + if line[0:16] == "ORBITAL ENERGIES": +#parser orbial energy information + dashes = inputfile.next() + text = inputfile.next() + text = inputfile.next() + + self.moenergies = [[]] + self.homos = [[0]] + + line = inputfile.next() + while len(line) > 20: #restricted calcs are terminated by ------ + info = line.split() + self.moenergies[0].append(float(info[3])) + if float(info[1]) > 0.00: #might be 1 or 2, depending on restricted-ness + self.homos[0] = int(info[0]) + line = inputfile.next() + + line = inputfile.next() + + #handle beta orbitals + if line[17:35] == "SPIN DOWN ORBITALS": + text = inputfile.next() + + self.moenergies.append([]) + self.homos.append(0) + + line = inputfile.next() + while len(line) > 20: #actually terminated by ------ + info = line.split() + self.moenergies[1].append(float(info[3])) + if float(info[1]) == 1.00: + self.homos[1] = int(info[0]) + line = inputfile.next() + + if line[1:32] == "# of contracted basis functions": + self.nbasis = int(line.split()[-1]) + + if line[0:14] == "OVERLAP MATRIX": +#parser the overlap matrix + dashes = inputfile.next() + + self.aooverlaps = numpy.zeros( (self.nbasis, self.nbasis), "d") + for i in range(0, self.nbasis, 6): + header = inputfile.next() + size = len(header.split()) + + for j in range(self.nbasis): + line = inputfile.next() + broken = line.split() + self.aooverlaps[j, i:i+size] = map(float, broken[1:size+1]) + + # Molecular orbital coefficients. + # This is also where atombasis is parsed. + if line[0:18] == "MOLECULAR ORBITALS": + + dashses = inputfile.next() + + mocoeffs = [ numpy.zeros((self.nbasis, self.nbasis), "d") ] + self.aonames = [] + self.atombasis = [] + for n in range(self.natom): + self.atombasis.append([]) + + for spin in range(len(self.moenergies)): + + if spin == 1: + blank = inputfile.next() + mocoeffs.append(numpy.zeros((self.nbasis, self.nbasis), "d")) + + for i in range(0, self.nbasis, 6): + numbers = inputfile.next() + energies = inputfile.next() + occs = inputfile.next() + dashes = inputfile.next() + broken = dashes.split() + size = len(broken) + + for j in range(self.nbasis): + line = inputfile.next() + broken = line.split() + + #only need this on the first time through + if spin == 0 and i == 0: + atomname = line[3:5].split()[0] + num = int(line[0:3]) + orbital = broken[1].upper() + + self.aonames.append("%s%i_%s"%(atomname, num+1, orbital)) + self.atombasis[num].append(j) + + temp = [] + vals = line[16:-1] #-1 to remove the last blank space + for k in range(0, len(vals), 10): + temp.append(float(vals[k:k+10])) + mocoeffs[spin][i:i+size, j] = temp + + self.mocoeffs = mocoeffs + + if line[0:18] == "TD-DFT/TDA EXCITED": + sym = "Triplet" # Could be singlets or triplets + if line.find("SINGLETS") >= 0: + sym = "Singlet" + self.etsecs = [] + self.etenergies = [] + self.etsyms = [] + lookup = {'a':0, 'b':1} + line = inputfile.next() + while line.find("STATE") < 0: + line = inputfile.next() + # Contains STATE or is blank + while line.find("STATE") >= 0: + broken = line.split() + self.etenergies.append(float(broken[-2])) + self.etsyms.append(sym) + line = inputfile.next() + sec = [] + # Contains SEC or is blank + while line.strip(): + start = line[0:8].strip() + start = (int(start[:-1]), lookup[start[-1]]) + end = line[10:17].strip() + end = (int(end[:-1]), lookup[end[-1]]) + contrib = float(line[35:47].strip()) + sec.append([start, end, contrib]) + line = inputfile.next() + self.etsecs.append(sec) + line = inputfile.next() + + if line[25:44] == "ABSORPTION SPECTRUM": + minus = inputfile.next() + header = inputfile.next() + header = inputfile.next() + minus = inputfile.next() + self.etoscs = [] + for x in self.etsyms: + osc = inputfile.next().split()[3] + if osc == "spin": # "spin forbidden" + osc = 0 + else: + osc = float(osc) + self.etoscs.append(osc) + + if line[0:23] == "VIBRATIONAL FREQUENCIES": +#parse the vibrational frequencies + dashes = inputfile.next() + blank = inputfile.next() + + self.vibfreqs = numpy.zeros((3 * self.natom,),"d") + + for i in range(3 * self.natom): + line = inputfile.next() + self.vibfreqs[i] = float(line.split()[1]) + + if line[0:11] == "IR SPECTRUM": +#parse ir intensities + dashes = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + dashes = inputfile.next() + + self.vibirs = numpy.zeros((3 * self.natom,),"d") + + line = inputfile.next() + while len(line) > 2: + num = int(line[0:4]) + self.vibirs[num] = float(line.split()[2]) + line = inputfile.next() + + if line[0:14] == "RAMAN SPECTRUM": +#parser raman intensities + dashes = inputfile.next() + blank = inputfile.next() + header = inputfile.next() + dashes = inputfile.next() + + self.vibramans = numpy.zeros((3 * self.natom,),"d") + + line = inputfile.next() + while len(line) > 2: + num = int(line[0:4]) + self.vibramans[num] = float(line.split()[2]) + line = inputfile.next() + + + +if __name__ == "__main__": + import doctest, orcaparser + doctest.testmod(orcaparser, verbose=False) diff --git a/external/cclib/parser/utils.py b/external/cclib/parser/utils.py index b4ee8e36b4..ba8f9177b2 100644 --- a/external/cclib/parser/utils.py +++ b/external/cclib/parser/utils.py @@ -1,71 +1,71 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 865 $" - - -def convertor(value, fromunits, tounits): - """Convert from one set of units to another. - - >>> print "%.1f" % convertor(8, "eV", "cm-1") - 64524.8 - """ - - _convertor = {"eV_to_cm-1": lambda x: x*8065.6, - "hartree_to_eV": lambda x: x*27.2113845, - "bohr_to_Angstrom": lambda x: x*0.529177, - "Angstrom_to_bohr": lambda x: x*1.889716, - "nm_to_cm-1": lambda x: 1e7/x, - "cm-1_to_nm": lambda x: 1e7/x, - "hartree_to_cm-1": lambda x: x*219474.6, - # Taken from GAMESS docs, "Further information", - # "Molecular Properties and Conversion Factors" - "Debye^2/amu-Angstrom^2_to_km/mol": lambda x: x*42.255} - - return _convertor["%s_to_%s" % (fromunits, tounits)] (value) - - -class PeriodicTable(object): - """Allows conversion between element name and atomic no. - - >>> t = PeriodicTable() - >>> t.element[6] - 'C' - >>> t.number['C'] - 6 - >>> t.element[44] - 'Ru' - >>> t.number['Au'] - 79 - """ - - def __init__(self): - self.element = [None, - 'H', 'He', - 'Li', 'Be', - 'B', 'C', 'N', 'O', 'F', 'Ne', - 'Na', 'Mg', - 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', - 'K', 'Ca', - 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', - 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', - 'Rb', 'Sr', - 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', - 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', - 'Cs', 'Ba', - 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', - 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', - 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', - 'Fr', 'Ra', - 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', - 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Uub'] - self.number = {} - for i in range(1, len(self.element)): - self.number[self.element[i]] = i - - -if __name__ == "__main__": - import doctest, utils - doctest.testmod(utils, verbose=False) +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 865 $" + + +def convertor(value, fromunits, tounits): + """Convert from one set of units to another. + + >>> print "%.1f" % convertor(8, "eV", "cm-1") + 64524.8 + """ + + _convertor = {"eV_to_cm-1": lambda x: x*8065.6, + "hartree_to_eV": lambda x: x*27.2113845, + "bohr_to_Angstrom": lambda x: x*0.529177, + "Angstrom_to_bohr": lambda x: x*1.889716, + "nm_to_cm-1": lambda x: 1e7/x, + "cm-1_to_nm": lambda x: 1e7/x, + "hartree_to_cm-1": lambda x: x*219474.6, + # Taken from GAMESS docs, "Further information", + # "Molecular Properties and Conversion Factors" + "Debye^2/amu-Angstrom^2_to_km/mol": lambda x: x*42.255} + + return _convertor["%s_to_%s" % (fromunits, tounits)] (value) + + +class PeriodicTable(object): + """Allows conversion between element name and atomic no. + + >>> t = PeriodicTable() + >>> t.element[6] + 'C' + >>> t.number['C'] + 6 + >>> t.element[44] + 'Ru' + >>> t.number['Au'] + 79 + """ + + def __init__(self): + self.element = [None, + 'H', 'He', + 'Li', 'Be', + 'B', 'C', 'N', 'O', 'F', 'Ne', + 'Na', 'Mg', + 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', + 'K', 'Ca', + 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', + 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', + 'Rb', 'Sr', + 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', + 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', + 'Cs', 'Ba', + 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', + 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', + 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', + 'Fr', 'Ra', + 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', + 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Uub'] + self.number = {} + for i in range(1, len(self.element)): + self.number[self.element[i]] = i + + +if __name__ == "__main__": + import doctest, utils + doctest.testmod(utils, verbose=False) diff --git a/external/cclib/progress/__init__.py b/external/cclib/progress/__init__.py index 12d34e7bf2..f31f42edb0 100644 --- a/external/cclib/progress/__init__.py +++ b/external/cclib/progress/__init__.py @@ -1,16 +1,16 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 620 $" - - -import sys - -if 'qt' in sys.modules.keys(): - from qtprogress import QtProgress -if 'PyQt4' in sys.modules.keys(): - from qt4progress import Qt4Progress - -from textprogress import TextProgress +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 620 $" + + +import sys + +if 'qt' in sys.modules.keys(): + from qtprogress import QtProgress +if 'PyQt4' in sys.modules.keys(): + from qt4progress import Qt4Progress + +from textprogress import TextProgress diff --git a/external/cclib/progress/qt4progress.py b/external/cclib/progress/qt4progress.py index 43f8af5371..10927b9618 100644 --- a/external/cclib/progress/qt4progress.py +++ b/external/cclib/progress/qt4progress.py @@ -1,42 +1,42 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 238 $" - - -from PyQt4 import QtGui,QtCore - - -class Qt4Progress(QtGui.QProgressDialog): - - def __init__(self, title, parent=None): - - QtGui.QProgressDialog.__init__(self, parent) - - self.nstep = 0 - self.text = None - self.oldprogress = 0 - self.progress = 0 - self.calls = 0 - self.loop=QtCore.QEventLoop(self) - self.setWindowTitle(title) - - def initialize(self, nstep, text=None): - - self.nstep = nstep - self.text = text - self.setRange(0,nstep) - if text: - self.setLabelText(text) - self.setValue(1) - #sys.stdout.write("\n") - - def update(self, step, text=None): - - if text: - self.setLabelText(text) - self.setValue(step) - self.loop.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) - +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 238 $" + + +from PyQt4 import QtGui,QtCore + + +class Qt4Progress(QtGui.QProgressDialog): + + def __init__(self, title, parent=None): + + QtGui.QProgressDialog.__init__(self, parent) + + self.nstep = 0 + self.text = None + self.oldprogress = 0 + self.progress = 0 + self.calls = 0 + self.loop=QtCore.QEventLoop(self) + self.setWindowTitle(title) + + def initialize(self, nstep, text=None): + + self.nstep = nstep + self.text = text + self.setRange(0,nstep) + if text: + self.setLabelText(text) + self.setValue(1) + #sys.stdout.write("\n") + + def update(self, step, text=None): + + if text: + self.setLabelText(text) + self.setValue(step) + self.loop.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) + diff --git a/external/cclib/progress/qtprogress.py b/external/cclib/progress/qtprogress.py index 78c363f29a..941ecc51f2 100644 --- a/external/cclib/progress/qtprogress.py +++ b/external/cclib/progress/qtprogress.py @@ -1,41 +1,41 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 620 $" - - -from qt import QProgressDialog - - -class QtProgress(QProgressDialog): - - def __init__(self, parent): - - QProgressDialog.__init__(self, parent, "progress", True) - - self.nstep = 0 - self.text = None - self.oldprogress = 0 - self.progress = 0 - self.calls = 0 - - self.setCaption("Progress...") - - def initialize(self, nstep, text=None): - - self.nstep = nstep - self.text = text - self.setTotalSteps(nstep) - if text: - self.setLabelText(text) - self.setProgress(1) - #sys.stdout.write("\n") - - def update(self, step, text=None): - - self.setLabelText(text) - self.setProgress(step) - - return +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 620 $" + + +from qt import QProgressDialog + + +class QtProgress(QProgressDialog): + + def __init__(self, parent): + + QProgressDialog.__init__(self, parent, "progress", True) + + self.nstep = 0 + self.text = None + self.oldprogress = 0 + self.progress = 0 + self.calls = 0 + + self.setCaption("Progress...") + + def initialize(self, nstep, text=None): + + self.nstep = nstep + self.text = text + self.setTotalSteps(nstep) + if text: + self.setLabelText(text) + self.setProgress(1) + #sys.stdout.write("\n") + + def update(self, step, text=None): + + self.setLabelText(text) + self.setProgress(step) + + return diff --git a/external/cclib/progress/textprogress.py b/external/cclib/progress/textprogress.py index c2302b9170..79a6f3d1ef 100644 --- a/external/cclib/progress/textprogress.py +++ b/external/cclib/progress/textprogress.py @@ -1,54 +1,54 @@ -""" -cclib (http://cclib.sf.net) is (c) 2006, the cclib development team -and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). -""" - -__revision__ = "$Revision: 620 $" - - -import sys - - -class TextProgress: - - def __init__(self): - - self.nstep = 0 - self.text = None - self.oldprogress = 0 - self.progress = 0 - self.calls = 0 - - def initialize(self, nstep, text=None): - - self.nstep = float(nstep) - self.text = text - - #sys.stdout.write("\n") - - def update(self, step, text=None): - - self.progress = int(step * 100 / self.nstep) - - if self.progress/2 >= self.oldprogress/2+1 or self.text != text: -# just went through at least an interval of ten, ie. from 39 to 41, so update - - mystr = "\r[" - prog = self.progress / 10 - mystr += prog*"="+(10-prog)*"-" - mystr += "] %3i" % self.progress + "%" - - if text: - mystr += " "+text - - sys.stdout.write("\r"+70*" ") - sys.stdout.flush() - sys.stdout.write(mystr) - sys.stdout.flush() - self.oldprogress = self.progress - - if self.progress >= 100 and text == "Done": - print " " - - - return +""" +cclib (http://cclib.sf.net) is (c) 2006, the cclib development team +and licensed under the LGPL (http://www.gnu.org/copyleft/lgpl.html). +""" + +__revision__ = "$Revision: 620 $" + + +import sys + + +class TextProgress: + + def __init__(self): + + self.nstep = 0 + self.text = None + self.oldprogress = 0 + self.progress = 0 + self.calls = 0 + + def initialize(self, nstep, text=None): + + self.nstep = float(nstep) + self.text = text + + #sys.stdout.write("\n") + + def update(self, step, text=None): + + self.progress = int(step * 100 / self.nstep) + + if self.progress/2 >= self.oldprogress/2+1 or self.text != text: +# just went through at least an interval of ten, ie. from 39 to 41, so update + + mystr = "\r[" + prog = self.progress / 10 + mystr += prog*"="+(10-prog)*"-" + mystr += "] %3i" % self.progress + "%" + + if text: + mystr += " "+text + + sys.stdout.write("\r"+70*" ") + sys.stdout.flush() + sys.stdout.write(mystr) + sys.stdout.flush() + self.oldprogress = self.progress + + if self.progress >= 100 and text == "Done": + print " " + + + return diff --git a/external/cinfony/webel.py b/external/cinfony/webel.py index 6a35bb5e8b..565d63106f 100644 --- a/external/cinfony/webel.py +++ b/external/cinfony/webel.py @@ -1,373 +1,373 @@ -""" -webel - A Cinfony module that runs entirely on web services - -webel can be used from all of CPython, Jython and IronPython. - -Global variables: - informats - a dictionary of supported input formats - outformats - a dictionary of supported output formats - fps - a list of supported fingerprint types -""" - -import re -import os -import urllib2 -import StringIO - -try: - import Tkinter as tk - import Image as PIL - import ImageTk as piltk -except ImportError: - tk = None - -informats = {"smi":"SMILES", "inchikey":"InChIKey", "inchi":"InChI", - "name":"Common name"} -"""A dictionary of supported input formats""" -outformats = {"smi":"SMILES", "cdxml":"ChemDraw XML", "inchi":"InChI", - "sdf":"Symyx SDF", "names":"Common names", "inchikey":"InChIKey", - "alc":"Alchemy", "cerius":"MSI Cerius II", "charmm":"CHARMM", - "cif":"Crystallographic Information File", - "cml":"Chemical Markup Language", "ctx":"Gasteiger Clear Text", - "gjf":"Gaussian job file", "gromacs":"GROMACS", - "hyperchem":"HyperChem", "jme":"Java Molecule Editor", - "maestro":"Schrodinger MacroModel", - "mol":"Symyx mol", "mol2":"Tripos Sybyl MOL2", - "mrv":"ChemAxon MRV", "pdb":"Protein Data Bank", - "sdf3000":"Symyx SDF3000", "sln":"Sybl line notation", - "xyz":"XYZ", "iupac":"IUPAC name"} -"""A dictionary of supported output formats""" - -fps = ["std", "maccs", "estate"] -"""A list of supported fingerprint types""" - -# The following function is taken from urllib.py in the IronPython dist -def _quo(text, safe="/"): - always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' - 'abcdefghijklmnopqrstuvwxyz' - '0123456789' '_.-') - _safemaps = {} - cachekey = (safe, always_safe) - try: - safe_map = _safemaps[cachekey] - except KeyError: - safe += always_safe - safe_map = {} - for i in range(256): - c = chr(i) - safe_map[c] = (c in safe) and c or ('%%%02X' % i) - _safemaps[cachekey] = safe_map - res = map(safe_map.__getitem__, text) - return ''.join(res) - -def _makeserver(serverurl): - """Curry the name of the server""" - def server(*urlcomponents): - url = "%s/" % serverurl + "/".join(urlcomponents) - resp = urllib2.urlopen(url) - return resp.read() - return server - -rajweb = _makeserver("http://ws1.bmc.uu.se:8182/cdk") -nci = _makeserver("http://cactus.nci.nih.gov/chemical/structure") - -_descs = None # Cache the list of descriptors -def getdescs(): - """Return a list of supported descriptor types""" - global _descs - if not _descs: - response = rajweb("descriptors").rstrip() - _descs = [x.split(".")[-1] for x in response.split("\n")] - return _descs - -def readstring(format, string): - """Read in a molecule from a string. - - Required parameters: - format - see the informats variable for a list of available - input formats - string - - Note: For InChIKeys a list of molecules is returned. - - Example: - >>> input = "C1=CC=CS1" - >>> mymol = readstring("smi", input) - """ - format = format.lower() - if not format in informats: - raise ValueError("%s is not a recognised Webel format" % format) - - if format != "smi": - smiles = nci(_quo(string), "smiles").rstrip() - else: - smiles = string - if format == "inchikey": - return [Molecule(smile) for smile in smiles.split("\n")] - else: - mol = Molecule(smiles) - if format == "name": - mol.title = string - return mol - -class Outputfile(object): - """Represent a file to which *output* is to be sent. - - Although it's possible to write a single molecule to a file by - calling the write() method of a molecule, if multiple molecules - are to be written to the same file you should use the Outputfile - class. - - Required parameters: - format - see the outformats variable for a list of available - output formats - filename - - Optional parameters: - overwrite -- if the output file already exists, should it - be overwritten? (default is False) - - Methods: - write(molecule) - close() - """ - def __init__(self, format, filename, overwrite=False): - self.format = format.lower() - self.filename = filename - if not overwrite and os.path.isfile(self.filename): - raise IOError("%s already exists. Use 'overwrite=True' to overwrite it." % self.filename) - if not format in outformats: - raise ValueError("%s is not a recognised Webel format" % format) - self.file = open(filename, "w") - - def write(self, molecule): - """Write a molecule to the output file. - - Required parameters: - molecule - """ - if self.file.closed: - raise IOError("Outputfile instance is closed.") - output = molecule.write(self.format) - print >> self.file, output - - def close(self): - """Close the Outputfile to further writing.""" - self.file.close() - -class Molecule(object): - """Represent a Webel Molecule. - - Required parameter: - smiles -- a SMILES string or any type of cinfony Molecule - - Attributes: - formula, molwt, title - - Methods: - calcfp(), calcdesc(), draw(), write() - - The underlying SMILES string can be accessed using the attribute: - smiles - """ - _cinfony = True - - def __init__(self, smiles): - - if hasattr(smiles, "_cinfony"): - a, b = smiles._exchange - if a == 0: - smiles = b - else: - # Must convert to SMILES - smiles = smiles.write("smi").split()[0] - - self.smiles = smiles - self.title = "" - - @property - def formula(self): return rajweb("mf", _quo(self.smiles)) - @property - def molwt(self): return float(rajweb("mw", _quo(self.smiles))) - @property - def _exchange(self): - return (0, self.smiles) - - def calcdesc(self, descnames=[]): - """Calculate descriptor values. - - Optional parameter: - descnames -- a list of names of descriptors - - If descnames is not specified, all available descriptors are - calculated. See the descs variable for a list of available - descriptors. - """ - if not descnames: - descnames = getdescs() - else: - for descname in descnames: - if descname not in getdescs(): - raise ValueError("%s is not a recognised Webel descriptor type" % descname) - ans = {} - p = re.compile("""Descriptor parent="(\w*)" name="([\w\-\+\d]*)" value="([\d\.]*)""") - for descname in descnames: - longname = "org.openscience.cdk.qsar.descriptors.molecular." + descname - response = rajweb("descriptor", longname, _quo(self.smiles)) - for match in p.findall(response): - if match[2]: - ans["%s_%s" % (match[0], match[1])] = float(match[2]) - return ans - - def calcfp(self, fptype="std"): - """Calculate a molecular fingerprint. - - Optional parameters: - fptype -- the fingerprint type (default is "std"). See the - fps variable for a list of of available fingerprint - types. - """ - fptype = fptype.lower() - if fptype not in fps: - raise ValueError("%s is not a recognised Webel Fingerprint type" % fptype) - fp = rajweb("fingerprint/%s/%s" % (fptype, _quo(self.smiles))).rstrip() - return Fingerprint(fp) - - def write(self, format="smi", filename=None, overwrite=False): - """Write the molecule to a file or return a string. - - Optional parameters: - format -- see the informats variable for a list of available - output formats (default is "smi") - filename -- default is None - overwite -- if the output file already exists, should it - be overwritten? (default is False) - - If a filename is specified, the result is written to a file. - Otherwise, a string is returned containing the result. - - To write multiple molecules to the same file you should use - the Outputfile class. - """ - format = format.lower() - if not format in outformats: - raise ValueError("%s is not a recognised Webel format" % format) - if format == "smi": - output = self.smiles - elif format == "names": - try: - output = nci(_quo(self.smiles), "%s" % format).rstrip().split("\n") - except urllib2.URLError, e: - if e.code == 404: - output = [] - elif format in ['inchi', 'inchikey']: - format = "std" + format - output = nci(_quo(self.smiles), "%s" % format).rstrip() - elif format == 'iupac': - format = format + "_name" - try: - output = nci(_quo(self.smiles), "%s" % format).rstrip() - except urllib2.URLError, e: - if e.code == 404: - output = "" - else: - output = nci(_quo(self.smiles), "file?format=%s" % format).rstrip() - - if filename: - if not overwrite and os.path.isfile(filename): - raise IOError("%s already exists. Use 'overwrite=True' to overwrite it." % filename) - outputfile = open(filename, "w") - print >> outputfile, output - outputfile.close() - else: - return output - - def __str__(self): - return self.write() - - def draw(self, show=True, filename=None): - """Create a 2D depiction of the molecule. - - Optional parameters: - show -- display on screen (default is True) - filename -- write to file (default is None) - - Tkinter and Python Imaging Library are required for - image display. - """ - imagedata = nci(_quo(self.smiles), "image") - if filename: - print >> open(filename, "wb"), imagedata - if show: - if not tk: - errormessage = ("Tkinter or Python Imaging " - "Library not found, but is required for image " - "display. See installation instructions for " - "more information.") - raise ImportError, errormessage - root = tk.Tk() - root.title(self.smiles) - frame = tk.Frame(root, colormap="new", visual='truecolor').pack() - image = PIL.open(StringIO.StringIO(imagedata)) - imagedata = piltk.PhotoImage(image) - label = tk.Label(frame, image=imagedata).pack() - quitbutton = tk.Button(root, text="Close", command=root.destroy).pack(fill=tk.X) - root.mainloop() - -class Fingerprint(object): - """A Molecular Fingerprint. - - Required parameters: - fingerprint -- a string of 0's and 1's representing a binary fingerprint - - Attributes: - fp -- the underlying fingerprint object - bits -- a list of bits set in the Fingerprint - - Methods: - The "|" operator can be used to calculate the Tanimoto coeff. For example, - given two Fingerprints 'a', and 'b', the Tanimoto coefficient is given by: - tanimoto = a | b - """ - def __init__(self, fingerprint): - self.fp = fingerprint - def __or__(self, other): - mybits = set(self.bits) - otherbits = set(other.bits) - return len(mybits&otherbits) / float(len(mybits|otherbits)) - @property - def bits(self): - return [i for i,x in enumerate(self.fp) if x=="1"] - def __str__(self): - return self.fp - -class Smarts(object): - """A Smarts Pattern Matcher - - Required parameters: - smartspattern - - Methods: - match(molecule) - - Example: - >>> mol = readstring("smi","CCN(CC)CC") # triethylamine - >>> smarts = Smarts("[#6][#6]") # Matches an ethyl group - >>> smarts.match(mol) - True - """ - def __init__(self, smartspattern): - """Initialise with a SMARTS pattern.""" - self.pat = smartspattern - def match(self, molecule): - """Does a SMARTS pattern match a particular molecule? - - Required parameters: - molecule - """ - resp = rajweb("substruct", _quo(molecule.smiles), _quo(self.pat)).rstrip() - return resp == "true" - -if __name__=="__main__": #pragma: no cover - import doctest - doctest.run_docstring_examples(rajweb, globals()) +""" +webel - A Cinfony module that runs entirely on web services + +webel can be used from all of CPython, Jython and IronPython. + +Global variables: + informats - a dictionary of supported input formats + outformats - a dictionary of supported output formats + fps - a list of supported fingerprint types +""" + +import re +import os +import urllib2 +import StringIO + +try: + import Tkinter as tk + import Image as PIL + import ImageTk as piltk +except ImportError: + tk = None + +informats = {"smi":"SMILES", "inchikey":"InChIKey", "inchi":"InChI", + "name":"Common name"} +"""A dictionary of supported input formats""" +outformats = {"smi":"SMILES", "cdxml":"ChemDraw XML", "inchi":"InChI", + "sdf":"Symyx SDF", "names":"Common names", "inchikey":"InChIKey", + "alc":"Alchemy", "cerius":"MSI Cerius II", "charmm":"CHARMM", + "cif":"Crystallographic Information File", + "cml":"Chemical Markup Language", "ctx":"Gasteiger Clear Text", + "gjf":"Gaussian job file", "gromacs":"GROMACS", + "hyperchem":"HyperChem", "jme":"Java Molecule Editor", + "maestro":"Schrodinger MacroModel", + "mol":"Symyx mol", "mol2":"Tripos Sybyl MOL2", + "mrv":"ChemAxon MRV", "pdb":"Protein Data Bank", + "sdf3000":"Symyx SDF3000", "sln":"Sybl line notation", + "xyz":"XYZ", "iupac":"IUPAC name"} +"""A dictionary of supported output formats""" + +fps = ["std", "maccs", "estate"] +"""A list of supported fingerprint types""" + +# The following function is taken from urllib.py in the IronPython dist +def _quo(text, safe="/"): + always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789' '_.-') + _safemaps = {} + cachekey = (safe, always_safe) + try: + safe_map = _safemaps[cachekey] + except KeyError: + safe += always_safe + safe_map = {} + for i in range(256): + c = chr(i) + safe_map[c] = (c in safe) and c or ('%%%02X' % i) + _safemaps[cachekey] = safe_map + res = map(safe_map.__getitem__, text) + return ''.join(res) + +def _makeserver(serverurl): + """Curry the name of the server""" + def server(*urlcomponents): + url = "%s/" % serverurl + "/".join(urlcomponents) + resp = urllib2.urlopen(url) + return resp.read() + return server + +rajweb = _makeserver("http://ws1.bmc.uu.se:8182/cdk") +nci = _makeserver("http://cactus.nci.nih.gov/chemical/structure") + +_descs = None # Cache the list of descriptors +def getdescs(): + """Return a list of supported descriptor types""" + global _descs + if not _descs: + response = rajweb("descriptors").rstrip() + _descs = [x.split(".")[-1] for x in response.split("\n")] + return _descs + +def readstring(format, string): + """Read in a molecule from a string. + + Required parameters: + format - see the informats variable for a list of available + input formats + string + + Note: For InChIKeys a list of molecules is returned. + + Example: + >>> input = "C1=CC=CS1" + >>> mymol = readstring("smi", input) + """ + format = format.lower() + if not format in informats: + raise ValueError("%s is not a recognised Webel format" % format) + + if format != "smi": + smiles = nci(_quo(string), "smiles").rstrip() + else: + smiles = string + if format == "inchikey": + return [Molecule(smile) for smile in smiles.split("\n")] + else: + mol = Molecule(smiles) + if format == "name": + mol.title = string + return mol + +class Outputfile(object): + """Represent a file to which *output* is to be sent. + + Although it's possible to write a single molecule to a file by + calling the write() method of a molecule, if multiple molecules + are to be written to the same file you should use the Outputfile + class. + + Required parameters: + format - see the outformats variable for a list of available + output formats + filename + + Optional parameters: + overwrite -- if the output file already exists, should it + be overwritten? (default is False) + + Methods: + write(molecule) + close() + """ + def __init__(self, format, filename, overwrite=False): + self.format = format.lower() + self.filename = filename + if not overwrite and os.path.isfile(self.filename): + raise IOError("%s already exists. Use 'overwrite=True' to overwrite it." % self.filename) + if not format in outformats: + raise ValueError("%s is not a recognised Webel format" % format) + self.file = open(filename, "w") + + def write(self, molecule): + """Write a molecule to the output file. + + Required parameters: + molecule + """ + if self.file.closed: + raise IOError("Outputfile instance is closed.") + output = molecule.write(self.format) + print >> self.file, output + + def close(self): + """Close the Outputfile to further writing.""" + self.file.close() + +class Molecule(object): + """Represent a Webel Molecule. + + Required parameter: + smiles -- a SMILES string or any type of cinfony Molecule + + Attributes: + formula, molwt, title + + Methods: + calcfp(), calcdesc(), draw(), write() + + The underlying SMILES string can be accessed using the attribute: + smiles + """ + _cinfony = True + + def __init__(self, smiles): + + if hasattr(smiles, "_cinfony"): + a, b = smiles._exchange + if a == 0: + smiles = b + else: + # Must convert to SMILES + smiles = smiles.write("smi").split()[0] + + self.smiles = smiles + self.title = "" + + @property + def formula(self): return rajweb("mf", _quo(self.smiles)) + @property + def molwt(self): return float(rajweb("mw", _quo(self.smiles))) + @property + def _exchange(self): + return (0, self.smiles) + + def calcdesc(self, descnames=[]): + """Calculate descriptor values. + + Optional parameter: + descnames -- a list of names of descriptors + + If descnames is not specified, all available descriptors are + calculated. See the descs variable for a list of available + descriptors. + """ + if not descnames: + descnames = getdescs() + else: + for descname in descnames: + if descname not in getdescs(): + raise ValueError("%s is not a recognised Webel descriptor type" % descname) + ans = {} + p = re.compile("""Descriptor parent="(\w*)" name="([\w\-\+\d]*)" value="([\d\.]*)""") + for descname in descnames: + longname = "org.openscience.cdk.qsar.descriptors.molecular." + descname + response = rajweb("descriptor", longname, _quo(self.smiles)) + for match in p.findall(response): + if match[2]: + ans["%s_%s" % (match[0], match[1])] = float(match[2]) + return ans + + def calcfp(self, fptype="std"): + """Calculate a molecular fingerprint. + + Optional parameters: + fptype -- the fingerprint type (default is "std"). See the + fps variable for a list of of available fingerprint + types. + """ + fptype = fptype.lower() + if fptype not in fps: + raise ValueError("%s is not a recognised Webel Fingerprint type" % fptype) + fp = rajweb("fingerprint/%s/%s" % (fptype, _quo(self.smiles))).rstrip() + return Fingerprint(fp) + + def write(self, format="smi", filename=None, overwrite=False): + """Write the molecule to a file or return a string. + + Optional parameters: + format -- see the informats variable for a list of available + output formats (default is "smi") + filename -- default is None + overwite -- if the output file already exists, should it + be overwritten? (default is False) + + If a filename is specified, the result is written to a file. + Otherwise, a string is returned containing the result. + + To write multiple molecules to the same file you should use + the Outputfile class. + """ + format = format.lower() + if not format in outformats: + raise ValueError("%s is not a recognised Webel format" % format) + if format == "smi": + output = self.smiles + elif format == "names": + try: + output = nci(_quo(self.smiles), "%s" % format).rstrip().split("\n") + except urllib2.URLError, e: + if e.code == 404: + output = [] + elif format in ['inchi', 'inchikey']: + format = "std" + format + output = nci(_quo(self.smiles), "%s" % format).rstrip() + elif format == 'iupac': + format = format + "_name" + try: + output = nci(_quo(self.smiles), "%s" % format).rstrip() + except urllib2.URLError, e: + if e.code == 404: + output = "" + else: + output = nci(_quo(self.smiles), "file?format=%s" % format).rstrip() + + if filename: + if not overwrite and os.path.isfile(filename): + raise IOError("%s already exists. Use 'overwrite=True' to overwrite it." % filename) + outputfile = open(filename, "w") + print >> outputfile, output + outputfile.close() + else: + return output + + def __str__(self): + return self.write() + + def draw(self, show=True, filename=None): + """Create a 2D depiction of the molecule. + + Optional parameters: + show -- display on screen (default is True) + filename -- write to file (default is None) + + Tkinter and Python Imaging Library are required for + image display. + """ + imagedata = nci(_quo(self.smiles), "image") + if filename: + print >> open(filename, "wb"), imagedata + if show: + if not tk: + errormessage = ("Tkinter or Python Imaging " + "Library not found, but is required for image " + "display. See installation instructions for " + "more information.") + raise ImportError, errormessage + root = tk.Tk() + root.title(self.smiles) + frame = tk.Frame(root, colormap="new", visual='truecolor').pack() + image = PIL.open(StringIO.StringIO(imagedata)) + imagedata = piltk.PhotoImage(image) + label = tk.Label(frame, image=imagedata).pack() + quitbutton = tk.Button(root, text="Close", command=root.destroy).pack(fill=tk.X) + root.mainloop() + +class Fingerprint(object): + """A Molecular Fingerprint. + + Required parameters: + fingerprint -- a string of 0's and 1's representing a binary fingerprint + + Attributes: + fp -- the underlying fingerprint object + bits -- a list of bits set in the Fingerprint + + Methods: + The "|" operator can be used to calculate the Tanimoto coeff. For example, + given two Fingerprints 'a', and 'b', the Tanimoto coefficient is given by: + tanimoto = a | b + """ + def __init__(self, fingerprint): + self.fp = fingerprint + def __or__(self, other): + mybits = set(self.bits) + otherbits = set(other.bits) + return len(mybits&otherbits) / float(len(mybits|otherbits)) + @property + def bits(self): + return [i for i,x in enumerate(self.fp) if x=="1"] + def __str__(self): + return self.fp + +class Smarts(object): + """A Smarts Pattern Matcher + + Required parameters: + smartspattern + + Methods: + match(molecule) + + Example: + >>> mol = readstring("smi","CCN(CC)CC") # triethylamine + >>> smarts = Smarts("[#6][#6]") # Matches an ethyl group + >>> smarts.match(mol) + True + """ + def __init__(self, smartspattern): + """Initialise with a SMARTS pattern.""" + self.pat = smartspattern + def match(self, molecule): + """Does a SMARTS pattern match a particular molecule? + + Required parameters: + molecule + """ + resp = rajweb("substruct", _quo(molecule.smiles), _quo(self.pat)).rstrip() + return resp == "true" + +if __name__=="__main__": #pragma: no cover + import doctest + doctest.run_docstring_examples(rajweb, globals()) diff --git a/rmgpy/cantherm/statmech.py b/rmgpy/cantherm/statmech.py index 7cc125f20b..2532bf718a 100644 --- a/rmgpy/cantherm/statmech.py +++ b/rmgpy/cantherm/statmech.py @@ -42,7 +42,6 @@ import rmgpy.constants as constants from rmgpy.cantherm.output import prettify from rmgpy.cantherm.gaussian import GaussianLog -from rmgpy.cantherm.molepro import MoleProLog from rmgpy.species import TransitionState from rmgpy.statmech import * @@ -196,7 +195,6 @@ def load(self): 'HinderedRotor': hinderedRotor, # File formats 'GaussianLog': GaussianLog, - 'MoleProLog': MoleProLog, 'ScanLog': ScanLog, } @@ -249,10 +247,7 @@ def load(self): except KeyError: raise InputError('Model chemistry {0!r} not found in from dictionary of energy values in species file {1!r}.'.format(self.modelChemistry, path)) if isinstance(energy, GaussianLog): - energyLog = energy; E0 = 'Gaussian' - energyLog.path = os.path.join(directory, energyLog.path) - if isinstance(energy, MoleProLog): - energyLog = energy; E0 = 'MolePro' + energyLog = energy; E0 = None energyLog.path = os.path.join(directory, energyLog.path) elif isinstance(energy, float): energyLog = None; E0 = energy @@ -292,10 +287,8 @@ def load(self): logging.debug(' Reading energy...') # The E0 that is read from the log file is without the ZPE and corresponds to E_elec - if E0 is 'Gaussian': + if E0 is None: E0 = energyLog.loadEnergy(self.frequencyScaleFactor) - elif E0 is 'MolePro': - E0 = energyLog.loadCCSDEnergy() else: E0 = E0 * constants.E_h * constants.Na # Hartree/particle to J/mol E0 = applyEnergyCorrections(E0, self.modelChemistry, atoms, bonds if self.applyBondEnergyCorrections else {}) @@ -397,7 +390,7 @@ def save(self, outputFile): logging.info('Saving statistical mechanics parameters for {0}...'.format(self.species.label)) f = open(outputFile, 'a') - numbers = {1: 'H', 6: 'C', 7: 'N', 8: 'O', 14: 'Si', 15: 'P', 16: 'S', 17: 'Cl'} + numbers = {1: 'H', 6: 'C', 7: 'N', 8: 'O', 14: 'Si', 15: 'P', 16: 'S'} conformer = self.species.conformer @@ -480,108 +473,117 @@ def applyEnergyCorrections(E0, modelChemistry, atoms, bonds): # Spin orbit correction (SOC) in Hartrees # Values taken from note 22 of http://jcp.aip.org/resource/1/jcpsa6/v109/i24/p10570_s1 and converted to hartrees # Values in millihartree are also available (with fewer significant figures) from http://jcp.aip.org/resource/1/jcpsa6/v106/i3/p1063_s1 - SOC = {'H':0.0, 'N':0.0, 'O': -0.000355, 'C': -0.000135, 'S': -0.000893, 'P': 0.0, 'Cl': -0.001338} + SOC = {'H':0.0, 'N':0.0, 'O': -0.000355, 'C': -0.000135, 'S': -0.000893, 'P': 0.0} # Step 1: Reference all energies to a model chemistry-independent basis # by subtracting out that model chemistry's atomic energies # Note: If your model chemistry does not include spin orbit coupling, you should add the corrections to the energies here if modelChemistry == 'CBS-QB3': - # 0K Energy - atomEnergies = {'H':-0.499818 , 'N':-54.520543, 'O':-74.987624, 'C':-37.785385, 'P':-340.817186, 'S': -397.657360, 'Cl': -459.683605} + atomEnergies = {'H':-0.499818 , 'N':-54.520543, 'O':-74.987624, 'C':-37.785385, 'P':-340.817186, 'S': -397.657360} elif modelChemistry == 'G3': atomEnergies = {'H':-0.5010030, 'N':-54.564343, 'O':-75.030991, 'C':-37.827717, 'P':-341.116432, 'S': -397.961110} elif modelChemistry == 'Klip_1': - atomEnergies = {'H':-0.50003976 + SOC['H'], 'N':-54.53383153 + SOC['N'], 'O':-75.00935474 + SOC['O'], 'C':-37.79266591 + SOC['C']} + atomEnergies = {'H':-0.50003976, 'N':-54.53383153, 'O':-75.00935474, 'C':-37.79266591} elif modelChemistry == 'Klip_2': #Klip QCI(tz,qz) - atomEnergies = {'H':-0.50003976 + SOC['H'], 'N':-54.53169400 + SOC['N'], 'O':-75.00714902 + SOC['O'], 'C':-37.79060419 + SOC['C']} + atomEnergies = {'H':-0.50003976, 'N':-54.53169400, 'O':-75.00714902, 'C':-37.79060419} elif modelChemistry == 'Klip_3': #Klip QCI(dz,tz) - atomEnergies = {'H':-0.50005578 + SOC['H'], 'N':-54.53128140 + SOC['N'], 'O':-75.00356581 + SOC['O'], 'C':-37.79025175 + SOC['C']} + atomEnergies = {'H':-0.50005578, 'N':-54.53128140, 'O':-75.00356581, 'C':-37.79025175} elif modelChemistry == 'Klip_2_cc': #Klip CCSD(T)(tz,qz) - atomEnergies = {'H':-0.50003976 + SOC['H'], 'O':-75.00681155 + SOC['O'], 'C':-37.79029443 + SOC['C']} + atomEnergies = {'H':-0.50003976, 'O':-75.00681155, 'C':-37.79029443} elif modelChemistry == 'CCSD(T)-F12/cc-pVDZ-F12_H-TZ': - atomEnergies = {'H':-0.499946213243 + SOC['H'], 'N':-54.526406291655 + SOC['N'], 'O':-74.995458316117 + SOC['O'], 'C':-37.788203485235 + SOC['C']} + atomEnergies = {'H':-0.499946213243, 'N':-54.526406291655, 'O':-74.995458316117, 'C':-37.788203485235} elif modelChemistry == 'CCSD(T)-F12/cc-pVDZ-F12_H-QZ': - atomEnergies = {'H':-0.499994558325 + SOC['H'], 'N':-54.526406291655 + SOC['N'], 'O':-74.995458316117 + SOC['O'], 'C':-37.788203485235 + SOC['C']} + atomEnergies = {'H':-0.499994558325, 'N':-54.526406291655, 'O':-74.995458316117, 'C':-37.788203485235} elif modelChemistry == 'CCSD(T)-F12/cc-pVDZ-F12': - atomEnergies = {'H':-0.499811124128 + SOC['H'], 'N':-54.526406291655 + SOC['N'], 'O':-74.995458316117 + SOC['O'], 'C':-37.788203485235 + SOC['C']} +# atomEnergies = {'H':-0.499811124128, 'N':-54.526406291655, 'O':-74.995458316117, 'C':-37.788203485235} + atomEnergies = {'H':-0.499811124128, 'N':-54.526406291655, 'O':-74.995458316117, 'C':-37.788203485235} elif modelChemistry == 'CCSD(T)-F12/cc-pVTZ-F12': - atomEnergies = {'H':-0.499946213243 + SOC['H'], 'N':-54.53000909621 + SOC['N'], 'O':-75.004127673424 + SOC['O'], 'C':-37.789862146471 + SOC['C']} + atomEnergies = {'H':-0.499946213243, 'N':-54.53000909621, 'O':-75.004127673424, 'C':-37.789862146471} elif modelChemistry == 'CCSD(T)-F12/cc-pVQZ-F12': - atomEnergies = {'H':-0.499994558325 + SOC['H'], 'N':-54.530515226371 + SOC['N'], 'O':-75.005600062003 + SOC['O'], 'C':-37.789961656228 + SOC['C']} + atomEnergies = {'H':-0.499994558325, 'N':-54.530515226371, 'O':-75.005600062003, 'C':-37.789961656228} elif modelChemistry == 'CCSD(T)-F12/cc-pCVDZ-F12': - atomEnergies = {'H':-0.499811124128 + SOC['H'], 'N':-54.582137180344 + SOC['N'], 'O':-75.053045547421 + SOC['O'], 'C':-37.840869118707 + SOC['C']} + atomEnergies = {'H':-0.499811124128, 'N':-54.582137180344, 'O':-75.053045547421, 'C':-37.840869118707} elif modelChemistry == 'CCSD(T)-F12/cc-pCVTZ-F12': - atomEnergies = {'H':-0.499946213243 + SOC['H'], 'N':-54.588545831900 + SOC['N'], 'O':-75.065995072347 + SOC['O'], 'C':-37.844662139972 + SOC['C']} + atomEnergies = {'H':-0.499946213243, 'N':-54.588545831900, 'O':-75.065995072347, 'C':-37.844662139972} elif modelChemistry == 'CCSD(T)-F12/cc-pCVQZ-F12': - atomEnergies = {'H':-0.499994558325 + SOC['H'], 'N':-54.589137594139 + SOC['N'], 'O':-75.067412234737 + SOC['O'], 'C':-37.844893820561 + SOC['C']} + atomEnergies = {'H':-0.499994558325, 'N':-54.589137594139, 'O':-75.067412234737, 'C':-37.844893820561} elif modelChemistry == 'CCSD(T)-F12/aug-cc-pVDZ': - atomEnergies = {'H':-0.499459066131 + SOC['H'], 'N':-54.524279516472 + SOC['N'], 'O':-74.992097308083 + SOC['O'], 'C':-37.786694171716 + SOC['C']} + atomEnergies = {'H':-0.499459066131, 'N':-54.524279516472, 'O':-74.992097308083, 'C':-37.786694171716} elif modelChemistry == 'CCSD(T)-F12/aug-cc-pVTZ': - atomEnergies = {'H':-0.499844820798 + SOC['H'], 'N':-54.527419359906 + SOC['N'], 'O':-75.000001429806 + SOC['O'], 'C':-37.788504810868 + SOC['C']} + atomEnergies = {'H':-0.499844820798, 'N':-54.527419359906, 'O':-75.000001429806, 'C':-37.788504810868} elif modelChemistry == 'CCSD(T)-F12/aug-cc-pVQZ': - atomEnergies = {'H':-0.499949526073 + SOC['H'], 'N':-54.529569719016 + SOC['N'], 'O':-75.004026586610 + SOC['O'], 'C':-37.789387892348 + SOC['C']} + atomEnergies = {'H':-0.499949526073, 'N':-54.529569719016, 'O':-75.004026586610, 'C':-37.789387892348} elif modelChemistry == 'B-CCSD(T)-F12/cc-pVDZ-F12': - atomEnergies = {'H':-0.499811124128 + SOC['H'], 'N':-54.523269942190 + SOC['N'], 'O':-74.990725918500 + SOC['O'], 'C':-37.785409916465 + SOC['C']} + atomEnergies = {'H':-0.499811124128, 'N':-54.523269942190, 'O':-74.990725918500, 'C':-37.785409916465} elif modelChemistry == 'B-CCSD(T)-F12/cc-pVTZ-F12': - atomEnergies = {'H':-0.499946213243 + SOC['H'], 'N':-54.528135889213 + SOC['N'], 'O':-75.001094055506 + SOC['O'], 'C':-37.788233578503 + SOC['C']} + atomEnergies = {'H':-0.499946213243, 'N':-54.528135889213, 'O':-75.001094055506, 'C':-37.788233578503} elif modelChemistry == 'B-CCSD(T)-F12/cc-pVQZ-F12': - atomEnergies = {'H':-0.499994558325 + SOC['H'], 'N':-54.529425753163 + SOC['N'], 'O':-75.003820485005 + SOC['O'], 'C':-37.789006506290 + SOC['C']} + atomEnergies = {'H':-0.499994558325, 'N':-54.529425753163, 'O':-75.003820485005, 'C':-37.789006506290} elif modelChemistry == 'B-CCSD(T)-F12/cc-pCVDZ-F12': - atomEnergies = {'H':-0.499811124128 + SOC['H'], 'N':-54.578602780288 + SOC['N'], 'O':-75.048064317367 + SOC['O'], 'C':-37.837592033417 + SOC['C']} + atomEnergies = {'H':-0.499811124128, 'N':-54.578602780288, 'O':-75.048064317367, 'C':-37.837592033417} elif modelChemistry == 'B-CCSD(T)-F12/cc-pCVTZ-F12': - atomEnergies = {'H':-0.499946213243 + SOC['H'], 'N':-54.586402551258 + SOC['N'], 'O':-75.062767632757 + SOC['O'], 'C':-37.842729156944 + SOC['C']} + atomEnergies = {'H':-0.499946213243, 'N':-54.586402551258, 'O':-75.062767632757, 'C':-37.842729156944} elif modelChemistry == 'B-CCSD(T)-F12/cc-pCVQZ-F12': - atomEnergies = {'H':-0.49999456 + SOC['H'], 'N':-54.587781507581 + SOC['N'], 'O':-75.065397706471 + SOC['O'], 'C':-37.843634971592 + SOC['C']} + atomEnergies = {'H':-0.49999456, 'N':-54.587781507581, 'O':-75.065397706471, 'C':-37.843634971592} elif modelChemistry == 'B-CCSD(T)-F12/aug-cc-pVDZ': - atomEnergies = {'H':-0.499459066131 + SOC['H'], 'N':-54.520475581942 + SOC['N'], 'O':-74.986992215049 + SOC['O'], 'C':-37.783294495799 + SOC['C']} + atomEnergies = {'H':-0.499459066131, 'N':-54.520475581942, 'O':-74.986992215049, 'C':-37.783294495799} elif modelChemistry == 'B-CCSD(T)-F12/aug-cc-pVTZ': - atomEnergies = {'H':-0.499844820798 + SOC['H'], 'N':-54.524927371700 + SOC['N'], 'O':-74.996328829705 + SOC['O'], 'C':-37.786320700792 + SOC['C']} + atomEnergies = {'H':-0.499844820798, 'N':-54.524927371700, 'O':-74.996328829705, 'C':-37.786320700792} elif modelChemistry == 'B-CCSD(T)-F12/aug-cc-pVQZ': - atomEnergies = {'H':-0.499949526073 + SOC['H'], 'N':-54.528189769291 + SOC['N'], 'O':-75.001879610563 + SOC['O'], 'C':-37.788165047059 + SOC['C']} - + atomEnergies = {'H':-0.499949526073, 'N':-54.528189769291, 'O':-75.001879610563, 'C':-37.788165047059} + elif modelChemistry == 'DFT_G03_b3lyp': + atomEnergies = {'H':-0.502256981529, 'N':-54.6007233648, 'O':-75.0898777574, 'C':-37.8572666349} elif modelChemistry == 'DFT_ks_b3lyp': - atomEnergies = {'H':-0.49785866 + SOC['H'], 'N':-54.45608798 + SOC['N'], 'O':-74.93566254 + SOC['O'], 'C':-37.76119132 + SOC['C']} + atomEnergies = {'H':-0.49785866, 'N':-54.45608798, 'O':-74.93566254, 'C':-37.76119132} elif modelChemistry == 'DFT_uks_b3lyp': - atomEnergies = {'H':-0.49785866 + SOC['H'], 'N':-54.45729113 + SOC['N'], 'O':-74.93566254 + SOC['O'], 'C':-37.76119132 + SOC['C']} + atomEnergies = {'H':-0.49785866, 'N':-54.45729113, 'O':-74.93566254, 'C':-37.76119132} elif modelChemistry == 'MP2_rmp2_pVDZ': - atomEnergies = {'H':-0.49927840 + SOC['H'], 'N':-54.46141996 + SOC['N'], 'O':-74.89408254 + SOC['O'], 'C':-37.73792713 + SOC['C']} + atomEnergies = {'H':-0.49927840, 'N':-54.46141996, 'O':-74.89408254, 'C':-37.73792713} elif modelChemistry == 'MP2_rmp2_pVTZ': - atomEnergies = {'H':-0.49980981 + SOC['H'], 'N':-54.49615972 + SOC['N'], 'O':-74.95506980 + SOC['O'], 'C':-37.75833104 + SOC['C']} + atomEnergies = {'H':-0.49980981, 'N':-54.49615972, 'O':-74.95506980, 'C':-37.75833104} elif modelChemistry == 'MP2_rmp2_pVQZ': - atomEnergies = {'H':-0.49994557 + SOC['H'], 'N':-54.50715868 + SOC['N'], 'O':-74.97515364 + SOC['O'], 'C':-37.76533215 + SOC['C']} - - elif modelChemistry == 'CCSD_DZ': - atomEnergies = {'H':-0.499811124 + SOC['H'], 'N':-54.52640629 + SOC['N'], 'O':-74.99545832 + SOC['O'], 'C':-37.78820349 + SOC['C']} - elif modelChemistry == 'CCSD_TZ': - atomEnergies = {'H':-0.499946213 + SOC['H'], 'N':-54.5300091 + SOC['N'], 'O':-75.00412767 + SOC['O'], 'C':-37.78986215 + SOC['C']} - elif modelChemistry == 'CCSD_QZ': - atomEnergies = {'H':-0.499994558 + SOC['H'], 'N':-54.53051523 + SOC['N'], 'O':-75.00560006 + SOC['O'], 'C':-37.78996166 + SOC['C']} - elif modelChemistry == 'CCSD_core_DZ': - atomEnergies = {'H':-0.499811124 + SOC['H'], 'N':-54.58213718 + SOC['N'], 'O':-75.05304555 + SOC['O'], 'C':-37.84086912 + SOC['C']} + atomEnergies = {'H':-0.49994557, 'N':-54.50715868, 'O':-74.97515364, 'C':-37.76533215} + + elif modelChemistry == 'CCSD-F12/cc-pVDZ-F12': + atomEnergies = {'H':-0.499811124128, 'N':-54.524325513811, 'O':-74.992326577897, 'C':-37.786213495943} + + elif modelChemistry == 'CCSD(T)-F12/cc-pVDZ-F12_noscale': + atomEnergies = {'H':-0.499811124128, 'N':-54.526026290887, 'O':-74.994751897699, 'C':-37.787881871511} + + elif modelChemistry == 'G03_PBEPBE_6-311++g_d_p': + atomEnergies = {'H':-0.499812273282, 'N':-54.5289567564, 'O':-75.0033596764, 'C':-37.7937388736} + + elif modelChemistry == 'FCI/cc-pVDZ': +# atomEnergies = {'C':-37.760717371923} + atomEnergies = {'C':-37.789527} + elif modelChemistry == 'FCI/cc-pVTZ': + atomEnergies = {'C':-37.781266669684} + elif modelChemistry == 'FCI/cc-pVQZ': + atomEnergies = {'C':-37.787052110598} + elif modelChemistry == 'BMK/cbsb7': atomEnergies = {'H':-0.498618853119+ SOC['H'], 'N':-54.5697851544+ SOC['N'], 'O':-75.0515210278+ SOC['O'], 'C':-37.8287310027+ SOC['C'], 'P':-341.167615941+ SOC['P'], 'S': -398.001619915+ SOC['S']} - else: logging.warning('Unknown model chemistry "{0}"; not applying energy corrections.'.format(modelChemistry)) return E0 for symbol, count in atoms.items(): - if symbol in atomEnergies: E0 -= count * atomEnergies[symbol] * constants.E_h * constants.Na + if symbol in atomEnergies: E0 -= count * atomEnergies[symbol] * 4.35974394e-18 * constants.Na else: logging.warning('Ignored unknown atom type "{0}".'.format(symbol)) @@ -590,10 +592,10 @@ def applyEnergyCorrections(E0, modelChemistry, atoms, bonds): # See Gaussian thermo whitepaper at http://www.gaussian.com/g_whitepap/thermo.htm) # Note: these values are relatively old and some improvement may be possible by using newer values, particularly for carbon # However, care should be taken to ensure that they are compatible with the BAC values (if BACs are used) - atomHf = {'H': 51.63 , 'N': 112.53 ,'O': 58.99 ,'C': 169.98, 'S': 65.66, 'Cl': 28.59 } + atomHf = {'H': 51.63 , 'N': 112.53 ,'O': 58.99 ,'C': 169.98, 'S': 65.55 } # Thermal contribution to enthalpy Hss(298 K) - Hss(0 K) reported by Gaussian thermo whitepaper # This will be subtracted from the corresponding value in atomHf to produce an enthalpy used in calculating the enthalpy of formation at 298 K - atomThermal = {'H': 1.01 , 'N': 1.04, 'O': 1.04 ,'C': 0.25, 'S': 1.05, 'Cl': 1.10 } + atomThermal = {'H': 1.01 , 'N': 1.04, 'O': 1.04 ,'C': 0.25, 'S': 1.05 } # Total energy correction used to reach gas-phase reference state # Note: Spin orbit coupling no longer included in these energies, since some model chemistries include it automatically atomEnergies = {} @@ -602,19 +604,26 @@ def applyEnergyCorrections(E0, modelChemistry, atoms, bonds): for symbol, count in atoms.items(): if symbol in atomEnergies: E0 += count * atomEnergies[symbol] * 4184. - # Step 3: Bond additivity corrections + # Step 3: Bond energy corrections if modelChemistry == 'CCSD(T)-F12/cc-pVDZ-F12': - bondEnergies = { 'C-H': -0.56, 'C-C': -0.53, 'C=C': -1.90, 'C#C': -0.64, - 'O-H': -0.34, 'C-O': -0.30, 'C=O': -0.92, 'O-O': 0.03, 'N-C': -0.49, - 'N=C': -1.50, 'N#C': -3.54, 'N-O': 0.60, 'N_O': -0.17, 'N=O': -0.72, - 'N-H': -0.75, 'N-N': -1.45, 'N=N': -1.98, 'N#N': -2.05,} + bondEnergies = { 'C-H': -0.46, 'C-C': -0.68, 'C=C': -1.90, 'C#C': -3.13, + 'O-H': -0.51, 'C-O': -0.23, 'C=O': -0.69, 'O-O': -0.02, 'N-C': -0.67, + 'N=C': -1.46, 'N#C': -2.79, 'N-O': 0.74, 'N_O': -0.23, 'N=O': -0.51, + 'N-H': -0.69, 'N-N': -0.47, 'N=N': -1.54, 'N#N': -2.05,} + elif modelChemistry == 'CCSD(T)-F12/cc-pVTZ-F12': + bondEnergies = { 'C-H': -0.09, 'C-C': -0.27, 'C=C': -1.03, 'C#C': -1.79, + 'O-H': -0.06, 'C-O': 0.14, 'C=O': -0.19, 'O-O': 0.16, 'N-C': -0.18, + 'N=C': -0.41, 'N#C': -1.41, 'N-O': 0.87, 'N_O': -0.09, 'N=O': -0.23, + 'N-H': -0.01, 'N-N': -0.21, 'N=N': -0.44, 'N#N': -0.76,} + elif modelChemistry == 'CCSD(T)-F12/cc-pVQZ-F12': + bondEnergies = { 'C-H': -0.08, 'C-C': -0.26, 'C=C': -1.01, 'C#C': -1.66, + 'O-H': 0.07, 'C-O': 0.25, 'C=O': -0.03, 'O-O': 0.26, 'N-C': -0.20, + 'N=C': -0.30, 'N#C': -1.33, 'N-O': 1.01, 'N_O': -0.03, 'N=O': -0.26, + 'N-H': 0.06, 'N-N': -0.23, 'N=N': -0.37, 'N#N': -0.64,} else: - # BAC corrections from Table IX in http://jcp.aip.org/resource/1/jcpsa6/v109/i24/p10570_s1 for CBS-Q method - # H-Cl correction from CBS-QB3 enthalpy difference with Gurvich 1989, HF298=-92.31 kJ bondEnergies = { 'C-H': -0.11, 'C-C': -0.3, 'C=C': -0.08, 'C#C': -0.64, 'O-H': 0.02, 'C-O': 0.33, 'C=O': 0.55, 'N#N': -2.0, 'O=O': -0.2, - 'H-H': 1.1, 'C#N': -0.89, 'C-S': 0.43, 'S=O': -0.78, 'C-Cl': 1.29, - 'N-H': -0.42, 'C-N': -0.13, 'S-H': 0.00, 'H-Cl': 1.16 } + 'H-H': 1.1, 'C#N': -0.89, 'C-S': 0.43, 'S=O': -0.78 } for symbol, count in bonds.items(): if symbol in bondEnergies: E0 += count * bondEnergies[symbol] * 4184. diff --git a/rmgpy/chemkin.py b/rmgpy/chemkin.py index 7abbea90fa..8e1d18eced 100644 --- a/rmgpy/chemkin.py +++ b/rmgpy/chemkin.py @@ -1534,7 +1534,25 @@ def saveSpeciesDictionary(path, species): """ with open(path, 'w') as f: for spec in species: - f.write(spec.molecule[0].toAdjacencyList(label=getSpeciesIdentifier(spec), removeH=True)) + f.write(spec.molecule[0].toAdjacencyList(label=getSpeciesIdentifier(spec), removeH=False)) + f.write('\n') + +def saveSpeciesDictionaryEdge(path, species): + """ + Save the given list of `species` as adjacency lists in a text file `path` + on disk. + """ + with open(path, 'w') as f: + for spec in species: + f.write(spec.molecule[0].toAdjacencyList(label=getSpeciesIdentifier(spec), removeH=False)) + f.write('HF298: '+str(spec.getEnthalpy(298)/4.184/1000.0)) + f.write('\n') + f.write('S298: '+str(spec.getEntropy(298)/4.184)) + f.write('\n') + f.write('Cp300: '+str(spec.getHeatCapacity(300)/4.184)) + f.write('\n') + f.write('Cp1500: '+str(spec.getHeatCapacity(1500)/4.184)) + f.write('\n') f.write('\n') def saveTransportFile(path, species): diff --git a/rmgpy/data/base.py b/rmgpy/data/base.py index a9ab11fc16..860a5c6254 100644 --- a/rmgpy/data/base.py +++ b/rmgpy/data/base.py @@ -644,7 +644,7 @@ def getLogicNodeComponents(entry_or_item): for entry in entries: f.write(entry.label + '\n') if isinstance(entry.item, Molecule): - f.write(entry.item.toAdjacencyList(removeH=True) + '\n') + f.write(entry.item.toAdjacencyList(removeH=False) + '\n') elif isinstance(entry.item, Group): f.write(entry.item.toAdjacencyList().replace('{2S,2T}','2') + '\n') elif isinstance(entry.item, LogicOr): @@ -663,7 +663,7 @@ def comment(s): for entry in entriesNotInTree: f.write(comment(entry.label + '\n')) if isinstance(entry.item, Molecule): - f.write(comment(entry.item.toAdjacencyList(removeH=True) + '\n')) + f.write(comment(entry.item.toAdjacencyList(removeH=False) + '\n')) elif isinstance(entry.item, Group): f.write(comment(entry.item.toAdjacencyList().replace('{2S,2T}','2') + '\n')) elif isinstance(entry.item, LogicOr): @@ -869,7 +869,8 @@ def matchNodeToStructure(self, node, structure, atoms): # Make sure labels actually point to atoms. if center is None or atom is None: return False - if isinstance(center, list): center = center[0] + if isinstance(center, list): + center = center[0] # Semantic check #1: atoms with same label are equivalent elif not atom.isSpecificCaseOf(center): return False @@ -922,7 +923,7 @@ def descendTree(self, structure, atoms, root=None): return None elif not self.matchNodeToStructure(root, structure, atoms): return None - + next = [] for child in root.children: if self.matchNodeToStructure(child, structure, atoms): @@ -1146,6 +1147,14 @@ def isMoleculeForbidden(self, molecule): initialMap[moleculeLabeledAtoms[label]] = entryLabeledAtoms[label] if molecule.isMappingValid(entry.item, initialMap) and molecule.isSubgraphIsomorphic(entry.item, initialMap): return True + + # Until we have more thermodynamic data of molecular ions we will forbid them + molecule_charge = 0 + for atom in molecule.atoms: + molecule_charge += atom.charge + if molecule_charge != 0: + return True + return False def loadOld(self, path): @@ -1201,7 +1210,7 @@ def saveEntry(self, f, entry, name='entry'): if isinstance(entry.item, Molecule): f.write(' molecule = \n') f.write('"""\n') - f.write(entry.item.toAdjacencyList(removeH=True)) + f.write(entry.item.toAdjacencyList(removeH=False)) f.write('""",\n') elif isinstance(entry.item, Group): f.write(' group = \n') diff --git a/rmgpy/data/kinetics/common.py b/rmgpy/data/kinetics/common.py index fc633cac74..d9744367f4 100644 --- a/rmgpy/data/kinetics/common.py +++ b/rmgpy/data/kinetics/common.py @@ -60,6 +60,7 @@ 'SubstitutionS', 'R_Addition_CSm', '1,3_Insertion_RSR', + 'lone_electron_pair_bond', ] # The names of all of the RMG reaction families that are unimolecular @@ -81,6 +82,7 @@ 'intra_substitutionCS_isomerization', 'intra_substitutionS_cyclization', 'intra_substitutionS_isomerization', + 'intra_NO2_ONO_conversion', ] ################################################################################ @@ -133,12 +135,12 @@ def sortEfficiencies(efficiencies0): if isinstance(reactant, Molecule): f.write(' reactant{0:d} = \n'.format(i+1)) f.write('"""\n') - f.write(reactant.toAdjacencyList(removeH=True)) + f.write(reactant.toAdjacencyList(removeH=False)) f.write('""",\n') elif isinstance(reactant, Species): f.write(' reactant{0:d} = \n'.format(i+1)) f.write('"""\n') - f.write(reactant.molecule[0].toAdjacencyList(label=reactant.label, removeH=True)) + f.write(reactant.molecule[0].toAdjacencyList(label=reactant.label, removeH=False)) f.write('""",\n') elif isinstance(reactant, Group): f.write(' group{0:d} = \n'.format(i+1)) @@ -151,12 +153,12 @@ def sortEfficiencies(efficiencies0): if isinstance(product, Molecule): f.write(' product{0:d} = \n'.format(i+1)) f.write('"""\n') - f.write(product.toAdjacencyList(removeH=True)) + f.write(product.toAdjacencyList(removeH=False)) f.write('""",\n') elif isinstance(reactant, Species): f.write(' product{0:d} = \n'.format(i+1)) f.write('"""\n') - f.write(product.molecule[0].toAdjacencyList(label=product.label, removeH=True)) + f.write(product.molecule[0].toAdjacencyList(label=product.label, removeH=False)) f.write('""",\n') if not isinstance(entry.item.reactants[0], Group) and not isinstance(entry.item.reactants[0], LogicNode): f.write(' degeneracy = {0:d},\n'.format(entry.item.degeneracy)) diff --git a/rmgpy/data/kinetics/family.py b/rmgpy/data/kinetics/family.py index eb7b61b95a..f4fedb0f3f 100644 --- a/rmgpy/data/kinetics/family.py +++ b/rmgpy/data/kinetics/family.py @@ -148,6 +148,8 @@ class ReactionRecipe: BREAK_BOND `center1`, `order`, `center2` break the bond between `center1` and `center2`, which should be of type `order` GAIN_RADICAL `center`, `radical` increase the number of free electrons on `center` by `radical` LOSE_RADICAL `center`, `radical` decrease the number of free electrons on `center` by `radical` + GAIN_PAIR `center`, `pair` increase the number of lone electron pairs on `center` by `pair` + LOSE_PAIR `center`, `pair` decrease the number of lone electron pairs on `center` by `pair` ============= ============================= ================================ The actions are stored as a list in the `actions` attribute. Each action is @@ -184,6 +186,10 @@ def getReverse(self): other.addAction(['GAIN_RADICAL', action[1], action[2]]) elif action[0] == 'GAIN_RADICAL': other.addAction(['LOSE_RADICAL', action[1], action[2]]) + elif action[0] == 'LOSE_PAIR': + other.addAction(['GAIN_PAIR', action[1], action[2]]) + elif action[0] == 'GAIN_PAIR': + other.addAction(['LOSE_PAIR', action[1], action[2]]) return other def __apply(self, struct, doForward, unique): @@ -254,6 +260,23 @@ def __apply(self, struct, doForward, unique): atom.applyAction(['GAIN_RADICAL', label, 1]) elif (action[0] == 'LOSE_RADICAL' and doForward) or (action[0] == 'GAIN_RADICAL' and not doForward): atom.applyAction(['LOSE_RADICAL', label, 1]) + + elif action[0] in ['LOSE_PAIR', 'GAIN_PAIR']: + + label, change = action[1:] + change = int(change) + + # Find associated atom + atom = struct.getLabeledAtom(label) + if atom is None: + raise InvalidActionError('Unable to find atom with label "{0}" while applying reaction recipe.'.format(label)) + + # Apply the action + for i in range(change): + if (action[0] == 'GAIN_PAIR' and doForward) or (action[0] == 'LOSE_PAIR' and not doForward): + atom.applyAction(['GAIN_PAIR', label, 1]) + elif (action[0] == 'LOSE_PAIR' and doForward) or (action[0] == 'GAIN_PAIR' and not doForward): + atom.applyAction(['LOSE_PAIR', label, 1]) else: raise InvalidActionError('Unknown action "' + action[0] + '" encountered.') @@ -591,7 +614,7 @@ def loadRecipe(self, actions): self.forwardRecipe = ReactionRecipe() for action in actions: action[0] = action[0].upper() - assert action[0] in ['CHANGE_BOND','FORM_BOND','BREAK_BOND','GAIN_RADICAL','LOSE_RADICAL'] + assert action[0] in ['CHANGE_BOND','FORM_BOND','BREAK_BOND','GAIN_RADICAL','LOSE_RADICAL','GAIN_PAIR','LOSE_PAIR'] self.forwardRecipe.addAction(action) def loadForbidden(self, label, group, shortDesc='', longDesc='', history=None): @@ -1058,11 +1081,10 @@ def __generateProductStructures(self, reactantStructures, maps, forward, **optio reactants are stored in the reaction family template. The `maps` parameter is a list of mappings of the top-level tree node of each *template* reactant to the corresponding *structure*. This function - returns the product structures. + returns a list of the product structures. """ - - if not forward: template = self.reverseTemplate - else: template = self.forwardTemplate + + productStructuresList = [] # Clear any previous atom labeling from all reactant structures for struct in reactantStructures: struct.clearLabeledAtoms() @@ -1075,7 +1097,8 @@ def __generateProductStructures(self, reactantStructures, maps, forward, **optio # Check that reactant structures are allowed in this family # If not, then stop for struct in reactantStructures: - if self.isMoleculeForbidden(struct): raise ForbiddenStructureException() + if self.isMoleculeForbidden(struct): + raise ForbiddenStructureException() # Generate the product structures by applying the forward reaction recipe try: @@ -1121,16 +1144,178 @@ def __generateProductStructures(self, reactantStructures, maps, forward, **optio raise ForbiddenStructureException() if len(struct.atoms) - H > maxHeavyAtoms: raise ForbiddenStructureException() - if struct.getNumberOfRadicalElectrons() > maxRadicals: + if (struct.getNumberOfRadicalElectrons() > maxRadicals) and (len(struct.atoms) - H > 1): raise ForbiddenStructureException() - - # Check that product structures are allowed in this family - # If not, then stop - for struct in productStructures: - struct.updateAtomTypes() - if self.isMoleculeForbidden(struct): raise ForbiddenStructureException() - - return productStructures + + # Generate other possible electronic states + electronicStrucutresList1 = [] + electronicStrucutresList2 = [] + + struct1 = productStructures[0] + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + electronicStrucutresList1.append(struct1a) + atoms1 = struct1.getRadicalAtoms() + + for atom1 in atoms1: + + radical1 = atom1.radicalElectrons + spin1 = atom1.spinMultiplicity + + if radical1 > 1 and radical1 < 4: + + if radical1 == 2 and spin1 == 3: + atom1.setSpinMultiplicity(1) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + elif radical1 == 2 and spin1 == 1: + atom1.setSpinMultiplicity(3) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + elif radical1 == 3 and spin1 == 4: + atom1.setSpinMultiplicity(2) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + elif radical1 == 3 and spin1 == 2: + atom1.setSpinMultiplicity(4) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + + for electronicStrucutres in electronicStrucutresList1: + if electronicStrucutres.isIsomorphic(struct1a): + break + else: + electronicStrucutresList1.append(struct1a) + + elif radical1 == 4: + + if spin1 == 5: + atom1.setSpinMultiplicity(3) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + + atom1.setSpinMultiplicity(1) + struct1b = struct1.copy(True) + struct1b.updateAtomTypes() + elif spin1 == 3: + atom1.setSpinMultiplicity(5) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + + atom1.setSpinMultiplicity(1) + struct1b = struct1.copy(True) + struct1b.updateAtomTypes() + elif spin1 == 1: + atom1.setSpinMultiplicity(5) + struct1a = struct1.copy(True) + struct1a.updateAtomTypes() + + atom1.setSpinMultiplicity(3) + struct1b = struct1.copy(True) + struct1b.updateAtomTypes() + + for electronicStrucutres in electronicStrucutresList1: + if electronicStrucutres.isIsomorphic(struct1a): + break + else: + electronicStrucutresList1.append(struct1a) + + for electronicStrucutres in electronicStrucutresList1: + if electronicStrucutres.isIsomorphic(struct1b): + break + else: + electronicStrucutresList1.append(struct1b) + + if len(productStructures) == 2: + + struct2 = productStructures[1] + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + electronicStrucutresList2.append(struct2a) + atoms2 = struct2.getRadicalAtoms() + + for atom2 in atoms2: + + radical2 = atom2.radicalElectrons + spin2 = atom2.spinMultiplicity + + if radical2 > 1 and radical2 < 4: + + if radical2 == 2 and spin2 == 3: + atom2.setSpinMultiplicity(1) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + elif radical2 == 2 and spin2 == 1: + atom2.setSpinMultiplicity(3) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + elif radical2 == 3 and spin2 == 4: + atom2.setSpinMultiplicity(2) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + elif radical2 == 3 and spin2 == 2: + atom2.setSpinMultiplicity(4) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + + for electronicStrucutres in electronicStrucutresList2: + if electronicStrucutres.isIsomorphic(struct2a): + break + else: + electronicStrucutresList2.append(struct2a) + + elif radical2 == 4: + + if spin2 == 5: + atom2.setSpinMultiplicity(3) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + + atom2.setSpinMultiplicity(1) + struct2b = struct2.copy(True) + struct2b.updateAtomTypes() + elif spin2 == 3: + atom2.setSpinMultiplicity(5) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + + atom2.setSpinMultiplicity(1) + struct2b = struct2.copy(True) + struct2b.updateAtomTypes() + elif spin2 == 1: + atom2.setSpinMultiplicity(5) + struct2a = struct2.copy(True) + struct2a.updateAtomTypes() + + atom2.setSpinMultiplicity(3) + struct2b = struct2.copy(True) + struct2b.updateAtomTypes() + + for electronicStrucutres in electronicStrucutresList2: + if electronicStrucutres.isIsomorphic(struct2a): + break + else: + electronicStrucutresList2.append(struct2a) + + for electronicStrucutres in electronicStrucutresList2: + if electronicStrucutres.isIsomorphic(struct2b): + break + else: + electronicStrucutresList2.append(struct2b) + + if len(productStructures) == 2: + + for structa in electronicStrucutresList1: + for structb in electronicStrucutresList2: + if not (self.isMoleculeForbidden(structa) or self.isMoleculeForbidden(structb)): + productStructuresList.append([structa,structb]) + elif len(productStructures) == 1: + + for structa in electronicStrucutresList1: + if not (self.isMoleculeForbidden(structa)): + productStructuresList.append([structa]) + + return productStructuresList def isMoleculeForbidden(self, molecule): """ @@ -1290,13 +1475,14 @@ def __generateReactions(self, reactants, products=None, forward=True, **options) for map in mappings: reactantStructures = [molecule] try: - productStructures = self.__generateProductStructures(reactantStructures, [map], forward, **options) + productStructuresList = self.__generateProductStructures(reactantStructures, [map], forward, **options) except ForbiddenStructureException: pass else: - if productStructures is not None: - rxn = self.__createReaction(reactantStructures, productStructures, forward) - if rxn: rxnList.append(rxn) + if productStructuresList is not None: + for productStructures in productStructuresList: + rxn = self.__createReaction(reactantStructures, productStructures, forward) + if rxn: rxnList.append(rxn) # Bimolecular reactants: A + B --> products elif len(reactants) == 2 and len(template.reactants) == 2: @@ -1317,13 +1503,14 @@ def __generateReactions(self, reactants, products=None, forward=True, **options) for mapB in mappingsB: reactantStructures = [moleculeA, moleculeB] try: - productStructures = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, **options) + productStructuresList = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, **options) except ForbiddenStructureException: pass else: - if productStructures is not None: - rxn = self.__createReaction(reactantStructures, productStructures, forward) - if rxn: rxnList.append(rxn) + if productStructuresList is not None: + for productStructures in productStructuresList: + rxn = self.__createReaction(reactantStructures, productStructures, forward) + if rxn: rxnList.append(rxn) # Only check for swapped reactants if they are different if reactants[0] is not reactants[1]: @@ -1337,13 +1524,14 @@ def __generateReactions(self, reactants, products=None, forward=True, **options) for mapB in mappingsB: reactantStructures = [moleculeA, moleculeB] try: - productStructures = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, **options) + productStructuresList = self.__generateProductStructures(reactantStructures, [mapA, mapB], forward, **options) except ForbiddenStructureException: pass else: - if productStructures is not None: - rxn = self.__createReaction(reactantStructures, productStructures, forward) - if rxn: rxnList.append(rxn) + if productStructuresList is not None: + for productStructures in productStructuresList: + rxn = self.__createReaction(reactantStructures, productStructures, forward) + if rxn: rxnList.append(rxn) # If products is given, remove reactions from the reaction list that # don't generate the given products @@ -1360,6 +1548,7 @@ def __generateReactions(self, reactants, products=None, forward=True, **options) # Skip reactions that don't match the given products match = False + if len(products) == len(products0) == 1: for product in products[0]: if products0[0].isIsomorphic(product): @@ -1376,7 +1565,7 @@ def __generateReactions(self, reactants, products=None, forward=True, **options) break if match: - rxnList.append(reaction) + rxnList.append(reaction) # The reaction list may contain duplicates of the same reaction # These duplicates should be combined (by increasing the degeneracy of diff --git a/rmgpy/data/kinetics/library.py b/rmgpy/data/kinetics/library.py index 1370f56567..100a216dc6 100644 --- a/rmgpy/data/kinetics/library.py +++ b/rmgpy/data/kinetics/library.py @@ -424,7 +424,7 @@ def writeArrhenius(f, arrhenius): speciesList.sort(key=lambda x: x.label) f = open(os.path.join(path, 'species.txt'), 'w') for species in speciesList: - f.write(species.molecule[0].toAdjacencyList(label=species.label, removeH=True) + "\n") + f.write(species.molecule[0].toAdjacencyList(label=species.label, removeH=False) + "\n") f.close() # Save the high-pressure limit reactions @@ -543,4 +543,4 @@ def writeArrhenius(f, arrhenius): f.write(' DUPLICATE\n') f.write('\n') f.close() - \ No newline at end of file + diff --git a/rmgpy/data/statmech.py b/rmgpy/data/statmech.py index 214c764e52..5a7178785e 100644 --- a/rmgpy/data/statmech.py +++ b/rmgpy/data/statmech.py @@ -52,7 +52,7 @@ def saveEntry(f, entry): if isinstance(entry.item, Molecule): f.write(' molecule = \n') f.write('"""\n') - f.write(entry.item.toAdjacencyList(removeH=True)) + f.write(entry.item.toAdjacencyList(removeH=False)) f.write('""",\n') elif isinstance(entry.item, Group): f.write(' group = \n') diff --git a/rmgpy/data/thermo.py b/rmgpy/data/thermo.py index a5b5213d29..807b4d6d61 100644 --- a/rmgpy/data/thermo.py +++ b/rmgpy/data/thermo.py @@ -60,7 +60,7 @@ def saveEntry(f, entry): if isinstance(entry.item, Molecule): f.write(' molecule = \n') f.write('"""\n') - f.write(entry.item.toAdjacencyList(removeH=True)) + f.write(entry.item.toAdjacencyList(removeH=False)) f.write('""",\n') elif isinstance(entry.item, Group): f.write(' group = \n') @@ -964,7 +964,6 @@ def __addGroupThermoData(self, thermoData, database, molecule, atom): in the structure `structure`, and add it to the existing thermo data `thermoData`. """ - node0 = database.descendTree(molecule, atom, None) if node0 is None: raise KeyError('Node not found in database.') diff --git a/rmgpy/data/transport.py b/rmgpy/data/transport.py index 46baa3b746..56dda0f604 100644 --- a/rmgpy/data/transport.py +++ b/rmgpy/data/transport.py @@ -274,10 +274,12 @@ def getTransportProperties(self, species): transport[0].comment = label break else: - #Transport not found in any loaded libraries, so estimate - transport = self.getTransportPropertiesViaGroupEstimates(species) - #data, library, entry = transport - #return data + try: + #Transport not found in any loaded libraries, so estimate + transport = self.getTransportPropertiesViaGroupEstimates(species) + except: + transport = self.getTransportPropertiesViaLennardJonesParameters(species) + return transport def getAllTransportProperties(self, species): @@ -368,7 +370,6 @@ def estimateCriticalPropertiesViaGroupAdditivity(self, molecule): # For transport estimation we need the atoms to already be sorted because we # iterate over them; if the order changes during the iteration then we # will probably not visit the right atoms, and so will get the transport wrong - molecule.sortVertices() if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species @@ -433,12 +434,9 @@ def estimateCriticalPropertiesViaGroupAdditivity(self, molecule): if molecule.isVertexInCycle(atom): self.__addCriticalPointContribution(groupData, self.groups['ring'], molecule, {'*':atom}) else: - self.__addCriticalPointContribution(groupData, self.groups['nonring'], molecule, {'*':atom}) + self.__addCriticalPointContribution(groupData, self.groups['nonring'], molecule, {'*':atom}) except KeyError: - logging.error("Couldn't find in any transport database:") - logging.error(molecule) - logging.error(molecule.toAdjacencyList()) - raise + raise Tb = 198.18 + groupData.Tb Vc = 17.5 + groupData.Vc @@ -493,6 +491,47 @@ def __addCriticalPointContribution(self, groupData, database, molecule, atom): groupData.structureIndex += data.structureIndex return groupData + + def getTransportPropertiesViaLennardJonesParameters(self,species): + """ + Serves as last resort if every other method to estimate Transport Properties fails. + + Generate the Lennard-Jones parameters for the species. + """ + count = sum([1 for atom in species.molecule[0].vertices if atom.isNonHydrogen()]) + + if count == 1: + sigma = (3.758e-10,"m") + epsilon = (148.6,"K") + elif count == 2: + sigma = (4.443e-10,"m") + epsilon = (110.7,"K") + elif count == 3: + sigma = (5.118e-10,"m") + epsilon = (237.1,"K") + elif count == 4: + sigma = (4.687e-10,"m") + epsilon = (531.4,"K") + elif count == 5: + sigma = (5.784e-10,"m") + epsilon = (341.1,"K") + else: + sigma = (5.949e-10,"m") + epsilon = (399.3,"K") + + shapeIndex = 1 if species.molecule[0].isLinear() else 2 + + transport = TransportData( + shapeIndex = shapeIndex, # 1 if linear, else 2 + epsilon = epsilon, + sigma = sigma, + dipoleMoment = (0, 'C*m'), + polarizability = (0, 'angstroms^3'), + rotrelaxcollnum = 0, # rotational relaxation collision number at 298 K + comment = 'Epsilon & sigma estimated with fixed Lennard Jones Parameters. This is the fallback method! Try improving transport databases!' + ) + + return (transport, None, None) class CriticalPoint: """ diff --git a/rmgpy/molecule/adjlist.py b/rmgpy/molecule/adjlist.py index 6f8d589ef2..b8513aae63 100644 --- a/rmgpy/molecule/adjlist.py +++ b/rmgpy/molecule/adjlist.py @@ -139,15 +139,46 @@ def fromAdjacencyList(adjlist, group=False): radicalElectrons.append(2); spinMultiplicity.append(3) elif e == '3': radicalElectrons.append(3); spinMultiplicity.append(4) + elif e == '3D': + radicalElectrons.append(3); spinMultiplicity.append(2) + elif e == '3Q': + radicalElectrons.append(3); spinMultiplicity.append(4) elif e == '4': radicalElectrons.append(4); spinMultiplicity.append(5) + elif e == '4S': + radicalElectrons.append(4); spinMultiplicity.append(1) + elif e == '4T': + radicalElectrons.append(4); spinMultiplicity.append(3) + elif e == '4V': + radicalElectrons.append(4); spinMultiplicity.append(5) index += 1 - + + # Next number defines the number of lone electron pairs (if provided) + lonePairElectrons = -1 + if not group and len(data) > index: + lpState = data[index] + if lpState[0] != '{': + if lpState == '0': + lonePairElectrons = 0 + if lpState == '1': + lonePairElectrons = 1 + if lpState == '2': + lonePairElectrons = 2 + if lpState == '3': + lonePairElectrons = 3 + if lpState == '4': + lonePairElectrons = 4 + index += 1 + else: + lonePairElectrons = -1 + else: + lonePairElectrons = -1 + # Create a new atom based on the above information if group: atom = GroupAtom(atomType, radicalElectrons, spinMultiplicity, [0 for e in radicalElectrons], label) else: - atom = Atom(atomType[0], radicalElectrons[0], spinMultiplicity[0], 0, label) + atom = Atom(atomType[0], radicalElectrons[0], spinMultiplicity[0], 0, label, lonePairElectrons) # Add the atom to the list atoms.append(atom) @@ -203,29 +234,38 @@ def fromAdjacencyList(adjlist, group=False): atom1.edges[atom2] = bond atom2.edges[atom1] = bond - # Add explicit hydrogen atoms to complete structure if desired - if not group: + # Calculate the number of lone pair electrons requiring molecule with all hydrogen atoms present + if not group and lonePairElectrons == -1: valences = {'H': 1, 'C': 4, 'O': 2, 'N': 3, 'S': 2, 'Si': 4, 'He': 0, 'Ne': 0, 'Ar': 0, 'Cl': 1} orders = {'S': 1, 'D': 2, 'T': 3, 'B': 1.5} - newAtoms = [] for atom in atoms: - try: + if not atom.isHydrogen(): + try: + valence = valences[atom.symbol] + except KeyError: + raise InvalidAdjacencyListError('Cannot add hydrogens to adjacency list: Unknown valence for atom "{0}".'.format(atom.symbol)) + radical = atom.radicalElectrons + order = 0 + for atom2, bond in atom.bonds.items(): + order += orders[bond.order] + lonePairs = 4 - order - radical + charge = 8 - valence - order - radical - 2*lonePairs + atom.setLonePairs(lonePairs) + atom.updateCharge() + else: valence = valences[atom.symbol] - except KeyError: - raise InvalidAdjacencyListError('Cannot add hydrogens to adjacency list: Unknown valence for atom "{0}".'.format(atom.symbol)) - radical = atom.radicalElectrons - order = 0 - for atom2, bond in atom.bonds.items(): - order += orders[bond.order] - count = valence - radical - int(order) - for i in range(count): - a = Atom('H', 0, 1, 0, '') - b = Bond(atom, a, 'S') - newAtoms.append(a) - atom.bonds[a] = b - a.bonds[atom] = b - atoms.extend(newAtoms) - + radical = atom.radicalElectrons + order = 0 + for atom2, bond in atom.bonds.items(): + order += orders[bond.order] + lonePairs = 1 - order - radical + charge = 2 - valence - order - radical - 2*lonePairs + atom.setLonePairs(lonePairs) + atom.updateCharge() + elif not group: + for atom in atoms: + atom.updateCharge() + except InvalidAdjacencyListError: print adjlist raise @@ -249,10 +289,16 @@ def getElectronState(radicalElectrons, spinMultiplicity): electronState = '2S' elif radicalElectrons == 2 and spinMultiplicity == 3: electronState = '2T' - elif radicalElectrons == 3: - electronState = '3' - elif radicalElectrons == 4: - electronState = '4' + elif radicalElectrons == 3 and spinMultiplicity == 2: + electronState = '3D' + elif radicalElectrons == 3 and spinMultiplicity == 4: + electronState = '3Q' + elif radicalElectrons == 4 and spinMultiplicity == 1: + electronState = '4S' + elif radicalElectrons == 4 and spinMultiplicity == 3: + electronState = '4T' + elif radicalElectrons == 4 and spinMultiplicity == 5: + electronState = '4V' else: raise ValueError('Unable to determine electron state for {0:d} radical electrons with spin multiplicity of {1:d}.'.format(radicalElectrons, spinMultiplicity)) return electronState @@ -284,6 +330,7 @@ def toAdjacencyList(atoms, label=None, group=False, removeH=False): atomTypes = {} atomElectronStates = {} + atomLonePairs = {} if group: for atom in atomNumbers: # Atom type(s) @@ -302,6 +349,8 @@ def toAdjacencyList(atoms, label=None, group=False, removeH=False): atomTypes[atom] = '{0}'.format(atom.element.symbol) # Electron state(s) atomElectronStates[atom] = '{0}'.format(getElectronState(atom.radicalElectrons, atom.spinMultiplicity)) + # Lone Pair(s) + atomLonePairs[atom] = atom.lonePairs # Determine field widths atomNumberWidth = max([len(s) for s in atomNumbers.values()]) + 1 @@ -309,6 +358,7 @@ def toAdjacencyList(atoms, label=None, group=False, removeH=False): if atomLabelWidth > 0: atomLabelWidth += 1 atomTypeWidth = max([len(s) for s in atomTypes.values()]) + 1 atomElectronStateWidth = max([len(s) for s in atomElectronStates.values()]) + atomLonePairWidth = 2 # Assemble the adjacency list for atom in atoms: @@ -322,6 +372,9 @@ def toAdjacencyList(atoms, label=None, group=False, removeH=False): adjlist += '{0:<{1:d}}'.format(atomTypes[atom], atomTypeWidth) # Electron state(s) adjlist += '{0:<{1:d}}'.format(atomElectronStates[atom], atomElectronStateWidth) + if group == False: + # Lone Pair(s) + adjlist += '{0:>{1:d}}'.format(atomLonePairs[atom], atomLonePairWidth) # Bonds list atoms2 = atom.bonds.keys() diff --git a/rmgpy/molecule/atomtype.pxd b/rmgpy/molecule/atomtype.pxd index fe531cd90e..be8ca29cb6 100644 --- a/rmgpy/molecule/atomtype.pxd +++ b/rmgpy/molecule/atomtype.pxd @@ -36,6 +36,8 @@ cdef class AtomType: cdef public list breakBond cdef public list incrementRadical cdef public list decrementRadical + cdef public list incrementLonePair + cdef public list decrementLonePair cpdef bint isSpecificCaseOf(self, AtomType other) diff --git a/rmgpy/molecule/atomtype.py b/rmgpy/molecule/atomtype.py index 8b5b9ab8f2..db7f006105 100644 --- a/rmgpy/molecule/atomtype.py +++ b/rmgpy/molecule/atomtype.py @@ -73,6 +73,8 @@ class AtomType: `breakBond` ``list`` The atom type(s) that result when an existing single bond to this atom type is broken `incrementRadical` ``list`` The atom type(s) that result when the number of radical electrons is incremented `decrementRadical` ``list`` The atom type(s) that result when the number of radical electrons is decremented + `incrementLonePair ``list`` The atom type(s) that result when the number of lone electron pairs is incremented + `decrementLonePair` ``list`` The atom type(s) that result when the number of lone electron pairs is decremented =================== =================== ==================================== """ @@ -87,6 +89,8 @@ def __init__(self, label='', generic=None, specific=None): self.breakBond = [] self.incrementRadical = [] self.decrementRadical = [] + self.incrementLonePair = [] + self.decrementLonePair = [] def __repr__(self): return '' % self.label @@ -105,6 +109,8 @@ def __reduce__(self): 'breakBond': self.breakBond, 'incrementRadical': self.incrementRadical, 'decrementRadical': self.decrementRadical, + 'incrementLonePair': self.incrementLonePair, + 'decrementLonePair': self.decrementLonePair, } return (AtomType, (), d) @@ -121,14 +127,18 @@ def __setstate__(self, d): self.breakBond = d['breakBond'] self.incrementRadical = d['incrementRadical'] self.decrementRadical = d['decrementRadical'] + self.incrementLonePair = d['incrementLonePair'] + self.decrementLonePair = d['decrementLonePair'] - def setActions(self, incrementBond, decrementBond, formBond, breakBond, incrementRadical, decrementRadical): + def setActions(self, incrementBond, decrementBond, formBond, breakBond, incrementRadical, decrementRadical, incrementLonePair, decrementLonePair): self.incrementBond = incrementBond self.decrementBond = decrementBond self.formBond = formBond self.breakBond = breakBond self.incrementRadical = incrementRadical self.decrementRadical = decrementRadical + self.incrementLonePair = incrementLonePair + self.decrementLonePair = decrementLonePair def equivalent(self, other): """ @@ -150,83 +160,113 @@ def isSpecificCaseOf(self, other): atomTypes = {} atomTypes['R'] = AtomType(label='R', generic=[], specific=[ 'R!H', - 'C','Cs','Cd','Cdd','Ct','CO','Cb','Cbf', 'CS', 'H', + 'C','Cs','Cd','Cdd','Ct','CO','Cb','Cbf', 'CS', + 'N','N2d','N3s','N3d','N3t','N3b','N5s','N5d','N5dd','N5t','N5b', 'O','Os','Od','Oa', 'Si','Sis','Sid','Sidd','Sit','SiO','Sib','Sibf', 'S','Ss','Sd','Sa'] ) atomTypes['R!H'] = AtomType(label='R!H', generic=['R'], specific=[ 'C','Cs','Cd','Cdd','Ct','CO','Cb','Cbf', 'CS', + 'N','N2d','N3s','N3d','N3t','N3b','N5s','N5d','N5dd','N5t','N5b', 'O','Os','Od','Oa', 'Si','Sis','Sid','Sidd','Sit','SiO','Sib','Sibf', 'S','Ss','Sd','Sa'] ) -atomTypes['C'] = AtomType('C', generic=['R','R!H'], specific=['Cs','Cd','Cdd','Ct','CO','Cb','Cbf','CS']) -atomTypes['Cs'] = AtomType('Cs', generic=['R','R!H', 'C'], specific=[]) -atomTypes['Cd'] = AtomType('Cd', generic=['R','R!H', 'C'], specific=[]) -atomTypes['Cdd'] = AtomType('Cdd', generic=['R','R!H', 'C'], specific=[]) -atomTypes['Ct'] = AtomType('Ct', generic=['R','R!H', 'C'], specific=[]) -atomTypes['CO'] = AtomType('CO', generic=['R','R!H', 'C'], specific=[]) -atomTypes['Cb'] = AtomType('Cb', generic=['R','R!H', 'C'], specific=[]) -atomTypes['Cbf'] = AtomType('Cbf', generic=['R','R!H', 'C'], specific=[]) -atomTypes['CS'] = AtomType('CS', generic=['R','R!H', 'C'], specific=[]) -atomTypes['H'] = AtomType('H', generic=['R'], specific=[]) -atomTypes['O'] = AtomType('O', generic=['R','R!H'], specific=['Os','Od','Oa']) -atomTypes['Os'] = AtomType('Os', generic=['R','R!H','O'], specific=[]) -atomTypes['Od'] = AtomType('Od', generic=['R','R!H','O'], specific=[]) -atomTypes['Oa'] = AtomType('Oa', generic=['R','R!H','O'], specific=[]) -atomTypes['Si'] = AtomType('Si', generic=['R','R!H'], specific=['Sis','Sid','Sidd','Sit','SiO','Sib','Sibf']) -atomTypes['Sis'] = AtomType('Sis', generic=['R','R!H','Si'], specific=[]) -atomTypes['Sid'] = AtomType('Sid', generic=['R','R!H','Si'], specific=[]) +atomTypes['H' ] = AtomType('H', generic=['R'], specific=[]) + +atomTypes['C' ] = AtomType('C', generic=['R','R!H'], specific=['Cs','Cd','Cdd','Ct','CO','Cb','Cbf','CS']) +atomTypes['Cs' ] = AtomType('Cs', generic=['R','R!H','C'], specific=[]) +atomTypes['Cd' ] = AtomType('Cd', generic=['R','R!H','C'], specific=[]) +atomTypes['Cdd' ] = AtomType('Cdd', generic=['R','R!H','C'], specific=[]) +atomTypes['Ct' ] = AtomType('Ct', generic=['R','R!H','C'], specific=[]) +atomTypes['CO' ] = AtomType('CO', generic=['R','R!H','C'], specific=[]) +atomTypes['Cb' ] = AtomType('Cb', generic=['R','R!H','C'], specific=[]) +atomTypes['Cbf' ] = AtomType('Cbf', generic=['R','R!H','C'], specific=[]) +atomTypes['CS' ] = AtomType('CS', generic=['R','R!H','C'], specific=[]) + +atomTypes['N' ] = AtomType('N', generic=['R','R!H'], specific=['N2d','N3s','N3d','N3t','N3b','N5s','N5d','N5dd','N5t','N5b']) +atomTypes['N2d' ] = AtomType('N2d', generic=['R','R!H','N'], specific=[]) +atomTypes['N3s' ] = AtomType('N3s', generic=['R','R!H','N'], specific=[]) +atomTypes['N3d' ] = AtomType('N3d', generic=['R','R!H','N'], specific=[]) +atomTypes['N3t' ] = AtomType('N3t', generic=['R','R!H','N'], specific=[]) +atomTypes['N3b' ] = AtomType('N3b', generic=['R','R!H','N'], specific=[]) +atomTypes['N5s' ] = AtomType('N5s', generic=['R','R!H','N'], specific=[]) +atomTypes['N5d' ] = AtomType('N5d', generic=['R','R!H','N'], specific=[]) +atomTypes['N5dd'] = AtomType('N5dd', generic=['R','R!H','N'], specific=[]) +atomTypes['N5t' ] = AtomType('N5t', generic=['R','R!H','N'], specific=[]) +atomTypes['N5b' ] = AtomType('N5b', generic=['R','R!H','N'], specific=[]) + +atomTypes['O' ] = AtomType('O', generic=['R','R!H'], specific=['Os','Od','Oa']) +atomTypes['Os' ] = AtomType('Os', generic=['R','R!H','O'], specific=[]) +atomTypes['Od' ] = AtomType('Od', generic=['R','R!H','O'], specific=[]) +atomTypes['Oa' ] = AtomType('Oa', generic=['R','R!H','O'], specific=[]) + +atomTypes['Si' ] = AtomType('Si', generic=['R','R!H'], specific=['Sis','Sid','Sidd','Sit','SiO','Sib','Sibf']) +atomTypes['Sis' ] = AtomType('Sis', generic=['R','R!H','Si'], specific=[]) +atomTypes['Sid' ] = AtomType('Sid', generic=['R','R!H','Si'], specific=[]) atomTypes['Sidd'] = AtomType('Sidd', generic=['R','R!H','Si'], specific=[]) -atomTypes['Sit'] = AtomType('Sit', generic=['R','R!H','Si'], specific=[]) -atomTypes['SiO'] = AtomType('SiO', generic=['R','R!H','Si'], specific=[]) -atomTypes['Sib'] = AtomType('Sib', generic=['R','R!H','Si'], specific=[]) +atomTypes['Sit' ] = AtomType('Sit', generic=['R','R!H','Si'], specific=[]) +atomTypes['SiO' ] = AtomType('SiO', generic=['R','R!H','Si'], specific=[]) +atomTypes['Sib' ] = AtomType('Sib', generic=['R','R!H','Si'], specific=[]) atomTypes['Sibf'] = AtomType('Sibf', generic=['R','R!H','Si'], specific=[]) -atomTypes['S'] = AtomType('S', generic=['R','R!H'], specific=['Ss','Sd','Sa']) -atomTypes['Ss'] = AtomType('Ss', generic=['R','R!H','S'], specific=[]) -atomTypes['Sd'] = AtomType('Sd', generic=['R','R!H','S'], specific=[]) -atomTypes['Sa'] = AtomType('Sa', generic=['R','R!H','S'], specific=[]) - -atomTypes['R' ].setActions(incrementBond=['R'], decrementBond=['R'], formBond=['R'], breakBond=['R'], incrementRadical=['R'], decrementRadical=['R']) -atomTypes['R!H' ].setActions(incrementBond=['R!H'], decrementBond=['R!H'], formBond=['R!H'], breakBond=['R!H'], incrementRadical=['R!H'], decrementRadical=['R!H']) - -atomTypes['C' ].setActions(incrementBond=['C'], decrementBond=['C'], formBond=['C'], breakBond=['C'], incrementRadical=['C'], decrementRadical=['C']) -atomTypes['Cs' ].setActions(incrementBond=['Cd','CO','Cs'], decrementBond=[], formBond=['Cs'], breakBond=['Cs'], incrementRadical=['Cs'], decrementRadical=['Cs']) -atomTypes['Cd' ].setActions(incrementBond=['Cdd','Ct'], decrementBond=['Cs'], formBond=['Cd'], breakBond=['Cd'], incrementRadical=['Cd'], decrementRadical=['Cd']) -atomTypes['Cdd' ].setActions(incrementBond=[], decrementBond=['Cd','CO','CS'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) -atomTypes['Ct' ].setActions(incrementBond=[], decrementBond=['Cd'], formBond=['Ct'], breakBond=['Ct'], incrementRadical=['Ct'], decrementRadical=['Ct']) -atomTypes['CO' ].setActions(incrementBond=['Cdd'], decrementBond=['Cs'], formBond=['CO'], breakBond=['CO'], incrementRadical=['CO'], decrementRadical=['CO']) -atomTypes['CS' ].setActions(incrementBond=['Cdd'], decrementBond=['Cs'], formBond=['CS'], breakBond=['CS'], incrementRadical=['CS'], decrementRadical=['CS']) -atomTypes['Cb' ].setActions(incrementBond=[], decrementBond=[], formBond=['Cb'], breakBond=['Cb'], incrementRadical=['Cb'], decrementRadical=['Cb']) -atomTypes['Cbf' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) - -atomTypes['H' ].setActions(incrementBond=[], decrementBond=[], formBond=['H'], breakBond=['H'], incrementRadical=['H'], decrementRadical=['H']) - -atomTypes['O' ].setActions(incrementBond=['O'], decrementBond=['O'], formBond=['O'], breakBond=['O'], incrementRadical=['O'], decrementRadical=['O']) -atomTypes['Os' ].setActions(incrementBond=['Od'], decrementBond=[], formBond=['Os'], breakBond=['Os'], incrementRadical=['Os'], decrementRadical=['Os']) -atomTypes['Od' ].setActions(incrementBond=[], decrementBond=['Os'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) -atomTypes['Oa' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) - -atomTypes['Si' ].setActions(incrementBond=['Si'], decrementBond=['Si'], formBond=['Si'], breakBond=['Si'], incrementRadical=['Si'], decrementRadical=['Si']) -atomTypes['Sis' ].setActions(incrementBond=['Sid','SiO'], decrementBond=[], formBond=['Sis'], breakBond=['Sis'], incrementRadical=['Sis'], decrementRadical=['Sis']) -atomTypes['Sid' ].setActions(incrementBond=['Sidd','Sit'], decrementBond=['Sis'], formBond=['Sid'], breakBond=['Sid'], incrementRadical=['Sid'], decrementRadical=['Sid']) -atomTypes['Sidd' ].setActions(incrementBond=[], decrementBond=['Sid','SiO'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) -atomTypes['Sit' ].setActions(incrementBond=[], decrementBond=['Sid'], formBond=['Sit'], breakBond=['Sit'], incrementRadical=['Sit'], decrementRadical=['Sit']) -atomTypes['SiO' ].setActions(incrementBond=['Sidd'], decrementBond=['Sis'], formBond=['SiO'], breakBond=['SiO'], incrementRadical=['SiO'], decrementRadical=['SiO']) -atomTypes['Sib' ].setActions(incrementBond=[], decrementBond=[], formBond=['Sib'], breakBond=['Sib'], incrementRadical=['Sib'], decrementRadical=['Sib']) -atomTypes['Sibf' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) - -atomTypes['S' ].setActions(incrementBond=['S'], decrementBond=['S'], formBond=['S'], breakBond=['S'], incrementRadical=['S'], decrementRadical=['S']) -atomTypes['Ss' ].setActions(incrementBond=['Sd'], decrementBond=[], formBond=['Ss'], breakBond=['Ss'], incrementRadical=['Ss'], decrementRadical=['Ss']) -atomTypes['Sd' ].setActions(incrementBond=[], decrementBond=['Ss'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) -atomTypes['Sa' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[]) + +atomTypes['S' ] = AtomType('S', generic=['R','R!H'], specific=['Ss','Sd','Sa']) +atomTypes['Ss' ] = AtomType('Ss', generic=['R','R!H','S'], specific=[]) +atomTypes['Sd' ] = AtomType('Sd', generic=['R','R!H','S'], specific=[]) +atomTypes['Sa' ] = AtomType('Sa', generic=['R','R!H','S'], specific=[]) + +atomTypes['R' ].setActions(incrementBond=['R'], decrementBond=['R'], formBond=['R'], breakBond=['R'], incrementRadical=['R'], decrementRadical=['R'], incrementLonePair=['R'], decrementLonePair=['R']) +atomTypes['R!H' ].setActions(incrementBond=['R!H'], decrementBond=['R!H'], formBond=['R!H'], breakBond=['R!H'], incrementRadical=['R!H'], decrementRadical=['R!H'], incrementLonePair=['R!H'], decrementLonePair=['R!H']) + +atomTypes['H' ].setActions(incrementBond=[], decrementBond=[], formBond=['H'], breakBond=['H'], incrementRadical=['H'], decrementRadical=['H'], incrementLonePair=[], decrementLonePair=[]) + +atomTypes['C' ].setActions(incrementBond=['C'], decrementBond=['C'], formBond=['C'], breakBond=['C'], incrementRadical=['C'], decrementRadical=['C'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Cs' ].setActions(incrementBond=['Cd','CO','Cs'], decrementBond=[], formBond=['Cs'], breakBond=['Cs'], incrementRadical=['Cs'], decrementRadical=['Cs'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Cd' ].setActions(incrementBond=['Cdd','Ct'], decrementBond=['Cs'], formBond=['Cd'], breakBond=['Cd'], incrementRadical=['Cd'], decrementRadical=['Cd'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Cdd' ].setActions(incrementBond=[], decrementBond=['Cd','CO','CS'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Ct' ].setActions(incrementBond=[], decrementBond=['Cd'], formBond=['Ct'], breakBond=['Ct'], incrementRadical=['Ct'], decrementRadical=['Ct'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['CO' ].setActions(incrementBond=['Cdd'], decrementBond=['Cs'], formBond=['CO'], breakBond=['CO'], incrementRadical=['CO'], decrementRadical=['CO'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['CS' ].setActions(incrementBond=['Cdd'], decrementBond=['Cs'], formBond=['CS'], breakBond=['CS'], incrementRadical=['CS'], decrementRadical=['CS'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Cb' ].setActions(incrementBond=[], decrementBond=[], formBond=['Cb'], breakBond=['Cb'], incrementRadical=['Cb'], decrementRadical=['Cb'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Cbf' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) + +atomTypes['N' ].setActions(incrementBond=['N'], decrementBond=['N'], formBond=['N'], breakBond=['N'], incrementRadical=['N'], decrementRadical=['N'], incrementLonePair=['N'], decrementLonePair=['N']) +atomTypes['N2d' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) +atomTypes['N3s' ].setActions(incrementBond=['N3d','N3s'], decrementBond=[], formBond=['N3s','N5s'], breakBond=['N3s'], incrementRadical=['N3s'], decrementRadical=['N3s'], incrementLonePair=['N3s'], decrementLonePair=['N3s']) +atomTypes['N3d' ].setActions(incrementBond=['N3t'], decrementBond=['N3s'], formBond=['N3d','N5d'], breakBond=['N3d'], incrementRadical=['N3d'], decrementRadical=['N3d'], incrementLonePair=['N3d'], decrementLonePair=['N3d']) +atomTypes['N3t' ].setActions(incrementBond=[], decrementBond=['N3d'], formBond=['N5t'], breakBond=[], incrementRadical=['N3t'], decrementRadical=['N3t'], incrementLonePair=['N3t'], decrementLonePair=['N3t']) +atomTypes['N3b' ].setActions(incrementBond=[], decrementBond=[], formBond=['N3b'], breakBond=['N3b'], incrementRadical=['N3b'], decrementRadical=['N3b'], incrementLonePair=['N3b'], decrementLonePair=['N3b']) +atomTypes['N5s' ].setActions(incrementBond=['N5d'], decrementBond=[], formBond=[], breakBond=['N3s'], incrementRadical=['N5s'], decrementRadical=['N5s'], incrementLonePair=['N5s'], decrementLonePair=['N5s']) +atomTypes['N5d' ].setActions(incrementBond=['N5dd','N5t'], decrementBond=['N5s'], formBond=[], breakBond=['N3d'], incrementRadical=['N5d'], decrementRadical=['N5d'], incrementLonePair=['N5d'], decrementLonePair=['N5d']) +atomTypes['N5dd'].setActions(incrementBond=[], decrementBond=['N3d'], formBond=[], breakBond=[], incrementRadical=['N5dd'], decrementRadical=['N5dd'], incrementLonePair=['N5d'], decrementLonePair=['N5d']) +atomTypes['N5t' ].setActions(incrementBond=[], decrementBond=['N3d','N3t'], formBond=[], breakBond=['N3d','N3t'], incrementRadical=['N5t'], decrementRadical=['N5t'], incrementLonePair=['N5t'], decrementLonePair=['N5t']) +atomTypes['N5b' ].setActions(incrementBond=[], decrementBond=[], formBond=['N5b'], breakBond=['N5b'], incrementRadical=['N5b'], decrementRadical=['N5b'], incrementLonePair=['N5b'], decrementLonePair=['N5b']) + +atomTypes['O' ].setActions(incrementBond=['O'], decrementBond=['O'], formBond=['O'], breakBond=['O'], incrementRadical=['O'], decrementRadical=['O'], incrementLonePair=['Os'], decrementLonePair=['Os']) +atomTypes['Os' ].setActions(incrementBond=['Od'], decrementBond=[], formBond=['Os'], breakBond=['Os'], incrementRadical=['Os'], decrementRadical=['Os'], incrementLonePair=['Os'], decrementLonePair=['Os']) +atomTypes['Od' ].setActions(incrementBond=[], decrementBond=['Os'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Oa' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) + +atomTypes['Si' ].setActions(incrementBond=['Si'], decrementBond=['Si'], formBond=['Si'], breakBond=['Si'], incrementRadical=['Si'], decrementRadical=['Si'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sis' ].setActions(incrementBond=['Sid','SiO'], decrementBond=[], formBond=['Sis'], breakBond=['Sis'], incrementRadical=['Sis'], decrementRadical=['Sis'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sid' ].setActions(incrementBond=['Sidd','Sit'], decrementBond=['Sis'], formBond=['Sid'], breakBond=['Sid'], incrementRadical=['Sid'], decrementRadical=['Sid'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sidd'].setActions(incrementBond=[], decrementBond=['Sid','SiO'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sit' ].setActions(incrementBond=[], decrementBond=['Sid'], formBond=['Sit'], breakBond=['Sit'], incrementRadical=['Sit'], decrementRadical=['Sit'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['SiO' ].setActions(incrementBond=['Sidd'], decrementBond=['Sis'], formBond=['SiO'], breakBond=['SiO'], incrementRadical=['SiO'], decrementRadical=['SiO'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sib' ].setActions(incrementBond=[], decrementBond=[], formBond=['Sib'], breakBond=['Sib'], incrementRadical=['Sib'], decrementRadical=['Sib'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sibf'].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) + +atomTypes['S' ].setActions(incrementBond=['S'], decrementBond=['S'], formBond=['S'], breakBond=['S'], incrementRadical=['S'], decrementRadical=['S'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Ss' ].setActions(incrementBond=['Sd'], decrementBond=[], formBond=['Ss'], breakBond=['Ss'], incrementRadical=['Ss'], decrementRadical=['Ss'], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sd' ].setActions(incrementBond=[], decrementBond=['Ss'], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) +atomTypes['Sa' ].setActions(incrementBond=[], decrementBond=[], formBond=[], breakBond=[], incrementRadical=[], decrementRadical=[], incrementLonePair=[], decrementLonePair=[]) for atomType in atomTypes.values(): for items in [atomType.generic, atomType.specific, atomType.incrementBond, atomType.decrementBond, atomType.formBond, - atomType.breakBond, atomType.incrementRadical, atomType.decrementRadical]: + atomType.breakBond, atomType.incrementRadical, atomType.decrementRadical, atomType.incrementLonePair, atomType.decrementLonePair]: for index in range(len(items)): items[index] = atomTypes[items[index]] @@ -242,10 +282,12 @@ def getAtomType(atom, bonds): atomType = '' # Count numbers of each higher-order bond type - double = 0; doubleO = 0; triple = 0; benzene = 0 + single = 0; double = 0; doubleO = 0; triple = 0; benzene = 0 for atom2, bond12 in bonds.iteritems(): - if bond12.isDouble(): - if atom2.isOxygen(): doubleO +=1 + if bond12.isSingle(): + single += 1 + elif bond12.isDouble(): + if atom2.isOxygen(): doubleO += 1 else: double += 1 elif bond12.isTriple(): triple += 1 elif bond12.isBenzene(): benzene += 1 @@ -261,6 +303,26 @@ def getAtomType(atom, bonds): elif double == 0 and doubleO == 0 and triple == 0 and benzene == 3: atomType = 'Cbf' elif atom.symbol == 'H': atomType = 'H' + elif atom.symbol == 'N': + if double == 0 and doubleO == 0 and triple == 0 and benzene == 0 and single == 0: atomType = 'N3s' + elif double == 1 and doubleO == 0 and triple == 0 and benzene == 0 and single == 0: atomType = 'N2d' + elif double == 0 and doubleO == 0 and triple == 0 and benzene == 0 and single == 1: atomType = 'N3s' + elif double == 0 and doubleO == 0 and triple == 0 and benzene == 0 and single == 2: atomType = 'N3s' + elif double == 0 and doubleO == 0 and triple == 0 and benzene == 0 and single == 3: atomType = 'N3s' + elif double == 1 and doubleO == 0 and triple == 0 and benzene == 0 and single == 0: atomType = 'N3d' + elif double == 0 and doubleO == 1 and triple == 0 and benzene == 0 and single == 0: atomType = 'N3d' + elif double == 1 and doubleO == 0 and triple == 0 and benzene == 0 and single == 1: atomType = 'N3d' + elif double == 0 and doubleO == 1 and triple == 0 and benzene == 0 and single == 1: atomType = 'N3d' + elif double == 0 and doubleO == 0 and triple == 1 and benzene == 0 and single == 0: atomType = 'N3t' + elif double == 0 and doubleO == 0 and triple == 0 and benzene == 2 and single == 0: atomType = 'N3b' + elif double == 0 and doubleO == 0 and triple == 0 and benzene == 0 and single == 4: atomType = 'N5s' + elif double == 1 and doubleO == 0 and triple == 0 and benzene == 0 and single == 2: atomType = 'N5d' + elif double == 0 and doubleO == 1 and triple == 0 and benzene == 0 and single == 2: atomType = 'N5d' + elif double == 2 and doubleO == 0 and triple == 0 and benzene == 0 and single == 0: atomType = 'N5dd' + elif double == 0 and doubleO == 2 and triple == 0 and benzene == 0 and single == 0: atomType = 'N5dd' + elif double == 1 and doubleO == 1 and triple == 0 and benzene == 0 and single == 0: atomType = 'N5dd' + elif double == 0 and doubleO == 0 and triple == 1 and benzene == 0 and single == 1: atomType = 'N5t' + elif double == 0 and doubleO == 0 and triple == 0 and benzene == 2 and single == 1: atomType = 'N5b' elif atom.symbol == 'O': if double + doubleO == 0 and triple == 0 and benzene == 0: atomType = 'Os' elif double + doubleO == 1 and triple == 0 and benzene == 0: atomType = 'Od' @@ -277,7 +339,7 @@ def getAtomType(atom, bonds): if double + doubleO == 0 and triple == 0 and benzene == 0: atomType = 'Ss' elif double + doubleO == 1 and triple == 0 and benzene == 0: atomType = 'Sd' elif len(bonds) == 0: atomType = 'Sa' - elif atom.symbol == 'N' or atom.symbol == 'Ar' or atom.symbol == 'He' or atom.symbol == 'Ne' or atom.symbol == 'Cl': + elif atom.symbol == 'Ar' or atom.symbol == 'He' or atom.symbol == 'Ne' or atom.symbol == 'Cl': return None # Raise exception if we could not identify the proper atom type diff --git a/rmgpy/molecule/atomtypeTest.py b/rmgpy/molecule/atomtypeTest.py index 0cdd677f11..e77ed9cab3 100644 --- a/rmgpy/molecule/atomtypeTest.py +++ b/rmgpy/molecule/atomtypeTest.py @@ -88,7 +88,9 @@ def testSetActions(self): self.atomType.formBond, self.atomType.breakBond, self.atomType.incrementRadical, - self.atomType.decrementRadical) + self.atomType.decrementRadical, + self.atomType.incrementLonePair, + self.atomType.decrementLonePair) self.assertEqual(self.atomType.incrementBond, other.incrementBond) self.assertEqual(self.atomType.decrementBond, other.decrementBond) self.assertEqual(self.atomType.formBond, other.formBond) diff --git a/rmgpy/molecule/draw.py b/rmgpy/molecule/draw.py index 5d4d3827a6..eb961c2812 100644 --- a/rmgpy/molecule/draw.py +++ b/rmgpy/molecule/draw.py @@ -279,36 +279,549 @@ def __generateCoordinates(self): """ atoms = self.molecule.atoms Natoms = len(atoms) + flag_nitrogen = 0 + + for atom in self.molecule.atoms: + if atom.isNitrogen(): + flag_nitrogen = 1 # Initialize array of coordinates self.coordinates = coordinates = numpy.zeros((Natoms, 2)) - # Use rdkit 2D coordinate generation: - - # Generate the RDkit molecule from the RDkit molecule, use geometry - # in order to match the atoms in the rdmol with the atoms in the - # RMG molecule (which is required to extract coordinates). - self.geometry = Geometry(None, None, self.molecule, None) - - rdmol, rdAtomIdx = self.geometry.rd_build() - AllChem.Compute2DCoords(rdmol) - - # Extract the coordinates from each atom. - for atom in atoms: - index = rdAtomIdx[atom] - point = rdmol.GetConformer(0).GetAtomPosition(index) - coordinates[index,:]= [point.x*0.6, point.y*0.6] - - # RDKit generates some molecules more vertically than horizontally, - # Especially linear ones. This will reflect any molecule taller than - # it is wide across the line y=x - ranges = numpy.ptp(coordinates, axis = 0) - if ranges[1] > ranges[0]: - temp = numpy.copy(coordinates) - coordinates[:,0] = temp[:,1] - coordinates[:,1] = temp[:,0] + if flag_nitrogen == 1: + # If there are only one or two atoms to draw, then determining the + # coordinates is trivial + if Natoms == 1: + self.coordinates[0,:] = [0.0, 0.0] + return self.coordinates + elif Natoms == 2: + self.coordinates[0,:] = [-0.5, 0.0] + self.coordinates[1,:] = [0.5, 0.0] + return self.coordinates + + if len(self.cycles) > 0: + # Cyclic molecule + backbone = self.__findCyclicBackbone() + self.__generateRingSystemCoordinates(backbone) + # Flatten backbone so that it contains a list of the atoms in the + # backbone, rather than a list of the cycles in the backbone + backbone = list(set([atom for cycle in backbone for atom in cycle])) + else: + # Straight chain molecule + backbone = self.__findStraightChainBackbone() + self.__generateStraightChainCoordinates(backbone) + + # If backbone is linear, then rotate so that the bond is parallel to the + # horizontal axis + vector0 = coordinates[atoms.index(backbone[1]),:] - coordinates[atoms.index(backbone[0]),:] + for i in range(2, len(backbone)): + vector = coordinates[atoms.index(backbone[i]),:] - coordinates[atoms.index(backbone[i-1]),:] + if numpy.linalg.norm(vector - vector0) > 1e-4: + break + else: + angle = math.atan2(vector0[0], vector0[1]) - math.pi / 2 + rot = numpy.array([[math.cos(angle), math.sin(angle)], [-math.sin(angle), math.cos(angle)]], numpy.float64) + coordinates = numpy.dot(coordinates, rot) + + # Center backbone at origin + xmin = numpy.min(coordinates[:,0]) + xmax = numpy.max(coordinates[:,0]) + ymin = numpy.min(coordinates[:,1]) + ymax = numpy.max(coordinates[:,1]) + xmid = 0.5 * (xmax + xmin) + ymid = 0.5 * (ymax + ymin) + for atom in backbone: + index = atoms.index(atom) + coordinates[index,0] -= xmid + coordinates[index,1] -= ymid + + # We now proceed by calculating the coordinates of the functional groups + # attached to the backbone + # Each functional group is independent, although they may contain further + # branching and cycles + # In general substituents should try to grow away from the origin to + # minimize likelihood of overlap + self.__generateNeighborCoordinates(backbone) + + return coordinates + + else: + + # Use rdkit 2D coordinate generation: + + # Generate the RDkit molecule from the RDkit molecule, use geometry + # in order to match the atoms in the rdmol with the atoms in the + # RMG molecule (which is required to extract coordinates). + self.geometry = Geometry(None, None, self.molecule, None) + + rdmol, rdAtomIdx = self.geometry.rd_build() + AllChem.Compute2DCoords(rdmol) + + # Extract the coordinates from each atom. + for atom in atoms: + index = rdAtomIdx[atom] + point = rdmol.GetConformer(0).GetAtomPosition(index) + coordinates[index,:]= [point.x*0.6, point.y*0.6] + + # RDKit generates some molecules more vertically than horizontally, + # Especially linear ones. This will reflect any molecule taller than + # it is wide across the line y=x + ranges = numpy.ptp(coordinates, axis = 0) + if ranges[1] > ranges[0]: + temp = numpy.copy(coordinates) + coordinates[:,0] = temp[:,1] + coordinates[:,1] = temp[:,0] + + return coordinates + + def __findCyclicBackbone(self): + """ + Return a set of atoms to use as the "backbone" of the molecule. For + cyclics this is simply the largest ring system. + """ + count = [len(set([atom for ring in ringSystem for atom in ring])) for ringSystem in self.ringSystems] + index = 0 + for i in range(1, len(self.ringSystems)): + if count[i] > count[index]: + index = i + return self.ringSystems[index] + + def __findStraightChainBackbone(self): + """ + Return a set of atoms to use as the "backbone" of the molecule. For + non-cyclics this is the largest straight chain between atoms. If carbon + atoms are present, then we define the backbone only in terms of them. + """ + # Find the terminal atoms - those that only have one explicit bond + terminalAtoms = [atom for atom in self.molecule.atoms if len(atom.bonds) == 1] + assert len(terminalAtoms) >= 2 + + # Starting from each terminal atom, find the longest straight path to + # another terminal + # The longest found is the backbone + backbone = [] + paths = [] + for atom in terminalAtoms: + paths.extend(self.__findStraightChainPaths([atom])) + + # Remove any paths that don't end in a terminal atom + # (I don't think this should remove any!) + paths = [path for path in paths if path[-1] in terminalAtoms] + + # Remove all paths shorter than the maximum + length = max([len(path) for path in paths]) + paths = [path for path in paths if len(path) == length] + + # Prefer the paths with the most carbon atoms + carbons = [sum([1 for atom in path if atom.isCarbon()]) for path in paths] + maxCarbons = max(carbons) + paths = [path for path, carbon in zip(paths, carbons) if carbon == maxCarbons] + + # At this point we could choose any remaining path, so simply choose the first + backbone = paths[0] + + assert len(backbone) > 1 + assert backbone[0] in terminalAtoms + assert backbone[-1] in terminalAtoms + + return backbone + + def __findStraightChainPaths(self, atoms0): + """ + Finds the paths containing the list of atoms `atoms0` in the + current molecule. The atoms are assumed to already be in a path, with + ``atoms0[0]`` being a terminal atom. + """ + atom1 = atoms0[-1] + paths = [] + for atom2 in atom1.bonds: + if atom2 not in atoms0: + atoms = atoms0[:] + atoms.append(atom2) + if not self.molecule.isAtomInCycle(atom2): + paths.extend(self.__findStraightChainPaths(atoms)) + if len(paths) == 0: + paths.append(atoms0[:]) + return paths + + def __generateRingSystemCoordinates(self, atoms): + """ + For a ring system composed of the given cycles of `atoms`, update the + coordinates of each atom in the system. + """ + coordinates = self.coordinates + atoms = atoms[:] + processed = [] + + # Lay out largest cycle in ring system first + cycle = atoms[0] + for cycle0 in atoms[1:]: + if len(cycle0) > len(cycle): + cycle = cycle0 + angle = - 2 * math.pi / len(cycle) + radius = 1.0 / (2 * math.sin(math.pi / len(cycle))) + for i, atom in enumerate(cycle): + index = self.molecule.atoms.index(atom) + coordinates[index,:] = [math.cos(math.pi / 2 + i * angle), math.sin(math.pi / 2 + i * angle)] + coordinates[index,:] *= radius + atoms.remove(cycle) + processed.append(cycle) + + # If there are other cycles, then try to lay them out as well + while len(atoms) > 0: + + # Find the largest cycle that shares one or two atoms with a ring that's + # already been processed + cycle = None + for cycle0 in atoms: + for cycle1 in processed: + count = sum([1 for atom in cycle0 if atom in cycle1]) + if (count == 1 or count == 2): + if cycle is None or len(cycle0) > len(cycle): cycle = cycle0 + cycle0 = cycle1 + atoms.remove(cycle) + + # Shuffle atoms in cycle such that the common atoms come first + # Also find the average center of the processed cycles that touch the + # current cycles + found = False + commonAtoms = [] + count = 0 + center0 = numpy.zeros(2, numpy.float64) + for cycle1 in processed: + found = False + for atom in cycle1: + if atom in cycle and atom not in commonAtoms: + commonAtoms.append(atom) + found = True + if found: + center1 = numpy.zeros(2, numpy.float64) + for atom in cycle1: + center1 += coordinates[cycle1.index(atom),:] + center1 /= len(cycle1) + center0 += center1 + count += 1 + center0 /= count + + if len(commonAtoms) > 1: + index0 = cycle.index(commonAtoms[0]) + index1 = cycle.index(commonAtoms[1]) + if (index0 == 0 and index1 == len(cycle) - 1) or (index1 == 0 and index0 == len(cycle) - 1): + cycle = cycle[-1:] + cycle[0:-1] + if cycle.index(commonAtoms[1]) < cycle.index(commonAtoms[0]): + cycle.reverse() + index = cycle.index(commonAtoms[0]) + cycle = cycle[index:] + cycle[0:index] + + # Determine center of cycle based on already-assigned positions of + # common atoms (which won't be changed) + if len(commonAtoms) == 1 or len(commonAtoms) == 2: + # Center of new cycle is reflection of center of adjacent cycle + # across common atom or bond + center = numpy.zeros(2, numpy.float64) + for atom in commonAtoms: + center += coordinates[self.molecule.atoms.index(atom),:] + center /= len(commonAtoms) + vector = center - center0 + center += vector + radius = 1.0 / (2 * math.sin(math.pi / len(cycle))) + + else: + # Use any three points to determine the point equidistant from these + # three; this is the center + index0 = self.molecule.atoms.index(commonAtoms[0]) + index1 = self.molecule.atoms.index(commonAtoms[1]) + index2 = self.molecule.atoms.index(commonAtoms[2]) + A = numpy.zeros((2,2), numpy.float64) + b = numpy.zeros((2), numpy.float64) + A[0,:] = 2 * (coordinates[index1,:] - coordinates[index0,:]) + A[1,:] = 2 * (coordinates[index2,:] - coordinates[index0,:]) + b[0] = coordinates[index1,0]**2 + coordinates[index1,1]**2 - coordinates[index0,0]**2 - coordinates[index0,1]**2 + b[1] = coordinates[index2,0]**2 + coordinates[index2,1]**2 - coordinates[index0,0]**2 - coordinates[index0,1]**2 + center = numpy.linalg.solve(A, b) + radius = numpy.linalg.norm(center - coordinates[index0,:]) + + startAngle = 0.0; endAngle = 0.0 + if len(commonAtoms) == 1: + # We will use the full 360 degrees to place the other atoms in the cycle + startAngle = math.atan2(-vector[1], vector[0]) + endAngle = startAngle + 2 * math.pi + elif len(commonAtoms) >= 2: + # Divide other atoms in cycle equally among unused angle + vector = coordinates[cycle.index(commonAtoms[-1]),:] - center + startAngle = math.atan2(vector[1], vector[0]) + vector = coordinates[cycle.index(commonAtoms[0]),:] - center + endAngle = math.atan2(vector[1], vector[0]) + + # Place remaining atoms in cycle + if endAngle < startAngle: + endAngle += 2 * math.pi + dAngle = (endAngle - startAngle) / (len(cycle) - len(commonAtoms) + 1) + else: + endAngle -= 2 * math.pi + dAngle = (endAngle - startAngle) / (len(cycle) - len(commonAtoms) + 1) + + count = 1 + for i in range(len(commonAtoms), len(cycle)): + angle = startAngle + count * dAngle + index = self.molecule.atoms.index(cycle[i]) + # Check that we aren't reassigning any atom positions + # This version assumes that no atoms belong at the origin, which is + # usually fine because the first ring is centered at the origin + if numpy.linalg.norm(coordinates[index,:]) < 1e-4: + vector = numpy.array([math.cos(angle), math.sin(angle)], numpy.float64) + coordinates[index,:] = center + radius * vector + count += 1 + + # We're done assigning coordinates for this cycle, so mark it as processed + processed.append(cycle) + + def __generateStraightChainCoordinates(self, atoms): + """ + Update the coordinates for the linear straight chain of `atoms` in + the current molecule. + """ + coordinates = self.coordinates + + # First atom goes at origin + index0 = self.molecule.atoms.index(atoms[0]) + coordinates[index0,:] = [0.0, 0.0] + + # Second atom goes on x-axis (for now; this could be improved!) + index1 = self.molecule.atoms.index(atoms[1]) + vector = numpy.array([1.0, 0.0], numpy.float64) + if atoms[0].bonds[atoms[1]].isTriple(): + rotatePositive = False + else: + rotatePositive = True + rot = numpy.array([[math.cos(-math.pi / 6), math.sin(-math.pi / 6)], [-math.sin(-math.pi / 6), math.cos(-math.pi / 6)]], numpy.float64) + vector = numpy.array([1.0, 0.0], numpy.float64) + vector = numpy.dot(rot, vector) + coordinates[index1,:] = coordinates[index0,:] + vector + + # Other atoms + for i in range(2, len(atoms)): + atom0 = atoms[i-2] + atom1 = atoms[i-1] + atom2 = atoms[i] + index1 = self.molecule.atoms.index(atom1) + index2 = self.molecule.atoms.index(atom2) + bond0 = atom0.bonds[atom1] + bond = atom1.bonds[atom2] + # Angle of next bond depends on the number of bonds to the start atom + numBonds = len(atom1.bonds) + if numBonds == 2: + if (bond0.isTriple() or bond.isTriple()) or (bond0.isDouble() and bond.isDouble()): + # Rotate by 0 degrees towards horizontal axis (to get angle of 180) + angle = 0.0 + else: + # Rotate by 60 degrees towards horizontal axis (to get angle of 120) + angle = math.pi / 3 + elif numBonds == 3: + # Rotate by 60 degrees towards horizontal axis (to get angle of 120) + angle = math.pi / 3 + elif numBonds == 4: + # Rotate by 0 degrees towards horizontal axis (to get angle of 90) + angle = 0.0 + elif numBonds == 5: + # Rotate by 36 degrees towards horizontal axis (to get angle of 144) + angle = math.pi / 5 + elif numBonds == 6: + # Rotate by 0 degrees towards horizontal axis (to get angle of 180) + angle = 0.0 + # Determine coordinates for atom + if angle != 0: + if not rotatePositive: angle = -angle + rot = numpy.array([[math.cos(angle), math.sin(angle)], [-math.sin(angle), math.cos(angle)]], numpy.float64) + vector = numpy.dot(rot, vector) + rotatePositive = not rotatePositive + coordinates[index2,:] = coordinates[index1,:] + vector + + def __generateNeighborCoordinates(self, backbone): + """ + Recursively update the coordinates for the atoms immediately adjacent + to the atoms in the molecular `backbone`. + """ + atoms = self.molecule.atoms + coordinates = self.coordinates + + for i in range(len(backbone)): + atom0 = backbone[i] + index0 = atoms.index(atom0) + + # Determine bond angles of all previously-determined bond locations for + # this atom + bondAngles = [] + for atom1 in atom0.bonds: + index1 = atoms.index(atom1) + if atom1 in backbone: + vector = coordinates[index1,:] - coordinates[index0,:] + angle = math.atan2(vector[1], vector[0]) + bondAngles.append(angle) + bondAngles.sort() + + bestAngle = 2 * math.pi / len(atom0.bonds) + regular = True + for angle1, angle2 in zip(bondAngles[0:-1], bondAngles[1:]): + if all([abs(angle2 - angle1 - (i+1) * bestAngle) > 1e-4 for i in range(len(atom0.bonds))]): + regular = False + + if regular: + # All the bonds around each atom are equally spaced + # We just need to fill in the missing bond locations + + # Determine rotation angle and matrix + rot = numpy.array([[math.cos(bestAngle), -math.sin(bestAngle)], [math.sin(bestAngle), math.cos(bestAngle)]], numpy.float64) + # Determine the vector of any currently-existing bond from this atom + vector = None + for atom1 in atom0.bonds: + index1 = atoms.index(atom1) + if atom1 in backbone or numpy.linalg.norm(coordinates[index1,:]) > 1e-4: + vector = coordinates[index1,:] - coordinates[index0,:] + + # Iterate through each neighboring atom to this backbone atom + # If the neighbor is not in the backbone and does not yet have + # coordinates, then we need to determine coordinates for it + for atom1 in atom0.bonds: + if atom1 not in backbone and numpy.linalg.norm(coordinates[atoms.index(atom1),:]) < 1e-4: + occupied = True; count = 0 + # Rotate vector until we find an unoccupied location + while occupied and count < len(atom0.bonds): + count += 1; occupied = False + vector = numpy.dot(rot, vector) + for atom2 in atom0.bonds: + index2 = atoms.index(atom2) + if numpy.linalg.norm(coordinates[index2,:] - coordinates[index0,:] - vector) < 1e-4: + occupied = True + coordinates[atoms.index(atom1),:] = coordinates[index0,:] + vector + self.__generateFunctionalGroupCoordinates(atom0, atom1) + + else: + + # The bonds are not evenly spaced (e.g. due to a ring) + # We place all of the remaining bonds evenly over the reflex angle + startAngle = max(bondAngles) + endAngle = min(bondAngles) + if 0.0 < endAngle - startAngle < math.pi: endAngle += 2 * math.pi + elif 0.0 > endAngle - startAngle > -math.pi: startAngle -= 2 * math.pi + dAngle = (endAngle - startAngle) / (len(atom0.bonds) - len(bondAngles) + 1) + + index = 1 + for atom1 in atom0.bonds: + if atom1 not in backbone and numpy.linalg.norm(coordinates[atoms.index(atom1),:]) < 1e-4: + angle = startAngle + index * dAngle + index += 1 + vector = numpy.array([math.cos(angle), math.sin(angle)], numpy.float64) + vector /= numpy.linalg.norm(vector) + coordinates[atoms.index(atom1),:] = coordinates[index0,:] + vector + self.__generateFunctionalGroupCoordinates(atom0, atom1) + + def __generateFunctionalGroupCoordinates(self, atom0, atom1): + """ + For the functional group starting with the bond from `atom0` to `atom1`, + generate the coordinates of the rest of the functional group. `atom0` is + treated as if a terminal atom. `atom0` and `atom1` must already have their + coordinates determined. `atoms` is a list of the atoms to be drawn, `bonds` + is a dictionary of the bonds to draw, and `coordinates` is an array of the + coordinates for each atom to be drawn. This function is designed to be + recursive. + """ + + atoms = self.molecule.atoms + coordinates = self.coordinates + + index0 = atoms.index(atom0) + index1 = atoms.index(atom1) + + # Determine the vector of any currently-existing bond from this atom + # (We use the bond to the previous atom here) + vector = coordinates[index0,:] - coordinates[index1,:] + bondAngle = math.atan2(vector[1], vector[0]) + + # Check to see if atom1 is in any cycles in the molecule + ringSystem = None + for ringSys in self.ringSystems: + if any([atom1 in ring for ring in ringSys]): + ringSystem = ringSys + + if ringSystem is not None: + # atom1 is part of a ring system, so we need to process the entire + # ring system at once + + # Generate coordinates for all atoms in the ring system + self.__generateRingSystemCoordinates(ringSystem) + + cycleAtoms = list(set([atom for ring in ringSystem for atom in ring])) + + coordinates_cycle = numpy.zeros_like(self.coordinates) + for atom in cycleAtoms: + coordinates_cycle[atoms.index(atom),:] = coordinates[atoms.index(atom),:] + + # Rotate the ring system coordinates so that the line connecting atom1 + # and the center of mass of the ring is parallel to that between + # atom0 and atom1 + center = numpy.zeros(2, numpy.float64) + for atom in cycleAtoms: + center += coordinates_cycle[atoms.index(atom),:] + center /= len(cycleAtoms) + vector0 = center - coordinates_cycle[atoms.index(atom1),:] + angle = math.atan2(vector[1] - vector0[1], vector[0] - vector0[0]) + rot = numpy.array([[math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)]], numpy.float64) + coordinates_cycle = numpy.dot(coordinates_cycle, rot) - return coordinates + # Translate the ring system coordinates to the position of atom1 + coordinates_cycle += coordinates[atoms.index(atom1),:] - coordinates_cycle[atoms.index(atom1),:] + for atom in cycleAtoms: + coordinates[atoms.index(atom),:] = coordinates_cycle[atoms.index(atom),:] + + # Generate coordinates for remaining neighbors of ring system, + # continuing to recurse as needed + self.__generateNeighborCoordinates(cycleAtoms) + + else: + # atom1 is not in any rings, so we can continue as normal + + # Determine rotation angle and matrix + numBonds = len(atom1.bonds) + angle = 0.0 + if numBonds == 2: + bond0, bond = atom1.bonds.values() + if (bond0.isTriple() or bond.isTriple()) or (bond0.isDouble() and bond.isDouble()): + angle = math.pi + else: + angle = 2 * math.pi / 3 + # Make sure we're rotating such that we move away from the origin, + # to discourage overlap of functional groups + rot1 = numpy.array([[math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)]], numpy.float64) + rot2 = numpy.array([[math.cos(angle), math.sin(angle)], [-math.sin(angle), math.cos(angle)]], numpy.float64) + vector1 = coordinates[index1,:] + numpy.dot(rot1, vector) + vector2 = coordinates[index1,:] + numpy.dot(rot2, vector) + if bondAngle < -0.5 * math.pi or bondAngle > 0.5 * math.pi: + angle = abs(angle) + else: + angle = -abs(angle) + else: + angle = 2 * math.pi / numBonds + rot = numpy.array([[math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)]], numpy.float64) + + # Iterate through each neighboring atom to this backbone atom + # If the neighbor is not in the backbone, then we need to determine + # coordinates for it + for atom, bond in atom1.bonds.iteritems(): + if atom is not atom0: + occupied = True; count = 0 + # Rotate vector until we find an unoccupied location + while occupied and count < len(atom1.bonds): + count += 1; occupied = False + vector = numpy.dot(rot, vector) + for atom2 in atom1.bonds: + index2 = atoms.index(atom2) + if numpy.linalg.norm(coordinates[index2,:] - coordinates[index1,:] - vector) < 1e-4: + occupied = True + coordinates[atoms.index(atom),:] = coordinates[index1,:] + vector + + # Recursively continue with functional group + self.__generateFunctionalGroupCoordinates(atom1, atom) def __generateAtomLabels(self): """ @@ -566,7 +1079,7 @@ def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True): boundingRect = [x1, y1, x2, y2] # Set color for text - if heavyAtom == 'C': cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) + if heavyAtom == 'C': cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) elif heavyAtom == 'N': cr.set_source_rgba(0.0, 0.0, 1.0, 1.0) elif heavyAtom == 'O': cr.set_source_rgba(1.0, 0.0, 0.0, 1.0) elif heavyAtom == 'F': cr.set_source_rgba(0.5, 0.75, 1.0, 1.0) @@ -735,6 +1248,27 @@ def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True): cr.move_to(xi, yi - extents[1]) cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.show_text(text) + + # Draw lone electron pairs + for i in range (atom.lonePairs): + cr.new_sub_path() + if i == 0: + x1lp = x-2 + y1lp = y-8 + x2lp = x+2 + y2lp = y-12 + elif i == 1: + x1lp = x+12 + y1lp = y-8 + x2lp = x+8 + y2lp = y-12 + elif i == 2: + x1lp = x-2 + y1lp = y-1 + x2lp = x+2 + y2lp = y+3 + self.__drawLine(cr, x1lp, y1lp, x2lp, y2lp) + elif orientation[0] == 'l' or orientation[0] == 'r': # Draw charges first text = '' @@ -754,6 +1288,25 @@ def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True): cr.arc(xi + width/2, yi + 3 * i + 1, 1, 0, 2 * math.pi) cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.fill() + # Draw lone electron pairs + for i in range (atom.lonePairs): + cr.new_sub_path() + if i == 0: + x1lp = x-2 + y1lp = y-8 + x2lp = x+2 + y2lp = y-12 + elif i == 1: + x1lp = x+12 + y1lp = y-8 + x2lp = x+8 + y2lp = y-12 + elif i == 2: + x1lp = x-2 + y1lp = y-1 + x2lp = x+2 + y2lp = y+3 + self.__drawLine(cr, x1lp, y1lp, x2lp, y2lp) # Update bounding rect to ensure atoms are included if boundingRect[0] < self.left: diff --git a/rmgpy/molecule/group.pxd b/rmgpy/molecule/group.pxd index 4df8de2efe..9939b85e07 100644 --- a/rmgpy/molecule/group.pxd +++ b/rmgpy/molecule/group.pxd @@ -36,6 +36,7 @@ cdef class GroupAtom(Vertex): cdef public list spinMultiplicity cdef public list charge cdef public str label + cdef public list lonePairs cpdef Vertex copy(self) @@ -48,6 +49,10 @@ cdef class GroupAtom(Vertex): cpdef __gainRadical(self, short radical) cpdef __loseRadical(self, short radical) + + cpdef __gainPair(self, short radical) + + cpdef __losePair(self, short radical) cpdef applyAction(self, list action) @@ -78,6 +83,7 @@ cdef class Group(Graph): # These read-only attribues act as a "fingerprint" for accelerating # subgraph isomorphism checks cdef public short carbonCount + cdef public short nitrogenCount cdef public short oxygenCount cdef public short sulfurCount cdef public short radicalCount diff --git a/rmgpy/molecule/group.py b/rmgpy/molecule/group.py index 0cb4d951e5..b6f83e501c 100644 --- a/rmgpy/molecule/group.py +++ b/rmgpy/molecule/group.py @@ -64,6 +64,7 @@ class GroupAtom(Vertex): `spinMultiplicity` ``list`` The allowed spin multiplicities (as short integers) `charge` ``list`` The allowed formal charges (as short integers) `label` ``str`` A string label that can be used to tag individual atoms + `lonePairs` ``list`` The number of lone electron pairs =================== =================== ==================================== Each list represents a logical OR construct, i.e. an atom will match the @@ -73,7 +74,7 @@ class GroupAtom(Vertex): order to match. """ - def __init__(self, atomType=None, radicalElectrons=None, spinMultiplicity=None, charge=None, label=''): + def __init__(self, atomType=None, radicalElectrons=None, spinMultiplicity=None, charge=None, label='', lonePairs=None): Vertex.__init__(self) self.atomType = atomType or [] for index in range(len(self.atomType)): @@ -83,6 +84,7 @@ def __init__(self, atomType=None, radicalElectrons=None, spinMultiplicity=None, self.spinMultiplicity = spinMultiplicity or [] self.charge = charge or [] self.label = label + self.lonePairs = lonePairs or [] def __reduce__(self): """ @@ -98,7 +100,7 @@ def __reduce__(self): atomType = self.atomType if atomType is not None: atomType = [a.label for a in atomType] - return (GroupAtom, (atomType, self.radicalElectrons, self.spinMultiplicity, self.charge, self.label), d) + return (GroupAtom, (atomType, self.radicalElectrons, self.spinMultiplicity, self.charge, self.label, self.lonePairs), d) def __setstate__(self, d): """ @@ -227,6 +229,34 @@ def __loseRadical(self, radical): # Set the new radical electron counts and spin multiplicities self.radicalElectrons = radicalElectrons self.spinMultiplicity = spinMultiplicity + + def __gainPair(self, pair): + """ + Update the atom group as a result of applying a GAIN_PAIR action, + where `pair` specifies the number of lone electron pairs to add. + """ + lonePairs = [] + if any([len(atomType.incrementLonePair) == 0 for atomType in self.atomType]): + raise ActionError('Unable to update GroupAtom due to GAIN_PAIR action: Unknown atom type produced from set "{0}".'.format(self.atomType)) + for lonePairs in zip(self.lonePairs): + lonePairs.append(lonePairs + pair) + # Set the new lone electron pair count + self.lonePairs = lonePairs + + def __losePair(self, pair): + """ + Update the atom group as a result of applying a LOSE_PAIR action, + where `pair` specifies the number of lone electron pairs to remove. + """ + lonePairs = [] + if any([len(atomType.decrementLonePair) == 0 for atomType in self.atomType]): + raise ActionError('Unable to update GroupAtom due to LOSE_PAIR action: Unknown atom type produced from set "{0}".'.format(self.atomType)) + for lonePairs in zip(self.lonePairs): + if lonePairs - pair < 0: + raise ActionError('Unable to update GroupAtom due to LOSE_PAIR action: Invalid lone electron pairs set "{0}".'.format(self.lonePairs)) + lonePairs.append(lonePairs - pair) + # Set the new lone electron pair count + self.lonePairs = lonePairs def applyAction(self, action): """ @@ -246,6 +276,10 @@ def applyAction(self, action): self.__gainRadical(action[2]) elif act == 'LOSE_RADICAL': self.__loseRadical(action[2]) + elif action[0].upper() == 'GAIN_PAIR': + self.__gainPair(action[2]) + elif action[0].upper() == 'LOSE_PAIR': + self.__losePair(action[2]) else: raise ActionError('Unable to update GroupAtom: Invalid action {0}".'.format(action)) @@ -639,28 +673,33 @@ def updateFingerprint(self): isomorphism checks. """ cython.declare(atom=GroupAtom, atomType=AtomType) - cython.declare(carbon=AtomType, oxygen=AtomType, sulfur=AtomType) - cython.declare(isCarbon=cython.bint, isOxygen=cython.bint, isSulfur=cython.bint, radical=cython.int) + cython.declare(carbon=AtomType, nitrogen=AtomType, oxygen=AtomType, sulfur=AtomType) + cython.declare(isCarbon=cython.bint, isNitrogen=cython.bint, isOxygen=cython.bint, isSulfur=cython.bint, radical=cython.int) - carbon = atomTypes['C'] - oxygen = atomTypes['O'] - sulfur = atomTypes['S'] + carbon = atomTypes['C'] + nitrogen = atomTypes['N'] + oxygen = atomTypes['O'] + sulfur = atomTypes['S'] - self.carbonCount = 0 - self.oxygenCount = 0 - self.sulfurCount = 0 - self.radicalCount = 0 + self.carbonCount = 0 + self.nitrogenCount = 0 + self.oxygenCount = 0 + self.sulfurCount = 0 + self.radicalCount = 0 for atom in self.vertices: if len(atom.atomType) == 1: - atomType = atom.atomType[0] - isCarbon = atomType.equivalent(carbon) - isOxygen = atomType.equivalent(oxygen) - isSulfur = atomType.equivalent(sulfur) - if isCarbon and not isOxygen and not isSulfur: + atomType = atom.atomType[0] + isCarbon = atomType.equivalent(carbon) + isNitrogen = atomType.equivalent(nitrogen) + isOxygen = atomType.equivalent(oxygen) + isSulfur = atomType.equivalent(sulfur) + if isCarbon and not isNitrogen and not isOxygen and not isSulfur: self.carbonCount += 1 - elif isOxygen and not isCarbon and not isSulfur: + elif isNitrogen and not isCarbon and not isOxygen and not isSulfur: + self.nitrogenCount += 1 + elif isOxygen and not isCarbon and not isNitrogen and not isSulfur: self.oxygenCount += 1 - elif isSulfur and not isCarbon and not isOxygen: + elif isSulfur and not isCarbon and not isNitrogen and not isOxygen: self.sulfurCount += 1 if len(atom.radicalElectrons) == 1: radical = atom.radicalElectrons[0] diff --git a/rmgpy/molecule/molecule.pxd b/rmgpy/molecule/molecule.pxd index be3bc3d06e..743419fc04 100644 --- a/rmgpy/molecule/molecule.pxd +++ b/rmgpy/molecule/molecule.pxd @@ -41,7 +41,8 @@ cdef class Atom(Vertex): cdef public str label cdef public AtomType atomType cdef public list coords - + cdef public short lonePairs + cpdef bint equivalent(self, Vertex other) except -2 cpdef bint isSpecificCaseOf(self, Vertex other) except -2 @@ -60,6 +61,16 @@ cdef class Atom(Vertex): cpdef decrementRadical(self) + cpdef setLonePairs(self, int lonePairs) + + cpdef incrementLonePairs(self) + + cpdef decrementLonePairs(self) + + cpdef updateCharge(self) + + cpdef setSpinMultiplicity(self, int spinMultiplicity) + ################################################################################ cpdef object SMILEwriter @@ -187,7 +198,15 @@ cdef class Molecule(Graph): cpdef list generateResonanceIsomers(self) cpdef list getAdjacentResonanceIsomers(self) + + cpdef list getLonePairRadicalResonanceIsomers(self) + + cpdef list getN5dd_N5tsResonanceIsomers(self) cpdef findAllDelocalizationPaths(self, Atom atom1) + + cpdef findAllDelocalizationPathsLonePairRadical(self, Atom atom1) + + cpdef findAllDelocalizationPathsN5dd_N5ts(self, Atom atom1) cpdef int calculateSymmetryNumber(self) except -1 diff --git a/rmgpy/molecule/molecule.py b/rmgpy/molecule/molecule.py index 9677ca5216..d541bf067f 100644 --- a/rmgpy/molecule/molecule.py +++ b/rmgpy/molecule/molecule.py @@ -40,7 +40,10 @@ import os import re import element as elements - +try: + import openbabel +except: + pass from rdkit import Chem from .graph import Vertex, Edge, Graph from .group import GroupAtom, GroupBond, Group, ActionError @@ -63,6 +66,7 @@ class Atom(Vertex): `charge` ``short`` The formal charge of the atom `label` ``str`` A string label that can be used to tag individual atoms `coords` + `lonePairs` ``short`` The number of lone electron pairs =================== =================== ==================================== Additionally, the ``mass``, ``number``, and ``symbol`` attributes of the @@ -70,7 +74,7 @@ class Atom(Vertex): e.g. ``atom.symbol`` instead of ``atom.element.symbol``. """ - def __init__(self, element=None, radicalElectrons=0, spinMultiplicity=1, charge=0, label=''): + def __init__(self, element=None, radicalElectrons=0, spinMultiplicity=1, charge=0, label='', lonePairs=0): Vertex.__init__(self) if isinstance(element, str): self.element = elements.__dict__[element] @@ -81,6 +85,7 @@ def __init__(self, element=None, radicalElectrons=0, spinMultiplicity=1, charge= self.charge = charge self.label = label self.atomType = None + self.lonePairs = lonePairs self.coords = list() def __str__(self): @@ -191,10 +196,11 @@ def isSpecificCaseOf(self, other): if self.radicalElectrons == radical and self.spinMultiplicity == spin: break else: return False - for charge in atom.charge: - if self.charge == charge: break - else: - return False +# until we have charges and lone pairs in the group values we neglect them here +# for charge in atom.charge: +# if self.charge == charge: break +# else: +# return False return True def copy(self): @@ -213,6 +219,7 @@ def copy(self): a.charge = self.charge a.label = self.label a.atomType = self.atomType + a.lonePairs = self.lonePairs a.coords = self.coords[:] return a @@ -236,6 +243,13 @@ def isCarbon(self): not. """ return self.element.number == 6 + + def isNitrogen(self): + """ + Return ``True`` if the atom represents a nitrogen atom or ``False`` if + not. + """ + return self.element.number == 7 def isOxygen(self): """ @@ -279,6 +293,48 @@ def decrementRadical(self): else: self.spinMultiplicity = self.radicalElectrons + 1 + def setLonePairs(self,lonePairs): + """ + Set the number of lone electron pairs. + """ + # Set the number of electron pairs + self.lonePairs = lonePairs + if self.lonePairs < 0: + raise ActionError('Unable to update Atom due to setLonePairs : Invalid lone electron pairs set "{0}".'.format(self.setLonePairs)) + self.updateCharge() + + def incrementLonePairs(self): + """ + Update the lone electron pairs pattern as a result of applying a GAIN_PAIR action. + """ + # Set the new lone electron pairs count + self.lonePairs += 1 + if self.lonePairs <= 0: + raise ActionError('Unable to update Atom due to GAIN_PAIR action: Invalid lone electron pairs set "{0}".'.format(self.lonePairs)) + self.updateCharge() + + def decrementLonePairs(self): + """ + Update the lone electron pairs pattern as a result of applying a LOSE_PAIR action. + """ + # Set the new lone electron pairs count + self.lonePairs -= 1 + if self.lonePairs < 0: + raise ActionError('Unable to update Atom due to LOSE_PAIR action: Invalid lone electron pairs set "{0}".'.format(self.lonePairs)) + self.updateCharge() + + def updateCharge(self): + valences = {'H': 1, 'C': 4, 'O': 2, 'N': 3, 'S': 2, 'Si': 4, 'He': 0, 'Ne': 0, 'Ar': 0} + orders = {'S': 1, 'D': 2, 'T': 3, 'B': 1.5} + valence = valences[self.symbol] + order = 0 + for atom2, bond in self.bonds.items(): + order += orders[bond.order] + if self.isHydrogen(): + self.charge = 2 - valence - order - self.radicalElectrons - 2*self.lonePairs + else: + self.charge = 8 - valence - order - self.radicalElectrons - 2*self.lonePairs + def applyAction(self, action): """ Update the atom pattern as a result of applying `action`, a tuple @@ -297,8 +353,23 @@ def applyAction(self, action): for i in range(action[2]): self.incrementRadical() elif act == 'LOSE_RADICAL': for i in range(abs(action[2])): self.decrementRadical() + elif action[0].upper() == 'GAIN_PAIR': + for i in range(action[2]): self.incrementLonePairs() + elif action[0].upper() == 'LOSE_PAIR': + for i in range(abs(action[2])): self.decrementLonePairs() else: raise ActionError('Unable to update Atom: Invalid action {0}".'.format(action)) + + def setSpinMultiplicity(self,spinMultiplicity): + """ + Set the spin multiplicity. + """ + # Set the spin multiplicity + self.spinMultiplicity = spinMultiplicity + if self.spinMultiplicity < 0: + raise ActionError('Unable to update Atom due to spin multiplicity : Invalid spin multiplicity set "{0}".'.format(self.spinMultiplicity)) + self.updateCharge() + ################################################################################ @@ -465,6 +536,12 @@ def applyAction(self, action): raise ActionError('Unable to update GroupBond: Invalid action {0}.'.format(action)) ################################################################################# +try: + SMILEwriter = openbabel.OBConversion() + SMILEwriter.SetOutFormat('smi') + SMILEwriter.SetOptions("i",SMILEwriter.OUTOPTIONS) # turn off isomer and stereochemistry information (the @ signs!) +except: + pass class Molecule(Graph): """ @@ -826,7 +903,7 @@ def isSubgraphIsomorphic(self, other, initialMap=None): be a :class:`Group` object, or a :class:`TypeError` is raised. """ cython.declare(group=Group, atom=Atom) - cython.declare(carbonCount=cython.short, oxygenCount=cython.short, sulfurCount=cython.short, radicalCount=cython.short) + cython.declare(carbonCount=cython.short, nitrogenCount=cython.short, oxygenCount=cython.short, sulfurCount=cython.short, radicalCount=cython.short) # It only makes sense to compare a Molecule to a Group for subgraph # isomorphism, so raise an exception if this is not what was requested @@ -835,10 +912,12 @@ def isSubgraphIsomorphic(self, other, initialMap=None): group = other # Count the number of carbons, oxygens, and radicals in the molecule - carbonCount = 0; oxygenCount = 0; sulfurCount = 0; radicalCount = 0 + carbonCount = 0; nitrogenCount = 0; oxygenCount = 0; sulfurCount = 0; radicalCount = 0 for atom in self.vertices: if atom.element.symbol == 'C': carbonCount += 1 + elif atom.element.symbol == 'N': + nitrogenCount += 1 elif atom.element.symbol == 'O': oxygenCount += 1 elif atom.element.symbol == 'S': @@ -849,6 +928,7 @@ def isSubgraphIsomorphic(self, other, initialMap=None): # needing to perform the full isomorphism check if (radicalCount < group.radicalCount or carbonCount < group.carbonCount or + nitrogenCount < group.nitrogenCount or oxygenCount < group.oxygenCount or sulfurCount < group.sulfurCount): return False @@ -1037,11 +1117,26 @@ def toInChI(self): Convert a molecular structure to an InChI string. Uses `RDKit `_ to perform the conversion. Perceives aromaticity. - """ - if not Chem.inchi.INCHI_AVAILABLE: - return "RDKitInstalledWithoutInChI" - rdkitmol = self.toRDKitMol() - return Chem.inchi.MolToInchi(rdkitmol) + + or + + Convert a molecular structure to an InChI string. Uses + `OpenBabel `_ to perform the conversion. + """ + try: + if not Chem.inchi.INCHI_AVAILABLE: + return "RDKitInstalledWithoutInChI" + rdkitmol = self.toRDKitMol() + return Chem.inchi.MolToInchi(rdkitmol) + except: + pass + + # This version does not write a warning to stderr if stereochemistry is undefined + obmol = self.toOBMol() + obConversion = openbabel.OBConversion() + obConversion.SetOutFormat('inchi') + obConversion.SetOptions('w', openbabel.OBConversion.OUTOPTIONS) + return obConversion.WriteString(obmol).strip() def toAugmentedInChI(self): """ @@ -1059,16 +1154,36 @@ def toAugmentedInChI(self): def toInChIKey(self): """ + Convert a molecular structure to an InChI Key string. Uses + `OpenBabel `_ to perform the conversion. + + or + Convert a molecular structure to an InChI Key string. Uses `RDKit `_ to perform the conversion. Removes check-sum dash (-) and character so that only the 14 + 9 characters remain. """ - if not Chem.inchi.INCHI_AVAILABLE: - return "RDKitInstalledWithoutInChI" - inchi = self.toInChI() - return Chem.inchi.InchiToInchiKey(inchi)[:-2] + try: + if not Chem.inchi.INCHI_AVAILABLE: + return "RDKitInstalledWithoutInChI" + inchi = self.toInChI() + return Chem.inchi.InchiToInchiKey(inchi)[:-2] + except: + pass + + import openbabel + +# for atom in self.vertices: + # if atom.isNitrogen(): + # This version does not write a warning to stderr if stereochemistry is undefined + obmol = self.toOBMol() + obConversion = openbabel.OBConversion() + obConversion.SetOutFormat('inchi') + obConversion.SetOptions('w', openbabel.OBConversion.OUTOPTIONS) + obConversion.SetOptions('K', openbabel.OBConversion.OUTOPTIONS) + return obConversion.WriteString(obmol).strip()[:-2] def toAugmentedInChIKey(self): """ @@ -1096,13 +1211,60 @@ def toSMARTS(self): def toSMILES(self): """ + Convert a molecular structure to an SMILES string. Uses + `OpenBabel `_ to perform the conversion. + + or + Convert a molecular structure to a canonical SMILES string. Uses `RDKit `_ to perform the conversion. Perceives aromaticity and removes Hydrogen atoms. """ + + for atom in self.vertices: + if atom.isNitrogen(): + mol = self.toOBMol() + if self.getFormula() == 'H2': + return '[H][H]' + elif self.getFormula() == 'H': + return '[H]' + return SMILEwriter.WriteString(mol).strip() + rdkitmol = self.toRDKitMol() return Chem.MolToSmiles(rdkitmol) + + def toOBMol(self): + """ + Convert a molecular structure to an OpenBabel OBMol object. Uses + `OpenBabel `_ to perform the conversion. + """ + cython.declare(atom=Atom, atom1=Atom, bonds=dict, atom2=Atom, bond=Bond) + cython.declare(index1=cython.int, index2=cython.int, order=cython.int) + + # Sort the atoms before converting to ensure output is consistent + # between different runs + self.sortAtoms() + + atoms = self.vertices + + obmol = openbabel.OBMol() + for atom in atoms: + a = obmol.NewAtom() + a.SetAtomicNum(atom.number) + a.SetFormalCharge(atom.charge) + orders = {'S': 1, 'D': 2, 'T': 3, 'B': 5} + for atom1 in self.vertices: + for atom2, bond in atom1.edges.iteritems(): + index1 = atoms.index(atom1) + index2 = atoms.index(atom2) + if index1 < index2: + order = orders[bond.order] + obmol.AddBond(index1+1, index2+1, order) + + obmol.AssignSpinMultiplicity(True) + + return obmol def toRDKitMol(self, removeHs=True, returnMapping=False): """ @@ -1294,21 +1456,24 @@ def generateResonanceIsomers(self): isomers = [self] # Iterate over resonance isomers - if self.isRadical(): - index = 0 - while index < len(isomers): - isomer = isomers[index] - newIsomers = isomer.getAdjacentResonanceIsomers() - for newIsomer in newIsomers: - newIsomer.updateAtomTypes() - # Append to isomer list if unique - for isom in isomers: - if isom.isIsomorphic(newIsomer): - break - else: - isomers.append(newIsomer) - # Move to next resonance isomer - index += 1 + index = 0 + while index < len(isomers): + isomer = isomers[index] + + newIsomers = isomer.getAdjacentResonanceIsomers() + newIsomers += isomer.getLonePairRadicalResonanceIsomers() + newIsomers += isomer.getN5dd_N5tsResonanceIsomers() + for newIsomer in newIsomers: + newIsomer.updateAtomTypes() + # Append to isomer list if unique + for isom in isomers: + if isom.isIsomorphic(newIsomer): + break + else: + isomers.append(newIsomer) + + # Move to next resonance isomer + index += 1 return isomers @@ -1353,6 +1518,133 @@ def getAdjacentResonanceIsomers(self): isomers.append(isomer) return isomers + + def getLonePairRadicalResonanceIsomers(self): + """ + Generate all of the resonance isomers formed by lone electron pair - radical shifts. + """ + cython.declare(isomers=list, paths=list, index=cython.int, isomer=Molecule) + cython.declare(atom=Atom, atom1=Atom, atom2=Atom) + cython.declare(v1=Vertex, v2=Vertex) + + isomers = [] + + # Radicals + if self.isRadical(): + # Iterate over radicals in structure + for atom in self.vertices: + paths = self.findAllDelocalizationPathsLonePairRadical(atom) + for atom1, atom2 in paths: + # Adjust to (potentially) new resonance isomer + atom1.decrementRadical() + atom1.incrementLonePairs() + atom1.updateCharge() + atom2.incrementRadical() + atom2.decrementLonePairs() + atom2.updateCharge() + # Make a copy of isomer + isomer = self.copy(deep=True) + # Also copy the connectivity values, since they are the same + # for all resonance forms + for index in range(len(self.vertices)): + v1 = self.vertices[index] + v2 = isomer.vertices[index] + v2.connectivity1 = v1.connectivity1 + v2.connectivity2 = v1.connectivity2 + v2.connectivity3 = v1.connectivity3 + v2.sortingLabel = v1.sortingLabel + # Restore current isomer + atom1.incrementRadical() + atom1.decrementLonePairs() + atom1.updateCharge() + atom2.decrementRadical() + atom2.incrementLonePairs() + atom2.updateCharge() + # Append to isomer list if unique + isomers.append(isomer) + + return isomers + + def getN5dd_N5tsResonanceIsomers(self): + """ + Generate all of the resonance isomers formed by shifts between N5dd and N5ts. + """ + cython.declare(isomers=list, paths=list, index=cython.int, isomer=Molecule) + cython.declare(atom=Atom, atom1=Atom, atom2=Atom, atom3=Atom) + cython.declare(bond12=Bond, bond13=Bond) + cython.declare(v1=Vertex, v2=Vertex) + + isomers = [] + + # Iterate over nitrogen atoms in structure + for atom in self.vertices: + paths = self.findAllDelocalizationPathsN5dd_N5ts(atom) + for atom1, atom2, atom3, bond12, bond13, direction in paths: + # from N5dd to N5ts + if direction == 1: + # Adjust to (potentially) new resonance isomer + bond12.decrementOrder() + bond13.incrementOrder() + atom2.incrementLonePairs() + atom3.decrementLonePairs() + atom1.updateCharge() + atom2.updateCharge() + atom3.updateCharge() + # Make a copy of isomer + isomer = self.copy(deep=True) + # Also copy the connectivity values, since they are the same + # for all resonance forms + for index in range(len(self.vertices)): + v1 = self.vertices[index] + v2 = isomer.vertices[index] + v2.connectivity1 = v1.connectivity1 + v2.connectivity2 = v1.connectivity2 + v2.connectivity3 = v1.connectivity3 + v2.sortingLabel = v1.sortingLabel + # Restore current isomer + bond12.incrementOrder() + bond13.decrementOrder() + atom2.decrementLonePairs() + atom3.incrementLonePairs() + atom1.updateCharge() + atom2.updateCharge() + atom3.updateCharge() + # Append to isomer list if unique + isomers.append(isomer) + + # from N5ts to N5dd + if direction == 2: + # Adjust to (potentially) new resonance isomer + bond12.decrementOrder() + bond13.incrementOrder() + atom2.incrementLonePairs() + atom3.decrementLonePairs() + atom1.updateCharge() + atom2.updateCharge() + atom3.updateCharge() + # Make a copy of isomer + isomer = self.copy(deep=True) + # Also copy the connectivity values, since they are the same + # for all resonance forms + for index in range(len(self.vertices)): + v1 = self.vertices[index] + v2 = isomer.vertices[index] + v2.connectivity1 = v1.connectivity1 + v2.connectivity2 = v1.connectivity2 + v2.connectivity3 = v1.connectivity3 + v2.sortingLabel = v1.sortingLabel + # Restore current isomer + bond12.incrementOrder() + bond13.decrementOrder() + atom2.decrementLonePairs() + atom3.incrementLonePairs() + atom1.updateCharge() + atom2.updateCharge() + atom3.updateCharge() + # Append to isomer list if unique + isomers.append(isomer) + + return isomers def findAllDelocalizationPaths(self, atom1): """ @@ -1370,12 +1662,76 @@ def findAllDelocalizationPaths(self, atom1): paths = [] for atom2, bond12 in atom1.edges.items(): # Vinyl bond must be capable of gaining an order - if bond12.isSingle() or bond12.isDouble(): + if (bond12.isSingle() or bond12.isDouble()) and atom1.radicalElectrons == 1: for atom3, bond23 in atom2.edges.items(): # Allyl bond must be capable of losing an order without breaking if atom1 is not atom3 and (bond23.isDouble() or bond23.isTriple()): paths.append([atom1, atom2, atom3, bond12, bond23]) return paths + + def findAllDelocalizationPathsLonePairRadical(self, atom1): + """ + Find all the delocalization paths of lone electron pairs next to the radical center indicated + by `atom1`. Used to generate resonance isomers. + """ + cython.declare(paths=list) + cython.declare(atom2=Atom, bond12=Bond) + + # No paths if atom1 is not a radical + if atom1.radicalElectrons <= 0: + return [] + + # In a first step we only consider nitrogen and oxygen atoms as possible radical centers + if not ((atom1.lonePairs == 0 and atom1.isNitrogen()) or(atom1.lonePairs == 2 and atom1.isOxygen())): + return [] + + # Find all delocalization paths + paths = [] + for atom2, bond12 in atom1.edges.items(): + # Only single bonds are considered + if bond12.isSingle(): + # Neighboring atom must posses a lone electron pair to loose it + if ((atom2.lonePairs == 1 and atom2.isNitrogen()) or (atom2.lonePairs == 3 and atom2.isOxygen())) and (atom2.radicalElectrons == 0): + paths.append([atom1, atom2]) + + return paths + + def findAllDelocalizationPathsN5dd_N5ts(self, atom1): + """ + Find all the resonance structures of nitrogen atoms with two double bonds (N5dd) + and nitrogen atoms with one triple and one single bond (N5ts) + """ + cython.declare(paths=list) + cython.declare(atom2=Atom, bond12=Bond) + + # No paths if atom1 is not nitrogen + if not (atom1.isNitrogen()): + return [] + + # Find all delocalization paths + paths = [] + index_atom_2 = 0 + index_atom_3 = 0 + + for atom2, bond12 in atom1.edges.items(): + index_atom_2 = index_atom_2 + 1 + # Only double bonds are considered + if bond12.isDouble(): + for atom3, bond13 in atom1.edges.items(): + index_atom_3 = index_atom_3 + 1 + # Only double bonds are considered, at the moment we only consider non-radical nitrogen and oxygen atoms + if (bond13.isDouble() and atom3.radicalElectrons == 0 and atom3.lonePairs > 0 and not atom3.isOxygen() and not atom3.isCarbon() and (index_atom_2 != index_atom_3)): + paths.append([atom1, atom2, atom3, bond12, bond13, 1]) + + for atom2, bond12 in atom1.edges.items(): + # Only triple bonds are considered + if bond12.isTriple(): + for atom3, bond13 in atom1.edges.items(): + # Only single bonds are considered, at the moment we only consider negatively charged nitrogen and oxygen + if (bond13.isSingle() and ((atom3.isNitrogen() and atom3.lonePairs >= 2) or (atom3.isOxygen() and atom3.lonePairs >= 3))): + paths.append([atom1, atom2, atom3, bond12, bond13, 2]) + + return paths def getURL(self): """ @@ -1384,7 +1740,52 @@ def getURL(self): # eg. http://dev.rmg.mit.edu/database/kinetics/reaction/reactant1=1%20C%200%20%7B2,S%7D;2%20O%200%20%7B1,S%7D;__reactant2=1%20C%202T;__product1=1%20C%201;__product2=1%20C%200%20%7B2,S%7D;2%20O%201%20%7B1,S%7D; url = "http://rmg.mit.edu/database/molecule/" - adjlist = self.toAdjacencyList(removeH=True) + adjlist = self.toAdjacencyList(removeH=False) url += "{0}".format(re.sub('\s+', '%20', adjlist.replace('\n', ';'))) return url.strip('_') + + def isBiradicalSinglet(self): + """ + Return ``True`` if the molecule is a 1-centered biradical in singlet state, + or ``False`` otherwise. + """ + cython.declare(atom=Atom) + for atom in self.vertices: + if atom.radicalElectrons == 2: + if atom.spinMultiplicity == 1: + return True + return False + + def isBiradicalTriplet(self): + """ + Return ``True`` if the molecule is a 1-centered biradical in triplet state, + or ``False`` otherwise. + """ + cython.declare(atom=Atom) + for atom in self.vertices: + if atom.radicalElectrons == 2: + if atom.spinMultiplicity == 3: + return True + return False + + def changeTripletSinglet(self): + """ + If the molecule is a 1-centered biradical in triplet state, + change it to singlet state. + """ + cython.declare(atom=Atom) + for atom in self.vertices: + if atom.radicalElectrons == 2: + if atom.spinMultiplicity == 3: + atom.spinMultiplicity = 1 + + def getRadicalAtoms(self): + """ + Return the atoms in the molecule that have unpaired electrons. + """ + radicalAtomsList = [] + for atom in self.vertices: + if atom.radicalElectrons > 0: + radicalAtomsList.append(atom) + return radicalAtomsList diff --git a/rmgpy/molecule/moleculeTest.py b/rmgpy/molecule/moleculeTest.py index 0acd8f1e6e..3116d1e945 100644 --- a/rmgpy/molecule/moleculeTest.py +++ b/rmgpy/molecule/moleculeTest.py @@ -469,9 +469,12 @@ class TestMolecule(unittest.TestCase): def setUp(self): self.adjlist = """ -1 *2 C 1 {2,D} {3,S} -2 *1 O 0 {1,D} -3 C 0 {1,S} +1 *2 C 1 0 {2,D} {3,S} +2 *1 O 0 2 {1,D} +3 C 0 0 {1,S} {4,S} {5,S} {6,S} +4 H 0 0 {3,S} +5 H 0 0 {3,S} +6 H 0 0 {3,S} """ self.molecule = Molecule().fromAdjacencyList(self.adjlist) @@ -576,7 +579,7 @@ def testToAdjacencyList(self): """ Test the Molecule.toAdjacencyList() method. """ - adjlist = self.molecule.toAdjacencyList(removeH=True) + adjlist = self.molecule.toAdjacencyList(removeH=False) self.assertEqual(adjlist.strip(), self.adjlist.strip()) def testIsomorphism(self): @@ -654,16 +657,22 @@ def testSubgraphIsomorphismAgain(self): def testSubgraphIsomorphismManyLabels(self): molecule = Molecule() # specific case (species) molecule.fromAdjacencyList(""" -1 *1 C 1 {2,S} {3,S} -2 C 0 {1,S} {3,S} -3 C 0 {1,S} {2,S} +1 *1 C 1 {2,S} {3,S} {4,S} +2 C 0 {1,S} {3,S} {5,S} {6,S} +3 C 0 {1,S} {2,S} {7,S} {8,S} +4 H 0 {1,S} +5 H 0 {2,S} +6 H 0 {2,S} +7 H 0 {3,S} +8 H 0 {3,S} """) + print molecule.toAdjacencyList() group = Group() # general case (functional group) group.fromAdjacencyList(""" -1 *1 C 1 {2,S}, {3,S} -2 R 0 {1,S} -3 R 0 {1,S} +1 *1 C 1 {2,S}, {3,S} +2 R!H 0 {1,S} +3 R!H 0 {1,S} """) labeled1 = molecule.getLabeledAtoms() @@ -674,7 +683,7 @@ def testSubgraphIsomorphismManyLabels(self): self.assertTrue(molecule.isSubgraphIsomorphic(group, initialMap)) mapping = molecule.findSubgraphIsomorphisms(group, initialMap) - self.assertEqual(len(mapping), 1) + self.assertEqual(len(mapping), 2) for map in mapping: self.assertTrue(len(map) == min(len(molecule.atoms), len(group.atoms))) for key, value in map.iteritems(): @@ -686,12 +695,21 @@ def testAdjacencyList(self): Check the adjacency list read/write functions for a full molecule. """ molecule1 = Molecule().fromAdjacencyList(""" - 1 C 0 {2,D} - 2 C 0 {1,D} {3,S} - 3 C 0 {2,S} {4,D} - 4 C 0 {3,D} {5,S} - 5 C 1 {4,S} {6,S} - 6 C 0 {5,S} + 1 C 0 {2,D} {7,S} {8,S} + 2 C 0 {1,D} {3,S} {9,S} + 3 C 0 {2,S} {4,D} {10,S} + 4 C 0 {3,D} {5,S} {11,S} + 5 C 1 {4,S} {6,S} {12,S} + 6 C 0 {5,S} {13,S} {14,S} {15,S} + 7 H 0 {1,S} + 8 H 0 {1,S} + 9 H 0 {2,S} + 10 H 0 {3,S} + 11 H 0 {4,S} + 12 H 0 {5,S} + 13 H 0 {6,S} + 14 H 0 {6,S} + 15 H 0 {6,S} """) molecule2 = Molecule().fromSMILES('C=CC=C[CH]C') self.assertTrue(molecule1.isIsomorphic(molecule2)) diff --git a/rmgpy/molecule/symmetry.py b/rmgpy/molecule/symmetry.py index 74b31024e1..c9dd35e0f2 100644 --- a/rmgpy/molecule/symmetry.py +++ b/rmgpy/molecule/symmetry.py @@ -84,6 +84,12 @@ def calculateAtomSymmetryNumber(molecule, atom): elif count == [2, 2]: symmetryNumber *= 2 elif count == [2, 1, 1]: symmetryNumber *= 1 elif count == [1, 1, 1, 1]: symmetryNumber *= 1 + elif single == 3: + # Three single bonds + if count == [3]: symmetryNumber *= 3 + elif count == [2, 1]: symmetryNumber *= 1 + elif count == [1, 1, 1]: symmetryNumber *= 1 + elif single == 2: # Two single bonds if count == [2]: symmetryNumber *= 2 @@ -99,8 +105,14 @@ def calculateAtomSymmetryNumber(molecule, atom): elif atom.radicalElectrons == 2: if single == 2: # Two single bonds - if count == [2]: symmetryNumber *= 2 - + if count == [2]: + symmetryNumber *= 2 + + if atom.isNitrogen(): + for groupN in groups: + if groupN.toSMILES() == "[N+](=O)[O-]": + symmetryNumber *= 2 + return symmetryNumber ################################################################################ @@ -276,7 +288,6 @@ def calculateAxisSymmetryNumber(molecule): symmetry_broken=False end_fragments_to_remove = [] for fragment in end_fragments: # a fragment is one end of the axis - # remove the atom that was at the end of the axis and split what's left into groups terminalAtom = None for atom in terminalAtoms: @@ -298,8 +309,11 @@ def calculateAxisSymmetryNumber(molecule): end_fragments_to_remove.append(fragment) continue # next end fragment elif len(groups)==1 and terminalAtom.radicalElectrons == 0: - end_fragments_to_remove.append(fragment) - continue # next end fragment + if terminalAtom.atomType.label == 'N3d': + symmetry_broken = True + else: + end_fragments_to_remove.append(fragment) + continue # next end fragment elif len(groups)==1 and terminalAtom.radicalElectrons != 0: symmetry_broken = True elif len(groups)==2: diff --git a/rmgpy/qm/molecule.py b/rmgpy/qm/molecule.py index 97e8f98c2f..9d45bd7597 100644 --- a/rmgpy/qm/molecule.py +++ b/rmgpy/qm/molecule.py @@ -250,7 +250,7 @@ def saveThermoData(self): resultFile.write("thermoData = {0!r}\n".format(self.thermo)) resultFile.write("pointGroup = {0!r}\n".format(self.pointGroup)) resultFile.write("qmData = {0!r}\n".format(self.qmData)) - resultFile.write('adjacencyList = """\n{0!s}"""\n'.format(self.molecule.toAdjacencyList(removeH=True))) + resultFile.write('adjacencyList = """\n{0!s}"""\n'.format(self.molecule.toAdjacencyList(removeH=False))) def loadThermoData(self): """ diff --git a/rmgpy/qm/mopac.py b/rmgpy/qm/mopac.py index 04ded33b91..176720ec24 100644 --- a/rmgpy/qm/mopac.py +++ b/rmgpy/qm/mopac.py @@ -197,6 +197,10 @@ def generateQMData(self): """ Calculate the QM data and return a QMData object, or None if it fails. """ + for atom in self.molecule.vertices: + if atom.atomType.label == 'N5s' or atom.atomType.label == 'N5d' or atom.atomType.label =='N5dd' or atom.atomType.label == 'N5t' or atom.atomType.label == 'N5b': + return None + if self.verifyOutputFile(): logging.info("Found a successful output file already; using that.") source = "QM MOPAC result file found from previous run." diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index cafb2e867e..5edb29fb98 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -180,10 +180,10 @@ def getURL(self): url = "http://rmg.mit.edu/database/kinetics/reaction/" for i,species in enumerate(self.reactants): - adjlist = species.molecule[0].toAdjacencyList(removeH=True) + adjlist = species.molecule[0].toAdjacencyList(removeH=False) url += "reactant{0}={1}__".format(i+1, re.sub('\s+', '%20', adjlist.replace('\n', ';'))) for i,species in enumerate(self.products): - adjlist = species.molecule[0].toAdjacencyList(removeH=True) + adjlist = species.molecule[0].toAdjacencyList(removeH=False) url += "product{0}={1}__".format(i+1, re.sub('\s+', '%20', adjlist.replace('\n', ';'))) return url.strip('_') diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index b0b33fbaed..1d2d1ff6ce 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -335,6 +335,9 @@ def initialize(self, args): # DON'T generate any more reactions for the seed species at this time for seedMechanism in self.seedMechanisms: self.reactionModel.addSeedMechanismToCore(seedMechanism, react=False) + + for spec in self.reactionModel.core.species: + spec.generateTransportData(self.database) # Reaction libraries: add species and reactions from reaction library to the edge so # that RMG can find them if their rates are large enough @@ -707,6 +710,20 @@ def saveChemkinFiles(self): os.unlink(latest_chemkin_path) shutil.copy2(this_chemkin_path,latest_chemkin_path) + def saveChemkinFileEdge(self): + """ + Save the current reaction edge to a Chemkin file. + """ + logging.info('Saving current edge to Chemkin file...') + this_chemkin_path = os.path.join(self.outputDirectory, 'chemkin', 'chem_edge%04i.inp' % len(self.reactionModel.core.species)) + latest_chemkin_path = os.path.join(self.outputDirectory, 'chemkin','chem_edge.inp') + latest_chemkin_verbose_path = os.path.join(self.outputDirectory, 'chemkin', 'chem_edge_annotated.inp') + latest_dictionary_path = os.path.join(self.outputDirectory, 'chemkin','species_edge_dictionary.txt') + self.reactionModel.saveChemkinFileEdge(this_chemkin_path, latest_chemkin_verbose_path, latest_dictionary_path) + if os.path.exists(latest_chemkin_path): + os.unlink(latest_chemkin_path) + shutil.copy2(this_chemkin_path,latest_chemkin_path) + def saveRestartFile(self, path, reactionModel, delay=0): """ Save a restart file to `path` on disk containing the contents of the diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index c1f1c5b1ac..964eb964ff 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -144,7 +144,7 @@ def generateThermoData(self, database, thermoClass=NASA, quantumMechanics=None): with open('thermoHBIcheck.txt','a') as f: f.write('// {0!r}\n'.format(thermo0).replace('),','),\n// ')) f.write('{0}\n'.format(molecule.toSMILES())) - f.write('{0}\n\n'.format(molecule.toAdjacencyList(removeH=True))) + f.write('{0}\n\n'.format(molecule.toAdjacencyList(removeH=False))) else: # Not too many radicals: do a direct calculation. thermo0 = quantumMechanics.getThermoData(molecule) # returns None if it fails @@ -1659,3 +1659,16 @@ def saveChemkinFile(self, path, verbose_path, dictionaryPath=None, transportPath saveSpeciesDictionary(dictionaryPath, speciesList) if transportPath: saveTransportFile(transportPath, speciesList) + + def saveChemkinFileEdge(self, path, verbose_path, dictionaryPath=None): + """ + Save a Chemkin file for the current model edge as well as any desired output + species and reactions to `path`. + """ + from rmgpy.chemkin import saveChemkinFile, saveSpeciesDictionaryEdge + speciesList = self.edge.species + self.outputSpeciesList + rxnList = self.edge.reactions + self.outputReactionList + saveChemkinFile(path, speciesList, rxnList, verbose = False) + saveChemkinFile(verbose_path, speciesList, rxnList, verbose = True) + if dictionaryPath: + saveSpeciesDictionaryEdge(dictionaryPath, speciesList) diff --git a/rmgpy/species.py b/rmgpy/species.py index 03797d3eb4..2ec3caf696 100644 --- a/rmgpy/species.py +++ b/rmgpy/species.py @@ -231,7 +231,7 @@ def toAdjacencyList(self): """ Return a string containing each of the molecules' adjacency lists. """ - output = '\n\n'.join([m.toAdjacencyList(label=self.label, removeH=True) for m in self.molecule]) + output = '\n\n'.join([m.toAdjacencyList(label=self.label, removeH=False) for m in self.molecule]) return output def hasStatMech(self): diff --git a/rmgpy/speciesTest.py b/rmgpy/speciesTest.py index 8ea6c69409..253249932c 100644 --- a/rmgpy/speciesTest.py +++ b/rmgpy/speciesTest.py @@ -105,8 +105,9 @@ def testToAdjacencyList(self): """ Test that toAdjacencyList() works as expected. """ + print self.species.toAdjacencyList() string = self.species.toAdjacencyList() - self.assertTrue(string.startswith(self.species.molecule[0].toAdjacencyList(label=self.species.label,removeH=True))) + self.assertTrue(string.startswith(self.species.molecule[0].toAdjacencyList(label=self.species.label,removeH=False))) ################################################################################ diff --git a/unittest/qm/data/QMfiles/3DMolfiles/WTARULDDTDQWMU-UHFFFAOYAW.mol b/unittest/qm/data/QMfiles/3DMolfiles/WTARULDDTDQWMU-UHFFFAOYAW.mol index ca76e3a869..ef39e1ebd0 100644 --- a/unittest/qm/data/QMfiles/3DMolfiles/WTARULDDTDQWMU-UHFFFAOYAW.mol +++ b/unittest/qm/data/QMfiles/3DMolfiles/WTARULDDTDQWMU-UHFFFAOYAW.mol @@ -1,58 +1,58 @@ -test - OpenBabel11021110093D - - 26 27 0 0 0 0 0 0 0 0999 V2000 - 1.0223 0.1865 -0.0679 C 0 0 0 0 0 0 0 0 0 0 0 0 - 0.3378 -0.6762 -1.1893 C 0 0 0 0 0 0 0 0 0 0 0 0 - -0.2765 1.0713 -0.1380 C 0 0 0 0 0 0 0 0 0 0 0 0 - -0.4762 0.5805 -1.6001 C 0 0 0 0 0 0 0 0 0 0 0 0 - -0.6296 -1.6795 -0.5756 C 0 0 0 0 0 0 0 0 0 0 0 0 - -1.6757 -0.9966 0.3243 C 0 0 0 0 0 0 0 0 0 0 0 0 - 2.2476 0.9233 -0.5823 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.3722 -0.4757 1.2450 C 0 0 0 0 0 0 0 0 0 0 0 0 - -1.3489 0.4278 0.6870 C 0 0 0 0 0 0 0 0 0 0 0 0 - -1.9851 1.0732 1.6628 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.0099 -1.1217 -1.9432 H 0 0 0 0 0 0 0 0 0 0 0 0 - -0.1537 2.1548 0.0339 H 0 0 0 0 0 0 0 0 0 0 0 0 - 0.0119 1.2123 -2.3582 H 0 0 0 0 0 0 0 0 0 0 0 0 - -1.5116 0.4118 -1.9292 H 0 0 0 0 0 0 0 0 0 0 0 0 - -0.0600 -2.4358 -0.0009 H 0 0 0 0 0 0 0 0 0 0 0 0 - -1.1411 -2.2391 -1.3820 H 0 0 0 0 0 0 0 0 0 0 0 0 - -2.6609 -0.9961 -0.1843 H 0 0 0 0 0 0 0 0 0 0 0 0 - -1.8203 -1.5901 1.2486 H 0 0 0 0 0 0 0 0 0 0 0 0 - 3.0976 0.2374 -0.6946 H 0 0 0 0 0 0 0 0 0 0 0 0 - 2.5482 1.7192 0.1120 H 0 0 0 0 0 0 0 0 0 0 0 0 - 2.0733 1.3917 -1.5607 H 0 0 0 0 0 0 0 0 0 0 0 0 - 2.0928 -1.2895 1.0881 H 0 0 0 0 0 0 0 0 0 0 0 0 - 0.4981 -0.9029 1.7554 H 0 0 0 0 0 0 0 0 0 0 0 0 - 1.8271 0.2453 1.9377 H 0 0 0 0 0 0 0 0 0 0 0 0 - -2.7614 0.6094 2.2639 H 0 0 0 0 0 0 0 0 0 0 0 0 - -1.7692 2.1056 1.9211 H 0 0 0 0 0 0 0 0 0 0 0 0 - 1 8 1 0 0 0 0 - 2 5 1 0 0 0 0 - 2 1 1 0 0 0 0 - 3 1 1 0 0 0 0 - 3 12 1 0 0 0 0 - 3 9 1 0 0 0 0 - 4 2 1 0 0 0 0 - 4 3 1 0 0 0 0 - 5 15 1 0 0 0 0 - 5 6 1 0 0 0 0 - 6 9 1 0 0 0 0 - 6 18 1 0 0 0 0 - 7 1 1 0 0 0 0 - 7 20 1 0 0 0 0 - 8 23 1 0 0 0 0 - 8 24 1 0 0 0 0 - 9 10 2 0 0 0 0 - 10 26 1 0 0 0 0 - 10 25 1 0 0 0 0 - 11 2 1 0 0 0 0 - 13 4 1 0 0 0 0 - 14 4 1 0 0 0 0 - 16 5 1 0 0 0 0 - 17 6 1 0 0 0 0 - 19 7 1 0 0 0 0 - 21 7 1 0 0 0 0 - 22 8 1 0 0 0 0 -M END +test + OpenBabel11021110093D + + 26 27 0 0 0 0 0 0 0 0999 V2000 + 1.0223 0.1865 -0.0679 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.3378 -0.6762 -1.1893 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.2765 1.0713 -0.1380 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.4762 0.5805 -1.6001 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.6296 -1.6795 -0.5756 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.6757 -0.9966 0.3243 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2476 0.9233 -0.5823 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.3722 -0.4757 1.2450 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3489 0.4278 0.6870 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.9851 1.0732 1.6628 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.0099 -1.1217 -1.9432 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.1537 2.1548 0.0339 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0119 1.2123 -2.3582 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.5116 0.4118 -1.9292 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0600 -2.4358 -0.0009 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1411 -2.2391 -1.3820 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.6609 -0.9961 -0.1843 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.8203 -1.5901 1.2486 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.0976 0.2374 -0.6946 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5482 1.7192 0.1120 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0733 1.3917 -1.5607 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0928 -1.2895 1.0881 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.4981 -0.9029 1.7554 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.8271 0.2453 1.9377 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.7614 0.6094 2.2639 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.7692 2.1056 1.9211 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 8 1 0 0 0 0 + 2 5 1 0 0 0 0 + 2 1 1 0 0 0 0 + 3 1 1 0 0 0 0 + 3 12 1 0 0 0 0 + 3 9 1 0 0 0 0 + 4 2 1 0 0 0 0 + 4 3 1 0 0 0 0 + 5 15 1 0 0 0 0 + 5 6 1 0 0 0 0 + 6 9 1 0 0 0 0 + 6 18 1 0 0 0 0 + 7 1 1 0 0 0 0 + 7 20 1 0 0 0 0 + 8 23 1 0 0 0 0 + 8 24 1 0 0 0 0 + 9 10 2 0 0 0 0 + 10 26 1 0 0 0 0 + 10 25 1 0 0 0 0 + 11 2 1 0 0 0 0 + 13 4 1 0 0 0 0 + 14 4 1 0 0 0 0 + 16 5 1 0 0 0 0 + 17 6 1 0 0 0 0 + 19 7 1 0 0 0 0 + 21 7 1 0 0 0 0 + 22 8 1 0 0 0 0 +M END diff --git a/unittest/qm/data/QMfiles/MOPAC/UMRZSTCPUPJPOJ-UHFFFAOYAR.mop b/unittest/qm/data/QMfiles/MOPAC/UMRZSTCPUPJPOJ-UHFFFAOYAR.mop index c9857feba4..aa1b1239be 100644 --- a/unittest/qm/data/QMfiles/MOPAC/UMRZSTCPUPJPOJ-UHFFFAOYAR.mop +++ b/unittest/qm/data/QMfiles/MOPAC/UMRZSTCPUPJPOJ-UHFFFAOYAR.mop @@ -1,24 +1,24 @@ -pm3 precise nosym -InChI=1/C7H12/c1-2-7-4-3-6(1)5-7/h6-7H,1-5H2 - -C 0.32300 1 1.03080 1 0.40880 1 -C -0.25790 1 -1.09130 1 0.27690 1 -C 0.13070 1 -0.12140 1 1.37700 1 -C -1.04980 1 1.10010 1 -0.27560 1 -C -1.45360 1 -0.37480 1 -0.36730 1 -C 0.96160 1 -1.01830 1 -0.65330 1 -C 1.36540 1 0.45670 1 -0.56160 1 -H 0.63470 1 1.99190 1 0.87850 1 -H -0.49220 1 -2.12420 1 0.62260 1 -H -0.66960 1 0.05040 1 2.13790 1 -H 1.05610 1 -0.40930 1 1.93350 1 -H -1.00780 1 1.59430 1 -1.27410 1 -H -1.77090 1 1.66970 1 0.35700 1 -H -2.38350 1 -0.56810 1 0.21790 1 -H -1.63680 1 -0.70310 1 -1.41690 1 -H 0.72580 1 -1.33250 1 -1.69670 1 -H 1.77870 1 -1.67690 1 -0.27500 1 -H 1.35480 1 0.96490 1 -1.55390 1 -H 2.39130 1 0.56080 1 -0.13590 1 - +pm3 precise nosym +InChI=1/C7H12/c1-2-7-4-3-6(1)5-7/h6-7H,1-5H2 + +C 0.32300 1 1.03080 1 0.40880 1 +C -0.25790 1 -1.09130 1 0.27690 1 +C 0.13070 1 -0.12140 1 1.37700 1 +C -1.04980 1 1.10010 1 -0.27560 1 +C -1.45360 1 -0.37480 1 -0.36730 1 +C 0.96160 1 -1.01830 1 -0.65330 1 +C 1.36540 1 0.45670 1 -0.56160 1 +H 0.63470 1 1.99190 1 0.87850 1 +H -0.49220 1 -2.12420 1 0.62260 1 +H -0.66960 1 0.05040 1 2.13790 1 +H 1.05610 1 -0.40930 1 1.93350 1 +H -1.00780 1 1.59430 1 -1.27410 1 +H -1.77090 1 1.66970 1 0.35700 1 +H -2.38350 1 -0.56810 1 0.21790 1 +H -1.63680 1 -0.70310 1 -1.41690 1 +H 0.72580 1 -1.33250 1 -1.69670 1 +H 1.77870 1 -1.67690 1 -0.27500 1 +H 1.35480 1 0.96490 1 -1.55390 1 +H 2.39130 1 0.56080 1 -0.13590 1 + oldgeo thermo nosym precise pm3 \ No newline at end of file