In [1]:
import numpy as np
import scipy.weave as weave
from IPython.core.display import publish_png
%gui qt
The purpose of this branch is two-fold:
Additional support must be in place for exporting/importing/generating configs, but that's a lower priority.
this bit is given to us for free by numpys record dtypes. here are a few examples
In [2]:
print "this is how you could model a beta-asynchronous single cell."
a = np.zeros((4, 3), dtype=[("hidden", "int8"), ("communicated", "int8")])
a[0][0]["hidden"] = 99
a[0][0]["communicated"] = 100
print a, a.dtype
In [3]:
print "a simplified JVN cell might look something like this:"
b = np.zeros((4, 3), dtype=[("exc", "bool"), ("dir", "int8"), ("special", "bool")])
b[0][1]["exc"] = True
b[0][1]["dir"] = 1 # maybe 1 stands for "east" or something
b[0][1]["special"] = True # special transmission
print b, b.dtype
In [4]:
print "access via numbers is also supported:"
print b[0][1][0], b[0][1][1], b[0][1][2]
print b[0,1][0], b[0,1][1], b[0,1][2]
In [5]:
print "but slicing along the record axis is not possible:"
try:
print b[0,1,0]
raise Exception("unexpected success")
except IndexError:
print "didn't work."
print "this doesn't work, either"
try:
print b[0,1,"exc"]
raise Exception("unexpected success")
except ValueError:
print "didn't work."
In [6]:
print "leaving out the last index gives us tuples"
print b[0,0]
print b[0][0]
print
print "these tuples are not regular tuples, because they can be accessed with strings."
print type(b[0,0])
print b[0,0]["exc"]
In [7]:
with_names = np.zeros((3, 2), dtype=[("foo", "int8"), ("bar", "int8")])
try:
weave.inline("""with_names(0,1,0) = 99;""", arg_names=["with_names"], type_converters=weave.converters.blitz)
except KeyError:
print "didn't work"
however, if the dtype is just "multiples of one type", like two 8-bit integers per field, it works
In [8]:
without_names = np.zeros((3, 2), dtype="2i8")
weave.inline("""without_names(0,1,0) = 99;""", arg_names=["without_names"], type_converters=weave.converters.blitz)
print without_names
record datatypes that are essentially the same as such a "multiples of one type" type can just be reshaped:
In [9]:
original = np.zeros((3, 2), dtype=[("foo", "int8"), ("bar", "int8")])
reshaped = original.view()
reshaped.dtype = "int8"
reshaped.shape = (3, 2, 2)
# reshaped being a view of original means we can change either and changes show up in both
original[0,1]["foo"] = 100
reshaped[2,1,1] = 99
print original, original.dtype
print reshaped, reshaped.dtype
such views can now be used in weave.inline without trouble
In [10]:
weave.inline("""reshaped(1,1,0) = 23;""", arg_names=["reshaped"], type_converters=weave.converters.blitz)
print original, original.dtype
assert original[1,1]["foo"] == 23
accessing the array with the name of the field first we get a view into the array that we can pass on to weave.inline
In [11]:
foo_conf = original["foo"]
bar_conf = original["bar"]
weave.inline("""
for(int i = 0; i < 3; i++) {
foo_conf(i, 0) += 10;
}
for(int i = 0; i < 2; i++) {
bar_conf(0, i) -= 23;
}""",
arg_names=["foo_conf", "bar_conf"],
type_converters=weave.converters.blitz)
print original
this gives us the possibility to expose each entry to the array as a separate array, which is still read-write usable.
what is apparently not possible is giving the user access to tuples as some blitz tiny vector or something, but that's possibly because those tiny vectors need to have the same type for each field.
In [12]:
with_subshapes = np.zeros((2, 2), dtype="2int8")
weave.inline("""
with_subshapes(1,0,0) = 99;
""", arg_names=["with_subshapes"],
type_converters=weave.converters.blitz)
print with_subshapes
in conclusion, we have two basic cases:
in case 1, we can just turn the config into one with one more dimension, or it already behaves like that (in the case of "3int8")
in this case we can also use slicing to get the "tuples" at each cell.
in case 2, however, we don't have that luxury. we have to create one view per field and we can just expose that to the C++ code.
In [13]:
test_types = [
"2int8",
"(2,3)int8",
"int8, int8",
"int8, int16",
[('foo', 'int8'), ('bar', 'int8')],
[('foo', 'int8'), ('bar', 'int16')],
]
for typ in test_types:
a = np.zeros((2, 2), dtype=typ)
print typ, a.dtype
print "kind:", a.dtype.kind
print "shape:", a.dtype.shape
print "fields:", a.dtype.fields
print "descr:", a.dtype.descr
print
In [14]:
class RecordAccessor(object):
def __init__(self, dtype):
self.descr = list(dtype.descr)
# so what goes here ...?
In [11]:
multipart = np.zeros((3, 3), dtype=[("first_bit", "bool"), ("second_bit", "bool")])
multipart["first_bit"].view().reshape(9)[:] = np.array([True, False, True, False, True, True, False, True, True])
multipart["second_bit"].view().reshape(9)[:] = np.array([False, False, True, True, True, False, False, False, True])
print multipart
display = multipart.view()
display.dtype="int16"
display.shape=(3,3)
print display
this configuration is limited to the values 0, 1, 256 and 257. It could now theoretically just be viewed with a normal zasim based config painter:
In [12]:
from zasim.display.qt import render_state_array, qimage_to_pngstr, make_gray_palette
new_palette,_ = make_gray_palette([0, 1, 256, 257])
publish_png(qimage_to_pngstr(render_state_array(display.T, new_palette).scaled(90, 90)))
reshaping and views gets a bit more complicated if the dtypes are not the same for each field, but since most dtypes are just padded up to multiples of bytes or something, we can just calculate the next bigger integer type and use that.
In [13]:
example_dtypes = ["int8, bool", "2int8, int16", "int8, int16, bool"]
for the_string in example_dtypes:
print "{}: {}".format(the_string, np.dtype(the_string).itemsize)
In [14]:
example_dtypes_2 = example_dtypes + \
[[("up", "bool"), ("left", "bool")],
[("one", "int8"), ("two", "bool")],
]
for the_string in example_dtypes_2:
print the_string
dt = np.dtype(the_string)
print dt.fields
print "sizes:", {fn: (x.name, x.itemsize) for fn, (x, offset) in dt.fields.iteritems()}
print "sum of sizes:", sum(x.itemsize for x, offset in dt.fields.values())
print
In [15]:
from itertools import product
from binascii import hexlify
def pad_dtype(arrdtype):
"""calculates a new dtype that is padded to fit each of the dtypes cells into one integer.
:returns: the new dtype and value ranges for the padding fields."""
bytesize = arrdtype.itemsize
available_bytesizes = [1, 2, 4, 8, 16, 32, 64]
# the first higher one we can take, we do take.
try:
new_bytesize = [bs for bs in available_bytesizes if bs >= bytesize][0]
except IndexError:
raise ValueError("There is no dtype large enough to encompass one field :(")
if new_bytesize != bytesize:
print "bytesizes differ: {} -> {}".format(bytesize, new_bytesize)
# if the bytesizes differ, we have to pad the data up!
pad_size = new_bytesize - bytesize
# turn pad_size into a binary like 1101. ones tell us which sizes we need
# for padding
pad_binary = bin(pad_size)[2:]
take_padders = [size + 1 for size, take in enumerate(pad_binary) if take == "1"]
possible_value_dict = {}
new_dtype_descrs = []
for padval in take_padders:
fieldname = "padding_" + str(padval)
typename = "i" + str(padval)
shape = ()
new_dtype_descrs.append((fieldname, typename, shape))
possible_value_dict[fieldname] = [0]
new_dtype_descrs.extend(arrdtype.descr)
print "the result of the padding operation is:"
print new_dtype_descrs
new_dtype = np.dtype(new_dtype_descrs)
return new_dtype, possible_value_dict
return arrdtype, {}
def analyze_dtype(arrdtype, possible_value_dict):
"""generates keys for a palette from thegiven array."""
# generate an array of all possibilities
sizes = [len(possible_value_dict[descr[0]]) for descr in arrdtype.descr]
all_values = np.zeros(shape=sizes, dtype=arrdtype)
value_ranges_per_subfield = [possible_value_dict[descr[0]] for descr in arrdtype.descr]
positions = product(*map(range, sizes))
for position in positions:
values = [value_ranges_per_subfield[idx][position[idx]] for idx in range(len(sizes))]
all_values[tuple(position)] = tuple(values)
print "these are all possible values:"
print all_values
# now change the dtype
new_array = all_values.view()
new_array.dtype = "i" + str(arrdtype.itemsize)
return new_array.reshape(reduce(lambda a, b: a * b, sizes))
test_dtype = np.dtype("int8, int16")
print "we're going to analyse this dtype:"
print test_dtype
print
possible_values = {"f0": [0, 1, 2, 3],
"f1": [1, 2, 4, 8, 16]}
test_dtype, possible_values_addendum = pad_dtype(test_dtype)
possible_values.update(possible_values_addendum)
print "we've decided, that the following values are possible for the fields:"
print possible_values
print
all_values = analyze_dtype(test_dtype, possible_values)
print "these are all values possible, as integers"
print all_values
print
print "the bytes are packed like this:"
chunksize = len(all_values.data) / len(all_values)
for chunk in range(len(all_values)):
print hexlify(str(all_values.data)[chunk * chunksize:(chunk + 1) * chunksize])
we can now turn any old array into a padded one that we can interpret as integers
In [16]:
def pad_array_and_copy(arr):
new_dtype, _ = pad_dtype(arr.dtype)
print new_dtype
print arr.dtype
if new_dtype != arr.dtype:
# we have to copy data over because of padding
new_arr = np.empty(arr.shape, dtype=new_dtype)
for descr in arr.dtype.descr:
fn = descr[0]
# just copy all data from the fields over
new_arr[fn] = arr[fn]
else:
new_arr = arr.copy()
return new_arr
def reinterpret_as_integers(arr):
return arr.view("i{}".format(arr.dtype.itemsize))
def uninterpret(arr, orig_dtype):
view = arr.view()
view.dtype = orig_dtype
return view
In [17]:
test = np.zeros((2, 2), dtype=("int8, int8"))
test["f0"] = [[0, 1], [2, 3]]
test["f1"] = [[3, 1], [0, 2]]
other = pad_array_and_copy(test)
print reinterpret_as_integers(other)
now we can turn that data into a palette and draw a bit of stuff with it.
the palette version of the random config generator should accept our "all_values" list.
In [18]:
new_palette,_ = make_gray_palette(list(all_values))
from zasim.config import RandomConfigurationFromPalette
display = RandomConfigurationFromPalette(list(all_values)).generate((5, 5))
publish_png(qimage_to_pngstr(render_state_array(display.T, new_palette).scaled(90, 90)))
print uninterpret(display, test_dtype)