Skip to content
This repository has been archived by the owner on Nov 30, 2023. It is now read-only.

Commit

Permalink
Started to add a Peano Curve implementation. #1
Browse files Browse the repository at this point in the history
  • Loading branch information
bramp committed Aug 8, 2016
1 parent 584f3da commit ff22ed6
Show file tree
Hide file tree
Showing 2 changed files with 378 additions and 0 deletions.
112 changes: 112 additions & 0 deletions peano.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package hilbert

type Peano struct {
N int // Always a power of three, and is the width/height of the space.
}

// isPow3 returns true if n is a power of 3.
func isPow3(n float64) bool {
// I wanted to do the following, but due to subtle floating point issues it didn't work
// const ln3 = 1.098612288668109691395245236922525704647490557822749451734694333637494 // https://oeis.org/A002391
//return n == math.Pow(3, math.Trunc(math.Log(n) / ln3))
for n >= 1 {
if n == 1 {
return true
}
n = n / 3
}
return false
}

// NewPeano returns a new Peano space filling curve which maps integers to and from the curve.
// n must be a power of three.
func NewPeano(n int) (*Peano, error) {
if n <= 0 {
return nil, ErrNotPositive
}

if !isPow3(float64(n)) {
return nil, ErrNotPowerOfThree
}

return &Peano{
N: n,
}, nil
}

// Returns the width and height of the 2D space.
func (s *Peano) GetDimensions() (int, int) {
return s.N, s.N
}

// Map transforms a one dimension value, t, in the range [0, n^3-1] to coordinates on the Peano
// curve in the two-dimension space, where x and y are within [0,n-1].
func (p *Peano) Map(t int) (x, y int, err error) {
if t < 0 || t >= p.N*p.N {
return -1, -1, ErrOutOfRange
}

for i := 1; i < p.N; i = i * 3 {
s := t % 9

// rx/ry are the coordinates in the 3x3 grid
rx := int(s / 3)
ry := int(s % 3)
if rx == 1 {
ry = 2 - ry
}

// now based on depth rotate our points
if i > 1 {
x, y = p.rotate(i, x, y, s)
}

x += rx * i
y += ry * i

t /= 9
}

return x, y, nil
}

func (p *Peano) rotate(n, x, y, s int) (int, int) {

if n == 1 {
// Special case
return x, y
}

n = n - 1
switch s {
case 0:
return x, y // normal
case 1:
return n - x, y // fliph
case 2:
return x, y // normal
case 3:
return x, n - y // flipv
case 4:
return n - x, n - y // flipv and fliph
case 5:
return x, n - y // flipv
case 6:
return x, y // normal
case 7:
return n - x, y // fliph
case 8:
return x, y // normal
}

panic("assertation failure: this line should never be reached")
}

func (p *Peano) MapInverse(x, y int) (t int, err error) {
if x < 0 || x >= p.N || y < 0 || y >= p.N {
return -1, ErrOutOfRange
}

panic("Not finished")
return -1, nil
}
266 changes: 266 additions & 0 deletions peano_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hilbert

import (
"testing"
)

/*
import (
"math/rand"
"testing"
"github.com/google/hilbert"
)
const benchmarkN = 81
*/

// Test cases below assume N=9
var peanoTestCases = []struct {
d, x, y int
}{
{0, 0, 0},
{1, 0, 1},
{2, 0, 2},
{3, 1, 2},
{4, 1, 1},
{5, 1, 0},
{6, 2, 0},
{7, 2, 1},
{8, 2, 2},
{9, 2, 3},
}

