kim-tools 0.2.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kim_tools/kimunits.py ADDED
@@ -0,0 +1,162 @@
1
+ """
2
+ Simple wrapper for executable for converting arbitrary units to SI units
3
+
4
+ Copyright (c) 2014-2022, Regents of the University of Minnesota. All rights
5
+ reserved.
6
+
7
+ This software may be distributed as-is, without modification.
8
+ """
9
+
10
+ import math
11
+ import re
12
+ import subprocess
13
+ import warnings
14
+
15
+ warnings.simplefilter("ignore")
16
+
17
+
18
+ class UnitConversion(Exception):
19
+ """Class for unit conversion errors"""
20
+
21
+
22
+ _units_output_expression = re.compile(
23
+ r"(?P<value>(?:[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?))(?: (?P<unit>.+))?"
24
+ )
25
+
26
+
27
+ def linear_fit(x, y):
28
+ """
29
+ Perform a linear fit between x,y, returning the average error for each data
30
+ point as well. This is written this way so as to not add a numpy dependency
31
+ """
32
+ n = len(x)
33
+ xx = sum([x**2 for x in x]) - sum(x) ** 2 / n
34
+ xy = sum(map(lambda x, y: x * y, x, y)) - sum(x) * sum(y) / n
35
+ a, b = sum(y) / n - xy / xx * sum(x) / n, xy / xx
36
+ yhat = [a + b * x for x in x]
37
+ yerr = math.sqrt(sum(map(lambda y, yh: (y - yh) ** 2 / y**2, y, yhat)) / n)
38
+ return a, b, yerr
39
+
40
+
41
+ def islinear(unit, to_unit=None):
42
+ """
43
+ Detect if the conversion from `unit` to `to_unit` is a linear map. Apparently
44
+ the units utility is float precision, so if error is less than 1e-7 we know
45
+ it is linear.
46
+ """
47
+ x = [100 ** (1e-2 * (i - 50)) for i in range(20)]
48
+ y = convert_list(x, unit, to_unit=to_unit, dofit=False)[0]
49
+ a, b, err = linear_fit(x, y)
50
+
51
+ a = convert_list(0, unit, to_unit=to_unit, dofit=False)[0]
52
+ b = convert_list(1, unit, to_unit=to_unit, dofit=False)[0] - a
53
+ return a, b, err < 1e-7
54
+
55
+
56
+ def convert_units(from_value, from_unit, wanted_unit=None, suppress_unit=False):
57
+ """Works with 'units' utility"""
58
+ from_sign = from_value < 0
59
+ from_value = str(abs(from_value))
60
+ from_unit = str(from_unit)
61
+
62
+ TEMPERATURE_FUNCTION_UNITS = ["degC", "tempC", "degF", "tempF"]
63
+
64
+ if from_unit in TEMPERATURE_FUNCTION_UNITS:
65
+ args = [
66
+ "units",
67
+ "-o",
68
+ "%1.15e",
69
+ "-qt1",
70
+ "".join((from_unit, "(", from_value, ")")),
71
+ ]
72
+
73
+ else:
74
+ args = ["units", "-o", "%1.15e", "-qt1", " ".join((from_value, from_unit))]
75
+
76
+ if wanted_unit:
77
+ args.append(wanted_unit)
78
+
79
+ try:
80
+ output = subprocess.check_output(args).decode("utf-8")
81
+ except subprocess.CalledProcessError:
82
+ tag = wanted_unit if wanted_unit else "SI"
83
+ raise UnitConversion(
84
+ "Error in unit conversion of {} {} to {}".format(from_value, from_unit, tag)
85
+ )
86
+
87
+ matches = _units_output_expression.match(output).groupdict(None)
88
+ out = ((-1) ** from_sign * float(matches["value"]), matches["unit"] or wanted_unit)
89
+
90
+ if suppress_unit:
91
+ return out[0]
92
+ return out
93
+
94
+
95
+ # Set default behavior
96
+ convert = convert_units
97
+
98
+
99
+ def convert_list(x, from_unit, to_unit=None, convert=convert, dofit=True):
100
+ """Thread conversion over a list, or list of lists"""
101
+ # Need a list for scoping reasons
102
+
103
+ # Constant shortcut
104
+ if from_unit in (1, 1.0, "1"):
105
+ to_unit = "1"
106
+
107
+ # get the SI unit if none provided
108
+ if to_unit is None:
109
+ _, to_unit = convert(1.0, from_unit)
110
+
111
+ def convert_inner(x, fit=None):
112
+ if isinstance(x, (list, tuple)):
113
+ return type(x)(convert_inner(i, fit=fit) for i in x)
114
+ else:
115
+ if to_unit == "1":
116
+ return float(x)
117
+ else:
118
+ if fit is not None:
119
+ return fit[0] + fit[1] * x
120
+ return float(convert(x, from_unit, to_unit, suppress_unit=True))
121
+
122
+ # setup the linear fit if we are requested to simplify
123
+ fit = None
124
+ if dofit and isinstance(x, (list, tuple)) and len(x) > 20:
125
+ a, b, linear = islinear(from_unit, to_unit)
126
+ fit = (a, b) if linear else None
127
+
128
+ output = convert_inner(x, fit=fit)
129
+ return output, to_unit
130
+
131
+
132
+ def add_si_units(doc, convert=convert):
133
+ """Given a document, add all of the appropriate si-units fields"""
134
+ if isinstance(doc, dict):
135
+ # check for a source-unit to defined a value with units
136
+ if "source-unit" in doc:
137
+ # we've found a place to add
138
+ assert "source-value" in doc, "Badly formed doc"
139
+ o_value = doc.get("source-value", None)
140
+ o_unit = doc.get("source-unit", None)
141
+
142
+ if o_value is None:
143
+ raise UnitConversion("No source-value provided")
144
+ if o_unit is None:
145
+ raise UnitConversion("No source-unit provided")
146
+
147
+ # convert the units and insert
148
+ value, unit = convert_list(o_value, o_unit, convert=convert)
149
+ si_dict = {"si-unit": unit, "si-value": value}
150
+ doc = doc.copy()
151
+ doc.update(si_dict)
152
+ return doc
153
+ else:
154
+ # recurse
155
+ return type(doc)(
156
+ (key, add_si_units(value)) for key, value in list(doc.items())
157
+ )
158
+
159
+ elif isinstance(doc, (list, tuple)):
160
+ return type(doc)(add_si_units(x) for x in doc)
161
+
162
+ return doc
@@ -0,0 +1,4 @@
1
+ from .core import *
2
+ from .core import __all__ as symmetry_all
3
+
4
+ __all__ = symmetry_all