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
57 changes: 37 additions & 20 deletions gemd/entity/bounds/integer_bounds.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
"""Bounds an integer to be between two values."""
from math import isfinite
from typing import Union

from gemd.entity.bounds.base_bounds import BaseBounds

from typing import Union
__all__ = ["IntegerBounds"]


class IntegerBounds(BaseBounds, typ="integer_bounds"):
"""
Bounded subset of the integers, parameterized by a lower and upper bound.
"""Bounded subset of the integers, parameterized by a lower and upper bound."""

Parameters
----------
lower_bound: int
Lower endpoint.
upper_bound: int
Upper endpoint.
def __init__(self, lower_bound: int, upper_bound: int):
self._lower_bound = None
self._upper_bound = None

"""

def __init__(self, lower_bound=None, upper_bound=None):
self.lower_bound = lower_bound
self.upper_bound = upper_bound

if self.lower_bound is None or abs(self.lower_bound) >= float("inf"):
raise ValueError("Lower bound must be given and finite: {}".format(self.lower_bound))

if self.upper_bound is None or abs(self.upper_bound) >= float("inf"):
raise ValueError("Upper bound must be given and finite")

if self.upper_bound < self.lower_bound:
raise ValueError("Upper bound must be greater than or equal to lower bound")
@property
def lower_bound(self) -> int:
"""The lower endpoint of the permitted range."""
return self._lower_bound

@lower_bound.setter
def lower_bound(self, value: int):
"""Set the lower endpoint of the permitted range."""
if value is None or not isfinite(value) or int(value) != float(value):
raise ValueError(f"Lower bound must be given, integer and finite: {value}")
if self.upper_bound is not None and value > self.upper_bound:
raise ValueError(f"Upper bound ({self.upper_bound}) must be "
f"greater than or equal to lower bound ({value})")
self._lower_bound = int(value)

@property
def upper_bound(self) -> int:
"""The upper endpoint of the permitted range."""
return self._upper_bound

@upper_bound.setter
def upper_bound(self, value: int):
"""Set the upper endpoint of the permitted range."""
if value is None or not isfinite(value) or int(value) != float(value):
raise ValueError(f"Upper bound must be given, integer and finite: {value}")
if self.lower_bound is not None and value < self.lower_bound:
raise ValueError(f"Upper bound ({value}) must be "
f"greater than or equal to lower bound ({self.lower_bound})")
self._upper_bound = int(value)

def contains(self, bounds: Union[BaseBounds, "BaseValue"]) -> bool: # noqa: F821
"""
Expand Down
77 changes: 47 additions & 30 deletions gemd/entity/bounds/real_bounds.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,70 @@
"""Bound a real number to be between two values."""
from math import isfinite
from typing import Union

from gemd.entity.bounds.base_bounds import BaseBounds
import gemd.units as units

from typing import Union


class RealBounds(BaseBounds, typ="real_bounds"):
"""
Bounded subset of the real numbers, parameterized by a lower and upper bound.

Parameters
----------
lower_bound: float
Lower endpoint.
upper_bound: float
Upper endpoint.
default_units: str
A string describing the units. Units must be present and parseable by Pint.
An empty string can be used for the units of a dimensionless quantity.
"""Bounded subset of the real numbers, parameterized by a lower and upper bound."""

"""
def __init__(self, lower_bound: float, upper_bound: float, default_units: str):
self._default_units = None
self._lower_bound = None
self._upper_bound = None

def __init__(self, lower_bound=None, upper_bound=None, default_units=None):
self.default_units = default_units
self.lower_bound = lower_bound
self.upper_bound = upper_bound

self._default_units = None
self.default_units = default_units

if self.lower_bound is None or abs(self.lower_bound) >= float("inf"):
raise ValueError("Lower bound must be given and finite: {}".format(self.lower_bound))

if self.upper_bound is None or abs(self.upper_bound) >= float("inf"):
raise ValueError("Upper bound must be given and finite")
@property
def lower_bound(self) -> float:
"""The lower endpoint of the permitted range."""
return self._lower_bound

@lower_bound.setter
def lower_bound(self, value: float):
"""Set the lower endpoint of the permitted range."""
if value is None or not isfinite(value):
raise ValueError(f"Lower bound must be given and finite: {value}")
if self.upper_bound is not None and value > self.upper_bound:
raise ValueError(f"Upper bound ({self.upper_bound}) must be "
f"greater than or equal to lower bound ({value})")
self._lower_bound = float(value)

if self.upper_bound < self.lower_bound:
raise ValueError("Upper bound must be greater than or equal to lower bound")
@property
def upper_bound(self) -> float:
"""The upper endpoint of the permitted range."""
return self._upper_bound

@upper_bound.setter
def upper_bound(self, value: float):
"""Set the upper endpoint of the permitted range."""
if value is None or not isfinite(value):
raise ValueError(f"Upper bound must be given and finite: {value}")
if self.lower_bound is not None and value < self.lower_bound:
raise ValueError(f"Upper bound ({value}) must be "
f"greater than or equal to lower bound ({self.lower_bound})")
self._upper_bound = float(value)

@property
def default_units(self):
"""Get default units."""
def default_units(self) -> str:
"""
A string describing the units.

