kim-tools 0.3.6__py3-none-any.whl → 0.4.2__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/__init__.py +1 -1
- kim_tools/aflow_util/core.py +81 -27
- kim_tools/ase/core.py +122 -33
- kim_tools/kimunits.py +26 -10
- kim_tools/symmetry_util/core.py +276 -102
- kim_tools/symmetry_util/data/elast_cubic.svg +105 -0
- kim_tools/symmetry_util/data/elast_hexagonal.svg +105 -0
- kim_tools/symmetry_util/data/elast_monoclinic.svg +108 -0
- kim_tools/symmetry_util/data/elast_orthorhombic.svg +96 -0
- kim_tools/symmetry_util/data/elast_tetragonal_4_slash_m.svg +114 -0
- kim_tools/symmetry_util/data/elast_tetragonal_4_slash_mmm.svg +105 -0
- kim_tools/symmetry_util/data/elast_triclinic.svg +132 -0
- kim_tools/symmetry_util/data/elast_trigonal_3bar.svg +129 -0
- kim_tools/symmetry_util/data/elast_trigonal_3bar_m_2nd_pos.svg +117 -0
- kim_tools/symmetry_util/data/elast_trigonal_3bar_m_3rd_pos.svg +117 -0
- kim_tools/symmetry_util/elasticity.py +390 -0
- kim_tools/test_driver/core.py +436 -80
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/METADATA +3 -2
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/RECORD +22 -11
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/WHEEL +0 -0
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/licenses/LICENSE.CDDL +0 -0
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/top_level.txt +0 -0
kim_tools/__init__.py
CHANGED
kim_tools/aflow_util/core.py
CHANGED
|
@@ -17,7 +17,6 @@ import numpy as np
|
|
|
17
17
|
import numpy.typing as npt
|
|
18
18
|
from ase import Atoms
|
|
19
19
|
from ase.cell import Cell
|
|
20
|
-
from ase.neighborlist import natural_cutoffs, neighbor_list
|
|
21
20
|
from semver import Version
|
|
22
21
|
from sympy import Symbol, linear_eq_to_matrix, matrix2numpy, parse_expr
|
|
23
22
|
|
|
@@ -30,6 +29,7 @@ from ..symmetry_util import (
|
|
|
30
29
|
cartesian_rotation_is_in_point_group,
|
|
31
30
|
get_possible_primitive_shifts,
|
|
32
31
|
get_primitive_wyckoff_multiplicity,
|
|
32
|
+
get_smallest_nn_dist,
|
|
33
33
|
get_wyck_pos_xform_under_normalizer,
|
|
34
34
|
space_group_numbers_are_enantiomorphic,
|
|
35
35
|
)
|
|
@@ -398,9 +398,7 @@ def get_atom_indices_for_each_wyckoff_orb(prototype_label: str) -> List[Dict]:
|
|
|
398
398
|
orbit.
|
|
399
399
|
|
|
400
400
|
Returns:
|
|
401
|
-
The information is in this format:
|
|
402
|
-
|
|
403
|
-
[{"letter":"a", "indices":[0,1]}, ... ]
|
|
401
|
+
The information is in this format -- ``[{"letter":"a", "indices":[0,1]}, ... ]``
|
|
404
402
|
"""
|
|
405
403
|
return_list = []
|
|
406
404
|
wyck_lists = get_wyckoff_lists_from_prototype(prototype_label)
|
|
@@ -416,6 +414,45 @@ def get_atom_indices_for_each_wyckoff_orb(prototype_label: str) -> List[Dict]:
|
|
|
416
414
|
return return_list
|
|
417
415
|
|
|
418
416
|
|
|
417
|
+
def get_all_equivalent_labels(prototype_label: str) -> List[str]:
|
|
418
|
+
"""
|
|
419
|
+
Return all possible permutations of the Wyckoff letters in a prototype
|
|
420
|
+
label under the operations of the affine normalizer.
|
|
421
|
+
|
|
422
|
+
NOTE: For now this function will not completely enumerate the possibilities
|
|
423
|
+
for triclinic and monoclinic space groups
|
|
424
|
+
"""
|
|
425
|
+
sgnum = get_space_group_number_from_prototype(prototype_label)
|
|
426
|
+
prototype_label_split = prototype_label.split("_")
|
|
427
|
+
equivalent_labels = []
|
|
428
|
+
for wyck_pos_xform in get_wyck_pos_xform_under_normalizer(sgnum):
|
|
429
|
+
prototype_label_split_permuted = prototype_label_split[:3]
|
|
430
|
+
for wycksec in prototype_label_split[3:]:
|
|
431
|
+
# list of letters joined with their nums, e.g. ["a", "2i"]
|
|
432
|
+
wycksec_permuted_list = []
|
|
433
|
+
prev_lett_ind = -1
|
|
434
|
+
for i, num_or_lett in enumerate(wycksec):
|
|
435
|
+
if isalpha(num_or_lett):
|
|
436
|
+
if num_or_lett == "A":
|
|
437
|
+
# Wyckoff position A comes after z in sg 47
|
|
438
|
+
lett_index = ord("z") + 1 - ord("a")
|
|
439
|
+
else:
|
|
440
|
+
lett_index = ord(num_or_lett) - ord("a")
|
|
441
|
+
# The start position of the (optional) numbers +
|
|
442
|
+
# letter describing this position
|
|
443
|
+
this_pos_start_ind = prev_lett_ind + 1
|
|
444
|
+
permuted_lett_and_num = wycksec[this_pos_start_ind:i]
|
|
445
|
+
permuted_lett_and_num += wyck_pos_xform[lett_index]
|
|
446
|
+
wycksec_permuted_list.append(permuted_lett_and_num)
|
|
447
|
+
prev_lett_ind = i
|
|
448
|
+
wycksec_permuted_list_sorted = sorted(
|
|
449
|
+
wycksec_permuted_list, key=lambda x: x[-1]
|
|
450
|
+
)
|
|
451
|
+
prototype_label_split_permuted.append("".join(wycksec_permuted_list_sorted))
|
|
452
|
+
equivalent_labels.append("_".join(prototype_label_split_permuted))
|
|
453
|
+
return list(set(equivalent_labels))
|
|
454
|
+
|
|
455
|
+
|
|
419
456
|
def prototype_labels_are_equivalent(
|
|
420
457
|
prototype_label_1: str,
|
|
421
458
|
prototype_label_2: str,
|
|
@@ -1510,7 +1547,8 @@ class AFLOW:
|
|
|
1510
1547
|
cell_rtol: float = 0.01,
|
|
1511
1548
|
rot_rtol: float = 0.01,
|
|
1512
1549
|
rot_atol: float = 0.01,
|
|
1513
|
-
|
|
1550
|
+
match_library_proto: bool = True,
|
|
1551
|
+
) -> Union[List[float], Tuple[List[float], Optional[str]]]:
|
|
1514
1552
|
"""
|
|
1515
1553
|
Given an Atoms object that is a primitive cell of its Bravais lattice as
|
|
1516
1554
|
defined in doi.org/10.1016/j.commatsci.2017.01.017, and its presumed prototype
|
|
@@ -1547,12 +1585,16 @@ class AFLOW:
|
|
|
1547
1585
|
Parameter to pass to :func:`numpy.allclose` for compariong fractional
|
|
1548
1586
|
rotations. Default value chosen to be commensurate with AFLOW
|
|
1549
1587
|
default distance tolerance of 0.01*(NN distance)
|
|
1588
|
+
match_library_proto:
|
|
1589
|
+
Whether to attempt matching to library prototypes
|
|
1550
1590
|
|
|
1551
1591
|
Returns:
|
|
1552
1592
|
* List of free parameters that will regenerate `atoms` (up to permutations,
|
|
1553
1593
|
rotations, and translations) when paired with `prototype_label`
|
|
1554
|
-
*
|
|
1555
|
-
|
|
1594
|
+
* Additionally, if 'match_library_proto' is True (default):
|
|
1595
|
+
* Library prototype label from the AFLOW prototype encyclopedia, if any
|
|
1596
|
+
* Title of library prototype from the AFLOW prototype encyclopedia,
|
|
1597
|
+
if any
|
|
1556
1598
|
|
|
1557
1599
|
Raises:
|
|
1558
1600
|
AFLOW.ChangedSymmetryException:
|
|
@@ -1563,20 +1605,12 @@ class AFLOW:
|
|
|
1563
1605
|
"""
|
|
1564
1606
|
# If max_resid not provided, determine it from neighborlist
|
|
1565
1607
|
if max_resid is None:
|
|
1566
|
-
nl_len = 0
|
|
1567
|
-
cov_mult = 1
|
|
1568
|
-
while nl_len == 0:
|
|
1569
|
-
logger.info(
|
|
1570
|
-
"Attempting to find NN distance by searching "
|
|
1571
|
-
f"within covalent radii times {cov_mult}"
|
|
1572
|
-
)
|
|
1573
|
-
nl = neighbor_list("d", atoms, natural_cutoffs(atoms, mult=cov_mult))
|
|
1574
|
-
nl_len = nl.size
|
|
1575
|
-
cov_mult += 1
|
|
1576
1608
|
# set the maximum error to 1% of NN distance to follow AFLOW convention
|
|
1577
1609
|
# rescale by cube root of cell volume to get rough conversion from
|
|
1578
1610
|
# cartesian to fractional
|
|
1579
|
-
max_resid =
|
|
1611
|
+
max_resid = (
|
|
1612
|
+
get_smallest_nn_dist(atoms) * 0.01 * atoms.get_volume() ** (-1 / 3)
|
|
1613
|
+
)
|
|
1580
1614
|
logger.info(
|
|
1581
1615
|
"Automatically set max fractional residual for solving position "
|
|
1582
1616
|
f"equations to {max_resid}"
|
|
@@ -1599,10 +1633,25 @@ class AFLOW:
|
|
|
1599
1633
|
"aflow_prototype_label"
|
|
1600
1634
|
]
|
|
1601
1635
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1636
|
+
if match_library_proto:
|
|
1637
|
+
try:
|
|
1638
|
+
library_prototype_label, short_name = (
|
|
1639
|
+
self.get_library_prototype_label_and_shortname_from_atoms(atoms)
|
|
1640
|
+
)
|
|
1641
|
+
except subprocess.CalledProcessError:
|
|
1642
|
+
library_prototype_label = None
|
|
1643
|
+
short_name = None
|
|
1644
|
+
msg = (
|
|
1645
|
+
"WARNING: aflow --compare2prototypes returned error, skipping "
|
|
1646
|
+
"library matching"
|
|
1647
|
+
)
|
|
1648
|
+
print()
|
|
1649
|
+
print(msg)
|
|
1650
|
+
print()
|
|
1651
|
+
logger.warning(msg)
|
|
1605
1652
|
|
|
1653
|
+
# NOTE: Because of below, this only works if the provided prototype label is
|
|
1654
|
+
# correctly alphabetized. Change this?
|
|
1606
1655
|
if not prototype_labels_are_equivalent(
|
|
1607
1656
|
prototype_label, prototype_label_detected
|
|
1608
1657
|
):
|
|
@@ -1665,7 +1714,7 @@ class AFLOW:
|
|
|
1665
1714
|
)
|
|
1666
1715
|
|
|
1667
1716
|
position_set_list = get_equivalent_atom_sets_from_prototype_and_atom_map(
|
|
1668
|
-
atoms,
|
|
1717
|
+
atoms, prototype_label_detected, atom_map, sort_atoms=True
|
|
1669
1718
|
)
|
|
1670
1719
|
|
|
1671
1720
|
# get equation sets
|
|
@@ -1785,11 +1834,14 @@ class AFLOW:
|
|
|
1785
1834
|
f"Found set of parameters for prototype {prototype_label} "
|
|
1786
1835
|
"that is unrotated"
|
|
1787
1836
|
)
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1837
|
+
if match_library_proto:
|
|
1838
|
+
return (
|
|
1839
|
+
candidate_prototype_param_values,
|
|
1840
|
+
library_prototype_label,
|
|
1841
|
+
short_name,
|
|
1842
|
+
)
|
|
1843
|
+
else:
|
|
1844
|
+
return candidate_prototype_param_values
|
|
1793
1845
|
else:
|
|
1794
1846
|
logger.info(
|
|
1795
1847
|
f"Found set of parameters for prototype {prototype_label}, "
|
|
@@ -1863,7 +1915,9 @@ class AFLOW:
|
|
|
1863
1915
|
cell_lengths_and_angles = ref_atoms.cell.cellpar()
|
|
1864
1916
|
|
|
1865
1917
|
test_atoms_copy = test_atoms.copy()
|
|
1918
|
+
del test_atoms_copy.constraints
|
|
1866
1919
|
ref_atoms_copy = ref_atoms.copy()
|
|
1920
|
+
del ref_atoms_copy.constraints
|
|
1867
1921
|
|
|
1868
1922
|
test_atoms_copy.set_cell(
|
|
1869
1923
|
Cell.fromcellpar(cell_lengths_and_angles), scale_atoms=True
|
kim_tools/ase/core.py
CHANGED
|
@@ -34,7 +34,6 @@ Helper routines for KIM Tests and Verification Checks
|
|
|
34
34
|
import itertools
|
|
35
35
|
import logging
|
|
36
36
|
import random
|
|
37
|
-
from typing import Union
|
|
38
37
|
|
|
39
38
|
import numpy as np
|
|
40
39
|
from ase import Atoms
|
|
@@ -208,45 +207,137 @@ def randomize_positions(atoms, pert_amp, seed=None):
|
|
|
208
207
|
|
|
209
208
|
|
|
210
209
|
################################################################################
|
|
211
|
-
def get_isolated_energy_per_atom(
|
|
210
|
+
def get_isolated_energy_per_atom(
|
|
211
|
+
model,
|
|
212
|
+
symbol,
|
|
213
|
+
initial_separation=1.0,
|
|
214
|
+
max_separation=15.0,
|
|
215
|
+
separation_neg_exponent=4,
|
|
216
|
+
quit_early_after_convergence=True,
|
|
217
|
+
energy_tolerance=1e-12,
|
|
218
|
+
):
|
|
212
219
|
"""
|
|
213
220
|
Construct a non-periodic cell containing a single atom and compute its energy.
|
|
221
|
+
It tries to iteratively finetune the atomic separation for a dimer up to a
|
|
222
|
+
specified precision (separation_neg_exponent). If between two successive phases
|
|
223
|
+
the energy difference is less than energy_tolerance, it stops early, i.e. if
|
|
224
|
+
4.0x and 4.00x are within energy_tolerance, it stops at 4.00x.
|
|
225
|
+
All separations are in Angstroms.
|
|
214
226
|
|
|
215
227
|
Args:
|
|
216
|
-
model:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
model: KIM model to use for calculations
|
|
229
|
+
symbol: Chemical symbol
|
|
230
|
+
initial_separation: Initial separation for dimer calculations
|
|
231
|
+
max_separation: maximum separation for dimer calculations
|
|
232
|
+
separation_neg_exponent: Number of decimal places to refine the separation
|
|
233
|
+
quit_early_after_convergence: Whether to stop early if energy converges
|
|
234
|
+
energy_tolerance: Energy difference tolerance for convergence check
|
|
223
235
|
"""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if isinstance(model, str):
|
|
231
|
-
calc = KIM(model)
|
|
232
|
-
elif isinstance(model, Calculator):
|
|
233
|
-
calc = model
|
|
234
|
-
else:
|
|
235
|
-
raise KIMASEError(
|
|
236
|
-
"`model` argument must be a string indicating a KIM model "
|
|
237
|
-
f"or an ASE Calculator. Instead got an object of type {type(model)}."
|
|
236
|
+
try:
|
|
237
|
+
single_atom = Atoms(
|
|
238
|
+
symbol,
|
|
239
|
+
positions=[(0.1, 0.1, 0.1)],
|
|
240
|
+
cell=(20, 20, 20),
|
|
241
|
+
pbc=(False, False, False),
|
|
238
242
|
)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
243
|
+
if isinstance(model, str):
|
|
244
|
+
calc = KIM(model)
|
|
245
|
+
elif isinstance(model, Calculator):
|
|
246
|
+
calc = model
|
|
247
|
+
|
|
248
|
+
single_atom.calc = calc
|
|
249
|
+
energy_per_atom = single_atom.get_potential_energy()
|
|
250
|
+
|
|
251
|
+
# Clean up
|
|
244
252
|
if hasattr(calc, "clean"):
|
|
245
253
|
calc.clean()
|
|
246
254
|
if hasattr(calc, "__del__"):
|
|
247
255
|
calc.__del__()
|
|
248
|
-
|
|
249
|
-
|
|
256
|
+
del single_atom
|
|
257
|
+
|
|
258
|
+
return energy_per_atom
|
|
259
|
+
|
|
260
|
+
except Exception:
|
|
261
|
+
|
|
262
|
+
def _try_dimer_energy(separation):
|
|
263
|
+
try:
|
|
264
|
+
dimer = Atoms(
|
|
265
|
+
[symbol, symbol],
|
|
266
|
+
positions=[(0.1, 0.1, 0.1), (0.1 + separation, 0.1, 0.1)],
|
|
267
|
+
cell=(max(20, separation + 10), 20, 20),
|
|
268
|
+
pbc=(False, False, False),
|
|
269
|
+
)
|
|
270
|
+
calc = KIM(model)
|
|
271
|
+
dimer.calc = calc
|
|
272
|
+
|
|
273
|
+
total_energy = dimer.get_potential_energy()
|
|
274
|
+
energy_per_atom = total_energy / 2.0
|
|
275
|
+
|
|
276
|
+
if hasattr(calc, "clean"):
|
|
277
|
+
calc.clean()
|
|
278
|
+
if hasattr(calc, "__del__"):
|
|
279
|
+
calc.__del__()
|
|
280
|
+
del dimer
|
|
281
|
+
|
|
282
|
+
return energy_per_atom
|
|
283
|
+
|
|
284
|
+
except Exception:
|
|
285
|
+
try:
|
|
286
|
+
if hasattr(calc, "clean"):
|
|
287
|
+
calc.clean()
|
|
288
|
+
if hasattr(calc, "__del__"):
|
|
289
|
+
calc.__del__()
|
|
290
|
+
if "dimer" in locals():
|
|
291
|
+
del dimer
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
# Start with integer separations: 1.0, 2.0, 3.0, 4.0, ...
|
|
297
|
+
last_successful_separation = None
|
|
298
|
+
last_successful_energy = None
|
|
299
|
+
|
|
300
|
+
separation = initial_separation
|
|
301
|
+
while separation <= max_separation:
|
|
302
|
+
energy = _try_dimer_energy(separation)
|
|
303
|
+
if energy is not None:
|
|
304
|
+
last_successful_separation = separation
|
|
305
|
+
last_successful_energy = energy
|
|
306
|
+
separation += 1.0
|
|
307
|
+
else:
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
if last_successful_separation is None:
|
|
311
|
+
raise RuntimeError(
|
|
312
|
+
f"Failed to obtain isolated energy for {symbol} - no separations worked"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# refine
|
|
316
|
+
current_separation = last_successful_separation
|
|
317
|
+
previous_phase_energy = last_successful_energy
|
|
318
|
+
|
|
319
|
+
for decimal_place in range(1, separation_neg_exponent + 1):
|
|
320
|
+
|
|
321
|
+
step_size = 10 ** (-decimal_place)
|
|
322
|
+
|
|
323
|
+
for i in range(1, 10):
|
|
324
|
+
test_sep = current_separation + step_size
|
|
325
|
+
energy = _try_dimer_energy(test_sep)
|
|
326
|
+
|
|
327
|
+
if energy is not None:
|
|
328
|
+
current_separation = test_sep
|
|
329
|
+
last_successful_energy = energy
|
|
330
|
+
else:
|
|
331
|
+
break
|
|
332
|
+
|
|
333
|
+
if quit_early_after_convergence:
|
|
334
|
+
energy_diff = abs(last_successful_energy - previous_phase_energy)
|
|
335
|
+
if energy_diff <= energy_tolerance:
|
|
336
|
+
return last_successful_energy
|
|
337
|
+
|
|
338
|
+
previous_phase_energy = last_successful_energy
|
|
339
|
+
|
|
340
|
+
return last_successful_energy
|
|
250
341
|
|
|
251
342
|
|
|
252
343
|
################################################################################
|
|
@@ -434,9 +525,7 @@ def check_if_atoms_interacting(
|
|
|
434
525
|
atoms_interacting_energy = check_if_atoms_interacting_energy(
|
|
435
526
|
model, symbols, etol
|
|
436
527
|
)
|
|
437
|
-
atoms_interacting_force =
|
|
438
|
-
model, symbols, ftol
|
|
439
|
-
)
|
|
528
|
+
atoms_interacting_force = check_if_atoms_interacting_force(model, symbols, ftol)
|
|
440
529
|
return atoms_interacting_energy, atoms_interacting_force
|
|
441
530
|
|
|
442
531
|
|
kim_tools/kimunits.py
CHANGED
|
@@ -12,6 +12,8 @@ import re
|
|
|
12
12
|
import subprocess
|
|
13
13
|
import warnings
|
|
14
14
|
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
15
17
|
warnings.simplefilter("ignore")
|
|
16
18
|
|
|
17
19
|
|
|
@@ -24,17 +26,31 @@ _units_output_expression = re.compile(
|
|
|
24
26
|
)
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
def check_units_util():
|
|
29
|
+
def check_units_util() -> str:
|
|
28
30
|
"""
|
|
29
|
-
|
|
31
|
+
Figure out if units (first choice) or gunits (second choice) works
|
|
32
|
+
with the options that we use
|
|
30
33
|
"""
|
|
34
|
+
args = ["-o", r"%1.15e", "-qt1", "0.0 eV/angstrom^3 bar"]
|
|
31
35
|
try:
|
|
32
|
-
subprocess.check_output(["units", "
|
|
36
|
+
output = subprocess.check_output(["units"] + args, encoding="utf-8")
|
|
37
|
+
assert np.isclose(float(output), 0)
|
|
38
|
+
units_command = "units"
|
|
33
39
|
except Exception:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
try:
|
|
41
|
+
output = subprocess.check_output(["gunits"] + args, encoding="utf-8")
|
|
42
|
+
assert np.isclose(float(output), 0)
|
|
43
|
+
units_command = "gunits"
|
|
44
|
+
except Exception:
|
|
45
|
+
raise UnitConversion(
|
|
46
|
+
"Neither "
|
|
47
|
+
r"units -o %1.15e -qt1 '0.0 eV/angstrom^3 bar'"
|
|
48
|
+
" nor "
|
|
49
|
+
r"gunits -o %1.15e -qt1 '0.0 eV/angstrom^3 bar'"
|
|
50
|
+
" successfully ran and returned 0.e0. Please install a "
|
|
51
|
+
"compatible version of units."
|
|
52
|
+
)
|
|
53
|
+
return units_command
|
|
38
54
|
|
|
39
55
|
|
|
40
56
|
def linear_fit(x, y):
|
|
@@ -68,7 +84,7 @@ def islinear(unit, to_unit=None):
|
|
|
68
84
|
|
|
69
85
|
def convert_units(from_value, from_unit, wanted_unit=None, suppress_unit=False):
|
|
70
86
|
"""Works with 'units' utility"""
|
|
71
|
-
check_units_util()
|
|
87
|
+
units_util = check_units_util()
|
|
72
88
|
from_sign = from_value < 0
|
|
73
89
|
from_value = str(abs(from_value))
|
|
74
90
|
from_unit = str(from_unit)
|
|
@@ -77,7 +93,7 @@ def convert_units(from_value, from_unit, wanted_unit=None, suppress_unit=False):
|
|
|
77
93
|
|
|
78
94
|
if from_unit in TEMPERATURE_FUNCTION_UNITS:
|
|
79
95
|
args = [
|
|
80
|
-
|
|
96
|
+
units_util,
|
|
81
97
|
"-o",
|
|
82
98
|
"%1.15e",
|
|
83
99
|
"-qt1",
|
|
@@ -85,7 +101,7 @@ def convert_units(from_value, from_unit, wanted_unit=None, suppress_unit=False):
|
|
|
85
101
|
]
|
|
86
102
|
|
|
87
103
|
else:
|
|
88
|
-
args = [
|
|
104
|
+
args = [units_util, "-o", "%1.15e", "-qt1", " ".join((from_value, from_unit))]
|
|
89
105
|
|
|
90
106
|
if wanted_unit:
|
|
91
107
|
args.append(wanted_unit)
|