Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test suite package for pyunitwizard."""
51 changes: 51 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from contextlib import contextmanager

import pyunitwizard as puw


@contextmanager
def loaded_libraries(libraries):
"""
Context manager to temporarily load the requested libraries for a test case.

Parameters
----------
libraries : list of str
List of library names to load temporarily for the duration of the context.

Behavior
--------
- Saves the current loaded libraries, default form, and default parser.
- Resets the configuration and loads the specified libraries.
- After the context, restores the previous configuration.
- If no previous libraries were loaded, attempts to load a default set
('pint', 'openmm.unit', 'unyt'), ignoring failures.

Exceptions
----------
- Any exception raised by `puw.configure.load_library` when loading the requested libraries
will propagate unless caught internally.
- Exceptions when loading default libraries after the context are caught and ignored.
"""
previous_libraries = list(puw.configure.get_libraries_loaded())
previous_default_form = puw.configure.get_default_form()
previous_default_parser = puw.configure.get_default_parser()

puw.configure.reset()
puw.configure.load_library(libraries)
try:
yield
finally:
puw.configure.reset()
if previous_libraries:
puw.configure.load_library(previous_libraries)
else:
for library in ['pint', 'openmm.unit', 'unyt']:
try:
puw.configure.load_library(library)
except (ImportError, ModuleNotFoundError):
continue
if previous_default_form is not None:
puw.configure.set_default_form(previous_default_form)
if previous_default_parser is not None:
puw.configure.set_default_parser(previous_default_parser)
70 changes: 70 additions & 0 deletions tests/test_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import pyunitwizard as puw
import pytest


@pytest.fixture(autouse=True)
def configure_pint():
loaded_libraries = list(puw.configure.get_libraries_loaded())
default_form = puw.configure.get_default_form()
default_parser = puw.configure.get_default_parser()
standard_units = list(puw.configure.get_standard_units().keys())

puw.configure.reset()
puw.configure.load_library('pint')
puw.configure.set_standard_units(['nm', 'ps', 'kcal', 'mole'])

yield

puw.configure.reset()

if loaded_libraries:
puw.configure.load_library(loaded_libraries)

if standard_units:
puw.configure.set_standard_units(standard_units)

if default_form is not None:
puw.configure.set_default_form(default_form)

if default_parser is not None:
puw.configure.set_default_parser(default_parser)


@pytest.fixture
def constants_module(monkeypatch):
from pyunitwizard.constants import constants as const_mod

monkeypatch.setattr(const_mod, 'quantity', puw.quantity, raising=False)
monkeypatch.setattr(const_mod, 'convert', puw.convert, raising=False)

return const_mod


def test_get_constant_synonym_and_conversion(constants_module):
universal = constants_module.get_constant('R', to_unit='kJ/(mole*kelvin)')
value, unit = puw.get_value_and_unit(universal, to_form='string')

assert value == pytest.approx(0.00813446, rel=1e-6)
assert unit == 'kilojoule / kelvin / mole'


def test_get_constant_standardized_string_form(constants_module):
raw = constants_module.get_constant('Avogadro')
standardized = constants_module.get_constant('NA', standardized=True)

assert puw.are_equal(standardized, puw.standardize(raw))

string_form = constants_module.get_constant('NA', to_form='string')
assert string_form == '6.02214076e+23 / mole'


def test_get_constant_unknown_raises(constants_module):
with pytest.raises(ValueError):
constants_module.get_constant('Unknown constant')


def test_show_constants_lists_synonyms(constants_module):
registry = constants_module.show_constants()

assert ('Avogadro', 'NA') in registry
assert registry[('Universal gas', 'R', 'Molar gas')] == '8.13446261815324 J/(kelvin*mole)'
54 changes: 30 additions & 24 deletions tests/test_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import numpy as np
import pyunitwizard as puw

from .helpers import loaded_libraries


def test_parse_with_pint_scalar():

quantity = _parse_with_pint("5 meters")
Expand Down Expand Up @@ -48,39 +51,42 @@ def test_parse_to_string():

def test_parse_to_openmm_scalar():

quantity = parse("5 meters", to_form="openmm.unit")
assert quantity._value == 5
assert str(quantity.unit) == "meter"
with loaded_libraries(['pint', 'openmm.unit']):
quantity = parse("5 meters", to_form="openmm.unit")
assert quantity._value == 5
assert str(quantity.unit) == "meter"

def test_parse_to_openmm_array():

quantity = parse("[2, 5, 7] joules", to_form="openmm.unit")
assert np.allclose(quantity._value, np.array([2, 5, 7]))
assert str(quantity.unit) == "joule"
with loaded_libraries(['pint', 'openmm.unit']):
quantity = parse("[2, 5, 7] joules", to_form="openmm.unit")
assert np.allclose(quantity._value, np.array([2, 5, 7]))
assert str(quantity.unit) == "joule"

quantity = parse("(3, 2, 1) meters", to_form="openmm.unit")
assert np.allclose(quantity._value, np.array([3, 2, 1]))
assert str(quantity.unit) == "meter"
quantity = parse("(3, 2, 1) meters", to_form="openmm.unit")
assert np.allclose(quantity._value, np.array([3, 2, 1]))
assert str(quantity.unit) == "meter"

quantity = parse("[[2, 5, 7], [7, 8, 9]] joules", to_form="openmm.unit")
assert np.allclose(quantity._value, np.array([[2, 5, 7], [7, 8, 9]]))
assert str(quantity.unit) == "joule"
quantity = parse("[[2, 5, 7], [7, 8, 9]] joules", to_form="openmm.unit")
assert np.allclose(quantity._value, np.array([[2, 5, 7], [7, 8, 9]]))
assert str(quantity.unit) == "joule"


def test_parse_to_unyt():

quantity = parse("5 meters", to_form="unyt")
assert quantity.value == 5
assert str(quantity.units) == "m"
with loaded_libraries(['pint', 'unyt']):
quantity = parse("5 meters", to_form="unyt")
assert quantity.value == 5
assert str(quantity.units) == "m"

quantity = parse("[2, 5, 7] joules", to_form="unyt")
assert np.allclose(quantity.value, np.array([2, 5, 7]))
assert str(quantity.units) == "J"
quantity = parse("[2, 5, 7] joules", to_form="unyt")
assert np.allclose(quantity.value, np.array([2, 5, 7]))
assert str(quantity.units) == "J"

quantity = parse("(3, 2, 1) meters", to_form="unyt")
assert np.allclose(quantity.value, np.array([3, 2, 1]))
assert str(quantity.units) == "m"
quantity = parse("(3, 2, 1) meters", to_form="unyt")
assert np.allclose(quantity.value, np.array([3, 2, 1]))
assert str(quantity.units) == "m"

quantity = parse("[[2, 5, 7], [7, 8, 9]] joules", to_form="unyt")
assert np.allclose(quantity.value, np.array([[2, 5, 7], [7, 8, 9]]))
assert str(quantity.units) == "J"
quantity = parse("[[2, 5, 7], [7, 8, 9]] joules", to_form="unyt")
assert np.allclose(quantity.value, np.array([[2, 5, 7], [7, 8, 9]]))
assert str(quantity.units) == "J"
17 changes: 11 additions & 6 deletions tests/test_quantity.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import pyunitwizard as puw
import unyt

from .helpers import loaded_libraries


def test_quantity_openmm_unit():

openmm_unit = puw.forms.api_openmm_unit.openmm_unit
quantity = puw.quantity('10 kilojoule/mole', form='openmm.unit')
q_true = 10 * openmm_unit.kilojoule/openmm_unit.mole
assert puw.are_close(quantity, q_true)
with loaded_libraries(['pint', 'openmm.unit']):
openmm_unit = puw.forms.api_openmm_unit.openmm_unit
quantity = puw.quantity('10 kilojoule/mole', form='openmm.unit')
q_true = 10 * openmm_unit.kilojoule/openmm_unit.mole
assert puw.are_close(quantity, q_true)

def test_quantity_pint():

Expand All @@ -16,5 +20,6 @@ def test_quantity_pint():

def test_quantity_unyt():

assert puw.quantity(1.0,
unyt.J/unyt.s, form="unyt") == 1.0 * unyt.J/unyt.s
with loaded_libraries(['pint', 'unyt']):
assert puw.quantity(1.0,
unyt.J/unyt.s, form="unyt") == 1.0 * unyt.J/unyt.s
23 changes: 23 additions & 0 deletions tests/utils/numpy/test_repeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,26 @@ def test_stack_1():
assert value.shape==(4, 3, 3)


@pytest.mark.parametrize('value_type', [None, 'list', 'tuple', 'numpy.ndarray'])
def test_repeat_respects_requested_unit(value_type):
puw.configure.reset()
puw.configure.load_library('pint')

quantity = puw.quantity([1, 2], 'meter')
repeated = puw.utils.numpy.repeat(quantity, 2, to_unit='centimeter', value_type=value_type)

value = puw.get_value(repeated)
assert np.array_equal(value, np.array([1, 1, 2, 2]))
Comment thread
dprada marked this conversation as resolved.
assert np.allclose(puw.get_value(repeated, to_unit='meter'), np.array([0.01, 0.01, 0.02, 0.02]))
assert puw.get_unit(repeated, to_form='string') == 'centimeter'
Comment thread
dprada marked this conversation as resolved.


def test_repeat_raises_for_unknown_value_type():
puw.configure.reset()
puw.configure.load_library('pint')

quantity = puw.quantity([1, 2], 'meter')

with pytest.raises(ValueError):
puw.utils.numpy.repeat(quantity, 1, value_type='invalid')

30 changes: 30 additions & 0 deletions tests/utils/sequences/test_concatenate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,33 @@ def test_concatenate_1():
assert value.shape==(10, 6, 3)


@pytest.mark.parametrize('value_type', ['list', 'tuple', 'numpy.ndarray'])
def test_concatenate_flat_sequence_value_types(value_type):
puw.configure.reset()
puw.configure.load_library('pint')

sequence = [puw.quantity(index + 1, 'meter') for index in range(3)]
concatenated = puw.utils.sequences.concatenate(sequence, to_unit='centimeter', value_type=value_type)

value = puw.get_value(concatenated)
assert np.array_equal(value, np.array([100, 200, 300]))
assert puw.get_unit(concatenated, to_form='string') == 'centimeter'


def test_concatenate_nested_sequences_and_error_branch():
puw.configure.reset()
puw.configure.load_library('pint')

nested = [
[puw.quantity(1, 'meter'), puw.quantity(2, 'meter')],
[puw.quantity(3, 'meter')],
]

concatenated = puw.utils.sequences.concatenate(nested, to_unit='centimeter', value_type='numpy.ndarray')
value = puw.get_value(concatenated)
assert np.array_equal(value, np.array([100, 200, 300]))

with pytest.raises(ValueError):
puw.utils.sequences.concatenate(nested, value_type='invalid')