Units must be present and parseable by Pint.
An empty string can be used for the units of a dimensionless quantity.
"""
return self._default_units

@default_units.setter
def default_units(self, default_units):
def default_units(self, default_units: str):
"""Set the string describing the units."""
if default_units is None:
raise ValueError("Real bounds must have units. "
"Use an empty string for a dimensionless quantity.")
self._default_units = units.parse_units(default_units)
self._default_units = units.parse_units(default_units, return_unit=False)

def contains(self, bounds: Union[BaseBounds, "BaseValue"]) -> bool: # noqa: F821
"""
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
packages.append("")

setup(name='gemd',
version='1.17.1',
version='1.18.0',
python_requires='>=3.8',
url='http://github.com/CitrineInformatics/gemd-python',
description="Python binding for Citrine's GEMD data model",
Expand Down
29 changes: 28 additions & 1 deletion tests/entity/bounds/test_integer_bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,42 @@

def test_errors():
"""Make sure invalid bounds raise value errors."""
with pytest.raises(ValueError):
with pytest.raises(TypeError):
IntegerBounds()

with pytest.raises(TypeError):
IntegerBounds("0", 10)

with pytest.raises(ValueError):
IntegerBounds(float("-inf"), 0)

with pytest.raises(ValueError):
IntegerBounds(0.5, 1)

with pytest.raises(ValueError):
IntegerBounds(0, float("inf"))

with pytest.raises(ValueError):
IntegerBounds(0, 0.5)

with pytest.raises(TypeError):
IntegerBounds(0, "10")

with pytest.raises(ValueError):
IntegerBounds(10, 1)

with pytest.raises(ValueError):
bnd = IntegerBounds(0, 1)
bnd.lower_bound = 10

with pytest.raises(ValueError):
bnd = IntegerBounds(0, 1)
bnd.upper_bound = -1

bnd = IntegerBounds(0, 1)
assert bnd.lower_bound == 0
assert bnd.upper_bound == 1


def test_incompatible_types():
"""Make sure that incompatible types aren't contained or validated."""
Expand Down
18 changes: 13 additions & 5 deletions tests/entity/bounds/test_real_bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,28 @@ def test_contains_incompatible_units():

def test_constructor_error():
"""Test that invalid real bounds cannot be constructed."""
with pytest.raises(ValueError):
with pytest.raises(TypeError):
RealBounds()

with pytest.raises(ValueError):
RealBounds(0, float("inf"), "meter")
RealBounds(lower_bound=0, upper_bound=float("inf"), default_units="meter")

with pytest.raises(ValueError):
RealBounds(None, 10, '')
RealBounds(lower_bound=None, upper_bound=10, default_units='')

with pytest.raises(ValueError):
RealBounds(0, 100)
RealBounds(lower_bound=0, upper_bound=100, default_units=None)

with pytest.raises(ValueError):
RealBounds(100, 0, "m")
RealBounds(lower_bound=100, upper_bound=0, default_units="m")

with pytest.raises(ValueError):
bnd = RealBounds(lower_bound=0, upper_bound=10, default_units="m")
bnd.lower_bound = 100

bnd = RealBounds(0, 1, "m")
assert bnd.lower_bound == 0.0
assert bnd.upper_bound == 1.0


def test_type_mismatch():
Expand Down