/*
func TestNewErrors(t *testing.T) {
var newTestCases = []struct {
n int
wantErr error
}{
{-1, hilbert.ErrNotPositive},
{0, hilbert.ErrNotPositive},
{3, hilbert.ErrNotPowerOfTwo},
{5, hilbert.ErrNotPowerOfTwo},
}
for _, tc := range newTestCases {
s, err := hilbert.New(tc.n)
if s != nil || err != tc.wantErr {
t.Errorf("New(%d) did not fail, want %q, got (%+v, %q)", tc.n, tc.wantErr, s, err)
}
}
}
func TestMapRangeErrors(t *testing.T) {
var mapRangeTestCases = []struct {
d int
wantErr error
}{
{-1, hilbert.ErrOutOfRange},
{0, nil},
{255, nil},
{256, hilbert.ErrOutOfRange},
}
s, err := hilbert.New(16)
if err != nil {
t.Fatalf("Failed to create hibert space: %s", err)
}
for _, tc := range mapRangeTestCases {
if _, _, err = s.Map(tc.d); err != tc.wantErr {
t.Errorf("Map(%d) did not fail, want %q, got %q", tc.d, tc.wantErr, err)
}
}
}
func TestMapInverseRangeErrors(t *testing.T) {
var mapInverseRangeTestCases = []struct {
x, y int
wantErr error
}{
{0, 0, nil},
{15, 15, nil},
{-1, 0, hilbert.ErrOutOfRange},
{0, -1, hilbert.ErrOutOfRange},
{16, 0, hilbert.ErrOutOfRange},
{0, 16, hilbert.ErrOutOfRange},
}
s, err := hilbert.New(16)
if err != nil {
t.Fatalf("Failed to create hibert space: %s", err)
}
for _, tc := range mapInverseRangeTestCases {
if _, err = s.MapInverse(tc.x, tc.y); err != tc.wantErr {
t.Errorf("MapInverse(%d, %d) did not fail, want %q, got %q", tc.x, tc.y, tc.wantErr, err)
}
}
}
func TestSmallMap(t *testing.T) {
s, err := hilbert.New(1)
if err != nil {
t.Fatalf("Failed to create hibert space: %s", err)
}
x, y, err := s.Map(0)
if err != nil {
t.Errorf("Map(0) returned error: %s", err)
}
if x != 0 || y != 0 {
t.Errorf("Map(0) failed, want (0, 0), got (%d, %d)", x, y)
}
d, err := s.MapInverse(0, 0)
if err != nil {
t.Errorf("MapInverse(0,0) returned error: %s", err)
}
if d != 0 {
t.Errorf("MapInverse(0, 0) failed, want 0, got %d", d)
}
}
*/
func TestPeanoMap(t *testing.T) {
s, err := NewPeano(9)
if err != nil {
t.Fatalf("Failed to create peano space: %s", err)
}

for _, tc := range peanoTestCases {
x, y, err := s.Map(tc.d)
if err != nil {
t.Errorf("Map(%d) returned error: %s", tc.d, err)
}
if x != tc.x || y != tc.y {
t.Errorf("Map(%d) = (%d, %d) want (%d, %d)", tc.d, x, y, tc.x, tc.y)
}
}
}

/*
func TestMapInverse(t *testing.T) {
s, err := hilbert.New(16)
if err != nil {
t.Fatalf("Failed to create hibert space: %s", err)
}
for _, tc := range testCases {
d, err := s.MapInverse(tc.x, tc.y)
if err != nil {
t.Errorf("MapInverse(%d, %d) returned error: %s", tc.x, tc.y, err)
}
if d != tc.d {
t.Errorf("MapInverse(%d, %d) failed, want %d, got %d", tc.x, tc.y, tc.d, d)
}
}
}
func TestAllMapValues(t *testing.T) {
s, err := hilbert.New(16)
if err != nil {
t.Fatalf("Failed to create hibert space: %s", err)
}
for d := 0; d < s.N*s.N; d++ {
// Map forwards and then back
x, y, err := s.Map(d)
if err != nil {
t.Errorf("Map(%d) returned error: %s", d, err)
}
if x < 0 || x >= s.N || y < 0 || y >= s.N {
t.Errorf("Map(%d) returned x,y out of range: (%d, %d)", d, x, y)
}
dPrime, err := s.MapInverse(x, y)
if err != nil {
t.Errorf("MapInverse(%d, %d) returned error: %s", x, y, err)
}
if d != dPrime {
t.Errorf("Failed Map(%d) -> MapInverse(%d, %d) -> %d", d, x, y, dPrime)
}
}
}
func BenchmarkMap(b *testing.B) {
for i := 0; i < b.N; i++ {
s, err := hilbert.New(benchmarkN)
if err != nil {
b.Fatalf("Failed to create hibert space: %s", err)
}
for d := 0; d < benchmarkN*benchmarkN; d++ {
s.Map(d)
}
}
}
func BenchmarkMapRandom(b *testing.B) {
for i := 0; i < b.N; i++ {
s, err := hilbert.New(benchmarkN)
if err != nil {
b.Fatalf("Failed to create hibert space: %s", err)
}
for d := 0; d < benchmarkN*benchmarkN; d++ {
rd := rand.Intn(benchmarkN * benchmarkN) // Pick a random d
s.Map(rd)
}
}
}
func BenchmarkMapInverse(b *testing.B) {
for i := 0; i < b.N; i++ {
s, err := hilbert.New(benchmarkN)
if err != nil {
b.Fatalf("Failed to create hibert space: %s", err)
}
for x := 0; x < benchmarkN; x++ {
for y := 0; y < benchmarkN; y++ {
s.MapInverse(x, y)
}
}
}
}
*/

func TestIsPow3(t *testing.T) {
testCases := []struct {
in float64
want bool
}{
{-1, false},
{0, false},
{1, true},
{2, false},
{3, true},
{3.1, false},
{4, false},
{5, false},
{8.9999, false},
{9, true},
{9.00001, false},
{27, true},
{59049, true},
}

for _, tc := range testCases {
got := isPow3(tc.in)
if got != tc.want {
t.Errorf("isPow3(%f) = %t want %t", tc.in, got, tc.want)
}
}
}

0 comments on commit ff22ed6

Please sign in to comment.