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
13 changes: 7 additions & 6 deletions tests/astar_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
import numpy
from worldengine import astar
from worldengine.common import _equal


class TestCommon(unittest.TestCase):
Expand All @@ -11,13 +12,13 @@ def test_traversal(self):
line.fill(1.0)
test_map[10, :] = line
test_map[10, 18] = 0.0
path_data = [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [0, 9],
[1, 9], [2, 9], [3, 9], [4, 9], [5, 9], [6, 9], [7, 9], [8, 9], [9, 9],
[10, 9], [11, 9], [12, 9], [13, 9], [14, 9], [15, 9], [16, 9], [17, 9],
[18, 9], [18, 10], [18, 11], [18, 12], [18, 13], [18, 14], [18, 15],
[18, 16], [18, 17], [18, 18], [18, 19], [19, 19]]
path_data = numpy.array([[0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [0, 9],
[1, 9], [2, 9], [3, 9], [4, 9], [5, 9], [6, 9], [7, 9], [8, 9], [9, 9],
[10, 9], [11, 9], [12, 9], [13, 9], [14, 9], [15, 9], [16, 9], [17, 9],
[18, 9], [18, 10], [18, 11], [18, 12], [18, 13], [18, 14], [18, 15],
[18, 16], [18, 17], [18, 18], [18, 19], [19, 19]])
shortest_path = astar.PathFinder().find(test_map, [0, 0], [19, 19])
self.assertEqual(path_data, shortest_path)
self.assertTrue(_equal(path_data, numpy.array(shortest_path)))

if __name__ == '__main__':
unittest.main()
9 changes: 5 additions & 4 deletions tests/common_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
import numpy
from worldengine.common import Counter, anti_alias, array_to_matrix, get_verbose, \
matrix_min_and_max, rescale_value, set_verbose

Expand Down Expand Up @@ -59,14 +60,14 @@ def test_rescale_value(self):
self.assertAlmostEqual(10.0, rescale_value(1.0, 0.0, 1.0, -10.0, 10.0))

def test_antialias(self):
original = [[0.5, 0.12, 0.7, 0.15, 0.0],
[0.0, 0.12, 0.7, 0.7, 8.0],
[0.2, 0.12, 0.7, 0.7, 4.0]]
original = numpy.array([[0.5, 0.12, 0.7, 0.15, 0.0],
[0.0, 0.12, 0.7, 0.7, 8.0],
[0.2, 0.12, 0.7, 0.7, 4.0]])
antialiased = anti_alias(original, 1)
self.assertAlmostEquals(1.2781818181818183, antialiased[0][0])
self.assertAlmostEquals(0.4918181818181818, antialiased[1][2])

original = [[0.8]]
original = numpy.array([[0.8]])
antialiased = anti_alias(original, 10)
self.assertAlmostEquals(0.8, antialiased[0][0])

Expand Down
65 changes: 29 additions & 36 deletions tests/serialization_test.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import unittest
from worldengine.plates import Step, world_gen
from worldengine.world import World
from worldengine.common import _equal
import tempfile
import os
import numpy


def _sort(l):
l2 = l
l2.sort()
return l2


class TestSerialization(unittest.TestCase):

Expand All @@ -23,48 +16,48 @@ def test_pickle_serialize_unserialize(self):
w.to_pickle_file(f)
unserialized = World.from_pickle_file(f)
os.remove(f)
self.assertEqual(w.elevation['data'], unserialized.elevation['data'])
self.assertTrue(_equal(w.elevation['data'], unserialized.elevation['data']))
self.assertEqual(w.elevation['thresholds'], unserialized.elevation['thresholds'])
self.assertEqual(w.ocean, unserialized.ocean)
self.assertEqual(w.biome, unserialized.biome)
self.assertEqual(w.humidity, unserialized.humidity)
self.assertTrue(numpy.array_equiv(w.irrigation, unserialized.irrigation))
self.assertEqual(w.permeability, unserialized.permeability)
self.assertEqual(w.watermap, unserialized.watermap)
self.assertEqual(w.precipitation, unserialized.precipitation)
self.assertEqual(w.temperature, unserialized.temperature)
self.assertEqual(w.sea_depth, unserialized.sea_depth)
self.assertTrue(_equal(w.ocean, unserialized.ocean))
self.assertTrue(_equal(w.biome, unserialized.biome))
self.assertTrue(_equal(w.humidity, unserialized.humidity))
self.assertTrue(_equal(w.irrigation, unserialized.irrigation))
self.assertTrue(_equal(w.permeability, unserialized.permeability))
self.assertTrue(_equal(w.watermap, unserialized.watermap))
self.assertTrue(_equal(w.precipitation, unserialized.precipitation))
self.assertTrue(_equal(w.temperature, unserialized.temperature))
self.assertTrue(_equal(w.sea_depth, unserialized.sea_depth))
self.assertEquals(w.seed, unserialized.seed)
self.assertEquals(w.n_plates, unserialized.n_plates)
self.assertEquals(w.ocean_level, unserialized.ocean_level)
self.assertEquals(w.lake_map, unserialized.lake_map)
self.assertEquals(w.river_map, unserialized.river_map)
self.assertTrue(_equal(w.ocean_level, unserialized.ocean_level))
self.assertTrue(_equal(w.lake_map, unserialized.lake_map))
self.assertTrue(_equal(w.river_map, unserialized.river_map))
self.assertEquals(w.step, unserialized.step)
self.assertEqual(_sort(dir(w)), _sort(dir(unserialized)))
self.assertEqual(sorted(dir(w)), sorted(dir(unserialized)))
self.assertEqual(w, unserialized)

def test_protobuf_serialize_unserialize(self):
w = world_gen("Dummy", 32, 16, 1, step=Step.get_by_name("full"))
serialized = w.protobuf_serialize()
unserialized = World.protobuf_unserialize(serialized)
self.assertEqual(w.elevation['data'], unserialized.elevation['data'])
self.assertTrue(_equal(w.elevation['data'], unserialized.elevation['data']))
self.assertEqual(w.elevation['thresholds'], unserialized.elevation['thresholds'])
self.assertEqual(w.ocean, unserialized.ocean)
self.assertEqual(w.biome, unserialized.biome)
self.assertEqual(w.humidity, unserialized.humidity)
self.assertTrue(numpy.array_equiv(w.irrigation, unserialized.irrigation))
self.assertEqual(w.permeability, unserialized.permeability)
self.assertEqual(w.watermap, unserialized.watermap)
self.assertEqual(w.precipitation, unserialized.precipitation)
self.assertEqual(w.temperature, unserialized.temperature)
self.assertEqual(w.sea_depth, unserialized.sea_depth)
self.assertTrue(_equal(w.ocean, unserialized.ocean))
self.assertTrue(_equal(w.biome, unserialized.biome))
self.assertTrue(_equal(w.humidity, unserialized.humidity))
self.assertTrue(_equal(w.irrigation, unserialized.irrigation))
self.assertTrue(_equal(w.permeability, unserialized.permeability))
self.assertTrue(_equal(w.watermap, unserialized.watermap))
self.assertTrue(_equal(w.precipitation, unserialized.precipitation))
self.assertTrue(_equal(w.temperature, unserialized.temperature))
self.assertTrue(_equal(w.sea_depth, unserialized.sea_depth))
self.assertEquals(w.seed, unserialized.seed)
self.assertEquals(w.n_plates, unserialized.n_plates)
self.assertEquals(w.ocean_level, unserialized.ocean_level)
self.assertEquals(w.lake_map, unserialized.lake_map)
self.assertEquals(w.river_map, unserialized.river_map)
self.assertTrue(_equal(w.ocean_level, unserialized.ocean_level))
self.assertTrue(_equal(w.lake_map, unserialized.lake_map))
self.assertTrue(_equal(w.river_map, unserialized.river_map))
self.assertEquals(w.step, unserialized.step)
self.assertEqual(_sort(dir(w)), _sort(dir(unserialized)))
self.assertEqual(sorted(dir(w)), sorted(dir(unserialized)))
self.assertEqual(w, unserialized)


Expand Down
14 changes: 2 additions & 12 deletions worldengine/astar.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ def get_node(self, location):
if x < 0 or x >= self.w or y < 0 or y >= self.h:
return None
d = self.m[(y * self.w) + x]

return Node(location, d, ((y * self.w) + x))

def get_adjacent_nodes(self, cur_node, destination):
Expand Down Expand Up @@ -198,14 +197,6 @@ def _handle_node(self, x, y, from_node, destination_x, destination_y):
return None


def _matrix_to_array(matrix):
array = []
for row in matrix:
for cell in row:
array.append(cell)
return array


class PathFinder:
"""Using the a* algorithm we will try to find the best path between two
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method ate huge amounts of time in comparison to everything else. I hope the replacement in line 215 below is ok, but I am scratching my head about that a bit. (tests and output, however, are perfect)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, that is numpy's strong point. good job!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just profiled a<b<c to gain about a millisecond while this change gained about 25% of the total runtime. :D I shouldn't try to look into those super-small things anymore. :P

points.
Expand All @@ -219,10 +210,9 @@ def find(height_map, source, destination):
sx, sy = source
dx, dy = destination
path = []
width = len(height_map[0])
height = len(height_map)
height, width = height_map.shape

graph = _matrix_to_array(height_map) # flatten array
graph = height_map.flatten('C') #flatten array (row-major)

pathfinder = AStar(SQMapHandler(graph, width, height))
start = SQLocation(sx, sy)
Expand Down
2 changes: 1 addition & 1 deletion worldengine/biome.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,6 @@ def biome_name_to_index(biome_name):

def biome_index_to_name(biome_index):
names = sorted(_BiomeMetaclass.biomes.keys())
if biome_index < 0 or biome_index >= len(names):
if not 0 <= biome_index < len(names):
raise Exception("Not found")
return names[biome_index]
3 changes: 2 additions & 1 deletion worldengine/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys
from argparse import ArgumentParser
import os
import numpy
import pickle
import random
import worldengine.generation as geo
Expand Down Expand Up @@ -95,7 +96,7 @@ def generate_plates(seed, world_name, output_dir, width, height,
num_plates=num_plates)

world = World(world_name, width, height, seed, num_plates, -1.0, "plates")
world.set_elevation(array_to_matrix(elevation, width, height), None)
world.set_elevation(numpy.array(elevation).reshape(height, width), None)
world.set_plates(array_to_matrix(plates, width, height))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you flattened 2d array into 1d array with numpy, I know that numpy can take a 1d array and numpy.reshape into an Xd array. Can you replace this function too please? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it can do that. Is elevation a 1d-array, though? I didn't notice. Maybe it's not time to sleep yet, after all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done. Thanks for the hint, I totally missed this.


# Generate images
Expand Down
46 changes: 38 additions & 8 deletions worldengine/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys
import copy
import numpy #for the _equal method only

# ----------------
# Global variables
Expand Down Expand Up @@ -80,32 +81,31 @@ def rescale_value(original, prev_min, prev_max, min, max):
return min + ((max - min) * f)


def anti_alias(elevation, steps):
def anti_alias(map, steps):#TODO: There is probably a bit of numpy-optimization that can be done here.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent an hour trying to optimize this, making heavy use of numpy. For some reason that ended up even slower, though. Somebody else could try himself, since this method is one of the last few that overall take a huge amount of processing time (20% of the total).

"""
Execute the anti_alias operation steps times on the given elevation map
Execute the anti_alias operation steps times on the given map
"""
width = len(elevation[0])
height = len(elevation)
height, width = map.shape

def _anti_alias_step(original):
anti_aliased = copy.deepcopy(original)
for y in range(height):
for x in range(width):
anti_aliased[y][x] = anti_alias_point(original, x, y)
anti_aliased[y, x] = anti_alias_point(original, x, y)
return anti_aliased

def anti_alias_point(original, x, y):
n = 2
tot = elevation[y][x] * 2
tot = map[y, x] * 2
for dy in range(-1, +2):
py = (y + dy) % height
for dx in range(-1, +2):
px = (x + dx) % width
n += 1
tot += original[py][px]
tot += original[py, px]
return tot / n

current = elevation
current = map
for i in range(steps):
current = _anti_alias_step(current)
return current
Expand All @@ -120,3 +120,33 @@ def array_to_matrix(array, width, height):
for x in range(width):
matrix[y].append(array[y * width + x])
return matrix

def _equal(a, b):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope this doesn't seem too much like a hack. I just didn't know how to handle numpy.array== otherwise (it defaults to returning an array of bools instead of a single bool, which did cause problems at many places in the code).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function used in other parts of WE or just tests? If just tests, can it be moved to the tests please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used in/as World.operator== - removing it would leave worlds without a proper way of checking for equality. I said this already at some point, but: I think this method should stay here.

#recursion on subclasses of types: tuple, list, dict
#specifically checks : float, ndarray
if type(a) is float and type(b) is float:#float
return(numpy.allclose(a, b))
elif type(a) is numpy.ndarray and type(b) is numpy.ndarray:#ndarray
return(numpy.array_equiv(a, b))#alternative for float-arrays: numpy.allclose(a, b[, rtol, atol])
elif isinstance(a, dict) and isinstance(b, dict):#dict
if len(a) != len(b):
return(False)
t = True
for key, val in a.items():
if key not in b:
return(False)
t = _equal(val, b[key])
if not t:
return(False)
return(t)
elif (isinstance(a, list) and isinstance(b, list)) or (isinstance(a, tuple) and isinstance(b, tuple)):#list, tuples
if len(a) != len(b):
return(False)
t = True
for vala, valb in zip(a, b):
t = _equal(vala, valb)
if not t:
return(False)
return(t)
else:#fallback
return(a == b)
Loading