Skip to content

Commit

Permalink
dev to main (#32)
Browse files Browse the repository at this point in the history
* Input Type Validation

Made all functions that take input compatible with floats that are equal to ints. Fixed a few formatting issues.

Added type validation for __init__:
Checks wether p and n can be converted cleanly to int using %1. If they can't a TypeError is thrown. If %1 is zero then they are cast to int using int().
Added Type Validation for coordinates_from_distance:
If h%1 is not 0 a TypeError is thrown. If h%1 is 0 h is cast to int using int().
Standardized Error Messages in coordinates_from_distance:
Changed the ValueErrors thrown to the same formatting as the ValueErrors in __init__.
Added Type Validation for distance_from_coordinates:
If for any element in x element%1 is not 0 then a TypeError is thrown. Otherwise x is iterated through and each element is cast to int using int().
Formatting Correction in _binary_repr:
"width:int" ->"width: int"

* Fixed syntax and type checking

Made an error in a error message syntax
0.0 != False so I changed how %1 is checked.

* more tests and travis (#26)

* more tests and travis

* add empty requirements in order to not have to change travis later

* misname

* added python 3.6 and 3.7 to travis (#27)

* Update README.rst

* Update README.rst

* Trav multi version (#28)

* added python 3.6 and 3.7 to travis

* travis typo

* More tests explicit min (#29)

* test bounds and explicit min

* fixed docstring

* extended type hints to floats (#30)

* new pypi version (#31)

Co-authored-by: C-Jameson <[email protected]>
  • Loading branch information
galtay and alicehayes authored Nov 11, 2020
1 parent 3363854 commit fcd750a
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 35 deletions.
11 changes: 11 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
language: python
python:
- "3.6"
- "3.7"
- "3.8"
# command to install dependencies
install:
- pip install -r requirements.txt
# command to run tests
script:
- pytest
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.. image:: https://travis-ci.com/galtay/hilbertcurve.svg?branch=develop
:target: https://travis-ci.com/galtay/hilbertcurve

============
Introduction
============
Expand Down
18 changes: 9 additions & 9 deletions examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,30 @@

# calculate distances along a hilbert curve given coordinates
p = 1
N = 2
hilbert_curve = HilbertCurve(p, N)
n = 2
hilbert_curve = HilbertCurve(p, n)
for coords in [[0,0], [0,1], [1,1], [1,0]]:
dist = hilbert_curve.distance_from_coordinates(coords)
print(f'distance(x={coords}) = {dist}')

# calculate coordinates given distances along a hilbert curve
p = 1
N = 2
hilbert_curve = HilbertCurve(p, N)
n = 2
hilbert_curve = HilbertCurve(p, n)
for ii in range(4):
print('coords(h={},p={},N={}) = {}'.format(
ii, p, N, hilbert_curve.coordinates_from_distance(ii)))
print('coords(h={},p={},n={}) = {}'.format(
ii, p, n, hilbert_curve.coordinates_from_distance(ii)))


# due to the magic of arbitrarily large integers in
# Python (https://www.python.org/dev/peps/pep-0237/)
# these calculations can be done with absurd numbers
p = 512
N = 10
hilbert_curve = HilbertCurve(p, N)
n = 10
hilbert_curve = HilbertCurve(p, n)
ii = 123456789101112131415161718192021222324252627282930
coords = hilbert_curve.coordinates_from_distance(ii)
print('coords(h={},p={},N={}) = {}'.format(ii, p, N, coords))
print('coords(h={},p={},n={}) = {}'.format(ii, p, n, coords))


coords = [121075, 67332, 67326, 108879, 26637, 43346, 23848, 1551, 68130, 84004]
Expand Down
2 changes: 1 addition & 1 deletion hilbertcurve/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Metadata for this package."""

__package_name__ = "hilbertcurve"
__version__ = "1.0.4"
__version__ = "1.0.5"
56 changes: 39 additions & 17 deletions hilbertcurve/hilbertcurve.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,45 @@
discrete distances along the Hilbert curve (indexed from :math:`0` to
:math:`2^{N p} - 1`).
"""
from typing import Iterable, List
from typing import Iterable, List, Union


def _binary_repr(num: int, width:int) -> str:
def _binary_repr(num: int, width: int) -> str:
"""Return a binary string representation of `num` zero padded to `width`
bits."""
return format(num, 'b').zfill(width)


class HilbertCurve:

def __init__(self, p: int, n: int) -> None:
def __init__(self, p: Union[int, float], n: Union[int, float]) -> None:
"""Initialize a hilbert curve with,
Args:
p (int): iterations to use in constructing the hilbert curve
n (int): number of dimensions
p (int or float): iterations to use in constructing the hilbert curve.
if float, must satisfy p % 1 = 0
n (int or float): number of dimensions.
if float must satisfy n % 1 = 0
"""
if p <= 0:
raise ValueError('p must be > 0')
if n <= 0:
raise ValueError('n must be > 0')
self.p = p
self.n = n

# maximum distance along curve
if (p % 1) != 0:
raise TypeError("p is not an integer and can not be converted")
if (n % 1) != 0:
raise TypeError("n is not an integer and can not be converted")

self.p = int(p)
self.n = int(n)

if self.p <= 0:
raise ValueError('p must be > 0 (got p={} as input)'.format(p))
if self.n <= 0:
raise ValueError('n must be > 0 (got n={} as input)'.format(n))

# minimum and maximum distance along curve
self.min_h = 0
self.max_h = 2**(self.p * self.n) - 1

# maximum coordinate value in any dimension
# minimum and maximum coordinate value in any dimension
self.min_x = 0
self.max_x = 2**self.p - 1

def _hilbert_integer_to_transpose(self, h: int) -> List[int]:
Expand Down Expand Up @@ -80,10 +90,15 @@ def coordinates_from_distance(self, h: int) -> List[int]:
x (list): transpose of h
(n components with values between 0 and 2**p-1)
"""

if (h % 1) != 0:
raise TypeError("h is not an integer and can not be converted")
if h > self.max_h:
raise ValueError('h={} is greater than 2**(p*N)-1={}'.format(h, self.max_h))
raise ValueError('h must be < 2**(p*N)-1={}')
if h < 0:
raise ValueError('h={} but must be > 0'.format(h))
raise ValueError('h must be > 0')

h = int(h)

x = self._hilbert_integer_to_transpose(h)
Z = 2 << (self.p-1)
Expand Down Expand Up @@ -131,11 +146,18 @@ def distance_from_coordinates(self, x_in: List[int]) -> int:
'invalid coordinate input x={}. one or more dimensions have a '
'value greater than 2**p-1={}'.format(x, self.max_x))

if any(elx < 0 for elx in x):
if any(elx < self.min_x for elx in x):
raise ValueError(
'invalid coordinate input x={}. one or more dimensions have a '
'value less than 0'.format(x))

if any((elx % 1) != 0 for elx in x):
raise TypeError(
'invalid coordinate input x={}. one or more dimensions is not '
'an integer and can not be converted'.format(x))

for i in range(len(x)): x[i] = int(x[i])

M = 1 << (self.p - 1)

# Inverse undo excess work
Expand Down
Empty file added requirements.txt
Empty file.
62 changes: 54 additions & 8 deletions tests/test_hilbertcurve.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python
"""Test the functions in hilbert.py"""

import pytest
import unittest
from hilbertcurve.hilbertcurve import HilbertCurve

Expand All @@ -24,8 +24,8 @@ def test_10590(self):
X[2] = 0b00110 = 6
"""
p = 5
N = 3
hilbert_curve = HilbertCurve(p, N)
n = 3
hilbert_curve = HilbertCurve(p, n)
h = 10590
expected_x = [13, 19, 6]
actual_x = hilbert_curve._hilbert_integer_to_transpose(h)
Expand All @@ -51,8 +51,8 @@ def test_13_19_6(self):
X[2] = 0b00110 = 6
"""
p = 5
N = 3
hilbert_curve = HilbertCurve(p, N)
n = 3
hilbert_curve = HilbertCurve(p, n)
x = [13, 19, 6]
expected_h = 10590
actual_h = hilbert_curve._transpose_to_hilbert_integer(x)
Expand All @@ -64,15 +64,61 @@ class TestReversibility(unittest.TestCase):
def test_reversibility(self):
"""Assert coordinates_from_distance and distance_from_coordinates
are inverse operations."""
N = 3
n = 3
p = 5
hilbert_curve = HilbertCurve(p, N)
n_h = 2**(N * p)
hilbert_curve = HilbertCurve(p, n)
n_h = 2**(n * p)
for h in range(n_h):
x = hilbert_curve.coordinates_from_distance(h)
h_test = hilbert_curve.distance_from_coordinates(x)
self.assertEqual(h, h_test)

class TestInitIntConversion(unittest.TestCase):
"""Test __init__ conversion of floating point to integers."""

def test_pt_oh(self):
"""Assert x.0 goes to x"""
n = 3.0
n_int = 3
p = 5
hilbert_curve = HilbertCurve(p, n)
self.assertTrue(isinstance(hilbert_curve.n, int))
self.assertEqual(hilbert_curve.n, n_int)

n = 3
p_int = 5
p = 5.0
hilbert_curve = HilbertCurve(p, n)
self.assertTrue(isinstance(hilbert_curve.p, int))
self.assertEqual(hilbert_curve.p, p_int)

def test_pt_one(self):
"""Assert x.1 raises an error"""
n = 3
p = 5.1
with pytest.raises(TypeError):
hilbert_curve = HilbertCurve(p, n)

n = 3.1
p = 5
with pytest.raises(TypeError):
hilbert_curve = HilbertCurve(p, n)

class TestInitBounds(unittest.TestCase):
"""Test __init__ bounds on n and p."""

def test_pt_one(self):
"""Assert x=0 raises an error"""
n = 0
p = 5
with pytest.raises(ValueError):
hilbert_curve = HilbertCurve(p, n)

n = 3
p = 0
with pytest.raises(ValueError):
hilbert_curve = HilbertCurve(p, n)


if __name__ == '__main__':
unittest.main()

0 comments on commit fcd750a

Please sign in to comment.