1# -*- coding: utf-8 -*-
2# MolMod is a collection of molecular modelling tools for python.
3# Copyright (C) 2007 - 2019 Toon Verstraelen <Toon.Verstraelen@UGent.be>, Center
4# for Molecular Modeling (CMM), Ghent University, Ghent, Belgium; all rights
5# reserved unless otherwise stated.
6#
7# This file is part of MolMod.
8#
9# MolMod is free software; you can redistribute it and/or
10# modify it under the terms of the GNU General Public License
11# as published by the Free Software Foundation; either version 3
12# of the License, or (at your option) any later version.
13#
14# MolMod is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, see <http://www.gnu.org/licenses/>
21#
22# --
23"""Conversion from and to atomic units
24
25   Internally the MolMod package works always in atomic units. This unit system
26   is consistent, like the SI unit system one does not need conversion factors
27   in the middle of a computation once all values are converted to atomic units.
28   This facilitates the programming and reduces accidental bugs due to
29   forgetting these conversion factor in the body of the code.
30
31   References for the conversion values:
32
33   * B. J. Mohr and B. N. Taylor,
34     CODATA recommended values of the fundamental physical
35     constants: 1998, Rev. Mod. Phys. 72(2), 351 (2000)
36   * The NIST Reference on Constants, Units, and Uncertainty
37     (http://physics.nist.gov/cuu/Constants/index.html)
38   * 1 calorie = 4.184 Joules
39
40   Naming conventions in this module: unit is the value of one external unit
41   in internal - i.e. atomic - units. e.g. If you want to have a distance of
42   five angstrom in internal units: ``5*angstrom``. If you want to convert a
43   length of 5 internal units to angstrom: ``5/angstrom``. It is recommended to
44   perform this kind of conversions, only when data is read from the input and
45   data is written to the output.
46
47   An often recurring question is how to convert a frequency in internal units
48   to a spectroscopic wavenumber in inverse centimeters. This is how it can be
49   done::
50
51     >>> from molmod import centimeter, lightspeed
52     >>> invcm = lightspeed/centimeter
53     >>> freq = 0.00320232
54     >>> print freq/invcm
55
56   These are the conversion constants defined in this module:
57
58"""
59
60
61from __future__ import division
62
63from molmod.constants import avogadro
64
65
66def parse_unit(expression):
67    """Evaluate a python expression string containing constants
68
69       Argument:
70        | ``expression``  --  A string containing a numerical expressions
71                              including unit conversions.
72
73       In addition to the variables in this module, also the following
74       shorthands are supported:
75
76    """
77    try:
78        g = globals()
79        g.update(shorthands)
80        return float(eval(str(expression), g))
81    except:
82        raise ValueError("Could not interpret '%s' as a unit or a measure." % expression)
83
84
85# *** Generic ***
86au = 1.0
87
88
89# *** Charge ***
90
91coulomb = 1.0/1.602176462e-19
92
93# Mol
94
95mol = avogadro
96
97# *** Mass ***
98
99kilogram = 1.0/9.10938188e-31
100
101gram = 1.0e-3*kilogram
102miligram = 1.0e-6*kilogram
103unified = 1.0e-3*kilogram/mol
104amu = unified
105
106# *** Length ***
107
108meter = 1.0/0.5291772083e-10
109
110decimeter = 1.0e-1*meter
111centimeter = 1.0e-2*meter
112milimeter = 1.0e-3*meter
113micrometer = 1.0e-6*meter
114nanometer = 1.0e-9*meter
115angstrom = 1.0e-10*meter
116picometer = 1.0e-12*meter
117
118# *** Volume ***
119
120liter = decimeter**3
121
122# *** Energy ***
123
124joule = 1/4.35974381e-18
125
126calorie = 4.184*joule
127kjmol = 1.0e3*joule/mol
128kcalmol = 1.0e3*calorie/mol
129electronvolt = (1.0/coulomb)*joule
130rydberg = 0.5
131
132# *** Force ***
133
134newton = joule/meter
135
136# *** Angles ***
137
138deg = 0.017453292519943295
139rad = 1.0
140
141# *** Time ***
142
143second = 1/2.418884326500e-17
144
145nanosecond = 1e-9*second
146femtosecond = 1e-15*second
147picosecond = 1e-12*second
148
149# *** Frequency ***
150
151hertz = 1/second
152
153# *** Pressure ***
154
155pascal = newton/meter**2
156bar = 100000*pascal
157atm = 1.01325*bar
158
159# *** Temperature ***
160
161kelvin = 1.0
162
163# *** Dipole ***
164
165debye = 0.39343031369146675 # = 1e-21*coulomb*meter**2/second/lightspeed
166
167# *** Current ***
168
169ampere = coulomb/second
170
171
172# Shorthands for the parse functions
173
174shorthands = {
175    "C": coulomb,
176    "kg": kilogram,
177    "g": gram,
178    "mg": miligram,
179    "u": unified,
180    "m": meter,
181    "cm": centimeter,
182    "mm": milimeter,
183    "um": micrometer,
184    "nm": nanometer,
185    "A": angstrom,
186    "pm": picometer,
187    "l": liter,
188    "J": joule,
189    "cal": calorie,
190    "eV": electronvolt,
191    "N": newton,
192    "s": second,
193    "Hz": hertz,
194    "ns": nanosecond,
195    "fs": femtosecond,
196    "ps": picosecond,
197    "Pa": pascal,
198    "K": kelvin,
199    # atomic units
200    "e": au,
201}
202
203
204# automatically spice up the docstrings
205
206lines = [
207    "    ================  ==================",
208    "    Name              Value             ",
209    "    ================  ==================",
210]
211
212for key, value in sorted(globals().items()):
213    if not isinstance(value, float):
214        continue
215    lines.append("    %16s  %.10e" % (key, value))
216lines.append("    ================  ==================")
217
218__doc__ += "\n".join(lines)
219
220
221lines = [
222    "     ================  ==================",
223    "         Short name        Value             ",
224    "         ================  ==================",
225]
226
227for key, value in sorted(shorthands.items()):
228    if not isinstance(value, float):
229        continue
230    lines.append("         %16s  %.10e" % (key, value))
231lines.append("         ================  ==================")
232
233parse_unit.__doc__ += "\n".join(lines)
234
235del lines
236