From 3a8cdcd9e08434f27a58c0a0efbd1042a2a1b3e0 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 18 Mar 2016 15:55:59 +0100 Subject: [PATCH 001/945] Added RELU and ELU, removed tanh and RECT --- .../theano_engine/theano_definitions.py | 20 +++--- .../nodenet/theano_engine/theano_nodenet.py | 2 +- .../nodenet/theano_engine/theano_partition.py | 64 ++++++++++--------- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_definitions.py b/micropsi_core/nodenet/theano_engine/theano_definitions.py index 850b98d9..8b51eea9 100644 --- a/micropsi_core/nodenet/theano_engine/theano_definitions.py +++ b/micropsi_core/nodenet/theano_engine/theano_definitions.py @@ -24,9 +24,11 @@ GATE_FUNCTION_IDENTITY = 0 GATE_FUNCTION_ABSOLUTE = 1 GATE_FUNCTION_SIGMOID = 2 -GATE_FUNCTION_TANH = 3 -GATE_FUNCTION_RECT = 4 +#GATE_FUNCTION_TANH = 3 +GATE_FUNCTION_RELU = 4 GATE_FUNCTION_DIST = 5 +GATE_FUNCTION_ELU = 6 + NFPG_PIPE_NON = 0 NFPG_PIPE_GEN = 1 @@ -208,12 +210,12 @@ def get_numerical_gatefunction_type(type): return GATE_FUNCTION_ABSOLUTE elif type == "sigmoid": return GATE_FUNCTION_SIGMOID - elif type == "tanh": - return GATE_FUNCTION_TANH - elif type == "rect": - return GATE_FUNCTION_RECT + elif type == "relu": + return GATE_FUNCTION_RELU elif type == "one_over_x": return GATE_FUNCTION_DIST + elif type == "elu": + return GATE_FUNCTION_ELU else: raise ValueError("Supplied gatefunction type is not a valid type: "+str(type)) @@ -225,12 +227,12 @@ def get_string_gatefunction_type(type): return "absolute" elif type == GATE_FUNCTION_SIGMOID: return "sigmoid" - elif type == GATE_FUNCTION_TANH: - return "tanh" - elif type == GATE_FUNCTION_RECT: + elif type == GATE_FUNCTION_RELU: return "rect" elif type == GATE_FUNCTION_DIST: return "one_over_x" + elif type == GATE_FUNCTION_ELU: + return "elu" else: raise ValueError("Supplied gatefunction type is not a valid type: "+str(type)) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index dbe4f7b0..a65a712b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1490,7 +1490,7 @@ def set_link_weights(self, nodespace_from_uid, group_from, nodespace_to_uid, gro # del self.proxycache[uid] def get_available_gatefunctions(self): - return ["identity", "absolute", "sigmoid", "tanh", "rect", "one_over_x"] + return ["identity", "absolute", "sigmoid", "elu", "relu", "one_over_x"] def add_slot_monitor(self, node_uid, slot, **_): raise RuntimeError("Theano engine does not support slot monitors") diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 817e8caa..a7a46ba6 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -91,24 +91,14 @@ def has_gatefunction_sigmoid(self, value): self.__has_gatefunction_sigmoid = value @property - def has_gatefunction_tanh(self): - return self.__has_gatefunction_tanh + def has_gatefunction_relu(self): + return self.__has_gatefunction_relu - @has_gatefunction_tanh.setter - def has_gatefunction_tanh(self, value): - if value != self.__has_gatefunction_tanh: + @has_gatefunction_relu.setter + def has_gatefunction_relu(self, value): + if value != self.__has_gatefunction_relu: self.__has_new_usages = True - self.__has_gatefunction_tanh = value - - @property - def has_gatefunction_rect(self): - return self.__has_gatefunction_rect - - @has_gatefunction_rect.setter - def has_gatefunction_rect(self, value): - if value != self.__has_gatefunction_rect: - self.__has_new_usages = True - self.__has_gatefunction_rect = value + self.__has_gatefunction_relu = value @property def has_gatefunction_one_over_x(self): @@ -120,6 +110,16 @@ def has_gatefunction_one_over_x(self, value): self.__has_new_usages = True self.__has_gatefunction_one_over_x = value + @property + def has_gatefunction_elu(self): + return self.__has_gatefunction_elu + + @has_gatefunction_elu.setter + def has_gatefunction_elu(self, value): + if value != self.__has_gatefunction_elu: + self.__has_new_usages = True + self.__has_gatefunction_elu = value + def __init__(self, nodenet, pid, sparse=True, initial_number_of_nodes=2000, average_elements_per_node_assumption=5, initial_number_of_nodespaces=10): # logger used by this partition @@ -315,8 +315,9 @@ def __init__(self, nodenet, pid, sparse=True, initial_number_of_nodes=2000, aver self.__has_gatefunction_absolute = False self.__has_gatefunction_sigmoid = False self.__has_gatefunction_tanh = False - self.__has_gatefunction_rect = False self.__has_gatefunction_one_over_x = False + self.__has_gatefunction_elu = False + self.__has_gatefunction_relu = False self.por_ret_dirty = True self.last_allocated_node = 0 @@ -537,13 +538,16 @@ def compile_calculate_nodes(self): gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_ABSOLUTE), abs(gate_function_output), gate_function_output) # apply GATE_FUNCTION_SIGMOID to masked gates if self.has_gatefunction_sigmoid: - gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_SIGMOID), N.sigmoid(gate_function_output + self.g_theta), gate_function_output) - # apply GATE_FUNCTION_TANH to masked gates - if self.has_gatefunction_tanh: - gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_TANH), T.tanh(gate_function_output + self.g_theta), gate_function_output) - # apply GATE_FUNCTION_RECT to masked gates - if self.has_gatefunction_rect: - gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_RECT), T.switch(gate_function_output + self.g_theta > 0, gate_function_output - self.g_theta, 0), gate_function_output) + x = gate_function_output + self.g_theta + gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_SIGMOID), N.sigmoid(x), gate_function_output) + # apply GATE_FUNCTION_ELU to masked gates + if self.has_gatefunction_elu: + x = gate_function_output + self.g_theta + gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_ELU), T.switch(gate_function_output > 0., x, T.exp(x) - 1.), gate_function_output) + # apply GATE_FUNCTION_RELU to masked gates + if self.has_gatefunction_relu: + x = gate_function_output + self.g_theta + gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_RELU), T.nnet.relu(x), gate_function_output) # apply GATE_FUNCTION_DIST to masked gates if self.has_gatefunction_one_over_x: gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_DIST), T.switch(T.neq(0, gate_function_output), 1 / gate_function_output, 0), gate_function_output) @@ -987,9 +991,9 @@ def load_data(self, datafilename, nodes_data): self.has_sampling_activators = np.sum(self.allocated_nodespaces_sampling_activators) > 0 self.has_gatefunction_absolute = GATE_FUNCTION_ABSOLUTE in g_function_selector self.has_gatefunction_sigmoid = GATE_FUNCTION_SIGMOID in g_function_selector - self.has_gatefunction_tanh = GATE_FUNCTION_TANH in g_function_selector - self.has_gatefunction_rect = GATE_FUNCTION_RECT in g_function_selector + self.has_gatefunction_relu = GATE_FUNCTION_RELU in g_function_selector self.has_gatefunction_one_over_x = GATE_FUNCTION_DIST in g_function_selector + self.has_gatefunction_elu = GATE_FUNCTION_ELU in g_function_selector else: self.logger.warn("no g_function_selector in file, falling back to defaults") @@ -1613,10 +1617,10 @@ def set_node_gatefunction_name(self, id, gate_type, gatefunction_name): self.has_gatefunction_absolute = True elif g_function_selector[elementindex] == GATE_FUNCTION_SIGMOID: self.has_gatefunction_sigmoid = True - elif g_function_selector[elementindex] == GATE_FUNCTION_TANH: - self.has_gatefunction_tanh = True - elif g_function_selector[elementindex] == GATE_FUNCTION_RECT: - self.has_gatefunction_rect = True + elif g_function_selector[elementindex] == GATE_FUNCTION_RELU: + self.has_gatefunction_relu = True + elif g_function_selector[elementindex] == GATE_FUNCTION_ELU: + self.has_gatefunction_elu = True elif g_function_selector[elementindex] == GATE_FUNCTION_DIST: self.has_gatefunction_one_over_x = True From 642fe42b457155810c7bd78aca4c22e6a7001bdf Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 18 Mar 2016 16:11:03 +0100 Subject: [PATCH 002/945] Displaying E and R in the GUI for elu and relu --- micropsi_server/static/js/nodenet.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 690a09ab..9b97a3d3 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -74,6 +74,8 @@ GATE_DEFAULTS = { gatefunction_icons = { 'sigmoid': 'Σ', + 'elu': 'E', + 'relu': 'R', 'absolute': '|x|', 'one_over_x': '1/x', 'identity': '' From e5c4ed700fd22396a4521d0296335e179cfb8a62 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 18 Mar 2016 16:21:49 +0100 Subject: [PATCH 003/945] Adding elu and relu for dict_engine --- micropsi_core/nodenet/gatefunctions.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/micropsi_core/nodenet/gatefunctions.py b/micropsi_core/nodenet/gatefunctions.py index 6064a183..f34c00b8 100644 --- a/micropsi_core/nodenet/gatefunctions.py +++ b/micropsi_core/nodenet/gatefunctions.py @@ -18,5 +18,17 @@ def sigmoid(input_activation, rho, theta): return 1.0 / (1.0 + math.exp(-(theta + input_activation))) +def elu(input_activation, rho, theta): + input_activation += theta + if input_activation <= 0: + return math.exp(input_activation) - 1. + else: + return input_activation + + +def relu(input_activation, rho, theta): + return max(0, input_activation+theta) + + def one_over_x(input_activation, rho, theta): return 0.0 if input_activation == 0.0 else 1.0 / input_activation From bbdc35412c6f8ed56e251f64545c085e5466e599 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 14 Apr 2016 21:30:04 +0200 Subject: [PATCH 004/945] since we're persisiting monitor data anyway, we might as well reconstruct them --- micropsi_core/nodenet/monitor.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index 95e99e17..24e4382d 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -23,10 +23,12 @@ class Monitor(metaclass=ABCMeta): values: the observed values """ - def __init__(self, nodenet, name='', uid=None, color=None): + def __init__(self, nodenet, name='', uid=None, color=None, values={}): self.uid = uid or micropsi_core.tools.generate_uid() self.nodenet = nodenet self.values = {} + for key in sorted(values.keys()): + self.values[int(key)] = values[key] self.name = name or "some monitor" self.color = color or "#%02d%02d%02d" % (random.randint(0,99), random.randint(0,99), random.randint(0,99)) @@ -49,9 +51,9 @@ def clear(self): class NodeMonitor(Monitor): - def __init__(self, nodenet, node_uid, type, target, sheaf=None, name=None, uid=None, color=None, **_): + def __init__(self, nodenet, node_uid, type, target, sheaf=None, name=None, uid=None, color=None, values={}, **_): name = name or "%s %s @ Node %s" % (type, target, nodenet.get_node(node_uid).name or nodenet.get_node(node_uid).uid) - super(NodeMonitor, self).__init__(nodenet, name, uid, color=color) + super(NodeMonitor, self).__init__(nodenet, name, uid, color=color, values=values) self.node_uid = node_uid self.type = type self.target = target or 'gen' @@ -79,10 +81,10 @@ def step(self, step): class LinkMonitor(Monitor): - def __init__(self, nodenet, source_node_uid, gate_type, target_node_uid, slot_type, property=None, name=None, uid=None, color=None, **_): + def __init__(self, nodenet, source_node_uid, gate_type, target_node_uid, slot_type, property=None, name=None, uid=None, color=None, values={}, **_): api = nodenet.netapi name = name or "%s:%s -> %s:%s" % (api.get_node(source_node_uid).name, gate_type, api.get_node(source_node_uid).name, slot_type) - super(LinkMonitor, self).__init__(nodenet, name, uid, color=color) + super(LinkMonitor, self).__init__(nodenet, name, uid, color=color, values=values) self.source_node_uid = source_node_uid self.target_node_uid = target_node_uid self.gate_type = gate_type @@ -120,9 +122,9 @@ def step(self, step): class ModulatorMonitor(Monitor): - def __init__(self, nodenet, modulator, name=None, uid=None, color=None, **_): + def __init__(self, nodenet, modulator, name=None, uid=None, color=None, values={}, **_): name = name or "Modulator: %s" % modulator - super(ModulatorMonitor, self).__init__(nodenet, name, uid, color=color) + super(ModulatorMonitor, self).__init__(nodenet, name, uid, color=color, values=values) self.modulator = modulator self.nodenet = nodenet @@ -139,8 +141,8 @@ def step(self, step): class CustomMonitor(Monitor): - def __init__(self, nodenet, function, name=None, uid=None, color=None, **_): - super(CustomMonitor, self).__init__(nodenet, name, uid, color=color) + def __init__(self, nodenet, function, name=None, uid=None, color=None, values={}, **_): + super(CustomMonitor, self).__init__(nodenet, name, uid, color=color, values=values) self.function = function self.compiled_function = micropsi_core.tools.create_function(self.function, parameters="netapi") From 0c775725ece5a5c266b09c0760243a0e79bdd58f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 15 Apr 2016 11:46:29 +0200 Subject: [PATCH 005/945] prepare swapping --- micropsi_core/nodenet/monitor.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index 24e4382d..9deed365 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -42,9 +42,12 @@ def get_data(self): } @abstractmethod - def step(self, step): + def getvalue(self): pass # pragma: no cover + def step(self, step): + self.values[step] = self.getvalue(step) + def clear(self): self.values = {} @@ -69,14 +72,14 @@ def get_data(self): }) return data - def step(self, step): + def getvalue(self): if self.nodenet.is_node(self.node_uid): if self.type == 'gate' and self.target in self.nodenet.get_node(self.node_uid).get_gate_types(): - self.values[step] = self.nodenet.get_node(self.node_uid).get_gate(self.target).activations[self.sheaf] + return self.nodenet.get_node(self.node_uid).get_gate(self.target).activations[self.sheaf] if self.type == 'slot' and self.target in self.nodenet.get_node(self.node_uid).get_slot_types(): - self.values[step] = self.nodenet.get_node(self.node_uid).get_slot(self.target).activations[self.sheaf] + return self.nodenet.get_node(self.node_uid).get_slot(self.target).activations[self.sheaf] else: - self.values[step] = None + return None class LinkMonitor(Monitor): @@ -112,12 +115,12 @@ def find_link(self): return l return None - def step(self, step): + def getvalue(self): link = self.find_link() if link: - self.values[step] = getattr(self.find_link(), self.property) + return getattr(self.find_link(), self.property) else: - self.values[step] = None + return None class ModulatorMonitor(Monitor): @@ -135,8 +138,8 @@ def get_data(self): }) return data - def step(self, step): - self.values[step] = self.nodenet.get_modulator(self.modulator) + def getvalue(self): + return self.nodenet.get_modulator(self.modulator) class CustomMonitor(Monitor): @@ -153,5 +156,5 @@ def get_data(self): }) return data - def step(self, step): - self.values[step] = self.compiled_function(self.nodenet.netapi) + def getvalue(self): + return self.compiled_function(self.nodenet.netapi) From a43a937495984d1be902489690fb25ea22cb0e2d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 19 Apr 2016 16:00:27 +0200 Subject: [PATCH 006/945] offer json exports for existing monitors --- micropsi_server/micropsi_app.py | 8 ++++++++ micropsi_server/static/css/micropsi-styles.css | 4 ++-- micropsi_server/static/js/monitor.js | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 1a07f7fb..a02c8af8 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -555,6 +555,14 @@ def export_nodenet(nodenet_uid): return runtime.export_nodenet(nodenet_uid) +@micropsi_app.route("/monitor/export/-") +def export_monitor(nodenet_uid, monitor_uid): + data = runtime.export_monitor_data(nodenet_uid, monitor_uid) + response.set_header('Content-type', 'application/json') + response.set_header('Content-Disposition', 'attachment; filename="monitor_%s.json"' % data['name']) + return json.dumps(data['values'], sort_keys=True, indent=2) + + @micropsi_app.route("/nodenet/edit") def edit_nodenet(): user_id, permissions, token = get_request_data() diff --git a/micropsi_server/static/css/micropsi-styles.css b/micropsi_server/static/css/micropsi-styles.css index cfcf39e0..45bf9119 100644 --- a/micropsi_server/static/css/micropsi-styles.css +++ b/micropsi_server/static/css/micropsi-styles.css @@ -38,8 +38,8 @@ h3 { .log_WARNING{color: #942628;} .log_ERROR {color: #e12628;} -.delete_monitor i { opacity: 0.5; } -.delete_monitor:hover i { opacity: 1; } +.monitor_action i { opacity: 0.5; } +.monitor_action:hover i { opacity: 1; } .logentry{ display:block; diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 4700a2d9..e4faa890 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -253,7 +253,9 @@ $(function(){ if(currentMonitors.indexOf(mon.uid) > -1){ html += ' checked="checked"'; } - html += ' /> '; + html += ' /> '; + html += ''; + html += ''; } list.html(html); $('.monitor_checkbox', list).on('change', updateMonitorSelection); From f882cf20a5a5bf39f567aeb8c6ea04798a811cfe Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 19 Apr 2016 18:38:32 +0200 Subject: [PATCH 007/945] fix monitor display: refresh list only when changed --- micropsi_server/static/js/monitor.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index e4faa890..edfc101d 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -111,6 +111,7 @@ $(function(){ } }); + var monitor_list_items = []; function init() { bindEvents(); @@ -247,6 +248,13 @@ $(function(){ var html = ''; var sorted = Object.values(monitors); sorted.sort(sortByName); + var keys = Object.keys(monitors); + var changed = $(keys).not(monitor_list_items).length != 0 || $(monitor_list_items).not(keys).length != 0; + if(!changed){ + return; + } + monitor_list_items = []; + var els = $('.monitor'); for(var i = 0; i < sorted.length; i++){ var mon = sorted[i]; html += '
  • ' + mon.name + ''; html += ''; html += '
  • '; + monitor_list_items.push(mon.uid); } list.html(html); $('.monitor_checkbox', list).on('change', updateMonitorSelection); From 187abd31732d8c92dc7e02f66894043328ca7ce3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 20 Apr 2016 16:40:22 +0200 Subject: [PATCH 008/945] initialize monitors after partitions --- .../nodenet/theano_engine/theano_nodenet.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index ded96a22..40ea5bf3 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -397,6 +397,9 @@ def load(self, filename): # determine whether we have a complete json dump, or our theano npz partition files: nodes_data = initfrom.get('nodes', {}) + # pop the monitors: + monitors = initfrom.pop('monitors', {}) + # initialize self.initialize_nodenet(initfrom) @@ -413,6 +416,15 @@ def load(self, filename): # was saved). self.reload_native_modules(self.native_module_definitions) + for monitorid in monitors: + data = monitors[monitorid] + if hasattr(monitor, data['classname']): + mon = getattr(monitor, data['classname'])(self, **data) + self._monitors[mon.uid] = mon + else: + self.logger.warn('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) + + # re-initialize step operators for theano recompile to new shared variables self.initialize_stepoperators() From 860c0beb4bf9b18f339db5bccd9606127e2beb49 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 20 Apr 2016 16:42:43 +0200 Subject: [PATCH 009/945] group monitors --- micropsi_core/_runtime_api_monitors.py | 7 +++ micropsi_core/nodenet/monitor.py | 31 +++++++++++ micropsi_core/nodenet/netapi.py | 6 +++ micropsi_core/nodenet/nodenet.py | 8 +++ micropsi_core/tests/test_runtime_monitors.py | 30 +++++++++++ micropsi_server/micropsi_app.py | 5 ++ micropsi_server/tests/test_json_api.py | 56 ++++++++++++++++++++ 7 files changed, 143 insertions(+) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index 2cfc7afa..c0f03449 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -50,6 +50,13 @@ def add_custom_monitor(nodenet_uid, function, name, color=None): return nodenet.add_custom_monitor(function, name, color=color) +def add_group_monitor(nodenet_uid, nodespace, name, node_name_prefix='', node_uids=[], gate='gen', color=None): + """Adds a group monitor, that tracks the activations of the given group + Returns the uid of the new monitor.""" + nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) + return nodenet.add_group_monitor(nodespace, name, node_name_prefix=node_name_prefix, node_uids=node_uids, gate=gate, color=color) + + def remove_monitor(nodenet_uid, monitor_uid): """Deletes an activation monitor.""" micropsi_core.runtime.get_nodenet(nodenet_uid).remove_monitor(monitor_uid) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index 9deed365..e61c4107 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -52,6 +52,37 @@ def clear(self): self.values = {} +class GroupMonitor(Monitor): + + def __init__(self, nodenet, nodespace, name, name_prefix='', node_uids=[], gate='gen', uid=None, color=None, values={}, **_): + super().__init__(nodenet, name=name, uid=uid, color=color, values=values) + self.nodespace = nodespace + self.node_uids = node_uids + self.name_prefix = name_prefix + self.gate = gate + if len(node_uids) == 0: + self.nodenet.group_nodes_by_names(nodespace, name_prefix, gatetype=gate, group_name=name) + self.node_uids = self.nodenet.get_node_uids(nodespace, name) + else: + self.nodenet.group_nodes_by_ids(nodespace, node_uids, name, gatetype=gate) + + def get_data(self): + data = super().get_data() + data.update({ + "nodespace": self.nodespace, + "node_uids": self.node_uids, + "gate": self.gate + }) + return data + + def getvalue(self): + data = self.nodenet.get_activations(self.nodespace, self.name) + if type(data) == list: + return data + else: + return data.tolist() + + class NodeMonitor(Monitor): def __init__(self, nodenet, node_uid, type, target, sheaf=None, name=None, uid=None, color=None, values={}, **_): diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index a35c6e52..f0462b3c 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -549,6 +549,12 @@ def add_custom_monitor(self, function, name, color=None): Returns the uid of the new monitor.""" return self.__nodenet.add_custom_monitor(function, name, color=color) + def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], gate='gen', color=None): + """Adds a continuous monitor, that tracks the activations of the given group + return-value for every calculation step. + Returns the uid of the new monitor.""" + return self.__nodenet.add_group_monitor(nodespace, name, node_name_prefix=node_name_prefix, node_uids=node_uids, gate=gate, color=color) + def get_monitor(self, uid): """Returns the monitor with the given uid""" return self.__nodenet.get_monitor(uid) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index dcfbbf53..b3e1327c 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -615,6 +615,14 @@ def add_custom_monitor(self, function, name, color=None): self._monitors[mon.uid] = mon return mon.uid + def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], gate='gen', color=None): + """Adds a continuous monitor, that tracks the activations of the given group + return-value for every calculation step. + Returns the uid of the new monitor.""" + mon = monitor.GroupMonitor(self, nodespace, name, node_name_prefix, node_uids, gate, color=color) + self._monitors[mon.uid] = mon + return mon.uid + def get_monitor(self, uid): return self._monitors.get(uid) diff --git a/micropsi_core/tests/test_runtime_monitors.py b/micropsi_core/tests/test_runtime_monitors.py index b8342804..70498fd4 100644 --- a/micropsi_core/tests/test_runtime_monitors.py +++ b/micropsi_core/tests/test_runtime_monitors.py @@ -199,3 +199,33 @@ def test_fetch_partial_monitor_data(fixed_nodenet): values = data[uid]['values'] assert len(values.keys()) == 41 assert set(list(values.keys())) == set(range(10, 51)) + + +def test_add_group_monitor(test_nodenet): + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + nodes = [] + for i in range(10): + node = netapi.create_node('Register', None, "testnode_%d" % i) + nodes.append(node) + if i > 0: + netapi.link(nodes[i - 1], 'gen', node, 'gen') + source = netapi.create_node("Register", None, "Source") + netapi.link(source, 'gen', source, 'gen') + netapi.link(source, 'gen', nodes[0], 'gen') + source.activation = 1 + monitor_uid = netapi.add_group_monitor(nodespace.uid, 'testndoes', node_name_prefix='testnode', gate='gen', color='purple') + for i in range(5): + micropsi.step_nodenet(test_nodenet) + data = micropsi.export_monitor_data(test_nodenet, monitor_uid=monitor_uid) + assert set(data['values'][4][:4]) == {1.0} # first 4 active + assert set(data['values'][4][4:]) == {0.0} # rest off + micropsi.save_nodenet(test_nodenet) + micropsi.revert_nodenet(test_nodenet) + data2 = micropsi.export_monitor_data(test_nodenet, monitor_uid=monitor_uid) + assert data2 == data + micropsi.step_nodenet(test_nodenet) + data3 = micropsi.export_monitor_data(test_nodenet, monitor_uid=monitor_uid) + assert set(data3['values'][6][:6]) == {1.0} # first 6 active + assert set(data3['values'][6][6:]) == {0.0} # rest off diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index a02c8af8..77c48bcc 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1019,6 +1019,11 @@ def add_custom_monitor(nodenet_uid, function, name, color=None): return True, runtime.add_custom_monitor(nodenet_uid, function, name, color=color) +@rpc("add_group_monitor") +def add_group_monitor(nodenet_uid, nodespace, name, node_name_prefix='', node_uids=[], gate='gen', color=None): + return True, runtime.add_group_monitor(nodenet_uid, nodespace, name, node_name_prefix=node_name_prefix, node_uids=node_uids, gate=gate, color=color) + + @rpc("remove_monitor") def remove_monitor(nodenet_uid, monitor_uid): try: diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index aeb632e8..8a0c3e0c 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -689,6 +689,62 @@ def test_add_custom_monitor(app, test_nodenet): assert response.json_body['data']['name'] == 'nodecount' +def test_add_group_monitor_by_name(app, test_nodenet): + app.set_auth() + uids = [] + for i in range(3): + response = app.post_json('/rpc/add_node', params={ + 'nodenet_uid': test_nodenet, + 'type': 'Register', + 'position': [23, 23, 12], + 'nodespace': None, + 'name': 'Testnode %d' % i + }) + uids.append(response.json_body['data']) + response = app.post_json('/rpc/add_group_monitor', { + 'nodenet_uid': test_nodenet, + 'name': 'testmonitor', + 'nodespace': None, + 'node_name_prefix': 'Testnode', + 'gate': 'gen' + }) + mon_uid = response.json_body['data'] + response = app.post_json('/rpc/export_monitor_data', params={ + 'nodenet_uid': test_nodenet, + 'monitor_uid': mon_uid + }) + assert response.json_body['data']['name'] == 'testmonitor' + assert response.json_body['data']['node_uids'] == uids + + +def test_add_group_monitor_by_ids(app, test_nodenet): + app.set_auth() + uids = [] + for i in range(3): + response = app.post_json('/rpc/add_node', params={ + 'nodenet_uid': test_nodenet, + 'type': 'Register', + 'position': [23, 23, 12], + 'nodespace': None, + 'name': 'Testnode %d' % i + }) + uids.append(response.json_body['data']) + response = app.post_json('/rpc/add_group_monitor', { + 'nodenet_uid': test_nodenet, + 'name': 'testmonitor', + 'nodespace': None, + 'node_uids': uids, + 'gate': 'gen' + }) + mon_uid = response.json_body['data'] + response = app.post_json('/rpc/export_monitor_data', params={ + 'nodenet_uid': test_nodenet, + 'monitor_uid': mon_uid + }) + assert response.json_body['data']['name'] == 'testmonitor' + assert response.json_body['data']['node_uids'] == uids + + def test_remove_monitor(app, test_nodenet, node): response = app.post_json('/rpc/add_gate_monitor', params={ 'nodenet_uid': test_nodenet, From e1604ea559fbb3c266566d176c6d36c1043e47f7 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 20 Apr 2016 16:43:00 +0200 Subject: [PATCH 010/945] make frontend compatible with group monitors --- micropsi_server/static/js/monitor.js | 43 ++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index edfc101d..8abc5cbd 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -262,8 +262,8 @@ $(function(){ html += ' checked="checked"'; } html += ' /> '; - html += ''; - html += ''; + html += ' '; + html += ' '; monitor_list_items.push(mon.uid); } list.html(html); @@ -340,6 +340,12 @@ $(function(){ y2max = Math.max(y2max, monitors[uid].values[step]); y2min = Math.min(y2min, monitors[uid].values[step]); } + } else if(monitors[uid].classname == 'GroupMonitor'){ + y1values.concat(monitors[uid].values[step]); + if (step >= xstart && step <= xmax) { + y1max = Math.max(y1max, Math.max.apply(Math, monitors[uid].values[step])); + y1min = Math.min(y1min, Math.min.apply(Math, monitors[uid].values[step])); + } } else { y1values.push(monitors[uid].values[step]); if (step >= xstart && step <= xmax) { @@ -449,6 +455,39 @@ $(function(){ return ((position && d[0] == position) ? 4 : 2); }); + } else if(monitors[uid].classname == 'GroupMonitor'){ + for(var i = 0; i < monitors[uid].values[step].length; i++){ + var line = d3.svg.line() + .x(function(d) { + return x(d[0]); + }) + .y(function(d) { + return y1(d[1]); + }) + .defined(function(d){ + return d[1] == 0 || Boolean(d[1]) + }); + for (var step in monitors[uid].values) { + step = parseInt(step, 10); + if(step >= xstart && step <= xmax){ + if(monitors[uid].values[step]){ + data.push([step, parseFloat(monitors[uid].values[step][i])]); + } else { + data.push([step, null]); + } + } + } + var points = svg.selectAll(".point") + .data(data) + .enter().append("svg:circle") + .filter(function(d, i){ return d[1] == 0 || Boolean(d[1]) }) + .attr("fill", function(d, i) { return monitors[uid].color }) + .attr("cx", function(d, i) { return x(d[0]); }) + .attr("cy", function(d, i) { return y1(d[1]); }) + .attr("r", function(d) { + return ((position && d[0] == position) ? 4 : 2); + }); + } } else { var line = d3.svg.line() .x(function(d) { From 63ef8b71fd52688a2eee81c3de2347db31128bd2 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 20 Apr 2016 16:43:44 +0200 Subject: [PATCH 011/945] restore current_step in dict_nodenet --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 15e185b5..2f80feeb 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -322,6 +322,9 @@ def initialize_nodenet(self, initfrom): self._nodespaces = {} self._nodespaces["Root"] = DictNodespace(self, None, [0, 0, 0], name="Root", uid="Root") + if 'current_step' in initfrom: + self._step = initfrom['current_step'] + if len(initfrom) != 0: # now merge in all init data (from the persisted file typically) self.merge_data(initfrom, keep_uids=True) From 139a8a4e95ec0b55c5d0c51ae072bec8da7d746c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 20 Apr 2016 16:47:37 +0200 Subject: [PATCH 012/945] remove param --- micropsi_core/nodenet/monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index e61c4107..5e9873e3 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -46,7 +46,7 @@ def getvalue(self): pass # pragma: no cover def step(self, step): - self.values[step] = self.getvalue(step) + self.values[step] = self.getvalue() def clear(self): self.values = {} From 6bb437b8b369dc58c3272e42a8389bd03b95f558 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Thu, 21 Apr 2016 11:58:37 +0200 Subject: [PATCH 013/945] enabling relus for Theano < 0.7.1 --- micropsi_core/nodenet/theano_engine/theano_partition.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index a7a46ba6..d5ef8fc0 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -547,7 +547,9 @@ def compile_calculate_nodes(self): # apply GATE_FUNCTION_RELU to masked gates if self.has_gatefunction_relu: x = gate_function_output + self.g_theta - gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_RELU), T.nnet.relu(x), gate_function_output) + gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_RELU), T.maximum(x, 0.), gate_function_output) + # wait for theano 0.7.1 for this to work + #gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_RELU), T.nnet.relu(x), gate_function_output) # apply GATE_FUNCTION_DIST to masked gates if self.has_gatefunction_one_over_x: gate_function_output = T.switch(T.eq(self.g_function_selector, GATE_FUNCTION_DIST), T.switch(T.neq(0, gate_function_output), 1 / gate_function_output, 0), gate_function_output) From 0b84d366ba7833bff36da3ec0ec34270d709949e Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Thu, 21 Apr 2016 11:58:47 +0200 Subject: [PATCH 014/945] Fix relu string --- micropsi_core/nodenet/theano_engine/theano_definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_definitions.py b/micropsi_core/nodenet/theano_engine/theano_definitions.py index 8b51eea9..055349d4 100644 --- a/micropsi_core/nodenet/theano_engine/theano_definitions.py +++ b/micropsi_core/nodenet/theano_engine/theano_definitions.py @@ -228,7 +228,7 @@ def get_string_gatefunction_type(type): elif type == GATE_FUNCTION_SIGMOID: return "sigmoid" elif type == GATE_FUNCTION_RELU: - return "rect" + return "relu" elif type == GATE_FUNCTION_DIST: return "one_over_x" elif type == GATE_FUNCTION_ELU: From a8e30050f724102e470baddf78352009e15f5042 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 22 Apr 2016 14:52:45 +0200 Subject: [PATCH 015/945] version bump --- CHANGELOG.md | 4 ++++ configuration.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61248979..b8bb1b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ +0.9-alpha7 (unreleased) +========== + + 0.8-alpha6 (2016-04-22) ========== diff --git a/configuration.py b/configuration.py index 6ff5d240..eb147d8d 100644 --- a/configuration.py +++ b/configuration.py @@ -24,7 +24,7 @@ warnings.warn('Can not read config from inifile %s' % filename) raise RuntimeError('Can not read config from inifile %s' % filename) -config['micropsi2']['version'] = "0.8-alpha6" +config['micropsi2']['version'] = "0.9-alpha7-dev" config['micropsi2']['apptitle'] = "MicroPsi" homedir = config['micropsi2']['data_directory'].startswith('~') From cc56691e7b07ac8db2b79596e9e1e95ec606021e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 24 Apr 2016 02:12:57 +0200 Subject: [PATCH 016/945] fix agent naming in frontend --- micropsi_core/runtime.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 664e0212..33bc0b4f 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -335,7 +335,7 @@ def load_nodenet(nodenet_uid): logging.getLogger("system").warn("World %s for nodenet %s not found" % (data.world, data.uid)) if world_uid: - result, worldadapter_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet_uid) + result, worldadapter_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet_uid, nodenet_name=data['name']) if not result: logging.getLogger('system').warn(worldadapter_instance) worldadapter_instance = None @@ -548,7 +548,7 @@ def set_nodenet_properties(nodenet_uid, nodenet_name=None, worldadapter=None, wo assert worldadapter in worlds[world_uid].supported_worldadapters nodenet.world = world_uid nodenet.worldadapter = worldadapter - result, wa_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet.uid) + result, wa_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet.uid, nodenet_name=nodenet.name) if result: nodenet.worldadapter_instance = wa_instance if nodenet_name: From 5040bf3efb480ccd682c6922a8e3129d3eb3ac4f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 24 Apr 2016 02:13:58 +0200 Subject: [PATCH 017/945] drop the stupid 'copy' suffix --- micropsi_core/runtime.py | 2 +- .../tests/test_runtime_nodenet_basics.py | 18 +++++++++--------- micropsi_server/tests/test_json_api.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 33bc0b4f..6293d776 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -857,7 +857,7 @@ def clone_nodes(nodenet_uid, node_uids, clonemode, nodespace=None, offset=[50, 5 for _, n in copynodes.items(): target_nodespace = nodespace if nodespace is not None else n.parent_nodespace - uid = nodenet.create_node(n.type, target_nodespace, [n.position[0] + offset[0], n.position[1] + offset[1], n.position[2] + offset[2]], name=n.name + '_copy', uid=None, parameters=n.clone_parameters().copy(), gate_parameters=n.get_gate_parameters()) + uid = nodenet.create_node(n.type, target_nodespace, [n.position[0] + offset[0], n.position[1] + offset[1], n.position[2] + offset[2]], name=n.name, uid=None, parameters=n.clone_parameters().copy(), gate_parameters=n.get_gate_parameters()) if uid: uidmap[n.uid] = uid else: diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index ac8eee83..9de401ac 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -134,9 +134,9 @@ def test_clone_nodes_nolinks(fixed_nodenet): success, result = micropsi.clone_nodes(fixed_nodenet, ['n0001', 'n0002'], 'none', offset=[10, 20, 2]) assert success for n in result.values(): - if n['name'] == 'A1_copy': + if n['name'] == 'A1': a1_copy = n - elif n['name'] == 'A2_copy': + elif n['name'] == 'A2': a2_copy = n assert nodenet.is_node(a1_copy['uid']) assert a1_copy['uid'] != 'n0001' @@ -146,7 +146,7 @@ def test_clone_nodes_nolinks(fixed_nodenet): assert a1_copy['position'][1] == nodenet.get_node('n0001').position[1] + 20 assert a1_copy['position'][2] == nodenet.get_node('n0001').position[2] + 2 assert nodenet.is_node(a2_copy['uid']) - assert a2_copy['name'] == nodenet.get_node('n0002').name + '_copy' + assert a2_copy['name'] == nodenet.get_node('n0002').name assert a2_copy['uid'] != 'n0002' assert len(result.keys()) == 2 assert a1_copy['links'] == {} @@ -161,9 +161,9 @@ def test_clone_nodes_all_links(fixed_nodenet): # as a followupdnode to A1_copy to render incoming links assert len(result.keys()) == 3 for n in result.values(): - if n['name'] == 'A1_copy': + if n['name'] == 'A1': a1_copy = n - elif n['name'] == 'A2_copy': + elif n['name'] == 'A2': a2_copy = n # assert the link between a1-copy and a2-copy exists @@ -185,9 +185,9 @@ def test_clone_nodes_internal_links(fixed_nodenet): assert success assert len(result.keys()) == 2 for n in result.values(): - if n['name'] == 'A1_copy': + if n['name'] == 'A1': a1_copy = n - elif n['name'] == 'A2_copy': + elif n['name'] == 'A2': a2_copy = n # assert the link between a1-copy and a2-copy exists @@ -213,9 +213,9 @@ def test_clone_nodes_to_new_nodespace(fixed_nodenet): assert success assert len(result.keys()) == 2 for n in result.values(): - if n['name'] == 'A1_copy': + if n['name'] == 'A1': a1_copy = n - elif n['name'] == 'A2_copy': + elif n['name'] == 'A2': a2_copy = n a1_copy = nodenet.get_node(a1_copy['uid']) diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index aeb632e8..845eb1ea 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -812,7 +812,7 @@ def test_clone_nodes(app, test_nodenet, node): }) assert_success(response) node = list(response.json_body['data'].values())[0] - assert node['name'] == 'N1_copy' + assert node['name'] == 'N1' assert node['position'] == [33, 33, 33] assert node['links']['gen'][0]['target_node_uid'] == node['uid'] From aa6bb113bb49720e056a8bf44323012e607fafd7 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 24 Apr 2016 02:14:17 +0200 Subject: [PATCH 018/945] convenience getter for world.config --- micropsi_core/world/world.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 39503e3d..d258c915 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -65,6 +65,10 @@ def is_active(self): def is_active(self, is_active): self.data['is_active'] = is_active + @property + def config(self): + return self.data['config'] + @staticmethod def get_config_options(): """ Returns a list of configuration-options for this world. From c66e57f7b81968cca1fa1d1e33e501d90f0d57dd Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 25 Apr 2016 12:09:47 +0200 Subject: [PATCH 019/945] default missing nodespace properties --- micropsi_server/static/js/nodenet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 53cd1ee1..1bc25d4a 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -275,7 +275,7 @@ function setCurrentNodenet(uid, nodespace, changed){ currentNodenet = uid; currentNodeSpace = data.rootnodespace; currentWorldadapter = data.worldadapter; - nodespaceProperties = data.nodespace_ui_properties; + nodespaceProperties = data.nodespace_ui_properties || {}; for(var key in data.nodespaces){ if(!(key in nodespaceProperties)){ nodespaceProperties[key] = {}; From 2c497fa9277cf65b6d643dfef8621fd0a7d3c7ef Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 25 Apr 2016 16:35:07 +0200 Subject: [PATCH 020/945] Fixing time series snapshot bug --- micropsi_core/world/timeseries/timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/timeseries/timeseries.py b/micropsi_core/world/timeseries/timeseries.py index f1fc158a..2d5217d7 100644 --- a/micropsi_core/world/timeseries/timeseries.py +++ b/micropsi_core/world/timeseries/timeseries.py @@ -115,7 +115,7 @@ def state(self): idxs = np.arange(self.len_ts) self.permutation = np.random.permutation(idxs) t = self.permutation[t] - return self.timeseries[:, t] + return self.timeseries[t, :] @staticmethod def get_config_options(): From c76dfe08d7eedb12f3219f3f47f4f5fe34810a3f Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 25 Apr 2016 17:43:10 +0200 Subject: [PATCH 021/945] removing update datasource leftovers --- micropsi_core/world/timeseries/timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/timeseries/timeseries.py b/micropsi_core/world/timeseries/timeseries.py index 2d5217d7..ad239972 100644 --- a/micropsi_core/world/timeseries/timeseries.py +++ b/micropsi_core/world/timeseries/timeseries.py @@ -183,7 +183,7 @@ def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) self.available_datatargets = [] - self.available_datasources = ["update"] + self.available_datasources = [] for idx, ID in enumerate(self.world.ids): self.available_datasources.append(str(ID)) From fe756725db6487322921e41dedc51b29729099f0 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Tue, 26 Apr 2016 15:27:07 +0200 Subject: [PATCH 022/945] More data file dimension switch fallout fixed --- micropsi_core/world/timeseries/timeseries.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/world/timeseries/timeseries.py b/micropsi_core/world/timeseries/timeseries.py index ad239972..8c4e3f7d 100644 --- a/micropsi_core/world/timeseries/timeseries.py +++ b/micropsi_core/world/timeseries/timeseries.py @@ -91,16 +91,16 @@ def sigm(X): if dummydata: self.logger.warn("! Using dummy data") - n_ids = self.timeseries.shape[0] + n_ids = self.timeseries.shape[1] self.timeseries = np.tile(np.random.rand(n_ids,1),(1,10)) - self.len_ts = self.timeseries.shape[1] + self.len_ts = self.timeseries.shape[0] # todo: option to use only a subset of the data (e.g. for training/test) def step(self): now = datetime.utcnow().timestamp() * 1000 - if now - self.realtime_per_entry > self.last_realtime_step: + if self.realtime_per_entry == 0 or now - self.realtime_per_entry > self.last_realtime_step: self.current_step += 1 for uid in self.agents: with self.agents[uid].datasource_lock: From 0e971d636b044f59b7c2301af4fe5c6e877407ec Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 28 Apr 2016 22:13:52 +0200 Subject: [PATCH 023/945] first recorder implementation --- micropsi_core/_runtime_api_monitors.py | 30 ++++ micropsi_core/nodenet/netapi.py | 16 ++ micropsi_core/nodenet/nodenet.py | 26 ++- micropsi_core/nodenet/recorder.py | 166 ++++++++++++++++++ .../nodenet/theano_engine/theano_nodenet.py | 20 +++ micropsi_core/runtime.py | 8 +- micropsi_core/tests/test_recorders.py | 98 +++++++++++ micropsi_server/micropsi_app.py | 28 +++ micropsi_server/tests/test_json_api.py | 96 ++++++++++ 9 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 micropsi_core/nodenet/recorder.py create mode 100644 micropsi_core/tests/test_recorders.py diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index c0f03449..b40d320c 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -115,3 +115,33 @@ def get_monitor_data(nodenet_uid, step=0, monitor_from=0, monitor_count=-1): else: data['monitors'] = micropsi_core.runtime.export_monitor_data(nodenet_uid, None, monitor_from=monitor_from, monitor_count=monitor_count) return data + + +def add_activation_recorder(nodenet_uid, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) + rec = nodenet.add_activation_recorder(group_definition, name, interval) + return True, rec.uid + + +def add_linkweight_recorder(nodenet_uid, from_group_definition, to_group_definition, name, interval=1): + """ Adds a linkweight recorder to links between to groups.""" + nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) + rec = nodenet.add_linkweight_recorder(from_group_definition, to_group_definition, name, interval) + return True, rec.uid + + +def remove_recorder(nodenet_uid, recorder_uid): + """Deletes a recorder.""" + micropsi_core.runtime.get_nodenet(nodenet_uid).remove_recorder(recorder_uid) + return True + + +def clear_recorder(nodenet_uid, recorder_uid): + """Leaves the recorder intact, but deletes the current list of stored values.""" + micropsi_core.runtime.get_nodenet(nodenet_uid).get_recorder(recorder_uid).clear() + return True + + +def get_recorder_data(nodenet_uid): + return True, get_nodenet(nodenet_uid).construct_recorder_dict() diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index f0462b3c..89d72848 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -563,6 +563,22 @@ def remove_monitor(self, uid): """Removes the monitor with the given uid""" return self.__nodenet.remove_monitor(uid) + def add_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + return self.__nodenet.add_activation_recorder(group_definition, name, interval) + + def add_linkweight_recorder(self, from_group_definition, to_group_definition, name, interval=1): + """ Adds a linkweight recorder to links between to groups.""" + return self.__nodenet.add_linkweight_recorder(from_group_definition, to_group_definition, name, interval) + + def get_recorder(self, uid): + """Returns the recorder with the given uid""" + return self.__nodenet.get_recorder(uid) + + def remove_recorder(self, uid): + """Removes the recorder with the given uid""" + return self.__nodenet.remove_recorder(uid) + def set_dashboard_value(self, name, value): """Allows the netapi to set values for the statistics and dashboard""" self.__nodenet.dashboard_values[name] = value diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index b3e1327c..e47adab4 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -13,6 +13,7 @@ import micropsi_core.tools from .netapi import NetAPI from . import monitor +from . import recorder __author__ = 'joscha' __date__ = '09.05.12' @@ -155,6 +156,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.owner = owner self._monitors = {} + self._recorders = {} self._nodespace_ui_properties = {} self.netlock = Lock() @@ -626,9 +628,14 @@ def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], def get_monitor(self, uid): return self._monitors.get(uid) - def update_monitors(self): + def get_recorder(self, uid): + return self._recorders.get(uid) + + def update_monitors_and_recorders(self): for uid in self._monitors: self._monitors[uid].step(self.current_step) + for uid in self._recorders: + self._recorders[uid].step(self.current_step) def construct_monitors_dict(self): data = {} @@ -636,9 +643,26 @@ def construct_monitors_dict(self): data[monitor_uid] = self._monitors[monitor_uid].get_data() return data + def construct_recorders_dict(self): + data = {} + for uid in self._recorders: + data[uid] = self._recorders[uid].get_data() + return data + def remove_monitor(self, monitor_uid): del self._monitors[monitor_uid] + def add_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + raise NotImplementedError("Recorders are not implemented in the this engine") + + def add_linkweight_recorder(self, from_group_definition, to_group_definition, name, interval=1): + """ Adds a linkweight recorder to links between to groups.""" + raise NotImplementedError("Recorders are not implemented in the this engine") + + def remove_recorder(self, recorder_uid): + del self._recorders[recorder_uid] + def get_dashboard(self): data = self.dashboard_values.copy() data['is_active'] = self.is_active diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py new file mode 100644 index 00000000..50af3908 --- /dev/null +++ b/micropsi_core/nodenet/recorder.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- + + +""" +Recorder + +Recorders need nummpy, record things like activation, linkweights, biases over time, +and persist to their own files. they can be imported and exported as numpy npz +""" + +import os +try: + import numpy as np +except ImportError: + pass +from abc import ABCMeta, abstractmethod +from micropsi_core import tools +from micropsi_core.runtime import PERSISTENCY_PATH, NODENET_DIRECTORY + + +class Recorder(metaclass=ABCMeta): + + """A recorder will record values from section of the nodenet + and offer import/export functionaliy + Recorders need numpy.""" + + initial_size = 10000 + + def __init__(self, nodenet, name="", uid="", interval=1): + self._nodenet = nodenet + self.name = name + self.uid = uid or tools.generate_uid() + self.interval = interval + self.filename = os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, '%s_recorder_%s.npz' % (self._nodenet.uid, self.uid)) + self.first_step = nodenet.current_step + 1 + self.shapes = {} + self.values = {} + if os.path.isfile(self.filename): + self.load() + + def get_data(self): + data = { + "uid": self.uid, + "name": self.name, + "interval": self.interval, + "filename": self.filename, + "first_step": self.first_step, + "classname": self.__class__.__name__ + } + return data + + def step(self, step): + if step % self.interval == 0: + values = self.get_values() + for key in values: + if key not in self.values: + self.values[key] = np.zeros(shape=self.shapes[key], dtype=self._nodenet.numpyfloatX) + if step - self.first_step >= len(self.values[key]): + self.shapes[key][0] += self.initial_size + new_values = np.zeros(shape=self.shapes[key], dtype=self._nodenet.numpyfloatX) + new_values[0:len(self.values[key])] = self.values + self.values = new_values + self.values[key][step - self.first_step] = values[key] + + @abstractmethod + def get_values(self): + pass # no cover + + def save(self, filename=None): + np.savez(filename if filename is not None else self.filename, **self.values) + + def load(self, filename=None): + data = np.load(filename if filename is not None else self.filename) + for key in data: + self.values[key] = data[key] + self.shapes[key] = data[key].shape + + def clear(self): + self.values = {} + + def import_file(self, filename): + self.load(filename) + + +class ActivationRecorder(Recorder): + """ An activation recorder to record activaitons of nodegroups""" + + def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, **_): + super().__init__(nodenet, name, uid, interval) + if 'group_name' not in group_config: + group_config['group_name'] = name + self.group_config = group_config + self.nodespace = group_config['nodespace_uid'] + self.group_name = group_config['group_name'] + + if not group_config.get('node_uids', []): + self._nodenet.group_nodes_by_names(**group_config) + else: + self._nodenet.group_nodes_by_ids(**group_config) + + uids = self._nodenet.get_node_uids(self.nodespace, self.group_name) + self.shapes = {'activations': (self.initial_size, len(uids))} + + def get_data(self): + data = super().get_data() + data.update({ + "group_config": self.group_config, + }) + return data + + def get_values(self): + return {'activations': self._nodenet.get_activations(self.nodespace, self.group_name)} + + +class LinkweightRecorder(Recorder): + """ An activation recorder to biases and the linkweights of two nodegroups""" + + def __init__(self, nodenet, from_group_config={}, to_group_config={}, name="", uid="", interval=1, **_): + super().__init__(nodenet, name, uid, interval) + + if 'group_name' not in from_group_config: + from_group_config['group_name'] = "%s_from" % name + if 'group_name' not in to_group_config: + to_group_config['group_name'] = "%s_to" % name + + self.from_group_config = from_group_config + self.to_group_config = to_group_config + + self.from_nodespace = from_group_config['nodespace_uid'] + self.to_nodespace = to_group_config['nodespace_uid'] + self.from_name = from_group_config['group_name'] + self.to_name = to_group_config['group_name'] + + if not from_group_config.get('node_uids', []): + self._nodenet.group_nodes_by_names(**from_group_config) + else: + self._nodenet.group_nodes_by_ids(**from_group_config) + + if not to_group_config.get('node_uids', []): + self._nodenet.group_nodes_by_names(**to_group_config) + else: + self.nodenet.group_nodes_by_ids(**to_group_config) + + weights = self._nodenet.get_link_weights(self.from_nodespace, self.from_name, self.to_nodespace, self.to_name) + from_uids = self._nodenet.get_node_uids(self.from_nodespace, self.from_name) + to_uids = self._nodenet.get_node_uids(self.to_nodespace, self.to_name) + self.shapes = { + 'linkweights': (self.initial_size, weights.shape[0], weights.shape[1]), + 'from_thetas': (self.initial_size, len(from_uids)), + 'to_thetas': (self.initial_size, len(to_uids)) + } + + def get_data(self): + data = super().get_data() + data.update({ + 'from_group_config': self.from_group_config, + 'to_group_config': self.to_group_config + }) + return data + + def get_values(self): + return { + 'linkweights': self._nodenet.get_link_weights(self.from_nodespace, self.from_name, self.to_nodespace, self.to_name), + 'from_thetas': self._nodenet.get_thetas(self.from_nodespace, self.from_name), + 'to_thetas': self._nodenet.get_thetas(self.to_nodespace, self.to_name) + } diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 40ea5bf3..6def200d 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -13,6 +13,7 @@ import scipy from micropsi_core.nodenet import monitor +from micropsi_core.nodenet import recorder from micropsi_core.nodenet.nodenet import Nodenet from micropsi_core.nodenet.node import Nodetype from micropsi_core.nodenet.stepoperators import DoernerianEmotionalModulators @@ -369,8 +370,12 @@ def save(self, filename): metadata['monitors'] = self.construct_monitors_dict() metadata['modulators'] = self.construct_modulators_dict() metadata['partition_parents'] = self.inverted_partitionmap + metadata['recorders'] = self.construct_recorders_dict() fp.write(json.dumps(metadata, sort_keys=True, indent=4)) + for recorder_uid in self._recorders: + self._recorders[recorder_uid].save() + for partition in self.partitions.values(): # write bulk data to our own numpy-based file format datafilename = os.path.join(os.path.dirname(filename), self.uid + "-data-" + partition.spid) @@ -424,6 +429,9 @@ def load(self, filename): else: self.logger.warn('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) + for recorder_uid in initfrom.get('recorders', {}): + data = initfrom['recorders'][recorder_uid] + self._recorders[recorder_uid] = getattr(recorder, data['classname'])(self, **data) # re-initialize step operators for theano recompile to new shared variables self.initialize_stepoperators() @@ -1613,6 +1621,18 @@ def get_nodespace_changes(self, nodespace_uids=[], since_step=0): result['nodespaces_dirty'][uid] = self.get_nodespace(uid).get_data() return result + def add_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + rec = recorder.ActivationRecorder(self, group_definition, name, interval=interval) + self._recorders[rec.uid] = rec + return rec + + def add_linkweight_recorder(self, from_group_definition, to_group_definition, name, interval=1): + """ Adds a linkweight recorder to links between to groups.""" + rec = recorder.LinkweightRecorder(self, from_group_definition, to_group_definition, name, interval=interval) + self._recorders[rec.uid] = rec + return rec + def get_dashboard(self): data = super(TheanoNodenet, self).get_dashboard() data['count_nodes'] = 0 diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 6293d776..7ec8a883 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -166,7 +166,7 @@ def run(self): nodenet.timed_step() if self.profiler: self.profiler.disable() - nodenet.update_monitors() + nodenet.update_monitors_and_recorders() except: if self.profiler: self.profiler.disable() @@ -633,7 +633,7 @@ def step_nodenet(nodenet_uid): """ nodenet = get_nodenet(nodenet_uid) nodenet.timed_step() - nodenet.update_monitors() + nodenet.update_monitors_and_recorders() if nodenet.world and nodenet.current_step % configs['runner_factor'] == 0: worlds[nodenet.world].step() return nodenet.current_step @@ -648,13 +648,13 @@ def step_nodenets_in_world(world_uid, nodenet_uid=None, steps=1): if nodenet and nodenet.world == world_uid: for i in range(steps): nodenet.timed_step() - nodenet.update_monitors() + nodenet.update_monitors_and_recorders() else: for i in range(steps): for uid in worlds[world_uid].agents: nodenet = get_nodenet(uid) nodenet.timed_step() - nodenet.update_monitors() + nodenet.update_monitors_and_recorders() return True diff --git a/micropsi_core/tests/test_recorders.py b/micropsi_core/tests/test_recorders.py new file mode 100644 index 00000000..28896cc5 --- /dev/null +++ b/micropsi_core/tests/test_recorders.py @@ -0,0 +1,98 @@ + +import os +import pytest + +from micropsi_core import runtime as micropsi + + +@pytest.mark.engine("theano_engine") +def test_activation_recorder(test_nodenet, resourcepath): + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + nodes = [] + for i in range(10): + micropsi.step_nodenet(test_nodenet) + node = netapi.create_node('Register', None, "testnode_%d" % i) + nodes.append(node) + if i > 0: + netapi.link(nodes[i - 1], 'gen', node, 'gen') + source = netapi.create_node("Register", None, "Source") + netapi.link(source, 'gen', source, 'gen') + netapi.link(source, 'gen', nodes[0], 'gen') + source.activation = 1 + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=2) + assert recorder.first_step == 11 + assert recorder.name == 'recorder' + assert recorder.interval == 2 + for i in range(5): + micropsi.step_nodenet(test_nodenet) + filename = os.path.join(resourcepath, 'recorder.npz') + recorder.save(filename=filename) + assert os.path.isfile(filename) + assert recorder.values['activations'][1].tolist() == [1, 1, 0, 0, 0, 0, 0, 0, 0, 0] + micropsi.save_nodenet(test_nodenet) + micropsi.revert_nodenet(test_nodenet) + recorder = netapi.get_recorder(recorder.uid) + assert recorder.values['activations'][1].tolist() == [1, 1, 0, 0, 0, 0, 0, 0, 0, 0] + + +@pytest.mark.engine("theano_engine") +def test_linkweight_recorder(test_nodenet, resourcepath): + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + layer1 = [] + layer2 = [] + for i in range(10): + layer1.append(netapi.create_node('Register', None, "l1_%d" % i)) + layer2.append(netapi.create_node('Register', None, "l2_%d" % i)) + for i in range(10): + for j in range(10): + netapi.link(layer1[i], 'gen', layer2[j], 'gen', weight=0.89) + + recorder = netapi.add_linkweight_recorder( + from_group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'l1'}, + to_group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'l2'}, + name="recorder", interval=1) + + micropsi.step_nodenet(test_nodenet) + values = recorder.values + assert set(["%.2f" % item for row in values['linkweights'][0] for item in row]) == {"0.89"} + assert len(values['from_thetas'][0]) == 10 + assert len(values['to_thetas'][0]) == 10 + micropsi.save_nodenet(test_nodenet) + micropsi.revert_nodenet(test_nodenet) + recorder = netapi.get_recorder(recorder.uid) + assert set(["%.2f" % item for row in recorder.values['linkweights'][0] for item in row]) == {"0.89"} + assert len(values['from_thetas'][0]) == 10 + assert len(values['to_thetas'][0]) == 10 + + +@pytest.mark.engine("theano_engine") +def test_clear_recorder(test_nodenet, resourcepath): + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(5): + netapi.create_node('Register', None, "testnode_%d" % i) + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + for i in range(3): + micropsi.step_nodenet(test_nodenet) + assert len(recorder.values['activations'].tolist()[3]) == 5 + recorder.clear() + assert recorder.values == {} + + +@pytest.mark.engine("theano_engine") +def test_remove_recorder(test_nodenet, resourcepath): + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(5): + netapi.create_node('Register', None, "testnode_%d" % i) + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + for i in range(3): + micropsi.step_nodenet(test_nodenet) + netapi.remove_recorder(recorder.uid) + assert netapi.get_recorder(recorder.uid) is None diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index a4c73824..380d7b42 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1233,6 +1233,34 @@ def get_emoexpression_parameters(nodenet_uid): nodenet = runtime.get_nodenet(nodenet_uid) return True, emoexpression.calc_emoexpression_parameters(nodenet) + +# --------- recorder -------- + + +@rpc("add_activation_recorder") +def add_activation_recorder(nodenet_uid, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + return runtime.add_activation_recorder(nodenet_uid, group_definition, name, interval) + + +@rpc("add_linkweight_recorder") +def add_linkweight_recorder(nodenet_uid, from_group_definition, to_group_definition, name, interval=1): + """ Adds a linkweight recorder to links between to groups.""" + return runtime.add_linkweight_recorder(nodenet_uid, from_group_definition, to_group_definition, name, interval) + + +@rpc("remove_recorder") +def remove_recorder(nodenet_uid, recorder_uid): + """Deletes a recorder.""" + return runtime.remove_recorder(nodenet_uid, recorder_uid) + + +@rpc("clear_recorder") +def clear_recorder(nodenet_uid, recorder_uid): + """Leaves the recorder intact, but deletes the current list of stored values.""" + return runtime.clear_recorder(nodenet_uid, recorder_uid) + + # --------- logging -------- diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index e561f43e..4313a6c9 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1526,3 +1526,99 @@ def test_run_operation(app, test_nodenet, node): 'selection_uids': [None] }) assert response.json_body['status'] == 'success' + + +@pytest.mark.engine("theano_engine") +def test_add_activation_recorder(app, test_nodenet, resourcepath): + from micropsi_core import runtime + app.set_auth() + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(3): + netapi.create_node('Register', None, "testnode_%d" % i) + response = app.post_json('/rpc/add_activation_recorder', { + 'nodenet_uid': test_nodenet, + 'group_definition': {'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, + 'name': "recorder", + 'interval': 2 + }) + assert_success(response) + recorder_uid = response.json_body['data'] + runtime.step_nodenet(test_nodenet) + runtime.step_nodenet(test_nodenet) + assert netapi.get_recorder(recorder_uid).name == 'recorder' + assert 'activations' in netapi.get_recorder(recorder_uid).values + + +@pytest.mark.engine("theano_engine") +def test_add_linkweight_recorder(app, test_nodenet, resourcepath): + from micropsi_core import runtime + app.set_auth() + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + layer1 = [] + layer2 = [] + for i in range(3): + layer1.append(netapi.create_node('Register', None, "l1_%d" % i)) + layer2.append(netapi.create_node('Register', None, "l2_%d" % i)) + for i in range(3): + for j in range(3): + netapi.link(layer1[i], 'gen', layer2[j], 'gen', weight=0.89) + + response = app.post_json('/rpc/add_linkweight_recorder', { + 'nodenet_uid': test_nodenet, + 'from_group_definition': {'nodespace_uid': nodespace.uid, 'node_name_prefix': 'l1'}, + 'to_group_definition': {'nodespace_uid': nodespace.uid, 'node_name_prefix': 'l2'}, + 'name': "recorder", + 'interval': 2 + }) + assert_success(response) + recorder_uid = response.json_body['data'] + runtime.step_nodenet(test_nodenet) + runtime.step_nodenet(test_nodenet) + assert netapi.get_recorder(recorder_uid).name == 'recorder' + assert 'linkweights' in netapi.get_recorder(recorder_uid).values + + +@pytest.mark.engine("theano_engine") +def test_clear_recorder(app, test_nodenet, resourcepath): + from micropsi_core import runtime + app.set_auth() + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(3): + netapi.create_node('Register', None, "testnode_%d" % i) + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + for i in range(3): + runtime.step_nodenet(test_nodenet) + + response = app.post_json('/rpc/clear_recorder', { + 'nodenet_uid': test_nodenet, + 'recorder_uid': recorder.uid, + }) + assert_success(response) + assert recorder.values == {} + + +@pytest.mark.engine("theano_engine") +def test_remove_recorder(app, test_nodenet, resourcepath): + from micropsi_core import runtime + app.set_auth() + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(3): + netapi.create_node('Register', None, "testnode_%d" % i) + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + for i in range(3): + runtime.step_nodenet(test_nodenet) + + response = app.post_json('/rpc/remove_recorder', { + 'nodenet_uid': test_nodenet, + 'recorder_uid': recorder.uid, + }) + assert_success(response) + assert netapi.get_recorder(recorder.uid) is None From b7b27d100773e8c5f2828c6403346bdbbd579384 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 29 Apr 2016 16:21:20 +0200 Subject: [PATCH 024/945] test & fix growing, do not use step as index --- micropsi_core/nodenet/recorder.py | 13 +++++++++---- micropsi_core/tests/test_recorders.py | 21 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 50af3908..b4c71f3c 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -35,6 +35,7 @@ def __init__(self, nodenet, name="", uid="", interval=1): self.first_step = nodenet.current_step + 1 self.shapes = {} self.values = {} + self.current_index = 0 if os.path.isfile(self.filename): self.load() @@ -44,6 +45,7 @@ def get_data(self): "name": self.name, "interval": self.interval, "filename": self.filename, + "current_index": self.current_index, "first_step": self.first_step, "classname": self.__class__.__name__ } @@ -56,11 +58,14 @@ def step(self, step): if key not in self.values: self.values[key] = np.zeros(shape=self.shapes[key], dtype=self._nodenet.numpyfloatX) if step - self.first_step >= len(self.values[key]): - self.shapes[key][0] += self.initial_size + newshapes = list(self.shapes[key]) + newshapes[0] += self.initial_size + self.shapes[key] = tuple(newshapes) new_values = np.zeros(shape=self.shapes[key], dtype=self._nodenet.numpyfloatX) - new_values[0:len(self.values[key])] = self.values - self.values = new_values - self.values[key][step - self.first_step] = values[key] + new_values[0:len(self.values[key])] = self.values[key] + self.values[key] = new_values + self.values[key][self.current_index] = values[key] + self.current_index += 1 @abstractmethod def get_values(self): diff --git a/micropsi_core/tests/test_recorders.py b/micropsi_core/tests/test_recorders.py index 28896cc5..4c065c87 100644 --- a/micropsi_core/tests/test_recorders.py +++ b/micropsi_core/tests/test_recorders.py @@ -30,11 +30,11 @@ def test_activation_recorder(test_nodenet, resourcepath): filename = os.path.join(resourcepath, 'recorder.npz') recorder.save(filename=filename) assert os.path.isfile(filename) - assert recorder.values['activations'][1].tolist() == [1, 1, 0, 0, 0, 0, 0, 0, 0, 0] + assert recorder.values['activations'][1].tolist() == [1, 1, 1, 1, 0, 0, 0, 0, 0, 0] micropsi.save_nodenet(test_nodenet) micropsi.revert_nodenet(test_nodenet) recorder = netapi.get_recorder(recorder.uid) - assert recorder.values['activations'][1].tolist() == [1, 1, 0, 0, 0, 0, 0, 0, 0, 0] + assert recorder.values['activations'][1].tolist() == [1, 1, 1, 1, 0, 0, 0, 0, 0, 0] @pytest.mark.engine("theano_engine") @@ -96,3 +96,20 @@ def test_remove_recorder(test_nodenet, resourcepath): micropsi.step_nodenet(test_nodenet) netapi.remove_recorder(recorder.uid) assert netapi.get_recorder(recorder.uid) is None + + +@pytest.mark.engine("theano_engine") +def test_grow_recorder_values(test_nodenet, resourcepath): + from micropsi_core.nodenet.recorder import Recorder + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(5): + netapi.create_node('Register', None, "testnode_%d" % i) + Recorder.initial_size = 5 + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + micropsi.step_nodenet(test_nodenet) + assert len(recorder.values['activations']) == 5 + for i in range(20): + micropsi.step_nodenet(test_nodenet) + assert len(recorder.values['activations'] == 25) From 83dd96013608ca9c666ae7c9002bb11493016416 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 29 Apr 2016 19:11:19 +0200 Subject: [PATCH 025/945] fixes, implement get_recorder --- micropsi_core/_runtime_api_monitors.py | 2 +- micropsi_core/nodenet/recorder.py | 18 ++++++++++-------- micropsi_core/tests/test_recorders.py | 3 ++- micropsi_server/micropsi_app.py | 8 ++++++-- micropsi_server/tests/test_json_api.py | 25 +++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index b40d320c..b9bc6f77 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -144,4 +144,4 @@ def clear_recorder(nodenet_uid, recorder_uid): def get_recorder_data(nodenet_uid): - return True, get_nodenet(nodenet_uid).construct_recorder_dict() + return True, micropsi_core.runtime.get_nodenet(nodenet_uid).construct_recorders_dict() diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index b4c71f3c..1afd9e96 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -26,16 +26,16 @@ class Recorder(metaclass=ABCMeta): initial_size = 10000 - def __init__(self, nodenet, name="", uid="", interval=1): + def __init__(self, nodenet, name="", uid="", interval=1, first_step=0, current_index=-1): self._nodenet = nodenet self.name = name self.uid = uid or tools.generate_uid() self.interval = interval self.filename = os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, '%s_recorder_%s.npz' % (self._nodenet.uid, self.uid)) - self.first_step = nodenet.current_step + 1 + self.first_step = first_step + self.current_index = current_index self.shapes = {} self.values = {} - self.current_index = 0 if os.path.isfile(self.filename): self.load() @@ -53,10 +53,13 @@ def get_data(self): def step(self, step): if step % self.interval == 0: + self.current_index += 1 values = self.get_values() for key in values: if key not in self.values: + self.first_step = step self.values[key] = np.zeros(shape=self.shapes[key], dtype=self._nodenet.numpyfloatX) + self.values[key][:] = np.NAN if step - self.first_step >= len(self.values[key]): newshapes = list(self.shapes[key]) newshapes[0] += self.initial_size @@ -65,7 +68,6 @@ def step(self, step): new_values[0:len(self.values[key])] = self.values[key] self.values[key] = new_values self.values[key][self.current_index] = values[key] - self.current_index += 1 @abstractmethod def get_values(self): @@ -90,8 +92,8 @@ def import_file(self, filename): class ActivationRecorder(Recorder): """ An activation recorder to record activaitons of nodegroups""" - def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, **_): - super().__init__(nodenet, name, uid, interval) + def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, first_step=0, current_index=-1, **_): + super().__init__(nodenet, name, uid, interval, first_step=first_step, current_index=current_index) if 'group_name' not in group_config: group_config['group_name'] = name self.group_config = group_config @@ -120,8 +122,8 @@ def get_values(self): class LinkweightRecorder(Recorder): """ An activation recorder to biases and the linkweights of two nodegroups""" - def __init__(self, nodenet, from_group_config={}, to_group_config={}, name="", uid="", interval=1, **_): - super().__init__(nodenet, name, uid, interval) + def __init__(self, nodenet, from_group_config={}, to_group_config={}, name="", uid="", interval=1, first_step=0, current_index=-1, **_): + super().__init__(nodenet, name, uid, interval, first_step=first_step, current_index=current_index) if 'group_name' not in from_group_config: from_group_config['group_name'] = "%s_from" % name diff --git a/micropsi_core/tests/test_recorders.py b/micropsi_core/tests/test_recorders.py index 4c065c87..f7744198 100644 --- a/micropsi_core/tests/test_recorders.py +++ b/micropsi_core/tests/test_recorders.py @@ -22,11 +22,12 @@ def test_activation_recorder(test_nodenet, resourcepath): netapi.link(source, 'gen', nodes[0], 'gen') source.activation = 1 recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=2) - assert recorder.first_step == 11 assert recorder.name == 'recorder' assert recorder.interval == 2 for i in range(5): micropsi.step_nodenet(test_nodenet) + assert recorder.first_step == 12 + assert recorder.current_index == 1 filename = os.path.join(resourcepath, 'recorder.npz') recorder.save(filename=filename) assert os.path.isfile(filename) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 380d7b42..81db5b3b 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1059,8 +1059,8 @@ def export_monitor_data(nodenet_uid, monitor_uid=None): @rpc("get_monitor_data") -def get_monitor_data(nodenet_uid, step, monitor_from=0, monitor_count=-1): - return True, runtime.get_monitor_data(nodenet_uid, step, monitor_from, monitor_count) +def get_monitor_data(nodenet_uid, step=0, monitor_from=0, monitor_count=-1): + return True, runtime.get_monitor_data(nodenet_uid, step, from_step=monitor_from, count=monitor_count) # Nodenet @@ -1261,6 +1261,10 @@ def clear_recorder(nodenet_uid, recorder_uid): return runtime.clear_recorder(nodenet_uid, recorder_uid) +@rpc("get_recorders") +def get_recorders(nodenet_uid): + return runtime.get_recorder_data(nodenet_uid) + # --------- logging -------- diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 4313a6c9..ec2654e5 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1622,3 +1622,28 @@ def test_remove_recorder(app, test_nodenet, resourcepath): }) assert_success(response) assert netapi.get_recorder(recorder.uid) is None + + +@pytest.mark.engine("theano_engine") +def test_get_recorders(app, test_nodenet): + from micropsi_core import runtime + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(3): + netapi.create_node('Register', None, "testnode_%d" % i) + runtime.step_nodenet(test_nodenet) + recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=3) + runtime.step_nodenet(test_nodenet) + runtime.step_nodenet(test_nodenet) + response = app.post_json('/rpc/get_recorders', { + 'nodenet_uid': test_nodenet + }) + data = response.json_body['data'][recorder.uid] + assert data["uid"] == recorder.uid + assert data["name"] == 'recorder' + assert data["interval"] == 3 + assert data["filename"] == recorder.filename + assert data["current_index"] == 0 + assert data["first_step"] == 3 + assert data["classname"] == 'ActivationRecorder' From 2414b8b5a7c2b9d7de87f9fcb21cea715f31bd6c Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 29 Apr 2016 22:07:32 +0200 Subject: [PATCH 026/945] Faster json export --- .../nodenet/theano_engine/theano_nodenet.py | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index e11e6da7..a4497c23 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1206,36 +1206,40 @@ def construct_links_list(self, nodespace_uid=None): nspartition = self.get_partition(nodespace_uid) if nspartition != partition: continue - parent = nodespace_from_id(nodespace_uid) - node_ids = np.where(partition.allocated_node_parents == parent)[0] - else: - node_ids = np.nonzero(partition.allocated_nodes)[0] + w_matrix = partition.w.get_value(borrow=True) - for node_id in node_ids: - - source_type = partition.allocated_nodes[node_id] - for gate_type in range(get_gates_per_type(source_type, self.native_modules)): - gatecolumn = w_matrix[:, partition.allocated_node_offsets[node_id] + gate_type] - links_indices = np.nonzero(gatecolumn)[0] - for index in links_indices: - target_id = partition.allocated_elements_to_nodes[index] - target_type = partition.allocated_nodes[target_id] - target_slot_numerical = index - partition.allocated_node_offsets[target_id] - target_slot_type = get_string_slot_type(target_slot_numerical, self.get_nodetype(get_string_node_type(target_type, self.native_modules))) - source_gate_type = get_string_gate_type(gate_type, self.get_nodetype(get_string_node_type(source_type, self.native_modules))) - if partition.sparse: # sparse matrices return matrices of dimension (1,1) as values - weight = float(gatecolumn[index].data) - else: - weight = gatecolumn[index].item() - - data.append({ - "weight": weight, - "certainty": 1, - "target_slot_name": target_slot_type, - "target_node_uid": node_to_id(target_id, partition.pid), - "source_gate_name": source_gate_type, - "source_node_uid": node_to_id(node_id, partition.pid) - }) + link_to_indices, link_from_indices = np.nonzero(w_matrix) + + for i, link_from_index in enumerate(link_from_indices): + link_to_index = link_to_indices[i] + + source_id = partition.allocated_elements_to_nodes[link_from_index] + source_type = partition.allocated_nodes[source_id] + + if nodespace_uid is not None: + nid = nodespace_from_id(nodespace_uid) + if partition.allocated_node_parents[source_id] != nid: + continue + + target_id = partition.allocated_elements_to_nodes[link_to_index] + target_type = partition.allocated_nodes[target_id] + + target_slot_numerical = link_to_index - partition.allocated_node_offsets[target_id] + target_slot_type = get_string_slot_type(target_slot_numerical, self.get_nodetype(get_string_node_type(target_type, self.native_modules))) + + source_gate_numerical = link_from_index - partition.allocated_node_offsets[source_id] + source_gate_type = get_string_gate_type(source_gate_numerical, self.get_nodetype(get_string_node_type(source_type, self.native_modules))) + + weight = w_matrix[link_to_index, link_from_index].item() + + data.append({ + "weight": weight, + "certainty": 1, + "target_slot_name": target_slot_type, + "target_node_uid": node_to_id(target_id, partition.pid), + "source_gate_name": source_gate_type, + "source_node_uid": node_to_id(source_id, partition.pid) + }) # find links going out to other partitions for partition_to_spid, to_partition in self.partitions.items(): From 2c182463ac5a89729ae161235ed58e39d039837f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 29 Apr 2016 23:09:21 +0200 Subject: [PATCH 027/945] get rid of incorrectly named export_monitor_data, delivery routes for recorders to frontend --- micropsi_core/_runtime_api_monitors.py | 64 +++++------ micropsi_core/nodenet/nodenet.py | 6 +- micropsi_core/nodenet/recorder.py | 4 +- micropsi_core/runtime.py | 8 +- micropsi_core/tests/test_runtime_monitors.py | 68 +++++------- micropsi_server/micropsi_app.py | 25 ++--- micropsi_server/static/js/nodenet.js | 10 -- micropsi_server/tests/test_json_api.py | 105 ++++++++----------- 8 files changed, 117 insertions(+), 173 deletions(-) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index b9bc6f77..35773165 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -69,40 +69,7 @@ def clear_monitor(nodenet_uid, monitor_uid): return True -def export_monitor_data(nodenet_uid, monitor_uid=None, monitor_from=0, monitor_count=-1): - """Returns a string with all currently stored monitor data for the given nodenet.""" - nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) - if monitor_from == 0 and monitor_count > 0: - monitor_count = min(nodenet.current_step + 1, monitor_count) - monitor_from = max(0, nodenet.current_step + 1 - monitor_count) - if monitor_from > 0: - if monitor_count < 1: - monitor_count = (nodenet.current_step + 1 - monitor_from) - elif monitor_from + monitor_count > nodenet.current_step: - monitor_from = max(nodenet.current_step + 1 - monitor_count, 0) - if monitor_uid is not None: - data = nodenet.construct_monitors_dict()[monitor_uid] - if monitor_from > 0 or monitor_count > 0: - values = {} - i = monitor_from - while i < monitor_count + monitor_from: - values[i] = data['values'].get(i) - i += 1 - data['values'] = values - else: - data = nodenet.construct_monitors_dict() - if monitor_from > 0 or monitor_count > 0: - for uid in data: - values = {} - i = monitor_from - while i < monitor_count + monitor_from: - values[i] = data[uid]['values'].get(i) - i += 1 - data[uid]['values'] = values - return data - - -def get_monitor_data(nodenet_uid, step=0, monitor_from=0, monitor_count=-1): +def get_monitor_data(nodenet_uid, step=0, from_step=0, count=-1, with_recorders=False): """Returns monitor and nodenet data for drawing monitor plots for the current step, if the current step is newer than the supplied calculation step.""" nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) @@ -113,7 +80,34 @@ def get_monitor_data(nodenet_uid, step=0, monitor_from=0, monitor_count=-1): if step > data['current_step']: return data else: - data['monitors'] = micropsi_core.runtime.export_monitor_data(nodenet_uid, None, monitor_from=monitor_from, monitor_count=monitor_count) + monitor_data = {} + if from_step == 0 and count > 0: + count = min(nodenet.current_step + 1, count) + from_step = max(0, nodenet.current_step + 1 - count) + if from_step > 0: + if count < 1: + count = (nodenet.current_step + 1 - from_step) + elif from_step + count > nodenet.current_step: + from_step = max(nodenet.current_step + 1 - count, 0) + monitor_data = nodenet.construct_monitors_dict() + if from_step > 0 or count > 0: + for uid in monitor_data: + values = {} + i = from_step + while i < count + from_step: + values[i] = monitor_data[uid]['values'].get(i) + i += 1 + monitor_data[uid]['values'] = values + data['monitors'] = monitor_data + if with_recorders: + # recorder_data = {} + # for uid in nodenet.construct_recorders_dict(): + # rec = nodenet.get_recorder(uid) + # recorder_data[uid] = rec.get_data() + # values = rec.values.tolist() + # recorder_data[uid]['values'] = values[from_step + rec.first_step : from_step + count + rec.first_step] + # data['recorders'] = recorder_data + data['recorders'] = nodenet.construct_recorders_dict() return data diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index e47adab4..074b2a61 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -4,8 +4,9 @@ Nodenet definition """ - +import os import logging + from datetime import datetime from threading import Lock from abc import ABCMeta, abstractmethod @@ -661,6 +662,9 @@ def add_linkweight_recorder(self, from_group_definition, to_group_definition, na raise NotImplementedError("Recorders are not implemented in the this engine") def remove_recorder(self, recorder_uid): + filename = self._recorders[recorder_uid].filename + if os.path.isfile(filename): + os.remove(filename) del self._recorders[recorder_uid] def get_dashboard(self): diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 1afd9e96..db3526ed 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -74,7 +74,8 @@ def get_values(self): pass # no cover def save(self, filename=None): - np.savez(filename if filename is not None else self.filename, **self.values) + values = self.values + np.savez(filename if filename is not None else self.filename, **values) def load(self, filename=None): data = np.load(filename if filename is not None else self.filename) @@ -84,6 +85,7 @@ def load(self, filename=None): def clear(self): self.values = {} + self.current_index = -1 def import_file(self, filename): self.load(filename) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 7ec8a883..8ed4c041 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -262,9 +262,9 @@ def get_logger_messages(loggers=[], after=0): return logger.get_logs(loggers, after) -def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor_count=-1): +def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor_count=-1, with_recorders=False): """ Returns log-messages and monitor-data for the given nodenet.""" - data = get_monitor_data(nodenet_uid, 0, monitor_from, monitor_count) + data = get_monitor_data(nodenet_uid, 0, monitor_from, monitor_count, with_recorders=with_recorders) data['logs'] = get_logger_messages(logger, after) return data @@ -412,7 +412,7 @@ def get_nodes(nodenet_uid, nodespaces=[], include_links=True): return nodenet.get_nodes(nodespaces, include_links) -def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=None, monitors=None, dashboard=None): +def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=None, monitors=None, dashboard=None, recorders=None): """ returns the current state of the calculation """ data = {} @@ -455,6 +455,8 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No data['monitors'] = get_monitoring_info(nodenet_uid=nodenet_uid, **monitors) if dashboard is not None: data['dashboard'] = get_agent_dashboard(nodenet_uid) + if recorders is not None: + data['recorders'] = nodenet_obj.construct_recorders_dict() return True, data else: return False, "No such nodenet" diff --git a/micropsi_core/tests/test_runtime_monitors.py b/micropsi_core/tests/test_runtime_monitors.py index 70498fd4..58e8fa39 100644 --- a/micropsi_core/tests/test_runtime_monitors.py +++ b/micropsi_core/tests/test_runtime_monitors.py @@ -100,27 +100,30 @@ def test_remove_monitor(fixed_nodenet): def test_remove_monitored_node(fixed_nodenet): + nodenet = micropsi.nodenets[fixed_nodenet] uid = micropsi.add_gate_monitor(fixed_nodenet, 'n0001', 'gen', sheaf='default') micropsi.delete_nodes(fixed_nodenet, ['n0001']) micropsi.step_nodenet(fixed_nodenet) - monitor = micropsi.export_monitor_data(fixed_nodenet) - assert monitor[uid]['values'][1] is None + monitor = nodenet.get_monitor(uid) + assert monitor.values[1] is None def test_remove_monitored_link(fixed_nodenet): + nodenet = micropsi.nodenets[fixed_nodenet] uid = micropsi.add_link_monitor(fixed_nodenet, 'n0005', 'gen', 'n0003', 'gen', 'weight', 'Testmonitor') micropsi.delete_link(fixed_nodenet, 'n0005', 'gen', 'n0003', 'gen') micropsi.step_nodenet(fixed_nodenet) - monitor = micropsi.export_monitor_data(fixed_nodenet) - assert monitor[uid]['values'][1] is None + monitor = nodenet.get_monitor(uid) + assert monitor.values[1] is None def test_remove_monitored_link_via_delete_node(fixed_nodenet): + nodenet = micropsi.nodenets[fixed_nodenet] uid = micropsi.add_link_monitor(fixed_nodenet, 'n0005', 'gen', 'n0003', 'gen', 'weight', 'Testmonitor') micropsi.delete_nodes(fixed_nodenet, ['n0005']) micropsi.step_nodenet(fixed_nodenet) - monitor = micropsi.export_monitor_data(fixed_nodenet) - assert monitor[uid]['values'][1] is None + monitor = nodenet.get_monitor(uid) + assert monitor.values[1] is None def test_get_monitor_data(fixed_nodenet): @@ -134,25 +137,6 @@ def test_get_monitor_data(fixed_nodenet): assert [k for k in values.keys()] == [1] -def test_export_monitor_data(fixed_nodenet): - uid1 = micropsi.add_gate_monitor(fixed_nodenet, 'n0001', 'gen') - uid2 = micropsi.add_gate_monitor(fixed_nodenet, 'n0003', 'gen') - micropsi.step_nodenet(fixed_nodenet) - data = micropsi.export_monitor_data(fixed_nodenet) - assert uid1 in data - assert 'values' in data[uid1] - assert uid2 in data - - -def test_export_monitor_data_with_id(fixed_nodenet): - uid1 = micropsi.add_gate_monitor(fixed_nodenet, 'n0001', 'gen', name="Testmonitor") - micropsi.add_gate_monitor(fixed_nodenet, 'n0003', 'gen') - micropsi.step_nodenet(fixed_nodenet) - data = micropsi.export_monitor_data(fixed_nodenet, monitor_uid=uid1) - assert data['name'] == 'Testmonitor' - assert 'values' in data - - def test_clear_monitor(fixed_nodenet): uid = micropsi.add_gate_monitor(fixed_nodenet, 'n0001', 'gen') micropsi.step_nodenet(fixed_nodenet) @@ -162,41 +146,36 @@ def test_clear_monitor(fixed_nodenet): assert len(values.keys()) == 0 -def test_fetch_partial_monitor_data(fixed_nodenet): +def test_get_partial_monitor_data(fixed_nodenet): uid = micropsi.add_gate_monitor(fixed_nodenet, 'n0001', 'gen') i = 0 while i < 50: micropsi.step_nodenet(fixed_nodenet) i += 1 - assert micropsi.nodenets[fixed_nodenet].current_step == 50 + nodenet = micropsi.nodenets[fixed_nodenet] + assert nodenet.current_step == 50 # get 10 items from [20 - 29] - data = micropsi.export_monitor_data(fixed_nodenet, monitor_from=20, monitor_count=10) - values = data[uid]['values'] - assert len(values.keys()) == 10 - assert set(list(values.keys())) == set(range(20, 30)) - - # get 10 items from [20 - 29] for one monitor - data = micropsi.export_monitor_data(fixed_nodenet, monitor_uid=uid, monitor_from=20, monitor_count=10) - values = data['values'] + data = micropsi.get_monitor_data(fixed_nodenet, from_step=20, count=10) + values = data['monitors'][uid]['values'] assert len(values.keys()) == 10 assert set(list(values.keys())) == set(range(20, 30)) # get 10 newest values [41-50] - data = micropsi.export_monitor_data(fixed_nodenet, monitor_count=10) - values = data[uid]['values'] + data = micropsi.get_monitor_data(fixed_nodenet, count=10) + values = data['monitors'][uid]['values'] assert len(values.keys()) == 10 assert set(list(values.keys())) == set(range(41, 51)) # get 10 items, starting at 45 -- assert they are filled up to the left. - data = micropsi.export_monitor_data(fixed_nodenet, monitor_from=40, monitor_count=15) - values = data[uid]['values'] + data = micropsi.get_monitor_data(fixed_nodenet, from_step=40, count=15) + values = data['monitors'][uid]['values'] assert len(values.keys()) == 15 assert set(list(values.keys())) == set(range(36, 51)) # get all items, starting at 10 - data = micropsi.export_monitor_data(fixed_nodenet, monitor_from=10) - values = data[uid]['values'] + data = micropsi.get_monitor_data(fixed_nodenet, from_step=10) + values = data['monitors'][uid]['values'] assert len(values.keys()) == 41 assert set(list(values.keys())) == set(range(10, 51)) @@ -218,14 +197,15 @@ def test_add_group_monitor(test_nodenet): monitor_uid = netapi.add_group_monitor(nodespace.uid, 'testndoes', node_name_prefix='testnode', gate='gen', color='purple') for i in range(5): micropsi.step_nodenet(test_nodenet) - data = micropsi.export_monitor_data(test_nodenet, monitor_uid=monitor_uid) + data = nodenet.get_monitor(monitor_uid).get_data() assert set(data['values'][4][:4]) == {1.0} # first 4 active assert set(data['values'][4][4:]) == {0.0} # rest off micropsi.save_nodenet(test_nodenet) micropsi.revert_nodenet(test_nodenet) - data2 = micropsi.export_monitor_data(test_nodenet, monitor_uid=monitor_uid) + nodenet = micropsi.nodenets[test_nodenet] + data2 = nodenet.get_monitor(monitor_uid).get_data() assert data2 == data micropsi.step_nodenet(test_nodenet) - data3 = micropsi.export_monitor_data(test_nodenet, monitor_uid=monitor_uid) + data3 = nodenet.get_monitor(monitor_uid).get_data() assert set(data3['values'][6][:6]) == {1.0} # first 6 active assert set(data3['values'][6][6:]) == {0.0} # rest off diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 81db5b3b..67419b16 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -555,12 +555,12 @@ def export_nodenet(nodenet_uid): return runtime.export_nodenet(nodenet_uid) -@micropsi_app.route("/monitor/export/-") -def export_monitor(nodenet_uid, monitor_uid): - data = runtime.export_monitor_data(nodenet_uid, monitor_uid) - response.set_header('Content-type', 'application/json') - response.set_header('Content-Disposition', 'attachment; filename="monitor_%s.json"' % data['name']) - return json.dumps(data['values'], sort_keys=True, indent=2) +# @micropsi_app.route("/monitor/export/-") +# def export_monitor(nodenet_uid, monitor_uid): +# data = runtime.export_monitor_data(nodenet_uid, monitor_uid) +# response.set_header('Content-type', 'application/json') +# response.set_header('Content-Disposition', 'attachment; filename="monitor_%s.json"' % data['name']) +# return json.dumps(data['values'], sort_keys=True, indent=2) @micropsi_app.route("/nodenet/edit") @@ -747,8 +747,8 @@ def new_nodenet(name, owner=None, engine='dict_engine', template=None, worldadap @rpc("get_calculation_state") -def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=None, monitors=None, dashboard=None): - return runtime.get_calculation_state(nodenet_uid, nodenet=nodenet, nodenet_diff=nodenet_diff, world=world, monitors=monitors, dashboard=dashboard) +def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=None, monitors=None, dashboard=None, recorders=None): + return runtime.get_calculation_state(nodenet_uid, nodenet=nodenet, nodenet_diff=nodenet_diff, world=world, monitors=monitors, dashboard=dashboard, recorders=recorders) @rpc("get_nodenet_changes") @@ -1053,11 +1053,6 @@ def clear_monitor(nodenet_uid, monitor_uid): return dict(status='error', msg='unknown nodenet or monitor') -@rpc("export_monitor_data") -def export_monitor_data(nodenet_uid, monitor_uid=None): - return True, runtime.export_monitor_data(nodenet_uid, monitor_uid) - - @rpc("get_monitor_data") def get_monitor_data(nodenet_uid, step=0, monitor_from=0, monitor_count=-1): return True, runtime.get_monitor_data(nodenet_uid, step, from_step=monitor_from, count=monitor_count) @@ -1280,8 +1275,8 @@ def get_logger_messages(logger=[], after=0): @rpc("get_monitoring_info") -def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor_count=-1): - data = runtime.get_monitoring_info(nodenet_uid, logger, after, monitor_from, monitor_count) +def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor_count=-1, with_recorders=False): + data = runtime.get_monitoring_info(nodenet_uid, logger, after, monitor_from, monitor_count, with_recorders=with_recorders) return True, data diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 1bc25d4a..d3aa91fe 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -3830,16 +3830,6 @@ function handleEditNodespace(event){ } } - -function setMonitorData(uid){ - api.call('export_monitor_data', params={ - 'nodenet_uid': currentNodenet, - 'monitor_uid': uid - }, function(data){ - monitors[uid] = data; - }) -} - function removeMonitor(node, target, type){ monitor = getMonitor(node, target, type); api.call('remove_monitor', { diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index ec2654e5..fc9bb3d3 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -602,10 +602,10 @@ def test_import_world(app, test_world): ## ################################################### -def test_export_monitor_data_all(app, test_nodenet): - response = app.get_json('/rpc/export_monitor_data(nodenet_uid="%s")' % test_nodenet) +def test_get_monitor_data_all(app, test_nodenet): + response = app.get_json('/rpc/get_monitor_data(nodenet_uid="%s")' % test_nodenet) assert_success(response) - assert response.json_body['data'] == {} + assert response.json_body['data']['monitors'] == {} def test_add_gate_monitor(app, test_nodenet, node): @@ -617,15 +617,14 @@ def test_add_gate_monitor(app, test_nodenet, node): }) assert_success(response) uid = response.json_body['data'] - response = app.post_json('/rpc/export_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'monitor_uid': uid + response = app.post_json('/rpc/get_monitor_data', params={ + 'nodenet_uid': test_nodenet }) - assert response.json_body['data']['node_uid'] == node - assert response.json_body['data']['target'] == 'sub' - assert response.json_body['data']['type'] == 'gate' - assert response.json_body['data']['sheaf'] == 'default' - assert response.json_body['data']['values'] == {} + assert response.json_body['data']['monitors'][uid]['node_uid'] == node + assert response.json_body['data']['monitors'][uid]['target'] == 'sub' + assert response.json_body['data']['monitors'][uid]['type'] == 'gate' + assert response.json_body['data']['monitors'][uid]['sheaf'] == 'default' + assert response.json_body['data']['monitors'][uid]['values'] == {} @pytest.mark.engine("dict_engine") @@ -638,16 +637,15 @@ def test_add_slot_monitor(app, test_nodenet, node): }) assert_success(response) uid = response.json_body['data'] - response = app.post_json('/rpc/export_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'monitor_uid': uid + response = app.post_json('/rpc/get_monitor_data', params={ + 'nodenet_uid': test_nodenet }) - assert response.json_body['data']['name'] == 'Foobar' - assert response.json_body['data']['node_uid'] == node - assert response.json_body['data']['target'] == 'gen' - assert response.json_body['data']['type'] == 'slot' - assert response.json_body['data']['sheaf'] == 'default' - assert response.json_body['data']['values'] == {} + assert response.json_body['data']['monitors'][uid]['name'] == 'Foobar' + assert response.json_body['data']['monitors'][uid]['node_uid'] == node + assert response.json_body['data']['monitors'][uid]['target'] == 'gen' + assert response.json_body['data']['monitors'][uid]['type'] == 'slot' + assert response.json_body['data']['monitors'][uid]['sheaf'] == 'default' + assert response.json_body['data']['monitors'][uid]['values'] == {} def test_add_link_monitor(app, test_nodenet, node): @@ -662,16 +660,15 @@ def test_add_link_monitor(app, test_nodenet, node): }) assert_success(response) uid = response.json_body['data'] - response = app.post_json('/rpc/export_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'monitor_uid': uid + response = app.post_json('/rpc/get_monitor_data', params={ + 'nodenet_uid': test_nodenet }) - assert response.json_body['data']['name'] == 'LinkWeight' - assert response.json_body['data']['source_node_uid'] == node - assert response.json_body['data']['gate_type'] == 'gen' - assert response.json_body['data']['target_node_uid'] == node - assert response.json_body['data']['slot_type'] == 'gen' - assert response.json_body['data']['property'] == 'weight' + assert response.json_body['data']['monitors'][uid]['name'] == 'LinkWeight' + assert response.json_body['data']['monitors'][uid]['source_node_uid'] == node + assert response.json_body['data']['monitors'][uid]['gate_type'] == 'gen' + assert response.json_body['data']['monitors'][uid]['target_node_uid'] == node + assert response.json_body['data']['monitors'][uid]['slot_type'] == 'gen' + assert response.json_body['data']['monitors'][uid]['property'] == 'weight' def test_add_custom_monitor(app, test_nodenet): @@ -682,11 +679,10 @@ def test_add_custom_monitor(app, test_nodenet): }) assert_success(response) uid = response.json_body['data'] - response = app.post_json('/rpc/export_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'monitor_uid': uid + response = app.post_json('/rpc/get_monitor_data', params={ + 'nodenet_uid': test_nodenet }) - assert response.json_body['data']['name'] == 'nodecount' + assert response.json_body['data']['monitors'][uid]['name'] == 'nodecount' def test_add_group_monitor_by_name(app, test_nodenet): @@ -709,12 +705,11 @@ def test_add_group_monitor_by_name(app, test_nodenet): 'gate': 'gen' }) mon_uid = response.json_body['data'] - response = app.post_json('/rpc/export_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'monitor_uid': mon_uid + response = app.post_json('/rpc/get_monitor_data', params={ + 'nodenet_uid': test_nodenet }) - assert response.json_body['data']['name'] == 'testmonitor' - assert response.json_body['data']['node_uids'] == uids + assert response.json_body['data']['monitors'][mon_uid]['name'] == 'testmonitor' + assert response.json_body['data']['monitors'][mon_uid]['node_uids'] == uids def test_add_group_monitor_by_ids(app, test_nodenet): @@ -737,12 +732,11 @@ def test_add_group_monitor_by_ids(app, test_nodenet): 'gate': 'gen' }) mon_uid = response.json_body['data'] - response = app.post_json('/rpc/export_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'monitor_uid': mon_uid + response = app.post_json('/rpc/get_monitor_data', params={ + 'nodenet_uid': test_nodenet }) - assert response.json_body['data']['name'] == 'testmonitor' - assert response.json_body['data']['node_uids'] == uids + assert response.json_body['data']['monitors'][mon_uid]['name'] == 'testmonitor' + assert response.json_body['data']['monitors'][mon_uid]['node_uids'] == uids def test_remove_monitor(app, test_nodenet, node): @@ -757,10 +751,10 @@ def test_remove_monitor(app, test_nodenet, node): 'monitor_uid': uid }) assert_success(response) - response = app.post_json('/rpc/export_monitor_data', params={ + response = app.post_json('/rpc/get_monitor_data', params={ 'nodenet_uid': test_nodenet }) - assert uid not in response.json_body['data'] + assert uid not in response.json_body['data']['monitors'] def test_clear_monitor(app, test_nodenet, node): @@ -777,23 +771,6 @@ def test_clear_monitor(app, test_nodenet, node): assert_success(response) -def test_get_monitor_data(app, test_nodenet, node): - response = app.post_json('/rpc/add_gate_monitor', params={ - 'nodenet_uid': test_nodenet, - 'node_uid': node, - 'gate': 'sub' - }) - uid = response.json_body['data'] - response = app.post_json('/rpc/get_monitor_data', params={ - 'nodenet_uid': test_nodenet, - 'step': 0, - 'monitor_from': 3, - 'monitor_count': 20 - }) - assert_success(response) - assert uid in response.json_body['data']['monitors'] - - ################################################### ## ## @@ -1395,8 +1372,8 @@ def test_nodenet_data_structure(app, test_nodenet, resourcepath, node): data = response_2.json_body['data'] # Monitors - response = app.get_json('/rpc/export_monitor_data(nodenet_uid="%s", monitor_uid="%s")' % (test_nodenet, monitor_uid)) - monitor_data = response.json_body['data'] + response = app.get_json('/rpc/get_monitor_data(nodenet_uid="%s")' % test_nodenet) + monitor_data = response.json_body['data']['monitors'][monitor_uid] assert data['monitors']['monitors'][monitor_uid]['name'] == 'Testmonitor' assert data['monitors']['monitors'][monitor_uid]['node_uid'] == node From f04ef8751d6acaf8ca5fc33badecba49f3052cee Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 29 Apr 2016 23:10:13 +0200 Subject: [PATCH 028/945] first basic frontend for recorders --- .../static/css/micropsi-styles.css | 4 ++ micropsi_server/static/js/monitor.js | 63 ++++++++++++++++++- micropsi_server/view/monitors.tpl | 18 +++--- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/micropsi_server/static/css/micropsi-styles.css b/micropsi_server/static/css/micropsi-styles.css index 8f6bd645..e88598b8 100644 --- a/micropsi_server/static/css/micropsi-styles.css +++ b/micropsi_server/static/css/micropsi-styles.css @@ -632,6 +632,10 @@ p.clear { font-size: 0.9em; } +table th { + text-align: left; +} + #console_input { border-top: 0px none; border-left: 0px none; diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 8abc5cbd..14fca430 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -56,9 +56,9 @@ $(function(){ if(!target.hasClass('active')){ var layout = target.attr('data'); if(layout == 'vertical'){ - $('.layout_field').addClass('span6'); + $('.layout_field').addClass('span4'); } else if(layout == 'horizontal'){ - $('.layout_field').removeClass('span6'); + $('.layout_field').removeClass('span4'); } refreshMonitors(); $('.layoutbtn').removeClass('active'); @@ -131,7 +131,8 @@ $(function(){ var params = { logger: poll, after: last_logger_call, - monitor_count: viewProperties.xvalues + monitor_count: viewProperties.xvalues, + with_recorders: true } if(fixed_position){ params['monitor_from'] = Math.max(fixed_position - (viewProperties.xvalues / 2), 1); @@ -139,10 +140,62 @@ $(function(){ return params; } + function setRecorderData(data){ + var table = $('#recorder_table'); + var html = ''; + for(var uid in data){ + var rec = data[uid]; + html += ''+rec.name+''; + html += ' '; + html += ' '; + html += ' '; + html += ' '; + html += '' + html += ' Type:'+rec.classname+''; + html += ' Entries:'+(rec.current_index + 1)+''; + html += ' Interval:'+rec.interval+''; + if(rec.group_config){ + html += ' Group:'+rec.group_config.group_name+''; + } + if(rec.from_group_config){ + html += ' Groups:From: '+rec.from_group_config.group_name+'
    To: '+rec.to_group_config.group_name+''; + } + html += ' '; + + html += ''; + } + table.html(html); + $('button', table).on('click', recorderAction); + } + + function recorderAction(event){ + var btn = $(event.target); + var uid = btn.attr("data-uid"); + var method_name = null; + switch(btn.attr('data-action')){ + case 'export': + // return self.location.href.replace('/recorder/export/'+currentNodenet+'/'+) + method_name = 'export_recorder'; break; + case 'clear': + method_name = 'clear_recorder'; break; + case 'delete': + method_name = 'remove_recorder'; break; + } + if(method_name){ + api.call(method_name, {nodenet_uid: currentNodenet, recorder_uid: uid}, function(data){ + api.defaultSuccessCallback(data); + refreshRecorders(); + }); + } + } + function setData(data){ currentSimulationStep = data.current_step; setMonitorData(data); setLoggingData(data); + if (data.recorders){ + setRecorderData(data.recorders); + } } register_stepping_function('monitors', getPollParams, setData); @@ -155,6 +208,10 @@ $(function(){ } } + function refreshRecorders(){ + api.call('get_recorders', {'nodenet_uid': currentNodenet}, setRecorderData); + } + function setMonitorData(data){ updateMonitorList(data.monitors); nodenetMonitors = data.monitors; diff --git a/micropsi_server/view/monitors.tpl b/micropsi_server/view/monitors.tpl index 91957112..ae7066be 100644 --- a/micropsi_server/view/monitors.tpl +++ b/micropsi_server/view/monitors.tpl @@ -36,7 +36,7 @@
    -
    +

    Monitors

    @@ -58,7 +58,7 @@
    -
    +

    Logs

    @@ -105,15 +105,17 @@

    - - -

    +

     

    +
    +
    +

    Recorders

    +
    + +
    +

     

    From 8ca5c57cf15882f3466fafc6e4c7f7813635f6d0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 29 Apr 2016 23:10:42 +0200 Subject: [PATCH 029/945] do not overwrite nodenet data if something goes wrong in the data-export --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 15e185b5..b541d2c3 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -248,8 +248,9 @@ def get_nodes(self, nodespace_uids=[], include_links=True): def save(self, filename): # dict_engine saves everything to json, just dump the json export + data = json.dumps(self.export_json(), sort_keys=True, indent=4) with open(filename, 'w+') as fp: - fp.write(json.dumps(self.export_json(), sort_keys=True, indent=4)) + fp.write(data) if os.path.getsize(filename) < 100: # kind of hacky, but we don't really know what was going on raise RuntimeError("Error writing nodenet file") From 18af715531eef23a65e0af7dc5b4530451ed4df3 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 13:48:03 +0200 Subject: [PATCH 030/945] =?UTF-8?q?Initial=20simulated=20iiwa=20world=20ch?= =?UTF-8?q?eckin=20works=20on=20OSX,=20but=20doesn=E2=80=99t=20know=20what?= =?UTF-8?q?=20its=20doing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- micropsi_core/world/iiwasim/iiwasim.py | 74 ++++++++++++++++++++++++++ micropsi_core/world/world.py | 6 +++ 2 files changed, 80 insertions(+) create mode 100644 micropsi_core/world/iiwasim/iiwasim.py diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py new file mode 100644 index 00000000..107cc61d --- /dev/null +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -0,0 +1,74 @@ +import math +import os +import logging +from micropsi_core.world.iiwasim import vrep +from micropsi_core.world.iiwasim import vrepConst +from micropsi_core.world.world import World +from micropsi_core.world.worldadapter import ArrayWorldAdapter + + +class iiwasim(World): + """ A simulated KUKA iiwa, using the vrep robot simulator + + In V-REP, the following setup has to be performed: + - An LBR_iiwa_7_R800 has to have been added to the scene + - simExtRemoteApiStart(19999) has to have been run + - the simulation must have been started + + """ + + supported_worldadapters = ['iiwa'] + + def __init__(self, filename, world_type="iiwasim", name="", owner="", engine=None, uid=None, version=1, config={}): + World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version) + + vrep.simxFinish(-1) # just in case, close all opened connections + self.clientID = vrep.simxStart('127.0.0.1', 19999, True, True, 5000, 5) # Connect to V-REP + if self.clientID == -1: + raise Exception("Could not connect to v-rep.") + + self.logger.info("Connected to local V-REP at port 19999") + + res, pingtime = vrep.simxGetPingTime(self.clientID) + self.handle_res(res) + + self.logger.info('Ping time to v-rep: %dms' % pingtime) + + res, self.iiwa_handle = vrep.simxGetObjectHandle(self.clientID, "LBR_iiwa_7_R800", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.iiwa_handle == 0: + raise Exception("There seems to be no robot with the name LBR_iiwa_7_R800 in the v-rep simulation.") + + res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) + self.handle_res(res) + if len(self.joints) != 7: + raise Exception("Could not get handles for all 7 joints of the LBR_iiwa_7_R800.") + + def handle_res(self, res): + if res != vrep.simx_return_ok: + self.logger.warn("v-rep call returned error code %d" % res) + + +class iiwa(ArrayWorldAdapter): + + def __init__(self, world, uid=None, **data): + super().__init__(world, uid, **data) + + self.available_datatargets = [] + self.available_datasources = [] + + for i in range(len(self.world.joints)): + self.available_datatargets.append("joint_%d" % i) + + def get_available_datasources(self): + return self.available_datasources + + def get_available_datatargets(self): + return self.available_datatargets + + def update_data_sources_and_targets(self): + vrep.simxPauseCommunication(self.world.clientID, True) + for i, joint_handle in enumerate(self.world.joints): + tval = self.datatarget_values[i] + vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxPauseCommunication(self.world.clientID, False) \ No newline at end of file diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index d258c915..72c68453 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -363,3 +363,9 @@ def __del__(self): pass else: sys.stdout.write("Could not import timeseries world.\nError: %s \n\n" % e.msg) + + +try: + from micropsi_core.world.iiwasim import iiwasim +except ImportError as e: + sys.stdout.write("Could not import iiwasim world.\nError: %s \n\n" % e.msg) From 3b97cb44cc89ce24bf3cea4307ef7f2c9da03ef0 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 15:01:26 +0200 Subject: [PATCH 031/945] Scaling to correct v-rep fractions of pi --- micropsi_core/world/iiwasim/iiwasim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index 107cc61d..3e520944 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -69,6 +69,6 @@ def get_available_datatargets(self): def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): - tval = self.datatarget_values[i] + tval = self.datatarget_values[i] * math.pi vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) \ No newline at end of file From 5226e08870a717e53804c2d88c143a49b586b008 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 15:43:30 +0200 Subject: [PATCH 032/945] Joint actuators now provide feedback --- micropsi_core/world/iiwasim/iiwasim.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index 3e520944..1c1d6e0f 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -71,4 +71,12 @@ def update_data_sources_and_targets(self): for i, joint_handle in enumerate(self.world.joints): tval = self.datatarget_values[i] * math.pi vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.world.clientID, False) \ No newline at end of file + vrep.simxPauseCommunication(self.world.clientID, False) + + res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) + self.datatarget_feedback_values = [0] * len(self.available_datatargets) + for i, joint_handle in enumerate(self.world.joints): + tval = self.datatarget_values[i] + rval = data[i*2] / math.pi + if abs(rval) - abs(tval) < .0001: + self.datatarget_feedback_values[i] = 1 From fc7cf25780db6fa8e6a5aa876e2b403c46bc8ac4 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 17:23:20 +0200 Subject: [PATCH 033/945] vision data retrieval needs to be made into datasources --- micropsi_core/world/iiwasim/iiwasim.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index 1c1d6e0f..db2f2cd1 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -44,9 +44,21 @@ def __init__(self, filename, world_type="iiwasim", name="", owner="", engine=Non if len(self.joints) != 7: raise Exception("Could not get handles for all 7 joints of the LBR_iiwa_7_R800.") + res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.observer_handle == 0: + self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") + else: + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) + if res != 0 and res != 1: + self.handle_res(res) + else: + self.vision_resolution = resolution + def handle_res(self, res): if res != vrep.simx_return_ok: - self.logger.warn("v-rep call returned error code %d" % res) + error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) + self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) class iiwa(ArrayWorldAdapter): @@ -80,3 +92,10 @@ def update_data_sources_and_targets(self): rval = data[i*2] / math.pi if abs(rval) - abs(tval) < .0001: self.datatarget_feedback_values[i] = 1 + + # if no observer present, don't query vision data + if self.world.observer_handle == 0: + return + + res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) + print(len(image)) \ No newline at end of file From 9bc9bd5c12d1354f56ba884dcb29e274c2abb3e0 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 18:23:03 +0200 Subject: [PATCH 034/945] 1-based joint numbering as in v-rep --- micropsi_core/world/iiwasim/iiwasim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index db2f2cd1..9d5fddfa 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -70,7 +70,7 @@ def __init__(self, world, uid=None, **data): self.available_datasources = [] for i in range(len(self.world.joints)): - self.available_datatargets.append("joint_%d" % i) + self.available_datatargets.append("joint_%s" % str(i+1)) def get_available_datasources(self): return self.available_datasources From 84d5f49bd6bd597d33745531687eb76f46453261 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 19:13:53 +0200 Subject: [PATCH 035/945] Feeding luminance values into the net --- micropsi_core/world/iiwasim/iiwasim.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index 9d5fddfa..c6b8a1a2 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -1,6 +1,7 @@ import math -import os +import time import logging +import numpy as np from micropsi_core.world.iiwasim import vrep from micropsi_core.world.iiwasim import vrepConst from micropsi_core.world.world import World @@ -53,7 +54,13 @@ def __init__(self, filename, world_type="iiwasim", name="", owner="", engine=Non if res != 0 and res != 1: self.handle_res(res) else: + time.sleep(1) + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) self.vision_resolution = resolution + if len(resolution) != 2: + raise Exception("Could not determine vision resolution after 1 second wait time.") + else: + self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) def handle_res(self, res): if res != vrep.simx_return_ok: @@ -72,6 +79,10 @@ def __init__(self, world, uid=None, **data): for i in range(len(self.world.joints)): self.available_datatargets.append("joint_%s" % str(i+1)) + for y in range(self.world.vision_resolution[1]): + for x in range(self.world.vision_resolution[0]): + self.available_datasources.append("px_%d_%d" % (x, y)) + def get_available_datasources(self): return self.available_datasources @@ -98,4 +109,8 @@ def update_data_sources_and_targets(self): return res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) - print(len(image)) \ No newline at end of file + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0]*self.world.vision_resolution[1], 3)).astype(np.float32) + rgb_image /= 255. + y_image = [.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image] # todo: npyify and make faster + + self.datasource_values = y_image \ No newline at end of file From 17f1f01297d6cfce35b94080d80c98e30e3cc6a4 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 19:55:34 +0200 Subject: [PATCH 036/945] Fixing orientation and adding debug PNG code --- micropsi_core/world/iiwasim/iiwasim.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index c6b8a1a2..8ff9ce8a 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -111,6 +111,15 @@ def update_data_sources_and_targets(self): res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0]*self.world.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. - y_image = [.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image] # todo: npyify and make faster + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image])[::-1] # todo: npyify and make faster - self.datasource_values = y_image \ No newline at end of file + self.datasource_values = y_image + + # images for debug purposes, should later be used in the world's GUI + # maybe use matplotlib instead of PIL? + + #from PIL import Image + #img = Image.new('L', self.world.vision_resolution) + #y_image *= 255 + #img.putdata(y_image.astype(np.uint8)) + #img.save('/tmp/test.png', 'PNG') #, transparency=0) \ No newline at end of file From 9ed19dd72c4a6259d257a471e2da994659db62b9 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 20:09:12 +0200 Subject: [PATCH 037/945] Type issue fix --- micropsi_core/world/iiwasim/iiwasim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index 8ff9ce8a..e89c7473 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -111,7 +111,7 @@ def update_data_sources_and_targets(self): res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0]*self.world.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image])[::-1] # todo: npyify and make faster + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32)[::-1] # todo: npyify and make faster self.datasource_values = y_image From 56280d019476e60399a9727f703c697701e517f2 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sat, 30 Apr 2016 20:58:39 +0200 Subject: [PATCH 038/945] Reporting raw force/torque values --- micropsi_core/world/iiwasim/iiwasim.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index e89c7473..9a9a7297 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -78,6 +78,10 @@ def __init__(self, world, uid=None, **data): for i in range(len(self.world.joints)): self.available_datatargets.append("joint_%s" % str(i+1)) + self.available_datasources.append("joint_force_%s" % str(i+1)) + + self.image_offset = len(self.world.joints) + self.image_length = self.world.vision_resolution[0] * self.world.vision_resolution[1] for y in range(self.world.vision_resolution[1]): for x in range(self.world.vision_resolution[0]): @@ -98,11 +102,14 @@ def update_data_sources_and_targets(self): res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) self.datatarget_feedback_values = [0] * len(self.available_datatargets) + self.datasource_values = [0] * (self.image_offset + self.image_length) for i, joint_handle in enumerate(self.world.joints): tval = self.datatarget_values[i] rval = data[i*2] / math.pi if abs(rval) - abs(tval) < .0001: self.datatarget_feedback_values[i] = 1 + force = data[i*2 + 1] + self.datasource_values[i] = force # if no observer present, don't query vision data if self.world.observer_handle == 0: @@ -113,7 +120,7 @@ def update_data_sources_and_targets(self): rgb_image /= 255. y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32)[::-1] # todo: npyify and make faster - self.datasource_values = y_image + self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image # images for debug purposes, should later be used in the world's GUI # maybe use matplotlib instead of PIL? From bedd07824b6fefd82719d702446231ad7e5e9312 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sun, 1 May 2016 16:23:57 +0200 Subject: [PATCH 039/945] Reset actuator, state sensors, primitive reward sensor --- micropsi_core/world/iiwasim/iiwasim.py | 71 +++++++++++++++++++++----- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/iiwasim/iiwasim.py index 9a9a7297..800a2580 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/iiwasim/iiwasim.py @@ -37,7 +37,7 @@ def __init__(self, filename, world_type="iiwasim", name="", owner="", engine=Non res, self.iiwa_handle = vrep.simxGetObjectHandle(self.clientID, "LBR_iiwa_7_R800", vrep.simx_opmode_blocking) self.handle_res(res) - if self.iiwa_handle == 0: + if self.iiwa_handle < 1: raise Exception("There seems to be no robot with the name LBR_iiwa_7_R800 in the v-rep simulation.") res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) @@ -45,9 +45,21 @@ def __init__(self, filename, world_type="iiwasim", name="", owner="", engine=Non if len(self.joints) != 7: raise Exception("Could not get handles for all 7 joints of the LBR_iiwa_7_R800.") + res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.ball_handle < 1: + self.logger.warn("Could not get handle for Ball object, reward values will not be available.") + else: + res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[6], -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) self.handle_res(res) - if self.observer_handle == 0: + if self.observer_handle < 1: self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") else: res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) @@ -76,11 +88,24 @@ def __init__(self, world, uid=None, **data): self.available_datatargets = [] self.available_datasources = [] + self.available_datasources.append("reward") + self.available_datatargets.append("reset") + + for i in range(len(self.world.joints)): + self.available_datatargets.append("joint_%s" % str(i + 1)) + for i in range(len(self.world.joints)): - self.available_datatargets.append("joint_%s" % str(i+1)) - self.available_datasources.append("joint_force_%s" % str(i+1)) + self.available_datasources.append("joint_angle_%s" % str(i + 1)) - self.image_offset = len(self.world.joints) + for i in range(len(self.world.joints)): + self.available_datasources.append("joint_force_%s" % str(i + 1)) + + self.reset_offset = 0 + self.joint_offset = 1 + self.reward_offset = 0 + self.joint_angle_offset = 1 + self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) + self.image_offset = self.joint_force_offset + len(self.world.joints) self.image_length = self.world.vision_resolution[0] * self.world.vision_resolution[1] for y in range(self.world.vision_resolution[1]): @@ -94,23 +119,43 @@ def get_available_datatargets(self): return self.available_datatargets def update_data_sources_and_targets(self): + + if self.datatarget_values[self.reset_offset] > 0.9: + vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + time.sleep(1) + vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + return + + # send joint target values vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): - tval = self.datatarget_values[i] * math.pi + tval = self.datatarget_values[self.joint_offset + i] * math.pi vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) - res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) + # get data and feedback self.datatarget_feedback_values = [0] * len(self.available_datatargets) - self.datasource_values = [0] * (self.image_offset + self.image_length) + self.datasource_values = [0] * len(self.available_datasources) + + # read reward value + if self.world.ball_handle > 0: + res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) + res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[6], -1, vrep.simx_opmode_streaming) + dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) + self.datasource_values[self.reset_offset] = -dist + + # read joint angle and force values + res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) for i, joint_handle in enumerate(self.world.joints): - tval = self.datatarget_values[i] - rval = data[i*2] / math.pi - if abs(rval) - abs(tval) < .0001: - self.datatarget_feedback_values[i] = 1 + target_angle = self.datatarget_values[self.joint_offset + i] + angle = data[i*2] / math.pi force = data[i*2 + 1] - self.datasource_values[i] = force + if abs(angle) - abs(target_angle) < .0001: + self.datatarget_feedback_values[self.joint_offset + i] = 1 + self.datasource_values[self.joint_angle_offset + i] = angle + self.datasource_values[self.joint_force_offset + i] = force + # read vision data # if no observer present, don't query vision data if self.world.observer_handle == 0: return From f792fde4f8e0354fceb2192907c68c83a2552a74 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 14:29:55 +0200 Subject: [PATCH 040/945] fix recorder persistency --- micropsi_core/nodenet/recorder.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index db3526ed..487fcd75 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -75,13 +75,17 @@ def get_values(self): def save(self, filename=None): values = self.values + if values == {}: + values['uid'] = self.uid # empty files cannot be loaded np.savez(filename if filename is not None else self.filename, **values) + def load(self, filename=None): data = np.load(filename if filename is not None else self.filename) for key in data: - self.values[key] = data[key] - self.shapes[key] = data[key].shape + if key != 'uid': + self.values[key] = data[key] + self.shapes[key] = data[key].shape def clear(self): self.values = {} From 0f112bf7bea97b24a0a06bc9ea52f43fff4df389 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 14:30:01 +0200 Subject: [PATCH 041/945] recoder npz export --- micropsi_core/_runtime_api_monitors.py | 4 ++++ micropsi_server/micropsi_app.py | 7 +++++++ micropsi_server/static/js/monitor.js | 3 +-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index 35773165..267cd081 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -139,3 +139,7 @@ def clear_recorder(nodenet_uid, recorder_uid): def get_recorder_data(nodenet_uid): return True, micropsi_core.runtime.get_nodenet(nodenet_uid).construct_recorders_dict() + + +def get_recorder(nodenet_uid, recorder_uid): + return micropsi_core.runtime.get_nodenet(nodenet_uid).get_recorder(recorder_uid) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 67419b16..9098d363 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -563,6 +563,13 @@ def export_nodenet(nodenet_uid): # return json.dumps(data['values'], sort_keys=True, indent=2) +@micropsi_app.route("/recorder/export/-") +def export_monitor(nodenet_uid, recorder_uid): + recorder = runtime.get_recorder(nodenet_uid, recorder_uid) + recorder.save() + return static_file(os.path.basename(recorder.filename), root=os.path.dirname(recorder.filename), download='recorder_%s.npz' % recorder.name) + + @micropsi_app.route("/nodenet/edit") def edit_nodenet(): user_id, permissions, token = get_request_data() diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 14fca430..e79f4a9a 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -174,8 +174,7 @@ $(function(){ var method_name = null; switch(btn.attr('data-action')){ case 'export': - // return self.location.href.replace('/recorder/export/'+currentNodenet+'/'+) - method_name = 'export_recorder'; break; + return window.location.replace('/recorder/export/'+currentNodenet+'-'+uid); case 'clear': method_name = 'clear_recorder'; break; case 'delete': From de7060be7317662d8030e85b208a8677a54f31c7 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 14:31:25 +0200 Subject: [PATCH 042/945] revert monitor json export. use recorders, if you want to export values --- micropsi_server/micropsi_app.py | 8 -------- micropsi_server/static/js/monitor.js | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 9098d363..172dd2d7 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -555,14 +555,6 @@ def export_nodenet(nodenet_uid): return runtime.export_nodenet(nodenet_uid) -# @micropsi_app.route("/monitor/export/-") -# def export_monitor(nodenet_uid, monitor_uid): -# data = runtime.export_monitor_data(nodenet_uid, monitor_uid) -# response.set_header('Content-type', 'application/json') -# response.set_header('Content-Disposition', 'attachment; filename="monitor_%s.json"' % data['name']) -# return json.dumps(data['values'], sort_keys=True, indent=2) - - @micropsi_app.route("/recorder/export/-") def export_monitor(nodenet_uid, recorder_uid): recorder = runtime.get_recorder(nodenet_uid, recorder_uid) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index e79f4a9a..eafffec3 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -318,8 +318,7 @@ $(function(){ html += ' checked="checked"'; } html += ' /> '; - html += ' '; - html += ' '; + html += ' '; monitor_list_items.push(mon.uid); } list.html(html); From 56e7edc8c6bdee90ad91c9764da95644e296b02b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 15:13:32 +0200 Subject: [PATCH 043/945] fix --- micropsi_core/nodenet/recorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 487fcd75..97b88a31 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -152,7 +152,7 @@ def __init__(self, nodenet, from_group_config={}, to_group_config={}, name="", u if not to_group_config.get('node_uids', []): self._nodenet.group_nodes_by_names(**to_group_config) else: - self.nodenet.group_nodes_by_ids(**to_group_config) + self._nodenet.group_nodes_by_ids(**to_group_config) weights = self._nodenet.get_link_weights(self.from_nodespace, self.from_name, self.to_nodespace, self.to_name) from_uids = self._nodenet.get_node_uids(self.from_nodespace, self.from_name) From 64b0085928f0b171aed35aeb4a7034a6700c79dd Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 15:13:42 +0200 Subject: [PATCH 044/945] fix run operation --- micropsi_server/static/js/nodenet.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index d3aa91fe..26a4b6bc 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -3020,7 +3020,8 @@ function selectOperation(name){ '
    '; } $('fieldset', modal).html(html); - var run = function(){ + var run = function(event){ + event.preventDefault(); data = $('form', modal).serializeArray(); parameters = {}; for(var i=0; i < data.length; i++){ @@ -3048,6 +3049,7 @@ function runOperation(name, params){ 'parameters': params || {}, 'selection_uids': selection_uids}, function(data){ refreshNodespace(); + $(document).trigger('runner_stepped') if(!$.isEmptyObject(data)){ html = ''; if(data.content_type && data.content_type.indexOf("image") > -1){ From 8993861323d5485460a2bf661bf7d3b33b2e62d6 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 15:13:54 +0200 Subject: [PATCH 045/945] operations to add recorders --- micropsi_core/nodenet/operations/recoders.py | 43 ++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 micropsi_core/nodenet/operations/recoders.py diff --git a/micropsi_core/nodenet/operations/recoders.py b/micropsi_core/nodenet/operations/recoders.py new file mode 100644 index 00000000..0b8befb5 --- /dev/null +++ b/micropsi_core/nodenet/operations/recoders.py @@ -0,0 +1,43 @@ + +from micropsi_core.nodenet.operations import selectioninfo + + +@selectioninfo(mincount=2) +def add_activation_recorder(netapi, selection, gate='gen', interval=1, name='activation_recoder'): + """Adds an activation recorder to the selected nodes""" + firstnode = netapi.get_node(selection[0]) + nodespace = netapi.get_nodespace(firstnode.parent_nodespace) + group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': selection, + 'gatetype': gate} + netapi.add_activation_recorder(group_config, name=name, interval=int(interval)) + + +@selectioninfo(mincount=2) +def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen', to_slot='gen', interval=1, name='linkweight_recoder'): + """ Attempts to detect two layers of nodes (y-coordinate) and adds a linkweight-monitor""" + nodes = [netapi.get_node(uid) for uid in selection] + nodespace = netapi.get_nodespace(nodes[0].parent_nodespace) + groups = {} + for n in nodes: + if n.position[1] in groups: + groups[n.position[1]].append(n) + else: + groups[n.position[1]] = [n] + if len(groups.keys()) != 2: + raise RuntimeError("Could not determine 2 node-layers") + + grouplist = list(groups.values()) + if direction == 'up': + grouplist.reverse() + + from_group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': [n.uid for n in grouplist[0]], + 'gatetype': from_gate} + to_group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': [n.uid for n in grouplist[1]], + 'gatetype': to_slot} + netapi.add_linkweight_recorder(from_group_config, to_group_config, name=name, interval=int(interval)) From aa00ab645a8f2e19d97e7eb73429257159f2372a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 15:23:01 +0200 Subject: [PATCH 046/945] layout stuff for monitoring pane move add_custom_monitor button into monitor-section remove useless "clear" button streamline borders and button styles --- micropsi_server/static/js/monitor.js | 7 +- micropsi_server/view/monitors.tpl | 99 ++++++++++++++-------------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index eafffec3..4039d583 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -147,9 +147,9 @@ $(function(){ var rec = data[uid]; html += ''+rec.name+''; html += ' '; - html += ' '; - html += ' '; - html += ' '; + html += ' '; + html += ' '; + html += ' '; html += '' html += ' Type:'+rec.classname+''; html += ' Entries:'+(rec.current_index + 1)+''; @@ -581,5 +581,4 @@ $(function(){ .attr("d", line); } } - }); diff --git a/micropsi_server/view/monitors.tpl b/micropsi_server/view/monitors.tpl index ae7066be..1897000a 100644 --- a/micropsi_server/view/monitors.tpl +++ b/micropsi_server/view/monitors.tpl @@ -25,8 +25,6 @@
    - -
    @@ -53,6 +51,9 @@ +

    + +

    @@ -62,53 +63,55 @@

    Logs

    -
    -
      -
    • - - -
    • -
    • - - -
    • -
    • - - -
    • -
    -
    -
    -

    -

    -

    -
    +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +

    +

    +

    +
    +

     

    +
    -

     

    Recorders

    From 2cf8c423e7830722114979f11f124466da36b96a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 17:49:52 +0200 Subject: [PATCH 047/945] add_recorder button for frontend --- micropsi_server/static/js/monitor.js | 73 ++++++++++++++++ micropsi_server/view/monitors.tpl | 123 ++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 1 deletion(-) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 4039d583..5b4055c1 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -44,6 +44,79 @@ $(function(){ var showStepInLog = true; var logs_to_add = []; + var rec_modal = $('#recorder_modal'); + var rec_type_dd = $('#recorder_type_input'); + rec_type_dd.on('change', function(event){ + var type = rec_type_dd.val(); + $('fieldset.recorder_specific').hide(); + $('fieldset.'+type).show(); + }) + $('.add_recorder').on('click', function(event){ + event.preventDefault(); + api.call('get_nodespace_list', {nodenet_uid: currentNodenet}, function(data){ + var html = ''; + for(uid in data){ + html += ''; + } + $('.recorder_nodespace_dropdown').html(html); + rec_type_dd.trigger('change'); + rec_modal.modal('show'); + }); + }) + $('.btn-primary', rec_modal).on('click', function(event){ + var params = { + nodenet_uid: currentNodenet, + interval: parseInt($('#recorder_interval').val()), + name: $('#recorder_name').val(), + }; + var type = $('#recorder_type_input').val(); + var method = null; + if(type == 'activation_recorder'){ + method = 'add_activation_recorder'; + params['group_definition'] = { + 'nodespace_uid': $('#recorder_nodespace_uid').val(), + 'gatetype': $('#recorder_gate').val(), + } + var ids = $('#recorder_node_uids').val(); + if(ids){ + ids = ids.split(',') + for(var i in ids){ + ids[i] = ids[i].trim(); + } + params.group_definition['node_uids'] = ids; + } else{ + params.group_definition['node_name_prefix'] = $('#recorder_node_name_prefix').val(); + } + } else if(type == 'linkweight_recorder') { + method = "add_linkweight_recorder"; + params['from_group_definition'] = { + 'nodespace_uid': $('#recorder_from_nodespace_uid').val(), + 'gatetype': $('#recorder_from_gate').val(), + } + var ids = $('#recorder_from_node_uids'); + if(ids.val()){ + params.from_group_definition['node_uids'] = ids.split(',') + } else{ + params.from_group_definition['node_name_prefix'] = $('#recorder_from_node_name_prefix').val(); + } + params['to_group_definition'] = { + 'nodespace_uid': $('#recorder_to_nodespace_uid').val(), + 'gatetype': $('#recorder_to_gate').val(), + } + var ids = $('#recorder_to_node_uids'); + if(ids.val()){ + params.to_group_definition['node_uids'] = ids.split(',') + } else{ + params.to_group_definition['node_name_prefix'] = $('#recorder_to_node_name_prefix').val(); + } + } + api.call(method, params, function(){ + rec_modal.modal('hide'); + api.defaultSuccessCallback(); + refreshRecorders(); + }); + }) + init(); if(!$('#nodenet_editor').length && currentNodenet){ diff --git a/micropsi_server/view/monitors.tpl b/micropsi_server/view/monitors.tpl index 1897000a..396969a1 100644 --- a/micropsi_server/view/monitors.tpl +++ b/micropsi_server/view/monitors.tpl @@ -119,8 +119,129 @@
    +
    +

    + +

    +

     

    - \ No newline at end of file + + + From 6d5cd5ca996ce8d0503f5a17fa0105e194505b29 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 2 May 2016 18:43:29 +0200 Subject: [PATCH 048/945] hide recorder stuff if we don't have theano --- micropsi_core/nodenet/operations/recoders.py | 84 +++++++++++--------- micropsi_server/micropsi_app.py | 16 ++-- micropsi_server/static/js/monitor.js | 11 ++- micropsi_server/view/boilerplate.tpl | 8 ++ micropsi_server/view/monitors.tpl | 30 +++---- 5 files changed, 87 insertions(+), 62 deletions(-) diff --git a/micropsi_core/nodenet/operations/recoders.py b/micropsi_core/nodenet/operations/recoders.py index 0b8befb5..1c5c97a0 100644 --- a/micropsi_core/nodenet/operations/recoders.py +++ b/micropsi_core/nodenet/operations/recoders.py @@ -2,42 +2,48 @@ from micropsi_core.nodenet.operations import selectioninfo -@selectioninfo(mincount=2) -def add_activation_recorder(netapi, selection, gate='gen', interval=1, name='activation_recoder'): - """Adds an activation recorder to the selected nodes""" - firstnode = netapi.get_node(selection[0]) - nodespace = netapi.get_nodespace(firstnode.parent_nodespace) - group_config = { - 'nodespace_uid': nodespace.uid, - 'node_uids': selection, - 'gatetype': gate} - netapi.add_activation_recorder(group_config, name=name, interval=int(interval)) - - -@selectioninfo(mincount=2) -def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen', to_slot='gen', interval=1, name='linkweight_recoder'): - """ Attempts to detect two layers of nodes (y-coordinate) and adds a linkweight-monitor""" - nodes = [netapi.get_node(uid) for uid in selection] - nodespace = netapi.get_nodespace(nodes[0].parent_nodespace) - groups = {} - for n in nodes: - if n.position[1] in groups: - groups[n.position[1]].append(n) - else: - groups[n.position[1]] = [n] - if len(groups.keys()) != 2: - raise RuntimeError("Could not determine 2 node-layers") - - grouplist = list(groups.values()) - if direction == 'up': - grouplist.reverse() - - from_group_config = { - 'nodespace_uid': nodespace.uid, - 'node_uids': [n.uid for n in grouplist[0]], - 'gatetype': from_gate} - to_group_config = { - 'nodespace_uid': nodespace.uid, - 'node_uids': [n.uid for n in grouplist[1]], - 'gatetype': to_slot} - netapi.add_linkweight_recorder(from_group_config, to_group_config, name=name, interval=int(interval)) +try: + import numpy as np + + @selectioninfo(mincount=2) + def add_activation_recorder(netapi, selection, gate='gen', interval=1, name='activation_recoder'): + """Adds an activation recorder to the selected nodes""" + firstnode = netapi.get_node(selection[0]) + nodespace = netapi.get_nodespace(firstnode.parent_nodespace) + group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': selection, + 'gatetype': gate} + netapi.add_activation_recorder(group_config, name=name, interval=int(interval)) + + @selectioninfo(mincount=2) + def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen', to_slot='gen', interval=1, name='linkweight_recoder'): + """ Attempts to detect two layers of nodes (y-coordinate) and adds a linkweight-monitor""" + nodes = [netapi.get_node(uid) for uid in selection] + nodespace = netapi.get_nodespace(nodes[0].parent_nodespace) + groups = {} + for n in nodes: + if n.position[1] in groups: + groups[n.position[1]].append(n) + else: + groups[n.position[1]] = [n] + if len(groups.keys()) != 2: + raise RuntimeError("Could not determine 2 node-layers") + + grouplist = list(groups.values()) + if direction == 'up': + grouplist.reverse() + + from_group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': [n.uid for n in grouplist[0]], + 'gatetype': from_gate} + to_group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': [n.uid for n in grouplist[1]], + 'gatetype': to_slot} + netapi.add_linkweight_recorder(from_group_config, to_group_config, name=name, interval=int(interval)) + + +except ImportError: + pass diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 172dd2d7..782f0a66 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -44,6 +44,14 @@ bottle.TEMPLATE_PATH.insert(0, os.path.join(APP_PATH, 'view', '')) bottle.TEMPLATE_PATH.insert(1, os.path.join(APP_PATH, 'static', '')) +theano_available = True +try: + import theano +except ImportError: + theano_available = False + +bottle.BaseTemplate.defaults['theano_available'] = theano_available + # runtime = micropsi_core.runtime.MicroPsiRuntime() usermanager = usermanagement.UserManager() @@ -568,18 +576,12 @@ def edit_nodenet(): # nodenet_id = request.params.get('id', None) title = 'Edit Nodenet' if id is not None else 'New Nodenet' - theano_available = True - try: - import theano - except ImportError: - theano_available = False - return template("nodenet_form.tpl", title=title, # nodenet_uid=nodenet_uid, nodenets=runtime.get_available_nodenets(), templates=runtime.get_available_nodenets(), worlds=runtime.get_available_worlds(), - version=VERSION, user_id=user_id, permissions=permissions, theano_available=theano_available) + version=VERSION, user_id=user_id, permissions=permissions) @micropsi_app.route("/nodenet/edit", method="POST") diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 5b4055c1..0d8744ce 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -123,21 +123,28 @@ $(function(){ refreshMonitors(); } + var splitviewclass = 'span6'; + if(theano_available){ + splitviewclass = 'span4' + } + + var count_sections = $('.layout_field').length; $('.layoutbtn').on('click', function(event){ event.preventDefault(); var target = $(event.target); if(!target.hasClass('active')){ var layout = target.attr('data'); if(layout == 'vertical'){ - $('.layout_field').addClass('span4'); + $('.layout_field').addClass(splitviewclass); } else if(layout == 'horizontal'){ - $('.layout_field').removeClass('span4'); + $('.layout_field').removeClass(splitviewclass); } refreshMonitors(); $('.layoutbtn').removeClass('active'); target.addClass('active'); } }) + $('.layoutbtn[data="vertical"]').trigger('click'); $('#monitor_x_axis').on('change', function(){ viewProperties.xvalues = parseInt($('#monitor_x_axis').val()); diff --git a/micropsi_server/view/boilerplate.tpl b/micropsi_server/view/boilerplate.tpl index 27e1915b..197dec73 100644 --- a/micropsi_server/view/boilerplate.tpl +++ b/micropsi_server/view/boilerplate.tpl @@ -26,6 +26,14 @@ + + LTtn9h4q(es&hexvC7m~LuO^g5=K z*D87w(|O-2`mpZ5OVP)fZhS$}J+ZM)wr{%?J&ftR&lPBjdI zzD}pPK0eKKU9<9klj#)BzYlbp>rV^Q$zLmc*Eq$$;Pee-I+^RoADB+$^iF0v_@sJ& zF4HM2&s&+U=kos#rUloRmzj3%QSa~7>30=N0=5vl>b_$om_t2V!Dy(_n6M%@^)0mGkuC_@sbL! zcYnoyfcv9!K%;rGD_3Jbl^Lf>woms)7Qh5o07e$qle zXQ5xQ&^s*jAq)Mzg^n5!F0TO=`T`4msfA9p&{tUKYzsZZLf>Ga3oUesh4xrzQnN@E zCH0L|Vp1bXbtCnWR5nukXlp7>_u$(!g630b-#)&aBzohI+I}bg`rxlG{?5Q(9RBD$ zxc>MXfIs?7^g#R#!XKSCn}9!DDXc z-S&UBVza!T8;V8+m|NV|MH!A+-z?6Z69F3G^+-cD|9zr$pEP&Va)xvagofsE9Ck`TTjb+h5{ItO1wf6(N(wlBu~@ae;ApA zq=kItTzM{xq6@H&6>bEan=4Iy$$}yD$ef#9=1B856D0hNHdNan0D%#>IhsnfZYS>+ zyfk6u*o+K>(F;4NH^Zq=2rxTJo0R{DT%m7m79y@%^O4O8Hx_AY2@b4Q_EJSa%pj4L2~fLrb&&~nkr_sI=xO1Vft;+1VY#^| z|AytQwB!tTQfuPjZke*oRw&c!(->jWo6?$Bt&oQsbdsmE|Fu}~8t!_7LWaJortk|E zkBfHp(^zQD&7BXk@=jXnD{v%ZfA~b)9Dp7~ zZZ?tIc|w*KhI6=qhVEPtZZcswt6EF~GFw;}-qH}mY*7*Jqgz$zuF57R5?=XO@hIV8 zRyS-MqX%d8=JtXGVD*(PM>~GX7N@;%*;GgOMqe~d_L6L1V{#{B2*b$6;w}wu3^$YI zj;cP)nd*yFz11radl|+?dLSicI}*bY^)qBIVs;H#mguf>qmx0PF_zXiA%~D1PRPw{ zsiV76Q^XB}Fx=R7$e_(8M7m>@+-}sCUFB|K*w{@K(Vi{W@J?%M`IRp^^qy_@mgQL) zk%e9gx0MUIU(wc9F65~U>O%C?2-bNaUs&<*R}Oh z=6RGAw;zQle^u}R<^P3RUU2ad6m1xKvCK8M)Z=lixGX4(QiaAX$LHQ+H69T zu5C+oZ8tC?k1DmHUU;E z-td8k!W*;$!Q!Y}!iPJDZ0f_^Lv0L;O7Vjsn@)%C_|ct>JyW`a zs(@`1(Y9hDGiPL4_Gsv#%*ZrvG{Zfbo7~)eNr+)I7e={ ztbHs+s=KvqGT;-raR_;s8wa>r8wcGfydltCt=%tkBLh)>;k5$qMQ9n|sst3N6)-&8 zR&2woO~p2R%oRK27*??jXG_I~`?N?K%JlqcTOIL+ULDnLAswo_8rz9N9_HGI_sxaO zRJ)WZwR(%Js;61%#57OX_HYF>?IpK5Oy|vw(miYsyL4&0PaVfes6!#b&XthIL4>)e zlP36V2z|Ct=;uN3Nf3Mn1pT<-Cr^FIogm54g*T1^lW}Nu@e> V0Vi%$(18p3NegXG;lN_R{{t0>C@25` literal 0 HcmV?d00001 diff --git a/micropsi_core/world/vrep_world/vrep.py b/micropsi_core/world/vrep_world/vrep.py new file mode 100755 index 00000000..e8d307da --- /dev/null +++ b/micropsi_core/world/vrep_world/vrep.py @@ -0,0 +1,1482 @@ +# This file is part of the REMOTE API +# +# Copyright 2006-2016 Coppelia Robotics GmbH. All rights reserved. +# marc@coppeliarobotics.com +# www.coppeliarobotics.com +# +# The REMOTE API is licensed under the terms of GNU GPL: +# +# ------------------------------------------------------------------- +# The REMOTE API is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# THE REMOTE API IS DISTRIBUTED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTY. THE USER WILL USE IT AT HIS/HER OWN RISK. THE ORIGINAL +# AUTHORS AND COPPELIA ROBOTICS GMBH WILL NOT BE LIABLE FOR DATA LOSS, +# DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING OR +# MISUSING THIS SOFTWARE. +# +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the REMOTE API. If not, see . +# ------------------------------------------------------------------- +# +# This file was automatically created for V-REP release V3.3.0 on February 19th 2016 + +import platform +import struct +import sys +import ctypes as ct +import os +from .vrepConst import * + +#load library +libsimx = None +try: + if platform.system() =='cli': + libsimx = ct.CDLL("remoteApi.dll") + elif platform.system() =='Windows': + libsimx = ct.CDLL("remoteApi.dll") + elif platform.system() == 'Darwin': + libsimx = ct.CDLL(os.path.abspath(os.path.join(__file__, '../remoteApi.dylib'), )) + else: + libsimx = ct.CDLL("remoteApi.so") +except: + print ('----------------------------------------------------') + print ('The remoteApi library could not be loaded. Make sure') + print ('it is located in the same folder as "vrep.py", or') + print ('appropriately adjust the file "vrep.py"') + print ('----------------------------------------------------') + print ('') + +#ctypes wrapper prototypes +c_GetJointPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetJointPosition", libsimx)) +c_SetJointPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointPosition", libsimx)) +c_GetJointMatrix = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetJointMatrix", libsimx)) +c_SetSphericalJointMatrix = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetSphericalJointMatrix", libsimx)) +c_SetJointTargetVelocity = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointTargetVelocity", libsimx)) +c_SetJointTargetPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointTargetPosition", libsimx)) +c_GetJointForce = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetJointForce", libsimx)) +c_SetJointForce = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointForce", libsimx)) +c_ReadForceSensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.c_int32)(("simxReadForceSensor", libsimx)) +c_BreakForceSensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxBreakForceSensor", libsimx)) +c_ReadVisionSensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.POINTER(ct.c_float)), ct.POINTER(ct.POINTER(ct.c_int32)), ct.c_int32)(("simxReadVisionSensor", libsimx)) +c_GetObjectHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectHandle", libsimx)) +c_GetVisionSensorImage = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_byte)), ct.c_ubyte, ct.c_int32)(("simxGetVisionSensorImage", libsimx)) +c_SetVisionSensorImage = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_byte), ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxSetVisionSensorImage", libsimx)) +c_GetVisionSensorDepthBuffer= ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_float)), ct.c_int32)(("simxGetVisionSensorDepthBuffer", libsimx)) +c_GetObjectChild = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectChild", libsimx)) +c_GetObjectParent = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectParent", libsimx)) +c_ReadProximitySensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.c_float), ct.POINTER(ct.c_int32), ct.POINTER(ct.c_float), ct.c_int32)(("simxReadProximitySensor", libsimx)) +c_LoadModel = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_ubyte, ct.POINTER(ct.c_int32), ct.c_int32)(("simxLoadModel", libsimx)) +c_LoadUI = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_ubyte, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.c_int32)(("simxLoadUI", libsimx)) +c_LoadScene = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_ubyte, ct.c_int32)(("simxLoadScene", libsimx)) +c_StartSimulation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxStartSimulation", libsimx)) +c_PauseSimulation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxPauseSimulation", libsimx)) +c_StopSimulation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxStopSimulation", libsimx)) +c_GetUIHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUIHandle", libsimx)) +c_GetUISlider = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUISlider", libsimx)) +c_SetUISlider = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetUISlider", libsimx)) +c_GetUIEventButton = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUIEventButton", libsimx)) +c_GetUIButtonProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUIButtonProperty", libsimx)) +c_SetUIButtonProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetUIButtonProperty", libsimx)) +c_AddStatusbarMessage = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxAddStatusbarMessage", libsimx)) +c_AuxiliaryConsoleOpen = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.c_int32), ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.POINTER(ct.c_int32), ct.c_int32)(("simxAuxiliaryConsoleOpen", libsimx)) +c_AuxiliaryConsoleClose = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxAuxiliaryConsoleClose", libsimx)) +c_AuxiliaryConsolePrint = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxAuxiliaryConsolePrint", libsimx)) +c_AuxiliaryConsoleShow = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxAuxiliaryConsoleShow", libsimx)) +c_GetObjectOrientation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectOrientation", libsimx)) +c_GetObjectPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectPosition", libsimx)) +c_SetObjectOrientation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetObjectOrientation", libsimx)) +c_SetObjectPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetObjectPosition", libsimx)) +c_SetObjectParent = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxSetObjectParent", libsimx)) +c_SetUIButtonLabel = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char), ct.c_int32)(("simxSetUIButtonLabel", libsimx)) +c_GetLastErrors = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetLastErrors", libsimx)) +c_GetArrayParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetArrayParameter", libsimx)) +c_SetArrayParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetArrayParameter", libsimx)) +c_GetBooleanParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.c_int32)(("simxGetBooleanParameter", libsimx)) +c_SetBooleanParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxSetBooleanParameter", libsimx)) +c_GetIntegerParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetIntegerParameter", libsimx)) +c_SetIntegerParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetIntegerParameter", libsimx)) +c_GetFloatingParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetFloatingParameter", libsimx)) +c_SetFloatingParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetFloatingParameter", libsimx)) +c_GetStringParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetStringParameter", libsimx)) +c_GetCollisionHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetCollisionHandle", libsimx)) +c_GetDistanceHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetDistanceHandle", libsimx)) +c_GetCollectionHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetCollectionHandle", libsimx)) +c_ReadCollision = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.c_int32)(("simxReadCollision", libsimx)) +c_ReadDistance = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxReadDistance", libsimx)) +c_RemoveObject = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxRemoveObject", libsimx)) +c_RemoveModel = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxRemoveModel", libsimx)) +c_RemoveUI = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxRemoveUI", libsimx)) +c_CloseScene = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxCloseScene", libsimx)) +c_GetObjects = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.c_int32)(("simxGetObjects", libsimx)) +c_DisplayDialog = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char), ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.POINTER(ct.c_int32), ct.POINTER(ct.c_int32), ct.c_int32)(("simxDisplayDialog", libsimx)) +c_EndDialog = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxEndDialog", libsimx)) +c_GetDialogInput = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetDialogInput", libsimx)) +c_GetDialogResult = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetDialogResult", libsimx)) +c_CopyPasteObjects = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32, ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxCopyPasteObjects", libsimx)) +c_GetObjectSelection = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectSelection", libsimx)) +c_SetObjectSelection = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32, ct.c_int32)(("simxSetObjectSelection", libsimx)) +c_ClearFloatSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxClearFloatSignal", libsimx)) +c_ClearIntegerSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxClearIntegerSignal", libsimx)) +c_ClearStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxClearStringSignal", libsimx)) +c_GetFloatSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_float), ct.c_int32)(("simxGetFloatSignal", libsimx)) +c_GetIntegerSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetIntegerSignal", libsimx)) +c_GetStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetStringSignal", libsimx)) +c_SetFloatSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_float, ct.c_int32)(("simxSetFloatSignal", libsimx)) +c_SetIntegerSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32, ct.c_int32)(("simxSetIntegerSignal", libsimx)) +c_SetStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.c_int32)(("simxSetStringSignal", libsimx)) +c_AppendStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.c_int32)(("simxAppendStringSignal", libsimx)) +c_WriteStringStream = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.c_int32)(("simxWriteStringStream", libsimx)) +c_GetObjectFloatParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectFloatParameter", libsimx)) +c_SetObjectFloatParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetObjectFloatParameter", libsimx)) +c_GetObjectIntParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectIntParameter", libsimx)) +c_SetObjectIntParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetObjectIntParameter", libsimx)) +c_GetModelProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetModelProperty", libsimx)) +c_SetModelProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetModelProperty", libsimx)) +c_Start = ct.CFUNCTYPE(ct.c_int32,ct.POINTER(ct.c_char), ct.c_int32, ct.c_ubyte, ct.c_ubyte, ct.c_int32, ct.c_int32)(("simxStart", libsimx)) +c_Finish = ct.CFUNCTYPE(None, ct.c_int32)(("simxFinish", libsimx)) +c_GetPingTime = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32))(("simxGetPingTime", libsimx)) +c_GetLastCmdTime = ct.CFUNCTYPE(ct.c_int32,ct.c_int32)(("simxGetLastCmdTime", libsimx)) +c_SynchronousTrigger = ct.CFUNCTYPE(ct.c_int32,ct.c_int32)(("simxSynchronousTrigger", libsimx)) +c_Synchronous = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_ubyte)(("simxSynchronous", libsimx)) +c_PauseCommunication = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_ubyte)(("simxPauseCommunication", libsimx)) +c_GetInMessageInfo = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32))(("simxGetInMessageInfo", libsimx)) +c_GetOutMessageInfo = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32))(("simxGetOutMessageInfo", libsimx)) +c_GetConnectionId = ct.CFUNCTYPE(ct.c_int32,ct.c_int32)(("simxGetConnectionId", libsimx)) +c_CreateBuffer = ct.CFUNCTYPE(ct.POINTER(ct.c_ubyte), ct.c_int32)(("simxCreateBuffer", libsimx)) +c_ReleaseBuffer = ct.CFUNCTYPE(None, ct.c_void_p)(("simxReleaseBuffer", libsimx)) +c_TransferFile = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char), ct.c_int32, ct.c_int32)(("simxTransferFile", libsimx)) +c_EraseFile = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxEraseFile", libsimx)) +c_GetAndClearStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetAndClearStringSignal", libsimx)) +c_ReadStringStream = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxReadStringStream", libsimx)) +c_CreateDummy = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_float, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.c_int32), ct.c_int32)(("simxCreateDummy", libsimx)) +c_Query = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxQuery", libsimx)) +c_GetObjectGroupData = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_float)), ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetObjectGroupData", libsimx)) +c_GetObjectVelocity = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectVelocity", libsimx)) +c_CallScriptFunction = ct.CFUNCTYPE(ct.c_int32,ct.c_int32,ct.POINTER(ct.c_char),ct.c_int32,ct.POINTER(ct.c_char),ct.c_int32,ct.POINTER(ct.c_int32),ct.c_int32,ct.POINTER(ct.c_float),ct.c_int32,ct.POINTER(ct.c_char),ct.c_int32,ct.POINTER(ct.c_ubyte),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_float)),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_char)),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_ubyte)),ct.c_int32)(("simxCallScriptFunction", libsimx)) + +#API functions +def simxGetJointPosition(clientID, jointHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + position = ct.c_float() + return c_GetJointPosition(clientID, jointHandle, ct.byref(position), operationMode), position.value + +def simxSetJointPosition(clientID, jointHandle, position, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetJointPosition(clientID, jointHandle, position, operationMode) + +def simxGetJointMatrix(clientID, jointHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + matrix = (ct.c_float*12)() + ret = c_GetJointMatrix(clientID, jointHandle, matrix, operationMode) + arr = [] + for i in range(12): + arr.append(matrix[i]) + return ret, arr + +def simxSetSphericalJointMatrix(clientID, jointHandle, matrix, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + matrix = (ct.c_float*12)(*matrix) + return c_SetSphericalJointMatrix(clientID, jointHandle, matrix, operationMode) + +def simxSetJointTargetVelocity(clientID, jointHandle, targetVelocity, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetJointTargetVelocity(clientID, jointHandle, targetVelocity, operationMode) + +def simxSetJointTargetPosition(clientID, jointHandle, targetPosition, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetJointTargetPosition(clientID, jointHandle, targetPosition, operationMode) + +def simxJointGetForce(clientID, jointHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + force = ct.c_float() + return c_GetJointForce(clientID, jointHandle, ct.byref(force), operationMode), force.value + +def simxGetJointForce(clientID, jointHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + force = ct.c_float() + return c_GetJointForce(clientID, jointHandle, ct.byref(force), operationMode), force.value + +def simxSetJointForce(clientID, jointHandle, force, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + return c_SetJointForce(clientID, jointHandle, force, operationMode) + +def simxReadForceSensor(clientID, forceSensorHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + state = ct.c_ubyte() + forceVector = (ct.c_float*3)() + torqueVector = (ct.c_float*3)() + ret = c_ReadForceSensor(clientID, forceSensorHandle, ct.byref(state), forceVector, torqueVector, operationMode) + arr1 = [] + for i in range(3): + arr1.append(forceVector[i]) + arr2 = [] + for i in range(3): + arr2.append(torqueVector[i]) + if sys.version_info[0] == 3: + state=state.value + else: + state=ord(state.value) + return ret, state, arr1, arr2 + +def simxBreakForceSensor(clientID, forceSensorHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + return c_BreakForceSensor(clientID, forceSensorHandle, operationMode) + +def simxReadVisionSensor(clientID, sensorHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + detectionState = ct.c_ubyte() + auxValues = ct.POINTER(ct.c_float)() + auxValuesCount = ct.POINTER(ct.c_int)() + ret = c_ReadVisionSensor(clientID, sensorHandle, ct.byref(detectionState), ct.byref(auxValues), ct.byref(auxValuesCount), operationMode) + + auxValues2 = [] + if ret == 0: + s = 0 + for i in range(auxValuesCount[0]): + auxValues2.append(auxValues[s:s+auxValuesCount[i+1]]) + s += auxValuesCount[i+1] + + #free C buffers + c_ReleaseBuffer(auxValues) + c_ReleaseBuffer(auxValuesCount) + + return ret, bool(detectionState.value!=0), auxValues2 + +def simxGetObjectHandle(clientID, objectName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + handle = ct.c_int() + if (sys.version_info[0] == 3) and (type(objectName) is str): + objectName=objectName.encode('utf-8') + return c_GetObjectHandle(clientID, objectName, ct.byref(handle), operationMode), handle.value + +def simxGetVisionSensorImage(clientID, sensorHandle, options, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + resolution = (ct.c_int*2)() + c_image = ct.POINTER(ct.c_byte)() + bytesPerPixel = 3 + if (options and 1) != 0: + bytesPerPixel = 1 + ret = c_GetVisionSensorImage(clientID, sensorHandle, resolution, ct.byref(c_image), options, operationMode) + + reso = [] + image = [] + if (ret == 0): + image = [None]*resolution[0]*resolution[1]*bytesPerPixel + for i in range(resolution[0] * resolution[1] * bytesPerPixel): + image[i] = c_image[i] + for i in range(2): + reso.append(resolution[i]) + return ret, reso, image + +def simxSetVisionSensorImage(clientID, sensorHandle, image, options, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + size = len(image) + image_bytes = (ct.c_byte*size)(*image) + return c_SetVisionSensorImage(clientID, sensorHandle, image_bytes, size, options, operationMode) + +def simxGetVisionSensorDepthBuffer(clientID, sensorHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + c_buffer = ct.POINTER(ct.c_float)() + resolution = (ct.c_int*2)() + ret = c_GetVisionSensorDepthBuffer(clientID, sensorHandle, resolution, ct.byref(c_buffer), operationMode) + reso = [] + buffer = [] + if (ret == 0): + buffer = [None]*resolution[0]*resolution[1] + for i in range(resolution[0] * resolution[1]): + buffer[i] = c_buffer[i] + for i in range(2): + reso.append(resolution[i]) + return ret, reso, buffer + +def simxGetObjectChild(clientID, parentObjectHandle, childIndex, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + childObjectHandle = ct.c_int() + return c_GetObjectChild(clientID, parentObjectHandle, childIndex, ct.byref(childObjectHandle), operationMode), childObjectHandle.value + +def simxGetObjectParent(clientID, childObjectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + parentObjectHandle = ct.c_int() + return c_GetObjectParent(clientID, childObjectHandle, ct.byref(parentObjectHandle), operationMode), parentObjectHandle.value + +def simxReadProximitySensor(clientID, sensorHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + detectionState = ct.c_ubyte() + detectedObjectHandle = ct.c_int() + detectedPoint = (ct.c_float*3)() + detectedSurfaceNormalVector = (ct.c_float*3)() + ret = c_ReadProximitySensor(clientID, sensorHandle, ct.byref(detectionState), detectedPoint, ct.byref(detectedObjectHandle), detectedSurfaceNormalVector, operationMode) + arr1 = [] + for i in range(3): + arr1.append(detectedPoint[i]) + arr2 = [] + for i in range(3): + arr2.append(detectedSurfaceNormalVector[i]) + return ret, bool(detectionState.value!=0), arr1, detectedObjectHandle.value, arr2 + +def simxLoadModel(clientID, modelPathAndName, options, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + baseHandle = ct.c_int() + if (sys.version_info[0] == 3) and (type(modelPathAndName) is str): + modelPathAndName=modelPathAndName.encode('utf-8') + return c_LoadModel(clientID, modelPathAndName, options, ct.byref(baseHandle), operationMode), baseHandle.value + +def simxLoadUI(clientID, uiPathAndName, options, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + count = ct.c_int() + uiHandles = ct.POINTER(ct.c_int)() + if (sys.version_info[0] == 3) and (type(uiPathAndName) is str): + uiPathAndName=uiPathAndName.encode('utf-8') + ret = c_LoadUI(clientID, uiPathAndName, options, ct.byref(count), ct.byref(uiHandles), operationMode) + + handles = [] + if ret == 0: + for i in range(count.value): + handles.append(uiHandles[i]) + #free C buffers + c_ReleaseBuffer(uiHandles) + + return ret, handles + +def simxLoadScene(clientID, scenePathAndName, options, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(scenePathAndName) is str): + scenePathAndName=scenePathAndName.encode('utf-8') + return c_LoadScene(clientID, scenePathAndName, options, operationMode) + +def simxStartSimulation(clientID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_StartSimulation(clientID, operationMode) + +def simxPauseSimulation(clientID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_PauseSimulation(clientID, operationMode) + +def simxStopSimulation(clientID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_StopSimulation(clientID, operationMode) + +def simxGetUIHandle(clientID, uiName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + handle = ct.c_int() + if (sys.version_info[0] == 3) and (type(uiName) is str): + uiName=uiName.encode('utf-8') + return c_GetUIHandle(clientID, uiName, ct.byref(handle), operationMode), handle.value + +def simxGetUISlider(clientID, uiHandle, uiButtonID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + position = ct.c_int() + return c_GetUISlider(clientID, uiHandle, uiButtonID, ct.byref(position), operationMode), position.value + +def simxSetUISlider(clientID, uiHandle, uiButtonID, position, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetUISlider(clientID, uiHandle, uiButtonID, position, operationMode) + +def simxGetUIEventButton(clientID, uiHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + uiEventButtonID = ct.c_int() + auxValues = (ct.c_int*2)() + ret = c_GetUIEventButton(clientID, uiHandle, ct.byref(uiEventButtonID), auxValues, operationMode) + arr = [] + for i in range(2): + arr.append(auxValues[i]) + return ret, uiEventButtonID.value, arr + +def simxGetUIButtonProperty(clientID, uiHandle, uiButtonID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + prop = ct.c_int() + return c_GetUIButtonProperty(clientID, uiHandle, uiButtonID, ct.byref(prop), operationMode), prop.value + +def simxSetUIButtonProperty(clientID, uiHandle, uiButtonID, prop, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetUIButtonProperty(clientID, uiHandle, uiButtonID, prop, operationMode) + +def simxAddStatusbarMessage(clientID, message, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(message) is str): + message=message.encode('utf-8') + return c_AddStatusbarMessage(clientID, message, operationMode) + +def simxAuxiliaryConsoleOpen(clientID, title, maxLines, mode, position, size, textColor, backgroundColor, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + consoleHandle = ct.c_int() + if (sys.version_info[0] == 3) and (type(title) is str): + title=title.encode('utf-8') + if position != None: + c_position = (ct.c_int*2)(*position) + else: + c_position = None + if size != None: + c_size = (ct.c_int*2)(*size) + else: + c_size = None + if textColor != None: + c_textColor = (ct.c_float*3)(*textColor) + else: + c_textColor = None + if backgroundColor != None: + c_backgroundColor = (ct.c_float*3)(*backgroundColor) + else: + c_backgroundColor = None + return c_AuxiliaryConsoleOpen(clientID, title, maxLines, mode, c_position, c_size, c_textColor, c_backgroundColor, ct.byref(consoleHandle), operationMode), consoleHandle.value + +def simxAuxiliaryConsoleClose(clientID, consoleHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_AuxiliaryConsoleClose(clientID, consoleHandle, operationMode) + +def simxAuxiliaryConsolePrint(clientID, consoleHandle, txt, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(txt) is str): + txt=txt.encode('utf-8') + return c_AuxiliaryConsolePrint(clientID, consoleHandle, txt, operationMode) + +def simxAuxiliaryConsoleShow(clientID, consoleHandle, showState, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_AuxiliaryConsoleShow(clientID, consoleHandle, showState, operationMode) + +def simxGetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + eulerAngles = (ct.c_float*3)() + ret = c_GetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, eulerAngles, operationMode) + arr = [] + for i in range(3): + arr.append(eulerAngles[i]) + return ret, arr + +def simxGetObjectPosition(clientID, objectHandle, relativeToObjectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + position = (ct.c_float*3)() + ret = c_GetObjectPosition(clientID, objectHandle, relativeToObjectHandle, position, operationMode) + arr = [] + for i in range(3): + arr.append(position[i]) + return ret, arr + +def simxSetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, eulerAngles, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + angles = (ct.c_float*3)(*eulerAngles) + return c_SetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, angles, operationMode) + +def simxSetObjectPosition(clientID, objectHandle, relativeToObjectHandle, position, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + c_position = (ct.c_float*3)(*position) + return c_SetObjectPosition(clientID, objectHandle, relativeToObjectHandle, c_position, operationMode) + +def simxSetObjectParent(clientID, objectHandle, parentObject, keepInPlace, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetObjectParent(clientID, objectHandle, parentObject, keepInPlace, operationMode) + +def simxSetUIButtonLabel(clientID, uiHandle, uiButtonID, upStateLabel, downStateLabel, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if sys.version_info[0] == 3: + if type(upStateLabel) is str: + upStateLabel=upStateLabel.encode('utf-8') + if type(downStateLabel) is str: + downStateLabel=downStateLabel.encode('utf-8') + return c_SetUIButtonLabel(clientID, uiHandle, uiButtonID, upStateLabel, downStateLabel, operationMode) + +def simxGetLastErrors(clientID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + errors =[] + errorCnt = ct.c_int() + errorStrings = ct.POINTER(ct.c_char)() + ret = c_GetLastErrors(clientID, ct.byref(errorCnt), ct.byref(errorStrings), operationMode) + if ret == 0: + s = 0 + for i in range(errorCnt.value): + a = bytearray() + while errorStrings[s] != b'\0': + if sys.version_info[0] == 3: + a.append(int.from_bytes(errorStrings[s],'big')) + else: + a.append(errorStrings[s]) + s += 1 + s += 1 #skip null + if sys.version_info[0] == 3: + errors.append(str(a,'utf-8')) + else: + errors.append(str(a)) + + return ret, errors + +def simxGetArrayParameter(clientID, paramIdentifier, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + paramValues = (ct.c_float*3)() + ret = c_GetArrayParameter(clientID, paramIdentifier, paramValues, operationMode) + arr = [] + for i in range(3): + arr.append(paramValues[i]) + return ret, arr + +def simxSetArrayParameter(clientID, paramIdentifier, paramValues, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + c_paramValues = (ct.c_float*3)(*paramValues) + return c_SetArrayParameter(clientID, paramIdentifier, c_paramValues, operationMode) + +def simxGetBooleanParameter(clientID, paramIdentifier, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + paramValue = ct.c_ubyte() + return c_GetBooleanParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode), bool(paramValue.value!=0) + +def simxSetBooleanParameter(clientID, paramIdentifier, paramValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetBooleanParameter(clientID, paramIdentifier, paramValue, operationMode) + +def simxGetIntegerParameter(clientID, paramIdentifier, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + paramValue = ct.c_int() + return c_GetIntegerParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode), paramValue.value + +def simxSetIntegerParameter(clientID, paramIdentifier, paramValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetIntegerParameter(clientID, paramIdentifier, paramValue, operationMode) + +def simxGetFloatingParameter(clientID, paramIdentifier, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + paramValue = ct.c_float() + return c_GetFloatingParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode), paramValue.value + +def simxSetFloatingParameter(clientID, paramIdentifier, paramValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetFloatingParameter(clientID, paramIdentifier, paramValue, operationMode) + +def simxGetStringParameter(clientID, paramIdentifier, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + paramValue = ct.POINTER(ct.c_char)() + ret = c_GetStringParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode) + + a = bytearray() + if ret == 0: + i = 0 + while paramValue[i] != b'\0': + if sys.version_info[0] == 3: + a.append(int.from_bytes(paramValue[i],'big')) + else: + a.append(paramValue[i]) + i=i+1 + if sys.version_info[0] == 3: + a=str(a,'utf-8') + else: + a=str(a) + return ret, a + +def simxGetCollisionHandle(clientID, collisionObjectName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + handle = ct.c_int() + if (sys.version_info[0] == 3) and (type(collisionObjectName) is str): + collisionObjectName=collisionObjectName.encode('utf-8') + return c_GetCollisionHandle(clientID, collisionObjectName, ct.byref(handle), operationMode), handle.value + +def simxGetCollectionHandle(clientID, collectionName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + handle = ct.c_int() + if (sys.version_info[0] == 3) and (type(collectionName) is str): + collectionName=collectionName.encode('utf-8') + return c_GetCollectionHandle(clientID, collectionName, ct.byref(handle), operationMode), handle.value + +def simxGetDistanceHandle(clientID, distanceObjectName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + handle = ct.c_int() + if (sys.version_info[0] == 3) and (type(distanceObjectName) is str): + distanceObjectName=distanceObjectName.encode('utf-8') + return c_GetDistanceHandle(clientID, distanceObjectName, ct.byref(handle), operationMode), handle.value + +def simxReadCollision(clientID, collisionObjectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + collisionState = ct.c_ubyte() + return c_ReadCollision(clientID, collisionObjectHandle, ct.byref(collisionState), operationMode), bool(collisionState.value!=0) + +def simxReadDistance(clientID, distanceObjectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + minimumDistance = ct.c_float() + return c_ReadDistance(clientID, distanceObjectHandle, ct.byref(minimumDistance), operationMode), minimumDistance.value + +def simxRemoveObject(clientID, objectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_RemoveObject(clientID, objectHandle, operationMode) + +def simxRemoveModel(clientID, objectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_RemoveModel(clientID, objectHandle, operationMode) + +def simxRemoveUI(clientID, uiHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_RemoveUI(clientID, uiHandle, operationMode) + +def simxCloseScene(clientID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_CloseScene(clientID, operationMode) + +def simxGetObjects(clientID, objectType, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + objectCount = ct.c_int() + objectHandles = ct.POINTER(ct.c_int)() + + ret = c_GetObjects(clientID, objectType, ct.byref(objectCount), ct.byref(objectHandles), operationMode) + handles = [] + if ret == 0: + for i in range(objectCount.value): + handles.append(objectHandles[i]) + + return ret, handles + + +def simxDisplayDialog(clientID, titleText, mainText, dialogType, initialText, titleColors, dialogColors, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + if titleColors != None: + c_titleColors = (ct.c_float*6)(*titleColors) + else: + c_titleColors = None + if dialogColors != None: + c_dialogColors = (ct.c_float*6)(*dialogColors) + else: + c_dialogColors = None + + c_dialogHandle = ct.c_int() + c_uiHandle = ct.c_int() + if sys.version_info[0] == 3: + if type(titleText) is str: + titleText=titleText.encode('utf-8') + if type(mainText) is str: + mainText=mainText.encode('utf-8') + if type(initialText) is str: + initialText=initialText.encode('utf-8') + return c_DisplayDialog(clientID, titleText, mainText, dialogType, initialText, c_titleColors, c_dialogColors, ct.byref(c_dialogHandle), ct.byref(c_uiHandle), operationMode), c_dialogHandle.value, c_uiHandle.value + +def simxEndDialog(clientID, dialogHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_EndDialog(clientID, dialogHandle, operationMode) + +def simxGetDialogInput(clientID, dialogHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + inputText = ct.POINTER(ct.c_char)() + ret = c_GetDialogInput(clientID, dialogHandle, ct.byref(inputText), operationMode) + + a = bytearray() + if ret == 0: + i = 0 + while inputText[i] != b'\0': + if sys.version_info[0] == 3: + a.append(int.from_bytes(inputText[i],'big')) + else: + a.append(inputText[i]) + i = i+1 + + if sys.version_info[0] == 3: + a=str(a,'utf-8') + else: + a=str(a) + return ret, a + + +def simxGetDialogResult(clientID, dialogHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + result = ct.c_int() + return c_GetDialogResult(clientID, dialogHandle, ct.byref(result), operationMode), result.value + +def simxCopyPasteObjects(clientID, objectHandles, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + c_objectHandles = (ct.c_int*len(objectHandles))(*objectHandles) + c_objectHandles = ct.cast(c_objectHandles,ct.POINTER(ct.c_int)) # IronPython needs this + newObjectCount = ct.c_int() + newObjectHandles = ct.POINTER(ct.c_int)() + ret = c_CopyPasteObjects(clientID, c_objectHandles, len(objectHandles), ct.byref(newObjectHandles), ct.byref(newObjectCount), operationMode) + + newobj = [] + if ret == 0: + for i in range(newObjectCount.value): + newobj.append(newObjectHandles[i]) + + return ret, newobj + + +def simxGetObjectSelection(clientID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + objectCount = ct.c_int() + objectHandles = ct.POINTER(ct.c_int)() + ret = c_GetObjectSelection(clientID, ct.byref(objectHandles), ct.byref(objectCount), operationMode) + + newobj = [] + if ret == 0: + for i in range(objectCount.value): + newobj.append(objectHandles[i]) + + return ret, newobj + + + +def simxSetObjectSelection(clientID, objectHandles, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + c_objectHandles = (ct.c_int*len(objectHandles))(*objectHandles) + return c_SetObjectSelection(clientID, c_objectHandles, len(objectHandles), operationMode) + +def simxClearFloatSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_ClearFloatSignal(clientID, signalName, operationMode) + +def simxClearIntegerSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_ClearIntegerSignal(clientID, signalName, operationMode) + +def simxClearStringSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_ClearStringSignal(clientID, signalName, operationMode) + +def simxGetFloatSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + signalValue = ct.c_float() + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_GetFloatSignal(clientID, signalName, ct.byref(signalValue), operationMode), signalValue.value + +def simxGetIntegerSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + signalValue = ct.c_int() + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_GetIntegerSignal(clientID, signalName, ct.byref(signalValue), operationMode), signalValue.value + +def simxGetStringSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + signalLength = ct.c_int(); + signalValue = ct.POINTER(ct.c_ubyte)() + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + ret = c_GetStringSignal(clientID, signalName, ct.byref(signalValue), ct.byref(signalLength), operationMode) + + a = bytearray() + if ret == 0: + for i in range(signalLength.value): + a.append(signalValue[i]) + if sys.version_info[0] != 3: + a=str(a) + + return ret, a + +def simxGetAndClearStringSignal(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + signalLength = ct.c_int(); + signalValue = ct.POINTER(ct.c_ubyte)() + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + ret = c_GetAndClearStringSignal(clientID, signalName, ct.byref(signalValue), ct.byref(signalLength), operationMode) + + a = bytearray() + if ret == 0: + for i in range(signalLength.value): + a.append(signalValue[i]) + if sys.version_info[0] != 3: + a=str(a) + + return ret, a + +def simxReadStringStream(clientID, signalName, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + signalLength = ct.c_int(); + signalValue = ct.POINTER(ct.c_ubyte)() + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + ret = c_ReadStringStream(clientID, signalName, ct.byref(signalValue), ct.byref(signalLength), operationMode) + + a = bytearray() + if ret == 0: + for i in range(signalLength.value): + a.append(signalValue[i]) + if sys.version_info[0] != 3: + a=str(a) + + return ret, a + +def simxSetFloatSignal(clientID, signalName, signalValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_SetFloatSignal(clientID, signalName, signalValue, operationMode) + +def simxSetIntegerSignal(clientID, signalName, signalValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(signalName) is str): + signalName=signalName.encode('utf-8') + return c_SetIntegerSignal(clientID, signalName, signalValue, operationMode) + +def simxSetStringSignal(clientID, signalName, signalValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + sigV=signalValue + if sys.version_info[0] == 3: + if type(signalName) is str: + signalName=signalName.encode('utf-8') + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=signalValue.encode('utf-8') + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + else: + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=bytearray(signalValue) + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this + return c_SetStringSignal(clientID, signalName, sigV, len(signalValue), operationMode) + +def simxAppendStringSignal(clientID, signalName, signalValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + sigV=signalValue + if sys.version_info[0] == 3: + if type(signalName) is str: + signalName=signalName.encode('utf-8') + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=signalValue.encode('utf-8') + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + else: + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=bytearray(signalValue) + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this + return c_AppendStringSignal(clientID, signalName, sigV, len(signalValue), operationMode) + +def simxWriteStringStream(clientID, signalName, signalValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + sigV=signalValue + if sys.version_info[0] == 3: + if type(signalName) is str: + signalName=signalName.encode('utf-8') + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=signalValue.encode('utf-8') + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + else: + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=bytearray(signalValue) + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this + return c_WriteStringStream(clientID, signalName, sigV, len(signalValue), operationMode) + +def simxGetObjectFloatParameter(clientID, objectHandle, parameterID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + parameterValue = ct.c_float() + return c_GetObjectFloatParameter(clientID, objectHandle, parameterID, ct.byref(parameterValue), operationMode), parameterValue.value + +def simxSetObjectFloatParameter(clientID, objectHandle, parameterID, parameterValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetObjectFloatParameter(clientID, objectHandle, parameterID, parameterValue, operationMode) + +def simxGetObjectIntParameter(clientID, objectHandle, parameterID, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + parameterValue = ct.c_int() + return c_GetObjectIntParameter(clientID, objectHandle, parameterID, ct.byref(parameterValue), operationMode), parameterValue.value + +def simxSetObjectIntParameter(clientID, objectHandle, parameterID, parameterValue, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetObjectIntParameter(clientID, objectHandle, parameterID, parameterValue, operationMode) + +def simxGetModelProperty(clientID, objectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + prop = ct.c_int() + return c_GetModelProperty(clientID, objectHandle, ct.byref(prop), operationMode), prop.value + +def simxSetModelProperty(clientID, objectHandle, prop, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SetModelProperty(clientID, objectHandle, prop, operationMode) + +def simxStart(connectionAddress, connectionPort, waitUntilConnected, doNotReconnectOnceDisconnected, timeOutInMs, commThreadCycleInMs): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(connectionAddress) is str): + connectionAddress=connectionAddress.encode('utf-8') + return c_Start(connectionAddress, connectionPort, waitUntilConnected, doNotReconnectOnceDisconnected, timeOutInMs, commThreadCycleInMs) + +def simxFinish(clientID): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_Finish(clientID) + +def simxGetPingTime(clientID): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + pingTime = ct.c_int() + return c_GetPingTime(clientID, ct.byref(pingTime)), pingTime.value + +def simxGetLastCmdTime(clientID): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_GetLastCmdTime(clientID) + +def simxSynchronousTrigger(clientID): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_SynchronousTrigger(clientID) + +def simxSynchronous(clientID, enable): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_Synchronous(clientID, enable) + +def simxPauseCommunication(clientID, enable): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_PauseCommunication(clientID, enable) + +def simxGetInMessageInfo(clientID, infoType): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + info = ct.c_int() + return c_GetInMessageInfo(clientID, infoType, ct.byref(info)), info.value + +def simxGetOutMessageInfo(clientID, infoType): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + info = ct.c_int() + return c_GetOutMessageInfo(clientID, infoType, ct.byref(info)), info.value + +def simxGetConnectionId(clientID): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_GetConnectionId(clientID) + +def simxCreateBuffer(bufferSize): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_CreateBuffer(bufferSize) + +def simxReleaseBuffer(buffer): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + return c_ReleaseBuffer(buffer) + +def simxTransferFile(clientID, filePathAndName, fileName_serverSide, timeOut, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(filePathAndName) is str): + filePathAndName=filePathAndName.encode('utf-8') + return c_TransferFile(clientID, filePathAndName, fileName_serverSide, timeOut, operationMode) + +def simxEraseFile(clientID, fileName_serverSide, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if (sys.version_info[0] == 3) and (type(fileName_serverSide) is str): + fileName_serverSide=fileName_serverSide.encode('utf-8') + return c_EraseFile(clientID, fileName_serverSide, operationMode) + +def simxCreateDummy(clientID, size, color, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + handle = ct.c_int() + if color != None: + c_color = (ct.c_ubyte*12)(*color) + else: + c_color = None + return c_CreateDummy(clientID, size, c_color, ct.byref(handle), operationMode), handle.value + +def simxQuery(clientID, signalName, signalValue, retSignalName, timeOutInMs): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + retSignalLength = ct.c_int(); + retSignalValue = ct.POINTER(ct.c_ubyte)() + + sigV=signalValue + if sys.version_info[0] == 3: + if type(signalName) is str: + signalName=signalName.encode('utf-8') + if type(retSignalName) is str: + retSignalName=retSignalName.encode('utf-8') + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=signalValue.encode('utf-8') + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + else: + if type(signalValue) is bytearray: + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + if type(signalValue) is str: + signalValue=bytearray(signalValue) + sigV = (ct.c_ubyte*len(signalValue))(*signalValue) + sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this + + ret = c_Query(clientID, signalName, sigV, len(signalValue), retSignalName, ct.byref(retSignalValue), ct.byref(retSignalLength), timeOutInMs) + + a = bytearray() + if ret == 0: + for i in range(retSignalLength.value): + a.append(retSignalValue[i]) + if sys.version_info[0] != 3: + a=str(a) + + return ret, a + +def simxGetObjectGroupData(clientID, objectType, dataType, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + handles =[] + intData =[] + floatData =[] + stringData =[] + handlesC = ct.c_int() + handlesP = ct.POINTER(ct.c_int)() + intDataC = ct.c_int() + intDataP = ct.POINTER(ct.c_int)() + floatDataC = ct.c_int() + floatDataP = ct.POINTER(ct.c_float)() + stringDataC = ct.c_int() + stringDataP = ct.POINTER(ct.c_char)() + ret = c_GetObjectGroupData(clientID, objectType, dataType, ct.byref(handlesC), ct.byref(handlesP), ct.byref(intDataC), ct.byref(intDataP), ct.byref(floatDataC), ct.byref(floatDataP), ct.byref(stringDataC), ct.byref(stringDataP), operationMode) + + if ret == 0: + for i in range(handlesC.value): + handles.append(handlesP[i]) + for i in range(intDataC.value): + intData.append(intDataP[i]) + for i in range(floatDataC.value): + floatData.append(floatDataP[i]) + s = 0 + for i in range(stringDataC.value): + a = bytearray() + while stringDataP[s] != b'\0': + if sys.version_info[0] == 3: + a.append(int.from_bytes(stringDataP[s],'big')) + else: + a.append(stringDataP[s]) + s += 1 + s += 1 #skip null + if sys.version_info[0] == 3: + a=str(a,'utf-8') + else: + a=str(a) + stringData.append(a) + + return ret, handles, intData, floatData, stringData + +def simxCallScriptFunction(clientID, scriptDescription, options, functionName, inputInts, inputFloats, inputStrings, inputBuffer, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + inputBufferV=inputBuffer + if sys.version_info[0] == 3: + if type(scriptDescription) is str: + scriptDescription=scriptDescription.encode('utf-8') + if type(functionName) is str: + functionName=functionName.encode('utf-8') + if type(inputBuffer) is bytearray: + inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) + if type(inputBuffer) is str: + inputBuffer=inputBuffer.encode('utf-8') + inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) + else: + if type(inputBuffer) is bytearray: + inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) + if type(inputBuffer) is str: + inputBuffer=bytearray(inputBuffer) + inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) + inputBufferV=ct.cast(inputBufferV,ct.POINTER(ct.c_ubyte)) # IronPython needs this + + c_inInts = (ct.c_int*len(inputInts))(*inputInts) + c_inInts = ct.cast(c_inInts,ct.POINTER(ct.c_int)) # IronPython needs this + c_inFloats = (ct.c_float*len(inputFloats))(*inputFloats) + c_inFloats = ct.cast(c_inFloats,ct.POINTER(ct.c_float)) # IronPython needs this + + concatStr=''.encode('utf-8') + for i in range(len(inputStrings)): + a=inputStrings[i] + a=a+'\0' + if type(a) is str: + a=a.encode('utf-8') + concatStr=concatStr+a + c_inStrings = (ct.c_char*len(concatStr))(*concatStr) + + intDataOut =[] + floatDataOut =[] + stringDataOut =[] + bufferOut =bytearray() + + intDataC = ct.c_int() + intDataP = ct.POINTER(ct.c_int)() + floatDataC = ct.c_int() + floatDataP = ct.POINTER(ct.c_float)() + stringDataC = ct.c_int() + stringDataP = ct.POINTER(ct.c_char)() + bufferS = ct.c_int() + bufferP = ct.POINTER(ct.c_ubyte)() + + ret = c_CallScriptFunction(clientID,scriptDescription,options,functionName,len(inputInts),c_inInts,len(inputFloats),c_inFloats,len(inputStrings),c_inStrings,len(inputBuffer),inputBufferV,ct.byref(intDataC),ct.byref(intDataP),ct.byref(floatDataC),ct.byref(floatDataP),ct.byref(stringDataC),ct.byref(stringDataP),ct.byref(bufferS),ct.byref(bufferP),operationMode) + + if ret == 0: + for i in range(intDataC.value): + intDataOut.append(intDataP[i]) + for i in range(floatDataC.value): + floatDataOut.append(floatDataP[i]) + s = 0 + for i in range(stringDataC.value): + a = bytearray() + while stringDataP[s] != b'\0': + if sys.version_info[0] == 3: + a.append(int.from_bytes(stringDataP[s],'big')) + else: + a.append(stringDataP[s]) + s += 1 + s += 1 #skip null + if sys.version_info[0] == 3: + a=str(a,'utf-8') + else: + a=str(a) + stringDataOut.append(a) + for i in range(bufferS.value): + bufferOut.append(bufferP[i]) + if sys.version_info[0] != 3: + bufferOut=str(bufferOut) + + return ret, intDataOut, floatDataOut, stringDataOut, bufferOut + +def simxGetObjectVelocity(clientID, objectHandle, operationMode): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + linearVel = (ct.c_float*3)() + angularVel = (ct.c_float*3)() + ret = c_GetObjectVelocity(clientID, objectHandle, linearVel, angularVel, operationMode) + arr1 = [] + for i in range(3): + arr1.append(linearVel[i]) + arr2 = [] + for i in range(3): + arr2.append(angularVel[i]) + return ret, arr1, arr2 + +def simxPackInts(intList): + ''' + Please have a look at the function description/documentation in the V-REP user manual + ''' + + if sys.version_info[0] == 3: + s=bytes() + for i in range(len(intList)): + s=s+struct.pack('. +# ------------------------------------------------------------------- +# +# This file was automatically created for V-REP release V3.3.0 on February 19th 2016 + +#constants +#Scene object types. Values are serialized +sim_object_shape_type =0 +sim_object_joint_type =1 +sim_object_graph_type =2 +sim_object_camera_type =3 +sim_object_dummy_type =4 +sim_object_proximitysensor_type =5 +sim_object_reserved1 =6 +sim_object_reserved2 =7 +sim_object_path_type =8 +sim_object_visionsensor_type =9 +sim_object_volume_type =10 +sim_object_mill_type =11 +sim_object_forcesensor_type =12 +sim_object_light_type =13 +sim_object_mirror_type =14 + +#General object types. Values are serialized +sim_appobj_object_type =109 +sim_appobj_collision_type =110 +sim_appobj_distance_type =111 +sim_appobj_simulation_type =112 +sim_appobj_ik_type =113 +sim_appobj_constraintsolver_type=114 +sim_appobj_collection_type =115 +sim_appobj_ui_type =116 +sim_appobj_script_type =117 +sim_appobj_pathplanning_type =118 +sim_appobj_RESERVED_type =119 +sim_appobj_texture_type =120 + +# Ik calculation methods. Values are serialized +sim_ik_pseudo_inverse_method =0 +sim_ik_damped_least_squares_method =1 +sim_ik_jacobian_transpose_method =2 + +# Ik constraints. Values are serialized +sim_ik_x_constraint =1 +sim_ik_y_constraint =2 +sim_ik_z_constraint =4 +sim_ik_alpha_beta_constraint=8 +sim_ik_gamma_constraint =16 +sim_ik_avoidance_constraint =64 + +# Ik calculation results +sim_ikresult_not_performed =0 +sim_ikresult_success =1 +sim_ikresult_fail =2 + +# Scene object sub-types. Values are serialized +# Light sub-types +sim_light_omnidirectional_subtype =1 +sim_light_spot_subtype =2 +sim_light_directional_subtype =3 +# Joint sub-types +sim_joint_revolute_subtype =10 +sim_joint_prismatic_subtype =11 +sim_joint_spherical_subtype =12 +# Shape sub-types +sim_shape_simpleshape_subtype =20 +sim_shape_multishape_subtype =21 +# Proximity sensor sub-types +sim_proximitysensor_pyramid_subtype =30 +sim_proximitysensor_cylinder_subtype=31 +sim_proximitysensor_disc_subtype =32 +sim_proximitysensor_cone_subtype =33 +sim_proximitysensor_ray_subtype =34 +# Mill sub-types +sim_mill_pyramid_subtype =40 +sim_mill_cylinder_subtype =41 +sim_mill_disc_subtype =42 +sim_mill_cone_subtype =42 +# No sub-type +sim_object_no_subtype =200 + + +#Scene object main properties (serialized) +sim_objectspecialproperty_collidable =0x0001 +sim_objectspecialproperty_measurable =0x0002 +#reserved =0x0004 +#reserved =0x0008 +sim_objectspecialproperty_detectable_ultrasonic =0x0010 +sim_objectspecialproperty_detectable_infrared =0x0020 +sim_objectspecialproperty_detectable_laser =0x0040 +sim_objectspecialproperty_detectable_inductive =0x0080 +sim_objectspecialproperty_detectable_capacitive =0x0100 +sim_objectspecialproperty_renderable =0x0200 +sim_objectspecialproperty_detectable_all =sim_objectspecialproperty_detectable_ultrasonic|sim_objectspecialproperty_detectable_infrared|sim_objectspecialproperty_detectable_laser|sim_objectspecialproperty_detectable_inductive|sim_objectspecialproperty_detectable_capacitive +sim_objectspecialproperty_cuttable =0x0400 +sim_objectspecialproperty_pathplanning_ignored =0x0800 + +# Model properties (serialized) +sim_modelproperty_not_collidable =0x0001 +sim_modelproperty_not_measurable =0x0002 +sim_modelproperty_not_renderable =0x0004 +sim_modelproperty_not_detectable =0x0008 +sim_modelproperty_not_cuttable =0x0010 +sim_modelproperty_not_dynamic =0x0020 +sim_modelproperty_not_respondable =0x0040 # cannot be selected if sim_modelproperty_not_dynamic is not selected +sim_modelproperty_not_reset =0x0080 # Model is not reset at simulation end. This flag is cleared at simulation end +sim_modelproperty_not_visible =0x0100 # Whole model is invisible independent of local visibility settings +sim_modelproperty_not_model =0xf000 # object is not a model + + +# Check the documentation instead of comments below!! +# Following messages are dispatched to the Lua-message container +sim_message_ui_button_state_change =0 # a UI button slider etc. changed (due to a user's action). aux[0]=UI handle aux[1]=button handle aux[2]=button attributes aux[3]=slider position (if slider) +sim_message_reserved9 =1 # Do not use +sim_message_object_selection_changed=2 +sim_message_reserved10 =3 # do not use +sim_message_model_loaded =4 +sim_message_reserved11 =5 # do not use +sim_message_keypress =6 # a key was pressed while the focus was on a page (aux[0]=key aux[1]=ctrl and shift key state) +sim_message_bannerclicked =7 # a banner was clicked (aux[0]=banner ID) + + +# Following messages are dispatched only to the C-API (not available from Lua) +sim_message_for_c_api_only_start =0x100 # Do not use +sim_message_reserved1 =0x101 # Do not use +sim_message_reserved2 =0x102 # Do not use +sim_message_reserved3 =0x103 # Do not use +sim_message_eventcallback_scenesave =0x104 # about to save a scene +sim_message_eventcallback_modelsave =0x105 # about to save a model (current selection will be saved) +sim_message_eventcallback_moduleopen =0x106 # called when simOpenModule in Lua is called +sim_message_eventcallback_modulehandle =0x107 # called when simHandleModule in Lua is called with argument false +sim_message_eventcallback_moduleclose =0x108 # called when simCloseModule in Lua is called +sim_message_reserved4 =0x109 # Do not use +sim_message_reserved5 =0x10a # Do not use +sim_message_reserved6 =0x10b # Do not use +sim_message_reserved7 =0x10c # Do not use +sim_message_eventcallback_instancepass =0x10d # Called once every main application loop pass. auxiliaryData[0] contains event flags of events that happened since last time +sim_message_eventcallback_broadcast =0x10e +sim_message_eventcallback_imagefilter_enumreset =0x10f +sim_message_eventcallback_imagefilter_enumerate =0x110 +sim_message_eventcallback_imagefilter_adjustparams =0x111 +sim_message_eventcallback_imagefilter_reserved =0x112 +sim_message_eventcallback_imagefilter_process =0x113 +sim_message_eventcallback_reserved1 =0x114 # do not use +sim_message_eventcallback_reserved2 =0x115 # do not use +sim_message_eventcallback_reserved3 =0x116 # do not use +sim_message_eventcallback_reserved4 =0x117 # do not use +sim_message_eventcallback_abouttoundo =0x118 # the undo button was hit and a previous state is about to be restored +sim_message_eventcallback_undoperformed =0x119 # the undo button was hit and a previous state restored +sim_message_eventcallback_abouttoredo =0x11a # the redo button was hit and a future state is about to be restored +sim_message_eventcallback_redoperformed =0x11b # the redo button was hit and a future state restored +sim_message_eventcallback_scripticondblclick =0x11c # scipt icon was double clicked. (aux[0]=object handle associated with script set replyData[0] to 1 if script should not be opened) +sim_message_eventcallback_simulationabouttostart =0x11d +sim_message_eventcallback_simulationended =0x11e +sim_message_eventcallback_reserved5 =0x11f # do not use +sim_message_eventcallback_keypress =0x120 # a key was pressed while the focus was on a page (aux[0]=key aux[1]=ctrl and shift key state) +sim_message_eventcallback_modulehandleinsensingpart =0x121 # called when simHandleModule in Lua is called with argument true +sim_message_eventcallback_renderingpass =0x122 # called just before the scene is rendered +sim_message_eventcallback_bannerclicked =0x123 # called when a banner was clicked (aux[0]=banner ID) +sim_message_eventcallback_menuitemselected =0x124 # auxiliaryData[0] indicates the handle of the item auxiliaryData[1] indicates the state of the item +sim_message_eventcallback_refreshdialogs =0x125 # aux[0]=refresh degree (0=light 1=medium 2=full) +sim_message_eventcallback_sceneloaded =0x126 +sim_message_eventcallback_modelloaded =0x127 +sim_message_eventcallback_instanceswitch =0x128 +sim_message_eventcallback_guipass =0x129 +sim_message_eventcallback_mainscriptabouttobecalled =0x12a +sim_message_eventcallback_rmlposition =0x12b #the command simRMLPosition was called. The appropriate plugin should handle the call +sim_message_eventcallback_rmlvelocity =0x12c # the command simRMLVelocity was called. The appropriate plugin should handle the call +sim_message_simulation_start_resume_request =0x1000 +sim_message_simulation_pause_request =0x1001 +sim_message_simulation_stop_request =0x1002 + +# Scene object properties. Combine with the | operator +sim_objectproperty_reserved1 =0x0000 +sim_objectproperty_reserved2 =0x0001 +sim_objectproperty_reserved3 =0x0002 +sim_objectproperty_reserved4 =0x0003 +sim_objectproperty_reserved5 =0x0004 # formely sim_objectproperty_visible +sim_objectproperty_reserved6 =0x0008 # formely sim_objectproperty_wireframe +sim_objectproperty_collapsed =0x0010 +sim_objectproperty_selectable =0x0020 +sim_objectproperty_reserved7 =0x0040 +sim_objectproperty_selectmodelbaseinstead =0x0080 +sim_objectproperty_dontshowasinsidemodel =0x0100 +# reserved =0x0200 +sim_objectproperty_canupdatedna =0x0400 +sim_objectproperty_selectinvisible =0x0800 +sim_objectproperty_depthinvisible =0x1000 + + +# type of arguments (input and output) for custom lua commands +sim_lua_arg_nil =0 +sim_lua_arg_bool =1 +sim_lua_arg_int =2 +sim_lua_arg_float =3 +sim_lua_arg_string =4 +sim_lua_arg_invalid =5 +sim_lua_arg_table =8 + +# custom user interface properties. Values are serialized. +sim_ui_property_visible =0x0001 +sim_ui_property_visibleduringsimulationonly =0x0002 +sim_ui_property_moveable =0x0004 +sim_ui_property_relativetoleftborder =0x0008 +sim_ui_property_relativetotopborder =0x0010 +sim_ui_property_fixedwidthfont =0x0020 +sim_ui_property_systemblock =0x0040 +sim_ui_property_settocenter =0x0080 +sim_ui_property_rolledup =0x0100 +sim_ui_property_selectassociatedobject =0x0200 +sim_ui_property_visiblewhenobjectselected =0x0400 + + +# button properties. Values are serialized. +sim_buttonproperty_button =0x0000 +sim_buttonproperty_label =0x0001 +sim_buttonproperty_slider =0x0002 +sim_buttonproperty_editbox =0x0003 +sim_buttonproperty_staydown =0x0008 +sim_buttonproperty_enabled =0x0010 +sim_buttonproperty_borderless =0x0020 +sim_buttonproperty_horizontallycentered =0x0040 +sim_buttonproperty_ignoremouse =0x0080 +sim_buttonproperty_isdown =0x0100 +sim_buttonproperty_transparent =0x0200 +sim_buttonproperty_nobackgroundcolor =0x0400 +sim_buttonproperty_rollupaction =0x0800 +sim_buttonproperty_closeaction =0x1000 +sim_buttonproperty_verticallycentered =0x2000 +sim_buttonproperty_downupevent =0x4000 + + +# Simulation status +sim_simulation_stopped =0x00 # Simulation is stopped +sim_simulation_paused =0x08 # Simulation is paused +sim_simulation_advancing =0x10 # Simulation is advancing +sim_simulation_advancing_firstafterstop =sim_simulation_advancing|0x00 # First simulation pass (1x) +sim_simulation_advancing_running =sim_simulation_advancing|0x01 # Normal simulation pass (>=1x) +# reserved =sim_simulation_advancing|0x02 +sim_simulation_advancing_lastbeforepause =sim_simulation_advancing|0x03 # Last simulation pass before pause (1x) +sim_simulation_advancing_firstafterpause =sim_simulation_advancing|0x04 # First simulation pass after pause (1x) +sim_simulation_advancing_abouttostop =sim_simulation_advancing|0x05 # "Trying to stop" simulation pass (>=1x) +sim_simulation_advancing_lastbeforestop =sim_simulation_advancing|0x06 # Last simulation pass (1x) + + +# Script execution result (first return value) +sim_script_no_error =0 +sim_script_main_script_nonexistent =1 +sim_script_main_script_not_called =2 +sim_script_reentrance_error =4 +sim_script_lua_error =8 +sim_script_call_error =16 + + + # Script types (serialized!) +sim_scripttype_mainscript =0 +sim_scripttype_childscript =1 +sim_scripttype_jointctrlcallback =4 +sim_scripttype_contactcallback =5 +sim_scripttype_customizationscript =6 +sim_scripttype_generalcallback =7 + +# API call error messages +sim_api_errormessage_ignore =0 # does not memorize nor output errors +sim_api_errormessage_report =1 # memorizes errors (default for C-API calls) +sim_api_errormessage_output =2 # memorizes and outputs errors (default for Lua-API calls) + + +# special argument of some functions +sim_handle_all =-2 +sim_handle_all_except_explicit =-3 +sim_handle_self =-4 +sim_handle_main_script =-5 +sim_handle_tree =-6 +sim_handle_chain =-7 +sim_handle_single =-8 +sim_handle_default =-9 +sim_handle_all_except_self =-10 +sim_handle_parent =-11 + + +# special handle flags +sim_handleflag_assembly =0x400000 +sim_handleflag_model =0x800000 + + +# distance calculation methods (serialized) +sim_distcalcmethod_dl =0 +sim_distcalcmethod_dac =1 +sim_distcalcmethod_max_dl_dac =2 +sim_distcalcmethod_dl_and_dac =3 +sim_distcalcmethod_sqrt_dl2_and_dac2=4 +sim_distcalcmethod_dl_if_nonzero =5 +sim_distcalcmethod_dac_if_nonzero =6 + + + # Generic dialog styles +sim_dlgstyle_message =0 +sim_dlgstyle_input =1 +sim_dlgstyle_ok =2 +sim_dlgstyle_ok_cancel =3 +sim_dlgstyle_yes_no =4 +sim_dlgstyle_dont_center =32# can be combined with one of above values. Only with this flag can the position of the related UI be set just after dialog creation + + # Generic dialog return values +sim_dlgret_still_open =0 +sim_dlgret_ok =1 +sim_dlgret_cancel =2 +sim_dlgret_yes =3 +sim_dlgret_no =4 + + +# Path properties +sim_pathproperty_show_line =0x0001 +sim_pathproperty_show_orientation =0x0002 +sim_pathproperty_closed_path =0x0004 +sim_pathproperty_automatic_orientation =0x0008 +sim_pathproperty_invert_velocity =0x0010 +sim_pathproperty_infinite_acceleration =0x0020 +sim_pathproperty_flat_path =0x0040 +sim_pathproperty_show_position =0x0080 +sim_pathproperty_auto_velocity_profile_translation =0x0100 +sim_pathproperty_auto_velocity_profile_rotation =0x0200 +sim_pathproperty_endpoints_at_zero =0x0400 +sim_pathproperty_keep_x_up =0x0800 + + + # drawing objects +# following are mutually exclusive +sim_drawing_points =0 # 3 values per point (point size in pixels) +sim_drawing_lines =1 # 6 values per line (line size in pixels) +sim_drawing_triangles =2 # 9 values per triangle +sim_drawing_trianglepoints =3 # 6 values per point (3 for triangle position 3 for triangle normal vector) (triangle size in meters) +sim_drawing_quadpoints =4 # 6 values per point (3 for quad position 3 for quad normal vector) (quad size in meters) +sim_drawing_discpoints =5 # 6 values per point (3 for disc position 3 for disc normal vector) (disc size in meters) +sim_drawing_cubepoints =6 # 6 values per point (3 for cube position 3 for cube normal vector) (cube size in meters) +sim_drawing_spherepoints =7 # 3 values per point (sphere size in meters) + +# following can be or-combined +sim_drawing_itemcolors =0x00020 # +3 values per item (each item has its own ambient color (rgb values)). + # Mutually exclusive with sim_drawing_vertexcolors +sim_drawing_vertexcolors =0x00040 # +3 values per vertex (each vertex has its own ambient color (rgb values). Only for sim_drawing_lines (+6) and for sim_drawing_triangles(+9)). Mutually exclusive with sim_drawing_itemcolors +sim_drawing_itemsizes =0x00080 # +1 value per item (each item has its own size). Not for sim_drawing_triangles +sim_drawing_backfaceculling =0x00100 # back faces are not displayed for all items +sim_drawing_wireframe =0x00200 # all items displayed in wireframe +sim_drawing_painttag =0x00400 # all items are tagged as paint (for additinal processing at a later stage) +sim_drawing_followparentvisibility =0x00800 # if the object is associated with a scene object then it follows that visibility otherwise it is always visible +sim_drawing_cyclic =0x01000 # if the max item count was reached then the first items are overwritten. +sim_drawing_50percenttransparency =0x02000 # the drawing object will be 50% transparent +sim_drawing_25percenttransparency =0x04000 # the drawing object will be 25% transparent +sim_drawing_12percenttransparency =0x08000 # the drawing object will be 12.5% transparent +sim_drawing_emissioncolor =0x10000 # When used in combination with sim_drawing_itemcolors or sim_drawing_vertexcolors then the specified colors will be for the emissive component +sim_drawing_facingcamera =0x20000 # Only for trianglepoints quadpoints discpoints and cubepoints. If specified the normal verctor is calculated to face the camera (each item data requires 3 values less) +sim_drawing_overlay =0x40000 # When specified objects are always drawn on top of "regular objects" +sim_drawing_itemtransparency =0x80000 # +1 value per item (each item has its own transparency value (0-1)). Not compatible with sim_drawing_vertexcolors + +# banner values +# following can be or-combined +sim_banner_left =0x00001 # Banners display on the left of the specified point +sim_banner_right =0x00002 # Banners display on the right of the specified point +sim_banner_nobackground =0x00004 # Banners have no background rectangle +sim_banner_overlay =0x00008 # When specified banners are always drawn on top of "regular objects" +sim_banner_followparentvisibility =0x00010 # if the object is associated with a scene object then it follows that visibility otherwise it is always visible +sim_banner_clickselectsparent =0x00020 # if the object is associated with a scene object then clicking the banner will select the scene object +sim_banner_clicktriggersevent =0x00040 # if the banner is clicked an event is triggered (sim_message_eventcallback_bannerclicked and sim_message_bannerclicked are generated) +sim_banner_facingcamera =0x00080 # If specified the banner will always face the camera by rotating around the banner's vertical axis (y-axis) +sim_banner_fullyfacingcamera =0x00100 # If specified the banner will always fully face the camera (the banner's orientation is same as the camera looking at it) +sim_banner_backfaceculling =0x00200 # If specified the banner will only be visible from one side +sim_banner_keepsamesize =0x00400 # If specified the banner will always appear in the same size. In that case size represents the character height in pixels +sim_banner_bitmapfont =0x00800 # If specified a fixed-size bitmap font is used. The text will also always fully face the camera and be right + # to the specified position. Bitmap fonts are not clickable + + +# particle objects following are mutually exclusive +sim_particle_points1 =0 # 6 values per point (pt1 and pt2. Pt1 is start position pt2-pt1 is the initial velocity vector). i + #Point is 1 pixel big. Only appearance is a point internally handled as a perfect sphere +sim_particle_points2 =1 # 6 values per point. Point is 2 pixel big. Only appearance is a point internally handled as a perfect sphere +sim_particle_points4 =2 # 6 values per point. Point is 4 pixel big. Only appearance is a point internally handled as a perfect sphere +sim_particle_roughspheres =3 # 6 values per sphere. Only appearance is rough. Internally a perfect sphere +sim_particle_spheres =4 # 6 values per sphere. Internally a perfect sphere + + + + +# following can be or-combined +sim_particle_respondable1to4 =0x0020 # the particles are respondable against shapes (against all objects that have at least one bit 1-4 activated in the global respondable mask) +sim_particle_respondable5to8 =0x0040 # the particles are respondable against shapes (against all objects that have at least one bit 5-8 activated in the global respondable mask) +sim_particle_particlerespondable =0x0080 # the particles are respondable against each other +sim_particle_ignoresgravity =0x0100 # the particles ignore the effect of gravity. Not compatible with sim_particle_water +sim_particle_invisible =0x0200 # the particles are invisible +sim_particle_itemsizes =0x0400 # +1 value per particle (each particle can have a different size) +sim_particle_itemdensities =0x0800 # +1 value per particle (each particle can have a different density) +sim_particle_itemcolors =0x1000 # +3 values per particle (each particle can have a different color) +sim_particle_cyclic =0x2000 # if the max item count was reached then the first items are overwritten. +sim_particle_emissioncolor =0x4000 # When used in combination with sim_particle_itemcolors then the specified colors will be for the emissive component +sim_particle_water =0x8000 # the particles are water particles (no weight in the water (i.e. when z<0)). Not compatible with sim_particle_ignoresgravity +sim_particle_painttag =0x10000 # The particles can be seen by vision sensors (sim_particle_invisible must not be set) + + + + +# custom user interface menu attributes +sim_ui_menu_title =1 +sim_ui_menu_minimize =2 +sim_ui_menu_close =4 +sim_ui_menu_systemblock =8 + + + +# Boolean parameters +sim_boolparam_hierarchy_visible =0 +sim_boolparam_console_visible =1 +sim_boolparam_collision_handling_enabled =2 +sim_boolparam_distance_handling_enabled =3 +sim_boolparam_ik_handling_enabled =4 +sim_boolparam_gcs_handling_enabled =5 +sim_boolparam_dynamics_handling_enabled =6 +sim_boolparam_joint_motion_handling_enabled =7 +sim_boolparam_path_motion_handling_enabled =8 +sim_boolparam_proximity_sensor_handling_enabled =9 +sim_boolparam_vision_sensor_handling_enabled =10 +sim_boolparam_mill_handling_enabled =11 +sim_boolparam_browser_visible =12 +sim_boolparam_scene_and_model_load_messages =13 +sim_reserved0 =14 +sim_boolparam_shape_textures_are_visible =15 +sim_boolparam_display_enabled =16 +sim_boolparam_infotext_visible =17 +sim_boolparam_statustext_open =18 +sim_boolparam_fog_enabled =19 +sim_boolparam_rml2_available =20 +sim_boolparam_rml4_available =21 +sim_boolparam_mirrors_enabled =22 +sim_boolparam_aux_clip_planes_enabled =23 +sim_boolparam_full_model_copy_from_api =24 +sim_boolparam_realtime_simulation =25 +sim_boolparam_force_show_wireless_emission =27 +sim_boolparam_force_show_wireless_reception =28 +sim_boolparam_video_recording_triggered =29 +sim_boolparam_threaded_rendering_enabled =32 +sim_boolparam_fullscreen =33 +sim_boolparam_headless =34 +sim_boolparam_hierarchy_toolbarbutton_enabled =35 +sim_boolparam_browser_toolbarbutton_enabled =36 +sim_boolparam_objectshift_toolbarbutton_enabled =37 +sim_boolparam_objectrotate_toolbarbutton_enabled=38 +sim_boolparam_force_calcstruct_all_visible =39 +sim_boolparam_force_calcstruct_all =40 +sim_boolparam_exit_request =41 +sim_boolparam_play_toolbarbutton_enabled =42 +sim_boolparam_pause_toolbarbutton_enabled =43 +sim_boolparam_stop_toolbarbutton_enabled =44 +sim_boolparam_waiting_for_trigger =45 + + +# Integer parameters +sim_intparam_error_report_mode =0 # Check sim_api_errormessage_... constants above for valid values +sim_intparam_program_version =1 # e.g Version 2.1.4 --> 20104. Can only be read +sim_intparam_instance_count =2 # do not use anymore (always returns 1 since V-REP 2.5.11) +sim_intparam_custom_cmd_start_id =3 # can only be read +sim_intparam_compilation_version =4 # 0=evaluation version 1=full version 2=player version. Can only be read +sim_intparam_current_page =5 +sim_intparam_flymode_camera_handle =6 # can only be read +sim_intparam_dynamic_step_divider =7 # can only be read +sim_intparam_dynamic_engine =8 # 0=Bullet 1=ODE. 2=Vortex. +sim_intparam_server_port_start =9 # can only be read +sim_intparam_server_port_range =10 # can only be read +sim_intparam_visible_layers =11 +sim_intparam_infotext_style =12 +sim_intparam_settings =13 +sim_intparam_edit_mode_type =14 # can only be read +sim_intparam_server_port_next =15 # is initialized at sim_intparam_server_port_start +sim_intparam_qt_version =16 # version of the used Qt framework +sim_intparam_event_flags_read =17 # can only be read +sim_intparam_event_flags_read_clear =18 # can only be read +sim_intparam_platform =19 # can only be read +sim_intparam_scene_unique_id =20 # can only be read +sim_intparam_work_thread_count =21 +sim_intparam_mouse_x =22 +sim_intparam_mouse_y =23 +sim_intparam_core_count =24 +sim_intparam_work_thread_calc_time_ms =25 +sim_intparam_idle_fps =26 +sim_intparam_prox_sensor_select_down =27 +sim_intparam_prox_sensor_select_up =28 +sim_intparam_stop_request_counter =29 +sim_intparam_program_revision =30 +sim_intparam_mouse_buttons =31 +sim_intparam_dynamic_warning_disabled_mask =32 +sim_intparam_simulation_warning_disabled_mask =33 +sim_intparam_scene_index =34 +sim_intparam_motionplanning_seed =35 +sim_intparam_speedmodifier =36 + +# Float parameters +sim_floatparam_rand=0 # random value (0.0-1.0) +sim_floatparam_simulation_time_step =1 +sim_floatparam_stereo_distance =2 + +# String parameters +sim_stringparam_application_path=0 # path of V-REP's executable +sim_stringparam_video_filename=1 +sim_stringparam_app_arg1 =2 +sim_stringparam_app_arg2 =3 +sim_stringparam_app_arg3 =4 +sim_stringparam_app_arg4 =5 +sim_stringparam_app_arg5 =6 +sim_stringparam_app_arg6 =7 +sim_stringparam_app_arg7 =8 +sim_stringparam_app_arg8 =9 +sim_stringparam_app_arg9 =10 +sim_stringparam_scene_path_and_name =13 + +# Array parameters +sim_arrayparam_gravity =0 +sim_arrayparam_fog =1 +sim_arrayparam_fog_color =2 +sim_arrayparam_background_color1=3 +sim_arrayparam_background_color2=4 +sim_arrayparam_ambient_light =5 +sim_arrayparam_random_euler =6 + + +# User interface elements +sim_gui_menubar =0x0001 +sim_gui_popups =0x0002 +sim_gui_toolbar1 =0x0004 +sim_gui_toolbar2 =0x0008 +sim_gui_hierarchy =0x0010 +sim_gui_infobar =0x0020 +sim_gui_statusbar =0x0040 +sim_gui_scripteditor =0x0080 +sim_gui_scriptsimulationparameters =0x0100 +sim_gui_dialogs =0x0200 +sim_gui_browser =0x0400 +sim_gui_all =0xffff + + +# Joint modes +sim_jointmode_passive =0 +sim_jointmode_motion =1 +sim_jointmode_ik =2 +sim_jointmode_ikdependent =3 +sim_jointmode_dependent =4 +sim_jointmode_force =5 + + +# Navigation and selection modes with the mouse. Lower byte values are mutually exclusive upper byte bits can be combined +sim_navigation_passive =0x0000 +sim_navigation_camerashift =0x0001 +sim_navigation_camerarotate =0x0002 +sim_navigation_camerazoom =0x0003 +sim_navigation_cameratilt =0x0004 +sim_navigation_cameraangle =0x0005 +sim_navigation_camerafly =0x0006 +sim_navigation_objectshift =0x0007 +sim_navigation_objectrotate =0x0008 +sim_navigation_reserved2 =0x0009 +sim_navigation_reserved3 =0x000A +sim_navigation_jointpathtest =0x000B +sim_navigation_ikmanip =0x000C +sim_navigation_objectmultipleselection =0x000D +# Bit-combine following values and add them to one of above's values for a valid navigation mode +sim_navigation_reserved4 =0x0100 +sim_navigation_clickselection =0x0200 +sim_navigation_ctrlselection =0x0400 +sim_navigation_shiftselection =0x0800 +sim_navigation_camerazoomwheel =0x1000 +sim_navigation_camerarotaterightbutton =0x2000 + + + +#Remote API constants +SIMX_VERSION =0 +# Remote API message header structure +SIMX_HEADER_SIZE =18 +simx_headeroffset_crc =0 # 1 simxUShort. Generated by the client or server. The CRC for the message +simx_headeroffset_version =2 # 1 byte. Generated by the client or server. The version of the remote API software +simx_headeroffset_message_id =3 # 1 simxInt. Generated by the client (and used in a reply by the server) +simx_headeroffset_client_time =7 # 1 simxInt. Client time stamp generated by the client (and sent back by the server) +simx_headeroffset_server_time =11 # 1 simxInt. Generated by the server when a reply is generated. The server timestamp +simx_headeroffset_scene_id =15 # 1 simxUShort. Generated by the server. A unique ID identifying the scene currently displayed +simx_headeroffset_server_state =17 # 1 byte. Generated by the server. Bit coded 0 set --> simulation not stopped 1 set --> simulation paused 2 set --> real-time switch on 3-5 edit mode type (0=no edit mode 1=triangle 2=vertex 3=edge 4=path 5=UI) + +# Remote API command header +SIMX_SUBHEADER_SIZE =26 +simx_cmdheaderoffset_mem_size =0 # 1 simxInt. Generated by the client or server. The buffer size of the command. +simx_cmdheaderoffset_full_mem_size =4 # 1 simxInt. Generated by the client or server. The full buffer size of the command (applies to split chunks). +simx_cmdheaderoffset_pdata_offset0 =8 # 1 simxUShort. Generated by the client or server. The amount of data that is part of the command identification. +simx_cmdheaderoffset_pdata_offset1 =10 # 1 simxInt. Generated by the client or server. The amount of shift of the pure data buffer (applies to split chunks). +simx_cmdheaderoffset_cmd=14 # 1 simxInt. Generated by the client (and used in a reply by the server). The command combined with the operation mode of the command. +simx_cmdheaderoffset_delay_or_split =18 # 1 simxUShort. Generated by the client or server. The amount of delay in ms of a continuous command or the max. pure data size to send at once (applies to split commands). +simx_cmdheaderoffset_sim_time =20 # 1 simxInt. Generated by the server. The simulation time (in ms) when the command was executed (or 0 if simulation is not running) +simx_cmdheaderoffset_status =24 # 1 byte. Generated by the server. (1 bit 0 is set --> error in function execution on server side). The client writes bit 1 if command cannot be overwritten +simx_cmdheaderoffset_reserved =25 # 1 byte. Not yet used + + + + + +# Regular operation modes +simx_opmode_oneshot =0x000000 # sends command as one chunk. Reply will also come as one chunk. Doesn't wait for the reply. +simx_opmode_blocking =0x010000 # sends command as one chunk. Reply will also come as one chunk. Waits for the reply (_REPLY_WAIT_TIMEOUT_IN_MS is the timeout). +simx_opmode_oneshot_wait =0x010000 # sends command as one chunk. Reply will also come as one chunk. Waits for the reply (_REPLY_WAIT_TIMEOUT_IN_MS is the timeout). +simx_opmode_continuous =0x020000 +simx_opmode_streaming =0x020000 # sends command as one chunk. Command will be stored on the server and always executed + #(every x ms (as far as possible) where x can be 0-65535. just add x to opmode_continuous). + # A reply will be sent continuously each time as one chunk. Doesn't wait for the reply. + +# Operation modes for heavy data +simx_opmode_oneshot_split =0x030000 # sends command as several chunks (max chunk size is x bytes where x can be _MIN_SPLIT_AMOUNT_IN_BYTES-65535. Just add x to opmode_oneshot_split). Reply will also come as several chunks. Doesn't wait for the reply. +simx_opmode_continuous_split =0x040000 +simx_opmode_streaming_split =0x040000 # sends command as several chunks (max chunk size is x bytes where x can be _MIN_SPLIT_AMOUNT_IN_BYTES-65535. Just add x to opmode_continuous_split). Command will be stored on the server and always executed. A reply will be sent continuously each time as several chunks. Doesn't wait for the reply. + +# Special operation modes +simx_opmode_discontinue =0x050000 # removes and cancels all commands stored on the client or server side (also continuous commands) +simx_opmode_buffer =0x060000 # doesn't send anything but checks if a reply for the given command is available in the input buffer (i.e. previously received from the server) +simx_opmode_remove =0x070000 # doesn't send anything and doesn't return any specific value. It just erases a similar command reply in the inbox (to free some memory) + + +# Command return codes +simx_return_ok =0x000000 +simx_return_novalue_flag =0x000001 # input buffer doesn't contain the specified command +simx_return_timeout_flag =0x000002 # command reply not received in time for opmode_oneshot_wait operation mode +simx_return_illegal_opmode_flag =0x000004 # command doesn't support the specified operation mode +simx_return_remote_error_flag =0x000008 # command caused an error on the server side +simx_return_split_progress_flag =0x000010 # previous similar command not yet fully processed (applies to opmode_oneshot_split operation modes) +simx_return_local_error_flag =0x000020 # command caused an error on the client side +simx_return_initialize_error_flag =0x000040 # simxStart was not yet called + +# Following for backward compatibility (same as above) +simx_error_noerror =0x000000 +simx_error_novalue_flag =0x000001 # input buffer doesn't contain the specified command +simx_error_timeout_flag =0x000002 # command reply not received in time for opmode_oneshot_wait operation mode +simx_error_illegal_opmode_flag =0x000004 # command doesn't support the specified operation mode +simx_error_remote_error_flag =0x000008 # command caused an error on the server side +simx_error_split_progress_flag =0x000010 # previous similar command not yet fully processed (applies to opmode_oneshot_split operation modes) +simx_error_local_error_flag =0x000020 # command caused an error on the client side +simx_error_initialize_error_flag =0x000040 # simxStart was not yet called + + diff --git a/micropsi_core/world/iiwasim/iiwasim.py b/micropsi_core/world/vrep_world/vrep_world.py similarity index 96% rename from micropsi_core/world/iiwasim/iiwasim.py rename to micropsi_core/world/vrep_world/vrep_world.py index ceacf639..abfa2994 100644 --- a/micropsi_core/world/iiwasim/iiwasim.py +++ b/micropsi_core/world/vrep_world/vrep_world.py @@ -5,25 +5,23 @@ import matplotlib.image as mpimg import matplotlib.pyplot as plt -from micropsi_core.world.iiwasim import vrep -from micropsi_core.world.iiwasim import vrepConst +from micropsi_core.world.vrep_world import vrep +from micropsi_core.world.vrep_world import vrepConst from micropsi_core.world.world import World from micropsi_core.world.worldadapter import ArrayWorldAdapter -class iiwasim(World): - """ A simulated KUKA iiwa, using the vrep robot simulator - +class vrep_world(World): + """ A vrep robot simulator environment In V-REP, the following setup has to be performed: - An LBR_iiwa_7_R800 has to have been added to the scene - simExtRemoteApiStart(19999) has to have been run - the simulation must have been started - """ supported_worldadapters = ['iiwa'] - def __init__(self, filename, world_type="iiwasim", name="", owner="", engine=None, uid=None, version=1, config={}): + def __init__(self, filename, world_type="vrep_world", name="", owner="", engine=None, uid=None, version=1, config={}): World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version) vrep.simxFinish(-1) # just in case, close all opened connections diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 72c68453..c4a67ea0 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -366,6 +366,6 @@ def __del__(self): try: - from micropsi_core.world.iiwasim import iiwasim + from micropsi_core.world.vrep_world import vrep_world except ImportError as e: - sys.stdout.write("Could not import iiwasim world.\nError: %s \n\n" % e.msg) + sys.stdout.write("Could not import vrep world.\nError: %s \n\n" % e.msg) From d49d0d9540d13b5440852acf21e957d88a4f78bc Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 5 May 2016 17:45:53 +0200 Subject: [PATCH 059/945] have some fundamental vrep frontend --- micropsi_core/world/vrep_world/vrep_world.py | 33 ++++++++++++++--- micropsi_core/world/worldadapter.py | 2 ++ micropsi_server/static/vrep/vrep.js | 38 ++++++++++++++++++++ micropsi_server/static/vrep/vrep.tpl | 7 ++++ 4 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 micropsi_server/static/vrep/vrep.js create mode 100644 micropsi_server/static/vrep/vrep.tpl diff --git a/micropsi_core/world/vrep_world/vrep_world.py b/micropsi_core/world/vrep_world/vrep_world.py index abfa2994..763b6e41 100644 --- a/micropsi_core/world/vrep_world/vrep_world.py +++ b/micropsi_core/world/vrep_world/vrep_world.py @@ -2,9 +2,14 @@ import time import logging import numpy as np +import matplotlib +matplotlib.use('agg') import matplotlib.image as mpimg import matplotlib.pyplot as plt +from io import BytesIO +import base64 + from micropsi_core.world.vrep_world import vrep from micropsi_core.world.vrep_world import vrepConst from micropsi_core.world.world import World @@ -19,6 +24,11 @@ class vrep_world(World): - the simulation must have been started """ + assets = { + 'template': 'vrep/vrep.tpl', + 'js': "vrep/vrep.js", + } + supported_worldadapters = ['iiwa'] def __init__(self, filename, world_type="vrep_world", name="", owner="", engine=None, uid=None, version=1, config={}): @@ -80,6 +90,22 @@ def handle_res(self, res): error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) + def get_world_view(self, step): + plots = {} + for uid in self.agents: + image = self.agents[uid].image + if image: + bio = BytesIO() + image.figure.savefig(bio, format="png") + plots[uid] = base64.encodebytes(bio.getvalue()).decode("utf-8") + + return { + 'objects': self.get_world_objects(), + 'agents': self.data.get('agents', {}), + 'current_step': self.current_step, + 'plots': plots + } + class iiwa(ArrayWorldAdapter): @@ -181,12 +207,11 @@ def update_data_sources_and_targets(self): return res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) - rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0]*self.world.vision_resolution[1], 3)).astype(np.float32) + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0] * self.world.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.world.vision_resolution[0],self.world.vision_resolution[1]))[::-1,:] # todo: npyify and make faster - + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.world.vision_resolution[0], self.world.vision_resolution[1]))[::-1,:] # todo: npyify and make faster self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() self.image.set_data(y_image) - plt.savefig("/tmp/out.png") + return self.image diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 073e56f6..1a4a9d8d 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -37,6 +37,8 @@ def __init__(self, world, uid=None, **data): self.datatarget_feedback = {} self.datasource_lock = Lock() WorldObject.__init__(self, world, category='agents', uid=uid, **data) + if data.get('name'): + self.data['name'] = data['name'] def initialize_worldobject(self, data): for key in self.datasources: diff --git a/micropsi_server/static/vrep/vrep.js b/micropsi_server/static/vrep/vrep.js new file mode 100644 index 00000000..fd28d6ba --- /dev/null +++ b/micropsi_server/static/vrep/vrep.js @@ -0,0 +1,38 @@ + + +$(function(){ + + var container = $('#vrep_world'); + + var view = $('#vrep_world_view'); + + var initialized = false; + + function get_world_data(){ + return {step: currentWorldSimulationStep}; + } + + $('.section.world .editor_field').height('auto'); + + function set_world_data(data){ + var agent_html = ''; + for(var uid in data.agents){ + agent_html += '

    ' + data.agents[uid].name + ' ('+data.agents[uid].type+')'; + if(uid in data.plots){ + agent_html += '
    '; + } + agent_html += '

    ' + + } + view.html(agent_html); + } + + function get_world_state(){ + api.call('get_world_view', {'world_uid': currentWorld, 'step': 0}, set_world_data); + } + + register_stepping_function('world', get_world_data, set_world_data); + + get_world_state(); + +}); diff --git a/micropsi_server/static/vrep/vrep.tpl b/micropsi_server/static/vrep/vrep.tpl new file mode 100644 index 00000000..d5266b8c --- /dev/null +++ b/micropsi_server/static/vrep/vrep.tpl @@ -0,0 +1,7 @@ +
    +
    +
    + +
    +
    +
    From e4e4810f866519077cf669d1d06a7f0b4a172875 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 5 May 2016 17:46:43 +0200 Subject: [PATCH 060/945] fix world saving --- micropsi_core/_runtime_api_world.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index c8a088ed..58249f11 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -181,9 +181,10 @@ def revert_world(world_uid): def save_world(world_uid): """Stores the world state on the server.""" - with open(os.path.join(micropsi_core.runtime.PERSISTENCY_PATH, micropsi_core.runtime.WORLD_DIRECTORY, - world_uid) + '.json', 'w+') as fp: - fp.write(json.dumps(micropsi_core.runtime.worlds[world_uid].data, sort_keys=True, indent=4)) + data = micropsi_core.runtime.worlds[world_uid].data + filename = os.path.join(micropsi_core.runtime.PERSISTENCY_PATH, micropsi_core.runtime.WORLD_DIRECTORY, world_uid) + with open(filename + '.json', 'w+') as fp: + fp.write(json.dumps(data, sort_keys=True, indent=4)) return True From 7a76417d31109213d5489b13e05c5b5c91551407 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 5 May 2016 17:46:55 +0200 Subject: [PATCH 061/945] remove typo --- micropsi_core/world/worldadapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 1a4a9d8d..9dc5e918 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -134,7 +134,7 @@ class ArrayWorldAdapter(WorldAdapter, metaclass=ABCMeta): Numpy arrays can be passed directly into the engine. """ def __init__(self, world, uid=None, **data): - WorldAdapter.__init__(self, world, duid=uid) + WorldAdapter.__init__(self, world, uid=uid) self.datasource_values = [] self.datatarget_values = [] self.datatarget_feedback_values = [] From c004b01ad635a544e28e18ce1f818343528f69cb Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 5 May 2016 17:46:55 +0200 Subject: [PATCH 062/945] remove typo --- micropsi_core/world/worldadapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 073e56f6..630eb6ac 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -132,7 +132,7 @@ class ArrayWorldAdapter(WorldAdapter, metaclass=ABCMeta): Numpy arrays can be passed directly into the engine. """ def __init__(self, world, uid=None, **data): - WorldAdapter.__init__(self, world, duid=uid) + WorldAdapter.__init__(self, world, uid=uid) self.datasource_values = [] self.datatarget_values = [] self.datatarget_feedback_values = [] From 6ff3fb98fec0b7dbb3b4bff122518622b9517709 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 5 May 2016 17:46:43 +0200 Subject: [PATCH 063/945] fix world saving --- micropsi_core/_runtime_api_world.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index c8a088ed..58249f11 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -181,9 +181,10 @@ def revert_world(world_uid): def save_world(world_uid): """Stores the world state on the server.""" - with open(os.path.join(micropsi_core.runtime.PERSISTENCY_PATH, micropsi_core.runtime.WORLD_DIRECTORY, - world_uid) + '.json', 'w+') as fp: - fp.write(json.dumps(micropsi_core.runtime.worlds[world_uid].data, sort_keys=True, indent=4)) + data = micropsi_core.runtime.worlds[world_uid].data + filename = os.path.join(micropsi_core.runtime.PERSISTENCY_PATH, micropsi_core.runtime.WORLD_DIRECTORY, world_uid) + with open(filename + '.json', 'w+') as fp: + fp.write(json.dumps(data, sort_keys=True, indent=4)) return True From 9bb6df38cd891b31b9141f01f8d506ce6470621c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 5 May 2016 18:03:48 +0200 Subject: [PATCH 064/945] rename again --- .../world/{vrep_world => vrep}/vrep_world.py | 8 +- .../world/vrep_world/remoteApi.dylib | Bin 99260 -> 0 bytes micropsi_core/world/vrep_world/vrep.py | 1482 ----------------- micropsi_core/world/vrep_world/vrepConst.py | 664 -------- micropsi_core/world/world.py | 2 +- 5 files changed, 5 insertions(+), 2151 deletions(-) rename micropsi_core/world/{vrep_world => vrep}/vrep_world.py (97%) delete mode 100755 micropsi_core/world/vrep_world/remoteApi.dylib delete mode 100755 micropsi_core/world/vrep_world/vrep.py delete mode 100755 micropsi_core/world/vrep_world/vrepConst.py diff --git a/micropsi_core/world/vrep_world/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py similarity index 97% rename from micropsi_core/world/vrep_world/vrep_world.py rename to micropsi_core/world/vrep/vrep_world.py index 763b6e41..a69762d5 100644 --- a/micropsi_core/world/vrep_world/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -10,13 +10,13 @@ from io import BytesIO import base64 -from micropsi_core.world.vrep_world import vrep -from micropsi_core.world.vrep_world import vrepConst +from micropsi_core.world.vrep import vrep +from micropsi_core.world.vrep import vrepConst from micropsi_core.world.world import World from micropsi_core.world.worldadapter import ArrayWorldAdapter -class vrep_world(World): +class VREPWorld(World): """ A vrep robot simulator environment In V-REP, the following setup has to be performed: - An LBR_iiwa_7_R800 has to have been added to the scene @@ -31,7 +31,7 @@ class vrep_world(World): supported_worldadapters = ['iiwa'] - def __init__(self, filename, world_type="vrep_world", name="", owner="", engine=None, uid=None, version=1, config={}): + def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=None, uid=None, version=1, config={}): World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version) vrep.simxFinish(-1) # just in case, close all opened connections diff --git a/micropsi_core/world/vrep_world/remoteApi.dylib b/micropsi_core/world/vrep_world/remoteApi.dylib deleted file mode 100755 index c2a502be377778e0667f9dda30adb0c75aa38d10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99260 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 V0Vi%$(18p3NegXG;lN_R{{t0>C@25` diff --git a/micropsi_core/world/vrep_world/vrep.py b/micropsi_core/world/vrep_world/vrep.py deleted file mode 100755 index e8d307da..00000000 --- a/micropsi_core/world/vrep_world/vrep.py +++ /dev/null @@ -1,1482 +0,0 @@ -# This file is part of the REMOTE API -# -# Copyright 2006-2016 Coppelia Robotics GmbH. All rights reserved. -# marc@coppeliarobotics.com -# www.coppeliarobotics.com -# -# The REMOTE API is licensed under the terms of GNU GPL: -# -# ------------------------------------------------------------------- -# The REMOTE API is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# THE REMOTE API IS DISTRIBUTED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED -# WARRANTY. THE USER WILL USE IT AT HIS/HER OWN RISK. THE ORIGINAL -# AUTHORS AND COPPELIA ROBOTICS GMBH WILL NOT BE LIABLE FOR DATA LOSS, -# DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING OR -# MISUSING THIS SOFTWARE. -# -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with the REMOTE API. If not, see . -# ------------------------------------------------------------------- -# -# This file was automatically created for V-REP release V3.3.0 on February 19th 2016 - -import platform -import struct -import sys -import ctypes as ct -import os -from .vrepConst import * - -#load library -libsimx = None -try: - if platform.system() =='cli': - libsimx = ct.CDLL("remoteApi.dll") - elif platform.system() =='Windows': - libsimx = ct.CDLL("remoteApi.dll") - elif platform.system() == 'Darwin': - libsimx = ct.CDLL(os.path.abspath(os.path.join(__file__, '../remoteApi.dylib'), )) - else: - libsimx = ct.CDLL("remoteApi.so") -except: - print ('----------------------------------------------------') - print ('The remoteApi library could not be loaded. Make sure') - print ('it is located in the same folder as "vrep.py", or') - print ('appropriately adjust the file "vrep.py"') - print ('----------------------------------------------------') - print ('') - -#ctypes wrapper prototypes -c_GetJointPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetJointPosition", libsimx)) -c_SetJointPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointPosition", libsimx)) -c_GetJointMatrix = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetJointMatrix", libsimx)) -c_SetSphericalJointMatrix = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetSphericalJointMatrix", libsimx)) -c_SetJointTargetVelocity = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointTargetVelocity", libsimx)) -c_SetJointTargetPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointTargetPosition", libsimx)) -c_GetJointForce = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetJointForce", libsimx)) -c_SetJointForce = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetJointForce", libsimx)) -c_ReadForceSensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.c_int32)(("simxReadForceSensor", libsimx)) -c_BreakForceSensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxBreakForceSensor", libsimx)) -c_ReadVisionSensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.POINTER(ct.c_float)), ct.POINTER(ct.POINTER(ct.c_int32)), ct.c_int32)(("simxReadVisionSensor", libsimx)) -c_GetObjectHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectHandle", libsimx)) -c_GetVisionSensorImage = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_byte)), ct.c_ubyte, ct.c_int32)(("simxGetVisionSensorImage", libsimx)) -c_SetVisionSensorImage = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_byte), ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxSetVisionSensorImage", libsimx)) -c_GetVisionSensorDepthBuffer= ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_float)), ct.c_int32)(("simxGetVisionSensorDepthBuffer", libsimx)) -c_GetObjectChild = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectChild", libsimx)) -c_GetObjectParent = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectParent", libsimx)) -c_ReadProximitySensor = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.c_float), ct.POINTER(ct.c_int32), ct.POINTER(ct.c_float), ct.c_int32)(("simxReadProximitySensor", libsimx)) -c_LoadModel = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_ubyte, ct.POINTER(ct.c_int32), ct.c_int32)(("simxLoadModel", libsimx)) -c_LoadUI = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_ubyte, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.c_int32)(("simxLoadUI", libsimx)) -c_LoadScene = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_ubyte, ct.c_int32)(("simxLoadScene", libsimx)) -c_StartSimulation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxStartSimulation", libsimx)) -c_PauseSimulation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxPauseSimulation", libsimx)) -c_StopSimulation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxStopSimulation", libsimx)) -c_GetUIHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUIHandle", libsimx)) -c_GetUISlider = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUISlider", libsimx)) -c_SetUISlider = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetUISlider", libsimx)) -c_GetUIEventButton = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUIEventButton", libsimx)) -c_GetUIButtonProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetUIButtonProperty", libsimx)) -c_SetUIButtonProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetUIButtonProperty", libsimx)) -c_AddStatusbarMessage = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxAddStatusbarMessage", libsimx)) -c_AuxiliaryConsoleOpen = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.c_int32), ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.POINTER(ct.c_int32), ct.c_int32)(("simxAuxiliaryConsoleOpen", libsimx)) -c_AuxiliaryConsoleClose = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxAuxiliaryConsoleClose", libsimx)) -c_AuxiliaryConsolePrint = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxAuxiliaryConsolePrint", libsimx)) -c_AuxiliaryConsoleShow = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxAuxiliaryConsoleShow", libsimx)) -c_GetObjectOrientation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectOrientation", libsimx)) -c_GetObjectPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectPosition", libsimx)) -c_SetObjectOrientation = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetObjectOrientation", libsimx)) -c_SetObjectPosition = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetObjectPosition", libsimx)) -c_SetObjectParent = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxSetObjectParent", libsimx)) -c_SetUIButtonLabel = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char), ct.c_int32)(("simxSetUIButtonLabel", libsimx)) -c_GetLastErrors = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetLastErrors", libsimx)) -c_GetArrayParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetArrayParameter", libsimx)) -c_SetArrayParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxSetArrayParameter", libsimx)) -c_GetBooleanParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.c_int32)(("simxGetBooleanParameter", libsimx)) -c_SetBooleanParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_ubyte, ct.c_int32)(("simxSetBooleanParameter", libsimx)) -c_GetIntegerParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetIntegerParameter", libsimx)) -c_SetIntegerParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetIntegerParameter", libsimx)) -c_GetFloatingParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetFloatingParameter", libsimx)) -c_SetFloatingParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetFloatingParameter", libsimx)) -c_GetStringParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetStringParameter", libsimx)) -c_GetCollisionHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetCollisionHandle", libsimx)) -c_GetDistanceHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetDistanceHandle", libsimx)) -c_GetCollectionHandle = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetCollectionHandle", libsimx)) -c_ReadCollision = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_ubyte), ct.c_int32)(("simxReadCollision", libsimx)) -c_ReadDistance = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxReadDistance", libsimx)) -c_RemoveObject = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxRemoveObject", libsimx)) -c_RemoveModel = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxRemoveModel", libsimx)) -c_RemoveUI = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxRemoveUI", libsimx)) -c_CloseScene = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32)(("simxCloseScene", libsimx)) -c_GetObjects = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.c_int32)(("simxGetObjects", libsimx)) -c_DisplayDialog = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char), ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.POINTER(ct.c_int32), ct.POINTER(ct.c_int32), ct.c_int32)(("simxDisplayDialog", libsimx)) -c_EndDialog = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32)(("simxEndDialog", libsimx)) -c_GetDialogInput = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetDialogInput", libsimx)) -c_GetDialogResult = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetDialogResult", libsimx)) -c_CopyPasteObjects = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32, ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxCopyPasteObjects", libsimx)) -c_GetObjectSelection = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectSelection", libsimx)) -c_SetObjectSelection = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32, ct.c_int32)(("simxSetObjectSelection", libsimx)) -c_ClearFloatSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxClearFloatSignal", libsimx)) -c_ClearIntegerSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxClearIntegerSignal", libsimx)) -c_ClearStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxClearStringSignal", libsimx)) -c_GetFloatSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_float), ct.c_int32)(("simxGetFloatSignal", libsimx)) -c_GetIntegerSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetIntegerSignal", libsimx)) -c_GetStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetStringSignal", libsimx)) -c_SetFloatSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_float, ct.c_int32)(("simxSetFloatSignal", libsimx)) -c_SetIntegerSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32, ct.c_int32)(("simxSetIntegerSignal", libsimx)) -c_SetStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.c_int32)(("simxSetStringSignal", libsimx)) -c_AppendStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.c_int32)(("simxAppendStringSignal", libsimx)) -c_WriteStringStream = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.c_int32)(("simxWriteStringStream", libsimx)) -c_GetObjectFloatParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectFloatParameter", libsimx)) -c_SetObjectFloatParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_float, ct.c_int32)(("simxSetObjectFloatParameter", libsimx)) -c_GetObjectIntParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetObjectIntParameter", libsimx)) -c_SetObjectIntParameter = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetObjectIntParameter", libsimx)) -c_GetModelProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetModelProperty", libsimx)) -c_SetModelProperty = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32)(("simxSetModelProperty", libsimx)) -c_Start = ct.CFUNCTYPE(ct.c_int32,ct.POINTER(ct.c_char), ct.c_int32, ct.c_ubyte, ct.c_ubyte, ct.c_int32, ct.c_int32)(("simxStart", libsimx)) -c_Finish = ct.CFUNCTYPE(None, ct.c_int32)(("simxFinish", libsimx)) -c_GetPingTime = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_int32))(("simxGetPingTime", libsimx)) -c_GetLastCmdTime = ct.CFUNCTYPE(ct.c_int32,ct.c_int32)(("simxGetLastCmdTime", libsimx)) -c_SynchronousTrigger = ct.CFUNCTYPE(ct.c_int32,ct.c_int32)(("simxSynchronousTrigger", libsimx)) -c_Synchronous = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_ubyte)(("simxSynchronous", libsimx)) -c_PauseCommunication = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_ubyte)(("simxPauseCommunication", libsimx)) -c_GetInMessageInfo = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32))(("simxGetInMessageInfo", libsimx)) -c_GetOutMessageInfo = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32))(("simxGetOutMessageInfo", libsimx)) -c_GetConnectionId = ct.CFUNCTYPE(ct.c_int32,ct.c_int32)(("simxGetConnectionId", libsimx)) -c_CreateBuffer = ct.CFUNCTYPE(ct.POINTER(ct.c_ubyte), ct.c_int32)(("simxCreateBuffer", libsimx)) -c_ReleaseBuffer = ct.CFUNCTYPE(None, ct.c_void_p)(("simxReleaseBuffer", libsimx)) -c_TransferFile = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char), ct.c_int32, ct.c_int32)(("simxTransferFile", libsimx)) -c_EraseFile = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.c_int32)(("simxEraseFile", libsimx)) -c_GetAndClearStringSignal = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxGetAndClearStringSignal", libsimx)) -c_ReadStringStream = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxReadStringStream", libsimx)) -c_CreateDummy = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_float, ct.POINTER(ct.c_ubyte), ct.POINTER(ct.c_int32), ct.c_int32)(("simxCreateDummy", libsimx)) -c_Query = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.c_ubyte), ct.c_int32, ct.POINTER(ct.c_char), ct.POINTER(ct.POINTER(ct.c_ubyte)), ct.POINTER(ct.c_int32), ct.c_int32)(("simxQuery", libsimx)) -c_GetObjectGroupData = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.c_int32, ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)), ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_float)), ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_char)), ct.c_int32)(("simxGetObjectGroupData", libsimx)) -c_GetObjectVelocity = ct.CFUNCTYPE(ct.c_int32,ct.c_int32, ct.c_int32, ct.POINTER(ct.c_float), ct.POINTER(ct.c_float), ct.c_int32)(("simxGetObjectVelocity", libsimx)) -c_CallScriptFunction = ct.CFUNCTYPE(ct.c_int32,ct.c_int32,ct.POINTER(ct.c_char),ct.c_int32,ct.POINTER(ct.c_char),ct.c_int32,ct.POINTER(ct.c_int32),ct.c_int32,ct.POINTER(ct.c_float),ct.c_int32,ct.POINTER(ct.c_char),ct.c_int32,ct.POINTER(ct.c_ubyte),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_int32)),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_float)),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_char)),ct.POINTER(ct.c_int32), ct.POINTER(ct.POINTER(ct.c_ubyte)),ct.c_int32)(("simxCallScriptFunction", libsimx)) - -#API functions -def simxGetJointPosition(clientID, jointHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - position = ct.c_float() - return c_GetJointPosition(clientID, jointHandle, ct.byref(position), operationMode), position.value - -def simxSetJointPosition(clientID, jointHandle, position, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetJointPosition(clientID, jointHandle, position, operationMode) - -def simxGetJointMatrix(clientID, jointHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - matrix = (ct.c_float*12)() - ret = c_GetJointMatrix(clientID, jointHandle, matrix, operationMode) - arr = [] - for i in range(12): - arr.append(matrix[i]) - return ret, arr - -def simxSetSphericalJointMatrix(clientID, jointHandle, matrix, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - matrix = (ct.c_float*12)(*matrix) - return c_SetSphericalJointMatrix(clientID, jointHandle, matrix, operationMode) - -def simxSetJointTargetVelocity(clientID, jointHandle, targetVelocity, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetJointTargetVelocity(clientID, jointHandle, targetVelocity, operationMode) - -def simxSetJointTargetPosition(clientID, jointHandle, targetPosition, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetJointTargetPosition(clientID, jointHandle, targetPosition, operationMode) - -def simxJointGetForce(clientID, jointHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - force = ct.c_float() - return c_GetJointForce(clientID, jointHandle, ct.byref(force), operationMode), force.value - -def simxGetJointForce(clientID, jointHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - force = ct.c_float() - return c_GetJointForce(clientID, jointHandle, ct.byref(force), operationMode), force.value - -def simxSetJointForce(clientID, jointHandle, force, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - return c_SetJointForce(clientID, jointHandle, force, operationMode) - -def simxReadForceSensor(clientID, forceSensorHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - state = ct.c_ubyte() - forceVector = (ct.c_float*3)() - torqueVector = (ct.c_float*3)() - ret = c_ReadForceSensor(clientID, forceSensorHandle, ct.byref(state), forceVector, torqueVector, operationMode) - arr1 = [] - for i in range(3): - arr1.append(forceVector[i]) - arr2 = [] - for i in range(3): - arr2.append(torqueVector[i]) - if sys.version_info[0] == 3: - state=state.value - else: - state=ord(state.value) - return ret, state, arr1, arr2 - -def simxBreakForceSensor(clientID, forceSensorHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - return c_BreakForceSensor(clientID, forceSensorHandle, operationMode) - -def simxReadVisionSensor(clientID, sensorHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - detectionState = ct.c_ubyte() - auxValues = ct.POINTER(ct.c_float)() - auxValuesCount = ct.POINTER(ct.c_int)() - ret = c_ReadVisionSensor(clientID, sensorHandle, ct.byref(detectionState), ct.byref(auxValues), ct.byref(auxValuesCount), operationMode) - - auxValues2 = [] - if ret == 0: - s = 0 - for i in range(auxValuesCount[0]): - auxValues2.append(auxValues[s:s+auxValuesCount[i+1]]) - s += auxValuesCount[i+1] - - #free C buffers - c_ReleaseBuffer(auxValues) - c_ReleaseBuffer(auxValuesCount) - - return ret, bool(detectionState.value!=0), auxValues2 - -def simxGetObjectHandle(clientID, objectName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - handle = ct.c_int() - if (sys.version_info[0] == 3) and (type(objectName) is str): - objectName=objectName.encode('utf-8') - return c_GetObjectHandle(clientID, objectName, ct.byref(handle), operationMode), handle.value - -def simxGetVisionSensorImage(clientID, sensorHandle, options, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - resolution = (ct.c_int*2)() - c_image = ct.POINTER(ct.c_byte)() - bytesPerPixel = 3 - if (options and 1) != 0: - bytesPerPixel = 1 - ret = c_GetVisionSensorImage(clientID, sensorHandle, resolution, ct.byref(c_image), options, operationMode) - - reso = [] - image = [] - if (ret == 0): - image = [None]*resolution[0]*resolution[1]*bytesPerPixel - for i in range(resolution[0] * resolution[1] * bytesPerPixel): - image[i] = c_image[i] - for i in range(2): - reso.append(resolution[i]) - return ret, reso, image - -def simxSetVisionSensorImage(clientID, sensorHandle, image, options, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - size = len(image) - image_bytes = (ct.c_byte*size)(*image) - return c_SetVisionSensorImage(clientID, sensorHandle, image_bytes, size, options, operationMode) - -def simxGetVisionSensorDepthBuffer(clientID, sensorHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - c_buffer = ct.POINTER(ct.c_float)() - resolution = (ct.c_int*2)() - ret = c_GetVisionSensorDepthBuffer(clientID, sensorHandle, resolution, ct.byref(c_buffer), operationMode) - reso = [] - buffer = [] - if (ret == 0): - buffer = [None]*resolution[0]*resolution[1] - for i in range(resolution[0] * resolution[1]): - buffer[i] = c_buffer[i] - for i in range(2): - reso.append(resolution[i]) - return ret, reso, buffer - -def simxGetObjectChild(clientID, parentObjectHandle, childIndex, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - childObjectHandle = ct.c_int() - return c_GetObjectChild(clientID, parentObjectHandle, childIndex, ct.byref(childObjectHandle), operationMode), childObjectHandle.value - -def simxGetObjectParent(clientID, childObjectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - parentObjectHandle = ct.c_int() - return c_GetObjectParent(clientID, childObjectHandle, ct.byref(parentObjectHandle), operationMode), parentObjectHandle.value - -def simxReadProximitySensor(clientID, sensorHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - detectionState = ct.c_ubyte() - detectedObjectHandle = ct.c_int() - detectedPoint = (ct.c_float*3)() - detectedSurfaceNormalVector = (ct.c_float*3)() - ret = c_ReadProximitySensor(clientID, sensorHandle, ct.byref(detectionState), detectedPoint, ct.byref(detectedObjectHandle), detectedSurfaceNormalVector, operationMode) - arr1 = [] - for i in range(3): - arr1.append(detectedPoint[i]) - arr2 = [] - for i in range(3): - arr2.append(detectedSurfaceNormalVector[i]) - return ret, bool(detectionState.value!=0), arr1, detectedObjectHandle.value, arr2 - -def simxLoadModel(clientID, modelPathAndName, options, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - baseHandle = ct.c_int() - if (sys.version_info[0] == 3) and (type(modelPathAndName) is str): - modelPathAndName=modelPathAndName.encode('utf-8') - return c_LoadModel(clientID, modelPathAndName, options, ct.byref(baseHandle), operationMode), baseHandle.value - -def simxLoadUI(clientID, uiPathAndName, options, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - count = ct.c_int() - uiHandles = ct.POINTER(ct.c_int)() - if (sys.version_info[0] == 3) and (type(uiPathAndName) is str): - uiPathAndName=uiPathAndName.encode('utf-8') - ret = c_LoadUI(clientID, uiPathAndName, options, ct.byref(count), ct.byref(uiHandles), operationMode) - - handles = [] - if ret == 0: - for i in range(count.value): - handles.append(uiHandles[i]) - #free C buffers - c_ReleaseBuffer(uiHandles) - - return ret, handles - -def simxLoadScene(clientID, scenePathAndName, options, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(scenePathAndName) is str): - scenePathAndName=scenePathAndName.encode('utf-8') - return c_LoadScene(clientID, scenePathAndName, options, operationMode) - -def simxStartSimulation(clientID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_StartSimulation(clientID, operationMode) - -def simxPauseSimulation(clientID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_PauseSimulation(clientID, operationMode) - -def simxStopSimulation(clientID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_StopSimulation(clientID, operationMode) - -def simxGetUIHandle(clientID, uiName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - handle = ct.c_int() - if (sys.version_info[0] == 3) and (type(uiName) is str): - uiName=uiName.encode('utf-8') - return c_GetUIHandle(clientID, uiName, ct.byref(handle), operationMode), handle.value - -def simxGetUISlider(clientID, uiHandle, uiButtonID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - position = ct.c_int() - return c_GetUISlider(clientID, uiHandle, uiButtonID, ct.byref(position), operationMode), position.value - -def simxSetUISlider(clientID, uiHandle, uiButtonID, position, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetUISlider(clientID, uiHandle, uiButtonID, position, operationMode) - -def simxGetUIEventButton(clientID, uiHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - uiEventButtonID = ct.c_int() - auxValues = (ct.c_int*2)() - ret = c_GetUIEventButton(clientID, uiHandle, ct.byref(uiEventButtonID), auxValues, operationMode) - arr = [] - for i in range(2): - arr.append(auxValues[i]) - return ret, uiEventButtonID.value, arr - -def simxGetUIButtonProperty(clientID, uiHandle, uiButtonID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - prop = ct.c_int() - return c_GetUIButtonProperty(clientID, uiHandle, uiButtonID, ct.byref(prop), operationMode), prop.value - -def simxSetUIButtonProperty(clientID, uiHandle, uiButtonID, prop, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetUIButtonProperty(clientID, uiHandle, uiButtonID, prop, operationMode) - -def simxAddStatusbarMessage(clientID, message, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(message) is str): - message=message.encode('utf-8') - return c_AddStatusbarMessage(clientID, message, operationMode) - -def simxAuxiliaryConsoleOpen(clientID, title, maxLines, mode, position, size, textColor, backgroundColor, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - consoleHandle = ct.c_int() - if (sys.version_info[0] == 3) and (type(title) is str): - title=title.encode('utf-8') - if position != None: - c_position = (ct.c_int*2)(*position) - else: - c_position = None - if size != None: - c_size = (ct.c_int*2)(*size) - else: - c_size = None - if textColor != None: - c_textColor = (ct.c_float*3)(*textColor) - else: - c_textColor = None - if backgroundColor != None: - c_backgroundColor = (ct.c_float*3)(*backgroundColor) - else: - c_backgroundColor = None - return c_AuxiliaryConsoleOpen(clientID, title, maxLines, mode, c_position, c_size, c_textColor, c_backgroundColor, ct.byref(consoleHandle), operationMode), consoleHandle.value - -def simxAuxiliaryConsoleClose(clientID, consoleHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_AuxiliaryConsoleClose(clientID, consoleHandle, operationMode) - -def simxAuxiliaryConsolePrint(clientID, consoleHandle, txt, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(txt) is str): - txt=txt.encode('utf-8') - return c_AuxiliaryConsolePrint(clientID, consoleHandle, txt, operationMode) - -def simxAuxiliaryConsoleShow(clientID, consoleHandle, showState, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_AuxiliaryConsoleShow(clientID, consoleHandle, showState, operationMode) - -def simxGetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - eulerAngles = (ct.c_float*3)() - ret = c_GetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, eulerAngles, operationMode) - arr = [] - for i in range(3): - arr.append(eulerAngles[i]) - return ret, arr - -def simxGetObjectPosition(clientID, objectHandle, relativeToObjectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - position = (ct.c_float*3)() - ret = c_GetObjectPosition(clientID, objectHandle, relativeToObjectHandle, position, operationMode) - arr = [] - for i in range(3): - arr.append(position[i]) - return ret, arr - -def simxSetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, eulerAngles, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - angles = (ct.c_float*3)(*eulerAngles) - return c_SetObjectOrientation(clientID, objectHandle, relativeToObjectHandle, angles, operationMode) - -def simxSetObjectPosition(clientID, objectHandle, relativeToObjectHandle, position, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - c_position = (ct.c_float*3)(*position) - return c_SetObjectPosition(clientID, objectHandle, relativeToObjectHandle, c_position, operationMode) - -def simxSetObjectParent(clientID, objectHandle, parentObject, keepInPlace, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetObjectParent(clientID, objectHandle, parentObject, keepInPlace, operationMode) - -def simxSetUIButtonLabel(clientID, uiHandle, uiButtonID, upStateLabel, downStateLabel, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if sys.version_info[0] == 3: - if type(upStateLabel) is str: - upStateLabel=upStateLabel.encode('utf-8') - if type(downStateLabel) is str: - downStateLabel=downStateLabel.encode('utf-8') - return c_SetUIButtonLabel(clientID, uiHandle, uiButtonID, upStateLabel, downStateLabel, operationMode) - -def simxGetLastErrors(clientID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - errors =[] - errorCnt = ct.c_int() - errorStrings = ct.POINTER(ct.c_char)() - ret = c_GetLastErrors(clientID, ct.byref(errorCnt), ct.byref(errorStrings), operationMode) - if ret == 0: - s = 0 - for i in range(errorCnt.value): - a = bytearray() - while errorStrings[s] != b'\0': - if sys.version_info[0] == 3: - a.append(int.from_bytes(errorStrings[s],'big')) - else: - a.append(errorStrings[s]) - s += 1 - s += 1 #skip null - if sys.version_info[0] == 3: - errors.append(str(a,'utf-8')) - else: - errors.append(str(a)) - - return ret, errors - -def simxGetArrayParameter(clientID, paramIdentifier, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - paramValues = (ct.c_float*3)() - ret = c_GetArrayParameter(clientID, paramIdentifier, paramValues, operationMode) - arr = [] - for i in range(3): - arr.append(paramValues[i]) - return ret, arr - -def simxSetArrayParameter(clientID, paramIdentifier, paramValues, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - c_paramValues = (ct.c_float*3)(*paramValues) - return c_SetArrayParameter(clientID, paramIdentifier, c_paramValues, operationMode) - -def simxGetBooleanParameter(clientID, paramIdentifier, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - paramValue = ct.c_ubyte() - return c_GetBooleanParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode), bool(paramValue.value!=0) - -def simxSetBooleanParameter(clientID, paramIdentifier, paramValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetBooleanParameter(clientID, paramIdentifier, paramValue, operationMode) - -def simxGetIntegerParameter(clientID, paramIdentifier, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - paramValue = ct.c_int() - return c_GetIntegerParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode), paramValue.value - -def simxSetIntegerParameter(clientID, paramIdentifier, paramValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetIntegerParameter(clientID, paramIdentifier, paramValue, operationMode) - -def simxGetFloatingParameter(clientID, paramIdentifier, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - paramValue = ct.c_float() - return c_GetFloatingParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode), paramValue.value - -def simxSetFloatingParameter(clientID, paramIdentifier, paramValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetFloatingParameter(clientID, paramIdentifier, paramValue, operationMode) - -def simxGetStringParameter(clientID, paramIdentifier, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - paramValue = ct.POINTER(ct.c_char)() - ret = c_GetStringParameter(clientID, paramIdentifier, ct.byref(paramValue), operationMode) - - a = bytearray() - if ret == 0: - i = 0 - while paramValue[i] != b'\0': - if sys.version_info[0] == 3: - a.append(int.from_bytes(paramValue[i],'big')) - else: - a.append(paramValue[i]) - i=i+1 - if sys.version_info[0] == 3: - a=str(a,'utf-8') - else: - a=str(a) - return ret, a - -def simxGetCollisionHandle(clientID, collisionObjectName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - handle = ct.c_int() - if (sys.version_info[0] == 3) and (type(collisionObjectName) is str): - collisionObjectName=collisionObjectName.encode('utf-8') - return c_GetCollisionHandle(clientID, collisionObjectName, ct.byref(handle), operationMode), handle.value - -def simxGetCollectionHandle(clientID, collectionName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - handle = ct.c_int() - if (sys.version_info[0] == 3) and (type(collectionName) is str): - collectionName=collectionName.encode('utf-8') - return c_GetCollectionHandle(clientID, collectionName, ct.byref(handle), operationMode), handle.value - -def simxGetDistanceHandle(clientID, distanceObjectName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - handle = ct.c_int() - if (sys.version_info[0] == 3) and (type(distanceObjectName) is str): - distanceObjectName=distanceObjectName.encode('utf-8') - return c_GetDistanceHandle(clientID, distanceObjectName, ct.byref(handle), operationMode), handle.value - -def simxReadCollision(clientID, collisionObjectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - collisionState = ct.c_ubyte() - return c_ReadCollision(clientID, collisionObjectHandle, ct.byref(collisionState), operationMode), bool(collisionState.value!=0) - -def simxReadDistance(clientID, distanceObjectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - minimumDistance = ct.c_float() - return c_ReadDistance(clientID, distanceObjectHandle, ct.byref(minimumDistance), operationMode), minimumDistance.value - -def simxRemoveObject(clientID, objectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_RemoveObject(clientID, objectHandle, operationMode) - -def simxRemoveModel(clientID, objectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_RemoveModel(clientID, objectHandle, operationMode) - -def simxRemoveUI(clientID, uiHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_RemoveUI(clientID, uiHandle, operationMode) - -def simxCloseScene(clientID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_CloseScene(clientID, operationMode) - -def simxGetObjects(clientID, objectType, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - objectCount = ct.c_int() - objectHandles = ct.POINTER(ct.c_int)() - - ret = c_GetObjects(clientID, objectType, ct.byref(objectCount), ct.byref(objectHandles), operationMode) - handles = [] - if ret == 0: - for i in range(objectCount.value): - handles.append(objectHandles[i]) - - return ret, handles - - -def simxDisplayDialog(clientID, titleText, mainText, dialogType, initialText, titleColors, dialogColors, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - if titleColors != None: - c_titleColors = (ct.c_float*6)(*titleColors) - else: - c_titleColors = None - if dialogColors != None: - c_dialogColors = (ct.c_float*6)(*dialogColors) - else: - c_dialogColors = None - - c_dialogHandle = ct.c_int() - c_uiHandle = ct.c_int() - if sys.version_info[0] == 3: - if type(titleText) is str: - titleText=titleText.encode('utf-8') - if type(mainText) is str: - mainText=mainText.encode('utf-8') - if type(initialText) is str: - initialText=initialText.encode('utf-8') - return c_DisplayDialog(clientID, titleText, mainText, dialogType, initialText, c_titleColors, c_dialogColors, ct.byref(c_dialogHandle), ct.byref(c_uiHandle), operationMode), c_dialogHandle.value, c_uiHandle.value - -def simxEndDialog(clientID, dialogHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_EndDialog(clientID, dialogHandle, operationMode) - -def simxGetDialogInput(clientID, dialogHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - inputText = ct.POINTER(ct.c_char)() - ret = c_GetDialogInput(clientID, dialogHandle, ct.byref(inputText), operationMode) - - a = bytearray() - if ret == 0: - i = 0 - while inputText[i] != b'\0': - if sys.version_info[0] == 3: - a.append(int.from_bytes(inputText[i],'big')) - else: - a.append(inputText[i]) - i = i+1 - - if sys.version_info[0] == 3: - a=str(a,'utf-8') - else: - a=str(a) - return ret, a - - -def simxGetDialogResult(clientID, dialogHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - result = ct.c_int() - return c_GetDialogResult(clientID, dialogHandle, ct.byref(result), operationMode), result.value - -def simxCopyPasteObjects(clientID, objectHandles, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - c_objectHandles = (ct.c_int*len(objectHandles))(*objectHandles) - c_objectHandles = ct.cast(c_objectHandles,ct.POINTER(ct.c_int)) # IronPython needs this - newObjectCount = ct.c_int() - newObjectHandles = ct.POINTER(ct.c_int)() - ret = c_CopyPasteObjects(clientID, c_objectHandles, len(objectHandles), ct.byref(newObjectHandles), ct.byref(newObjectCount), operationMode) - - newobj = [] - if ret == 0: - for i in range(newObjectCount.value): - newobj.append(newObjectHandles[i]) - - return ret, newobj - - -def simxGetObjectSelection(clientID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - objectCount = ct.c_int() - objectHandles = ct.POINTER(ct.c_int)() - ret = c_GetObjectSelection(clientID, ct.byref(objectHandles), ct.byref(objectCount), operationMode) - - newobj = [] - if ret == 0: - for i in range(objectCount.value): - newobj.append(objectHandles[i]) - - return ret, newobj - - - -def simxSetObjectSelection(clientID, objectHandles, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - c_objectHandles = (ct.c_int*len(objectHandles))(*objectHandles) - return c_SetObjectSelection(clientID, c_objectHandles, len(objectHandles), operationMode) - -def simxClearFloatSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_ClearFloatSignal(clientID, signalName, operationMode) - -def simxClearIntegerSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_ClearIntegerSignal(clientID, signalName, operationMode) - -def simxClearStringSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_ClearStringSignal(clientID, signalName, operationMode) - -def simxGetFloatSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - signalValue = ct.c_float() - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_GetFloatSignal(clientID, signalName, ct.byref(signalValue), operationMode), signalValue.value - -def simxGetIntegerSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - signalValue = ct.c_int() - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_GetIntegerSignal(clientID, signalName, ct.byref(signalValue), operationMode), signalValue.value - -def simxGetStringSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - signalLength = ct.c_int(); - signalValue = ct.POINTER(ct.c_ubyte)() - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - ret = c_GetStringSignal(clientID, signalName, ct.byref(signalValue), ct.byref(signalLength), operationMode) - - a = bytearray() - if ret == 0: - for i in range(signalLength.value): - a.append(signalValue[i]) - if sys.version_info[0] != 3: - a=str(a) - - return ret, a - -def simxGetAndClearStringSignal(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - signalLength = ct.c_int(); - signalValue = ct.POINTER(ct.c_ubyte)() - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - ret = c_GetAndClearStringSignal(clientID, signalName, ct.byref(signalValue), ct.byref(signalLength), operationMode) - - a = bytearray() - if ret == 0: - for i in range(signalLength.value): - a.append(signalValue[i]) - if sys.version_info[0] != 3: - a=str(a) - - return ret, a - -def simxReadStringStream(clientID, signalName, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - signalLength = ct.c_int(); - signalValue = ct.POINTER(ct.c_ubyte)() - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - ret = c_ReadStringStream(clientID, signalName, ct.byref(signalValue), ct.byref(signalLength), operationMode) - - a = bytearray() - if ret == 0: - for i in range(signalLength.value): - a.append(signalValue[i]) - if sys.version_info[0] != 3: - a=str(a) - - return ret, a - -def simxSetFloatSignal(clientID, signalName, signalValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_SetFloatSignal(clientID, signalName, signalValue, operationMode) - -def simxSetIntegerSignal(clientID, signalName, signalValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(signalName) is str): - signalName=signalName.encode('utf-8') - return c_SetIntegerSignal(clientID, signalName, signalValue, operationMode) - -def simxSetStringSignal(clientID, signalName, signalValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - sigV=signalValue - if sys.version_info[0] == 3: - if type(signalName) is str: - signalName=signalName.encode('utf-8') - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=signalValue.encode('utf-8') - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - else: - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=bytearray(signalValue) - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this - return c_SetStringSignal(clientID, signalName, sigV, len(signalValue), operationMode) - -def simxAppendStringSignal(clientID, signalName, signalValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - sigV=signalValue - if sys.version_info[0] == 3: - if type(signalName) is str: - signalName=signalName.encode('utf-8') - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=signalValue.encode('utf-8') - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - else: - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=bytearray(signalValue) - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this - return c_AppendStringSignal(clientID, signalName, sigV, len(signalValue), operationMode) - -def simxWriteStringStream(clientID, signalName, signalValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - sigV=signalValue - if sys.version_info[0] == 3: - if type(signalName) is str: - signalName=signalName.encode('utf-8') - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=signalValue.encode('utf-8') - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - else: - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=bytearray(signalValue) - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this - return c_WriteStringStream(clientID, signalName, sigV, len(signalValue), operationMode) - -def simxGetObjectFloatParameter(clientID, objectHandle, parameterID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - parameterValue = ct.c_float() - return c_GetObjectFloatParameter(clientID, objectHandle, parameterID, ct.byref(parameterValue), operationMode), parameterValue.value - -def simxSetObjectFloatParameter(clientID, objectHandle, parameterID, parameterValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetObjectFloatParameter(clientID, objectHandle, parameterID, parameterValue, operationMode) - -def simxGetObjectIntParameter(clientID, objectHandle, parameterID, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - parameterValue = ct.c_int() - return c_GetObjectIntParameter(clientID, objectHandle, parameterID, ct.byref(parameterValue), operationMode), parameterValue.value - -def simxSetObjectIntParameter(clientID, objectHandle, parameterID, parameterValue, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetObjectIntParameter(clientID, objectHandle, parameterID, parameterValue, operationMode) - -def simxGetModelProperty(clientID, objectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - prop = ct.c_int() - return c_GetModelProperty(clientID, objectHandle, ct.byref(prop), operationMode), prop.value - -def simxSetModelProperty(clientID, objectHandle, prop, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SetModelProperty(clientID, objectHandle, prop, operationMode) - -def simxStart(connectionAddress, connectionPort, waitUntilConnected, doNotReconnectOnceDisconnected, timeOutInMs, commThreadCycleInMs): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(connectionAddress) is str): - connectionAddress=connectionAddress.encode('utf-8') - return c_Start(connectionAddress, connectionPort, waitUntilConnected, doNotReconnectOnceDisconnected, timeOutInMs, commThreadCycleInMs) - -def simxFinish(clientID): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_Finish(clientID) - -def simxGetPingTime(clientID): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - pingTime = ct.c_int() - return c_GetPingTime(clientID, ct.byref(pingTime)), pingTime.value - -def simxGetLastCmdTime(clientID): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_GetLastCmdTime(clientID) - -def simxSynchronousTrigger(clientID): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_SynchronousTrigger(clientID) - -def simxSynchronous(clientID, enable): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_Synchronous(clientID, enable) - -def simxPauseCommunication(clientID, enable): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_PauseCommunication(clientID, enable) - -def simxGetInMessageInfo(clientID, infoType): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - info = ct.c_int() - return c_GetInMessageInfo(clientID, infoType, ct.byref(info)), info.value - -def simxGetOutMessageInfo(clientID, infoType): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - info = ct.c_int() - return c_GetOutMessageInfo(clientID, infoType, ct.byref(info)), info.value - -def simxGetConnectionId(clientID): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_GetConnectionId(clientID) - -def simxCreateBuffer(bufferSize): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_CreateBuffer(bufferSize) - -def simxReleaseBuffer(buffer): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - return c_ReleaseBuffer(buffer) - -def simxTransferFile(clientID, filePathAndName, fileName_serverSide, timeOut, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(filePathAndName) is str): - filePathAndName=filePathAndName.encode('utf-8') - return c_TransferFile(clientID, filePathAndName, fileName_serverSide, timeOut, operationMode) - -def simxEraseFile(clientID, fileName_serverSide, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if (sys.version_info[0] == 3) and (type(fileName_serverSide) is str): - fileName_serverSide=fileName_serverSide.encode('utf-8') - return c_EraseFile(clientID, fileName_serverSide, operationMode) - -def simxCreateDummy(clientID, size, color, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - handle = ct.c_int() - if color != None: - c_color = (ct.c_ubyte*12)(*color) - else: - c_color = None - return c_CreateDummy(clientID, size, c_color, ct.byref(handle), operationMode), handle.value - -def simxQuery(clientID, signalName, signalValue, retSignalName, timeOutInMs): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - retSignalLength = ct.c_int(); - retSignalValue = ct.POINTER(ct.c_ubyte)() - - sigV=signalValue - if sys.version_info[0] == 3: - if type(signalName) is str: - signalName=signalName.encode('utf-8') - if type(retSignalName) is str: - retSignalName=retSignalName.encode('utf-8') - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=signalValue.encode('utf-8') - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - else: - if type(signalValue) is bytearray: - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - if type(signalValue) is str: - signalValue=bytearray(signalValue) - sigV = (ct.c_ubyte*len(signalValue))(*signalValue) - sigV=ct.cast(sigV,ct.POINTER(ct.c_ubyte)) # IronPython needs this - - ret = c_Query(clientID, signalName, sigV, len(signalValue), retSignalName, ct.byref(retSignalValue), ct.byref(retSignalLength), timeOutInMs) - - a = bytearray() - if ret == 0: - for i in range(retSignalLength.value): - a.append(retSignalValue[i]) - if sys.version_info[0] != 3: - a=str(a) - - return ret, a - -def simxGetObjectGroupData(clientID, objectType, dataType, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - handles =[] - intData =[] - floatData =[] - stringData =[] - handlesC = ct.c_int() - handlesP = ct.POINTER(ct.c_int)() - intDataC = ct.c_int() - intDataP = ct.POINTER(ct.c_int)() - floatDataC = ct.c_int() - floatDataP = ct.POINTER(ct.c_float)() - stringDataC = ct.c_int() - stringDataP = ct.POINTER(ct.c_char)() - ret = c_GetObjectGroupData(clientID, objectType, dataType, ct.byref(handlesC), ct.byref(handlesP), ct.byref(intDataC), ct.byref(intDataP), ct.byref(floatDataC), ct.byref(floatDataP), ct.byref(stringDataC), ct.byref(stringDataP), operationMode) - - if ret == 0: - for i in range(handlesC.value): - handles.append(handlesP[i]) - for i in range(intDataC.value): - intData.append(intDataP[i]) - for i in range(floatDataC.value): - floatData.append(floatDataP[i]) - s = 0 - for i in range(stringDataC.value): - a = bytearray() - while stringDataP[s] != b'\0': - if sys.version_info[0] == 3: - a.append(int.from_bytes(stringDataP[s],'big')) - else: - a.append(stringDataP[s]) - s += 1 - s += 1 #skip null - if sys.version_info[0] == 3: - a=str(a,'utf-8') - else: - a=str(a) - stringData.append(a) - - return ret, handles, intData, floatData, stringData - -def simxCallScriptFunction(clientID, scriptDescription, options, functionName, inputInts, inputFloats, inputStrings, inputBuffer, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - inputBufferV=inputBuffer - if sys.version_info[0] == 3: - if type(scriptDescription) is str: - scriptDescription=scriptDescription.encode('utf-8') - if type(functionName) is str: - functionName=functionName.encode('utf-8') - if type(inputBuffer) is bytearray: - inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) - if type(inputBuffer) is str: - inputBuffer=inputBuffer.encode('utf-8') - inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) - else: - if type(inputBuffer) is bytearray: - inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) - if type(inputBuffer) is str: - inputBuffer=bytearray(inputBuffer) - inputBufferV = (ct.c_ubyte*len(inputBuffer))(*inputBuffer) - inputBufferV=ct.cast(inputBufferV,ct.POINTER(ct.c_ubyte)) # IronPython needs this - - c_inInts = (ct.c_int*len(inputInts))(*inputInts) - c_inInts = ct.cast(c_inInts,ct.POINTER(ct.c_int)) # IronPython needs this - c_inFloats = (ct.c_float*len(inputFloats))(*inputFloats) - c_inFloats = ct.cast(c_inFloats,ct.POINTER(ct.c_float)) # IronPython needs this - - concatStr=''.encode('utf-8') - for i in range(len(inputStrings)): - a=inputStrings[i] - a=a+'\0' - if type(a) is str: - a=a.encode('utf-8') - concatStr=concatStr+a - c_inStrings = (ct.c_char*len(concatStr))(*concatStr) - - intDataOut =[] - floatDataOut =[] - stringDataOut =[] - bufferOut =bytearray() - - intDataC = ct.c_int() - intDataP = ct.POINTER(ct.c_int)() - floatDataC = ct.c_int() - floatDataP = ct.POINTER(ct.c_float)() - stringDataC = ct.c_int() - stringDataP = ct.POINTER(ct.c_char)() - bufferS = ct.c_int() - bufferP = ct.POINTER(ct.c_ubyte)() - - ret = c_CallScriptFunction(clientID,scriptDescription,options,functionName,len(inputInts),c_inInts,len(inputFloats),c_inFloats,len(inputStrings),c_inStrings,len(inputBuffer),inputBufferV,ct.byref(intDataC),ct.byref(intDataP),ct.byref(floatDataC),ct.byref(floatDataP),ct.byref(stringDataC),ct.byref(stringDataP),ct.byref(bufferS),ct.byref(bufferP),operationMode) - - if ret == 0: - for i in range(intDataC.value): - intDataOut.append(intDataP[i]) - for i in range(floatDataC.value): - floatDataOut.append(floatDataP[i]) - s = 0 - for i in range(stringDataC.value): - a = bytearray() - while stringDataP[s] != b'\0': - if sys.version_info[0] == 3: - a.append(int.from_bytes(stringDataP[s],'big')) - else: - a.append(stringDataP[s]) - s += 1 - s += 1 #skip null - if sys.version_info[0] == 3: - a=str(a,'utf-8') - else: - a=str(a) - stringDataOut.append(a) - for i in range(bufferS.value): - bufferOut.append(bufferP[i]) - if sys.version_info[0] != 3: - bufferOut=str(bufferOut) - - return ret, intDataOut, floatDataOut, stringDataOut, bufferOut - -def simxGetObjectVelocity(clientID, objectHandle, operationMode): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - linearVel = (ct.c_float*3)() - angularVel = (ct.c_float*3)() - ret = c_GetObjectVelocity(clientID, objectHandle, linearVel, angularVel, operationMode) - arr1 = [] - for i in range(3): - arr1.append(linearVel[i]) - arr2 = [] - for i in range(3): - arr2.append(angularVel[i]) - return ret, arr1, arr2 - -def simxPackInts(intList): - ''' - Please have a look at the function description/documentation in the V-REP user manual - ''' - - if sys.version_info[0] == 3: - s=bytes() - for i in range(len(intList)): - s=s+struct.pack('. -# ------------------------------------------------------------------- -# -# This file was automatically created for V-REP release V3.3.0 on February 19th 2016 - -#constants -#Scene object types. Values are serialized -sim_object_shape_type =0 -sim_object_joint_type =1 -sim_object_graph_type =2 -sim_object_camera_type =3 -sim_object_dummy_type =4 -sim_object_proximitysensor_type =5 -sim_object_reserved1 =6 -sim_object_reserved2 =7 -sim_object_path_type =8 -sim_object_visionsensor_type =9 -sim_object_volume_type =10 -sim_object_mill_type =11 -sim_object_forcesensor_type =12 -sim_object_light_type =13 -sim_object_mirror_type =14 - -#General object types. Values are serialized -sim_appobj_object_type =109 -sim_appobj_collision_type =110 -sim_appobj_distance_type =111 -sim_appobj_simulation_type =112 -sim_appobj_ik_type =113 -sim_appobj_constraintsolver_type=114 -sim_appobj_collection_type =115 -sim_appobj_ui_type =116 -sim_appobj_script_type =117 -sim_appobj_pathplanning_type =118 -sim_appobj_RESERVED_type =119 -sim_appobj_texture_type =120 - -# Ik calculation methods. Values are serialized -sim_ik_pseudo_inverse_method =0 -sim_ik_damped_least_squares_method =1 -sim_ik_jacobian_transpose_method =2 - -# Ik constraints. Values are serialized -sim_ik_x_constraint =1 -sim_ik_y_constraint =2 -sim_ik_z_constraint =4 -sim_ik_alpha_beta_constraint=8 -sim_ik_gamma_constraint =16 -sim_ik_avoidance_constraint =64 - -# Ik calculation results -sim_ikresult_not_performed =0 -sim_ikresult_success =1 -sim_ikresult_fail =2 - -# Scene object sub-types. Values are serialized -# Light sub-types -sim_light_omnidirectional_subtype =1 -sim_light_spot_subtype =2 -sim_light_directional_subtype =3 -# Joint sub-types -sim_joint_revolute_subtype =10 -sim_joint_prismatic_subtype =11 -sim_joint_spherical_subtype =12 -# Shape sub-types -sim_shape_simpleshape_subtype =20 -sim_shape_multishape_subtype =21 -# Proximity sensor sub-types -sim_proximitysensor_pyramid_subtype =30 -sim_proximitysensor_cylinder_subtype=31 -sim_proximitysensor_disc_subtype =32 -sim_proximitysensor_cone_subtype =33 -sim_proximitysensor_ray_subtype =34 -# Mill sub-types -sim_mill_pyramid_subtype =40 -sim_mill_cylinder_subtype =41 -sim_mill_disc_subtype =42 -sim_mill_cone_subtype =42 -# No sub-type -sim_object_no_subtype =200 - - -#Scene object main properties (serialized) -sim_objectspecialproperty_collidable =0x0001 -sim_objectspecialproperty_measurable =0x0002 -#reserved =0x0004 -#reserved =0x0008 -sim_objectspecialproperty_detectable_ultrasonic =0x0010 -sim_objectspecialproperty_detectable_infrared =0x0020 -sim_objectspecialproperty_detectable_laser =0x0040 -sim_objectspecialproperty_detectable_inductive =0x0080 -sim_objectspecialproperty_detectable_capacitive =0x0100 -sim_objectspecialproperty_renderable =0x0200 -sim_objectspecialproperty_detectable_all =sim_objectspecialproperty_detectable_ultrasonic|sim_objectspecialproperty_detectable_infrared|sim_objectspecialproperty_detectable_laser|sim_objectspecialproperty_detectable_inductive|sim_objectspecialproperty_detectable_capacitive -sim_objectspecialproperty_cuttable =0x0400 -sim_objectspecialproperty_pathplanning_ignored =0x0800 - -# Model properties (serialized) -sim_modelproperty_not_collidable =0x0001 -sim_modelproperty_not_measurable =0x0002 -sim_modelproperty_not_renderable =0x0004 -sim_modelproperty_not_detectable =0x0008 -sim_modelproperty_not_cuttable =0x0010 -sim_modelproperty_not_dynamic =0x0020 -sim_modelproperty_not_respondable =0x0040 # cannot be selected if sim_modelproperty_not_dynamic is not selected -sim_modelproperty_not_reset =0x0080 # Model is not reset at simulation end. This flag is cleared at simulation end -sim_modelproperty_not_visible =0x0100 # Whole model is invisible independent of local visibility settings -sim_modelproperty_not_model =0xf000 # object is not a model - - -# Check the documentation instead of comments below!! -# Following messages are dispatched to the Lua-message container -sim_message_ui_button_state_change =0 # a UI button slider etc. changed (due to a user's action). aux[0]=UI handle aux[1]=button handle aux[2]=button attributes aux[3]=slider position (if slider) -sim_message_reserved9 =1 # Do not use -sim_message_object_selection_changed=2 -sim_message_reserved10 =3 # do not use -sim_message_model_loaded =4 -sim_message_reserved11 =5 # do not use -sim_message_keypress =6 # a key was pressed while the focus was on a page (aux[0]=key aux[1]=ctrl and shift key state) -sim_message_bannerclicked =7 # a banner was clicked (aux[0]=banner ID) - - -# Following messages are dispatched only to the C-API (not available from Lua) -sim_message_for_c_api_only_start =0x100 # Do not use -sim_message_reserved1 =0x101 # Do not use -sim_message_reserved2 =0x102 # Do not use -sim_message_reserved3 =0x103 # Do not use -sim_message_eventcallback_scenesave =0x104 # about to save a scene -sim_message_eventcallback_modelsave =0x105 # about to save a model (current selection will be saved) -sim_message_eventcallback_moduleopen =0x106 # called when simOpenModule in Lua is called -sim_message_eventcallback_modulehandle =0x107 # called when simHandleModule in Lua is called with argument false -sim_message_eventcallback_moduleclose =0x108 # called when simCloseModule in Lua is called -sim_message_reserved4 =0x109 # Do not use -sim_message_reserved5 =0x10a # Do not use -sim_message_reserved6 =0x10b # Do not use -sim_message_reserved7 =0x10c # Do not use -sim_message_eventcallback_instancepass =0x10d # Called once every main application loop pass. auxiliaryData[0] contains event flags of events that happened since last time -sim_message_eventcallback_broadcast =0x10e -sim_message_eventcallback_imagefilter_enumreset =0x10f -sim_message_eventcallback_imagefilter_enumerate =0x110 -sim_message_eventcallback_imagefilter_adjustparams =0x111 -sim_message_eventcallback_imagefilter_reserved =0x112 -sim_message_eventcallback_imagefilter_process =0x113 -sim_message_eventcallback_reserved1 =0x114 # do not use -sim_message_eventcallback_reserved2 =0x115 # do not use -sim_message_eventcallback_reserved3 =0x116 # do not use -sim_message_eventcallback_reserved4 =0x117 # do not use -sim_message_eventcallback_abouttoundo =0x118 # the undo button was hit and a previous state is about to be restored -sim_message_eventcallback_undoperformed =0x119 # the undo button was hit and a previous state restored -sim_message_eventcallback_abouttoredo =0x11a # the redo button was hit and a future state is about to be restored -sim_message_eventcallback_redoperformed =0x11b # the redo button was hit and a future state restored -sim_message_eventcallback_scripticondblclick =0x11c # scipt icon was double clicked. (aux[0]=object handle associated with script set replyData[0] to 1 if script should not be opened) -sim_message_eventcallback_simulationabouttostart =0x11d -sim_message_eventcallback_simulationended =0x11e -sim_message_eventcallback_reserved5 =0x11f # do not use -sim_message_eventcallback_keypress =0x120 # a key was pressed while the focus was on a page (aux[0]=key aux[1]=ctrl and shift key state) -sim_message_eventcallback_modulehandleinsensingpart =0x121 # called when simHandleModule in Lua is called with argument true -sim_message_eventcallback_renderingpass =0x122 # called just before the scene is rendered -sim_message_eventcallback_bannerclicked =0x123 # called when a banner was clicked (aux[0]=banner ID) -sim_message_eventcallback_menuitemselected =0x124 # auxiliaryData[0] indicates the handle of the item auxiliaryData[1] indicates the state of the item -sim_message_eventcallback_refreshdialogs =0x125 # aux[0]=refresh degree (0=light 1=medium 2=full) -sim_message_eventcallback_sceneloaded =0x126 -sim_message_eventcallback_modelloaded =0x127 -sim_message_eventcallback_instanceswitch =0x128 -sim_message_eventcallback_guipass =0x129 -sim_message_eventcallback_mainscriptabouttobecalled =0x12a -sim_message_eventcallback_rmlposition =0x12b #the command simRMLPosition was called. The appropriate plugin should handle the call -sim_message_eventcallback_rmlvelocity =0x12c # the command simRMLVelocity was called. The appropriate plugin should handle the call -sim_message_simulation_start_resume_request =0x1000 -sim_message_simulation_pause_request =0x1001 -sim_message_simulation_stop_request =0x1002 - -# Scene object properties. Combine with the | operator -sim_objectproperty_reserved1 =0x0000 -sim_objectproperty_reserved2 =0x0001 -sim_objectproperty_reserved3 =0x0002 -sim_objectproperty_reserved4 =0x0003 -sim_objectproperty_reserved5 =0x0004 # formely sim_objectproperty_visible -sim_objectproperty_reserved6 =0x0008 # formely sim_objectproperty_wireframe -sim_objectproperty_collapsed =0x0010 -sim_objectproperty_selectable =0x0020 -sim_objectproperty_reserved7 =0x0040 -sim_objectproperty_selectmodelbaseinstead =0x0080 -sim_objectproperty_dontshowasinsidemodel =0x0100 -# reserved =0x0200 -sim_objectproperty_canupdatedna =0x0400 -sim_objectproperty_selectinvisible =0x0800 -sim_objectproperty_depthinvisible =0x1000 - - -# type of arguments (input and output) for custom lua commands -sim_lua_arg_nil =0 -sim_lua_arg_bool =1 -sim_lua_arg_int =2 -sim_lua_arg_float =3 -sim_lua_arg_string =4 -sim_lua_arg_invalid =5 -sim_lua_arg_table =8 - -# custom user interface properties. Values are serialized. -sim_ui_property_visible =0x0001 -sim_ui_property_visibleduringsimulationonly =0x0002 -sim_ui_property_moveable =0x0004 -sim_ui_property_relativetoleftborder =0x0008 -sim_ui_property_relativetotopborder =0x0010 -sim_ui_property_fixedwidthfont =0x0020 -sim_ui_property_systemblock =0x0040 -sim_ui_property_settocenter =0x0080 -sim_ui_property_rolledup =0x0100 -sim_ui_property_selectassociatedobject =0x0200 -sim_ui_property_visiblewhenobjectselected =0x0400 - - -# button properties. Values are serialized. -sim_buttonproperty_button =0x0000 -sim_buttonproperty_label =0x0001 -sim_buttonproperty_slider =0x0002 -sim_buttonproperty_editbox =0x0003 -sim_buttonproperty_staydown =0x0008 -sim_buttonproperty_enabled =0x0010 -sim_buttonproperty_borderless =0x0020 -sim_buttonproperty_horizontallycentered =0x0040 -sim_buttonproperty_ignoremouse =0x0080 -sim_buttonproperty_isdown =0x0100 -sim_buttonproperty_transparent =0x0200 -sim_buttonproperty_nobackgroundcolor =0x0400 -sim_buttonproperty_rollupaction =0x0800 -sim_buttonproperty_closeaction =0x1000 -sim_buttonproperty_verticallycentered =0x2000 -sim_buttonproperty_downupevent =0x4000 - - -# Simulation status -sim_simulation_stopped =0x00 # Simulation is stopped -sim_simulation_paused =0x08 # Simulation is paused -sim_simulation_advancing =0x10 # Simulation is advancing -sim_simulation_advancing_firstafterstop =sim_simulation_advancing|0x00 # First simulation pass (1x) -sim_simulation_advancing_running =sim_simulation_advancing|0x01 # Normal simulation pass (>=1x) -# reserved =sim_simulation_advancing|0x02 -sim_simulation_advancing_lastbeforepause =sim_simulation_advancing|0x03 # Last simulation pass before pause (1x) -sim_simulation_advancing_firstafterpause =sim_simulation_advancing|0x04 # First simulation pass after pause (1x) -sim_simulation_advancing_abouttostop =sim_simulation_advancing|0x05 # "Trying to stop" simulation pass (>=1x) -sim_simulation_advancing_lastbeforestop =sim_simulation_advancing|0x06 # Last simulation pass (1x) - - -# Script execution result (first return value) -sim_script_no_error =0 -sim_script_main_script_nonexistent =1 -sim_script_main_script_not_called =2 -sim_script_reentrance_error =4 -sim_script_lua_error =8 -sim_script_call_error =16 - - - # Script types (serialized!) -sim_scripttype_mainscript =0 -sim_scripttype_childscript =1 -sim_scripttype_jointctrlcallback =4 -sim_scripttype_contactcallback =5 -sim_scripttype_customizationscript =6 -sim_scripttype_generalcallback =7 - -# API call error messages -sim_api_errormessage_ignore =0 # does not memorize nor output errors -sim_api_errormessage_report =1 # memorizes errors (default for C-API calls) -sim_api_errormessage_output =2 # memorizes and outputs errors (default for Lua-API calls) - - -# special argument of some functions -sim_handle_all =-2 -sim_handle_all_except_explicit =-3 -sim_handle_self =-4 -sim_handle_main_script =-5 -sim_handle_tree =-6 -sim_handle_chain =-7 -sim_handle_single =-8 -sim_handle_default =-9 -sim_handle_all_except_self =-10 -sim_handle_parent =-11 - - -# special handle flags -sim_handleflag_assembly =0x400000 -sim_handleflag_model =0x800000 - - -# distance calculation methods (serialized) -sim_distcalcmethod_dl =0 -sim_distcalcmethod_dac =1 -sim_distcalcmethod_max_dl_dac =2 -sim_distcalcmethod_dl_and_dac =3 -sim_distcalcmethod_sqrt_dl2_and_dac2=4 -sim_distcalcmethod_dl_if_nonzero =5 -sim_distcalcmethod_dac_if_nonzero =6 - - - # Generic dialog styles -sim_dlgstyle_message =0 -sim_dlgstyle_input =1 -sim_dlgstyle_ok =2 -sim_dlgstyle_ok_cancel =3 -sim_dlgstyle_yes_no =4 -sim_dlgstyle_dont_center =32# can be combined with one of above values. Only with this flag can the position of the related UI be set just after dialog creation - - # Generic dialog return values -sim_dlgret_still_open =0 -sim_dlgret_ok =1 -sim_dlgret_cancel =2 -sim_dlgret_yes =3 -sim_dlgret_no =4 - - -# Path properties -sim_pathproperty_show_line =0x0001 -sim_pathproperty_show_orientation =0x0002 -sim_pathproperty_closed_path =0x0004 -sim_pathproperty_automatic_orientation =0x0008 -sim_pathproperty_invert_velocity =0x0010 -sim_pathproperty_infinite_acceleration =0x0020 -sim_pathproperty_flat_path =0x0040 -sim_pathproperty_show_position =0x0080 -sim_pathproperty_auto_velocity_profile_translation =0x0100 -sim_pathproperty_auto_velocity_profile_rotation =0x0200 -sim_pathproperty_endpoints_at_zero =0x0400 -sim_pathproperty_keep_x_up =0x0800 - - - # drawing objects -# following are mutually exclusive -sim_drawing_points =0 # 3 values per point (point size in pixels) -sim_drawing_lines =1 # 6 values per line (line size in pixels) -sim_drawing_triangles =2 # 9 values per triangle -sim_drawing_trianglepoints =3 # 6 values per point (3 for triangle position 3 for triangle normal vector) (triangle size in meters) -sim_drawing_quadpoints =4 # 6 values per point (3 for quad position 3 for quad normal vector) (quad size in meters) -sim_drawing_discpoints =5 # 6 values per point (3 for disc position 3 for disc normal vector) (disc size in meters) -sim_drawing_cubepoints =6 # 6 values per point (3 for cube position 3 for cube normal vector) (cube size in meters) -sim_drawing_spherepoints =7 # 3 values per point (sphere size in meters) - -# following can be or-combined -sim_drawing_itemcolors =0x00020 # +3 values per item (each item has its own ambient color (rgb values)). - # Mutually exclusive with sim_drawing_vertexcolors -sim_drawing_vertexcolors =0x00040 # +3 values per vertex (each vertex has its own ambient color (rgb values). Only for sim_drawing_lines (+6) and for sim_drawing_triangles(+9)). Mutually exclusive with sim_drawing_itemcolors -sim_drawing_itemsizes =0x00080 # +1 value per item (each item has its own size). Not for sim_drawing_triangles -sim_drawing_backfaceculling =0x00100 # back faces are not displayed for all items -sim_drawing_wireframe =0x00200 # all items displayed in wireframe -sim_drawing_painttag =0x00400 # all items are tagged as paint (for additinal processing at a later stage) -sim_drawing_followparentvisibility =0x00800 # if the object is associated with a scene object then it follows that visibility otherwise it is always visible -sim_drawing_cyclic =0x01000 # if the max item count was reached then the first items are overwritten. -sim_drawing_50percenttransparency =0x02000 # the drawing object will be 50% transparent -sim_drawing_25percenttransparency =0x04000 # the drawing object will be 25% transparent -sim_drawing_12percenttransparency =0x08000 # the drawing object will be 12.5% transparent -sim_drawing_emissioncolor =0x10000 # When used in combination with sim_drawing_itemcolors or sim_drawing_vertexcolors then the specified colors will be for the emissive component -sim_drawing_facingcamera =0x20000 # Only for trianglepoints quadpoints discpoints and cubepoints. If specified the normal verctor is calculated to face the camera (each item data requires 3 values less) -sim_drawing_overlay =0x40000 # When specified objects are always drawn on top of "regular objects" -sim_drawing_itemtransparency =0x80000 # +1 value per item (each item has its own transparency value (0-1)). Not compatible with sim_drawing_vertexcolors - -# banner values -# following can be or-combined -sim_banner_left =0x00001 # Banners display on the left of the specified point -sim_banner_right =0x00002 # Banners display on the right of the specified point -sim_banner_nobackground =0x00004 # Banners have no background rectangle -sim_banner_overlay =0x00008 # When specified banners are always drawn on top of "regular objects" -sim_banner_followparentvisibility =0x00010 # if the object is associated with a scene object then it follows that visibility otherwise it is always visible -sim_banner_clickselectsparent =0x00020 # if the object is associated with a scene object then clicking the banner will select the scene object -sim_banner_clicktriggersevent =0x00040 # if the banner is clicked an event is triggered (sim_message_eventcallback_bannerclicked and sim_message_bannerclicked are generated) -sim_banner_facingcamera =0x00080 # If specified the banner will always face the camera by rotating around the banner's vertical axis (y-axis) -sim_banner_fullyfacingcamera =0x00100 # If specified the banner will always fully face the camera (the banner's orientation is same as the camera looking at it) -sim_banner_backfaceculling =0x00200 # If specified the banner will only be visible from one side -sim_banner_keepsamesize =0x00400 # If specified the banner will always appear in the same size. In that case size represents the character height in pixels -sim_banner_bitmapfont =0x00800 # If specified a fixed-size bitmap font is used. The text will also always fully face the camera and be right - # to the specified position. Bitmap fonts are not clickable - - -# particle objects following are mutually exclusive -sim_particle_points1 =0 # 6 values per point (pt1 and pt2. Pt1 is start position pt2-pt1 is the initial velocity vector). i - #Point is 1 pixel big. Only appearance is a point internally handled as a perfect sphere -sim_particle_points2 =1 # 6 values per point. Point is 2 pixel big. Only appearance is a point internally handled as a perfect sphere -sim_particle_points4 =2 # 6 values per point. Point is 4 pixel big. Only appearance is a point internally handled as a perfect sphere -sim_particle_roughspheres =3 # 6 values per sphere. Only appearance is rough. Internally a perfect sphere -sim_particle_spheres =4 # 6 values per sphere. Internally a perfect sphere - - - - -# following can be or-combined -sim_particle_respondable1to4 =0x0020 # the particles are respondable against shapes (against all objects that have at least one bit 1-4 activated in the global respondable mask) -sim_particle_respondable5to8 =0x0040 # the particles are respondable against shapes (against all objects that have at least one bit 5-8 activated in the global respondable mask) -sim_particle_particlerespondable =0x0080 # the particles are respondable against each other -sim_particle_ignoresgravity =0x0100 # the particles ignore the effect of gravity. Not compatible with sim_particle_water -sim_particle_invisible =0x0200 # the particles are invisible -sim_particle_itemsizes =0x0400 # +1 value per particle (each particle can have a different size) -sim_particle_itemdensities =0x0800 # +1 value per particle (each particle can have a different density) -sim_particle_itemcolors =0x1000 # +3 values per particle (each particle can have a different color) -sim_particle_cyclic =0x2000 # if the max item count was reached then the first items are overwritten. -sim_particle_emissioncolor =0x4000 # When used in combination with sim_particle_itemcolors then the specified colors will be for the emissive component -sim_particle_water =0x8000 # the particles are water particles (no weight in the water (i.e. when z<0)). Not compatible with sim_particle_ignoresgravity -sim_particle_painttag =0x10000 # The particles can be seen by vision sensors (sim_particle_invisible must not be set) - - - - -# custom user interface menu attributes -sim_ui_menu_title =1 -sim_ui_menu_minimize =2 -sim_ui_menu_close =4 -sim_ui_menu_systemblock =8 - - - -# Boolean parameters -sim_boolparam_hierarchy_visible =0 -sim_boolparam_console_visible =1 -sim_boolparam_collision_handling_enabled =2 -sim_boolparam_distance_handling_enabled =3 -sim_boolparam_ik_handling_enabled =4 -sim_boolparam_gcs_handling_enabled =5 -sim_boolparam_dynamics_handling_enabled =6 -sim_boolparam_joint_motion_handling_enabled =7 -sim_boolparam_path_motion_handling_enabled =8 -sim_boolparam_proximity_sensor_handling_enabled =9 -sim_boolparam_vision_sensor_handling_enabled =10 -sim_boolparam_mill_handling_enabled =11 -sim_boolparam_browser_visible =12 -sim_boolparam_scene_and_model_load_messages =13 -sim_reserved0 =14 -sim_boolparam_shape_textures_are_visible =15 -sim_boolparam_display_enabled =16 -sim_boolparam_infotext_visible =17 -sim_boolparam_statustext_open =18 -sim_boolparam_fog_enabled =19 -sim_boolparam_rml2_available =20 -sim_boolparam_rml4_available =21 -sim_boolparam_mirrors_enabled =22 -sim_boolparam_aux_clip_planes_enabled =23 -sim_boolparam_full_model_copy_from_api =24 -sim_boolparam_realtime_simulation =25 -sim_boolparam_force_show_wireless_emission =27 -sim_boolparam_force_show_wireless_reception =28 -sim_boolparam_video_recording_triggered =29 -sim_boolparam_threaded_rendering_enabled =32 -sim_boolparam_fullscreen =33 -sim_boolparam_headless =34 -sim_boolparam_hierarchy_toolbarbutton_enabled =35 -sim_boolparam_browser_toolbarbutton_enabled =36 -sim_boolparam_objectshift_toolbarbutton_enabled =37 -sim_boolparam_objectrotate_toolbarbutton_enabled=38 -sim_boolparam_force_calcstruct_all_visible =39 -sim_boolparam_force_calcstruct_all =40 -sim_boolparam_exit_request =41 -sim_boolparam_play_toolbarbutton_enabled =42 -sim_boolparam_pause_toolbarbutton_enabled =43 -sim_boolparam_stop_toolbarbutton_enabled =44 -sim_boolparam_waiting_for_trigger =45 - - -# Integer parameters -sim_intparam_error_report_mode =0 # Check sim_api_errormessage_... constants above for valid values -sim_intparam_program_version =1 # e.g Version 2.1.4 --> 20104. Can only be read -sim_intparam_instance_count =2 # do not use anymore (always returns 1 since V-REP 2.5.11) -sim_intparam_custom_cmd_start_id =3 # can only be read -sim_intparam_compilation_version =4 # 0=evaluation version 1=full version 2=player version. Can only be read -sim_intparam_current_page =5 -sim_intparam_flymode_camera_handle =6 # can only be read -sim_intparam_dynamic_step_divider =7 # can only be read -sim_intparam_dynamic_engine =8 # 0=Bullet 1=ODE. 2=Vortex. -sim_intparam_server_port_start =9 # can only be read -sim_intparam_server_port_range =10 # can only be read -sim_intparam_visible_layers =11 -sim_intparam_infotext_style =12 -sim_intparam_settings =13 -sim_intparam_edit_mode_type =14 # can only be read -sim_intparam_server_port_next =15 # is initialized at sim_intparam_server_port_start -sim_intparam_qt_version =16 # version of the used Qt framework -sim_intparam_event_flags_read =17 # can only be read -sim_intparam_event_flags_read_clear =18 # can only be read -sim_intparam_platform =19 # can only be read -sim_intparam_scene_unique_id =20 # can only be read -sim_intparam_work_thread_count =21 -sim_intparam_mouse_x =22 -sim_intparam_mouse_y =23 -sim_intparam_core_count =24 -sim_intparam_work_thread_calc_time_ms =25 -sim_intparam_idle_fps =26 -sim_intparam_prox_sensor_select_down =27 -sim_intparam_prox_sensor_select_up =28 -sim_intparam_stop_request_counter =29 -sim_intparam_program_revision =30 -sim_intparam_mouse_buttons =31 -sim_intparam_dynamic_warning_disabled_mask =32 -sim_intparam_simulation_warning_disabled_mask =33 -sim_intparam_scene_index =34 -sim_intparam_motionplanning_seed =35 -sim_intparam_speedmodifier =36 - -# Float parameters -sim_floatparam_rand=0 # random value (0.0-1.0) -sim_floatparam_simulation_time_step =1 -sim_floatparam_stereo_distance =2 - -# String parameters -sim_stringparam_application_path=0 # path of V-REP's executable -sim_stringparam_video_filename=1 -sim_stringparam_app_arg1 =2 -sim_stringparam_app_arg2 =3 -sim_stringparam_app_arg3 =4 -sim_stringparam_app_arg4 =5 -sim_stringparam_app_arg5 =6 -sim_stringparam_app_arg6 =7 -sim_stringparam_app_arg7 =8 -sim_stringparam_app_arg8 =9 -sim_stringparam_app_arg9 =10 -sim_stringparam_scene_path_and_name =13 - -# Array parameters -sim_arrayparam_gravity =0 -sim_arrayparam_fog =1 -sim_arrayparam_fog_color =2 -sim_arrayparam_background_color1=3 -sim_arrayparam_background_color2=4 -sim_arrayparam_ambient_light =5 -sim_arrayparam_random_euler =6 - - -# User interface elements -sim_gui_menubar =0x0001 -sim_gui_popups =0x0002 -sim_gui_toolbar1 =0x0004 -sim_gui_toolbar2 =0x0008 -sim_gui_hierarchy =0x0010 -sim_gui_infobar =0x0020 -sim_gui_statusbar =0x0040 -sim_gui_scripteditor =0x0080 -sim_gui_scriptsimulationparameters =0x0100 -sim_gui_dialogs =0x0200 -sim_gui_browser =0x0400 -sim_gui_all =0xffff - - -# Joint modes -sim_jointmode_passive =0 -sim_jointmode_motion =1 -sim_jointmode_ik =2 -sim_jointmode_ikdependent =3 -sim_jointmode_dependent =4 -sim_jointmode_force =5 - - -# Navigation and selection modes with the mouse. Lower byte values are mutually exclusive upper byte bits can be combined -sim_navigation_passive =0x0000 -sim_navigation_camerashift =0x0001 -sim_navigation_camerarotate =0x0002 -sim_navigation_camerazoom =0x0003 -sim_navigation_cameratilt =0x0004 -sim_navigation_cameraangle =0x0005 -sim_navigation_camerafly =0x0006 -sim_navigation_objectshift =0x0007 -sim_navigation_objectrotate =0x0008 -sim_navigation_reserved2 =0x0009 -sim_navigation_reserved3 =0x000A -sim_navigation_jointpathtest =0x000B -sim_navigation_ikmanip =0x000C -sim_navigation_objectmultipleselection =0x000D -# Bit-combine following values and add them to one of above's values for a valid navigation mode -sim_navigation_reserved4 =0x0100 -sim_navigation_clickselection =0x0200 -sim_navigation_ctrlselection =0x0400 -sim_navigation_shiftselection =0x0800 -sim_navigation_camerazoomwheel =0x1000 -sim_navigation_camerarotaterightbutton =0x2000 - - - -#Remote API constants -SIMX_VERSION =0 -# Remote API message header structure -SIMX_HEADER_SIZE =18 -simx_headeroffset_crc =0 # 1 simxUShort. Generated by the client or server. The CRC for the message -simx_headeroffset_version =2 # 1 byte. Generated by the client or server. The version of the remote API software -simx_headeroffset_message_id =3 # 1 simxInt. Generated by the client (and used in a reply by the server) -simx_headeroffset_client_time =7 # 1 simxInt. Client time stamp generated by the client (and sent back by the server) -simx_headeroffset_server_time =11 # 1 simxInt. Generated by the server when a reply is generated. The server timestamp -simx_headeroffset_scene_id =15 # 1 simxUShort. Generated by the server. A unique ID identifying the scene currently displayed -simx_headeroffset_server_state =17 # 1 byte. Generated by the server. Bit coded 0 set --> simulation not stopped 1 set --> simulation paused 2 set --> real-time switch on 3-5 edit mode type (0=no edit mode 1=triangle 2=vertex 3=edge 4=path 5=UI) - -# Remote API command header -SIMX_SUBHEADER_SIZE =26 -simx_cmdheaderoffset_mem_size =0 # 1 simxInt. Generated by the client or server. The buffer size of the command. -simx_cmdheaderoffset_full_mem_size =4 # 1 simxInt. Generated by the client or server. The full buffer size of the command (applies to split chunks). -simx_cmdheaderoffset_pdata_offset0 =8 # 1 simxUShort. Generated by the client or server. The amount of data that is part of the command identification. -simx_cmdheaderoffset_pdata_offset1 =10 # 1 simxInt. Generated by the client or server. The amount of shift of the pure data buffer (applies to split chunks). -simx_cmdheaderoffset_cmd=14 # 1 simxInt. Generated by the client (and used in a reply by the server). The command combined with the operation mode of the command. -simx_cmdheaderoffset_delay_or_split =18 # 1 simxUShort. Generated by the client or server. The amount of delay in ms of a continuous command or the max. pure data size to send at once (applies to split commands). -simx_cmdheaderoffset_sim_time =20 # 1 simxInt. Generated by the server. The simulation time (in ms) when the command was executed (or 0 if simulation is not running) -simx_cmdheaderoffset_status =24 # 1 byte. Generated by the server. (1 bit 0 is set --> error in function execution on server side). The client writes bit 1 if command cannot be overwritten -simx_cmdheaderoffset_reserved =25 # 1 byte. Not yet used - - - - - -# Regular operation modes -simx_opmode_oneshot =0x000000 # sends command as one chunk. Reply will also come as one chunk. Doesn't wait for the reply. -simx_opmode_blocking =0x010000 # sends command as one chunk. Reply will also come as one chunk. Waits for the reply (_REPLY_WAIT_TIMEOUT_IN_MS is the timeout). -simx_opmode_oneshot_wait =0x010000 # sends command as one chunk. Reply will also come as one chunk. Waits for the reply (_REPLY_WAIT_TIMEOUT_IN_MS is the timeout). -simx_opmode_continuous =0x020000 -simx_opmode_streaming =0x020000 # sends command as one chunk. Command will be stored on the server and always executed - #(every x ms (as far as possible) where x can be 0-65535. just add x to opmode_continuous). - # A reply will be sent continuously each time as one chunk. Doesn't wait for the reply. - -# Operation modes for heavy data -simx_opmode_oneshot_split =0x030000 # sends command as several chunks (max chunk size is x bytes where x can be _MIN_SPLIT_AMOUNT_IN_BYTES-65535. Just add x to opmode_oneshot_split). Reply will also come as several chunks. Doesn't wait for the reply. -simx_opmode_continuous_split =0x040000 -simx_opmode_streaming_split =0x040000 # sends command as several chunks (max chunk size is x bytes where x can be _MIN_SPLIT_AMOUNT_IN_BYTES-65535. Just add x to opmode_continuous_split). Command will be stored on the server and always executed. A reply will be sent continuously each time as several chunks. Doesn't wait for the reply. - -# Special operation modes -simx_opmode_discontinue =0x050000 # removes and cancels all commands stored on the client or server side (also continuous commands) -simx_opmode_buffer =0x060000 # doesn't send anything but checks if a reply for the given command is available in the input buffer (i.e. previously received from the server) -simx_opmode_remove =0x070000 # doesn't send anything and doesn't return any specific value. It just erases a similar command reply in the inbox (to free some memory) - - -# Command return codes -simx_return_ok =0x000000 -simx_return_novalue_flag =0x000001 # input buffer doesn't contain the specified command -simx_return_timeout_flag =0x000002 # command reply not received in time for opmode_oneshot_wait operation mode -simx_return_illegal_opmode_flag =0x000004 # command doesn't support the specified operation mode -simx_return_remote_error_flag =0x000008 # command caused an error on the server side -simx_return_split_progress_flag =0x000010 # previous similar command not yet fully processed (applies to opmode_oneshot_split operation modes) -simx_return_local_error_flag =0x000020 # command caused an error on the client side -simx_return_initialize_error_flag =0x000040 # simxStart was not yet called - -# Following for backward compatibility (same as above) -simx_error_noerror =0x000000 -simx_error_novalue_flag =0x000001 # input buffer doesn't contain the specified command -simx_error_timeout_flag =0x000002 # command reply not received in time for opmode_oneshot_wait operation mode -simx_error_illegal_opmode_flag =0x000004 # command doesn't support the specified operation mode -simx_error_remote_error_flag =0x000008 # command caused an error on the server side -simx_error_split_progress_flag =0x000010 # previous similar command not yet fully processed (applies to opmode_oneshot_split operation modes) -simx_error_local_error_flag =0x000020 # command caused an error on the client side -simx_error_initialize_error_flag =0x000040 # simxStart was not yet called - - diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index c4a67ea0..72b6b2b8 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -366,6 +366,6 @@ def __del__(self): try: - from micropsi_core.world.vrep_world import vrep_world + from micropsi_core.world.vrep import vrep_world except ImportError as e: sys.stdout.write("Could not import vrep world.\nError: %s \n\n" % e.msg) From 6d681016130ded1fa35f6f6dffe135422b376ee5 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 6 May 2016 12:05:23 +0200 Subject: [PATCH 065/945] Generalizing towards other robots MTB_Robot should work --- micropsi_core/world/vrep/vrep_world.py | 148 +++++++++++++++---------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index a69762d5..b23245da 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -4,7 +4,6 @@ import numpy as np import matplotlib matplotlib.use('agg') -import matplotlib.image as mpimg import matplotlib.pyplot as plt from io import BytesIO @@ -19,21 +18,23 @@ class VREPWorld(World): """ A vrep robot simulator environment In V-REP, the following setup has to be performed: - - An LBR_iiwa_7_R800 has to have been added to the scene - simExtRemoteApiStart(19999) has to have been run - the simulation must have been started """ + supported_worldadapters = ['Robot'] assets = { 'template': 'vrep/vrep.tpl', 'js': "vrep/vrep.js", } - supported_worldadapters = ['iiwa'] - def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=None, uid=None, version=1, config={}): World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version) + self.robot_name = config['robot_name'] + self.vision_type = config['vision_type'] + self.control_type = config['control_type'] + vrep.simxFinish(-1) # just in case, close all opened connections self.clientID = vrep.simxStart('127.0.0.1', 19999, True, 0, 5000, 5) # Connect to V-REP if self.clientID == -1: @@ -46,15 +47,14 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.logger.info('Ping time to v-rep: %dms' % pingtime) - res, self.iiwa_handle = vrep.simxGetObjectHandle(self.clientID, "LBR_iiwa_7_R800", vrep.simx_opmode_blocking) + res, self.iiwa_handle = vrep.simxGetObjectHandle(self.clientID, self.robot_name, vrep.simx_opmode_blocking) self.handle_res(res) if self.iiwa_handle < 1: - raise Exception("There seems to be no robot with the name LBR_iiwa_7_R800 in the v-rep simulation.") + raise Exception("There seems to be no robot with the name %s in the v-rep simulation." % self.robot_name) res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) self.handle_res(res) - if len(self.joints) != 7: - raise Exception("Could not get handles for all 7 joints of the LBR_iiwa_7_R800.") + self.logger.info("Found robot with %d joints" % len(self.joints)) res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) self.handle_res(res) @@ -64,26 +64,27 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) if res != 0 and res != 1: self.handle_res(res) - res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[6], -1, vrep.simx_opmode_streaming) + res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) if res != 0 and res != 1: self.handle_res(res) - res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) - self.handle_res(res) - if self.observer_handle < 1: - self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") - else: - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) - if res != 0 and res != 1: - self.handle_res(res) + if self.vision_type == "grayscale": + res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.observer_handle < 1: + self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") else: - time.sleep(1) - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) - self.vision_resolution = resolution - if len(resolution) != 2: - raise Exception("Could not determine vision resolution after 1 second wait time.") + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) + if res != 0 and res != 1: + self.handle_res(res) else: - self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) + time.sleep(1) + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) + self.vision_resolution = resolution + if len(resolution) != 2: + raise Exception("Could not determine vision resolution after 1 second wait time.") + else: + self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) def handle_res(self, res): if res != vrep.simx_return_ok: @@ -91,23 +92,42 @@ def handle_res(self, res): self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) def get_world_view(self, step): - plots = {} - for uid in self.agents: - image = self.agents[uid].image - if image: - bio = BytesIO() - image.figure.savefig(bio, format="png") - plots[uid] = base64.encodebytes(bio.getvalue()).decode("utf-8") - - return { - 'objects': self.get_world_objects(), - 'agents': self.data.get('agents', {}), - 'current_step': self.current_step, - 'plots': plots - } - - -class iiwa(ArrayWorldAdapter): + if self.vision_type == "grayscale": + plots = {} + for uid in self.agents: + image = self.agents[uid].image + if image: + bio = BytesIO() + image.figure.savefig(bio, format="png") + plots[uid] = base64.encodebytes(bio.getvalue()).decode("utf-8") + + return { + 'objects': self.get_world_objects(), + 'agents': self.data.get('agents', {}), + 'current_step': self.current_step, + 'plots': plots + } + else: + return None + + @staticmethod + def get_config_options(): + return [ + {'name': 'robot_name', + 'description': 'The name of the robot object in V-REP', + 'default': 'LBR_iiwa_7_R800'}, + {'name': 'control_type', + 'description': 'The type of input sent to the robot', + 'default': 'force/torque', + 'options': ["force/torque", "angles"]}, + {'name': 'vision_type', + 'description': 'Type of vision information to receive', + 'default': 'none', + 'options': ["none", "grayscale"]} + ] + + +class Robot(ArrayWorldAdapter): def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) @@ -129,6 +149,8 @@ def __init__(self, world, uid=None, **data): for i in range(len(self.world.joints)): self.available_datasources.append("joint_force_%s" % str(i + 1)) + self.current_angle_target_values = np.zeros_like(self.world.joints) + self.restart_offset = 0 self.execute_offset = 1 self.joint_offset = 2 @@ -136,17 +158,18 @@ def __init__(self, world, uid=None, **data): self.reward_offset = 0 self.joint_angle_offset = 1 self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) - self.image_offset = self.joint_force_offset + len(self.world.joints) - self.image_length = self.world.vision_resolution[0] * self.world.vision_resolution[1] - for y in range(self.world.vision_resolution[1]): - for x in range(self.world.vision_resolution[0]): - self.available_datasources.append("px_%d_%d" % (x, y)) + if self.world.vision_type == "grayscale": + self.image_offset = self.joint_force_offset + len(self.world.joints) + self.image_length = self.world.vision_resolution[0] * self.world.vision_resolution[1] - self.image = plt.imshow(np.zeros(shape=(self.world.vision_resolution[0],self.world.vision_resolution[1])), cmap="bone") - self.image.norm.vmin = 0 - self.image.norm.vmax = 1 - self.current_angle_target_values = np.zeros_like(self.world.joints) + for y in range(self.world.vision_resolution[1]): + for x in range(self.world.vision_resolution[0]): + self.available_datasources.append("px_%d_%d" % (x, y)) + + self.image = plt.imshow(np.zeros(shape=(self.world.vision_resolution[0],self.world.vision_resolution[1])), cmap="bone") + self.image.norm.vmin = 0 + self.image.norm.vmax = 1 def get_available_datasources(self): return self.available_datasources @@ -174,30 +197,35 @@ def update_data_sources_and_targets(self): self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.world.joints)]) vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): - tval = self.current_angle_target_values[i] * math.pi - vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + if self.world.control_type == "force/torque": + tval = self.current_angle_target_values[i] * math.pi + vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + elif self.world.control_type == "angles": + tval = self.current_angle_target_values[i] * 180 + vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) # get data and feedback # read reward value if self.world.ball_handle > 0: res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) - res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[6], -1, vrep.simx_opmode_streaming) + res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) self.datasource_values[self.reward_offset] = -dist # read joint angle and force values res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) - if len(data) != 14: - self.world.logger.warn("Could not get robot state values, skipping update") - return - for i, joint_handle in enumerate(self.world.joints): target_angle = self.datatarget_values[self.joint_offset + i] - angle = data[i*2] / math.pi - force = data[i*2 + 1] - if abs(angle) - abs(target_angle) < .001 and execute: - self.datatarget_feedback_values[self.joint_offset + i] = 1 + angle = 0 + force = 0 + if self.world.control_type == "force/torque": + angle = data[i*2] / math.pi + force = data[i*2 + 1] + if abs(angle) - abs(target_angle) < .001 and execute: + self.datatarget_feedback_values[self.joint_offset + i] = 1 + elif self.world.control_type == "angles": + angle = data[i * 2] / 180 self.datasource_values[self.joint_angle_offset + i] = angle self.datasource_values[self.joint_force_offset + i] = force From 404f58198a9f3379df916f0ccb8884d5553f99e2 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 6 May 2016 15:05:34 +0200 Subject: [PATCH 066/945] Fix disabled vision bug --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index b23245da..cb021ca9 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -231,7 +231,7 @@ def update_data_sources_and_targets(self): # read vision data # if no observer present, don't query vision data - if self.world.observer_handle == 0: + if self.world.vision_type != "grayscale": return res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) From 28cc6f7ca4c861cc6d8072ac00861459fde0432c Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 6 May 2016 15:12:05 +0200 Subject: [PATCH 067/945] Angle calculation correction --- micropsi_core/world/vrep/vrep_world.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index cb021ca9..90f51116 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -197,11 +197,10 @@ def update_data_sources_and_targets(self): self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.world.joints)]) vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): + tval = self.current_angle_target_values[i] * math.pi if self.world.control_type == "force/torque": - tval = self.current_angle_target_values[i] * math.pi vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "angles": - tval = self.current_angle_target_values[i] * 180 vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) @@ -225,7 +224,7 @@ def update_data_sources_and_targets(self): if abs(angle) - abs(target_angle) < .001 and execute: self.datatarget_feedback_values[self.joint_offset + i] = 1 elif self.world.control_type == "angles": - angle = data[i * 2] / 180 + angle = data[i * 2] / math.pi self.datasource_values[self.joint_angle_offset + i] = angle self.datasource_values[self.joint_force_offset + i] = force From d8c0d25757de3191fb6bfb7a630d70295f855d1c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 6 May 2016 18:30:43 +0200 Subject: [PATCH 068/945] prepare for fat nodetypes: arrays of slots and gates --- micropsi_core/nodenet/node.py | 68 ++++++++++++++++++- .../theano_engine/theano_definitions.py | 16 ++--- .../nodenet/theano_engine/theano_node.py | 2 + 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 793706d9..6e59c1da 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -619,7 +619,7 @@ def nodefunction_name(self, nodefunction_name): def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, gate_defaults=None, - symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category=''): + symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', is_fat=False, fat_config=None): """Initializes or creates a nodetype. Arguments: @@ -634,9 +634,10 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._nodefunction_definition = None self._nodefunction_name = None + self.is_fat = is_fat self.name = name - self.slottypes = slottypes or {} - self.gatetypes = gatetypes or {} + self.slottypes = slottypes or [] + self.gatetypes = gatetypes or [] self.path = path self.category = category @@ -664,3 +665,64 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.nodefunction_name = nodefunction_name else: self.nodefunction = None + + if self.is_fat: + self.fat_config = fat_config + + def get_number_of_gates(self): + if self.is_fat: + num = 0 + for k in self.gatetypes: + num += self.fat_config.get(k, 1) + return num + else: + return len(self.gatetypes) + + def get_number_of_slots(self): + if self.is_fat: + num = 0 + for k in self.gatetypes: + num += self.fat_config.get(k, 1) + return num + else: + return len(self.slottypes) + + def get_numerical_gate_type(self, type): + if self.is_fat: + num = -1 + for i, gate in enumerate(self.gatetypes): + num += i * self.fat_config.get(gate, 1) + if gate == type: + return num + else: + return self.gatetypes.index(type) + + def get_string_gate_type(self, type): + if self.is_fat: + num = -1 + for i, gate in enumerate(self.gatetypes): + if num <= type: + return gate + num += i * self.fat_config.get(gate, 1) + else: + return self.gatetypes[type] + + def get_numerical_slot_type(self, type): + if self.is_fat: + num = -1 + for i, gate in enumerate(self.gatetypes): + num += i * self.fat_config.get(gate, 1) + if gate == type: + return num + else: + return self.slottypes.index(type) + + def get_string_slot_type(self, type): + if self.is_fat: + num = -1 + for i, slot in enumerate(self.slottypes): + if num <= type: + return slot + num += i * self.fat_config.get(slot, 1) + else: + return self.slottypes[type] diff --git a/micropsi_core/nodenet/theano_engine/theano_definitions.py b/micropsi_core/nodenet/theano_engine/theano_definitions.py index 055349d4..d0a6d664 100644 --- a/micropsi_core/nodenet/theano_engine/theano_definitions.py +++ b/micropsi_core/nodenet/theano_engine/theano_definitions.py @@ -47,7 +47,7 @@ def get_numerical_gate_type(type, nodetype=None): if nodetype is not None and type in nodetype.gatetypes: - return nodetype.gatetypes.index(type) + return nodetype.get_numerical_gate_type(type) elif type == "gen": return GEN elif type == "por": @@ -74,7 +74,7 @@ def get_numerical_gate_type(type, nodetype=None): def get_string_gate_type(type, nodetype=None): if nodetype is not None and len(nodetype.gatetypes) > 0: - return nodetype.gatetypes[type] + return nodetype.get_string_gate_type(type) elif type == GEN: return "gen" elif type == POR: @@ -101,7 +101,7 @@ def get_string_gate_type(type, nodetype=None): def get_numerical_slot_type(type, nodetype=None): if nodetype is not None and type in nodetype.slottypes: - return nodetype.slottypes.index(type) + return nodetype.get_numerical_slot_type(type) elif type == "gen": return GEN elif type == "por": @@ -128,7 +128,7 @@ def get_numerical_slot_type(type, nodetype=None): def get_string_slot_type(type, nodetype=None): if nodetype is not None and len(nodetype.slottypes) > 0: - return nodetype.slottypes[type] + return nodetype.get_string_slot_type(type) elif type == GEN: return "gen" elif type == POR: @@ -258,7 +258,7 @@ def get_elements_per_type(type, nativemodules=None): return 0 elif nativemodules is not None and get_string_node_type(type, nativemodules) in nativemodules: native_module_definition = nativemodules[get_string_node_type(type, nativemodules)] - return max(len(native_module_definition.gatetypes), len(native_module_definition.slottypes)) + return max(native_module_definition.get_number_of_gates(), native_module_definition.get_number_of_slots()) else: raise ValueError("Supplied type is not a valid node type: "+str(type)) @@ -284,7 +284,7 @@ def get_gates_per_type(type, nativemodules=None): return 0 elif nativemodules is not None and get_string_node_type(type, nativemodules) in nativemodules: native_module_definition = nativemodules[get_string_node_type(type, nativemodules)] - return len(native_module_definition.gatetypes) + return native_module_definition.get_number_of_gates() else: raise ValueError("Supplied type is not a valid node type: "+str(type)) @@ -309,8 +309,8 @@ def get_slots_per_type(type, nativemodules=None): elif type == COMMENT: return 0 elif nativemodules is not None and get_string_node_type(type, nativemodules) in nativemodules: - native_module_definition = nativemodules[get_string_node_type(type, nativemodules)] - return len(native_module_definition.slottypes) + native_module = nativemodules[get_string_node_type(type, nativemodules)] + return native_module.get_number_of_slots() else: raise ValueError("Supplied type is not a valid node type: "+str(type)) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index b4192a03..dc7a2423 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -31,6 +31,8 @@ def __init__(self, nodenet, partition, parent_uid, uid, type, parameters={}, **_ Node.__init__(self, strtype, nodenet.get_nodetype(strtype)) + self.is_fat = self._nodetype.is_fat + if strtype in nodenet.native_modules or strtype == "Comment": self.slot_activation_snapshot = {} self._state = {} From 41de7804aa4e5af90144f59f98bae0a501353762 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 6 May 2016 21:42:53 +0200 Subject: [PATCH 069/945] Fixing emo datasource/target leak --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index e11e6da7..0a6f86b5 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1417,7 +1417,7 @@ def _rebuild_sensor_actor_indices(self, partition=None): def get_datasources(self): """ Returns a sorted list of available datasources, including worldadapter datasources and readable modulators""" - datasources = self.worldadapter_instance.get_available_datasources() if self.worldadapter_instance else [] + datasources = list(self.worldadapter_instance.get_available_datasources()) if self.worldadapter_instance else [] if self.use_modulators: for item in sorted(DoernerianEmotionalModulators.readable_modulators): datasources.append(item) @@ -1426,7 +1426,7 @@ def get_datasources(self): def get_datatargets(self): """ Returns a sorted list of available datatargets, including worldadapter datatargets and writeable modulators""" - datatargets = self.worldadapter_instance.get_available_datatargets() if self.worldadapter_instance else [] + datatargets = list(self.worldadapter_instance.get_available_datatargets()) if self.worldadapter_instance else [] if self.use_modulators: for item in sorted(DoernerianEmotionalModulators.writeable_modulators): datatargets.append(item) From a8b6bb468aa165e95694d91647c08a3a46ec09a1 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 6 May 2016 21:45:09 +0200 Subject: [PATCH 070/945] No step-0 arrays of the wrong size any more --- micropsi_core/world/worldadapter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 073e56f6..53cf0665 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -132,10 +132,10 @@ class ArrayWorldAdapter(WorldAdapter, metaclass=ABCMeta): Numpy arrays can be passed directly into the engine. """ def __init__(self, world, uid=None, **data): - WorldAdapter.__init__(self, world, duid=uid) - self.datasource_values = [] - self.datatarget_values = [] - self.datatarget_feedback_values = [] + WorldAdapter.__init__(self, world, uid=uid) + self.datasource_values = [0] * len(self.get_available_datasources()) + self.datatarget_values = [0] * len(self.get_available_datatargets()) + self.datatarget_feedback_values = [0] * len(self.get_available_datatargets()) def get_datasource_value(self, key): """allows the agent to read a value from a datasource""" From e3e7f321ad91c82668fcfecbd458954460410433 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 6 May 2016 21:49:36 +0200 Subject: [PATCH 071/945] Following ArrayWorldAdapter changes from master --- micropsi_core/world/vrep/vrep_world.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 90f51116..e3f85ae5 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -130,7 +130,6 @@ def get_config_options(): class Robot(ArrayWorldAdapter): def __init__(self, world, uid=None, **data): - super().__init__(world, uid, **data) self.available_datatargets = [] self.available_datasources = [] @@ -140,15 +139,17 @@ def __init__(self, world, uid=None, **data): self.available_datatargets.append("restart") self.available_datatargets.append("execute") - for i in range(len(self.world.joints)): + for i in range(len(world.joints)): self.available_datatargets.append("joint_%s" % str(i + 1)) - for i in range(len(self.world.joints)): + for i in range(len(world.joints)): self.available_datasources.append("joint_angle_%s" % str(i + 1)) - for i in range(len(self.world.joints)): + for i in range(len(world.joints)): self.available_datasources.append("joint_force_%s" % str(i + 1)) + super().__init__(world, uid, **data) + self.current_angle_target_values = np.zeros_like(self.world.joints) self.restart_offset = 0 From b55d43326c7d66c627b5ab7b3f92e3fb7a5a226c Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 9 May 2016 16:43:31 +0200 Subject: [PATCH 072/945] Movement actuators, untested --- micropsi_core/world/vrep/vrep_world.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index e3f85ae5..e8b5e0da 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -119,7 +119,7 @@ def get_config_options(): {'name': 'control_type', 'description': 'The type of input sent to the robot', 'default': 'force/torque', - 'options': ["force/torque", "angles"]}, + 'options': ["force/torque", "angles", "movements"]}, {'name': 'vision_type', 'description': 'Type of vision information to receive', 'default': 'none', @@ -203,6 +203,9 @@ def update_data_sources_and_targets(self): vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "angles": vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + elif self.world.control_type == "movements": + tval += self.datasource_values[self.joint_angle_offset + i] + vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) # get data and feedback @@ -226,6 +229,8 @@ def update_data_sources_and_targets(self): self.datatarget_feedback_values[self.joint_offset + i] = 1 elif self.world.control_type == "angles": angle = data[i * 2] / math.pi + elif self.world.control_type == "movements": + angle = data[i * 2] / math.pi self.datasource_values[self.joint_angle_offset + i] = angle self.datasource_values[self.joint_force_offset + i] = force From b6304313cc4455542c4975008bb757187d429496 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 9 May 2016 17:26:56 +0200 Subject: [PATCH 073/945] Fixing stupid initialization bug --- micropsi_core/world/vrep/vrep_world.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index e8b5e0da..00ded7b8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -180,6 +180,8 @@ def get_available_datatargets(self): def update_data_sources_and_targets(self): + old_datasource_values = np.array(self.datasource_values) + self.datatarget_feedback_values = [0] * len(self.available_datatargets) self.datasource_values = [0] * len(self.available_datasources) @@ -204,7 +206,7 @@ def update_data_sources_and_targets(self): elif self.world.control_type == "angles": vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "movements": - tval += self.datasource_values[self.joint_angle_offset + i] + tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) From ad425e3cc02f713dc2f9d9acf4dc93034da0f27d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 9 May 2016 19:22:23 +0200 Subject: [PATCH 074/945] drop the FatGate Approach, instantiate each gate --- micropsi_core/nodenet/node.py | 87 +++++++------------ .../theano_engine/theano_definitions.py | 16 ++-- 2 files changed, 37 insertions(+), 66 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 6e59c1da..2fc3c2fe 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -668,61 +668,32 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non if self.is_fat: self.fat_config = fat_config - - def get_number_of_gates(self): - if self.is_fat: - num = 0 - for k in self.gatetypes: - num += self.fat_config.get(k, 1) - return num - else: - return len(self.gatetypes) - - def get_number_of_slots(self): - if self.is_fat: - num = 0 - for k in self.gatetypes: - num += self.fat_config.get(k, 1) - return num - else: - return len(self.slottypes) - - def get_numerical_gate_type(self, type): - if self.is_fat: - num = -1 - for i, gate in enumerate(self.gatetypes): - num += i * self.fat_config.get(gate, 1) - if gate == type: - return num - else: - return self.gatetypes.index(type) - - def get_string_gate_type(self, type): - if self.is_fat: - num = -1 - for i, gate in enumerate(self.gatetypes): - if num <= type: - return gate - num += i * self.fat_config.get(gate, 1) - else: - return self.gatetypes[type] - - def get_numerical_slot_type(self, type): - if self.is_fat: - num = -1 - for i, gate in enumerate(self.gatetypes): - num += i * self.fat_config.get(gate, 1) - if gate == type: - return num - else: - return self.slottypes.index(type) - - def get_string_slot_type(self, type): - if self.is_fat: - num = -1 - for i, slot in enumerate(self.slottypes): - if num <= type: - return slot - num += i * self.fat_config.get(slot, 1) - else: - return self.slottypes[type] + self.gatetypes = [] + self.slottypes = [] + self.gatetypes = [i for i in (['sub', 'sur'] + gatetypes) if i not in self.gatetypes] + self.slottypes = [i for i in (['sub', 'sur'] + slottypes) if i not in self.slottypes] + gates = [] + slots = [] + fat_config['groupgates'] = [] + fat_config['groupslots'] = [] + for g in self.gatetypes: + group = ["%s:%d" % (g, i) for i in range(fat_config['gates'].get(g, 1))] + fat_config['groupgates'].append(group[0]) + gates.extend(group) + for s in self.slottypes: + group = ["%s:%d" % (s, i) for i in range(fat_config['slots'].get(s, 1))] + fat_config['groupslots'].append(group[0]) + slots.extend(group) + self.gatetypes = gates + self.slottypes = slots + + self.gate_defaults = {} + for g in self.fat_config['groupgates']: + self.gate_defaults[g] = Nodetype.GATE_DEFAULTS.copy() + + if gate_defaults is not None: + for g in gate_defaults: + for key in gate_defaults[g]: + if g not in self.gate_defaults: + raise Exception("Invalid gate default value for nodetype %s: Gate %s not found" % (name, g)) + self.gate_defaults[g][key] = gate_defaults[g][key] diff --git a/micropsi_core/nodenet/theano_engine/theano_definitions.py b/micropsi_core/nodenet/theano_engine/theano_definitions.py index d0a6d664..055349d4 100644 --- a/micropsi_core/nodenet/theano_engine/theano_definitions.py +++ b/micropsi_core/nodenet/theano_engine/theano_definitions.py @@ -47,7 +47,7 @@ def get_numerical_gate_type(type, nodetype=None): if nodetype is not None and type in nodetype.gatetypes: - return nodetype.get_numerical_gate_type(type) + return nodetype.gatetypes.index(type) elif type == "gen": return GEN elif type == "por": @@ -74,7 +74,7 @@ def get_numerical_gate_type(type, nodetype=None): def get_string_gate_type(type, nodetype=None): if nodetype is not None and len(nodetype.gatetypes) > 0: - return nodetype.get_string_gate_type(type) + return nodetype.gatetypes[type] elif type == GEN: return "gen" elif type == POR: @@ -101,7 +101,7 @@ def get_string_gate_type(type, nodetype=None): def get_numerical_slot_type(type, nodetype=None): if nodetype is not None and type in nodetype.slottypes: - return nodetype.get_numerical_slot_type(type) + return nodetype.slottypes.index(type) elif type == "gen": return GEN elif type == "por": @@ -128,7 +128,7 @@ def get_numerical_slot_type(type, nodetype=None): def get_string_slot_type(type, nodetype=None): if nodetype is not None and len(nodetype.slottypes) > 0: - return nodetype.get_string_slot_type(type) + return nodetype.slottypes[type] elif type == GEN: return "gen" elif type == POR: @@ -258,7 +258,7 @@ def get_elements_per_type(type, nativemodules=None): return 0 elif nativemodules is not None and get_string_node_type(type, nativemodules) in nativemodules: native_module_definition = nativemodules[get_string_node_type(type, nativemodules)] - return max(native_module_definition.get_number_of_gates(), native_module_definition.get_number_of_slots()) + return max(len(native_module_definition.gatetypes), len(native_module_definition.slottypes)) else: raise ValueError("Supplied type is not a valid node type: "+str(type)) @@ -284,7 +284,7 @@ def get_gates_per_type(type, nativemodules=None): return 0 elif nativemodules is not None and get_string_node_type(type, nativemodules) in nativemodules: native_module_definition = nativemodules[get_string_node_type(type, nativemodules)] - return native_module_definition.get_number_of_gates() + return len(native_module_definition.gatetypes) else: raise ValueError("Supplied type is not a valid node type: "+str(type)) @@ -309,8 +309,8 @@ def get_slots_per_type(type, nativemodules=None): elif type == COMMENT: return 0 elif nativemodules is not None and get_string_node_type(type, nativemodules) in nativemodules: - native_module = nativemodules[get_string_node_type(type, nativemodules)] - return native_module.get_number_of_slots() + native_module_definition = nativemodules[get_string_node_type(type, nativemodules)] + return len(native_module_definition.slottypes) else: raise ValueError("Supplied type is not a valid node type: "+str(type)) From 75ac907af2ba5c7c9be9dcc402a17dca0b1aaf12 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 9 May 2016 19:24:35 +0200 Subject: [PATCH 075/945] implement fatNM in node --- .../nodenet/theano_engine/theano_node.py | 42 ++++++++++++++++++- .../nodenet/theano_engine/theano_partition.py | 13 ++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index dc7a2423..8f049fcb 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- +import os +import numpy as np + +from micropsi_core.runtime import PERSISTENCY_PATH, NODENET_DIRECTORY from micropsi_core.nodenet.node import Node, Gate, Slot from micropsi_core.nodenet.theano_engine.theano_link import TheanoLink from micropsi_core.nodenet.theano_engine.theano_stepoperators import * from micropsi_core.nodenet.theano_engine.theano_definitions import * -import numpy as np class TheanoNode(Node): @@ -33,6 +36,8 @@ def __init__(self, nodenet, partition, parent_uid, uid, type, parameters={}, **_ self.is_fat = self._nodetype.is_fat + self.datafile = os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, '%s_node_%s.npz' % (self._nodenet.uid, self.uid)) + if strtype in nodenet.native_modules or strtype == "Comment": self.slot_activation_snapshot = {} self._state = {} @@ -42,6 +47,9 @@ def __init__(self, nodenet, partition, parent_uid, uid, type, parameters={}, **_ else: self.parameters = {} + if self.is_fat: + self.slot_fat_snapshot = None + @property def uid(self): return self._uid @@ -136,7 +144,10 @@ def clone_non_default_gate_parameters(self, gate_type=None): g_theta = self._partition.g_theta.get_value(borrow=True) gatemap = {} - gate_types = self.nodetype.gate_defaults.keys() + if self.is_fat: + gate_types = self.nodetype.fat_config['groupgates'] + else: + gate_types = self.nodetype.gate_defaults.keys() if gate_type is not None: if gate_type in gate_types: gate_types = [gate_type] @@ -178,6 +189,10 @@ def take_slot_activation_snapshot(self): for slottype in self.nodetype.slottypes: self.slot_activation_snapshot[slottype] = \ a_array[self._partition.allocated_node_offsets[self._id] + get_numerical_slot_type(slottype, self.nodetype)] + if self.is_fat: + start = self._partition.allocated_node_offsets[self._id] + end = start + len(self._nodetype.slottypes) + self.slot_fat_snapshot = a_array[start:end] def get_slot(self, type): if type not in self.__slotcache: @@ -402,6 +417,29 @@ def node_function(self): else: raise + def get_activation_array(self): + return self.slot_fat_snapshot + + def set_activation_array(self, new_activations): + start = self._partition.allocated_node_offsets[node_from_id(self.uid)] + end = start + len(self._nodetype.gatetypes) + a_array = self._partition.a.get_value(borrow=True) + a_array[start:end] = new_activations + self._partition.a.set_value(a_array, borrow=True) + + def get_gate_links(self): + pass + + def get_slot_links(self): + pass + + def save_data(self, data): + np.savez(self.datafile, data=data) + + def load_data(self): + if os.path.isfile(self.datafile): + return np.load(self.datafile)['data'] + class TheanoGate(Gate): """ diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 5d59bd40..138c96d1 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1899,8 +1899,14 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li gate_parameters = {} gate_activations = {} links = {} - for gate in self.nodenet.get_nodetype(strtype).gatetypes: - numericalgate = get_numerical_gate_type(gate, self.nodenet.get_nodetype(strtype)) + + if nodetype.is_fat: + gates = nodetype.fat_config['groupgates'] + else: + gates = nodetype.gatetypes + + for gate in gates: + numericalgate = get_numerical_gate_type(gate, nodetype) element = self.allocated_node_offsets[id] + numericalgate gate_functions[gate] = get_string_gatefunction_type(g_function_selector[element]) @@ -1993,7 +1999,8 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li "activation": float(a[self.allocated_node_offsets[id] + GEN])}}, "activation": float(a[self.allocated_node_offsets[id] + GEN]), "gate_activations": gate_activations, - "gate_functions": gate_functions} + "gate_functions": gate_functions, + "is_fat": nodetype.is_fat} if complete: data['index'] = id if include_links: From 7741f258ce208e466318990166dfec35a7a9dbb3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 9 May 2016 19:25:16 +0200 Subject: [PATCH 076/945] basic test --- micropsi_core/tests/test_node.py | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 1d8f30df..3529db23 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -54,3 +54,52 @@ def test_entity_positions_as_tuples(test_nodenet): nodespace.position = (13, 23, 42) assert node.position == [23, 42, 0] assert nodespace.position == [13, 23, 42] + + +@pytest.mark.engine("theano_engine") +def test_fat_native_modules(test_nodenet, resourcepath): + import os + import numpy as np + with open(os.path.join(resourcepath, 'nodetypes.json'), 'w') as fp: + fp.write(""" + {"PhatNM": { + "name": "PhatNM", + "slottypes": ["gen", "A_in", "B_in"], + "gatetypes": ["gen", "A_out", "B_out"], + "nodefunction_name": "phatNM", + "is_fat": 1, + "symbol": "F", + "fat_config": { + "gates": { + "A_out": 768, + "B_out": 13 + }, + "slots": { + "A_in": 1024, + "B_in": 62 + } + } + }}""") + with open(os.path.join(resourcepath, 'nodefunctions.py'), 'w') as fp: + fp.write(""" +def phatNM(netapi, node, **_): + pass""") + + micropsi.reload_native_modules() + netapi = micropsi.nodenets[test_nodenet].netapi + node = netapi.create_node("PhatNM", None, "phatty") + node.take_slot_activation_snapshot() + data = node.get_activation_array() + assert len(data) == 1024 + 62 + 3 # fat_slots + gen/sub/sur + new_activation = np.random.rand(768 + 13 + 3) # fat gates + gen/sub/sur + node.set_activation_array(new_activation) + target = netapi.create_node("Register", None, "Target") + for g in node.get_gate_types(): + netapi.link(node, g, target, 'gen') + micropsi.step_nodenet(test_nodenet) + assert target.activation > 0 + node.save_data(new_activation) + assert np.all(node.load_data() == new_activation) + micropsi.save_nodenet(test_nodenet) + micropsi.revert_nodenet(test_nodenet) + assert np.all(node.load_data() == new_activation) From 18841ee5a6215ccbd87c3e3422440e846ca27033 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 9 May 2016 19:25:35 +0200 Subject: [PATCH 077/945] grow rootpartition during load() if need be --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index e11e6da7..d8f5dc4b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -491,6 +491,15 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only for nodespace in nodespaces_to_merge: self.merge_nodespace_data(nodespace, nodenet_data['nodespaces'], uidmap, keep_uids) + # make sure rootpartition has enough NoN, NoE + if native_module_instances_only: + non = noe = 0 + for uid in nodenet_data.get('nodes', {}): + non += 1 + noe += get_elements_per_type(get_numerical_node_type(nodenet_data['nodes'][uid]['type'], self.native_modules), self.native_modules) + if non > self.rootpartition.NoN or noe > self.rootpartition.NoE: + self.rootpartition.announce_nodes(non, math.ceil(noe / non)) + # merge in nodes for uid in nodenet_data.get('nodes', {}): data = nodenet_data['nodes'][uid] From b07c0841e7d1eaba3bedd15d80c3e814253cd6f2 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 9 May 2016 19:27:07 +0200 Subject: [PATCH 078/945] fix reload_native_modules instantiate new nodetypes first, use nodetype instances to infer slots and gates --- .../nodenet/theano_engine/theano_nodenet.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index d8f5dc4b..b1ef28bd 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1090,6 +1090,12 @@ def reload_native_modules(self, native_modules): # check which instances need to be recreated because of gate/slot changes and keep their .data instances_to_recreate = {} instances_to_delete = {} + + # create the new nodetypes + self.native_modules = {} + for type, data in native_modules.items(): + self.native_modules[type] = Nodetype(nodenet=self, **native_modules[type]) + for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): if instance.type not in native_modules: @@ -1100,9 +1106,9 @@ def reload_native_modules(self, native_modules): numeric_id = node_from_id(uid) number_of_elements = len(np.where(partition.allocated_elements_to_nodes == numeric_id)[0]) - new_numer_of_elements = max(len(native_modules[instance.type].get('slottypes', [])), len(native_modules[instance.type].get('gatetypes', []))) + new_numer_of_elements = max(len(self.native_modules[instance.type].slottypes), len(self.native_modules[instance.type].gatetypes)) if number_of_elements != new_numer_of_elements: - self.logger.warn("Number of elements changed for node type %s from %d to %d, recreating instance %s" % + self.logger.warning("Number of elements changed for node type %s from %d to %d, recreating instance %s" % (instance.type, number_of_elements, new_numer_of_elements, uid)) instances_to_recreate[uid] = instance.get_data(complete=True, include_links=False) @@ -1112,11 +1118,6 @@ def reload_native_modules(self, native_modules): for uid in instances_to_recreate.keys(): self.delete_node(uid) - # update the node functions of all Nodetypes - self.native_modules = {} - for type, data in native_modules.items(): - self.native_modules[type] = Nodetype(nodenet=self, **native_modules[type]) - # update the living instances that have the same slot/gate numbers new_instances = {} for id, instance in partition.native_module_instances.items(): From 8b0685975cb5dd3cba01dd0db8eca83463d9767c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 10 May 2016 16:58:30 +0200 Subject: [PATCH 079/945] fix get_changes implementation --- micropsi_server/micropsi_app.py | 8 +++++++- micropsi_server/tests/test_json_api.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 10c22f60..df3f584a 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -745,7 +745,12 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No @rpc("get_nodenet_changes") def get_nodenet_changes(nodenet_uid, nodespaces=[], since_step=0): - return runtime.get_nodenet_changes(nodenet_uid, nodespaces=nodespaces, since_step=since_step) + data = runtime.get_nodenet_activation_data(nodenet_uid, nodespaces=nodespaces, last_call_step=since_step) + if data['has_changes']: + data['changes'] = runtime.get_nodespace_changes(nodenet_uid, nodespaces=nodespaces, since_step=since_step) + else: + data['changes'] = {} + return True, data @rpc("generate_uid") @@ -765,6 +770,7 @@ def create_auth_token(user, password, remember=True): else: return False, "User unknown" + @rpc("invalidate_auth_token") def invalidate_auth_token(token): usermanager.end_session(token) diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 0960b59d..52c4d12d 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1456,6 +1456,30 @@ def test_get_state_diff(app, test_nodenet, node): assert [node2] == list(data['changes']['nodes_dirty'].keys()) +def test_get_nodenet_diff(app, test_nodenet, node): + from micropsi_core import runtime + nodenet = runtime.nodenets[test_nodenet] + runtime.step_nodenet(test_nodenet) + response = app.post_json('/rpc/get_nodenet_changes', params={ + 'nodenet_uid': test_nodenet, + 'nodespaces': [None], + 'since_step': 0 + }) + data = response.json_body['data'] + assert 'activations' in data + assert 'changes' in data + assert node in data['changes']['nodes_dirty'] + node2 = nodenet.create_node("Register", None, [10, 10], name="node2") + runtime.step_nodenet(test_nodenet) + response = app.post_json('/rpc/get_nodenet_changes', params={ + 'nodenet_uid': test_nodenet, + 'nodespaces': [None], + 'since_step': 1 + }) + data = response.json_body['data'] + assert [node2] == list(data['changes']['nodes_dirty'].keys()) + + def test_get_operations(app, test_nodenet): response = app.get_json('/rpc/get_available_operations()') data = response.json_body['data'] From 1a5aefed687108c124e3ccf0f8e6c6c53f3f6402 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 10 May 2016 20:59:08 +0200 Subject: [PATCH 080/945] enumerate fat gates, treat as normal gates, condense only for client --- micropsi_core/nodenet/node.py | 47 ++++++------------- .../nodenet/theano_engine/theano_node.py | 20 ++++---- .../nodenet/theano_engine/theano_partition.py | 20 ++++++-- micropsi_core/tests/test_node.py | 37 +++++++++++++-- 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 2fc3c2fe..4d32277f 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -619,7 +619,7 @@ def nodefunction_name(self, nodefunction_name): def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, gate_defaults=None, - symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', is_fat=False, fat_config=None): + symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', fat_config=None): """Initializes or creates a nodetype. Arguments: @@ -634,7 +634,7 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._nodefunction_definition = None self._nodefunction_name = None - self.is_fat = is_fat + self.is_fat = fat_config is not None self.name = name self.slottypes = slottypes or [] self.gatetypes = gatetypes or [] @@ -644,17 +644,6 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.logger = nodenet.logger - self.gate_defaults = {} - for g in self.gatetypes: - self.gate_defaults[g] = Nodetype.GATE_DEFAULTS.copy() - - if gate_defaults is not None: - for g in gate_defaults: - for key in gate_defaults[g]: - if g not in self.gate_defaults: - raise Exception("Invalid gate default value for nodetype %s: Gate %s not found" % (name, g)) - self.gate_defaults[g][key] = gate_defaults[g][key] - self.parameters = parameters or {} self.parameter_values = parameter_values or {} self.parameter_defaults = parameter_defaults or {} @@ -667,33 +656,27 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.nodefunction = None if self.is_fat: + self.slotgroups = slottypes + self.gategroups = gatetypes self.fat_config = fat_config - self.gatetypes = [] - self.slottypes = [] - self.gatetypes = [i for i in (['sub', 'sur'] + gatetypes) if i not in self.gatetypes] - self.slottypes = [i for i in (['sub', 'sur'] + slottypes) if i not in self.slottypes] gates = [] slots = [] - fat_config['groupgates'] = [] - fat_config['groupslots'] = [] for g in self.gatetypes: - group = ["%s:%d" % (g, i) for i in range(fat_config['gates'].get(g, 1))] - fat_config['groupgates'].append(group[0]) + group = ["%s%d" % (g, i) for i in range(fat_config['gates'].get(g, 1))] gates.extend(group) for s in self.slottypes: - group = ["%s:%d" % (s, i) for i in range(fat_config['slots'].get(s, 1))] - fat_config['groupslots'].append(group[0]) + group = ["%s%d" % (s, i) for i in range(fat_config['slots'].get(s, 1))] slots.extend(group) self.gatetypes = gates self.slottypes = slots - self.gate_defaults = {} - for g in self.fat_config['groupgates']: - self.gate_defaults[g] = Nodetype.GATE_DEFAULTS.copy() + self.gate_defaults = {} + for g in self.gatetypes: + self.gate_defaults[g] = Nodetype.GATE_DEFAULTS.copy() - if gate_defaults is not None: - for g in gate_defaults: - for key in gate_defaults[g]: - if g not in self.gate_defaults: - raise Exception("Invalid gate default value for nodetype %s: Gate %s not found" % (name, g)) - self.gate_defaults[g][key] = gate_defaults[g][key] + if gate_defaults is not None: + for g in gate_defaults: + for key in gate_defaults[g]: + if g not in self.gate_defaults: + raise Exception("Invalid gate default value for nodetype %s: Gate %s not found" % (name, g)) + self.gate_defaults[g][key] = gate_defaults[g][key] diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 8f049fcb..8ee21902 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -144,10 +144,8 @@ def clone_non_default_gate_parameters(self, gate_type=None): g_theta = self._partition.g_theta.get_value(borrow=True) gatemap = {} - if self.is_fat: - gate_types = self.nodetype.fat_config['groupgates'] - else: - gate_types = self.nodetype.gate_defaults.keys() + gate_types = self.nodetype.gate_defaults.keys() + if gate_type is not None: if gate_type in gate_types: gate_types = [gate_type] @@ -417,21 +415,21 @@ def node_function(self): else: raise - def get_activation_array(self): + def get_slot_activation_array(self): return self.slot_fat_snapshot - def set_activation_array(self, new_activations): + def set_gate_activation_array(self, new_activations): start = self._partition.allocated_node_offsets[node_from_id(self.uid)] end = start + len(self._nodetype.gatetypes) a_array = self._partition.a.get_value(borrow=True) a_array[start:end] = new_activations self._partition.a.set_value(a_array, borrow=True) - def get_gate_links(self): - pass - - def get_slot_links(self): - pass + def get_gate_activation_array(self): + start = self._partition.allocated_node_offsets[node_from_id(self.uid)] + end = start + len(self._nodetype.gatetypes) + a_array = self._partition.a.get_value(borrow=True) + return a_array[start:end] def save_data(self, data): np.savez(self.datafile, data=data) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 138c96d1..c8da7162 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1889,7 +1889,9 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li node_ids = np.intersect1d(node_ids, ids) nodes = {} + fatnodes = [] followupuids = set() + for id in node_ids: uid = node_to_id(id, self.pid) strtype = get_string_node_type(self.allocated_nodes[id], self.nodenet.native_modules) @@ -1898,10 +1900,10 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li gate_functions = {} gate_parameters = {} gate_activations = {} - links = {} if nodetype.is_fat: - gates = nodetype.fat_config['groupgates'] + gates = ["%s0" % g for g in nodetype.gategroups] + fatnodes.append(uid) else: gates = nodetype.gatetypes @@ -2021,6 +2023,8 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li source_nodetype = self.nodenet.get_nodetype(get_string_node_type(source_type, self.nodenet.native_modules)) source_gate_numerical = gate_index - self.allocated_node_offsets[source_id] source_gate_type = get_string_gate_type(source_gate_numerical, source_nodetype) + if source_uid in fatnodes: + source_gate_type = source_gate_type.rstrip('0123456789') + '0' slot_index = slots[index] target_id = self.allocated_elements_to_nodes[slot_index] @@ -2029,13 +2033,19 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li target_nodetype = self.nodenet.get_nodetype(get_string_node_type(target_type, self.nodenet.native_modules)) target_slot_numerical = slot_index - self.allocated_node_offsets[target_id] target_slot_type = get_string_slot_type(target_slot_numerical, target_nodetype) + if target_uid in fatnodes: + target_slot_type = target_slot_type.rstrip('0123456789') + '0' linkdict = {"weight": float(w[slot_index, gate_index]), "certainty": 1, "target_slot_name": target_slot_type, "target_node_uid": target_uid} if source_gate_type not in nodes[source_uid]["links"]: nodes[source_uid]["links"][source_gate_type] = [] - nodes[source_uid]["links"][source_gate_type].append(linkdict) + if source_uid in fatnodes: + if linkdict not in nodes[source_uid]['links'][source_gate_type]: + nodes[source_uid]["links"][source_gate_type].append(linkdict) + else: + nodes[source_uid]["links"][source_gate_type].append(linkdict) followupuids.add(target_uid) # outgoing cross-partition links @@ -2056,6 +2066,8 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li source_nodetype = self.nodenet.get_nodetype(get_string_node_type(source_type, self.nodenet.native_modules)) source_gate_numerical = from_elements[gate_index] - self.allocated_node_offsets[source_id] source_gate_type = get_string_gate_type(source_gate_numerical, source_nodetype) + if source_uid in fatnodes: + source_gate_type = source_gate_type.rstrip('0123456789') + '0' slot_index = slots[index] target_id = to_partition.allocated_elements_to_nodes[to_elements[slot_index]] @@ -2064,6 +2076,8 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li target_nodetype = to_partition.nodenet.get_nodetype(get_string_node_type(target_type, to_partition.nodenet.native_modules)) target_slot_numerical = to_elements[slot_index] - to_partition.allocated_node_offsets[target_id] target_slot_type = get_string_slot_type(target_slot_numerical, target_nodetype) + if target_uid in fatnodes: + target_slot_type = target_slot_type.rstrip('0123456789') + '0' linkdict = {"weight": float(w[slot_index, gate_index]), "certainty": 1, "target_slot_name": target_slot_type, diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 3529db23..7d4d1886 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -64,10 +64,9 @@ def test_fat_native_modules(test_nodenet, resourcepath): fp.write(""" {"PhatNM": { "name": "PhatNM", - "slottypes": ["gen", "A_in", "B_in"], - "gatetypes": ["gen", "A_out", "B_out"], + "slottypes": ["gen", "sub", "sur", "A_in", "B_in"], + "gatetypes": ["gen", "sub", "sur", "A_out", "B_out"], "nodefunction_name": "phatNM", - "is_fat": 1, "symbol": "F", "fat_config": { "gates": { @@ -89,17 +88,45 @@ def phatNM(netapi, node, **_): netapi = micropsi.nodenets[test_nodenet].netapi node = netapi.create_node("PhatNM", None, "phatty") node.take_slot_activation_snapshot() - data = node.get_activation_array() + + # test get_slot_activation + data = node.get_slot_activation_array() assert len(data) == 1024 + 62 + 3 # fat_slots + gen/sub/sur new_activation = np.random.rand(768 + 13 + 3) # fat gates + gen/sub/sur - node.set_activation_array(new_activation) + + # test set_gate_activation + node.set_gate_activation_array(new_activation) target = netapi.create_node("Register", None, "Target") for g in node.get_gate_types(): netapi.link(node, g, target, 'gen') micropsi.step_nodenet(test_nodenet) assert target.activation > 0 + + # test saving/loading data node.save_data(new_activation) assert np.all(node.load_data() == new_activation) + + # test persistency micropsi.save_nodenet(test_nodenet) micropsi.revert_nodenet(test_nodenet) + netapi = micropsi.nodenets[test_nodenet].netapi + node = netapi.get_node(node.uid) + target = netapi.get_node(target.uid) assert np.all(node.load_data() == new_activation) + + # test setting gate details, get_gate_activation + node.set_gatefunction_name("A_out0", "sigmoid") + micropsi.step_nodenet(test_nodenet) + act = node.get_gate_activation_array() + assert act[3] == 0.5 + assert np.all(act[4:] == 0) + + # test delivery to frontend + netapi.link(target, 'gen', node, 'A_in580') + data = micropsi.nodenets[test_nodenet].get_nodes() + nodedata = data['nodes'][node.uid] + assert len(nodedata['gate_activations'].keys()) == 5 + assert 'gen0' in nodedata['gate_activations'] + assert len(nodedata['links']['A_out0']) == 1 # all to same node + assert 'A_out1' not in nodedata['links'] + assert data['nodes'][target.uid]['links']['gen'][0]['target_slot_name'] == 'A_in0' From 7e0b4b838905373fc37b3059629428726fa3fd31 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 10 May 2016 21:00:29 +0200 Subject: [PATCH 081/945] fix reload_native_modules --- .../nodenet/theano_engine/theano_nodenet.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index b1ef28bd..a8202e7a 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1092,9 +1092,9 @@ def reload_native_modules(self, native_modules): instances_to_delete = {} # create the new nodetypes - self.native_modules = {} + new_native_modules = {} for type, data in native_modules.items(): - self.native_modules[type] = Nodetype(nodenet=self, **native_modules[type]) + new_native_modules[type] = Nodetype(nodenet=self, **native_modules[type]) for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): @@ -1106,7 +1106,7 @@ def reload_native_modules(self, native_modules): numeric_id = node_from_id(uid) number_of_elements = len(np.where(partition.allocated_elements_to_nodes == numeric_id)[0]) - new_numer_of_elements = max(len(self.native_modules[instance.type].slottypes), len(self.native_modules[instance.type].gatetypes)) + new_numer_of_elements = max(len(new_native_modules[instance.type].slottypes), len(new_native_modules[instance.type].gatetypes)) if number_of_elements != new_numer_of_elements: self.logger.warning("Number of elements changed for node type %s from %d to %d, recreating instance %s" % (instance.type, number_of_elements, new_numer_of_elements, uid)) @@ -1118,6 +1118,8 @@ def reload_native_modules(self, native_modules): for uid in instances_to_recreate.keys(): self.delete_node(uid) + self.native_modules = new_native_modules + # update the living instances that have the same slot/gate numbers new_instances = {} for id, instance in partition.native_module_instances.items(): @@ -1136,16 +1138,6 @@ def reload_native_modules(self, native_modules): new_instances[id] = new_native_module_instance partition.native_module_instances = new_instances - # recreate the deleted ones. Gate configurations and links will not be transferred. - for uid, data in instances_to_recreate.items(): - new_uid = self.create_node( - data['type'], - data['parent_nodespace'], - data['position'], - name=data['name'], - uid=uid, - parameters=data['parameters']) - # update native modules numeric types, as these may have been set with a different native module # node types list native_module_ids = np.where(partition.allocated_nodes > MAX_STD_NODETYPE)[0] @@ -1153,6 +1145,16 @@ def reload_native_modules(self, native_modules): instance = self.get_node(node_to_id(id, partition.pid)) partition.allocated_nodes[id] = get_numerical_node_type(instance.type, self.native_modules) + # recreate the deleted ones. Gate configurations and links will not be transferred. + for uid, data in instances_to_recreate.items(): + new_uid = self.create_node( + data['type'], + data['parent_nodespace'], + data['position'], + name=data['name'], + uid=uid, + parameters=data['parameters']) + def get_nodespace_data(self, nodespace_uid, include_links=True): partition = self.get_partition(nodespace_uid) data = { From b4b3fe5855d2b1afe1ebf14121cc6dae5b360b9b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 11 May 2016 12:24:56 +0200 Subject: [PATCH 082/945] deliver nodetype data from instances if we allow for "macros" in the nodetype definition we have to evaluate them. So Nodetype class gets its own get_data method --- .../nodenet/dict_engine/dict_nodenet.py | 27 ++++++------- micropsi_core/nodenet/node.py | 27 ++++++++++++- micropsi_core/nodenet/nodenet.py | 17 +++++++- .../nodenet/theano_engine/theano_nodenet.py | 40 ++++++++++--------- .../nodenet/theano_engine/theano_partition.py | 2 +- micropsi_core/runtime.py | 17 +++----- 6 files changed, 80 insertions(+), 50 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index b541d2c3..af589196 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -166,7 +166,11 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No uid (optional): unique handle of the agent; if none is given, it will be generated """ - super(DictNodenet, self).__init__(name, worldadapter, world, owner, uid, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance) + super().__init__(name, worldadapter, world, owner, uid, native_modules=native_modules, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance) + + self.nodetypes = {} + for type, data in STANDARD_NODETYPES.items(): + self.nodetypes[type] = Nodetype(nodenet=self, **data) self.stepoperators = [DictPropagate(), DictCalculate()] if self.use_modulators: @@ -179,14 +183,6 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self._nodes = {} self._nodespaces = {} - self._nodetypes = {} - for type, data in STANDARD_NODETYPES.items(): - self._nodetypes[type] = Nodetype(nodenet=self, **data) - - self._native_modules = {} - for type, data in native_modules.items(): - self._native_modules[type] = Nodetype(nodenet=self, **data) - self.nodegroups = {} self.initialize_nodenet({}) @@ -286,9 +282,10 @@ def remove(self, filename): def reload_native_modules(self, native_modules): """ reloads the native-module definition, and their nodefunctions and afterwards reinstantiates the nodenet.""" - self._native_modules = {} + self.native_modules = {} for key in native_modules: - self._native_modules[key] = Nodetype(nodenet=self, **native_modules[key]) + if native_modules[key].get('engine', self.engine) == self.engine: + self.native_modules[key] = Nodetype(nodenet=self, **native_modules[key]) saved = self.export_json() self.clear() self.merge_data(saved, keep_uids=True) @@ -371,10 +368,10 @@ def construct_nodespaces_dict(self, nodespace_uid, transitive=False): def get_nodetype(self, type): """ Returns the nodetpype instance for the given nodetype or native_module or None if not found""" - if type in self._nodetypes: - return self._nodetypes[type] + if type in self.nodetypes: + return self.nodetypes[type] else: - return self._native_modules[type] + return self.native_modules[type] def get_nodespace_data(self, nodespace_uid, include_links): data = { @@ -479,7 +476,7 @@ def merge_data(self, nodenet_data, keep_uids=False): newuid = uid data['uid'] = newuid uidmap[uid] = newuid - if data['type'] not in self._nodetypes and data['type'] not in self._native_modules: + if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: self.logger.warn("Invalid nodetype %s for node %s" % (data['type'], uid)) data['parameters'] = { 'comment': 'There was a %s node here' % data['type'] diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 4d32277f..988e6f34 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -641,7 +641,8 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.path = path self.category = category - + self.shape = shape + self.symbol = symbol self.logger = nodenet.logger self.parameters = parameters or {} @@ -656,8 +657,8 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.nodefunction = None if self.is_fat: - self.slotgroups = slottypes self.gategroups = gatetypes + self.slotgroups = slottypes self.fat_config = fat_config gates = [] slots = [] @@ -680,3 +681,25 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non if g not in self.gate_defaults: raise Exception("Invalid gate default value for nodetype %s: Gate %s not found" % (name, g)) self.gate_defaults[g][key] = gate_defaults[g][key] + + def get_data(self): + data = { + 'name': self.name, + 'parameters': self.parameters, + 'parameter_values': self.parameter_values, + 'parameter_defaults': self.parameter_defaults, + 'gate_defaults': self.gate_defaults, + 'symbol': self.symbol, + 'shape': self.shape, + 'nodefunction_definition': self.nodefunction_definition, + 'nodefunction_name': self.nodefunction_name, + 'path': self.path, + 'category': self.category + } + if self.is_fat: + data['gatetypes'] = ["%s0" % g for g in self.gategroups] + data['slottypes'] = ["%s0" % g for g in self.slotgroups] + else: + data['gatetypes'] = self.gatetypes + data['slottypes'] = self.slottypes + return data diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index dcfbbf53..3a2d994e 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -13,6 +13,7 @@ import micropsi_core.tools from .netapi import NetAPI from . import monitor +from .node import Nodetype __author__ = 'joscha' __date__ = '09.05.12' @@ -137,7 +138,7 @@ def worldadapter_instance(self, _worldadapter_instance): """ self._worldadapter_instance = _worldadapter_instance - def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, use_modulators=True, worldadapter_instance=None): + def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None): """ Constructor for the abstract base class, must be called by implementations """ @@ -170,6 +171,11 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.stepping_rate = [] self.dashboard_values = {} + self.native_modules = {} + for type, data in native_modules.items(): + if data.get('engine', self.engine) == self.engine: + self.native_modules[type] = Nodetype(nodenet=self, **data) + self._modulators = {} if use_modulators: from micropsi_core.nodenet.stepoperators import DoernerianEmotionalModulators as emo @@ -451,6 +457,15 @@ def get_standard_nodetype_definitions(self): """ pass # pragma: no cover + def get_native_module_definitions(self): + """ + Returns the native modules supported by this nodenet + """ + data = {} + for key in self.native_modules: + data[key] = self.native_modules[key].get_data() + return data + @abstractmethod def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gatetype="gen", sortby='id', group_name=None): """ diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index a8202e7a..ae5bf08f 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -193,7 +193,11 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No # map of data targets to string node IDs self.actuatormap = {} - super(TheanoNodenet, self).__init__(name, worldadapter, world, owner, uid, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance) + super().__init__(name, worldadapter, world, owner, uid, native_modules=native_modules, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance) + + self.nodetypes = {} + for type, data in STANDARD_NODETYPES.items(): + self.nodetypes[type] = Nodetype(nodenet=self, **data) precision = settings['theano']['precision'] if precision == "32": @@ -268,14 +272,10 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.stepoperators = [] self.initialize_stepoperators() - self._nodetypes = {} - for type, data in STANDARD_NODETYPES.items(): - self._nodetypes[type] = Nodetype(nodenet=self, **data) - - self.native_module_definitions = native_modules - self.native_modules = {} - for type, data in self.native_module_definitions.items(): - self.native_modules[type] = Nodetype(nodenet=self, **data) + self.native_module_definitions = {} + for key in native_modules: + if native_modules[key].get('engine', self.engine) == self.engine: + self.native_module_definitions[key] = native_modules[key] self.create_nodespace(None, None, "Root", nodespace_to_id(1, rootpartition.pid)) @@ -365,7 +365,7 @@ def save(self, filename): metadata['names'] = self.names metadata['actuatormap'] = self.actuatormap metadata['sensormap'] = self.sensormap - metadata['nodes'] = self.construct_native_modules_and_comments_dict() + metadata['nodes'] = self.constructnative_modules_and_comments_dict() metadata['monitors'] = self.construct_monitors_dict() metadata['modulators'] = self.construct_modulators_dict() metadata['partition_parents'] = self.inverted_partitionmap @@ -508,7 +508,7 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only if not keep_uids: parent_uid = uidmap[data['parent_nodespace']] id_to_pass = None - if data['type'] not in self._nodetypes and data['type'] not in self.native_modules: + if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: self.logger.warn("Invalid nodetype %s for node %s" % (data['type'], uid)) data['parameters'] = { 'comment': 'There was a %s node here' % data['type'] @@ -1085,16 +1085,18 @@ def delete_link(self, source_node_uid, gate_type, target_node_uid, slot_type): def reload_native_modules(self, native_modules): - self.native_module_definitions = native_modules # check which instances need to be recreated because of gate/slot changes and keep their .data instances_to_recreate = {} instances_to_delete = {} # create the new nodetypes - new_native_modules = {} + self.native_module_definitions = {} + newnative_modules = {} for type, data in native_modules.items(): - new_native_modules[type] = Nodetype(nodenet=self, **native_modules[type]) + if data.get('engine', self.engine) == self.engine: + newnative_modules[type] = Nodetype(nodenet=self, **data) + self.native_module_definitions[type] = data for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): @@ -1106,7 +1108,7 @@ def reload_native_modules(self, native_modules): numeric_id = node_from_id(uid) number_of_elements = len(np.where(partition.allocated_elements_to_nodes == numeric_id)[0]) - new_numer_of_elements = max(len(new_native_modules[instance.type].slottypes), len(new_native_modules[instance.type].gatetypes)) + new_numer_of_elements = max(len(newnative_modules[instance.type].slottypes), len(newnative_modules[instance.type].gatetypes)) if number_of_elements != new_numer_of_elements: self.logger.warning("Number of elements changed for node type %s from %d to %d, recreating instance %s" % (instance.type, number_of_elements, new_numer_of_elements, uid)) @@ -1118,7 +1120,7 @@ def reload_native_modules(self, native_modules): for uid in instances_to_recreate.keys(): self.delete_node(uid) - self.native_modules = new_native_modules + self.native_modules = newnative_modules # update the living instances that have the same slot/gate numbers new_instances = {} @@ -1205,8 +1207,8 @@ def get_activation_data(self, nodespace_uids=[], rounded=1): return activations def get_nodetype(self, type): - if type in self._nodetypes: - return self._nodetypes[type] + if type in self.nodetypes: + return self.nodetypes[type] else: return self.native_modules.get(type) @@ -1281,7 +1283,7 @@ def construct_links_list(self, nodespace_uid=None): return data - def construct_native_modules_and_comments_dict(self): + def constructnative_modules_and_comments_dict(self): data = {} i = 0 for partition in self.partitions.values(): diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index c8da7162..d25345a5 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1870,7 +1870,7 @@ def get_nodespace_changes(self, nodespace_uid, since_step): return node_ids, nodespace_ids def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_links=True, include_followupnodes=True): - + import re a = self.a.get_value(borrow=True) g_threshold_array = self.g_threshold.get_value(borrow=True) g_amplification_array = self.g_amplification.get_value(borrow=True) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 6293d776..6ff98cc5 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -353,7 +353,7 @@ def load_nodenet(nodenet_uid): 'world': world_uid, 'owner': data.owner, 'uid': data.uid, - 'native_modules': filter_native_modules(engine), + 'native_modules': native_modules, 'use_modulators': data.get('use_modulators', True) # getter for compatibility } if engine == 'dict_engine': @@ -389,7 +389,7 @@ def get_nodenet_metadata(nodenet_uid): data.update({ 'nodetypes': nodenet.get_standard_nodetype_definitions(), 'nodespaces': nodenet.construct_nodespaces_dict(None, transitive=True), - 'native_modules': filter_native_modules(nodenet.engine), + 'native_modules': nodenet.get_native_module_definitions(), 'monitors': nodenet.construct_monitors_dict(), 'rootnodespace': nodenet.get_nodespace(None).uid }) @@ -1104,14 +1104,14 @@ def get_available_node_types(nodenet_uid): nodenet = get_nodenet(nodenet_uid) return { 'nodetypes': nodenet.get_standard_nodetype_definitions(), - 'native_modules': filter_native_modules(nodenet.engine) + 'native_modules': nodenet.get_native_module_definitions() } def get_available_native_module_types(nodenet_uid): """Returns a list of native modules. If an nodenet uid is supplied, filter for node types defined within this nodenet.""" - return filter_native_modules(get_nodenet(nodenet_uid).engine) + return get_nodenet(nodenet_uid).get_native_module_definitions() def set_node_parameters(nodenet_uid, node_uid, parameters): @@ -1415,13 +1415,6 @@ def parsemembers(members): # --- end of API -def filter_native_modules(engine=None): - data = {} - for key in native_modules: - if native_modules[key].get('engine') is None or engine is None or engine == native_modules[key]['engine']: - data[key] = native_modules[key].copy() - return data - def crawl_definition_files(path, type="definition"): """Traverse the directories below the given path for JSON definitions of nodenets and worlds, @@ -1650,7 +1643,7 @@ def reload_native_modules(): nodenets[uid].is_active = False errors.extend(load_user_files(RESOURCE_PATH, reload_nodefunctions=True, errors=[])) for nodenet_uid in nodenets: - nodenets[nodenet_uid].reload_native_modules(filter_native_modules(nodenets[nodenet_uid].engine)) + nodenets[nodenet_uid].reload_native_modules(native_modules) # restart previously active nodenets for uid in runners: nodenets[uid].is_active = True From f7da06df69c1c6d4d620461e6079bd0c0920fe6b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 11 May 2016 17:17:21 +0200 Subject: [PATCH 083/945] rename param, only enumerate high_dim slots and gates --- micropsi_core/nodenet/node.py | 38 ++++++++++++------- .../nodenet/theano_engine/theano_node.py | 6 +-- .../nodenet/theano_engine/theano_partition.py | 37 ++++++++++-------- micropsi_core/tests/test_node.py | 9 ++++- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 988e6f34..ea4ed800 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -619,7 +619,7 @@ def nodefunction_name(self, nodefunction_name): def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, gate_defaults=None, - symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', fat_config=None): + symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', dimensionality=None): """Initializes or creates a nodetype. Arguments: @@ -634,7 +634,9 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._nodefunction_definition = None self._nodefunction_name = None - self.is_fat = fat_config is not None + self.dimensionality = dimensionality + self.is_highdimensional = dimensionality is not None + self.name = name self.slottypes = slottypes or [] self.gatetypes = gatetypes or [] @@ -656,18 +658,24 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non else: self.nodefunction = None - if self.is_fat: - self.gategroups = gatetypes - self.slotgroups = slottypes - self.fat_config = fat_config + if self.is_highdimensional: + self.gategroups = [("%s0" % g) if dimensionality['gates'].get(g, 1) > 1 else g for g in gatetypes ] + self.slotgroups = [("%s0" % s) if dimensionality['slots'].get(s, 1) > 1 else s for s in slottypes ] + self.dimensionality = dimensionality gates = [] slots = [] for g in self.gatetypes: - group = ["%s%d" % (g, i) for i in range(fat_config['gates'].get(g, 1))] - gates.extend(group) + if dimensionality['gates'].get(g, 1) > 1: + group = ["%s%d" % (g, i) for i in range(dimensionality['gates'][g])] + gates.extend(group) + else: + gates.append(g) for s in self.slottypes: - group = ["%s%d" % (s, i) for i in range(fat_config['slots'].get(s, 1))] - slots.extend(group) + if dimensionality['slots'].get(s, 1) > 1: + group = ["%s%d" % (s, i) for i in range(dimensionality['slots'][s])] + slots.extend(group) + else: + slots.append(s) self.gatetypes = gates self.slottypes = slots @@ -694,11 +702,13 @@ def get_data(self): 'nodefunction_definition': self.nodefunction_definition, 'nodefunction_name': self.nodefunction_name, 'path': self.path, - 'category': self.category + 'category': self.category, + 'is_highdimensional': self.is_highdimensional } - if self.is_fat: - data['gatetypes'] = ["%s0" % g for g in self.gategroups] - data['slottypes'] = ["%s0" % g for g in self.slotgroups] + if self.is_highdimensional: + data['gatetypes'] = self.gategroups + data['slottypes'] = self.slotgroups + data['dimensionality'] = self.dimensionality else: data['gatetypes'] = self.gatetypes data['slottypes'] = self.slottypes diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 8ee21902..9e75a0e5 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -34,7 +34,7 @@ def __init__(self, nodenet, partition, parent_uid, uid, type, parameters={}, **_ Node.__init__(self, strtype, nodenet.get_nodetype(strtype)) - self.is_fat = self._nodetype.is_fat + self.is_highdimensional = self._nodetype.is_highdimensional self.datafile = os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, '%s_node_%s.npz' % (self._nodenet.uid, self.uid)) @@ -47,7 +47,7 @@ def __init__(self, nodenet, partition, parent_uid, uid, type, parameters={}, **_ else: self.parameters = {} - if self.is_fat: + if self.is_highdimensional: self.slot_fat_snapshot = None @property @@ -187,7 +187,7 @@ def take_slot_activation_snapshot(self): for slottype in self.nodetype.slottypes: self.slot_activation_snapshot[slottype] = \ a_array[self._partition.allocated_node_offsets[self._id] + get_numerical_slot_type(slottype, self.nodetype)] - if self.is_fat: + if self.is_highdimensional: start = self._partition.allocated_node_offsets[self._id] end = start + len(self._nodetype.slottypes) self.slot_fat_snapshot = a_array[start:end] diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index d25345a5..b4e503b6 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1870,7 +1870,6 @@ def get_nodespace_changes(self, nodespace_uid, since_step): return node_ids, nodespace_ids def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_links=True, include_followupnodes=True): - import re a = self.a.get_value(borrow=True) g_threshold_array = self.g_threshold.get_value(borrow=True) g_amplification_array = self.g_amplification.get_value(borrow=True) @@ -1889,7 +1888,7 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li node_ids = np.intersect1d(node_ids, ids) nodes = {} - fatnodes = [] + highdim_nodes = [] followupuids = set() for id in node_ids: @@ -1901,9 +1900,9 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li gate_parameters = {} gate_activations = {} - if nodetype.is_fat: - gates = ["%s0" % g for g in nodetype.gategroups] - fatnodes.append(uid) + if nodetype.is_highdimensional: + gates = nodetype.gategroups + highdim_nodes.append(uid) else: gates = nodetype.gatetypes @@ -2002,7 +2001,7 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li "activation": float(a[self.allocated_node_offsets[id] + GEN]), "gate_activations": gate_activations, "gate_functions": gate_functions, - "is_fat": nodetype.is_fat} + "is_highdimensional": nodetype.is_highdimensional} if complete: data['index'] = id if include_links: @@ -2023,8 +2022,10 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li source_nodetype = self.nodenet.get_nodetype(get_string_node_type(source_type, self.nodenet.native_modules)) source_gate_numerical = gate_index - self.allocated_node_offsets[source_id] source_gate_type = get_string_gate_type(source_gate_numerical, source_nodetype) - if source_uid in fatnodes: - source_gate_type = source_gate_type.rstrip('0123456789') + '0' + if source_uid in highdim_nodes: + source_gate_type = source_gate_type.rstrip('0123456789') + if source_gate_type in source_nodetype.dimensionality['gates']: + source_gate_type = source_gate_type + '0' slot_index = slots[index] target_id = self.allocated_elements_to_nodes[slot_index] @@ -2033,15 +2034,17 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li target_nodetype = self.nodenet.get_nodetype(get_string_node_type(target_type, self.nodenet.native_modules)) target_slot_numerical = slot_index - self.allocated_node_offsets[target_id] target_slot_type = get_string_slot_type(target_slot_numerical, target_nodetype) - if target_uid in fatnodes: - target_slot_type = target_slot_type.rstrip('0123456789') + '0' + if target_uid in highdim_nodes: + target_slot_type = target_slot_type.rstrip('0123456789') + if target_slot_type in target_nodetype.dimensionality['slots']: + target_slot_type = target_slot_type + '0' linkdict = {"weight": float(w[slot_index, gate_index]), "certainty": 1, "target_slot_name": target_slot_type, "target_node_uid": target_uid} if source_gate_type not in nodes[source_uid]["links"]: nodes[source_uid]["links"][source_gate_type] = [] - if source_uid in fatnodes: + if source_uid in highdim_nodes: if linkdict not in nodes[source_uid]['links'][source_gate_type]: nodes[source_uid]["links"][source_gate_type].append(linkdict) else: @@ -2066,8 +2069,10 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li source_nodetype = self.nodenet.get_nodetype(get_string_node_type(source_type, self.nodenet.native_modules)) source_gate_numerical = from_elements[gate_index] - self.allocated_node_offsets[source_id] source_gate_type = get_string_gate_type(source_gate_numerical, source_nodetype) - if source_uid in fatnodes: - source_gate_type = source_gate_type.rstrip('0123456789') + '0' + if source_uid in highdim_nodes: + source_gate_type = source_gate_type.rstrip('0123456789') + if source_gate_type in source_nodetype.dimensionality['gates']: + source_gate_type = source_gate_type + '0' slot_index = slots[index] target_id = to_partition.allocated_elements_to_nodes[to_elements[slot_index]] @@ -2076,8 +2081,10 @@ def get_node_data(self, ids=None, nodespace_ids=None, complete=False, include_li target_nodetype = to_partition.nodenet.get_nodetype(get_string_node_type(target_type, to_partition.nodenet.native_modules)) target_slot_numerical = to_elements[slot_index] - to_partition.allocated_node_offsets[target_id] target_slot_type = get_string_slot_type(target_slot_numerical, target_nodetype) - if target_uid in fatnodes: - target_slot_type = target_slot_type.rstrip('0123456789') + '0' + if target_uid in highdim_nodes: + target_slot_type = target_slot_type.rstrip('0123456789') + if target_slot_type in target_nodetype.dimensionality['slots']: + target_slot_type = target_slot_type + '0' linkdict = {"weight": float(w[slot_index, gate_index]), "certainty": 1, "target_slot_name": target_slot_type, diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 7d4d1886..7a8162f5 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -68,7 +68,7 @@ def test_fat_native_modules(test_nodenet, resourcepath): "gatetypes": ["gen", "sub", "sur", "A_out", "B_out"], "nodefunction_name": "phatNM", "symbol": "F", - "fat_config": { + "dimensionality": { "gates": { "A_out": 768, "B_out": 13 @@ -123,10 +123,15 @@ def phatNM(netapi, node, **_): # test delivery to frontend netapi.link(target, 'gen', node, 'A_in580') + pipe = netapi.create_node("Pipe", None, "pipe") + netapi.link_with_reciprocal(pipe, node, 'subsur') data = micropsi.nodenets[test_nodenet].get_nodes() nodedata = data['nodes'][node.uid] assert len(nodedata['gate_activations'].keys()) == 5 - assert 'gen0' in nodedata['gate_activations'] + assert 'gen' in nodedata['gate_activations'] assert len(nodedata['links']['A_out0']) == 1 # all to same node assert 'A_out1' not in nodedata['links'] assert data['nodes'][target.uid]['links']['gen'][0]['target_slot_name'] == 'A_in0' + assert nodedata['links']['sur'][0]['target_node_uid'] == pipe.uid + assert nodedata['links']['sur'][0]['target_slot_name'] == 'sur' + assert data['nodes'][pipe.uid]['links']['sub'][0]['target_slot_name'] == 'sub' From 651d1320284112f298b47e919daecace4c164fa2 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 11 May 2016 18:30:30 +0200 Subject: [PATCH 084/945] select first entry if none selected --- micropsi_server/static/js/netapi_console.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/micropsi_server/static/js/netapi_console.js b/micropsi_server/static/js/netapi_console.js index bb161371..532c6613 100644 --- a/micropsi_server/static/js/netapi_console.js +++ b/micropsi_server/static/js/netapi_console.js @@ -353,7 +353,15 @@ $(function(){ function autocomplete_select(event){ if(event && $(event.target).attr('id') == 'console_input'){ - var el = $('a.selected', autocomplete_container) + var el = $('a.selected', autocomplete_container); + if(el.length == 0){ + var els = $('a', autocomplete_container); + if(els.length){ + el = $(els[0]); + } else { + return + } + } } else { if(event){ var el = $(event.target); From dc22dfa146ccdb1d01bc2566abd8441355da65b1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 11 May 2016 20:26:58 +0200 Subject: [PATCH 085/945] disable frontend gateform for highdim gates --- micropsi_core/nodenet/node.py | 4 +-- micropsi_server/static/js/nodenet.js | 37 +++++++++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index ea4ed800..194443a3 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -659,8 +659,8 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.nodefunction = None if self.is_highdimensional: - self.gategroups = [("%s0" % g) if dimensionality['gates'].get(g, 1) > 1 else g for g in gatetypes ] - self.slotgroups = [("%s0" % s) if dimensionality['slots'].get(s, 1) > 1 else s for s in slottypes ] + self.gategroups = [("%s0" % g) if dimensionality['gates'].get(g, 1) > 1 else g for g in gatetypes] + self.slotgroups = [("%s0" % s) if dimensionality['slots'].get(s, 1) > 1 else s for s in slottypes] self.dimensionality = dimensionality gates = [] slots = [] diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 19b9213a..31582f01 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -401,7 +401,7 @@ function setNodespaceData(data, changed){ } var links_data = {} for(uid in data.nodes){ - item = new Node(uid, data.nodes[uid]['position'][0], data.nodes[uid]['position'][1], data.nodes[uid].parent_nodespace, data.nodes[uid].name, data.nodes[uid].type, data.nodes[uid].sheaves, data.nodes[uid].state, data.nodes[uid].parameters, data.nodes[uid].gate_activations, data.nodes[uid].gate_parameters, data.nodes[uid].gate_functions); + item = new Node(uid, data.nodes[uid]['position'][0], data.nodes[uid]['position'][1], data.nodes[uid].parent_nodespace, data.nodes[uid].name, data.nodes[uid].type, data.nodes[uid].sheaves, data.nodes[uid].state, data.nodes[uid].parameters, data.nodes[uid].gate_activations, data.nodes[uid].gate_parameters, data.nodes[uid].gate_functions, data.nodes[uid].is_highdimensional); if(uid in nodes){ if(nodeRedrawNeeded(item)) { nodes[uid].update(item); @@ -489,7 +489,7 @@ function setNodespaceDiffData(data, changed){ links_data = {} for(var uid in data.changes.nodes_dirty){ var nodedata = data.changes.nodes_dirty[uid]; - item = new Node(uid, nodedata['position'][0], nodedata['position'][1], nodedata.parent_nodespace, nodedata.name, nodedata.type, nodedata.sheaves, nodedata.state, nodedata.parameters, nodedata.gate_activations, nodedata.gate_parameters, nodedata.gate_functions); + item = new Node(uid, nodedata['position'][0], nodedata['position'][1], nodedata.parent_nodespace, nodedata.name, nodedata.type, nodedata.sheaves, nodedata.state, nodedata.parameters, nodedata.gate_activations, nodedata.gate_parameters, nodedata.gate_functions, data.nodes[uid].is_highdimensional); if(uid in nodes){ for (var gateName in nodes[uid].gates) { for (linkUid in nodes[uid].gates[gateName].outgoing) { @@ -692,7 +692,7 @@ function updateModulators(data){ // data structure for net entities -function Node(uid, x, y, nodeSpaceUid, name, type, sheaves, state, parameters, gate_activations, gate_parameters, gatefunctions) { +function Node(uid, x, y, nodeSpaceUid, name, type, sheaves, state, parameters, gate_activations, gate_parameters, gatefunctions, is_highdim) { this.uid = uid; this.x = x; this.y = y; @@ -715,6 +715,7 @@ function Node(uid, x, y, nodeSpaceUid, name, type, sheaves, state, parameters, g this.gate_parameters = gate_parameters || {}; this.gate_activations = gate_activations || {}; this.gatefunctions = gatefunctions || {}; + this.is_highdim = is_highdim; if(type == "Nodespace") { this.symbol = "NS"; } else { @@ -741,7 +742,8 @@ function Node(uid, x, y, nodeSpaceUid, name, type, sheaves, state, parameters, g parameters[key] = this.gate_parameters[gatetype][key]; } } - this.gates[gatetype] = new Gate(gatetype, i, sheaves, parameters, this.gatefunctions[gatetype]); + var highdim = (is_highdim && gatetype.substr(gatetype, gatetype.length - 1) in nodetypes[type].dimensionality.gates); + this.gates[gatetype] = new Gate(gatetype, i, sheaves, parameters, this.gatefunctions[gatetype], highdim); } this.slotIndexes = Object.keys(this.slots); this.gateIndexes = Object.keys(this.gates); @@ -788,11 +790,12 @@ function Slot(name) { } // source for links, part of a net entity -function Gate(name, index, sheaves, parameters, gatefunction) { +function Gate(name, index, sheaves, parameters, gatefunction, is_highdim) { this.name = name; this.index = index; this.outgoing = {}; this.sheaves = sheaves; + this.is_highdim = is_highdim; this.gatefunction = gatefunction || 'identity'; if(parameters){ this.parameters = parameters; @@ -3639,6 +3642,9 @@ function handleEditGate(event){ node = nodes[form.attr('data-node')]; gate = node.gates[form.attr('data-gate')]; } + if(gate.is_highdim) { + return false; + } var data = form.serializeArray(); var params = {}; var old_params = gate.parameters; @@ -4117,13 +4123,20 @@ function showGateForm(node, gate){ $('.gate_nodetype', form).html(''+ node.type +''); $('.gate_gatetype', form).html(''+ gate.name +''); $.each($('input, select, textarea', form), function(index, el){ - el.value = ''; - if(el.name in gate.parameters){ - el.value = gate.parameters[el.name]; - } else if(el.name == 'activation'){ - el.value = gate.sheaves[currentSheaf].activation || '0'; - } else if(nodetypes[node.type].gate_defaults && el.name in nodetypes[node.type].gate_defaults[gate.name]){ - el.value = nodetypes[node.type].gate_defaults[gate.name][el.name]; + el = $(el); + el.val(''); + if(el.attr('name') in gate.parameters){ + el.val(gate.parameters[el.attr('name')]); + } else if(el.attr('name') == 'activation'){ + el.val(gate.sheaves[currentSheaf].activation || '0'); + } else if(nodetypes[node.type].gate_defaults && el.attr('name') in nodetypes[node.type].gate_defaults[gate.name]){ + el.val(nodetypes[node.type].gate_defaults[gate.name][el.attr('name')]); + } + if(gate.is_highdim){ + el.attr('disabled', 'disabled'); + } else { + if(el.attr('name') != 'activation') + el.removeAttr('disabled'); } }); $('#gate_gatefunction').val(gate.gatefunction); From 0b1ec8d6267135c5fb011f65fdd2be395e161067 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 12 May 2016 11:43:59 +0200 Subject: [PATCH 086/945] friendlier dimensionality dict for frontend --- micropsi_core/nodenet/node.py | 10 +++++++--- micropsi_server/static/js/nodenet.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 194443a3..b10d49b5 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -659,8 +659,8 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.nodefunction = None if self.is_highdimensional: - self.gategroups = [("%s0" % g) if dimensionality['gates'].get(g, 1) > 1 else g for g in gatetypes] - self.slotgroups = [("%s0" % s) if dimensionality['slots'].get(s, 1) > 1 else s for s in slottypes] + self.gategroups = [("%s0" % g) if dimensionality['gates'].get(g, 1) > 1 else g for g in gatetypes ] + self.slotgroups = [("%s0" % s) if dimensionality['slots'].get(s, 1) > 1 else s for s in slottypes ] self.dimensionality = dimensionality gates = [] slots = [] @@ -708,8 +708,12 @@ def get_data(self): if self.is_highdimensional: data['gatetypes'] = self.gategroups data['slottypes'] = self.slotgroups - data['dimensionality'] = self.dimensionality + data['dimensionality'] = { + 'gates': dict(("%s0" % g, self.dimensionality['gates'][g]) for g in self.dimensionality['gates']), + 'slots': dict(("%s0" % s, self.dimensionality['slots'][s]) for s in self.dimensionality['slots']), + } else: data['gatetypes'] = self.gatetypes data['slottypes'] = self.slottypes + data['dimensionality'] = {} return data diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 31582f01..43e72cf7 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -742,7 +742,7 @@ function Node(uid, x, y, nodeSpaceUid, name, type, sheaves, state, parameters, g parameters[key] = this.gate_parameters[gatetype][key]; } } - var highdim = (is_highdim && gatetype.substr(gatetype, gatetype.length - 1) in nodetypes[type].dimensionality.gates); + var highdim = is_highdim && gatetype in nodetypes[type].dimensionality.gates; this.gates[gatetype] = new Gate(gatetype, i, sheaves, parameters, this.gatefunctions[gatetype], highdim); } this.slotIndexes = Object.keys(this.slots); From 08896285472ea336b289bb76560bde2e47d2cc03 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 12 May 2016 11:44:48 +0200 Subject: [PATCH 087/945] mark highdimensional gates in frontend --- micropsi_server/static/js/nodenet.js | 5 +++++ micropsi_server/view/nodenet.tpl | 1 + 2 files changed, 6 insertions(+) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 43e72cf7..13920b9a 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -4139,6 +4139,11 @@ function showGateForm(node, gate){ el.removeAttr('disabled'); } }); + if(gate.is_highdim){ + $('.highdim', form).text("This is a high-dimensional gate with " + nodetypes[node.type].dimensionality.gates[gate.name] + " dimensions").show(); + } else { + $('.highdim', form).hide(); + } $('#gate_gatefunction').val(gate.gatefunction); form.attr('data-node', node.uid); form.attr('data-gate', gate.name); diff --git a/micropsi_server/view/nodenet.tpl b/micropsi_server/view/nodenet.tpl index cdc77835..84ddc2f2 100644 --- a/micropsi_server/view/nodenet.tpl +++ b/micropsi_server/view/nodenet.tpl @@ -151,6 +151,7 @@

    Gate

    +

    From 897e1b569f2dbfcd3a7d165977062f361cec15e5 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 12 May 2016 11:55:45 +0200 Subject: [PATCH 088/945] do not deliver all gate_defaults for high-dim gates --- micropsi_core/nodenet/node.py | 5 ++++- micropsi_core/tests/test_node.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index b10d49b5..c81fd039 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -696,7 +696,6 @@ def get_data(self): 'parameters': self.parameters, 'parameter_values': self.parameter_values, 'parameter_defaults': self.parameter_defaults, - 'gate_defaults': self.gate_defaults, 'symbol': self.symbol, 'shape': self.shape, 'nodefunction_definition': self.nodefunction_definition, @@ -712,8 +711,12 @@ def get_data(self): 'gates': dict(("%s0" % g, self.dimensionality['gates'][g]) for g in self.dimensionality['gates']), 'slots': dict(("%s0" % s, self.dimensionality['slots'][s]) for s in self.dimensionality['slots']), } + data['gate_defaults'] = {} + for g in self.gategroups: + data['gate_defaults'][g] = self.gate_defaults[g] else: data['gatetypes'] = self.gatetypes data['slottypes'] = self.slottypes data['dimensionality'] = {} + data['gate_defaults'] = self.gate_defaults return data diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 7a8162f5..4a5316a8 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -135,3 +135,13 @@ def phatNM(netapi, node, **_): assert nodedata['links']['sur'][0]['target_node_uid'] == pipe.uid assert nodedata['links']['sur'][0]['target_slot_name'] == 'sur' assert data['nodes'][pipe.uid]['links']['sub'][0]['target_slot_name'] == 'sub' + + # test get nodetypes + result = micropsi.get_available_native_module_types(test_nodenet)['PhatNM'] + assert result['dimensionality']['gates']['A_out0'] == 768 + assert result['dimensionality']['gates']['B_out0'] == 13 + assert result['dimensionality']['slots']['A_in0'] == 1024 + assert result['dimensionality']['slots']['B_in0'] == 62 + assert result['gatetypes'] == ['gen', 'sub', 'sur', 'A_out0', 'B_out0'] + assert set(result['gate_defaults'].keys()) == set(result['gatetypes']) + assert result['is_highdimensional'] From add6e4211b2022f8b36505eebfb0c5ea19164537 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 12 May 2016 15:14:04 +0200 Subject: [PATCH 089/945] dict engine does not support high-dimensional native modules right now --- micropsi_core/nodenet/node.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index c81fd039..7ac3b3ba 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -634,8 +634,12 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._nodefunction_definition = None self._nodefunction_name = None - self.dimensionality = dimensionality + self.dimensionality = None self.is_highdimensional = dimensionality is not None + if nodenet.engine == "dict_engine" and self.is_highdimensional: + nodenet.logger.warning("Dict engine does not support high dimensional native_modules") + self.is_highdimensional = False + self.dimensionality = None self.name = name self.slottypes = slottypes or [] From 8038a198f3dd30c92fb4a9699a2d9a26e4fcf28e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 12 May 2016 15:18:41 +0200 Subject: [PATCH 090/945] change emotional_modulators default to false --- micropsi_server/view/nodenet_form.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_server/view/nodenet_form.tpl b/micropsi_server/view/nodenet_form.tpl index 9e833667..15441e3e 100644 --- a/micropsi_server/view/nodenet_form.tpl +++ b/micropsi_server/view/nodenet_form.tpl @@ -50,7 +50,7 @@
    - +
    From 8442147a364ccc3c1882626ae3d08983bf962b6a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 12 May 2016 23:47:02 +0200 Subject: [PATCH 091/945] allow exporting multiple recorders, export recorder on the fly --- micropsi_core/_runtime_api_monitors.py | 14 +++++++++++++ micropsi_core/nodenet/recorder.py | 1 - micropsi_server/micropsi_app.py | 20 +++++++++++++++--- .../static/css/micropsi-styles.css | 1 + micropsi_server/static/js/monitor.js | 13 +++++++++++- micropsi_server/view/monitors.tpl | 21 +++++++++++-------- 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index 267cd081..02701085 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -143,3 +143,17 @@ def get_recorder_data(nodenet_uid): def get_recorder(nodenet_uid, recorder_uid): return micropsi_core.runtime.get_nodenet(nodenet_uid).get_recorder(recorder_uid) + + +def export_recorders(nodenet_uid, recorder_uids): + """ Returns a bytestream containing an npz export for the given recorders""" + import numpy as np + from io import BytesIO + nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) + data = {} + stream = BytesIO() + for uid in recorder_uids: + recorder = nodenet.get_recorder(uid) + data[recorder.name] = recorder.values + np.savez(stream, **data) + return stream.getvalue() diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 97b88a31..5057cb7c 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -79,7 +79,6 @@ def save(self, filename=None): values['uid'] = self.uid # empty files cannot be loaded np.savez(filename if filename is not None else self.filename, **values) - def load(self, filename=None): data = np.load(filename if filename is not None else self.filename) for key in data: diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 782f0a66..97ea96a2 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -564,10 +564,24 @@ def export_nodenet(nodenet_uid): @micropsi_app.route("/recorder/export/-") -def export_monitor(nodenet_uid, recorder_uid): +def export_recorder(nodenet_uid, recorder_uid): + data = runtime.export_recorders(nodenet_uid, [recorder_uid]) recorder = runtime.get_recorder(nodenet_uid, recorder_uid) - recorder.save() - return static_file(os.path.basename(recorder.filename), root=os.path.dirname(recorder.filename), download='recorder_%s.npz' % recorder.name) + response.set_header('Content-type', 'application/octet-stream') + response.set_header('Content-Disposition', 'attachment; filename="recorder_%s.npz"' % recorder.name) + return data + + +@micropsi_app.route("/recorder/export/", method="POST") +def export_recorders(nodenet_uid): + uids = [] + for param in request.params.allitems(): + if param[0] == 'recorder_uids[]': + uids.append(param[1]) + data = runtime.export_recorders(nodenet_uid, uids) + response.set_header('Content-type', 'application/octet-stream') + response.set_header('Content-Disposition', 'attachment; filename="recorders_%s.npz"' % nodenet_uid) + return data @micropsi_app.route("/nodenet/edit") diff --git a/micropsi_server/static/css/micropsi-styles.css b/micropsi_server/static/css/micropsi-styles.css index e88598b8..426a4b09 100644 --- a/micropsi_server/static/css/micropsi-styles.css +++ b/micropsi_server/static/css/micropsi-styles.css @@ -22,6 +22,7 @@ h3 { .monitor_list input {display:inline;} .monitor_list label {display:inline;} .monitor_list li {padding: 2px 0;} +#monitor input[type="checkbox"] {margin: 0;} #graph, #logs { overflow: auto; diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 0d8744ce..88a61faa 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -19,6 +19,7 @@ $(function(){ var cookieval = $.cookie('selected_nodenet'); if (cookieval && cookieval.indexOf('/')){ currentNodenet = cookieval.split('/')[0]; + $('form#export_recorders').attr('action', '/recorder/export/' + currentNodenet); } var capturedLoggers = { @@ -168,6 +169,7 @@ $(function(){ currentNodenet = newNodenet; init(); refreshMonitors(); + $('form#export_recorders').attr('action', '/recorder/export/' + currentNodenet); }); $(document).on('nodenet_loaded', function(data, newNodenet){ currentNodenet = newNodenet; @@ -225,7 +227,7 @@ $(function(){ var html = ''; for(var uid in data){ var rec = data[uid]; - html += '
    '; + html += ''; html += ''); }); var world_selector = $("#nodenet_world"); + var worldadapter_selector = $("#nodenet_worldadapter"); + var update_worldadapter_params = function(data){ + var html = []; + var wa = worldadapters[worldadapter_selector.val()]; + if(!wa) return ; + for(var i in wa.config_options){ + var op = wa.config_options[i] + var param = ''; + if(op.options){ + param += ''; + } else { + param += ''; + } + html.push(param +'') + } + $('#nodenet_editor .worldadapter_config').html('
    '+rec.name+'
    '+rec.name +'
     '; html += ' '; html += ' '; @@ -249,12 +251,21 @@ $(function(){ } function recorderAction(event){ + event.preventDefault(); var btn = $(event.target); var uid = btn.attr("data-uid"); var method_name = null; switch(btn.attr('data-action')){ case 'export': return window.location.replace('/recorder/export/'+currentNodenet+'-'+uid); + case 'export_selected_recorders': + var table = $('#recorder_table'); + var uids = []; + $('input[type=checkbox]', table).each(function(idx, el){ + if(el.checked) uids.push(el.value); + }) + $.post('/recorder/export/'+currentNodenet, {'recorder_uids': uids}); + break; case 'clear': method_name = 'clear_recorder'; break; case 'delete': diff --git a/micropsi_server/view/monitors.tpl b/micropsi_server/view/monitors.tpl index 900ee61e..991ffdec 100644 --- a/micropsi_server/view/monitors.tpl +++ b/micropsi_server/view/monitors.tpl @@ -116,15 +116,18 @@ % if theano_available:

    Recorders

    -
    - -
    -
    -
    -

    - -

    -
    + +
    + +
    +
    +
    +

    + + +

    +
    +
    % end

     

    From 86c299578c820a13fe60b5a11edf2c3ba6ffd8c2 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 13 May 2016 16:11:17 +0200 Subject: [PATCH 092/945] add netapi methods to group high dimensional slots and gates --- micropsi_core/nodenet/node.py | 14 ++++-- .../nodenet/theano_engine/theano_netapi.py | 6 +++ .../nodenet/theano_engine/theano_nodenet.py | 4 ++ .../nodenet/theano_engine/theano_partition.py | 16 +++++++ micropsi_core/tests/test_node_netapi.py | 45 +++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 7ac3b3ba..55c91d62 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -619,7 +619,7 @@ def nodefunction_name(self, nodefunction_name): def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, gate_defaults=None, - symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', dimensionality=None): + symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', dimensionality={}): """Initializes or creates a nodetype. Arguments: @@ -634,12 +634,12 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._nodefunction_definition = None self._nodefunction_name = None - self.dimensionality = None - self.is_highdimensional = dimensionality is not None + self.dimensionality = {} + self.is_highdimensional = bool(dimensionality) if nodenet.engine == "dict_engine" and self.is_highdimensional: nodenet.logger.warning("Dict engine does not support high dimensional native_modules") self.is_highdimensional = False - self.dimensionality = None + self.dimensionality = {} self.name = name self.slottypes = slottypes or [] @@ -694,6 +694,12 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non raise Exception("Invalid gate default value for nodetype %s: Gate %s not found" % (name, g)) self.gate_defaults[g][key] = gate_defaults[g][key] + def get_gate_dimensionality(self, gate): + return self.dimensionality.get('gates', {}).get(gate, 1) + + def get_slot_dimensionality(self, slot): + return self.dimensionality.get('slots', {}).get(slot, 1) + def get_data(self): data = { 'name': self.name, diff --git a/micropsi_core/nodenet/theano_engine/theano_netapi.py b/micropsi_core/nodenet/theano_engine/theano_netapi.py index 2d200c41..4f9da293 100644 --- a/micropsi_core/nodenet/theano_engine/theano_netapi.py +++ b/micropsi_core/nodenet/theano_engine/theano_netapi.py @@ -45,3 +45,9 @@ def decay_por_links(self, nodespace_uid): w_update *= (1 - porretdecay) w[rows, cols] = w_update partition.w.set_value(w, borrow=True) + + def group_highdimensional_gates(self, node_uid, gate, group_name=None): + self.__nodenet.group_highdimensional_elements(node_uid, gate=gate, group_name=group_name) + + def group_highdimensional_slots(self, node_uid, slot, group_name=None): + self.__nodenet.group_highdimensional_elements(node_uid, slot=slot, group_name=group_name) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index ae5bf08f..ba36ee8e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1476,6 +1476,10 @@ def group_nodes_by_ids(self, nodespace_uid, node_uids, group_name, gatetype="gen partition.group_nodes_by_ids(nodespace_uid, ids, group_name, gatetype) + def group_highdimensional_elements(self, node_uid, gate=None, slot=None, group_name=None): + partition = self.get_partition(node_uid) + partition.group_highdimensional_elements(node_uid, gate=gate, slot=slot, group_name=group_name) + def ungroup_nodes(self, nodespace_uid, group): if nodespace_uid is None: nodespace_uid = self.get_nodespace(None).uid diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index b4e503b6..c5c7db7e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1731,6 +1731,22 @@ def group_nodes_by_ids(self, nodespace_uid, ids, group_name, gatetype="gen"): gate = get_numerical_gate_type(gatetype) self.nodegroups[nodespace_uid][group_name] = self.allocated_node_offsets[ids] + gate + def group_highdimensional_elements(self, node_uid, gate=None, slot=None, group_name=None): + node_id = node_from_id(node_uid) + nodespace_id = self.allocated_node_parents[node_id] + nodespace_uid = nodespace_to_id(nodespace_id, self.pid) + strnodetype = get_string_node_type(self.allocated_nodes[node_id], self.nodenet.native_modules) + nodetype = self.nodenet.get_nodetype(strnodetype) + if gate: + element = get_numerical_gate_type("%s0" % gate, nodetype) + dimensionality = nodetype.get_gate_dimensionality(gate) + elif slot: + element = get_numerical_slot_type("%s0" % slot, nodetype) + dimensionality = nodetype.get_slot_dimensionality(slot) + start = self.allocated_node_offsets[node_id] + element + stop = start + dimensionality + self.nodegroups[nodespace_uid][group_name] = range(start, stop) + def ungroup_nodes(self, nodespace_uid, group): if nodespace_uid in self.nodegroups and group in self.nodegroups[nodespace_uid]: del self.nodegroups[nodespace_uid][group] diff --git a/micropsi_core/tests/test_node_netapi.py b/micropsi_core/tests/test_node_netapi.py index d7ae7d86..990498bb 100644 --- a/micropsi_core/tests/test_node_netapi.py +++ b/micropsi_core/tests/test_node_netapi.py @@ -1150,3 +1150,48 @@ def test_nodespace_properties(test_nodenet): netapi.set_nodespace_properties(None, {'foo': 'bar'}) data = netapi.get_nodespace_properties() assert data[rootns.uid] == {'foo': 'bar'} + + +@pytest.mark.engine("theano_engine") +def test_group_highdim_elements(test_nodenet, resourcepath): + import numpy as np + import os + with open(os.path.join(resourcepath, 'nodetypes.json'), 'w') as fp: + fp.write(""" + {"PhatNM": { + "name": "PhatNM", + "slottypes": ["gen", "sub", "sur", "inbound"], + "gatetypes": ["gen", "sub", "sur", "outbound"], + "nodefunction_name": "phatNM", + "symbol": "F", + "dimensionality": { + "gates": { + "outbound": 2 + }, + "slots": { + "inbound": 10 + } + } + }}""") + with open(os.path.join(resourcepath, 'nodefunctions.py'), 'w') as fp: + fp.write(""" +def phatNM(netapi, node, **_): + pass""") + + micropsi.reload_native_modules() + nodenet = micropsi.get_nodenet(test_nodenet) + netapi = nodenet.netapi + node = netapi.create_node("PhatNM", None, 'fatnode') + registers = [] + for i in range(10): + registers.append(netapi.create_node("Register", None, 'reg%d' % i)) + netapi.group_nodes_by_names(None, node_name_prefix='reg', gate='gen') + netapi.group_highdimensional_slots(node.uid, slot='inbound', group_name='fat_in') + netapi.set_link_weights(None, 'reg', None, 'fat_in', np.eye(10)) + for i, r in enumerate(registers): + links = r.get_gate('gen').get_links() + assert len(links) == 1 + assert links[0].target_node.uid == node.uid + assert links[0].target_slot.type == 'inbound%d' % i + netapi.group_highdimensional_gates(node.uid, 'outbound', group_name='fat_out') + assert np.all(netapi.get_activations(None, 'fat_out') == np.zeros(2)) From 383bcf725de4b8a45e6593a60ab699c75525d0ac Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 13 May 2016 19:37:00 +0200 Subject: [PATCH 093/945] move recorder methods to theano netapi --- micropsi_core/nodenet/netapi.py | 16 -------------- .../nodenet/theano_engine/theano_netapi.py | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 89d72848..f0462b3c 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -563,22 +563,6 @@ def remove_monitor(self, uid): """Removes the monitor with the given uid""" return self.__nodenet.remove_monitor(uid) - def add_activation_recorder(self, group_definition, name, interval=1): - """ Adds an activation recorder to a group of nodes.""" - return self.__nodenet.add_activation_recorder(group_definition, name, interval) - - def add_linkweight_recorder(self, from_group_definition, to_group_definition, name, interval=1): - """ Adds a linkweight recorder to links between to groups.""" - return self.__nodenet.add_linkweight_recorder(from_group_definition, to_group_definition, name, interval) - - def get_recorder(self, uid): - """Returns the recorder with the given uid""" - return self.__nodenet.get_recorder(uid) - - def remove_recorder(self, uid): - """Removes the recorder with the given uid""" - return self.__nodenet.remove_recorder(uid) - def set_dashboard_value(self, name, value): """Allows the netapi to set values for the statistics and dashboard""" self.__nodenet.dashboard_values[name] = value diff --git a/micropsi_core/nodenet/theano_engine/theano_netapi.py b/micropsi_core/nodenet/theano_engine/theano_netapi.py index 2d200c41..414fc385 100644 --- a/micropsi_core/nodenet/theano_engine/theano_netapi.py +++ b/micropsi_core/nodenet/theano_engine/theano_netapi.py @@ -45,3 +45,24 @@ def decay_por_links(self, nodespace_uid): w_update *= (1 - porretdecay) w[rows, cols] = w_update partition.w.set_value(w, borrow=True) + + def add_gate_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + return self.__nodenet.add_gate_activation_recorder(group_definition, name, interval) + + def add_node_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + return self.__nodenet.add_node_activation_recorder(group_definition, name, interval) + + def add_linkweight_recorder(self, from_group_definition, to_group_definition, name, interval=1): + """ Adds a linkweight recorder to links between to groups.""" + return self.__nodenet.add_linkweight_recorder(from_group_definition, to_group_definition, name, interval) + + def get_recorder(self, uid): + """Returns the recorder with the given uid""" + return self.__nodenet.get_recorder(uid) + + def remove_recorder(self, uid): + """Removes the recorder with the given uid""" + return self.__nodenet.remove_recorder(uid) + From b483a8f758b2ce3bd2dac27824c2137854ec2b92 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 13 May 2016 19:40:18 +0200 Subject: [PATCH 094/945] add node activation recorder which records all gates of the given nodes --- micropsi_core/_runtime_api_monitors.py | 18 ++++----- micropsi_core/nodenet/nodenet.py | 6 ++- micropsi_core/nodenet/recorder.py | 39 ++++++++++++++++++- .../nodenet/theano_engine/theano_nodenet.py | 10 ++++- micropsi_core/tests/test_recorders.py | 33 ++++++++++++++-- micropsi_server/micropsi_app.py | 12 ++++-- 6 files changed, 98 insertions(+), 20 deletions(-) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index 02701085..6e8b03b1 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -100,21 +100,21 @@ def get_monitor_data(nodenet_uid, step=0, from_step=0, count=-1, with_recorders= monitor_data[uid]['values'] = values data['monitors'] = monitor_data if with_recorders: - # recorder_data = {} - # for uid in nodenet.construct_recorders_dict(): - # rec = nodenet.get_recorder(uid) - # recorder_data[uid] = rec.get_data() - # values = rec.values.tolist() - # recorder_data[uid]['values'] = values[from_step + rec.first_step : from_step + count + rec.first_step] - # data['recorders'] = recorder_data data['recorders'] = nodenet.construct_recorders_dict() return data -def add_activation_recorder(nodenet_uid, group_definition, name, interval=1): +def add_gate_activation_recorder(nodenet_uid, group_definition, name, interval=1): """ Adds an activation recorder to a group of nodes.""" nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) - rec = nodenet.add_activation_recorder(group_definition, name, interval) + rec = nodenet.add_gate_activation_recorder(group_definition, name, interval) + return True, rec.uid + + +def add_node_activation_recorder(nodenet_uid, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + nodenet = micropsi_core.runtime.get_nodenet(nodenet_uid) + rec = nodenet.add_node_activation_recorder(group_definition, name, interval) return True, rec.uid diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 074b2a61..7847fa42 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -653,7 +653,11 @@ def construct_recorders_dict(self): def remove_monitor(self, monitor_uid): del self._monitors[monitor_uid] - def add_activation_recorder(self, group_definition, name, interval=1): + def add_gate_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + raise NotImplementedError("Recorders are not implemented in the this engine") + + def add_node_activation_recorder(self, group_definition, name, interval=1): """ Adds an activation recorder to a group of nodes.""" raise NotImplementedError("Recorders are not implemented in the this engine") diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 5057cb7c..5a4f0124 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -94,7 +94,7 @@ def import_file(self, filename): self.load(filename) -class ActivationRecorder(Recorder): +class GateActivationRecorder(Recorder): """ An activation recorder to record activaitons of nodegroups""" def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, first_step=0, current_index=-1, **_): @@ -124,6 +124,43 @@ def get_values(self): return {'activations': self._nodenet.get_activations(self.nodespace, self.group_name)} +class NodeActivationRecorder(Recorder): + """ An activation recorder to record activaitons of nodegroups""" + + def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, first_step=0, current_index=-1, **_): + super().__init__(nodenet, name, uid, interval, first_step=first_step, current_index=current_index) + + self.group_config = group_config + self.nodespace = group_config['nodespace_uid'] + self.base_group_name = group_config.pop('group_name', name) + + if not group_config.get('node_uids', []): + nodes = self._nodenet.netapi.get_nodes(nodespace=self.nodespace, node_name_prefix=group_config['node_name_prefix'], sortby=group_config.get('sortby', 'id')) + else: + nodes = [self._nodenet.get_node(uid) for uid in node_uids] + + assert len(set([n.type for n in nodes])) == 1 # assert we have a homogenous group + self.gatetypes = nodes[0].get_gate_types() + self.groupnames = [] + for g in self.gatetypes: + group_config['gatetype'] = g + group_config['group_name'] = self.base_group_name + '_%s' % g + self.groupnames.append(group_config['group_name']) + self._nodenet.group_nodes_by_names(**group_config) + + self.shapes = {'activations': (self.initial_size, len(self.gatetypes), len(nodes))} + + def get_data(self): + data = super().get_data() + data.update({ + "group_config": self.group_config, + }) + return data + + def get_values(self): + return {'activations': [self._nodenet.get_activations(self.nodespace, groupname) for groupname in self.groupnames]} + + class LinkweightRecorder(Recorder): """ An activation recorder to biases and the linkweights of two nodegroups""" diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 6def200d..e38cc2bf 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1621,9 +1621,15 @@ def get_nodespace_changes(self, nodespace_uids=[], since_step=0): result['nodespaces_dirty'][uid] = self.get_nodespace(uid).get_data() return result - def add_activation_recorder(self, group_definition, name, interval=1): + def add_gate_activation_recorder(self, group_definition, name, interval=1): """ Adds an activation recorder to a group of nodes.""" - rec = recorder.ActivationRecorder(self, group_definition, name, interval=interval) + rec = recorder.GateActivationRecorder(self, group_definition, name, interval=interval) + self._recorders[rec.uid] = rec + return rec + + def add_node_activation_recorder(self, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + rec = recorder.NodeActivationRecorder(self, group_definition, name, interval=interval) self._recorders[rec.uid] = rec return rec diff --git a/micropsi_core/tests/test_recorders.py b/micropsi_core/tests/test_recorders.py index f7744198..d0c730fd 100644 --- a/micropsi_core/tests/test_recorders.py +++ b/micropsi_core/tests/test_recorders.py @@ -21,7 +21,7 @@ def test_activation_recorder(test_nodenet, resourcepath): netapi.link(source, 'gen', source, 'gen') netapi.link(source, 'gen', nodes[0], 'gen') source.activation = 1 - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=2) + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=2) assert recorder.name == 'recorder' assert recorder.interval == 2 for i in range(5): @@ -38,6 +38,31 @@ def test_activation_recorder(test_nodenet, resourcepath): assert recorder.values['activations'][1].tolist() == [1, 1, 1, 1, 0, 0, 0, 0, 0, 0] +@pytest.mark.engine("theano_engine") +def test_nodeactivation_recorder(test_nodenet, resourcepath): + import numpy as np + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + nodes = [] + source = netapi.create_node("Register", None, 'source') + source.activation = 1 + for i in range(10): + node = netapi.create_node('Pipe', None, "testnode_%d" % i) + netapi.link(source, 'gen', node, 'sub') + nodes.append(node) + + recorder = netapi.add_node_activation_recorder(group_definition={'nodespace_uid': None, 'node_name_prefix': 'testnode'}, name="recorder") + + gatecount = len(nodes[0].get_gate_types()) + micropsi.step_nodenet(test_nodenet) + values = recorder.values['activations'][0] + + assert values.shape == (gatecount, 10) + assert np.all(values[5] == 1) + assert np.all(values[3] == 1) + assert np.all(values[0] == 0) + + @pytest.mark.engine("theano_engine") def test_linkweight_recorder(test_nodenet, resourcepath): nodenet = micropsi.nodenets[test_nodenet] @@ -77,7 +102,7 @@ def test_clear_recorder(test_nodenet, resourcepath): nodespace = netapi.get_nodespace(None) for i in range(5): netapi.create_node('Register', None, "testnode_%d" % i) - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") for i in range(3): micropsi.step_nodenet(test_nodenet) assert len(recorder.values['activations'].tolist()[3]) == 5 @@ -92,7 +117,7 @@ def test_remove_recorder(test_nodenet, resourcepath): nodespace = netapi.get_nodespace(None) for i in range(5): netapi.create_node('Register', None, "testnode_%d" % i) - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") for i in range(3): micropsi.step_nodenet(test_nodenet) netapi.remove_recorder(recorder.uid) @@ -108,7 +133,7 @@ def test_grow_recorder_values(test_nodenet, resourcepath): for i in range(5): netapi.create_node('Register', None, "testnode_%d" % i) Recorder.initial_size = 5 - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") micropsi.step_nodenet(test_nodenet) assert len(recorder.values['activations']) == 5 for i in range(20): diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 97ea96a2..92401002 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1247,10 +1247,16 @@ def get_emoexpression_parameters(nodenet_uid): # --------- recorder -------- -@rpc("add_activation_recorder") -def add_activation_recorder(nodenet_uid, group_definition, name, interval=1): +@rpc("add_gate_activation_recorder") +def add_gate_activation_recorder(nodenet_uid, group_definition, name, interval=1): """ Adds an activation recorder to a group of nodes.""" - return runtime.add_activation_recorder(nodenet_uid, group_definition, name, interval) + return runtime.add_gate_activation_recorder(nodenet_uid, group_definition, name, interval) + + +@rpc("add_node_activation_recorder") +def add_node_activation_recorder(nodenet_uid, group_definition, name, interval=1): + """ Adds an activation recorder to a group of nodes.""" + return runtime.add_node_activation_recorder(nodenet_uid, group_definition, name, interval) @rpc("add_linkweight_recorder") From 0921f71992706c60ff96791706b1cfa75556f0fe Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 13 May 2016 22:01:07 +0200 Subject: [PATCH 095/945] operation for noderecorder, test operations, bugfixes --- micropsi_core/nodenet/operations/recoders.py | 19 +++++- micropsi_core/nodenet/recorder.py | 12 ++-- micropsi_core/tests/test_operations.py | 63 +++++++++++++++++++- micropsi_server/tests/test_json_api.py | 35 +++++++++-- 4 files changed, 112 insertions(+), 17 deletions(-) diff --git a/micropsi_core/nodenet/operations/recoders.py b/micropsi_core/nodenet/operations/recoders.py index 1c5c97a0..c5eaae2e 100644 --- a/micropsi_core/nodenet/operations/recoders.py +++ b/micropsi_core/nodenet/operations/recoders.py @@ -6,7 +6,7 @@ import numpy as np @selectioninfo(mincount=2) - def add_activation_recorder(netapi, selection, gate='gen', interval=1, name='activation_recoder'): + def add_gate_activation_recorder(netapi, selection, gate='gen', interval=1, name='gate_activation_recoder'): """Adds an activation recorder to the selected nodes""" firstnode = netapi.get_node(selection[0]) nodespace = netapi.get_nodespace(firstnode.parent_nodespace) @@ -14,7 +14,19 @@ def add_activation_recorder(netapi, selection, gate='gen', interval=1, name='act 'nodespace_uid': nodespace.uid, 'node_uids': selection, 'gatetype': gate} - netapi.add_activation_recorder(group_config, name=name, interval=int(interval)) + recorder = netapi.add_gate_activation_recorder(group_config, name=name, interval=int(interval)) + return {'uid': recorder.uid} + + @selectioninfo(mincount=2) + def add_node_activation_recorder(netapi, selection, interval=1, name='node_activation_recoder'): + """Adds an activation recorder to the selected nodes""" + firstnode = netapi.get_node(selection[0]) + nodespace = netapi.get_nodespace(firstnode.parent_nodespace) + group_config = { + 'nodespace_uid': nodespace.uid, + 'node_uids': selection} + recorder = netapi.add_node_activation_recorder(group_config, name=name, interval=int(interval)) + return {'uid': recorder.uid} @selectioninfo(mincount=2) def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen', to_slot='gen', interval=1, name='linkweight_recoder'): @@ -42,7 +54,8 @@ def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen' 'nodespace_uid': nodespace.uid, 'node_uids': [n.uid for n in grouplist[1]], 'gatetype': to_slot} - netapi.add_linkweight_recorder(from_group_config, to_group_config, name=name, interval=int(interval)) + recorder = netapi.add_linkweight_recorder(from_group_config, to_group_config, name=name, interval=int(interval)) + return {'uid': recorder.uid} except ImportError: diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 5a4f0124..e7545733 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -137,16 +137,16 @@ def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, first_ if not group_config.get('node_uids', []): nodes = self._nodenet.netapi.get_nodes(nodespace=self.nodespace, node_name_prefix=group_config['node_name_prefix'], sortby=group_config.get('sortby', 'id')) else: - nodes = [self._nodenet.get_node(uid) for uid in node_uids] + nodes = [self._nodenet.get_node(uid) for uid in group_config['node_uids']] - assert len(set([n.type for n in nodes])) == 1 # assert we have a homogenous group + node_uids = [n.uid for n in nodes] + assert len(set([n.type for n in nodes])) == 1 # assert we have a homogeneous group self.gatetypes = nodes[0].get_gate_types() self.groupnames = [] for g in self.gatetypes: - group_config['gatetype'] = g - group_config['group_name'] = self.base_group_name + '_%s' % g - self.groupnames.append(group_config['group_name']) - self._nodenet.group_nodes_by_names(**group_config) + group_name = self.base_group_name + '_%s' % g + self.groupnames.append(group_name) + self._nodenet.group_nodes_by_ids(self.nodespace, node_uids, gatetype=g, group_name=group_name, sortby=group_config.get('sortby', 'id')) self.shapes = {'activations': (self.initial_size, len(self.gatetypes), len(nodes))} diff --git a/micropsi_core/tests/test_operations.py b/micropsi_core/tests/test_operations.py index b9cef870..bcc1d0e3 100644 --- a/micropsi_core/tests/test_operations.py +++ b/micropsi_core/tests/test_operations.py @@ -1,4 +1,4 @@ - +import pytest from micropsi_core import runtime @@ -51,4 +51,63 @@ def test_autoalign_operation(test_nodenet): assert p1.position[1] < p2.position[1] assert p2.position[1] == p3.position[1] result, data = runtime.run_operation(test_nodenet, "autoalign", {}, [p1.uid]) - assert 'error' in data \ No newline at end of file + assert 'error' in data + + +@pytest.mark.engine("theano_engine") +def test_add_gate_activation_recorder_operation(test_nodenet): + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodes = [] + for i in range(3): + nodes.append(netapi.create_node("Register", None, "node%d" % i)) + res, data = runtime.run_operation(test_nodenet, 'add_gate_activation_recorder', { + 'gate': 'gen', + 'interval': 1, + 'name': 'gate_activation_recoder', + }, [n.uid for n in nodes]) + runtime.step_nodenet(test_nodenet) + runtime.get_recorder(test_nodenet, data['uid']).values['activations'].shape == (3) + + +@pytest.mark.engine("theano_engine") +def test_add_node_activation_recorder_operation(test_nodenet): + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodes = [] + for i in range(3): + nodes.append(netapi.create_node("Pipe", None, "node%d" % i)) + res, data = runtime.run_operation(test_nodenet, 'add_node_activation_recorder', { + 'interval': 1, + 'name': 'node_activation_recoder', + }, [n.uid for n in nodes]) + runtime.step_nodenet(test_nodenet) + runtime.get_recorder(test_nodenet, data['uid']).values['activations'].shape == (7, 3) + + +@pytest.mark.engine("theano_engine") +def test_add_linkweight_recorder_operation(test_nodenet): + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodes1 = [] + nodes2 = [] + for i in range(3): + n1 = netapi.create_node("Register", None, "node1%d" % i) + n2 = netapi.create_node("Register", None, "node2%d" % i) + n1.position = [i * 20, 20] + n2.position = [i * 20, 40] + nodes1.append(n1) + nodes2.append(n2) + for i in range(3): + for j in range(3): + netapi.link(nodes1[i], 'gen', nodes2[j], 'gen') + + res, data = runtime.run_operation(test_nodenet, 'add_linkweight_recorder', { + 'direction': 'down', + 'from_gate': 'gen', + 'to_slot': 'gen', + 'interval': 1, + 'name': 'linkweight_recoder' + }, [n.uid for n in nodes1] + [n.uid for n in nodes2]) + runtime.step_nodenet(test_nodenet) + runtime.get_recorder(test_nodenet, data['uid']).values['linkweights'].shape == (3, 3) diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index fc9bb3d3..448f9e0f 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1506,7 +1506,7 @@ def test_run_operation(app, test_nodenet, node): @pytest.mark.engine("theano_engine") -def test_add_activation_recorder(app, test_nodenet, resourcepath): +def test_add_gate_activation_recorder(app, test_nodenet, resourcepath): from micropsi_core import runtime app.set_auth() nodenet = runtime.nodenets[test_nodenet] @@ -1514,7 +1514,7 @@ def test_add_activation_recorder(app, test_nodenet, resourcepath): nodespace = netapi.get_nodespace(None) for i in range(3): netapi.create_node('Register', None, "testnode_%d" % i) - response = app.post_json('/rpc/add_activation_recorder', { + response = app.post_json('/rpc/add_gate_activation_recorder', { 'nodenet_uid': test_nodenet, 'group_definition': {'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, 'name': "recorder", @@ -1528,6 +1528,29 @@ def test_add_activation_recorder(app, test_nodenet, resourcepath): assert 'activations' in netapi.get_recorder(recorder_uid).values +@pytest.mark.engine("theano_engine") +def test_add_node_activation_recorder(app, test_nodenet, resourcepath): + from micropsi_core import runtime + app.set_auth() + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + nodespace = netapi.get_nodespace(None) + for i in range(3): + netapi.create_node('Pipe', None, "testnode_%d" % i) + response = app.post_json('/rpc/add_node_activation_recorder', { + 'nodenet_uid': test_nodenet, + 'group_definition': {'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, + 'name': "recorder", + 'interval': 2 + }) + assert_success(response) + recorder_uid = response.json_body['data'] + runtime.step_nodenet(test_nodenet) + runtime.step_nodenet(test_nodenet) + assert netapi.get_recorder(recorder_uid).name == 'recorder' + assert netapi.get_recorder(recorder_uid).values['activations'][0].shape == (7, 3) + + @pytest.mark.engine("theano_engine") def test_add_linkweight_recorder(app, test_nodenet, resourcepath): from micropsi_core import runtime @@ -1568,7 +1591,7 @@ def test_clear_recorder(app, test_nodenet, resourcepath): nodespace = netapi.get_nodespace(None) for i in range(3): netapi.create_node('Register', None, "testnode_%d" % i) - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") for i in range(3): runtime.step_nodenet(test_nodenet) @@ -1589,7 +1612,7 @@ def test_remove_recorder(app, test_nodenet, resourcepath): nodespace = netapi.get_nodespace(None) for i in range(3): netapi.create_node('Register', None, "testnode_%d" % i) - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder") for i in range(3): runtime.step_nodenet(test_nodenet) @@ -1610,7 +1633,7 @@ def test_get_recorders(app, test_nodenet): for i in range(3): netapi.create_node('Register', None, "testnode_%d" % i) runtime.step_nodenet(test_nodenet) - recorder = netapi.add_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=3) + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, name="recorder", interval=3) runtime.step_nodenet(test_nodenet) runtime.step_nodenet(test_nodenet) response = app.post_json('/rpc/get_recorders', { @@ -1623,4 +1646,4 @@ def test_get_recorders(app, test_nodenet): assert data["filename"] == recorder.filename assert data["current_index"] == 0 assert data["first_step"] == 3 - assert data["classname"] == 'ActivationRecorder' + assert data["classname"] == 'GateActivationRecorder' From 1ae1f031d6cc7cf28417f37b4a7c724c07bba4b9 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 13 May 2016 22:07:31 +0200 Subject: [PATCH 096/945] frontend --- micropsi_server/static/js/monitor.js | 23 +++++++++++++++++++---- micropsi_server/view/monitors.tpl | 7 ++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 88a61faa..40ac8c32 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -49,8 +49,8 @@ $(function(){ var rec_type_dd = $('#recorder_type_input'); rec_type_dd.on('change', function(event){ var type = rec_type_dd.val(); - $('fieldset.recorder_specific').hide(); - $('fieldset.'+type).show(); + $('.recorder_specific').hide(); + $('.'+type).show(); }) $('.add_recorder').on('click', function(event){ event.preventDefault(); @@ -72,8 +72,8 @@ $(function(){ }; var type = $('#recorder_type_input').val(); var method = null; - if(type == 'activation_recorder'){ - method = 'add_activation_recorder'; + if(type == 'gate_activation_recorder'){ + method = 'add_gate_activation_recorder'; params['group_definition'] = { 'nodespace_uid': $('#recorder_nodespace_uid').val(), 'gatetype': $('#recorder_gate').val(), @@ -88,6 +88,21 @@ $(function(){ } else{ params.group_definition['node_name_prefix'] = $('#recorder_node_name_prefix').val(); } + } else if(type == 'node_activation_recorder'){ + method = 'add_node_activation_recorder'; + params['group_definition'] = { + 'nodespace_uid': $('#recorder_nodespace_uid').val(), + } + var ids = $('#recorder_node_uids').val(); + if(ids){ + ids = ids.split(',') + for(var i in ids){ + ids[i] = ids[i].trim(); + } + params.group_definition['node_uids'] = ids; + } else{ + params.group_definition['node_name_prefix'] = $('#recorder_node_name_prefix').val(); + } } else if(type == 'linkweight_recorder') { method = "add_linkweight_recorder"; params['from_group_definition'] = { diff --git a/micropsi_server/view/monitors.tpl b/micropsi_server/view/monitors.tpl index 991ffdec..73547d51 100644 --- a/micropsi_server/view/monitors.tpl +++ b/micropsi_server/view/monitors.tpl @@ -147,13 +147,14 @@
    -
    +
    Nodes
    @@ -171,7 +172,7 @@
    -
    +
    From f3f798571854a37048d8b10f0ecb9eecacd1fcda Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 17 May 2016 15:50:02 +0200 Subject: [PATCH 097/945] update dependencies --- requirements.txt | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/requirements.txt b/requirements.txt index fdc4a6b8..17fb030f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,23 @@ -CherryPy==3.6.0 -Theano==0.7.0 -WebOb==1.4 -WebTest==2.0.16 beautifulsoup4==4.4.1 -cov-core==1.14.0 -coverage==3.7.1 -matplotlib==1.4.3 -mock==1.0.1 -numpy==1.9.2 +CherryPy==5.4.0 +coverage==4.0.3 +cycler==0.10.0 +matplotlib==1.5.1 +mock==2.0.0 +nose-parameterized==0.5.0 +numpy==1.11.0 +pbr==1.9.1 py==1.4.31 pycrypto==2.6.1 -pytest==2.8.7 -pytest-cov==1.8.1 -scipy==0.17.0 -six==1.8.0 --e git+https://github.com/micropsi-industries/spock.git#egg=spock-dev -waitress==0.8.9 +pyparsing==2.1.4 +pytest==2.9.1 +pytest-cov==2.2.1 +python-dateutil==2.5.3 +pytz==2016.4 +scipy==0.17.1 +six==1.10.0 +-e git+https://github.com/micropsi-industries/spock.git#egg=spock +Theano==0.8.2 +waitress==0.9.0 +WebOb==1.6.0 +WebTest==2.0.21 From 28d75d1a081dd84f178da08ad4c978bfb40077ac Mon Sep 17 00:00:00 2001 From: policecar Date: Wed, 18 May 2016 11:03:34 +0200 Subject: [PATCH 098/945] Provide distance not reward. --- micropsi_core/world/vrep/vrep_world.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 00ded7b8..8bfbe540 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -59,7 +59,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) self.handle_res(res) if self.ball_handle < 1: - self.logger.warn("Could not get handle for Ball object, reward values will not be available.") + self.logger.warn("Could not get handle for Ball object, distance values will not be available.") else: res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) if res != 0 and res != 1: @@ -134,7 +134,7 @@ def __init__(self, world, uid=None, **data): self.available_datatargets = [] self.available_datasources = [] - self.available_datasources.append("reward") + self.available_datasources.append("distance") self.available_datatargets.append("restart") self.available_datatargets.append("execute") @@ -156,7 +156,7 @@ def __init__(self, world, uid=None, **data): self.execute_offset = 1 self.joint_offset = 2 - self.reward_offset = 0 + self.distance_offset = 0 self.joint_angle_offset = 1 self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) @@ -211,12 +211,12 @@ def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, False) # get data and feedback - # read reward value + # read distance value if self.world.ball_handle > 0: res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) - self.datasource_values[self.reward_offset] = -dist + self.datasource_values[self.distance_offset] = dist # read joint angle and force values res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) From 3a52f3540e799531e3fd7e4319a9f069dfd61182 Mon Sep 17 00:00:00 2001 From: policecar Date: Wed, 18 May 2016 11:07:08 +0200 Subject: [PATCH 099/945] Don't propagate stale values after restart. --- micropsi_core/world/vrep/vrep_world.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 8bfbe540..282c2b59 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -172,6 +172,8 @@ def __init__(self, world, uid=None, **data): self.image.norm.vmin = 0 self.image.norm.vmax = 1 + self.update_data_sources_and_targets() + def get_available_datasources(self): return self.available_datasources From 31168e967eb1f1eb64803f5bcee86e5aa116c4cf Mon Sep 17 00:00:00 2001 From: policecar Date: Wed, 18 May 2016 11:08:07 +0200 Subject: [PATCH 100/945] Do write current sensor values but not old actor values on restart. --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 282c2b59..6b42daa8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -195,7 +195,7 @@ def update_data_sources_and_targets(self): vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(1) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) - return + execute = False # execute movement, send new target angles if execute: From e056080f235095f6998db093c2a32d3ca8f3103f Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 11:44:13 +0200 Subject: [PATCH 101/945] Explicit sensor value fetching --- micropsi_core/world/vrep/vrep_world.py | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 6b42daa8..3bf4638d 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -172,7 +172,7 @@ def __init__(self, world, uid=None, **data): self.image.norm.vmin = 0 self.image.norm.vmax = 1 - self.update_data_sources_and_targets() + self.fetch_sensor_and_feedback_values_from_simulation() def get_available_datasources(self): return self.available_datasources @@ -195,7 +195,8 @@ def update_data_sources_and_targets(self): vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(1) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) - execute = False + self.fetch_sensor_and_feedback_values_from_simulation() + return # execute movement, send new target angles if execute: @@ -212,6 +213,26 @@ def update_data_sources_and_targets(self): vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) + # read joint angle and force values + self.fetch_sensor_and_feedback_values_from_simulation(True) + + # read vision data + # if no observer present, don't query vision data + if self.world.vision_type != "grayscale": + return + + res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0] * self.world.vision_resolution[1], 3)).astype(np.float32) + rgb_image /= 255. + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.world.vision_resolution[0], self.world.vision_resolution[1]))[::-1,:] # todo: npyify and make faster + self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() + + self.image.set_data(y_image) + + return self.image + + def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=False): + # get data and feedback # read distance value if self.world.ball_handle > 0: @@ -220,7 +241,6 @@ def update_data_sources_and_targets(self): dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) self.datasource_values[self.distance_offset] = dist - # read joint angle and force values res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) for i, joint_handle in enumerate(self.world.joints): target_angle = self.datatarget_values[self.joint_offset + i] @@ -229,7 +249,7 @@ def update_data_sources_and_targets(self): if self.world.control_type == "force/torque": angle = data[i*2] / math.pi force = data[i*2 + 1] - if abs(angle) - abs(target_angle) < .001 and execute: + if abs(angle) - abs(target_angle) < .001 and include_feedback: self.datatarget_feedback_values[self.joint_offset + i] = 1 elif self.world.control_type == "angles": angle = data[i * 2] / math.pi @@ -237,18 +257,3 @@ def update_data_sources_and_targets(self): angle = data[i * 2] / math.pi self.datasource_values[self.joint_angle_offset + i] = angle self.datasource_values[self.joint_force_offset + i] = force - - # read vision data - # if no observer present, don't query vision data - if self.world.vision_type != "grayscale": - return - - res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) - rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0] * self.world.vision_resolution[1], 3)).astype(np.float32) - rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.world.vision_resolution[0], self.world.vision_resolution[1]))[::-1,:] # todo: npyify and make faster - self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() - - self.image.set_data(y_image) - - return self.image From 007cf1af8a32b6795d0154d88f98fb918f756a9e Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 11:56:23 +0200 Subject: [PATCH 102/945] Resetting to 0.5 (in preparation for random) --- micropsi_core/world/vrep/vrep_world.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 3bf4638d..9d914fef 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -194,7 +194,11 @@ def update_data_sources_and_targets(self): if restart: vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(1) + for i, joint_handle in enumerate(self.world.joints): + tval = 0.5 * math.pi + vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + self.fetch_sensor_and_feedback_values_from_simulation() return From b3938ad06546ea57fb905e0c18d263419fd8c7c7 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 11:56:23 +0200 Subject: [PATCH 103/945] Resetting to 0.5 (in preparation for random) --- micropsi_core/world/vrep/vrep_world.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 3bf4638d..dfe84609 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -194,7 +194,13 @@ def update_data_sources_and_targets(self): if restart: vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(1) + for i, joint_handle in enumerate(self.world.joints): + self.datatarget_values[self.joint_offset + i] = 0.5 + self.current_angle_target_values[i] = 0.5 + tval = self.current_angle_target_values[i] * math.pi + vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + self.fetch_sensor_and_feedback_values_from_simulation() return From ec899fe9158f4fd6072ce77c1f804b12c476fd2e Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 12:17:39 +0200 Subject: [PATCH 104/945] oneshot fix --- micropsi_core/world/vrep/vrep_world.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index dfe84609..70151bcd 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -194,12 +194,15 @@ def update_data_sources_and_targets(self): if restart: vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(1) + vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + + vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): self.datatarget_values[self.joint_offset + i] = 0.5 self.current_angle_target_values[i] = 0.5 tval = self.current_angle_target_values[i] * math.pi vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + vrep.simxPauseCommunication(self.world.clientID, False) self.fetch_sensor_and_feedback_values_from_simulation() return From 9405b7d36a7bfa1f3d4bb5a49b809c185272c702 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 12:44:11 +0200 Subject: [PATCH 105/945] reset fix --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 70151bcd..0163b803 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -201,7 +201,7 @@ def update_data_sources_and_targets(self): self.datatarget_values[self.joint_offset + i] = 0.5 self.current_angle_target_values[i] = 0.5 tval = self.current_angle_target_values[i] * math.pi - vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) self.fetch_sensor_and_feedback_values_from_simulation() From 957b81ed220cf0d0497b4080e86a323c035b6c83 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 14:03:11 +0200 Subject: [PATCH 106/945] random re-init after reset --- micropsi_core/world/vrep/vrep_world.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0163b803..9f3f6ba9 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -8,6 +8,7 @@ from io import BytesIO import base64 +import random from micropsi_core.world.vrep import vrep from micropsi_core.world.vrep import vrepConst @@ -198,8 +199,8 @@ def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): - self.datatarget_values[self.joint_offset + i] = 0.5 - self.current_angle_target_values[i] = 0.5 + self.datatarget_values[self.joint_offset + i] = random.uniform(0.2, 0.8) + self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] tval = self.current_angle_target_values[i] * math.pi vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) From 8028c8485008959e32c40256af861a67fe20b409 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 14:25:57 +0200 Subject: [PATCH 107/945] API name changes --- micropsi_core/nodenet/theano_engine/theano_netapi.py | 8 ++++---- micropsi_core/nodenet/theano_engine/theano_node.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_netapi.py b/micropsi_core/nodenet/theano_engine/theano_netapi.py index 4f9da293..cadd6092 100644 --- a/micropsi_core/nodenet/theano_engine/theano_netapi.py +++ b/micropsi_core/nodenet/theano_engine/theano_netapi.py @@ -46,8 +46,8 @@ def decay_por_links(self, nodespace_uid): w[rows, cols] = w_update partition.w.set_value(w, borrow=True) - def group_highdimensional_gates(self, node_uid, gate, group_name=None): - self.__nodenet.group_highdimensional_elements(node_uid, gate=gate, group_name=group_name) + def group_node_gates(self, node_uid, gate_prefix, group_name=None): + self.__nodenet.group_highdimensional_elements(node_uid, gate=gate_prefix, group_name=group_name) - def group_highdimensional_slots(self, node_uid, slot, group_name=None): - self.__nodenet.group_highdimensional_elements(node_uid, slot=slot, group_name=group_name) + def group_node_slots(self, node_uid, slot_prefix, group_name=None): + self.__nodenet.group_highdimensional_elements(node_uid, slot=slot_prefix, group_name=group_name) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 9e75a0e5..ee029e88 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -415,17 +415,17 @@ def node_function(self): else: raise - def get_slot_activation_array(self): + def get_slot_activations(self): return self.slot_fat_snapshot - def set_gate_activation_array(self, new_activations): + def set_gate_activations(self, new_activations): start = self._partition.allocated_node_offsets[node_from_id(self.uid)] end = start + len(self._nodetype.gatetypes) a_array = self._partition.a.get_value(borrow=True) a_array[start:end] = new_activations self._partition.a.set_value(a_array, borrow=True) - def get_gate_activation_array(self): + def get_gate_activations(self): start = self._partition.allocated_node_offsets[node_from_id(self.uid)] end = start + len(self._nodetype.gatetypes) a_array = self._partition.a.get_value(borrow=True) From 1728187edaa489175e8634252b4f97a40a1305c8 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 14:28:30 +0200 Subject: [PATCH 108/945] Negative angles are also good anges --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 9f3f6ba9..92b69668 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -199,7 +199,7 @@ def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): - self.datatarget_values[self.joint_offset + i] = random.uniform(0.2, 0.8) + self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] tval = self.current_angle_target_values[i] * math.pi vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) From 8d8403f4cf347c125f0f21f319c8b6c964528ec5 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 18:31:53 +0200 Subject: [PATCH 109/945] Moving GPLed vrep files to their own private repository From 58e668cba36ef6593a20a830e5f9a3986b97bfbd Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 18:33:02 +0200 Subject: [PATCH 110/945] use proxy=less retrieval --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 6ef8fbef..6870432f 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1611,9 +1611,7 @@ def get_nodespace_changes(self, nodespace_uids=[], since_step=0): result['nodespaces_deleted'].extend(self.deleted_items[i].get('nodespaces_deleted', [])) result['nodes_deleted'].extend(self.deleted_items[i].get('nodes_deleted', [])) changed_nodes, changed_nodespaces = partition.get_nodespace_changes(nodespace.uid, since_step) - for uid in changed_nodes: - uid = node_to_id(uid, partition.pid) - result['nodes_dirty'][uid] = self.get_node(uid).get_data(include_links=True) + result['nodes_dirty'].update(partition.get_node_data(ids=changed_nodes, include_links=True, include_followupnodes=False)[0]) for uid in changed_nodespaces: uid = nodespace_to_id(uid, partition.pid) result['nodespaces_dirty'][uid] = self.get_nodespace(uid).get_data() From 53b3d1ebd6ffa3d761d5b92e5a86602d10876ea9 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 18:43:55 +0200 Subject: [PATCH 111/945] Using external repo for vrep interface --- Makefile | 2 ++ micropsi_core/world/vrep/vrep_world.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ec457e7e..44cd5e9f 100644 --- a/Makefile +++ b/Makefile @@ -29,5 +29,7 @@ test-toolkit: test-agents: bin/py.test --agents +vrep: + bin/pip install -e git+https://github.com/micropsi-industries/vrep-interface.git#egg=vrep-interface-dev .PHONY: run diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 92b69668..70e95178 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -10,8 +10,7 @@ import base64 import random -from micropsi_core.world.vrep import vrep -from micropsi_core.world.vrep import vrepConst +import vrep from micropsi_core.world.world import World from micropsi_core.world.worldadapter import ArrayWorldAdapter From a5b335afd6028fcc55a3f7514d1c10048b9a76d2 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 19:48:23 +0200 Subject: [PATCH 112/945] fix tests --- micropsi_core/tests/test_node.py | 4 ++-- micropsi_core/tests/test_node_netapi.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 4a5316a8..8586c35f 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -90,7 +90,7 @@ def phatNM(netapi, node, **_): node.take_slot_activation_snapshot() # test get_slot_activation - data = node.get_slot_activation_array() + data = node.get_slot_activations() assert len(data) == 1024 + 62 + 3 # fat_slots + gen/sub/sur new_activation = np.random.rand(768 + 13 + 3) # fat gates + gen/sub/sur @@ -117,7 +117,7 @@ def phatNM(netapi, node, **_): # test setting gate details, get_gate_activation node.set_gatefunction_name("A_out0", "sigmoid") micropsi.step_nodenet(test_nodenet) - act = node.get_gate_activation_array() + act = node.get_gate_activations() assert act[3] == 0.5 assert np.all(act[4:] == 0) diff --git a/micropsi_core/tests/test_node_netapi.py b/micropsi_core/tests/test_node_netapi.py index 990498bb..69fcdd8c 100644 --- a/micropsi_core/tests/test_node_netapi.py +++ b/micropsi_core/tests/test_node_netapi.py @@ -1186,12 +1186,12 @@ def phatNM(netapi, node, **_): for i in range(10): registers.append(netapi.create_node("Register", None, 'reg%d' % i)) netapi.group_nodes_by_names(None, node_name_prefix='reg', gate='gen') - netapi.group_highdimensional_slots(node.uid, slot='inbound', group_name='fat_in') + netapi.group_node_slots(node.uid, slot='inbound', group_name='fat_in') netapi.set_link_weights(None, 'reg', None, 'fat_in', np.eye(10)) for i, r in enumerate(registers): links = r.get_gate('gen').get_links() assert len(links) == 1 assert links[0].target_node.uid == node.uid assert links[0].target_slot.type == 'inbound%d' % i - netapi.group_highdimensional_gates(node.uid, 'outbound', group_name='fat_out') + netapi.group_node_gates(node.uid, 'outbound', group_name='fat_out') assert np.all(netapi.get_activations(None, 'fat_out') == np.zeros(2)) From f90632bb5e1c27f2db2bbeb3c835ce37ad056ed1 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 19:49:28 +0200 Subject: [PATCH 113/945] Initial V-REP readme --- micropsi_core/world/vrep/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 micropsi_core/world/vrep/README.md diff --git a/micropsi_core/world/vrep/README.md b/micropsi_core/world/vrep/README.md new file mode 100644 index 00000000..fc46cf64 --- /dev/null +++ b/micropsi_core/world/vrep/README.md @@ -0,0 +1,2 @@ +# vrep-interface +v-rep GPL code that cannot be part of the main micropsi2 repository From 799c3f593692b384dad660770ce91b658039203a Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 19:52:21 +0200 Subject: [PATCH 114/945] Proper readme content --- micropsi_core/world/vrep/README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/README.md b/micropsi_core/world/vrep/README.md index fc46cf64..3516b4c2 100644 --- a/micropsi_core/world/vrep/README.md +++ b/micropsi_core/world/vrep/README.md @@ -1,2 +1,22 @@ -# vrep-interface -v-rep GPL code that cannot be part of the main micropsi2 repository +# V-REP interface + +vrep_world.py provides an interface to the Virtual Robot Experimentation platform (V-REP) by Coppelia Robotics GmbH. +V-REP is free for academic use. + +To create a V-REP world in the toolkit, please: + +- download and install V-REP from http://www.coppeliarobotics.com/downloads.html +- get the vrep.py and vrepConst.py files from the V-REP folder, in programming/remoteApiBindings/python/python, and add them to your virtualenv +- get the dylib/dll/so file for your platform from the V-REP folder, the file should be in programming/remoteApiBindings/lib/lib, and put it next to vrep.py +- make sure your firewalls aren't blocking local connections to port 19999 + + portNumber = 19999 + status, info, serverVersion, clientVersion, clientIp=simExtRemoteApiStatus(portNumber) + if status < 0 then + simExtRemoteApiStart(portNumber) + end + +When instatiating the V-REP world in the toolkit, you will be able to specify the name of the robot that will +be controlled. + +If a camera called "Observer" is present in the V-REP scene, pixel data will be provided to connected node nets. \ No newline at end of file From d541188680ef5fc4e0f2304bd115450642b45d99 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 19:53:38 +0200 Subject: [PATCH 115/945] formatting --- micropsi_core/world/vrep/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/README.md b/micropsi_core/world/vrep/README.md index 3516b4c2..4b817103 100644 --- a/micropsi_core/world/vrep/README.md +++ b/micropsi_core/world/vrep/README.md @@ -9,6 +9,7 @@ To create a V-REP world in the toolkit, please: - get the vrep.py and vrepConst.py files from the V-REP folder, in programming/remoteApiBindings/python/python, and add them to your virtualenv - get the dylib/dll/so file for your platform from the V-REP folder, the file should be in programming/remoteApiBindings/lib/lib, and put it next to vrep.py - make sure your firewalls aren't blocking local connections to port 19999 + portNumber = 19999 status, info, serverVersion, clientVersion, clientIp=simExtRemoteApiStatus(portNumber) @@ -19,4 +20,4 @@ To create a V-REP world in the toolkit, please: When instatiating the V-REP world in the toolkit, you will be able to specify the name of the robot that will be controlled. -If a camera called "Observer" is present in the V-REP scene, pixel data will be provided to connected node nets. \ No newline at end of file +If a camera called "Observer" is present in the V-REP scene, pixel data will be provided to connected node nets. From e81bf8f4f168c47fe36c4e1c9e0db0cf16cb41fe Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 18 May 2016 19:54:18 +0200 Subject: [PATCH 116/945] formatting --- micropsi_core/world/vrep/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/README.md b/micropsi_core/world/vrep/README.md index 4b817103..4fe9b70b 100644 --- a/micropsi_core/world/vrep/README.md +++ b/micropsi_core/world/vrep/README.md @@ -9,7 +9,8 @@ To create a V-REP world in the toolkit, please: - get the vrep.py and vrepConst.py files from the V-REP folder, in programming/remoteApiBindings/python/python, and add them to your virtualenv - get the dylib/dll/so file for your platform from the V-REP folder, the file should be in programming/remoteApiBindings/lib/lib, and put it next to vrep.py - make sure your firewalls aren't blocking local connections to port 19999 - + + portNumber = 19999 status, info, serverVersion, clientVersion, clientIp=simExtRemoteApiStatus(portNumber) @@ -17,7 +18,9 @@ To create a V-REP world in the toolkit, please: simExtRemoteApiStart(portNumber) end + When instatiating the V-REP world in the toolkit, you will be able to specify the name of the robot that will be controlled. + If a camera called "Observer" is present in the V-REP scene, pixel data will be provided to connected node nets. From ff7d7e367ffbdd27094670f92531cc6bd22bccee Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 15:47:57 +0200 Subject: [PATCH 117/945] kill vrep communication thread on ctrl+c, on unload --- micropsi_core/world/vrep/vrep_world.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 70e95178..5fbbfb32 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -86,6 +86,10 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N else: self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) + from micropsi_core.runtime import add_signal_handler + add_signal_handler(self.kill_vrep_connection) + + def handle_res(self, res): if res != vrep.simx_return_ok: error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) @@ -110,6 +114,13 @@ def get_world_view(self, step): else: return None + def kill_vrep_connection(self, *args): + vrep.simxFinish(-1) + + def __del__(self): + self.kill_vrep_connection() + + @staticmethod def get_config_options(): return [ From bf9f5c7b8a257642a845d76cad921c227da8ce46 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 15:48:33 +0200 Subject: [PATCH 118/945] default the robot names for the moment --- micropsi_core/world/vrep/vrep_world.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 5fbbfb32..0735c7b8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -126,7 +126,8 @@ def get_config_options(): return [ {'name': 'robot_name', 'description': 'The name of the robot object in V-REP', - 'default': 'LBR_iiwa_7_R800'}, + 'default': 'LBR_iiwa_7_R800', + 'options': ["LBR_iiwa_7_R800", "MTB_Robot"]}, {'name': 'control_type', 'description': 'The type of input sent to the robot', 'default': 'force/torque', From f9dc42dc344d9b6d6b73dd4c2e902c8b188e07d3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 15:49:38 +0200 Subject: [PATCH 119/945] sometimes vrep does not send any data. ignore for the moment --- micropsi_core/world/vrep/vrep_world.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0735c7b8..4475f608 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -263,6 +263,11 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals self.datasource_values[self.distance_offset] = dist res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) + + if len(data) == 0: + self.world.logger.warning("No data from vrep received") + return + for i, joint_handle in enumerate(self.world.joints): target_angle = self.datatarget_values[self.joint_offset + i] angle = 0 From ac9288a256db89079442b42be1f30d67bf887764 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 19:52:01 +0200 Subject: [PATCH 120/945] replace exceptions with logger messages --- micropsi_core/world/vrep/vrep_world.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 4475f608..0ed23542 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -35,10 +35,17 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.vision_type = config['vision_type'] self.control_type = config['control_type'] + self.joints = [] + self.vision_resolution = [] + + self.iiwa_handle = -1 + self.ball_handle = -1 + vrep.simxFinish(-1) # just in case, close all opened connections self.clientID = vrep.simxStart('127.0.0.1', 19999, True, 0, 5000, 5) # Connect to V-REP if self.clientID == -1: - raise Exception("Could not connect to v-rep.") + self.logger.critical("Could not connect to v-rep") + return self.logger.info("Connected to local V-REP at port 19999") @@ -50,7 +57,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N res, self.iiwa_handle = vrep.simxGetObjectHandle(self.clientID, self.robot_name, vrep.simx_opmode_blocking) self.handle_res(res) if self.iiwa_handle < 1: - raise Exception("There seems to be no robot with the name %s in the v-rep simulation." % self.robot_name) + self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.robot_name) res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) self.handle_res(res) @@ -82,14 +89,13 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) self.vision_resolution = resolution if len(resolution) != 2: - raise Exception("Could not determine vision resolution after 1 second wait time.") + self.logger.error("Could not determine vision resolution after 1 second wait time.") else: self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) from micropsi_core.runtime import add_signal_handler add_signal_handler(self.kill_vrep_connection) - def handle_res(self, res): if res != vrep.simx_return_ok: error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) From 5a09daf91fe55496f251e2c2298c5d2c00a3f6c7 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 18 May 2016 19:53:51 +0200 Subject: [PATCH 121/945] fix world logger, paint errors and criticals more flashy --- micropsi_core/world/world.py | 2 +- micropsi_server/static/css/micropsi-styles.css | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 72b6b2b8..293e4993 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -95,7 +95,7 @@ def __init__(self, filename, world_type="", name="", owner="", uid=None, engine= uid (optional): unique handle of the world; if none is given, it will be generated """ - self.logger = logging.getLogger('world_logger') + self.logger = logging.getLogger('world') # persistent data self.data = { diff --git a/micropsi_server/static/css/micropsi-styles.css b/micropsi_server/static/css/micropsi-styles.css index e2b5c4c0..5e2d8820 100644 --- a/micropsi_server/static/css/micropsi-styles.css +++ b/micropsi_server/static/css/micropsi-styles.css @@ -36,7 +36,10 @@ h3 { .log_DEBUG {color: #1700FF;} .log_INFO {color: #4b4b4b;} .log_WARNING{color: #942628;} -.log_ERROR {color: #e12628;} +.log_ERROR, .log_CRITICAL, .log_ERROR:hover, .log_CRITICAL:hover { + background-color: #c22; + color: white; +} .delete_monitor i { opacity: 0.5; } .delete_monitor:hover i { opacity: 1; } From 7a0ed719a2ae2e547a2aa249389a78e88bbde27a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 19 May 2016 11:41:33 +0200 Subject: [PATCH 122/945] clone via ssh --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 44cd5e9f..e7d87edb 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,6 @@ test-agents: bin/py.test --agents vrep: - bin/pip install -e git+https://github.com/micropsi-industries/vrep-interface.git#egg=vrep-interface-dev + bin/pip install -e git+git@github.com:micropsi-industries/vrep-interface.git#egg=vrep-interface-dev .PHONY: run From 73c0ae0976604f87e36442a8b0c35835229920b8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 19 May 2016 11:41:47 +0200 Subject: [PATCH 123/945] ignore exceptions in teardown --- micropsi_core/world/vrep/vrep_world.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0ed23542..2aa19b02 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -121,12 +121,14 @@ def get_world_view(self, step): return None def kill_vrep_connection(self, *args): - vrep.simxFinish(-1) + try: + vrep.simxFinish(-1) + except: + pass def __del__(self): self.kill_vrep_connection() - @staticmethod def get_config_options(): return [ From e68f7ac0692c5f8bdb2df381f345e8533fd968d7 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Thu, 19 May 2016 11:49:00 +0200 Subject: [PATCH 124/945] Introducing restart actuator cooldown --- micropsi_core/world/vrep/vrep_world.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 70e95178..47576a72 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -150,6 +150,8 @@ def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) + self.last_restart = 0 + self.current_angle_target_values = np.zeros_like(self.world.joints) self.restart_offset = 0 @@ -187,7 +189,7 @@ def update_data_sources_and_targets(self): self.datatarget_feedback_values = [0] * len(self.available_datatargets) self.datasource_values = [0] * len(self.available_datasources) - restart = self.datatarget_values[self.restart_offset] > 0.9 + restart = self.datatarget_values[self.restart_offset] > 0.9 and self.world.current_step - self.last_restart >= 5 execute = self.datatarget_values[self.execute_offset] > 0.9 # simulation restart @@ -205,6 +207,7 @@ def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, False) self.fetch_sensor_and_feedback_values_from_simulation() + self.last_restart = self.world.current_step return # execute movement, send new target angles From ca63fa4415106179f2601f69ec350da3e0530ca1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 19 May 2016 12:24:33 +0200 Subject: [PATCH 125/945] vrep host/port as parameters --- micropsi_core/world/vrep/vrep_world.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 50b47b23..0efa0fc5 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -42,7 +42,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.ball_handle = -1 vrep.simxFinish(-1) # just in case, close all opened connections - self.clientID = vrep.simxStart('127.0.0.1', 19999, True, 0, 5000, 5) # Connect to V-REP + self.clientID = vrep.simxStart(config['vrep_host'], config['vrep_port'], True, 0, 5000, 5) # Connect to V-REP if self.clientID == -1: self.logger.critical("Could not connect to v-rep") return @@ -132,6 +132,10 @@ def __del__(self): @staticmethod def get_config_options(): return [ + {'name': 'vrep_host', + 'default': '127.0.0.1'}, + {'name': 'vrep_port', + 'default': 19999}, {'name': 'robot_name', 'description': 'The name of the robot object in V-REP', 'default': 'LBR_iiwa_7_R800', From 364fee2f91d5f4607ee3158c45ec6f9919ca3e07 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Thu, 19 May 2016 16:16:01 +0200 Subject: [PATCH 126/945] various recoder -> recorder typos --- .../nodenet/operations/{recoders.py => recorders.py} | 4 ++-- micropsi_core/tests/test_operations.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename micropsi_core/nodenet/operations/{recoders.py => recorders.py} (97%) diff --git a/micropsi_core/nodenet/operations/recoders.py b/micropsi_core/nodenet/operations/recorders.py similarity index 97% rename from micropsi_core/nodenet/operations/recoders.py rename to micropsi_core/nodenet/operations/recorders.py index c5eaae2e..1cc966e7 100644 --- a/micropsi_core/nodenet/operations/recoders.py +++ b/micropsi_core/nodenet/operations/recorders.py @@ -18,7 +18,7 @@ def add_gate_activation_recorder(netapi, selection, gate='gen', interval=1, name return {'uid': recorder.uid} @selectioninfo(mincount=2) - def add_node_activation_recorder(netapi, selection, interval=1, name='node_activation_recoder'): + def add_node_activation_recorder(netapi, selection, interval=1, name='node_activation_recorder'): """Adds an activation recorder to the selected nodes""" firstnode = netapi.get_node(selection[0]) nodespace = netapi.get_nodespace(firstnode.parent_nodespace) @@ -29,7 +29,7 @@ def add_node_activation_recorder(netapi, selection, interval=1, name='node_activ return {'uid': recorder.uid} @selectioninfo(mincount=2) - def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen', to_slot='gen', interval=1, name='linkweight_recoder'): + def add_linkweight_recorder(netapi, selection, direction='down', from_gate='gen', to_slot='gen', interval=1, name='linkweight_recorder'): """ Attempts to detect two layers of nodes (y-coordinate) and adds a linkweight-monitor""" nodes = [netapi.get_node(uid) for uid in selection] nodespace = netapi.get_nodespace(nodes[0].parent_nodespace) diff --git a/micropsi_core/tests/test_operations.py b/micropsi_core/tests/test_operations.py index 97c25e22..a3390385 100644 --- a/micropsi_core/tests/test_operations.py +++ b/micropsi_core/tests/test_operations.py @@ -69,7 +69,7 @@ def test_add_gate_activation_recorder_operation(test_nodenet): res, data = runtime.run_operation(test_nodenet, 'add_gate_activation_recorder', { 'gate': 'gen', 'interval': 1, - 'name': 'gate_activation_recoder', + 'name': 'gate_activation_recorder', }, [n.uid for n in nodes]) runtime.step_nodenet(test_nodenet) runtime.get_recorder(test_nodenet, data['uid']).values['activations'].shape == (3) @@ -84,7 +84,7 @@ def test_add_node_activation_recorder_operation(test_nodenet): nodes.append(netapi.create_node("Pipe", None, "node%d" % i)) res, data = runtime.run_operation(test_nodenet, 'add_node_activation_recorder', { 'interval': 1, - 'name': 'node_activation_recoder', + 'name': 'node_activation_recorder', }, [n.uid for n in nodes]) runtime.step_nodenet(test_nodenet) runtime.get_recorder(test_nodenet, data['uid']).values['activations'].shape == (7, 3) @@ -112,7 +112,7 @@ def test_add_linkweight_recorder_operation(test_nodenet): 'from_gate': 'gen', 'to_slot': 'gen', 'interval': 1, - 'name': 'linkweight_recoder' + 'name': 'linkweight_recorder' }, [n.uid for n in nodes1] + [n.uid for n in nodes2]) runtime.step_nodenet(test_nodenet) runtime.get_recorder(test_nodenet, data['uid']).values['linkweights'].shape == (3, 3) From c1f8c68671fd65654c5d7d6bf50d85194f69f48b Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Thu, 19 May 2016 17:03:57 +0200 Subject: [PATCH 127/945] One more recorder typo --- micropsi_core/nodenet/operations/recorders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/operations/recorders.py b/micropsi_core/nodenet/operations/recorders.py index 1cc966e7..0d7ccc90 100644 --- a/micropsi_core/nodenet/operations/recorders.py +++ b/micropsi_core/nodenet/operations/recorders.py @@ -6,7 +6,7 @@ import numpy as np @selectioninfo(mincount=2) - def add_gate_activation_recorder(netapi, selection, gate='gen', interval=1, name='gate_activation_recoder'): + def add_gate_activation_recorder(netapi, selection, gate='gen', interval=1, name='gate_activation_recorder'): """Adds an activation recorder to the selected nodes""" firstnode = netapi.get_node(selection[0]) nodespace = netapi.get_nodespace(firstnode.parent_nodespace) From 7270a62f6a3190f561777546f78a300ac65de13e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 19 May 2016 17:45:10 +0200 Subject: [PATCH 128/945] node monitors should be defensive against NaN otherwise, the json becomes corrupt --- micropsi_core/nodenet/monitor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index 95e99e17..9cee8f37 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -4,6 +4,7 @@ Monitor definition """ +import math import random import micropsi_core.tools from abc import ABCMeta, abstractmethod @@ -68,11 +69,15 @@ def get_data(self): return data def step(self, step): + value = None if self.nodenet.is_node(self.node_uid): if self.type == 'gate' and self.target in self.nodenet.get_node(self.node_uid).get_gate_types(): - self.values[step] = self.nodenet.get_node(self.node_uid).get_gate(self.target).activations[self.sheaf] + value = self.nodenet.get_node(self.node_uid).get_gate(self.target).activations[self.sheaf] if self.type == 'slot' and self.target in self.nodenet.get_node(self.node_uid).get_slot_types(): - self.values[step] = self.nodenet.get_node(self.node_uid).get_slot(self.target).activations[self.sheaf] + value = self.nodenet.get_node(self.node_uid).get_slot(self.target).activations[self.sheaf] + + if value is not None and not math.isnan(value): + self.values[step] = value else: self.values[step] = None From 2c894da57968b1016ad02a2ded31b9d77c4d9844 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 19 May 2016 17:48:59 +0200 Subject: [PATCH 129/945] fix tests (again?) --- micropsi_core/tests/test_node.py | 2 +- micropsi_core/tests/test_node_netapi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 8586c35f..93190b20 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -95,7 +95,7 @@ def phatNM(netapi, node, **_): new_activation = np.random.rand(768 + 13 + 3) # fat gates + gen/sub/sur # test set_gate_activation - node.set_gate_activation_array(new_activation) + node.set_gate_activations(new_activation) target = netapi.create_node("Register", None, "Target") for g in node.get_gate_types(): netapi.link(node, g, target, 'gen') diff --git a/micropsi_core/tests/test_node_netapi.py b/micropsi_core/tests/test_node_netapi.py index 69fcdd8c..3eee949d 100644 --- a/micropsi_core/tests/test_node_netapi.py +++ b/micropsi_core/tests/test_node_netapi.py @@ -1186,7 +1186,7 @@ def phatNM(netapi, node, **_): for i in range(10): registers.append(netapi.create_node("Register", None, 'reg%d' % i)) netapi.group_nodes_by_names(None, node_name_prefix='reg', gate='gen') - netapi.group_node_slots(node.uid, slot='inbound', group_name='fat_in') + netapi.group_node_slots(node.uid, slot_prefix='inbound', group_name='fat_in') netapi.set_link_weights(None, 'reg', None, 'fat_in', np.eye(10)) for i, r in enumerate(registers): links = r.get_gate('gen').get_links() From 9c2915a93e8e40e04fec654fec537d9f42295587 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Thu, 19 May 2016 19:05:38 +0200 Subject: [PATCH 130/945] Introducing absolute ball position sensors --- micropsi_core/world/vrep/vrep_world.py | 57 ++++++++++++++++++-------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0efa0fc5..840156e2 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -34,13 +34,16 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.robot_name = config['robot_name'] self.vision_type = config['vision_type'] self.control_type = config['control_type'] + self.ballgame_type = config['ballgame_type'] self.joints = [] self.vision_resolution = [] - self.iiwa_handle = -1 + self.robot_handle = -1 self.ball_handle = -1 + self.robot_position = [] + vrep.simxFinish(-1) # just in case, close all opened connections self.clientID = vrep.simxStart(config['vrep_host'], config['vrep_port'], True, 0, 5000, 5) # Connect to V-REP if self.clientID == -1: @@ -54,26 +57,31 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.logger.info('Ping time to v-rep: %dms' % pingtime) - res, self.iiwa_handle = vrep.simxGetObjectHandle(self.clientID, self.robot_name, vrep.simx_opmode_blocking) + res, self.robot_handle = vrep.simxGetObjectHandle(self.clientID, self.robot_name, vrep.simx_opmode_blocking) self.handle_res(res) - if self.iiwa_handle < 1: + if self.robot_handle < 1: self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.robot_name) res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) self.handle_res(res) self.logger.info("Found robot with %d joints" % len(self.joints)) - res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) - self.handle_res(res) - if self.ball_handle < 1: - self.logger.warn("Could not get handle for Ball object, distance values will not be available.") - else: - res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) + if self.ballgame_type != "none": + res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.ball_handle < 1: + self.logger.warn("Could not get handle for Ball object, distance values will not be available.") + else: + res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, robot_position = vrep.simxGetObjectPosition(self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking) + if res != 0 and res != 1: + self.handle_res(res) + self.robot_position = robot_position if self.vision_type == "grayscale": res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) @@ -147,7 +155,11 @@ def get_config_options(): {'name': 'vision_type', 'description': 'Type of vision information to receive', 'default': 'none', - 'options': ["none", "grayscale"]} + 'options': ["none", "grayscale"]}, + {'name': 'ballgame_type', + 'description': 'Type of ball game to be played', + 'default': 'none', + 'options': ["none", "reach-fixed", "reach-randomized"]} ] @@ -158,7 +170,9 @@ def __init__(self, world, uid=None, **data): self.available_datatargets = [] self.available_datasources = [] - self.available_datasources.append("distance") + self.available_datasources.append("ball-distance") + self.available_datasources.append("ball-x") + self.available_datasources.append("ball-y") self.available_datatargets.append("restart") self.available_datatargets.append("execute") @@ -183,7 +197,8 @@ def __init__(self, world, uid=None, **data): self.joint_offset = 2 self.distance_offset = 0 - self.joint_angle_offset = 1 + self.position_offset = 1 + self.joint_angle_offset = self.position_offset + 2 self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) if self.world.vision_type == "grayscale": @@ -271,11 +286,17 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals # get data and feedback # read distance value - if self.world.ball_handle > 0: + if self.world.ballgame_type != "none" and self.world.ball_handle > 0: res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) + relative_pos = [0,0] + relative_pos[0] = ball_pos[0] - self.world.robot_position[0] + relative_pos[1] = ball_pos[1] - self.world.robot_position[1] + dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) self.datasource_values[self.distance_offset] = dist + self.datasource_values[self.position_offset + 0] = relative_pos[0] + self.datasource_values[self.position_offset + 1] = relative_pos[1] res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) From bb2d80bf4d09ef2064df184585382abdaa2b1a5f Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 10:10:27 +0200 Subject: [PATCH 131/945] Implementing random ball position after restart --- micropsi_core/world/vrep/vrep_world.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 840156e2..97749fa3 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -9,6 +9,7 @@ from io import BytesIO import base64 import random +import math import vrep from micropsi_core.world.world import World @@ -245,6 +246,13 @@ def update_data_sources_and_targets(self): vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) + if self.world.ballgame_type == "reach-randomized": + max_dist = 0.8 + rx = random.uniform(-max_dist, max_dist) + max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) + ry = random.uniform(-max_y, max_y) + vrep.simxSetObjectPosition(self.world.clientID, self.world.ball_handle, self.world.robot_handle, [rx, ry], vrep.simx_opmode_blocking) + self.fetch_sensor_and_feedback_values_from_simulation() self.last_restart = self.world.current_step return From 00fdf4ccd636d5372875ea9243aaf4083a9a3050 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 10:51:54 +0200 Subject: [PATCH 132/945] Fixing test after renaming --- micropsi_core/tests/test_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 8586c35f..93190b20 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -95,7 +95,7 @@ def phatNM(netapi, node, **_): new_activation = np.random.rand(768 + 13 + 3) # fat gates + gen/sub/sur # test set_gate_activation - node.set_gate_activation_array(new_activation) + node.set_gate_activations(new_activation) target = netapi.create_node("Register", None, "Target") for g in node.get_gate_types(): netapi.link(node, g, target, 'gen') From 99ca47847cd176c970e4914171ae5b0a968dd589 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 10:52:27 +0200 Subject: [PATCH 133/945] Fixing RUN-72 --- micropsi_core/nodenet/theano_engine/theano_partition.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 3a5053ad..3016960e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -606,6 +606,8 @@ def __take_native_module_slot_snapshots(self): def __calculate_native_modules(self): for uid, instance in self.native_module_instances.items(): + for gate_type in instance.get_gate_types(): + instance.get_gate(gate_type).activation = 0 instance.node_function() def __calculate_g_factors(self): From 3d46df5cebd7e3c5906e88c7056eae3110c39c3b Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 10:55:43 +0200 Subject: [PATCH 134/945] Another post-renaming test fix --- micropsi_core/tests/test_node_netapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/tests/test_node_netapi.py b/micropsi_core/tests/test_node_netapi.py index 69fcdd8c..3eee949d 100644 --- a/micropsi_core/tests/test_node_netapi.py +++ b/micropsi_core/tests/test_node_netapi.py @@ -1186,7 +1186,7 @@ def phatNM(netapi, node, **_): for i in range(10): registers.append(netapi.create_node("Register", None, 'reg%d' % i)) netapi.group_nodes_by_names(None, node_name_prefix='reg', gate='gen') - netapi.group_node_slots(node.uid, slot='inbound', group_name='fat_in') + netapi.group_node_slots(node.uid, slot_prefix='inbound', group_name='fat_in') netapi.set_link_weights(None, 'reg', None, 'fat_in', np.eye(10)) for i, r in enumerate(registers): links = r.get_gate('gen').get_links() From f2fc755856806cc4cab18ada6b7461182b4570d0 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 11:14:58 +0200 Subject: [PATCH 135/945] Better fix for RUN-72 --- micropsi_core/nodenet/theano_engine/theano_partition.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 3016960e..47c1f8ef 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -597,6 +597,7 @@ def calculate(self): self.__rebuild_shifted() if self.has_directional_activators or self.__has_sampling_activators: self.__calculate_g_factors() + self.__clean_native_module_gates() self.calculate_nodes() self.__calculate_native_modules() @@ -604,10 +605,13 @@ def __take_native_module_slot_snapshots(self): for uid, instance in self.native_module_instances.items(): instance.take_slot_activation_snapshot() - def __calculate_native_modules(self): + def __clean_native_module_gates(self): for uid, instance in self.native_module_instances.items(): for gate_type in instance.get_gate_types(): instance.get_gate(gate_type).activation = 0 + + def __calculate_native_modules(self): + for uid, instance in self.native_module_instances.items(): instance.node_function() def __calculate_g_factors(self): From 6e449a78374fd2920c369986780b5bb06d22470e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 11:37:03 +0200 Subject: [PATCH 136/945] change npz data format --- micropsi_core/nodenet/recorder.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index e7545733..581cff48 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -71,20 +71,23 @@ def step(self, step): @abstractmethod def get_values(self): - pass # no cover + pass # pragma: no cover def save(self, filename=None): - values = self.values - if values == {}: - values['uid'] = self.uid # empty files cannot be loaded - np.savez(filename if filename is not None else self.filename, **values) + data = {} + for key in self.values: + data["%s_%s" % (self.name, key)] = self.values[key] + data['%s_meta' % self.name] = [self.first_step, self.interval] + np.savez(filename if filename is not None else self.filename, **data) def load(self, filename=None): data = np.load(filename if filename is not None else self.filename) for key in data: - if key != 'uid': + if key.endswith('_meta'): + self.first_step = data[key][0] + self.interval = data[key][1] + else: self.values[key] = data[key] - self.shapes[key] = data[key].shape def clear(self): self.values = {} From 4e6a5288e2e7c67105445a5d62b2c874e410b267 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 11:42:31 +0200 Subject: [PATCH 137/945] js fix --- micropsi_server/static/js/nodenet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 13920b9a..da83859c 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -489,7 +489,7 @@ function setNodespaceDiffData(data, changed){ links_data = {} for(var uid in data.changes.nodes_dirty){ var nodedata = data.changes.nodes_dirty[uid]; - item = new Node(uid, nodedata['position'][0], nodedata['position'][1], nodedata.parent_nodespace, nodedata.name, nodedata.type, nodedata.sheaves, nodedata.state, nodedata.parameters, nodedata.gate_activations, nodedata.gate_parameters, nodedata.gate_functions, data.nodes[uid].is_highdimensional); + item = new Node(uid, nodedata['position'][0], nodedata['position'][1], nodedata.parent_nodespace, nodedata.name, nodedata.type, nodedata.sheaves, nodedata.state, nodedata.parameters, nodedata.gate_activations, nodedata.gate_parameters, nodedata.gate_functions, nodedata.is_highdimensional); if(uid in nodes){ for (var gateName in nodes[uid].gates) { for (linkUid in nodes[uid].gates[gateName].outgoing) { From dd133a47cb66ac14354263c87b63a1c767c88670 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 12:02:01 +0200 Subject: [PATCH 138/945] VREP world creation fix --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0efa0fc5..f1141163 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -42,7 +42,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.ball_handle = -1 vrep.simxFinish(-1) # just in case, close all opened connections - self.clientID = vrep.simxStart(config['vrep_host'], config['vrep_port'], True, 0, 5000, 5) # Connect to V-REP + self.clientID = vrep.simxStart(config['vrep_host'], int(config['vrep_port']), True, 0, 5000, 5) # Connect to V-REP if self.clientID == -1: self.logger.critical("Could not connect to v-rep") return From d789563a1a412f668e208ae352fa3ce5d2c3920a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 12:02:06 +0200 Subject: [PATCH 139/945] fix multiple run_operation calls --- micropsi_server/static/js/nodenet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index da83859c..df110a9b 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -3038,8 +3038,8 @@ function selectOperation(name){ modal.modal('hide'); runOperation(name, parameters); }; - $('form', modal).on('submit', run); - $('.btn-primary', modal).on('click', run); + $('form', modal).off().on('submit', run); + $('.btn-primary', modal).off().on('click', run); modal.modal('show'); } else { runOperation(name); From 9e7e60e85ed8797dd3b962c66c294c1ba1f5b69a Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 12:13:24 +0200 Subject: [PATCH 140/945] Convenient autonaming of sensors and actuators --- micropsi_core/nodenet/theano_engine/theano_node.py | 8 ++++++++ micropsi_core/nodenet/theano_engine/theano_nodenet.py | 9 +++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index ee029e88..0868d6ef 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -284,6 +284,10 @@ def set_parameter(self, parameter, value): self._nodenet.sensormap[value] = self.uid self._partition.sensor_indices[datasource_index] = sensor_element + + if self.name is None or self.name == "" or self.name == self.uid: + self.name = value + elif self.type == "Actor" and parameter == "datatarget": if value is not None and value != "": datatargets = self._nodenet.get_datatargets() @@ -306,6 +310,10 @@ def set_parameter(self, parameter, value): self._nodenet.actuatormap[value] = self.uid self._partition.actuator_indices[datatarget_index] = actuator_element + + if self.name is None or self.name == "" or self.name == self.uid: + self.name = value + elif self.type == "Activator" and parameter == "type": if value != "sampling": self._nodenet.set_nodespace_gatetype_activator(self.parent_nodespace, value, self.uid) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 1b5a32ae..64cf1614 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -686,8 +686,6 @@ def create_node(self, nodetype, nodespace_uid, position, name=None, uid=None, pa if position is not None: position = (position + [0] * 3)[:3] self.positions[uid] = position - if name is not None and name != "" and name != uid: - self.names[uid] = name if parameters is None: parameters = {} @@ -695,9 +693,16 @@ def create_node(self, nodetype, nodespace_uid, position, name=None, uid=None, pa if nodetype == "Sensor": if 'datasource' in parameters: self.get_node(uid).set_parameter("datasource", parameters['datasource']) + if name is None or name == "" or name == uid: + name = parameters['datasource'] elif nodetype == "Actor": if 'datatarget' in parameters: self.get_node(uid).set_parameter("datatarget", parameters['datatarget']) + if name is None or name == "" or name == uid: + name = parameters['datatarget'] + + if name is not None and name != "" and name != uid: + self.names[uid] = name return uid From 56cec7ef130e822c5b0b4c7bd4b82123a45d4e43 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 12:22:59 +0200 Subject: [PATCH 141/945] change recorder export format, test export --- micropsi_core/_runtime_api_monitors.py | 2 +- micropsi_core/nodenet/recorder.py | 6 +++++- micropsi_core/tests/test_recorders.py | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index 6e8b03b1..9e713df9 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -154,6 +154,6 @@ def export_recorders(nodenet_uid, recorder_uids): stream = BytesIO() for uid in recorder_uids: recorder = nodenet.get_recorder(uid) - data[recorder.name] = recorder.values + data.update(recorder.export_data()) np.savez(stream, **data) return stream.getvalue() diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 581cff48..bbc0a0f9 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -73,11 +73,15 @@ def step(self, step): def get_values(self): pass # pragma: no cover - def save(self, filename=None): + def export_data(self): data = {} for key in self.values: data["%s_%s" % (self.name, key)] = self.values[key] data['%s_meta' % self.name] = [self.first_step, self.interval] + return data + + def save(self, filename=None): + data = self.export_data() np.savez(filename if filename is not None else self.filename, **data) def load(self, filename=None): diff --git a/micropsi_core/tests/test_recorders.py b/micropsi_core/tests/test_recorders.py index d0c730fd..9c1c6809 100644 --- a/micropsi_core/tests/test_recorders.py +++ b/micropsi_core/tests/test_recorders.py @@ -139,3 +139,28 @@ def test_grow_recorder_values(test_nodenet, resourcepath): for i in range(20): micropsi.step_nodenet(test_nodenet) assert len(recorder.values['activations'] == 25) + + +@pytest.mark.engine("theano_engine") +def test_export_recorders(test_nodenet): + from micropsi_core.nodenet.recorder import Recorder + import numpy as np + from io import BytesIO + nodenet = micropsi.nodenets[test_nodenet] + netapi = nodenet.netapi + for i in range(4): + micropsi.step_nodenet(test_nodenet) + nodespace = netapi.get_nodespace(None) + for i in range(5): + netapi.create_node('Register', None, "testnode_%d" % i) + Recorder.initial_size = 5 + recorder = netapi.add_gate_activation_recorder(group_definition={'nodespace_uid': nodespace.uid, 'node_name_prefix': 'testnode'}, interval=2, name="recorder") + micropsi.step_nodenet(test_nodenet) + micropsi.step_nodenet(test_nodenet) + data = micropsi.export_recorders(test_nodenet, [recorder.uid]) + stream = BytesIO(data) + loaded = np.load(stream) + assert 'recorder_activations' in loaded + assert 'recorder_meta' in loaded + assert np.all(loaded['recorder_meta'] == [6, 2]) + assert loaded['recorder_activations'][0][0] == 0 From a1a58c59ec55ae8302e79895fca892b3861189d7 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 14:34:33 +0200 Subject: [PATCH 142/945] do not save empty recorder files --- micropsi_core/nodenet/recorder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index bbc0a0f9..49cb00cc 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -82,7 +82,8 @@ def export_data(self): def save(self, filename=None): data = self.export_data() - np.savez(filename if filename is not None else self.filename, **data) + if data: + np.savez(filename if filename is not None else self.filename, **data) def load(self, filename=None): data = np.load(filename if filename is not None else self.filename) From 0422170d56867c4fbe36521c83df70b7a9604132 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 14:34:48 +0200 Subject: [PATCH 143/945] avoid json export errors by converting all numpy stuff to python beforehand --- micropsi_server/micropsi_app.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 7962abf7..62936488 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -47,6 +47,7 @@ theano_available = True try: import theano + import numpy as np except ImportError: theano_available = False @@ -56,6 +57,21 @@ usermanager = usermanagement.UserManager() +class MicropsiEncoder(json.JSONEncoder): + import math + def default(self, obj): + if theano_available: + if isinstance(obj, np.integer): + return int(obj) + elif isinstance(obj, np.floating): + return float(obj) + elif isinstance(obj, np.ndarray): + return obj.tolist() + elif math.isnan(obj): + return None + return super(MyEncoder, self).default(obj) + + def rpc(command, route_prefix="/rpc/", method="GET", permission_required=None): """Defines a decorator for accessing API calls. Use it by specifying the API method, followed by the permissions necessary to execute the method. @@ -126,7 +142,7 @@ def _wrapper(argument=None): return json.dumps({ 'status': 'success' if state else 'error', 'data': data - }) + }, cls=MicropsiEncoder) except Exception as err: response.status = 500 import traceback From 3b0f2bed4a1e686eb52ef01005f8ba4f04056367 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 15:55:28 +0200 Subject: [PATCH 144/945] Revert "avoid json export errors by converting all numpy stuff to python beforehand" This reverts commit 0422170d56867c4fbe36521c83df70b7a9604132. --- micropsi_server/micropsi_app.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 62936488..7962abf7 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -47,7 +47,6 @@ theano_available = True try: import theano - import numpy as np except ImportError: theano_available = False @@ -57,21 +56,6 @@ usermanager = usermanagement.UserManager() -class MicropsiEncoder(json.JSONEncoder): - import math - def default(self, obj): - if theano_available: - if isinstance(obj, np.integer): - return int(obj) - elif isinstance(obj, np.floating): - return float(obj) - elif isinstance(obj, np.ndarray): - return obj.tolist() - elif math.isnan(obj): - return None - return super(MyEncoder, self).default(obj) - - def rpc(command, route_prefix="/rpc/", method="GET", permission_required=None): """Defines a decorator for accessing API calls. Use it by specifying the API method, followed by the permissions necessary to execute the method. @@ -142,7 +126,7 @@ def _wrapper(argument=None): return json.dumps({ 'status': 'success' if state else 'error', 'data': data - }, cls=MicropsiEncoder) + }) except Exception as err: response.status = 500 import traceback From 0304bb0596c3a2f03bc4c1e1013163fe37e64996 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 15:55:35 +0200 Subject: [PATCH 145/945] ok, cast directly, numpy floats/ints lead to all kind of incompatibilities --- micropsi_core/nodenet/recorder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index 49cb00cc..d52ea64a 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -89,8 +89,8 @@ def load(self, filename=None): data = np.load(filename if filename is not None else self.filename) for key in data: if key.endswith('_meta'): - self.first_step = data[key][0] - self.interval = data[key][1] + self.first_step = int(data[key][0]) + self.interval = int(data[key][1]) else: self.values[key] = data[key] From bea46fceba3b05af7cee8eb09f4f21df47e18694 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 20 May 2016 16:18:11 +0200 Subject: [PATCH 146/945] Raising exception on unknown sortby values --- micropsi_core/nodenet/netapi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index a35c6e52..181f3e50 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -52,7 +52,7 @@ def get_node(self, uid): """ return self.__nodenet.get_node(uid) - def get_nodes(self, nodespace=None, node_name_prefix=None, nodetype=None, sortby='id'): + def get_nodes(self, nodespace=None, node_name_prefix=None, nodetype=None, sortby='ids'): """ Returns a list of nodes in the given nodespace (all Nodespaces if None) whose names start with the given prefix (all if None) @@ -76,6 +76,8 @@ def get_nodes(self, nodespace=None, node_name_prefix=None, nodetype=None, sortby nodes = sorted(nodes, key=lambda node: node.uid) elif sortby == 'names': nodes = sorted(nodes, key=lambda node: node.name) + else: + raise ValueError("Unknown sortby value %s" % sortby) return nodes @@ -454,7 +456,7 @@ def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gate="gen", """ self.__nodenet.group_nodes_by_names(nodespace_uid, node_name_prefix, gatetype=gate, sortby=sortby, group_name=group_name) - def group_nodes_by_ids(self, nodespace_uid, node_uids, group_name, gate="gen", sortby='id'): + def group_nodes_by_ids(self, nodespace_uid, node_uids, group_name, gate="gen", sortby='ids'): """ Will group the given set of nodes. Groups can be used in bulk operations. From b47f65e58da2ffc664eb0fbac48132caf2f2593d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 16:27:58 +0200 Subject: [PATCH 147/945] remove cruft, prevent export if nothing selected --- micropsi_server/static/js/monitor.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 40ac8c32..81fe8866 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -63,6 +63,16 @@ $(function(){ rec_type_dd.trigger('change'); rec_modal.modal('show'); }); + }); + $('#export_recorders').on('submit', function(event){ + var something_selected = false; + $('input[type=checkbox]', this).each(function(idx, el){ + if (el.checked){something_selected = true;} + }) + if (!something_selected){ + dialogs.notification("No recorders selected"); + event.preventDefault(); + } }) $('.btn-primary', rec_modal).on('click', function(event){ var params = { @@ -273,14 +283,6 @@ $(function(){ switch(btn.attr('data-action')){ case 'export': return window.location.replace('/recorder/export/'+currentNodenet+'-'+uid); - case 'export_selected_recorders': - var table = $('#recorder_table'); - var uids = []; - $('input[type=checkbox]', table).each(function(idx, el){ - if(el.checked) uids.push(el.value); - }) - $.post('/recorder/export/'+currentNodenet, {'recorder_uids': uids}); - break; case 'clear': method_name = 'clear_recorder'; break; case 'delete': From 7ab612c750c7c624cb30a60b63eeba5fee8d3249 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 16:59:31 +0200 Subject: [PATCH 148/945] fix vrep get_world_view --- micropsi_core/world/vrep/vrep_world.py | 16 +++++++--------- micropsi_server/static/vrep/vrep.js | 6 ++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index f1141163..9990f1c3 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -102,6 +102,11 @@ def handle_res(self, res): self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) def get_world_view(self, step): + data = { + 'objects': self.get_world_objects(), + 'agents': self.data.get('agents', {}), + 'current_step': self.current_step, + } if self.vision_type == "grayscale": plots = {} for uid in self.agents: @@ -110,15 +115,8 @@ def get_world_view(self, step): bio = BytesIO() image.figure.savefig(bio, format="png") plots[uid] = base64.encodebytes(bio.getvalue()).decode("utf-8") - - return { - 'objects': self.get_world_objects(), - 'agents': self.data.get('agents', {}), - 'current_step': self.current_step, - 'plots': plots - } - else: - return None + data['plots'] = plots + return data def kill_vrep_connection(self, *args): try: diff --git a/micropsi_server/static/vrep/vrep.js b/micropsi_server/static/vrep/vrep.js index fd28d6ba..a08ba720 100644 --- a/micropsi_server/static/vrep/vrep.js +++ b/micropsi_server/static/vrep/vrep.js @@ -18,8 +18,10 @@ $(function(){ var agent_html = ''; for(var uid in data.agents){ agent_html += '

    ' + data.agents[uid].name + ' ('+data.agents[uid].type+')'; - if(uid in data.plots){ - agent_html += '
    '; + if(data.plots){ + if(uid in data.plots){ + agent_html += '
    '; + } } agent_html += '

    ' From 6ac5fb9006c7c985aaab475f1e826ec9bf224e31 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 16:59:42 +0200 Subject: [PATCH 149/945] pass additional data onto parent constructor (for name etc) --- micropsi_core/world/worldadapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 0a050633..41715808 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -134,7 +134,7 @@ class ArrayWorldAdapter(WorldAdapter, metaclass=ABCMeta): Numpy arrays can be passed directly into the engine. """ def __init__(self, world, uid=None, **data): - WorldAdapter.__init__(self, world, uid=uid) + WorldAdapter.__init__(self, world, uid=uid, **data) self.datasource_values = [0] * len(self.get_available_datasources()) self.datatarget_values = [0] * len(self.get_available_datatargets()) self.datatarget_feedback_values = [0] * len(self.get_available_datatargets()) From 7cf7dbf2bf7261843b8ba21880b848fb9dfff5d3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 20 May 2016 18:03:24 +0200 Subject: [PATCH 150/945] add a datasource for collisions --- micropsi_core/world/vrep/vrep_world.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 9990f1c3..4932b158 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -34,9 +34,11 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.robot_name = config['robot_name'] self.vision_type = config['vision_type'] self.control_type = config['control_type'] + self.collision_name = config.get('collision_name', '') self.joints = [] self.vision_resolution = [] + self.collision_handle = None self.iiwa_handle = -1 self.ball_handle = -1 @@ -63,6 +65,12 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.handle_res(res) self.logger.info("Found robot with %d joints" % len(self.joints)) + if self.collision_name: + res, self.collision_handle = vrep.simxGetCollisionHandle(self.clientID, self.collision_name, vrep.simx_opmode_blocking) + self.handle_res(res) + if not self.collision_handle: + self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) + res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) self.handle_res(res) if self.ball_handle < 1: @@ -138,6 +146,9 @@ def get_config_options(): 'description': 'The name of the robot object in V-REP', 'default': 'LBR_iiwa_7_R800', 'options': ["LBR_iiwa_7_R800", "MTB_Robot"]}, + {'name': 'collision_name', + 'default': 'Collision', + 'description': 'The name of the robot\'s collision handle'}, {'name': 'control_type', 'description': 'The type of input sent to the robot', 'default': 'force/torque', @@ -157,6 +168,7 @@ def __init__(self, world, uid=None, **data): self.available_datasources = [] self.available_datasources.append("distance") + self.available_datasources.append("collision") self.available_datatargets.append("restart") self.available_datatargets.append("execute") @@ -181,7 +193,8 @@ def __init__(self, world, uid=None, **data): self.joint_offset = 2 self.distance_offset = 0 - self.joint_angle_offset = 1 + self.collision_offset = 1 + self.joint_angle_offset = 2 self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) if self.world.vision_type == "grayscale": @@ -281,6 +294,10 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals self.world.logger.warning("No data from vrep received") return + if self.world.collision_handle is not None: + res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, vrep.simx_opmode_streaming) + self.datasource_values[self.collision_offset] = collision_state or 0 + for i, joint_handle in enumerate(self.world.joints): target_angle = self.datatarget_values[self.joint_offset + i] angle = 0 From 1b812a87742cf7444ff9ec39a23e462cd0fd91b1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 23 May 2016 15:36:46 +0200 Subject: [PATCH 151/945] remove the world-file if instantiation gone wrong --- micropsi_core/_runtime_api_world.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index 58249f11..966fa90f 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -117,8 +117,9 @@ def new_world(world_name, world_type, owner="", uid=None, config={}): try: kwargs = micropsi_core.runtime.world_data[uid] micropsi_core.runtime.worlds[uid] = get_world_class_from_name(world_type)(**kwargs) - except AttributeError: - return False, "World type unknown" + except Exception as e: + os.remove(filename) + raise e return True, uid From 9e38a733acd83faefefe081b7a4d5a770d6d5b5a Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 23 May 2016 15:42:06 +0200 Subject: [PATCH 152/945] Not creating proxies for non-existing gates --- micropsi_core/nodenet/theano_engine/theano_node.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 0868d6ef..a6bbda3e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -112,6 +112,8 @@ def activation(self, activation): def get_gate(self, type): if type not in self.__gatecache: + if type not in self.get_gate_types(): + return None self.__gatecache[type] = TheanoGate(type, self, self._nodenet, self._partition) return self.__gatecache[type] From b4388428a1a9f8e98788e28a0ba57ae1ceade558 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 23 May 2016 15:43:20 +0200 Subject: [PATCH 153/945] Fixing sortby defaults --- micropsi_core/nodenet/netapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 181f3e50..d6683b6f 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -447,7 +447,7 @@ def copy_nodes(self, nodes, nodespace_uid): mapping[node] = self.get_node(uidmap[node.uid]) return mapping - def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gate="gen", sortby='id', group_name=None): + def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gate="gen", sortby='ids', group_name=None): """ Will group the given set of nodes. Groups can be used in bulk operations. From 384539a66fe034a099d1627f7ee8beead5b7a307 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 23 May 2016 16:27:25 +0200 Subject: [PATCH 154/945] force/torque now does diffs --- micropsi_core/world/vrep/vrep_world.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 97749fa3..e6412451 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -264,6 +264,7 @@ def update_data_sources_and_targets(self): for i, joint_handle in enumerate(self.world.joints): tval = self.current_angle_target_values[i] * math.pi if self.world.control_type == "force/torque": + tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "angles": vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) From 3cec8ee6eb3c3c6baf55aa2af1322f1842892073 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 23 May 2016 17:14:17 +0200 Subject: [PATCH 155/945] =?UTF-8?q?Introducing=20ball=20game=20type=20?= =?UTF-8?q?=E2=80=9Ereach=E2=80=9C=20with=20no=20random=20initial=20positi?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- micropsi_core/world/vrep/vrep_world.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0043ee19..eedd8381 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -158,7 +158,7 @@ def get_config_options(): {'name': 'ballgame_type', 'description': 'Type of ball game to be played', 'default': 'none', - 'options': ["none", "reach-fixed", "reach-randomized"]} + 'options': ["none", "reach", "reach-fixed", "reach-randomized"]} ] @@ -236,13 +236,14 @@ def update_data_sources_and_targets(self): time.sleep(1) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.world.clientID, True) - for i, joint_handle in enumerate(self.world.joints): - self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) - self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] - tval = self.current_angle_target_values[i] * math.pi - vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.world.clientID, False) + if self.world.ballgame_type != "reach": + vrep.simxPauseCommunication(self.world.clientID, True) + for i, joint_handle in enumerate(self.world.joints): + self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) + self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] + tval = self.current_angle_target_values[i] * math.pi + vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxPauseCommunication(self.world.clientID, False) if self.world.ballgame_type == "reach-randomized": max_dist = 0.8 From 920194559bef5fa25df0215f1c93d4d5707451f6 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 23 May 2016 17:52:40 +0200 Subject: [PATCH 156/945] don't pull monitor data if collapsed refresh content if expanding while paused --- micropsi_server/static/js/dialogs.js | 1 + micropsi_server/static/js/monitor.js | 14 +++++++++++++- micropsi_server/static/js/nodenet.js | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index 576951f0..a8d086cc 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -711,6 +711,7 @@ fetch_stepping_info = function(){ } var end = new Date().getTime(); + calculationRunning = data.calculation_running; if(data.calculation_running && !busy){ if(runner_properties.timestep - (end - start) > 0){ window.setTimeout(fetch_stepping_info, runner_properties.timestep - (end - start)); diff --git a/micropsi_server/static/js/monitor.js b/micropsi_server/static/js/monitor.js index 4700a2d9..ee2b53d6 100644 --- a/micropsi_server/static/js/monitor.js +++ b/micropsi_server/static/js/monitor.js @@ -144,7 +144,19 @@ $(function(){ setLoggingData(data); } - register_stepping_function('monitors', getPollParams, setData); + if($('#monitor').height() > 0){ + register_stepping_function('monitors', getPollParams, setData); + } + $('#monitor').on('shown', function(){ + register_stepping_function('monitors', getPollParams, setData); + if(!calculationRunning){ + $(document).trigger('runner_stepped'); + } + }); + $('#monitor').on('hidden', function(){ + unregister_stepping_function('monitors'); + }); + function refreshMonitors(newNodenet){ params = getPollParams(); diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index df110a9b..9ab4b909 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -609,6 +609,9 @@ if($('#nodenet_editor').height() > 0){ } $('#nodenet_editor').on('shown', function(){ register_stepping_function('nodenet_diff', get_nodenet_diff_params, setNodespaceDiffData); + if(!calculationRunning){ + $(document).trigger('runner_stepped'); + } }); $('#nodenet_editor').on('hidden', function(){ unregister_stepping_function('nodenet_diff'); From caa084583b207fe9180e8c9ed06d53167d5d632e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 24 May 2016 17:19:07 +0200 Subject: [PATCH 157/945] this is only an example, and shouldn't really be polling stuff --- micropsi_server/static/js/world.js | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/micropsi_server/static/js/world.js b/micropsi_server/static/js/world.js index 7822e6db..4100a40e 100644 --- a/micropsi_server/static/js/world.js +++ b/micropsi_server/static/js/world.js @@ -8,23 +8,23 @@ $(function(){ registerResizeHandler(); - function get_world_data(){ - return {step: currentWorldSimulationStep}; - } - - function set_world_data(data){ - if(!jQuery.isEmptyObject(data)){ - currentWorldSimulationStep = data.current_step; - } - } - - register_stepping_function('world', get_world_data, set_world_data); - - function updateViewSize() { - if(typeof view != 'undefined'){ - view.draw(true); - } - } + // function get_world_data(){ + // return {step: currentWorldSimulationStep}; + // } + + // function set_world_data(data){ + // if(!jQuery.isEmptyObject(data)){ + // currentWorldSimulationStep = data.current_step; + // } + // } + + // register_stepping_function('world', get_world_data, set_world_data); + + // function updateViewSize() { + // if(typeof view != 'undefined'){ + // view.draw(true); + // } + // } function registerResizeHandler(){ // resize handler for nodenet viewer: From 88aa998cbc3e859608a3784863addaa6cbcccf4c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 24 May 2016 18:07:37 +0200 Subject: [PATCH 158/945] fix collision detection --- micropsi_core/world/vrep/vrep_world.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 4932b158..f6eb623b 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -68,7 +68,9 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N if self.collision_name: res, self.collision_handle = vrep.simxGetCollisionHandle(self.clientID, self.collision_name, vrep.simx_opmode_blocking) self.handle_res(res) - if not self.collision_handle: + if self.collision_handle > 0: + res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, vrep.simx_opmode_streaming) + else: self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) @@ -294,8 +296,8 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals self.world.logger.warning("No data from vrep received") return - if self.world.collision_handle is not None: - res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, vrep.simx_opmode_streaming) + if self.world.collision_handle > 0: + res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, vrep.simx_opmode_buffer) self.datasource_values[self.collision_offset] = collision_state or 0 for i, joint_handle in enumerate(self.world.joints): From d6fba670f5cb89efd07543311b8462463913b1ad Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 24 May 2016 18:19:06 +0200 Subject: [PATCH 159/945] fix instantiation --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index f6eb623b..8853d8b8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -38,7 +38,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.joints = [] self.vision_resolution = [] - self.collision_handle = None + self.collision_handle = -1 self.iiwa_handle = -1 self.ball_handle = -1 From 688980dcdba4a39d01d6a2dbf6df0ce46e28b70c Mon Sep 17 00:00:00 2001 From: policecar Date: Tue, 24 May 2016 19:51:01 +0200 Subject: [PATCH 160/945] Offset in vrep_world must be +2 after all. --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 35a3f65e..bf3b7b56 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -211,7 +211,7 @@ def __init__(self, world, uid=None, **data): self.distance_offset = 0 self.collision_offset = 1 self.position_offset = 2 - self.joint_angle_offset = self.position_offset + 1 + self.joint_angle_offset = self.position_offset + 2 # because ball_x, ball_y self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) if self.world.vision_type == "grayscale": From 777aedf1a9dbd9a32b6b66f7fa761fc26a24a5e4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 24 May 2016 23:04:58 +0200 Subject: [PATCH 161/945] fix order of datasources --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index bf3b7b56..638fdd4a 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -181,8 +181,8 @@ def __init__(self, world, uid=None, **data): self.available_datatargets = [] self.available_datasources = [] - self.available_datasources.append("collision") self.available_datasources.append("ball-distance") + self.available_datasources.append("collision") self.available_datasources.append("ball-x") self.available_datasources.append("ball-y") From d83eaf4bf1b1a5b2bf564d43b3791c2726b1abe4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 25 May 2016 11:23:35 +0200 Subject: [PATCH 162/945] readCollision returns a boolean --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 638fdd4a..0c5e0cfc 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -328,7 +328,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals if self.world.collision_handle > 0: res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, vrep.simx_opmode_buffer) - self.datasource_values[self.collision_offset] = collision_state or 0 + self.datasource_values[self.collision_offset] = 1 if collision_state else 0 for i, joint_handle in enumerate(self.world.joints): target_angle = self.datatarget_values[self.joint_offset + i] From 48efbaa9aaa7e21c1f5fb1cfeba7b8201f7f867b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 25 May 2016 16:41:24 +0200 Subject: [PATCH 163/945] fix grouping slots/gates --- micropsi_core/nodenet/theano_engine/theano_partition.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 47c1f8ef..f568be46 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1742,6 +1742,8 @@ def group_highdimensional_elements(self, node_uid, gate=None, slot=None, group_n node_id = node_from_id(node_uid) nodespace_id = self.allocated_node_parents[node_id] nodespace_uid = nodespace_to_id(nodespace_id, self.pid) + if nodespace_uid not in self.nodegroups: + self.nodegroups[nodespace_uid] = {} strnodetype = get_string_node_type(self.allocated_nodes[node_id], self.nodenet.native_modules) nodetype = self.nodenet.get_nodetype(strnodetype) if gate: From ef5476f57ed9d701dfdbb39cd8b04eb356bd5163 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 25 May 2016 18:24:23 +0200 Subject: [PATCH 164/945] use the server's generated name for newly created sensors and actors --- micropsi_server/static/js/nodenet.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 9ab4b909..2d3019a9 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -998,6 +998,7 @@ function nodeRedrawNeeded(node){ if(node.uid in nodeLayer.children){ if(node.x == nodes[node.uid].x && node.y == nodes[node.uid].y && + node.name == nodes[node.uid].name && node.sheaves[currentSheaf].activation == nodes[node.uid].sheaves[currentSheaf].activation && node.gatechecksum() == nodes[node.uid].gatechecksum() && Object.keys(node.sheaves).length == Object.keys(nodes[node.uid].sheaves).length && @@ -3742,12 +3743,14 @@ function handleSelectDatasourceModal(event){ var nodeUid = clickOriginUid; var value = $('#select_datasource_modal select').val(); $("#select_datasource_modal").modal("hide"); - nodes[clickOriginUid].parameters['datasource'] = value; + nodes[nodeUid].parameters['datasource'] = value; showNodeForm(nodeUid); api.call("bind_datasource_to_sensor", { nodenet_uid: currentNodenet, sensor_uid: nodeUid, datasource: value + }, function(data){ + showNodeForm(nodeUid, true); }); } @@ -3755,12 +3758,14 @@ function handleSelectDatatargetModal(event){ var nodeUid = clickOriginUid; var value = $('#select_datatarget_modal select').val(); $("#select_datatarget_modal").modal("hide"); - nodes[clickOriginUid].parameters['datatarget'] = value; + nodes[nodeUid].parameters['datatarget'] = value; showNodeForm(nodeUid); api.call("bind_datatarget_to_actor", { nodenet_uid: currentNodenet, actor_uid: nodeUid, datatarget: value + }, function(data){ + showNodeForm(nodeUid, true); }); } @@ -3979,6 +3984,7 @@ function showNodeForm(nodeUid, refresh){ node_uid: nodeUid }, function(data){ item = new Node(nodeUid, data['position'][0], data['position'][1], data.parent_nodespace, data.name, data.type, data.sheaves, data.state, data.parameters, data.gate_activations, data.gate_parameters, data.gate_functions); + redrawNode(item); nodes[nodeUid].update(item); if(clickType == 'gate'){ showGateForm(nodes[nodeUid], nodes[nodeUid].gates[nodes[nodeUid].gateIndexes[clickIndex]]); From bf8784a7579011c7eab1010b7b9a5fa7e127a896 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 26 May 2016 14:41:55 +0200 Subject: [PATCH 165/945] fix sortby defaults --- micropsi_core/nodenet/netapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index d6683b6f..fc666ebf 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -447,7 +447,7 @@ def copy_nodes(self, nodes, nodespace_uid): mapping[node] = self.get_node(uidmap[node.uid]) return mapping - def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gate="gen", sortby='ids', group_name=None): + def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gate="gen", sortby='id', group_name=None): """ Will group the given set of nodes. Groups can be used in bulk operations. @@ -456,7 +456,7 @@ def group_nodes_by_names(self, nodespace_uid, node_name_prefix=None, gate="gen", """ self.__nodenet.group_nodes_by_names(nodespace_uid, node_name_prefix, gatetype=gate, sortby=sortby, group_name=group_name) - def group_nodes_by_ids(self, nodespace_uid, node_uids, group_name, gate="gen", sortby='ids'): + def group_nodes_by_ids(self, nodespace_uid, node_uids, group_name, gate="gen", sortby='id'): """ Will group the given set of nodes. Groups can be used in bulk operations. From 912f4e9278d32ddd7273f81a1746811899f6965b Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 27 May 2016 12:02:30 +0200 Subject: [PATCH 166/945] Fixing sensor/actuator deletion bug --- micropsi_core/nodenet/theano_engine/theano_partition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index f568be46..212ce691 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -237,8 +237,8 @@ def __init__(self, nodenet, pid, sparse=True, initial_number_of_nodes=2000, aver self.allocated_elements_to_activators = np.zeros(self.NoE, dtype=np.int32) - self.sensor_indices = np.zeros(0, dtype=np.int32) # index := datasource, value:=node_id - self.actuator_indices = np.zeros(0, dtype=np.int32) # index := datatarget, value:=node_id + self.sensor_indices = np.zeros(0, dtype=np.int32) # index := datasource, value:=element index + self.actuator_indices = np.zeros(0, dtype=np.int32) # index := datatarget, value:=element index self.inlinks = {} @@ -1443,11 +1443,11 @@ def delete_node(self, node_id): self.allocated_elements_to_nodes[np.where(self.allocated_elements_to_nodes == node_id)[0]] = 0 if type == SENSOR: - sensor_index = np.where(self.sensor_indices == node_id)[0] + sensor_index = np.where(self.sensor_indices == offset)[0] self.sensor_indices[sensor_index] = 0 if type == ACTUATOR: - actuator_index = np.where(self.actuator_indices == node_id)[0] + actuator_index = np.where(self.actuator_indices == offset)[0] self.actuator_indices[actuator_index] = 0 if type == PIPE: From 14ed08f5e45aec5949ea225d0e0a909ad41c38f2 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 27 May 2016 14:14:33 +0200 Subject: [PATCH 167/945] vrep connection stability experiments --- micropsi_core/world/vrep/vrep_world.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index eedd8381..c8dd547b 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -46,7 +46,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.robot_position = [] vrep.simxFinish(-1) # just in case, close all opened connections - self.clientID = vrep.simxStart(config['vrep_host'], int(config['vrep_port']), True, 0, 5000, 5) # Connect to V-REP + self.clientID = vrep.simxStart(config['vrep_host'], int(config['vrep_port']), True, False, 5000, 4) # Connect to V-REP if self.clientID == -1: self.logger.critical("Could not connect to v-rep") return @@ -307,9 +307,19 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals self.datasource_values[self.position_offset + 1] = relative_pos[1] res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) + self.world.handle_res(res) if len(data) == 0: - self.world.logger.warning("No data from vrep received") + self.world.logger.warning("No data from vrep received. Sleeping for 5secs, the retrying.") + time.sleep(1) + + res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, + vrep.sim_object_joint_type, 15, + vrep.simx_opmode_blocking) + self.world.handle_res(res) + + if len(data) == 0: + self.world.logger.error("No data from vrep received on retry. Giving up and returning no data.") return for i, joint_handle in enumerate(self.world.joints): From c748e42009c71649b68594805189df3d048e5d26 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 27 May 2016 18:14:14 +0200 Subject: [PATCH 168/945] Fixing broken fat module slot snapshotting --- micropsi_core/nodenet/theano_engine/theano_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index a6bbda3e..51334f02 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -192,7 +192,7 @@ def take_slot_activation_snapshot(self): if self.is_highdimensional: start = self._partition.allocated_node_offsets[self._id] end = start + len(self._nodetype.slottypes) - self.slot_fat_snapshot = a_array[start:end] + self.slot_fat_snapshot = np.array(a_array[start:end]) def get_slot(self, type): if type not in self.__slotcache: From b9c634ddb2da9b2772358e32c4dee3f1602acb50 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 28 May 2016 15:49:45 +0200 Subject: [PATCH 169/945] add a connection daemon that handles the vrep connection daemon will ping regularly, and attempt reconnects if the connection is lost --- micropsi_core/world/vrep/vrep_world.py | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0c5e0cfc..e2f2e0fa 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -6,6 +6,7 @@ matplotlib.use('agg') import matplotlib.pyplot as plt +import threading from io import BytesIO import base64 import random @@ -16,6 +17,64 @@ from micropsi_core.world.worldadapter import ArrayWorldAdapter +class VREPConnection(threading.Thread): + wait = 2 + current_try = 0 + ping_interval = 1 + + def __init__(self, host, port, connection_listeners=[]): + threading.Thread.__init__(self) + self.host = host + self.port = port + self.clientID = -1 + self.daemon = True + self.paused = False + self.is_connected = False + self.logger = logging.getLogger("world") + self.state = threading.Condition() + self.is_active = True + self.connection_listeners = connection_listeners + self.start() + + def run(self): + self.reconnect() + while self.is_active: + with self.state: + if self.paused: + self.state.wait() + res, pingtime = vrep.simxGetPingTime(self.clientID) + if res != vrep.simx_return_ok: + self.reconnect() + time.sleep(self.ping_interval) + vrep.simxFinish(-1) + + def reconnect(self): + self.is_connected = False + self.current_try = 0 + self.clientID = -1 + vrep.simxFinish(-1) # just in case, close all opened connections + while self.clientID < 0 and self.is_active: + self.clientID = vrep.simxStart(self.host, self.port, True, 0, 5000, 5) # Connect to V-REP + if self.clientID == -1: + self.logger.error("Could not connect to v-rep, trying again in %d seconds", self.current_try * self.wait) + time.sleep(self.current_try * self.wait) + self.current_try += 1 + else: + self.is_connected = True + self.logger.info("Connected to local V-REP at port %d" % self.port) + for item in self.connection_listeners: + item.on_vrep_connect() + + def resume(self): + with self.state: + self.paused = False + self.state.notify() + + def pause(self): + with self.state: + self.paused = True + + class VREPWorld(World): """ A vrep robot simulator environment In V-REP, the following setup has to be performed: From 8dfdc53fba7ac892f9e0be6912ccf82fe09a897e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 28 May 2016 15:50:57 +0200 Subject: [PATCH 170/945] worldadapter has to now the nodenet since its datasources might change if the vrep connection state changes from disconnected to connected --- micropsi_core/nodenet/nodenet.py | 2 ++ micropsi_core/nodenet/theano_engine/theano_nodenet.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 3a2d994e..962b0b15 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -136,6 +136,7 @@ def worldadapter_instance(self, _worldadapter_instance): """ Connects the node net to the given world adapter uid, or disconnects if None is given """ + self._worldadapter_instance.nodenet = self self._worldadapter_instance = _worldadapter_instance def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None): @@ -147,6 +148,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self._world_uid = world self._worldadapter_uid = worldadapter if world else None self._worldadapter_instance = worldadapter_instance + self._worldadapter_instance.nodenet = self self.is_active = False self.use_modulators = use_modulators diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 64cf1614..764cac26 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -173,6 +173,7 @@ def worldadapter_instance(self): @worldadapter_instance.setter def worldadapter_instance(self, _worldadapter_instance): self._worldadapter_instance = _worldadapter_instance + self._worldadapter_instance.nodenet = self self._rebuild_sensor_actor_indices() @property @@ -197,7 +198,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.nodetypes = {} for type, data in STANDARD_NODETYPES.items(): - self.nodetypes[type] = Nodetype(nodenet=self, **data) + self.nodetypes[type] = Nodetype(nodenet=self, **data) precision = settings['theano']['precision'] if precision == "32": From f5c421958e5293c3e01a812e24f58cb8286c5f3b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 28 May 2016 15:53:02 +0200 Subject: [PATCH 171/945] move all the vrep communication into the worldadapter --- micropsi_core/world/vrep/vrep_world.py | 262 +++++++++++++------------ 1 file changed, 140 insertions(+), 122 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index e2f2e0fa..c2fabf5e 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -97,88 +97,11 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.collision_name = config.get('collision_name', '') self.ballgame_type = config['ballgame_type'] - self.joints = [] - self.vision_resolution = [] - self.collision_handle = -1 - - self.robot_handle = -1 - self.ball_handle = -1 - - self.robot_position = [] - - vrep.simxFinish(-1) # just in case, close all opened connections - self.clientID = vrep.simxStart(config['vrep_host'], int(config['vrep_port']), True, 0, 5000, 5) # Connect to V-REP - if self.clientID == -1: - self.logger.critical("Could not connect to v-rep") - return - - self.logger.info("Connected to local V-REP at port 19999") - - res, pingtime = vrep.simxGetPingTime(self.clientID) - self.handle_res(res) - - self.logger.info('Ping time to v-rep: %dms' % pingtime) - - res, self.robot_handle = vrep.simxGetObjectHandle(self.clientID, self.robot_name, vrep.simx_opmode_blocking) - self.handle_res(res) - if self.robot_handle < 1: - self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.robot_name) - - res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) - self.handle_res(res) - self.logger.info("Found robot with %d joints" % len(self.joints)) - - if self.collision_name: - res, self.collision_handle = vrep.simxGetCollisionHandle(self.clientID, self.collision_name, vrep.simx_opmode_blocking) - self.handle_res(res) - if self.collision_handle > 0: - res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, vrep.simx_opmode_streaming) - else: - self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) - - if self.ballgame_type != "none": - res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) - self.handle_res(res) - if self.ball_handle < 1: - self.logger.warn("Could not get handle for Ball object, distance values will not be available.") - else: - res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, robot_position = vrep.simxGetObjectPosition(self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking) - if res != 0 and res != 1: - self.handle_res(res) - self.robot_position = robot_position - - if self.vision_type == "grayscale": - res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) - self.handle_res(res) - if self.observer_handle < 1: - self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") - else: - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) - if res != 0 and res != 1: - self.handle_res(res) - else: - time.sleep(1) - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) - self.vision_resolution = resolution - if len(resolution) != 2: - self.logger.error("Could not determine vision resolution after 1 second wait time.") - else: - self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) + self.connection_daemon = VREPConnection(config['vrep_host'], int(config['vrep_port']), connection_listeners=[self]) from micropsi_core.runtime import add_signal_handler add_signal_handler(self.kill_vrep_connection) - def handle_res(self, res): - if res != vrep.simx_return_ok: - error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) - self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) - def get_world_view(self, step): data = { 'objects': self.get_world_objects(), @@ -197,10 +120,15 @@ def get_world_view(self, step): return data def kill_vrep_connection(self, *args): - try: - vrep.simxFinish(-1) - except: - pass + if hasattr(self, "connection_daemon"): + self.connection_daemon.is_active = False + if self.connection_daemon: + self.connection_daemon.join() + + def on_vrep_connect(self): + """ is called by the connection_daemon, if a connection was established """ + for uid in self.agents: + self.agents[uid].on_vrep_connect() def __del__(self): self.kill_vrep_connection() @@ -237,6 +165,85 @@ def get_config_options(): class Robot(ArrayWorldAdapter): def __init__(self, world, uid=None, **data): + super().__init__(world, uid, **data) + self.available_datatargets = [] + self.available_datasources = [] + self.block = True + self.joints = [] + self.vision_resolution = [] + self.collision_handle = -1 + + self.robot_handle = -1 + self.ball_handle = -1 + + self.robot_position = [] + + self.get_vrep_data() + + def handle_res(self, res): + if res != vrep.simx_return_ok: + error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) + self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) + + def on_vrep_connect(self): + """ is called by the world, if a connection was established """ + self.get_vrep_data() + + def get_vrep_data(self): + + self.clientID = self.world.connection_daemon.clientID + + res, self.robot_handle = vrep.simxGetObjectHandle(self.clientID, self.world.robot_name, vrep.simx_opmode_blocking) + self.handle_res(res) + if self.robot_handle < 1: + self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.world.robot_name) + + res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) + self.handle_res(res) + self.logger.info("Found robot with %d joints" % len(self.joints)) + + if self.world.collision_name: + res, self.collision_handle = vrep.simxGetCollisionHandle(self.clientID, self.world.collision_name, vrep.simx_opmode_blocking) + self.handle_res(res) + if self.collision_handle > 0: + res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, vrep.simx_opmode_streaming) + else: + self.logger.warning("Collision handle %s not found, not tracking collisions" % self.world.collision_name) + + if self.world.ballgame_type != "none": + res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.ball_handle < 1: + self.logger.warn("Could not get handle for Ball object, distance values will not be available.") + else: + res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, robot_position = vrep.simxGetObjectPosition(self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking) + if res != 0 and res != 1: + self.handle_res(res) + self.robot_position = robot_position + + if self.world.vision_type == "grayscale": + res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.observer_handle < 1: + self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") + else: + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) + if res != 0 and res != 1: + self.handle_res(res) + else: + time.sleep(1) + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) + self.vision_resolution = resolution + if len(resolution) != 2: + self.logger.error("Could not determine vision resolution after 1 second wait time.") + else: + self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) self.available_datatargets = [] self.available_datasources = [] @@ -248,20 +255,18 @@ def __init__(self, world, uid=None, **data): self.available_datatargets.append("restart") self.available_datatargets.append("execute") - for i in range(len(world.joints)): + for i in range(len(self.joints)): self.available_datatargets.append("joint_%s" % str(i + 1)) - for i in range(len(world.joints)): + for i in range(len(self.joints)): self.available_datasources.append("joint_angle_%s" % str(i + 1)) - for i in range(len(world.joints)): + for i in range(len(self.joints)): self.available_datasources.append("joint_force_%s" % str(i + 1)) - super().__init__(world, uid, **data) - self.last_restart = 0 - self.current_angle_target_values = np.zeros_like(self.world.joints) + self.current_angle_target_values = np.zeros_like(self.joints) self.restart_offset = 0 self.execute_offset = 1 @@ -271,21 +276,26 @@ def __init__(self, world, uid=None, **data): self.collision_offset = 1 self.position_offset = 2 self.joint_angle_offset = self.position_offset + 2 # because ball_x, ball_y - self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) + self.joint_force_offset = self.joint_angle_offset + len(self.joints) if self.world.vision_type == "grayscale": - self.image_offset = self.joint_force_offset + len(self.world.joints) - self.image_length = self.world.vision_resolution[0] * self.world.vision_resolution[1] + self.image_offset = self.joint_force_offset + len(self.joints) + self.image_length = self.vision_resolution[0] * self.vision_resolution[1] - for y in range(self.world.vision_resolution[1]): - for x in range(self.world.vision_resolution[0]): + for y in range(self.vision_resolution[1]): + for x in range(self.vision_resolution[0]): self.available_datasources.append("px_%d_%d" % (x, y)) - self.image = plt.imshow(np.zeros(shape=(self.world.vision_resolution[0], self.world.vision_resolution[1])), cmap="bone") + self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") self.image.norm.vmin = 0 self.image.norm.vmax = 1 - self.fetch_sensor_and_feedback_values_from_simulation() + if self.nodenet: + self.nodenet.worldadapter_instance = self + self.datasource_values = [0] * len(self.available_datasources) + self.datatarget_values = [0] * len(self.available_datatargets) + self.datatarget_feedback_values = [0] * len(self.available_datatargets) + self.fetch_sensor_and_feedback_values_from_simulation(initial=True) def get_available_datasources(self): return self.available_datasources @@ -305,25 +315,25 @@ def update_data_sources_and_targets(self): # simulation restart if restart: - vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + vrep.simxStopSimulation(self.clientID, vrep.simx_opmode_oneshot) time.sleep(1) - vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + vrep.simxStartSimulation(self.clientID, vrep.simx_opmode_oneshot) if self.world.ballgame_type != "reach": - vrep.simxPauseCommunication(self.world.clientID, True) - for i, joint_handle in enumerate(self.world.joints): + vrep.simxPauseCommunication(self.clientID, True) + for i, joint_handle in enumerate(self.joints): self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] tval = self.current_angle_target_values[i] * math.pi - vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.world.clientID, False) + vrep.simxSetJointPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxPauseCommunication(self.clientID, False) if self.world.ballgame_type == "reach-randomized": max_dist = 0.8 rx = random.uniform(-max_dist, max_dist) max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) ry = random.uniform(-max_y, max_y) - vrep.simxSetObjectPosition(self.world.clientID, self.world.ball_handle, self.world.robot_handle, [rx, ry], vrep.simx_opmode_blocking) + vrep.simxSetObjectPosition(self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking) self.fetch_sensor_and_feedback_values_from_simulation() self.last_restart = self.world.current_step @@ -331,19 +341,19 @@ def update_data_sources_and_targets(self): # execute movement, send new target angles if execute: - self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.world.joints)]) - vrep.simxPauseCommunication(self.world.clientID, True) - for i, joint_handle in enumerate(self.world.joints): + self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.joints)]) + vrep.simxPauseCommunication(self.clientID, True) + for i, joint_handle in enumerate(self.joints): tval = self.current_angle_target_values[i] * math.pi if self.world.control_type == "force/torque": tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi - vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxSetJointTargetPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "angles": - vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxSetJointPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "movements": tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi - vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.world.clientID, False) + vrep.simxSetJointPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + vrep.simxPauseCommunication(self.clientID, False) # read joint angle and force values self.fetch_sensor_and_feedback_values_from_simulation(True) @@ -353,43 +363,51 @@ def update_data_sources_and_targets(self): if self.world.vision_type != "grayscale": return - res, resolution, image = vrep.simxGetVisionSensorImage(self.world.clientID, self.world.observer_handle, 0, vrep.simx_opmode_buffer) - rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.world.vision_resolution[0] * self.world.vision_resolution[1], 3)).astype(np.float32) + res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.world.vision_resolution[0], self.world.vision_resolution[1]))[::-1,:] # todo: npyify and make faster + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() self.image.set_data(y_image) return self.image - def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=False): + def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=False, initial=False): # get data and feedback # read distance value - if self.world.ballgame_type != "none" and self.world.ball_handle > 0: - res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) - res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) + if not self.world.connection_daemon.is_connected: + if self.block and not initial: + while not self.world.connection_daemon.is_connected: + time.sleep(0.5) + else: + return + if self.world.connection_daemon.clientID != self.clientID: + self.get_vrep_data() + if self.world.ballgame_type != "none" and self.ball_handle > 0: + res, ball_pos = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer) + res, joint_pos = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) relative_pos = [0,0] - relative_pos[0] = ball_pos[0] - self.world.robot_position[0] - relative_pos[1] = ball_pos[1] - self.world.robot_position[1] + relative_pos[0] = ball_pos[0] - self.robot_position[0] + relative_pos[1] = ball_pos[1] - self.robot_position[1] dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) self.datasource_values[self.distance_offset] = dist self.datasource_values[self.position_offset + 0] = relative_pos[0] self.datasource_values[self.position_offset + 1] = relative_pos[1] - res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) + res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) if len(data) == 0: self.world.logger.warning("No data from vrep received") return - if self.world.collision_handle > 0: - res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, vrep.simx_opmode_buffer) + if self.collision_handle > 0: + res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, vrep.simx_opmode_buffer) self.datasource_values[self.collision_offset] = 1 if collision_state else 0 - for i, joint_handle in enumerate(self.world.joints): + for i, joint_handle in enumerate(self.joints): target_angle = self.datatarget_values[self.joint_offset + i] angle = 0 force = 0 From 125eb19305aaf73b20862018e373bdb641fff2fc Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 28 May 2016 15:53:32 +0200 Subject: [PATCH 172/945] give the agent an agent logger --- micropsi_core/world/worldadapter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 41715808..f5bac472 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -19,6 +19,7 @@ __author__ = 'joscha' __date__ = '10.05.12' +import logging from threading import Lock from micropsi_core.world.worldobject import WorldObject from abc import ABCMeta, abstractmethod @@ -36,7 +37,9 @@ def __init__(self, world, uid=None, **data): self.datatargets = {} self.datatarget_feedback = {} self.datasource_lock = Lock() + self.nodenet = None # will be assigned by the nodenet once it's loaded WorldObject.__init__(self, world, category='agents', uid=uid, **data) + self.logger = logging.getLogger('agent.%s' % self.uid) if data.get('name'): self.data['name'] = data['name'] From 0a6dc573146450b1879ade91085efa0dc15bc6dd Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 28 May 2016 15:55:59 +0200 Subject: [PATCH 173/945] cleanup --- micropsi_core/world/vrep/vrep_world.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index c2fabf5e..a8e386e8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -119,17 +119,17 @@ def get_world_view(self, step): data['plots'] = plots return data + def on_vrep_connect(self): + """ is called by the connection_daemon, if a connection was established """ + for uid in self.agents: + self.agents[uid].on_vrep_connect() + def kill_vrep_connection(self, *args): if hasattr(self, "connection_daemon"): self.connection_daemon.is_active = False if self.connection_daemon: self.connection_daemon.join() - def on_vrep_connect(self): - """ is called by the connection_daemon, if a connection was established """ - for uid in self.agents: - self.agents[uid].on_vrep_connect() - def __del__(self): self.kill_vrep_connection() @@ -165,9 +165,9 @@ def get_config_options(): class Robot(ArrayWorldAdapter): def __init__(self, world, uid=None, **data): - super().__init__(world, uid, **data) self.available_datatargets = [] self.available_datasources = [] + super().__init__(world, uid, **data) self.block = True self.joints = [] self.vision_resolution = [] From 4bf7ecee17806b9fff66e2d9f4d1b8e7c18bc64f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 15:14:58 +0200 Subject: [PATCH 174/945] do not set if worldadapter is None --- micropsi_core/nodenet/nodenet.py | 6 ++++-- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 962b0b15..dd9147e7 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -136,8 +136,9 @@ def worldadapter_instance(self, _worldadapter_instance): """ Connects the node net to the given world adapter uid, or disconnects if None is given """ - self._worldadapter_instance.nodenet = self self._worldadapter_instance = _worldadapter_instance + if self._worldadapter_instance: + self._worldadapter_instance.nodenet = self def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None): """ @@ -148,7 +149,8 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self._world_uid = world self._worldadapter_uid = worldadapter if world else None self._worldadapter_instance = worldadapter_instance - self._worldadapter_instance.nodenet = self + if self._worldadapter_instance: + self._worldadapter_instance.nodenet = self self.is_active = False self.use_modulators = use_modulators diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 764cac26..90cc16e4 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -173,7 +173,8 @@ def worldadapter_instance(self): @worldadapter_instance.setter def worldadapter_instance(self, _worldadapter_instance): self._worldadapter_instance = _worldadapter_instance - self._worldadapter_instance.nodenet = self + if self._worldadapter_instance: + self._worldadapter_instance.nodenet = self self._rebuild_sensor_actor_indices() @property From 19bd6b2d6afe996a05862c1cf61ac31acabcc198 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 15:15:18 +0200 Subject: [PATCH 175/945] faster kill of connection thread on ctrl+c --- micropsi_core/world/vrep/vrep_world.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index a8e386e8..da248b84 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -28,7 +28,7 @@ def __init__(self, host, port, connection_listeners=[]): self.port = port self.clientID = -1 self.daemon = True - self.paused = False + self.stop = threading.Event() self.is_connected = False self.logger = logging.getLogger("world") self.state = threading.Condition() @@ -39,9 +39,6 @@ def __init__(self, host, port, connection_listeners=[]): def run(self): self.reconnect() while self.is_active: - with self.state: - if self.paused: - self.state.wait() res, pingtime = vrep.simxGetPingTime(self.clientID) if res != vrep.simx_return_ok: self.reconnect() @@ -57,7 +54,7 @@ def reconnect(self): self.clientID = vrep.simxStart(self.host, self.port, True, 0, 5000, 5) # Connect to V-REP if self.clientID == -1: self.logger.error("Could not connect to v-rep, trying again in %d seconds", self.current_try * self.wait) - time.sleep(self.current_try * self.wait) + self.stop.wait(self.current_try * self.wait) self.current_try += 1 else: self.is_connected = True @@ -65,15 +62,8 @@ def reconnect(self): for item in self.connection_listeners: item.on_vrep_connect() - def resume(self): - with self.state: - self.paused = False - self.state.notify() - - def pause(self): - with self.state: - self.paused = True - + def terminate(self): + self.stop.set() class VREPWorld(World): """ A vrep robot simulator environment @@ -128,6 +118,7 @@ def kill_vrep_connection(self, *args): if hasattr(self, "connection_daemon"): self.connection_daemon.is_active = False if self.connection_daemon: + self.connection_daemon.terminate() self.connection_daemon.join() def __del__(self): From 92bcb4d9c97c0d37ad7efb1a8114f0f1a3b4adfe Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 17:36:33 +0200 Subject: [PATCH 176/945] do not ping vrep, use daemon for async reconnects if vrep returns an error --- micropsi_core/world/vrep/vrep_world.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index ee594f0c..df8d1bc3 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -39,10 +39,10 @@ def __init__(self, host, port, connection_listeners=[]): def run(self): self.reconnect() while self.is_active: - res, pingtime = vrep.simxGetPingTime(self.clientID) - if res != vrep.simx_return_ok: - self.reconnect() - time.sleep(self.ping_interval) + with self.state: + if self.paused: + self.state.wait() + self.reconnect() vrep.simxFinish(-1) def reconnect(self): @@ -61,6 +61,16 @@ def reconnect(self): self.logger.info("Connected to local V-REP at port %d" % self.port) for item in self.connection_listeners: item.on_vrep_connect() + self.pause() + + def resume(self): + with self.state: + self.paused = False + self.state.notify() + + def pause(self): + with self.state: + self.paused = True def terminate(self): self.stop.set() @@ -173,8 +183,8 @@ def __init__(self, world, uid=None, **data): def handle_res(self, res): if res != vrep.simx_return_ok: - error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) - self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) + self.logger.warn("vrep call returned error, reconnecting instance") + self.world.connection_daemon.resume() def on_vrep_connect(self): """ is called by the world, if a connection was established """ @@ -355,6 +365,7 @@ def update_data_sources_and_targets(self): return res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) + self.handle_res(res) rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster From 1ebbead403e56c67febc770ca16875ea8113fd2c Mon Sep 17 00:00:00 2001 From: policecar Date: Mon, 30 May 2016 17:43:50 +0200 Subject: [PATCH 177/945] Prevents import_sensors/actors() from re-sorting sensors/actors. --- micropsi_core/nodenet/netapi.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index fc666ebf..51f31598 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -299,7 +299,6 @@ def import_actors(self, nodespace, datatarget_prefix=None): return all_actors datatargets = self.worldadapter.get_available_datatargets() - datatargets = sorted(datatargets) for datatarget in datatargets: if datatarget_prefix is None or datatarget.startswith(datatarget_prefix): @@ -323,7 +322,6 @@ def import_sensors(self, nodespace, datasource_prefix=None): return all_sensors datasources = self.worldadapter.get_available_datasources() - datasources = sorted(datasources) for datasource in datasources: if datasource_prefix is None or datasource.startswith(datasource_prefix): From e172a671e2a6d87a6af2b9fb1627aa94f8dabe4f Mon Sep 17 00:00:00 2001 From: policecar Date: Mon, 30 May 2016 17:46:26 +0200 Subject: [PATCH 178/945] Wait a moment after restarting the vrep simulation to prevent spreading bogus sensor values. --- micropsi_core/world/vrep/vrep_world.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 5cf9f1ff..95ced7aa 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -249,6 +249,7 @@ def update_data_sources_and_targets(self): vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(1) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + time.sleep(0.5) if self.world.ballgame_type != "reach": vrep.simxPauseCommunication(self.world.clientID, True) From 730d6859d6f9014b3216898feaf2025efb73e254 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 17:58:28 +0200 Subject: [PATCH 179/945] missed a spot --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index df8d1bc3..79b1fb5a 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -406,7 +406,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals self.logger.warning("No data from vrep received. Sleeping for 5secs, the retrying.") time.sleep(1) - res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, + res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) self.handle_res(res) From c6b1feeec3229c7b6fa8b536570b6b872820222b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 18:00:24 +0200 Subject: [PATCH 180/945] skip image generation if we received no data --- micropsi_core/world/vrep/vrep_world.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 79b1fb5a..21dcf529 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -366,14 +366,15 @@ def update_data_sources_and_targets(self): res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) self.handle_res(res) - rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) - rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster - self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() + if len(image): + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) + rgb_image /= 255. + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster + self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() - self.image.set_data(y_image) + self.image.set_data(y_image) - return self.image + return self.image def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=False, initial=False): From 11a1d813c59f6f0526aecee64483ca684e222fe8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 18:02:37 +0200 Subject: [PATCH 181/945] stop nodenets before killing world connections --- micropsi_core/runtime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 6ff98cc5..b6406df3 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -226,9 +226,9 @@ def pause(self): def kill_runners(signal=None, frame=None): - for uid in worlds: - if hasattr(worlds[uid], 'kill_minecraft_thread'): - worlds[uid].kill_minecraft_thread() + for uid in nodenets: + if nodenets[uid].is_active: + nodenets[uid].is_active = False runner['runner'].resume() runner['running'] = False runner['runner'].join() From ee98a803ecf5bdf694a2a6864729a47fc9447ca2 Mon Sep 17 00:00:00 2001 From: cknd Date: Mon, 30 May 2016 18:23:20 +0200 Subject: [PATCH 182/945] separate config options for arm and ball random initialisation --- micropsi_core/world/vrep/vrep_world.py | 56 ++++++++++++++------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 95ced7aa..73df99d6 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -36,7 +36,9 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N self.vision_type = config['vision_type'] self.control_type = config['control_type'] self.collision_name = config.get('collision_name', '') - self.ballgame_type = config['ballgame_type'] + + self.randomize_arm = config['randomize_arm'] + self.randomize_ball = config['randomize_ball'] self.joints = [] self.vision_resolution = [] @@ -77,22 +79,22 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N else: self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) - if self.ballgame_type != "none": - res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) - self.handle_res(res) - if self.ball_handle < 1: - self.logger.warn("Could not get handle for Ball object, distance values will not be available.") - else: - res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, robot_position = vrep.simxGetObjectPosition(self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking) - if res != 0 and res != 1: - self.handle_res(res) - self.robot_position = robot_position + res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) + self.handle_res(res) + if self.ball_handle < 1: + self.logger.warn("Could not get handle for Ball object, distance values will not be available.") + else: + res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming) + if res != 0 and res != 1: + self.handle_res(res) + res, robot_position = vrep.simxGetObjectPosition(self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking) + if res != 0 and res != 1: + self.handle_res(res) + self.robot_position = robot_position + if self.vision_type == "grayscale": res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) @@ -168,10 +170,14 @@ def get_config_options(): 'description': 'Type of vision information to receive', 'default': 'none', 'options': ["none", "grayscale"]}, - {'name': 'ballgame_type', - 'description': 'Type of ball game to be played', - 'default': 'none', - 'options': ["none", "reach", "reach-fixed", "reach-randomized"]} + {'name': 'randomize_arm', + 'description': 'Initialize the robot arm randomly', + 'default': 'False', + 'options': ["False", "True"]}, + {'name': 'randomize_ball', + 'description': 'Initialize the ball position randomly', + 'default': 'False', + 'options': ["False", "True"]} ] @@ -247,11 +253,11 @@ def update_data_sources_and_targets(self): # simulation restart if restart: vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) - time.sleep(1) + time.sleep(0.1) vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) time.sleep(0.5) - if self.world.ballgame_type != "reach": + if self.world.randomize_arm == "True": vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) @@ -260,7 +266,7 @@ def update_data_sources_and_targets(self): vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) vrep.simxPauseCommunication(self.world.clientID, False) - if self.world.ballgame_type == "reach-randomized": + if self.world.randomize_ball == "True": max_dist = 0.8 rx = random.uniform(-max_dist, max_dist) max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) @@ -309,7 +315,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals # get data and feedback # read distance value - if self.world.ballgame_type != "none" and self.world.ball_handle > 0: + if self.world.ball_handle > 0: res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) relative_pos = [0,0] From 97c8dc24839b5cbb32f8b7f6a17ac9ec5e42945d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 19:11:38 +0200 Subject: [PATCH 183/945] plots can be optionally named and then later updated --- micropsi_core/nodenet/vizapi.py | 63 ++++++++++++++++++++++++++---- micropsi_core/tests/test_vizapi.py | 30 ++++++++++++++ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/micropsi_core/nodenet/vizapi.py b/micropsi_core/nodenet/vizapi.py index c36b72d5..d00bd946 100644 --- a/micropsi_core/nodenet/vizapi.py +++ b/micropsi_core/nodenet/vizapi.py @@ -35,6 +35,13 @@ class NodenetPlot(object): >>> image.add_activation_plot(netapi.get_activations(ns1, group1)) >>> image.add_linkweights_plot(netapi.get_link_weights(ns1, group1, ns2, group2)) >>> image.save_to_file('/tmp/plot.png') + + If you provide a name for the plots, you can later update them: + >>> image = NodenetPlot(cols=2) + >>> image.add_activation_plot(np.random.rand(10), name="group_activation_plot") + >>> new_data = np.random.rand(10) + >>> image.update_plot('group_activation_plot', new_data) + """ def __init__(self, plotsize=(6.0, 6.0), rows=1, cols=1, wspace=0.1, hspace=0.1): @@ -50,18 +57,44 @@ def __init__(self, plotsize=(6.0, 6.0), rows=1, cols=1, wspace=0.1, hspace=0.1): """ plt.close() # attempt to close old instance self.figure = plt.figure(figsize=plotsize) + self.plots = {} self.plotindex = 0 self.rows = rows self.cols = cols self.grid = gridspec.GridSpec(rows, cols, wspace=wspace, hspace=hspace) - def add_activation_plot(self, activations, rows=-1, cols=-1, vmin=None, vmax=None): + def update_plot(self, name, new_data): + """ update a named plot in this Image with the new data + returns True on success, False otherwise. + Parameters: + name - the name you gave when adding the plot + new_data - the new data for the plot + """ + if name in self.plots: + plot, shape = self.plots[name] + if new_data.shape != shape: + new_data = new_data.reshape(shape) + if type(plot) == list: + # 4d plot + row, col, inner_row, inner_col = shape + for r in range(row): + row_data = new_data[r, :] + for c in range(col): + plot[r+c].set_data(row_data[c, :]) + else: + # 2d plot + plot.set_data(new_data) + return True + return False + + def add_activation_plot(self, activations, name=None, rows=-1, cols=-1, vmin=None, vmax=None): """ Adds a plot of node-activations to the figure. Per default, the plot will attempt to render the activations into a square image If you have non-quadratic data, you have to give numbers for rows and cols so that the numbers can be reshaped accordingly Parameters: activations - array of activations + name - optional identification for later updates rows - number of rows, defaults to sqrt() cols - number of cols, defaults to sqrt() vmin - minimal value, defaults to 0 @@ -74,12 +107,15 @@ def add_activation_plot(self, activations, rows=-1, cols=-1, vmin=None, vmax=Non sz = int(np.ceil(np.sqrt(data.shape[0]))) matrix = data.reshape((sz, sz)) - self.add_2d_matrix_plot(matrix, vmin=vmin, vmax=vmax) + result = self.add_2d_matrix_plot(matrix, vmin=vmin, vmax=vmax) + if name is not None: + self.plots[name] = result - def add_linkweights_plot(self, linkweights, wspace=0.1, hspace=0.1, rows_outer=0, cols_outer=0, rows_inner=0, cols_inner=0): + def add_linkweights_plot(self, linkweights, name=None, wspace=0.1, hspace=0.1, rows_outer=0, cols_outer=0, rows_inner=0, cols_inner=0): """ Adds a plot of linkweights to the figure. Parameters: linkweights - output of netapi.get_link_weights + name - optional identification for later updates wspace - vertical spacing, defaults to 0.1 hspace - horizontal spacing, defaults to 0.1 rows_outer - number of rows of linkweight-plots, defaults to sqrt() @@ -88,7 +124,7 @@ def add_linkweights_plot(self, linkweights, wspace=0.1, hspace=0.1, rows_outer=0 cols_inner - number of pixel-cols per linkweight-plot, defaults to sqrt() """ data = np.array(linkweights) - (r, c) = data.shape + r, c = data.shape outer_sqrt = int(np.ceil(np.sqrt(r))) inner_sqrt = int(np.ceil(np.sqrt(c))) matrix = data.reshape(( @@ -97,7 +133,9 @@ def add_linkweights_plot(self, linkweights, wspace=0.1, hspace=0.1, rows_outer=0 rows_inner or inner_sqrt, cols_inner or inner_sqrt )) - self.add_4d_matrix_plot(matrix, wspace=wspace, hspace=hspace) + result = self.add_4d_matrix_plot(matrix, wspace=wspace, hspace=hspace) + if name is not None: + self.plots[name] = result def add_2d_matrix_plot(self, matrix, vmin=None, vmax=None): """ General plotter function to add a two-dimensional plot. The shape @@ -107,13 +145,17 @@ def add_2d_matrix_plot(self, matrix, vmin=None, vmax=None): data - 2-dimensional numpy matrix vmin - minimal value vmax - maximal value + Returns: + AxesImage - the image, that can later be updated if need be + shape - the shape of the data this image can handle """ ax = plt.Subplot(self.figure, self.grid[self.plotindex]) ax.set_xticks([]) ax.set_yticks([]) - ax.imshow(matrix, cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax) + thing = ax.imshow(matrix, cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax) self.figure.add_subplot(ax) self.plotindex += 1 + return thing, matrix.shape def add_4d_matrix_plot(self, data, wspace=0, hspace=0, vmin=None, vmax=None): """ General plotter function to add a grid of several two-dimensional plots @@ -125,19 +167,24 @@ def add_4d_matrix_plot(self, data, wspace=0, hspace=0, vmin=None, vmax=None): hspace - horizontal spacing vmin - minimal value vmax - maximal value + Returns: + list of AxesImages - the images, that can later be updated if need be + shape - the shape of the data this collection of images can handle """ # compute rows & cols - (row, col, inner_row, inner_col) = data.shape + row, col, inner_row, inner_col = data.shape grid = gridspec.GridSpecFromSubplotSpec(row, col, subplot_spec=self.grid[self.plotindex], wspace=wspace, hspace=hspace) + plots = [] for r in range(row): row_data = data[r, :] for c in range(col): ax = plt.Subplot(self.figure, grid[(r * col + c)]) ax.set_xticks([]) ax.set_yticks([]) - ax.imshow(row_data[c, :], cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax) + plots.append(ax.imshow(row_data[c, :], cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax)) self.figure.add_subplot(ax) self.plotindex += 1 + return plots, data.shape def save_to_file(self, filename, format="png", **params): """ saves the generated figure to the given file diff --git a/micropsi_core/tests/test_vizapi.py b/micropsi_core/tests/test_vizapi.py index 95aea62e..b7f4af19 100644 --- a/micropsi_core/tests/test_vizapi.py +++ b/micropsi_core/tests/test_vizapi.py @@ -4,8 +4,11 @@ Tests for vizapi """ +import pytest from micropsi_core import runtime as micropsi +# skip these tests if numpy is not installed +pytest.importorskip("numpy") def test_plot_activations(test_nodenet): from random import random @@ -90,3 +93,30 @@ def plotfunc(netapi, node=None, **params): micropsi.stop_nodenetrunner(test_nodenet) assert micropsi.MicropsiRunner.last_nodenet_exception == {} assert os.path.isfile(os.path.join(resourcepath, "plot.png")) + + +def test_update_plot(test_nodenet): + import numpy as np + nodenet = micropsi.get_nodenet(test_nodenet) + vizapi = nodenet.netapi.vizapi + image = vizapi.NodenetPlot((4, 4)) + activations_1 = np.random.rand(16) + image.add_activation_plot(activations_1, name='my_activations_plot') + result_1 = image.to_base64() + image.save_to_file('act_1.png') + activations_2 = np.random.rand(16) + image.update_plot('my_activations_plot', activations_2) + result_2 = image.to_base64() + image.save_to_file('act_2.png') + assert result_1 != result_2 + + image = vizapi.NodenetPlot((4, 4)) + linkweights_1 = np.random.rand(4, 4) + image.add_linkweights_plot(linkweights_1, name='my_linkweights_plot') + result_1 = image.to_base64() + image.save_to_file('lw_1.png') + linkweights_2 = np.random.rand(4, 4) + image.update_plot('my_linkweights_plot', linkweights_2) + result_2 = image.to_base64() + image.save_to_file('lw_2.png') + assert result_1 != result_2 From 8a4fb60409388dffa8c6a8318baa6fee2c65ca8e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 19:17:50 +0200 Subject: [PATCH 184/945] actually, offer to name all plots --- micropsi_core/nodenet/vizapi.py | 24 ++++++++---------------- micropsi_core/tests/test_vizapi.py | 4 ---- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/micropsi_core/nodenet/vizapi.py b/micropsi_core/nodenet/vizapi.py index d00bd946..160b2dc6 100644 --- a/micropsi_core/nodenet/vizapi.py +++ b/micropsi_core/nodenet/vizapi.py @@ -107,9 +107,7 @@ def add_activation_plot(self, activations, name=None, rows=-1, cols=-1, vmin=Non sz = int(np.ceil(np.sqrt(data.shape[0]))) matrix = data.reshape((sz, sz)) - result = self.add_2d_matrix_plot(matrix, vmin=vmin, vmax=vmax) - if name is not None: - self.plots[name] = result + self.add_2d_matrix_plot(matrix, name=name, vmin=vmin, vmax=vmax) def add_linkweights_plot(self, linkweights, name=None, wspace=0.1, hspace=0.1, rows_outer=0, cols_outer=0, rows_inner=0, cols_inner=0): """ Adds a plot of linkweights to the figure. @@ -133,11 +131,9 @@ def add_linkweights_plot(self, linkweights, name=None, wspace=0.1, hspace=0.1, r rows_inner or inner_sqrt, cols_inner or inner_sqrt )) - result = self.add_4d_matrix_plot(matrix, wspace=wspace, hspace=hspace) - if name is not None: - self.plots[name] = result + result = self.add_4d_matrix_plot(matrix, name=name, wspace=wspace, hspace=hspace) - def add_2d_matrix_plot(self, matrix, vmin=None, vmax=None): + def add_2d_matrix_plot(self, matrix, name=None, vmin=None, vmax=None): """ General plotter function to add a two-dimensional plot. The shape of the passed matrix determins the layout in rows and cols of the plot @@ -145,9 +141,6 @@ def add_2d_matrix_plot(self, matrix, vmin=None, vmax=None): data - 2-dimensional numpy matrix vmin - minimal value vmax - maximal value - Returns: - AxesImage - the image, that can later be updated if need be - shape - the shape of the data this image can handle """ ax = plt.Subplot(self.figure, self.grid[self.plotindex]) ax.set_xticks([]) @@ -155,9 +148,10 @@ def add_2d_matrix_plot(self, matrix, vmin=None, vmax=None): thing = ax.imshow(matrix, cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax) self.figure.add_subplot(ax) self.plotindex += 1 - return thing, matrix.shape + if name is not None: + self.plots[name] = thing, matrix.shape - def add_4d_matrix_plot(self, data, wspace=0, hspace=0, vmin=None, vmax=None): + def add_4d_matrix_plot(self, data, name=None, wspace=0, hspace=0, vmin=None, vmax=None): """ General plotter function to add a grid of several two-dimensional plots The shape of the passed matrix determins the layout in rows and cols of the plot @@ -167,9 +161,6 @@ def add_4d_matrix_plot(self, data, wspace=0, hspace=0, vmin=None, vmax=None): hspace - horizontal spacing vmin - minimal value vmax - maximal value - Returns: - list of AxesImages - the images, that can later be updated if need be - shape - the shape of the data this collection of images can handle """ # compute rows & cols row, col, inner_row, inner_col = data.shape @@ -184,7 +175,8 @@ def add_4d_matrix_plot(self, data, wspace=0, hspace=0, vmin=None, vmax=None): plots.append(ax.imshow(row_data[c, :], cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax)) self.figure.add_subplot(ax) self.plotindex += 1 - return plots, data.shape + if name is not None: + self.plots[name] = plots, data.shape def save_to_file(self, filename, format="png", **params): """ saves the generated figure to the given file diff --git a/micropsi_core/tests/test_vizapi.py b/micropsi_core/tests/test_vizapi.py index b7f4af19..19cd89e9 100644 --- a/micropsi_core/tests/test_vizapi.py +++ b/micropsi_core/tests/test_vizapi.py @@ -103,20 +103,16 @@ def test_update_plot(test_nodenet): activations_1 = np.random.rand(16) image.add_activation_plot(activations_1, name='my_activations_plot') result_1 = image.to_base64() - image.save_to_file('act_1.png') activations_2 = np.random.rand(16) image.update_plot('my_activations_plot', activations_2) result_2 = image.to_base64() - image.save_to_file('act_2.png') assert result_1 != result_2 image = vizapi.NodenetPlot((4, 4)) linkweights_1 = np.random.rand(4, 4) image.add_linkweights_plot(linkweights_1, name='my_linkweights_plot') result_1 = image.to_base64() - image.save_to_file('lw_1.png') linkweights_2 = np.random.rand(4, 4) image.update_plot('my_linkweights_plot', linkweights_2) result_2 = image.to_base64() - image.save_to_file('lw_2.png') assert result_1 != result_2 From 7862a93ee5cad176721606151f1245652debd199 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 30 May 2016 19:31:56 +0200 Subject: [PATCH 185/945] pep8 --- micropsi_core/tests/test_vizapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropsi_core/tests/test_vizapi.py b/micropsi_core/tests/test_vizapi.py index 19cd89e9..1a0e0cfc 100644 --- a/micropsi_core/tests/test_vizapi.py +++ b/micropsi_core/tests/test_vizapi.py @@ -10,6 +10,7 @@ # skip these tests if numpy is not installed pytest.importorskip("numpy") + def test_plot_activations(test_nodenet): from random import random nodenet = micropsi.get_nodenet(test_nodenet) From c4f32fa0c4d28a869c62709ba2ddf8a44f451f82 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 13:02:58 +0200 Subject: [PATCH 186/945] fix theano's reload_native_modules --- micropsi_core/nodenet/theano_engine/theano_partition.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 212ce691..2bafab31 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1436,11 +1436,14 @@ def delete_node(self, node_id): self.allocated_node_offsets[node_id] = 0 self.allocated_node_parents[node_id] = 0 g_function_selector_array = self.g_function_selector.get_value(borrow=True) - for element in range (0, get_elements_per_type(type, self.nodenet.native_modules)): + + element = 0 + while self.allocated_elements_to_nodes[offset + element] == node_id: self.allocated_elements_to_nodes[offset + element] = 0 g_function_selector_array[offset + element] = 0 + element += 1 + self.g_function_selector.set_value(g_function_selector_array, borrow=True) - self.allocated_elements_to_nodes[np.where(self.allocated_elements_to_nodes == node_id)[0]] = 0 if type == SENSOR: sensor_index = np.where(self.sensor_indices == offset)[0] From f9099d59f109daac4d75dea85fbc915547add484 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 15:17:37 +0200 Subject: [PATCH 187/945] improve shutdown procedure --- micropsi_core/runtime.py | 4 +--- micropsi_core/world/vrep/vrep_world.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index b6406df3..2fe0922c 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -114,6 +114,7 @@ def add_signal_handler(handler): def signal_handler(signal, frame): logging.getLogger('system').info("Shutting down") + kill_runners() for handler in signal_handler_registry: handler(signal, frame) sys.exit(0) @@ -1697,8 +1698,5 @@ def initialize(persistency_path=None, resource_path=None): if runner.get('runner') is None: runner['runner'] = MicropsiRunner() - if kill_runners not in signal_handler_registry: - add_signal_handler(kill_runners) - signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 0e35999c..99853f92 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -130,8 +130,10 @@ def kill_vrep_connection(self, *args): if hasattr(self, "connection_daemon"): self.connection_daemon.is_active = False if self.connection_daemon: + self.connection_daemon.resume() self.connection_daemon.terminate() self.connection_daemon.join() + vrep.simxFinish(-1) def __del__(self): self.kill_vrep_connection() From 991c2430d5a6b7d6d3a9f27624c6dd61775fc93b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 15:18:24 +0200 Subject: [PATCH 188/945] naming --- micropsi_core/world/vrep/vrep_world.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 99853f92..474bd690 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -173,11 +173,12 @@ def get_config_options(): class Robot(ArrayWorldAdapter): + block_runner_if_connection_lost = True + def __init__(self, world, uid=None, **data): self.available_datatargets = [] self.available_datasources = [] super().__init__(world, uid, **data) - self.block = True self.joints = [] self.vision_resolution = [] self.collision_handle = -1 @@ -388,12 +389,14 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals # get data and feedback # read distance value + if not self.world.connection_daemon.is_connected: - if self.block and not initial: + if self.block_runner_if_connection_lost and not initial: while not self.world.connection_daemon.is_connected: time.sleep(0.5) else: return + if self.world.connection_daemon.clientID != self.clientID: self.get_vrep_data() From 82e4e44bea2dcba89feba40121a1d8dc76be1bf1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 15:18:33 +0200 Subject: [PATCH 189/945] reset before gathering vrep data --- micropsi_core/world/vrep/vrep_world.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 474bd690..0313ea60 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -179,14 +179,6 @@ def __init__(self, world, uid=None, **data): self.available_datatargets = [] self.available_datasources = [] super().__init__(world, uid, **data) - self.joints = [] - self.vision_resolution = [] - self.collision_handle = -1 - - self.robot_handle = -1 - self.ball_handle = -1 - - self.robot_position = [] self.get_vrep_data() @@ -203,6 +195,13 @@ def get_vrep_data(self): self.clientID = self.world.connection_daemon.clientID + self.joints = [] + self.vision_resolution = [] + self.collision_handle = -1 + self.robot_handle = -1 + self.ball_handle = -1 + self.robot_position = [] + res, self.robot_handle = vrep.simxGetObjectHandle(self.clientID, self.world.robot_name, vrep.simx_opmode_blocking) self.handle_res(res) if self.robot_handle < 1: From a1db3a99654785bd78b58adb703c9ef41beed886 Mon Sep 17 00:00:00 2001 From: cknd Date: Tue, 31 May 2016 16:16:35 +0200 Subject: [PATCH 190/945] added tip position datasource and gave the ball a minimum distance to the robot --- micropsi_core/world/vrep/vrep_world.py | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 73df99d6..ada4bc93 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -192,6 +192,10 @@ def __init__(self, world, uid=None, **data): self.available_datasources.append("ball-x") self.available_datasources.append("ball-y") + self.available_datasources.append("tip-x") + self.available_datasources.append("tip-y") + self.available_datasources.append("tip-z") + self.available_datatargets.append("restart") self.available_datatargets.append("execute") @@ -216,8 +220,9 @@ def __init__(self, world, uid=None, **data): self.distance_offset = 0 self.collision_offset = 1 - self.position_offset = 2 - self.joint_angle_offset = self.position_offset + 2 # because ball_x, ball_y + self.ball_position_offset = 2 + self.tip_position_offset = self.ball_position_offset + 2 # because ball_x, ball_y + self.joint_angle_offset = self.tip_position_offset + 3 # because tipx tipy tipz self.joint_force_offset = self.joint_angle_offset + len(self.world.joints) if self.world.vision_type == "grayscale": @@ -253,8 +258,9 @@ def update_data_sources_and_targets(self): # simulation restart if restart: vrep.simxStopSimulation(self.world.clientID, vrep.simx_opmode_oneshot) - time.sleep(0.1) - vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + time.sleep(0.5) + res = vrep.simxStartSimulation(self.world.clientID, vrep.simx_opmode_oneshot) + self.world.handle_res(res) time.sleep(0.5) if self.world.randomize_arm == "True": @@ -267,10 +273,18 @@ def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, False) if self.world.randomize_ball == "True": + # max_dist = 0.8 + # rx = random.uniform(-max_dist, max_dist) + # max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) + # ry = random.uniform(-max_y, max_y) max_dist = 0.8 - rx = random.uniform(-max_dist, max_dist) - max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) - ry = random.uniform(-max_y, max_y) + min_dist = 0.2 + k = max_dist**2 - min_dist**2 + a = np.random.rand() * 2 * np.pi + r = np.sqrt(np.random.rand() * k + min_dist**2) + rx = r*np.cos(a) + ry = r*np.sin(a) + vrep.simxSetObjectPosition(self.world.clientID, self.world.ball_handle, self.world.robot_handle, [rx, ry], vrep.simx_opmode_blocking) self.fetch_sensor_and_feedback_values_from_simulation() @@ -313,19 +327,22 @@ def update_data_sources_and_targets(self): def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=False): + res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) + self.datasource_values[self.tip_position_offset + 0] = joint_pos[0] - self.world.robot_position[0] + self.datasource_values[self.tip_position_offset + 1] = joint_pos[1] - self.world.robot_position[1] + self.datasource_values[self.tip_position_offset + 2] = joint_pos[2] - self.world.robot_position[2] # get data and feedback # read distance value if self.world.ball_handle > 0: res, ball_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.ball_handle, -1, vrep.simx_opmode_buffer) - res, joint_pos = vrep.simxGetObjectPosition(self.world.clientID, self.world.joints[len(self.world.joints)-1], -1, vrep.simx_opmode_streaming) relative_pos = [0,0] relative_pos[0] = ball_pos[0] - self.world.robot_position[0] relative_pos[1] = ball_pos[1] - self.world.robot_position[1] dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) self.datasource_values[self.distance_offset] = dist - self.datasource_values[self.position_offset + 0] = relative_pos[0] - self.datasource_values[self.position_offset + 1] = relative_pos[1] + self.datasource_values[self.ball_position_offset + 0] = relative_pos[0] + self.datasource_values[self.ball_position_offset + 1] = relative_pos[1] res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) self.world.handle_res(res) From 441d7113b5cb1282b3337408880444f84386442a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 16:29:39 +0200 Subject: [PATCH 191/945] warn() is deprecated --- micropsi_core/config.py | 2 +- .../nodenet/dict_engine/dict_node.py | 2 +- .../nodenet/dict_engine/dict_nodenet.py | 8 +-- micropsi_core/nodenet/native_modules.py | 6 +- micropsi_core/nodenet/netapi.py | 2 +- micropsi_core/nodenet/node.py | 2 +- .../nodenet/theano_engine/theano_node.py | 2 +- .../nodenet/theano_engine/theano_nodenet.py | 28 ++++---- .../nodenet/theano_engine/theano_partition.py | 64 +++++++++---------- micropsi_core/runtime.py | 12 ++-- .../minecraft/minecraft_histogram_vision.py | 2 +- .../world/minecraft/minecraft_vision.py | 2 +- micropsi_core/world/timeseries/timeseries.py | 4 +- micropsi_core/world/vrep/vrep_world.py | 6 +- micropsi_core/world/world.py | 10 +-- micropsi_server/usermanagement.py | 2 +- 16 files changed, 77 insertions(+), 77 deletions(-) diff --git a/micropsi_core/config.py b/micropsi_core/config.py index e5d62559..4f730a6a 100644 --- a/micropsi_core/config.py +++ b/micropsi_core/config.py @@ -78,7 +78,7 @@ def load_configs(self): self.data = json.load(file) return True except ValueError: - logging.getLogger("system").warn("Could not read config data at %s" % self.config_file_name) + logging.getLogger("system").warning("Could not read config data at %s" % self.config_file_name) except IOError: logging.getLogger("system").info("No readable config data file, attempting to create one") return False diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index e5ea9934..5a92fd35 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -110,7 +110,7 @@ def __init__(self, nodenet, parent_nodespace, position, state=None, activation=0 try: gate_parameters[gate_name][key] = float(gate_parameters[gate_name][key]) except: - self.logger.warn('Invalid gate parameter value for gate %s, param %s, node %s' % (gate_name, key, self.uid)) + self.logger.warning('Invalid gate parameter value for gate %s, param %s, node %s' % (gate_name, key, self.uid)) gate_parameters[gate_name][key] = self.nodetype.gate_defaults[gate_name].get(key, 0) else: gate_parameters[gate_name][key] = float(gate_parameters[gate_name][key]) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 7ac19209..cbc5ca50 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -264,10 +264,10 @@ def load(self, filename): with open(filename) as file: initfrom.update(json.load(file)) except ValueError: - self.logger.warn("Could not read nodenet data") + self.logger.warning("Could not read nodenet data") return False except IOError: - self.logger.warn("Could not open nodenet file") + self.logger.warning("Could not open nodenet file") return False if self._version == NODENET_VERSION: @@ -480,7 +480,7 @@ def merge_data(self, nodenet_data, keep_uids=False): data['uid'] = newuid uidmap[uid] = newuid if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.warn("Invalid nodetype %s for node %s" % (data['type'], uid)) + self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) data['parameters'] = { 'comment': 'There was a %s node here' % data['type'] } @@ -516,7 +516,7 @@ def merge_data(self, nodenet_data, keep_uids=False): mon = getattr(monitor, data['classname'])(self, **data) self._monitors[mon.uid] = mon else: - self.logger.warn('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) + self.logger.warning('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) else: # Compatibility mode mon = monitor.NodeMonitor(self, name=data['node_name'], **data) diff --git a/micropsi_core/nodenet/native_modules.py b/micropsi_core/nodenet/native_modules.py index 47ded65f..d14c2cd8 100644 --- a/micropsi_core/nodenet/native_modules.py +++ b/micropsi_core/nodenet/native_modules.py @@ -823,15 +823,15 @@ def gradient_descent(netapi, node=None, **params): len_output = len(a_o_array) if len_input == 0: - netapi.logger.warn("Node net has no input nodes whose names start with '%s'", input_) + netapi.logger.warning("Node net has no input nodes whose names start with '%s'", input_) node.set_parameter('ctr', 0) return elif len_hidden == 0: - netapi.logger.warn("Node net has no hidden nodes whose names start with '%s'.", hidden) + netapi.logger.warning("Node net has no hidden nodes whose names start with '%s'.", hidden) node.set_parameter('ctr', 0) return elif len_output == 0: - netapi.logger.warn("Node net has no output names whose names start with '%s'.", output) + netapi.logger.warning("Node net has no output names whose names start with '%s'.", output) node.set_parameter('ctr', 0) return else: diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index c4aa08ef..211e5b44 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -246,7 +246,7 @@ def unlink_direction(self, node, gateslot=None): Deletes all links from a node ending at the given slot or originating at the given gate Read this as 'delete all por linkage from this node' """ - self.logger.warn("unlink direction is deprecated. use unlink_gate and unlink_slot") + self.logger.warning("unlink direction is deprecated. use unlink_gate and unlink_slot") node.unlink(gateslot) links_to_delete = set() diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 55c91d62..3935550f 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -590,7 +590,7 @@ def nodefunction_definition(self, nodefunction_definition): self.nodefunction = micropsi_core.tools.create_function(nodefunction_definition, parameters="nodenet, node, " + args) except SyntaxError as err: - self.logger.warn("Syntax error while compiling node function: %s", str(err)) + self.logger.warning("Syntax error while compiling node function: %s", str(err)) raise err @property diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 51334f02..4b65ef19 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -421,7 +421,7 @@ def node_function(self): except Exception: self._nodenet.is_active = False if self.nodetype is not None and self.nodetype.nodefunction is None: - self.logger.warn("No nodefunction found for nodetype %s. Node function definition is: %s" % (self.nodetype.name, self.nodetype.nodefunction_definition)) + self.logger.warning("No nodefunction found for nodetype %s. Node function definition is: %s" % (self.nodetype.name, self.nodetype.nodefunction_definition)) else: raise diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index b7f505bf..c791c859 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -212,7 +212,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.numpyfloatX = np.float64 self.byte_per_float = 8 else: # pragma: no cover - self.logger.warn("Unsupported precision value from configuration: %s, falling back to float64", precision) + self.logger.warning("Unsupported precision value from configuration: %s, falling back to float64", precision) T.config.floatX = "float64" self.scipyfloatX = scipy.float64 self.numpyfloatX = np.float64 @@ -223,7 +223,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No if device.startswith("gpu"): self.logger.info("Using CUDA with cuda_root=%s and theano_flags=%s", os.environ["CUDA_ROOT"], os.environ["THEANO_FLAGS"]) if T.config.floatX != "float32": - self.logger.warn("Precision set to %s, but attempting to use gpu.", precision) + self.logger.warning("Precision set to %s, but attempting to use gpu.", precision) self.netapi = TheanoNetAPI(self) @@ -235,14 +235,14 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No try: average_elements_per_node_assumption = int(configured_elements_per_node_assumption) except: # pragma: no cover - self.logger.warn("Unsupported elements_per_node_assumption value from configuration: %s, falling back to 4", configured_elements_per_node_assumption) + self.logger.warning("Unsupported elements_per_node_assumption value from configuration: %s, falling back to 4", configured_elements_per_node_assumption) initial_number_of_nodes = 2000 configured_initial_number_of_nodes = settings['theano']['initial_number_of_nodes'] try: initial_number_of_nodes = int(configured_initial_number_of_nodes) except: # pragma: no cover - self.logger.warn("Unsupported initial_number_of_nodes value from configuration: %s, falling back to 2000", configured_initial_number_of_nodes) + self.logger.warning("Unsupported initial_number_of_nodes value from configuration: %s, falling back to 2000", configured_initial_number_of_nodes) sparse = True configuredsparse = settings['theano']['sparse_weight_matrix'] @@ -251,7 +251,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No elif configuredsparse == "False": sparse = False else: # pragma: no cover - self.logger.warn("Unsupported sparse_weight_matrix value from configuration: %s, falling back to True", configuredsparse) + self.logger.warning("Unsupported sparse_weight_matrix value from configuration: %s, falling back to True", configuredsparse) sparse = True rootpartition = TheanoPartition(self, @@ -393,10 +393,10 @@ def load(self, filename): with open(filename) as file: initfrom.update(json.load(file)) except ValueError: # pragma: no cover - self.logger.warn("Could not read nodenet metadata from file %s", filename) + self.logger.warning("Could not read nodenet metadata from file %s", filename) return False except IOError: # pragma: no cover - self.logger.warn("Could not open nodenet metadata file %s", filename) + self.logger.warning("Could not open nodenet metadata file %s", filename) return False # determine whether we have a complete json dump, or our theano npz partition files: @@ -427,7 +427,7 @@ def load(self, filename): mon = getattr(monitor, data['classname'])(self, **data) self._monitors[mon.uid] = mon else: - self.logger.warn('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) + self.logger.warning('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) for recorder_uid in initfrom.get('recorders', {}): data = initfrom['recorders'][recorder_uid] @@ -529,7 +529,7 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only parent_uid = uidmap[data['parent_nodespace']] id_to_pass = None if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.warn("Invalid nodetype %s for node %s" % (data['type'], uid)) + self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) data['parameters'] = { 'comment': 'There was a %s node here' % data['type'] } @@ -587,7 +587,7 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only mon = getattr(monitor, data['classname'])(self, **data) self._monitors[mon.uid] = mon else: - self.logger.warn('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) + self.logger.warning('unknown classname for monitor: %s (uid:%s) ' % (data['classname'], monitorid)) else: # Compatibility mode mon = monitor.NodeMonitor(self, name=data['node_name'], **data) @@ -961,7 +961,7 @@ def create_nodespace(self, parent_uid, position, name="", uid=None, options=None try: average_elements_per_node_assumption = int(configured_elements_per_node_assumption) except: - self.logger.warn("Unsupported elements_per_node_assumption value from configuration: %s, falling back to 4", configured_elements_per_node_assumption) # pragma: no cover + self.logger.warning("Unsupported elements_per_node_assumption value from configuration: %s, falling back to 4", configured_elements_per_node_assumption) # pragma: no cover initial_number_of_nodes = 2000 if "initial_number_of_nodes" in options: @@ -971,7 +971,7 @@ def create_nodespace(self, parent_uid, position, name="", uid=None, options=None try: initial_number_of_nodes = int(configured_initial_number_of_nodes) except: - self.logger.warn("Unsupported initial_number_of_nodes value from configuration: %s, falling back to 2000", configured_initial_number_of_nodes) # pragma: no cover + self.logger.warning("Unsupported initial_number_of_nodes value from configuration: %s, falling back to 2000", configured_initial_number_of_nodes) # pragma: no cover sparse = True if "sparse" in options: @@ -983,7 +983,7 @@ def create_nodespace(self, parent_uid, position, name="", uid=None, options=None elif configuredsparse == "False": sparse = False else: - self.logger.warn("Unsupported sparse_weight_matrix value from configuration: %s, falling back to True", configuredsparse) # pragma: no cover + self.logger.warning("Unsupported sparse_weight_matrix value from configuration: %s, falling back to True", configuredsparse) # pragma: no cover sparse = True self.last_allocated_partition += 1 @@ -1126,7 +1126,7 @@ def reload_native_modules(self, native_modules): for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): if instance.type not in native_modules: - self.logger.warn("No more definition available for node type %s, deleting instance %s" % + self.logger.warning("No more definition available for node type %s, deleting instance %s" % (instance.type, uid)) instances_to_delete[uid] = instance continue diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 2bafab31..7a7acbed 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -824,10 +824,10 @@ def load_data(self, datafilename, nodes_data): self.logger.info("Loading nodenet %s partition %i bulk data from file %s" % (self.nodenet.name, self.pid, datafilename)) datafile = np.load(datafilename) except ValueError: # pragma: no cover - self.logger.warn("Could not read nodenet data from file %s" % datafile) + self.logger.warning("Could not read nodenet data from file %s" % datafile) return False except IOError: # pragma: no cover - self.logger.warn("Could not open nodenet file %s" % datafile) + self.logger.warning("Could not open nodenet file %s" % datafile) return False if not datafile: @@ -847,73 +847,73 @@ def load_data(self, datafilename, nodes_data): self.a_prev = theano.shared(value=a_prev_array.astype(T.config.floatX), name="a_prev", borrow=True) else: - self.logger.warn("no sizeinformation in file, falling back to defaults") # pragma: no cover + self.logger.warning("no sizeinformation in file, falling back to defaults") # pragma: no cover # the load bulk data into numpy arrays if 'allocated_nodes' in datafile: self.allocated_nodes = datafile['allocated_nodes'] else: - self.logger.warn("no allocated_nodes in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodes in file, falling back to defaults") # pragma: no cover if 'allocated_node_offsets' in datafile: self.allocated_node_offsets = datafile['allocated_node_offsets'] else: - self.logger.warn("no allocated_node_offsets in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_node_offsets in file, falling back to defaults") # pragma: no cover if 'allocated_elements_to_nodes' in datafile: self.allocated_elements_to_nodes = datafile['allocated_elements_to_nodes'] else: - self.logger.warn("no allocated_elements_to_nodes in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_elements_to_nodes in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces' in datafile: self.allocated_nodespaces = datafile['allocated_nodespaces'] else: - self.logger.warn("no allocated_nodespaces in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces in file, falling back to defaults") # pragma: no cover if 'allocated_node_parents' in datafile: self.allocated_node_parents = datafile['allocated_node_parents'] else: - self.logger.warn("no allocated_node_parents in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_node_parents in file, falling back to defaults") # pragma: no cover if 'allocated_elements_to_activators' in datafile: self.allocated_elements_to_activators = datafile['allocated_elements_to_activators'] else: - self.logger.warn("no allocated_elements_to_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_elements_to_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_por_activators' in datafile: self.allocated_nodespaces_por_activators = datafile['allocated_nodespaces_por_activators'] else: - self.logger.warn("no allocated_nodespaces_por_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_por_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_ret_activators' in datafile: self.allocated_nodespaces_ret_activators = datafile['allocated_nodespaces_ret_activators'] else: - self.logger.warn("no allocated_nodespaces_ret_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_ret_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_sub_activators' in datafile: self.allocated_nodespaces_sub_activators = datafile['allocated_nodespaces_sub_activators'] else: - self.logger.warn("no allocated_nodespaces_sub_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_sub_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_sur_activators' in datafile: self.allocated_nodespaces_sur_activators = datafile['allocated_nodespaces_sur_activators'] else: - self.logger.warn("no allocated_nodespaces_sur_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_sur_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_cat_activators' in datafile: self.allocated_nodespaces_cat_activators = datafile['allocated_nodespaces_cat_activators'] else: - self.logger.warn("no allocated_nodespaces_cat_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_cat_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_exp_activators' in datafile: self.allocated_nodespaces_exp_activators = datafile['allocated_nodespaces_exp_activators'] else: - self.logger.warn("no allocated_nodespaces_exp_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_exp_activators in file, falling back to defaults") # pragma: no cover if 'allocated_nodespaces_sampling_activators' in datafile: self.allocated_nodespaces_sampling_activators = datafile['allocated_nodespaces_sampling_activators'] else: - self.logger.warn("no allocated_nodespaces_por_activators in file, falling back to defaults") # pragma: no cover + self.logger.warning("no allocated_nodespaces_por_activators in file, falling back to defaults") # pragma: no cover if 'w_data' in datafile and 'w_indices' in datafile and 'w_indptr' in datafile: w = sp.csr_matrix((datafile['w_data'], datafile['w_indices'], datafile['w_indptr']), shape = (self.NoE, self.NoE)) @@ -924,62 +924,62 @@ def load_data(self, datafilename, nodes_data): self.a = theano.shared(value=datafile['a'].astype(T.config.floatX), name="a", borrow=False) self.a_in = theano.shared(value=np.zeros_like(datafile['a']).astype(T.config.floatX), name="a_in", borrow=False) else: - self.logger.warn("no w_data, w_indices or w_indptr in file, falling back to defaults") # pragma: no cover + self.logger.warning("no w_data, w_indices or w_indptr in file, falling back to defaults") # pragma: no cover if 'g_theta' in datafile: self.g_theta = theano.shared(value=datafile['g_theta'].astype(T.config.floatX), name="theta", borrow=False) else: - self.logger.warn("no g_theta in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_theta in file, falling back to defaults") # pragma: no cover if 'g_factor' in datafile: self.g_factor = theano.shared(value=datafile['g_factor'].astype(T.config.floatX), name="g_factor", borrow=False) else: - self.logger.warn("no g_factor in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_factor in file, falling back to defaults") # pragma: no cover if 'g_threshold' in datafile: self.g_threshold = theano.shared(value=datafile['g_threshold'].astype(T.config.floatX), name="g_threshold", borrow=False) else: - self.logger.warn("no g_threshold in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_threshold in file, falling back to defaults") # pragma: no cover if 'g_amplification' in datafile: self.g_amplification = theano.shared(value=datafile['g_amplification'].astype(T.config.floatX), name="g_amplification", borrow=False) else: - self.logger.warn("no g_amplification in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_amplification in file, falling back to defaults") # pragma: no cover if 'g_min' in datafile: self.g_min = theano.shared(value=datafile['g_min'].astype(T.config.floatX), name="g_min", borrow=False) else: - self.logger.warn("no g_min in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_min in file, falling back to defaults") # pragma: no cover if 'g_max' in datafile: self.g_max = theano.shared(value=datafile['g_max'].astype(T.config.floatX), name="g_max", borrow=False) else: - self.logger.warn("no g_max in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_max in file, falling back to defaults") # pragma: no cover if 'g_function_selector' in datafile: self.g_function_selector = theano.shared(value=datafile['g_function_selector'], name="gatefunction", borrow=False) else: - self.logger.warn("no g_function_selector in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_function_selector in file, falling back to defaults") # pragma: no cover if 'g_expect' in datafile: self.g_expect = theano.shared(value=datafile['g_expect'], name="expectation", borrow=False) else: - self.logger.warn("no g_expect in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_expect in file, falling back to defaults") # pragma: no cover if 'g_countdown' in datafile: self.g_countdown = theano.shared(value=datafile['g_countdown'], name="countdown", borrow=False) else: - self.logger.warn("no g_countdown in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_countdown in file, falling back to defaults") # pragma: no cover if 'g_wait' in datafile: self.g_wait = theano.shared(value=datafile['g_wait'], name="wait", borrow=False) else: - self.logger.warn("no g_wait in file, falling back to defaults") # pragma: no cover + self.logger.warning("no g_wait in file, falling back to defaults") # pragma: no cover if 'n_function_selector' in datafile: self.n_function_selector = theano.shared(value=datafile['n_function_selector'], name="nodefunction_per_gate", borrow=False) else: - self.logger.warn("no n_function_selector in file, falling back to defaults") # pragma: no cover + self.logger.warning("no n_function_selector in file, falling back to defaults") # pragma: no cover # reconstruct other states self.por_ret_dirty = True @@ -1004,7 +1004,7 @@ def load_data(self, datafilename, nodes_data): self.has_gatefunction_one_over_x = GATE_FUNCTION_DIST in g_function_selector self.has_gatefunction_elu = GATE_FUNCTION_ELU in g_function_selector else: - self.logger.warn("no g_function_selector in file, falling back to defaults") + self.logger.warning("no g_function_selector in file, falling back to defaults") for id in np.nonzero(self.allocated_nodes)[0]: if self.allocated_nodes[id] > MAX_STD_NODETYPE: @@ -1042,10 +1042,10 @@ def load_inlinks(self, datafilename): try: datafile = np.load(datafilename) except ValueError: # pragma: no cover - self.logger.warn("Could not read nodenet data from file %s" % datafile) + self.logger.warning("Could not read nodenet data from file %s" % datafile) return False except IOError: # pragma: no cover - self.logger.warn("Could not open nodenet file %s" % datafile) + self.logger.warning("Could not open nodenet file %s" % datafile) return False if not datafile: @@ -1083,7 +1083,7 @@ def load_inlinks(self, datafilename): inlink_from_offset += inlink_from_lengths[i] inlink_to_offset += inlink_to_lengths[i] else: - self.logger.warn("no or incomplete inlink information in file, no inter-partition links will be loaded") # pragma: no cover + self.logger.warning("no or incomplete inlink information in file, no inter-partition links will be loaded") # pragma: no cover def grow_number_of_nodespaces(self, growby): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 9f9ca931..4d4f73fa 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -332,12 +332,12 @@ def load_nodenet(nodenet_uid): world_uid = data.world worldadapter = data.get('worldadapter') else: - logging.getLogger("system").warn("World %s for nodenet %s not found" % (data.world, data.uid)) + logging.getLogger("system").warning("World %s for nodenet %s not found" % (data.world, data.uid)) if world_uid: result, worldadapter_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet_uid, nodenet_name=data['name']) if not result: - logging.getLogger('system').warn(worldadapter_instance) + logging.getLogger('system').warning(worldadapter_instance) worldadapter_instance = None worldadapter = None world_uid = None @@ -1435,9 +1435,9 @@ def crawl_definition_files(path, type="definition"): data = parse_definition(json.load(file), filename) result[data.uid] = data except ValueError: - logging.getLogger('system').warn("Invalid %s data in file '%s'" % (type, definition_file_name)) + logging.getLogger('system').warning("Invalid %s data in file '%s'" % (type, definition_file_name)) except IOError: - logging.getLogger('system').warn("Could not open %s data file '%s'" % (type, definition_file_name)) + logging.getLogger('system').warning("Could not open %s data file '%s'" % (type, definition_file_name)) return result @@ -1489,9 +1489,9 @@ def init_worlds(world_data): except TypeError: worlds[uid] = world.World(**world_data[uid]) except AttributeError as err: - logging.getLogger('system').warn("Unknown world_type: %s (%s)" % (world_data[uid].world_type, str(err))) + logging.getLogger('system').warning("Unknown world_type: %s (%s)" % (world_data[uid].world_type, str(err))) except: - logging.getLogger('system').warn("Can not instantiate World \"%s\": %s" % (world_data[uid].name, str(sys.exc_info()[1]))) + logging.getLogger('system').warning("Can not instantiate World \"%s\": %s" % (world_data[uid].name, str(sys.exc_info()[1]))) else: worlds[uid] = world.World(**world_data[uid]) return worlds diff --git a/micropsi_core/world/minecraft/minecraft_histogram_vision.py b/micropsi_core/world/minecraft/minecraft_histogram_vision.py index 0d110739..b0af74d6 100644 --- a/micropsi_core/world/minecraft/minecraft_histogram_vision.py +++ b/micropsi_core/world/minecraft/minecraft_histogram_vision.py @@ -226,7 +226,7 @@ def get_visual_input(self, fov_x, fov_y, label): data = "{0}".format(",".join(str(b) for b in patch)) self.record_file.write("%s,%s,%d,%d,%d,%d\n" % (data, label, pitch, yaw, fov_x, fov_y)) else: - self.logger.warn('potentially corrupt data were ignored') + self.logger.warning('potentially corrupt data were ignored') def simulate_visual_input(self): """ diff --git a/micropsi_core/world/minecraft/minecraft_vision.py b/micropsi_core/world/minecraft/minecraft_vision.py index b6052c77..d21c004c 100644 --- a/micropsi_core/world/minecraft/minecraft_vision.py +++ b/micropsi_core/world/minecraft/minecraft_vision.py @@ -261,7 +261,7 @@ def get_visual_input(self, fov_x, fov_y, res_x, res_y, len_x, len_y, label): self.record_file.write("%s,%s,%d,%d,%d,%d,%.3f,%.3f,%d,%d\n" % (data, label, pitch, yaw, fov_x, fov_y, res_x, res_y, len_x, len_y)) else: - self.logger.warn('potentially corrupt data were ignored') + self.logger.warning('potentially corrupt data were ignored') def simulate_visual_input(self, len_x, len_y): """ diff --git a/micropsi_core/world/timeseries/timeseries.py b/micropsi_core/world/timeseries/timeseries.py index 8c4e3f7d..1b508844 100644 --- a/micropsi_core/world/timeseries/timeseries.py +++ b/micropsi_core/world/timeseries/timeseries.py @@ -59,7 +59,7 @@ def __init__(self, filename, world_type="TimeSeries", name="", owner="", engine= self.shuffle = config['shuffle'] == "True" if clip_and_scale and sigmoid: - self.logger.warn("clip_and_scale and sigmoid cannot both be configured, choosing sigmoid") + self.logger.warning("clip_and_scale and sigmoid cannot both be configured, choosing sigmoid") clip_and_scale = False def sigm(X): @@ -90,7 +90,7 @@ def sigm(X): self.timeseries = data_z if not sigmoid else sigm(data_z) if dummydata: - self.logger.warn("! Using dummy data") + self.logger.warning("! Using dummy data") n_ids = self.timeseries.shape[1] self.timeseries = np.tile(np.random.rand(n_ids,1),(1,10)) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index c8dd547b..c697953e 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -71,7 +71,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) self.handle_res(res) if self.ball_handle < 1: - self.logger.warn("Could not get handle for Ball object, distance values will not be available.") + self.logger.warning("Could not get handle for Ball object, distance values will not be available.") else: res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) if res != 0 and res != 1: @@ -88,7 +88,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) self.handle_res(res) if self.observer_handle < 1: - self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") + self.logger.warning("Could not get handle for Observer vision sensor, vision will not be available.") else: res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) if res != 0 and res != 1: @@ -108,7 +108,7 @@ def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=N def handle_res(self, res): if res != vrep.simx_return_ok: error = vrep.simxGetLastErrors(self.clientID, vrep.simx_opmode_blocking) - self.logger.warn("v-rep call returned error code %d, error: %s" % (res, error)) + self.logger.warning("v-rep call returned error code %d, error: %s" % (res, error)) def get_world_view(self, step): data = { diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 293e4993..9c267628 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -139,23 +139,23 @@ def load(self, string=None): try: self.data.update(json.loads(string)) except ValueError: - self.logger.warn("Could not read world data from string") + self.logger.warning("Could not read world data from string") return False else: try: with open(self.filename) as file: self.data.update(json.load(file)) except ValueError: - self.logger.warn("Could not read world data") + self.logger.warning("Could not read world data") return False except IOError: - self.logger.warn("Could not open world file: " + self.filename) + self.logger.warning("Could not open world file: " + self.filename) self.data['world_type'] = self.__class__.__name__ if "version" in self.data and self.data["version"] == WORLD_VERSION: self.initialize_world() return True else: - self.logger.warn("Wrong version of the world data") + self.logger.warning("Wrong version of the world data") return False def get_available_worldadapters(self): @@ -172,7 +172,7 @@ def initialize_world(self): if object_data['type'] in self.supported_worldobjects: self.objects[uid] = self.supported_worldobjects[object_data['type']](self, **object_data) else: - self.logger.warn('Worldobject of type %s not supported anymore. Deleting object of this type.' % object_data['type']) + self.logger.warning('Worldobject of type %s not supported anymore. Deleting object of this type.' % object_data['type']) del self.data['objects'][uid] def step(self): diff --git a/micropsi_server/usermanagement.py b/micropsi_server/usermanagement.py index f73170c6..ceeca4f5 100644 --- a/micropsi_server/usermanagement.py +++ b/micropsi_server/usermanagement.py @@ -96,7 +96,7 @@ def __init__(self, userfile_path=None): with open(self.user_file_name) as file: self.users = json.load(file) except ValueError: - logging.getLogger('system').warn("Invalid user data") + logging.getLogger('system').warning("Invalid user data") except IOError: logging.getLogger('system').info("No readable userdata file, attempting to create one.") From 9f863e7e7a48adbb23028662305e7079616f3d16 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 16:32:01 +0200 Subject: [PATCH 192/945] get rid of the pesky backslash syntax --- .../tests/test_nodenet_partitions.py | 20 +-- .../tests/test_runtime_nodenet_basics.py | 62 +++---- micropsi_core/tests/test_runtime_nodes.py | 166 +++++++++--------- micropsi_server/tests/test_json_api.py | 48 ++--- 4 files changed, 148 insertions(+), 148 deletions(-) diff --git a/micropsi_core/tests/test_nodenet_partitions.py b/micropsi_core/tests/test_nodenet_partitions.py index b3fbaecc..3cfed643 100644 --- a/micropsi_core/tests/test_nodenet_partitions.py +++ b/micropsi_core/tests/test_nodenet_partitions.py @@ -177,11 +177,11 @@ def test_delete_partition_unlinks_native_module(test_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"]}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"]}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") micropsi.reload_native_modules() @@ -203,11 +203,11 @@ def test_delete_nodespace_unlinks_native_module(test_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"]}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"]}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") micropsi.reload_native_modules() diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 9de401ac..0fefaac8 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -54,16 +54,16 @@ def test_user_prompt(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "gatetypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "parameters": ["testparam"],\ - "parameter_defaults": {\ - "testparam": 13\ - }\ - }}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": ["testparam"], + "parameter_defaults": { + "testparam": 13 + } + }}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -298,21 +298,21 @@ def test_node_parameters(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "gatetypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "parameters": ["linktype", "threshold", "protocol_mode"],\ - "parameter_values": {\ - "linktype": ["catexp", "subsur"],\ - "protocol_mode": ["all_active", "most_active_one"]\ - },\ - "parameter_defaults": {\ - "linktype": "catexp",\ - "protocol_mode": "all_active"\ - }}\ - }') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": ["linktype", "threshold", "protocol_mode"], + "parameter_values": { + "linktype": ["catexp", "subsur"], + "protocol_mode": ["all_active", "most_active_one"] + }, + "parameter_defaults": { + "linktype": "catexp", + "protocol_mode": "all_active" + }} + }""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -354,12 +354,12 @@ def test_multiple_nodenet_interference(engine, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "gatetypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc"\ - }}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc" + }}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n node.get_gate('gen').gate_function(17)") diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index 7a950a46..f43f70c0 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -172,12 +172,12 @@ def test_native_module_and_recipe_categories(fixed_nodenet, resourcepath): nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') recipe_file = os.path.join(resourcepath, 'Test', 'Test2', 'recipes.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"]\ - }}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"] + }}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") with open(recipe_file, 'w') as fp: @@ -201,33 +201,33 @@ def test_gate_defaults_change_with_nodetype(fixed_nodenet, resourcepath,): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t",\ - "gate_defaults":{\ - "foo": {\ - "amplification": 13\ - }\ - }}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t", + "gate_defaults":{ + "foo": { + "amplification": 13 + } + }}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") micropsi.reload_native_modules() res, uid = micropsi.add_node(fixed_nodenet, "Testnode", [10, 10], name="Testnode") with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t",\ - "gate_defaults":{\ - "foo": {\ - "amplification": 5\ - }\ - }}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t", + "gate_defaults":{ + "foo": { + "amplification": 5 + } + }}}""") micropsi.reload_native_modules() params = micropsi.nodenets[fixed_nodenet].get_node(uid).get_gate_parameters() assert params["foo"]["amplification"] == 5 @@ -264,17 +264,17 @@ def test_remove_and_reload_native_module(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t",\ - "gate_defaults":{\ - "foo": {\ - "amplification": 13\ - }\ - }}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t", + "gate_defaults":{ + "foo": { + "amplification": 13 + } + }}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -292,18 +292,18 @@ def test_engine_specific_nodetype_dict(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "engine": "theano_engine",\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t",\ - "gate_defaults":{\ - "foo": {\ - "amplification": 13\ - }\ - }}}') + fp.write("""{"Testnode": { + "engine": "theano_engine", + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t", + "gate_defaults":{ + "foo": { + "amplification": 13 + } + }}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -318,18 +318,18 @@ def test_engine_specific_nodetype_theano(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "engine": "dict_engine",\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t",\ - "gate_defaults":{\ - "foo": {\ - "amplification": 13\ - }\ - }}}') + fp.write("""{"Testnode": { + "engine": "dict_engine", + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t", + "gate_defaults":{ + "foo": { + "amplification": 13 + } + }}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -382,16 +382,16 @@ def test_node_parameter_defaults(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "gatetypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "parameters": ["testparam"],\ - "parameter_defaults": {\ - "testparam": 13\ - }\ - }}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": ["testparam"], + "parameter_defaults": { + "testparam": 13 + } + }}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -406,16 +406,16 @@ def test_node_parameters_from_persistence(fixed_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "gatetypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "parameters": ["testparam"],\ - "parameter_defaults": {\ - "testparam": 13\ - }\ - }}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": ["testparam"], + "parameter_defaults": { + "testparam": 13 + } + }}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") micropsi.reload_native_modules() diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index fa1dfaf1..7037a6f0 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -91,12 +91,12 @@ def test_set_node_state(app, test_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t"}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t"}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") @@ -1133,12 +1133,12 @@ def test_reload_native_modules(app, test_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t"}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t"}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") response = app.get_json('/rpc/reload_native_modules()') @@ -1158,12 +1158,12 @@ def test_user_prompt_response(app, test_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t"}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t"}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") response = app.get_json('/rpc/reload_native_modules()') @@ -1325,12 +1325,12 @@ def test_nodenet_data_structure(app, test_nodenet, resourcepath, node): nodetype_file = os.path.join(resourcepath, 'Test', 'nodetypes.json') nodefunc_file = os.path.join(resourcepath, 'Test', 'nodefunctions.py') with open(nodetype_file, 'w') as fp: - fp.write('{"Testnode": {\ - "name": "Testnode",\ - "slottypes": ["gen", "foo", "bar"],\ - "nodefunction_name": "testnodefunc",\ - "gatetypes": ["gen", "foo", "bar"],\ - "symbol": "t"}}') + fp.write("""{"Testnode": { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t"}}""") with open(nodefunc_file, 'w') as fp: fp.write("def testnodefunc(netapi, node=None, **prams):\r\n return 17") response = app.get_json('/rpc/reload_native_modules()') From 39eec871471c4a263160d6be32325259dc7eef1f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 17:28:02 +0200 Subject: [PATCH 193/945] fix recorders --- micropsi_core/nodenet/recorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index d52ea64a..eabadec0 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -143,7 +143,7 @@ def __init__(self, nodenet, group_config={}, name="", uid="", interval=1, first_ self.base_group_name = group_config.pop('group_name', name) if not group_config.get('node_uids', []): - nodes = self._nodenet.netapi.get_nodes(nodespace=self.nodespace, node_name_prefix=group_config['node_name_prefix'], sortby=group_config.get('sortby', 'id')) + nodes = self._nodenet.netapi.get_nodes(nodespace=self.nodespace, node_name_prefix=group_config['node_name_prefix'], sortby=group_config.get('sortby', 'ids')) else: nodes = [self._nodenet.get_node(uid) for uid in group_config['node_uids']] From ede642eb055959ad53b54c4485345312eb57175b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 31 May 2016 17:49:25 +0200 Subject: [PATCH 194/945] initialize sensor/actuator indices with -1 --- micropsi_core/nodenet/theano_engine/theano_node.py | 4 ++-- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 6 ++++-- micropsi_core/nodenet/theano_engine/theano_partition.py | 4 ++-- micropsi_core/tests/test_nodenet_partitions.py | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 4b65ef19..227b3b59 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -269,7 +269,7 @@ def set_parameter(self, parameter, value): sensor_element = self._partition.allocated_node_offsets[self._id] + GEN old_datasource_index = np.where(self._partition.sensor_indices == sensor_element)[0] - self._partition.sensor_indices[old_datasource_index] = 0 + self._partition.sensor_indices[old_datasource_index] = -1 if value not in datasources: self.logger.warning("Datasource %s not known, will not be assigned." % value) return @@ -295,7 +295,7 @@ def set_parameter(self, parameter, value): datatargets = self._nodenet.get_datatargets() actuator_element = self._partition.allocated_node_offsets[self._id] + GEN old_datatarget_index = np.where(self._partition.actuator_indices == actuator_element)[0] - self._partition.actuator_indices[old_datatarget_index] = 0 + self._partition.actuator_indices[old_datatarget_index] = -1 if value not in datatargets: self.logger.warning("Datatarget %s not known, will not be assigned." % value) return diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index c791c859..a25fcb9e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1443,8 +1443,10 @@ def _rebuild_sensor_actor_indices(self, partition=None): else: partitions = self.partitions.values() for partition in partitions: - partition.sensor_indices = np.zeros(len(self.get_datasources()), np.int32) - partition.actuator_indices = np.zeros(len(self.get_datatargets()), np.int32) + partition.sensor_indices = np.empty(len(self.get_datasources()), np.int32) + partition.sensor_indices.fill(-1) + partition.actuator_indices = np.empty(len(self.get_datatargets()), np.int32) + partition.actuator_indices.fill(-1) for datatarget, node_id in self.actuatormap.items(): if not isinstance(node_id, str): node_id = node_id[0] diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 7a7acbed..d93285c4 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1447,11 +1447,11 @@ def delete_node(self, node_id): if type == SENSOR: sensor_index = np.where(self.sensor_indices == offset)[0] - self.sensor_indices[sensor_index] = 0 + self.sensor_indices[sensor_index] = -1 if type == ACTUATOR: actuator_index = np.where(self.actuator_indices == offset)[0] - self.actuator_indices[actuator_index] = 0 + self.actuator_indices[actuator_index] = -1 if type == PIPE: n_function_selector_array = self.n_function_selector.get_value(borrow=True) diff --git a/micropsi_core/tests/test_nodenet_partitions.py b/micropsi_core/tests/test_nodenet_partitions.py index 3cfed643..5d37e411 100644 --- a/micropsi_core/tests/test_nodenet_partitions.py +++ b/micropsi_core/tests/test_nodenet_partitions.py @@ -263,8 +263,8 @@ def test_sensor_actuator_indices(test_nodenet): assert round(actor.get_gate('gen').activation, 3) == 0.8 netapi.delete_node(sensor) netapi.delete_node(actor) - assert set(nodenet.rootpartition.actuator_indices) == {0} - assert set(nodenet.rootpartition.sensor_indices) == {0} + assert set(nodenet.rootpartition.actuator_indices) == {-1} + assert set(nodenet.rootpartition.sensor_indices) == {-1} def test_partition_get_node_data(test_nodenet): From 2999e8911ae91a5593eba67bb719f4ec8762451f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 1 Jun 2016 13:11:37 +0200 Subject: [PATCH 195/945] do not send activation data for inactive nodes --- .../nodenet/dict_engine/dict_nodenet.py | 8 ++++-- .../nodenet/theano_engine/theano_nodenet.py | 16 ++++++++--- micropsi_server/static/js/nodenet.js | 27 ++++++++++--------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index cbc5ca50..00e986a6 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -413,9 +413,13 @@ def get_activation_data(self, nodespace_uids=None, rounded=1): for uid in node_ids: node = self.get_node(uid) if rounded is None: - activations[uid] = [node.get_gate(gate_name).activation for gate_name in node.get_gate_types()] + act = [node.get_gate(gate_name).activation for gate_name in node.get_gate_types()] + if set(act) != {0}: + activations[uid] = act else: - activations[uid] = [round(node.get_gate(gate_name).activation, rounded) for gate_name in node.get_gate_types()] + act = [round(node.get_gate(gate_name).activation, rounded) for gate_name in node.get_gate_types()] + if set(act) != {0}: + activations[uid] = act return activations def delete_node(self, node_uid): diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index a25fcb9e..c461b9d1 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1213,9 +1213,13 @@ def get_activation_data(self, nodespace_uids=[], rounded=1): elements = get_elements_per_type(partition.allocated_nodes[id], self.native_modules) offset = partition.allocated_node_offsets[id] if rounded is None: - activations[node_to_id(id, partition.pid)] = [n.item() for n in partition.a.get_value()[offset:offset+elements]] + act = [n.item() for n in partition.a.get_value()[offset:offset+elements]] + if set(act) != {0}: + activations[node_to_id(id, partition.pid)] = act else: - activations[node_to_id(id, partition.pid)] = [n.item() / mult for n in np.rint(partition.a.get_value()[offset:offset+elements]*mult)] + act = [n.item() / mult for n in np.rint(partition.a.get_value()[offset:offset+elements]*mult)] + if set(act) != {0}: + activations[node_to_id(id, partition.pid)] = act else: for nsuid in nodespace_uids: nodespace = self.get_nodespace(nsuid) @@ -1226,9 +1230,13 @@ def get_activation_data(self, nodespace_uids=[], rounded=1): elements = get_elements_per_type(partition.allocated_nodes[id], self.native_modules) offset = partition.allocated_node_offsets[id] if rounded is None: - activations[node_to_id(id, partition.pid)] = [n.item() for n in partition.a.get_value()[offset:offset+elements]] + act = [n.item() for n in partition.a.get_value()[offset:offset+elements]] + if set(act) != {0}: + activations[node_to_id(id, partition.pid)] = act else: - activations[node_to_id(id, partition.pid)] = [n.item() / mult for n in np.rint(partition.a.get_value()[offset:offset+elements]*mult)] + act = [n.item() / mult for n in np.rint(partition.a.get_value()[offset:offset+elements]*mult)] + if set(act) != {0}: + activations[node_to_id(id, partition.pid)] = act return activations def get_nodetype(self, type): diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 58f71e2d..e5d34b91 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -528,22 +528,25 @@ function setNodespaceDiffData(data, changed){ } } // activations: - for(var uid in data.activations){ - if (uid in nodes){ + for(var uid in nodes){ + activations = false + if(uid in data.activations){ activations = data.activations[uid]; - var gen = 0 - for(var i=0; i < nodes[uid].gateIndexes.length; i++){ - var type = nodes[uid].gateIndexes[i]; - nodes[uid].gates[type].sheaves['default'].activation = activations[i]; - if(type == 'gen'){ - gen = activations[i]; - } + } + var gen = 0 + for(var i=0; i < nodes[uid].gateIndexes.length; i++){ + var type = nodes[uid].gateIndexes[i]; + var gateAct = (activations) ? activations[i] : 0; + nodes[uid].gates[type].sheaves['default'].activation = gateAct; + if(type == 'gen'){ + gen = gateAct; } - nodes[uid].sheaves['default'].activation = gen; - setActivation(nodes[uid]); - redrawNodeLinks(nodes[uid]); } + nodes[uid].sheaves['default'].activation = gen; + setActivation(nodes[uid]); + redrawNodeLinks(nodes[uid]); } + updateModulators(data.modulators); if(data.monitors){ From 79301d8eb3849b212a1fc8bedabd2409cf282f24 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 1 Jun 2016 18:37:31 +0200 Subject: [PATCH 196/945] Introducing force/torque-sync --- micropsi_core/world/vrep/vrep_world.py | 75 ++++++++++++++++++-------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 5cf9f1ff..3de16f1f 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -163,7 +163,7 @@ def get_config_options(): {'name': 'control_type', 'description': 'The type of input sent to the robot', 'default': 'force/torque', - 'options': ["force/torque", "angles", "movements"]}, + 'options': ["force/torque", "force/torque-sync", "angles", "movements"]}, {'name': 'vision_type', 'description': 'Type of vision information to receive', 'default': 'none', @@ -226,7 +226,7 @@ def __init__(self, world, uid=None, **data): self.image.norm.vmin = 0 self.image.norm.vmax = 1 - self.fetch_sensor_and_feedback_values_from_simulation() + self.fetch_sensor_and_feedback_values_from_simulation(None) def get_available_datasources(self): return self.available_datasources @@ -240,6 +240,7 @@ def update_data_sources_and_targets(self): self.datatarget_feedback_values = [0] * len(self.available_datatargets) self.datasource_values = [0] * len(self.available_datasources) + tvals = None restart = self.datatarget_values[self.restart_offset] > 0.9 and self.world.current_step - self.last_restart >= 5 execute = self.datatarget_values[self.execute_offset] > 0.9 @@ -266,18 +267,20 @@ def update_data_sources_and_targets(self): ry = random.uniform(-max_y, max_y) vrep.simxSetObjectPosition(self.world.clientID, self.world.ball_handle, self.world.robot_handle, [rx, ry], vrep.simx_opmode_blocking) - self.fetch_sensor_and_feedback_values_from_simulation() + self.fetch_sensor_and_feedback_values_from_simulation(None) self.last_restart = self.world.current_step return # execute movement, send new target angles if execute: + tvals = [0] * len(self.available_datatargets) self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.world.joints)]) vrep.simxPauseCommunication(self.world.clientID, True) for i, joint_handle in enumerate(self.world.joints): tval = self.current_angle_target_values[i] * math.pi - if self.world.control_type == "force/torque": + if self.world.control_type == "force/torque" or self.world.control_type == "force/torque-sync": tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi + tvals[i] = tval vrep.simxSetJointTargetPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) elif self.world.control_type == "angles": vrep.simxSetJointPosition(self.world.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) @@ -287,7 +290,7 @@ def update_data_sources_and_targets(self): vrep.simxPauseCommunication(self.world.clientID, False) # read joint angle and force values - self.fetch_sensor_and_feedback_values_from_simulation(True) + self.fetch_sensor_and_feedback_values_from_simulation(tvals, True) # read vision data # if no observer present, don't query vision data @@ -304,7 +307,7 @@ def update_data_sources_and_targets(self): return self.image - def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=False): + def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feedback=False): # get data and feedback # read distance value @@ -336,22 +339,48 @@ def fetch_sensor_and_feedback_values_from_simulation(self, include_feedback=Fals self.world.logger.error("No data from vrep received on retry. Giving up and returning no data.") return + movement_finished = False + count = 0 + while not movement_finished: + allgood = True + for i, joint_handle in enumerate(self.world.joints): + angle = 0 + force = 0 + if self.world.control_type == "force/torque" or self.world.control_type == "force/torque-sync": + angle = data[i*2] + force = data[i*2 + 1] + if targets is not None: + target_angle = targets[i] + if abs(abs(angle) - abs(target_angle)) < .001 and include_feedback: + self.datatarget_feedback_values[self.joint_offset + i] = 1 + else: + allgood = False + elif self.world.control_type == "angles": + angle = data[i * 2] + elif self.world.control_type == "movements": + angle = data[i * 2] + self.datasource_values[self.joint_angle_offset + i] = angle / math.pi + self.datasource_values[self.joint_force_offset + i] = force + + movement_finished = allgood + if not movement_finished: + joint_positions = [] + for i, joint_handle in enumerate(self.world.joints): + joint_positions.append(data[i*2]) + + count += 1 + if count == 10: + self.world.logger.warning("Robot did not complete movement in time, giving up."); + self.world.logger.warning("Joint targets: %s" % str(targets)); + self.world.logger.warning("Joint positions: %s" % str(joint_positions)); + movement_finished = True + time.sleep(0.2) + res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.world.clientID, + vrep.sim_object_joint_type, 15, + vrep.simx_opmode_blocking) + self.world.handle_res(res) + if self.world.collision_handle > 0: - res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, vrep.simx_opmode_buffer) + res, collision_state = vrep.simxReadCollision(self.world.clientID, self.world.collision_handle, + vrep.simx_opmode_buffer) self.datasource_values[self.collision_offset] = 1 if collision_state else 0 - - for i, joint_handle in enumerate(self.world.joints): - target_angle = self.datatarget_values[self.joint_offset + i] - angle = 0 - force = 0 - if self.world.control_type == "force/torque": - angle = data[i*2] / math.pi - force = data[i*2 + 1] - if abs(angle) - abs(target_angle) < .001 and include_feedback: - self.datatarget_feedback_values[self.joint_offset + i] = 1 - elif self.world.control_type == "angles": - angle = data[i * 2] / math.pi - elif self.world.control_type == "movements": - angle = data[i * 2] / math.pi - self.datasource_values[self.joint_angle_offset + i] = angle - self.datasource_values[self.joint_force_offset + i] = force From 1025767e42fb4c01a0fc5680fd337da817c61f95 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 2 Jun 2016 14:13:47 +0200 Subject: [PATCH 197/945] fix merge --- micropsi_core/world/vrep/vrep_world.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 290213e5..4a78eda8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -403,13 +403,6 @@ def update_data_sources_and_targets(self): def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feedback=False, initial=False): - res, joint_pos = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) - self.datasource_values[self.tip_position_offset + 0] = joint_pos[0] - self.robot_position[0] - self.datasource_values[self.tip_position_offset + 1] = joint_pos[1] - self.robot_position[1] - self.datasource_values[self.tip_position_offset + 2] = joint_pos[2] - self.robot_position[2] - # get data and feedback - # read distance value - if not self.world.connection_daemon.is_connected: if self.block_runner_if_connection_lost and not initial: while not self.world.connection_daemon.is_connected: @@ -420,6 +413,11 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed if self.world.connection_daemon.clientID != self.clientID: self.get_vrep_data() + res, joint_pos = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) + self.datasource_values[self.tip_position_offset + 0] = joint_pos[0] - self.robot_position[0] + self.datasource_values[self.tip_position_offset + 1] = joint_pos[1] - self.robot_position[1] + self.datasource_values[self.tip_position_offset + 2] = joint_pos[2] - self.robot_position[2] + if self.ball_handle > 0: res, ball_pos = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer) res, joint_pos = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) From e9497ac522c867fffe209a18ef4ebddab7fd3904 Mon Sep 17 00:00:00 2001 From: cknd Date: Thu, 2 Jun 2016 14:24:04 +0200 Subject: [PATCH 198/945] merged vrep_connection_daemon --- micropsi_core/world/vrep/vrep_world.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 4a78eda8..305e3af8 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -478,11 +478,11 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed count += 1 if count == 10: - self.logger.warning("Robot did not complete movement in time, giving up."); - self.logger.warning("Joint targets: %s" % str(targets)); - self.logger.warning("Joint positions: %s" % str(joint_positions)); + self.logger.info("Robot did not complete movement in time, giving up."); + self.logger.info("Joint targets: %s" % str(targets)); + self.logger.info("Joint positions: %s" % str(joint_positions)); movement_finished = True - time.sleep(0.2) + time.sleep(0.1) res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) From d2bd8fda396b1cf86bf1c21792317a4b4b85f48a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 2 Jun 2016 15:28:07 +0200 Subject: [PATCH 199/945] fix --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 4a78eda8..11691506 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -217,7 +217,7 @@ def get_vrep_data(self): if self.collision_handle > 0: res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, vrep.simx_opmode_streaming) else: - self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) + self.logger.warning("Collision handle %s not found, not tracking collisions" % self.world.collision_name) res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) self.handle_res(res) From 7db39872518cfca47ba82a5974f17c845edd6e39 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 2 Jun 2016 17:41:05 +0200 Subject: [PATCH 200/945] add a wrapper for vrep calls handles waiting on empty result, and reconnecting or error, makes sure the expected data is returned. --- micropsi_core/world/vrep/vrep_world.py | 119 +++++++++++-------------- 1 file changed, 53 insertions(+), 66 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 11691506..29a26c01 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -184,8 +184,26 @@ def __init__(self, world, uid=None, **data): def handle_res(self, res): if res != vrep.simx_return_ok: - self.logger.warn("vrep call returned error, reconnecting instance") + self.logger.warn("VREP ERROR") + + def call_vrep(self, method, params, empty_result_ok=False): + res, *data = method(*params) + if res == vrep.simx_return_novalue_flag and not empty_result_ok: + # streaming mode did not return data. wait a bit, try again + self.logger.debug("Did not receive data from vrep, trying again in 500 ms") + time.sleep(0.5) + res, *data = method(*params) + if res != vrep.simx_return_ok and not empty_result_ok: + self.logger.warn("Vrep returned code %d, attempting a reconnect" % res) self.world.connection_daemon.resume() + while not self.world.connection_daemon.is_connected: + sleep(0.2) + return self.call_vrep(method, params) + else: + if len(data) == 1: + return data[0] + else: + return data def on_vrep_connect(self): """ is called by the world, if a connection was established """ @@ -202,56 +220,40 @@ def get_vrep_data(self): self.ball_handle = -1 self.robot_position = [] - res, self.robot_handle = vrep.simxGetObjectHandle(self.clientID, self.world.robot_name, vrep.simx_opmode_blocking) - self.handle_res(res) + self.robot_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, self.world.robot_name, vrep.simx_opmode_blocking]) + if self.robot_handle < 1: self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.world.robot_name) - res, self.joints = vrep.simxGetObjects(self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking) - self.handle_res(res) + self.joints = self.call_vrep(vrep.simxGetObjects, [self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking]) self.logger.info("Found robot with %d joints" % len(self.joints)) if self.world.collision_name: - res, self.collision_handle = vrep.simxGetCollisionHandle(self.clientID, self.world.collision_name, vrep.simx_opmode_blocking) - self.handle_res(res) - if self.collision_handle > 0: - res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, vrep.simx_opmode_streaming) - else: + self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.world.collision_name, vrep.simx_opmode_blocking]) + if self.collision_handle < 1: self.logger.warning("Collision handle %s not found, not tracking collisions" % self.world.collision_name) + else: + self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_streaming], empty_result_ok=True) - res, self.ball_handle = vrep.simxGetObjectHandle(self.clientID, "Ball", vrep.simx_opmode_blocking) - self.handle_res(res) + self.ball_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Ball", vrep.simx_opmode_blocking]) if self.ball_handle < 1: self.logger.warn("Could not get handle for Ball object, distance values will not be available.") else: - res, _ = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, _ = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming) - if res != 0 and res != 1: - self.handle_res(res) - res, robot_position = vrep.simxGetObjectPosition(self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking) - if res != 0 and res != 1: - self.handle_res(res) - self.robot_position = robot_position + self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming], empty_result_ok=True) + self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming], empty_result_ok=True) + self.robot_position = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking]) if self.world.vision_type == "grayscale": - res, self.observer_handle = vrep.simxGetObjectHandle(self.clientID, "Observer", vrep.simx_opmode_blocking) - self.handle_res(res) + self.observer_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Observer", vrep.simx_opmode_blocking]) if self.observer_handle < 1: self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") else: - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming) # _split+4000) - if res != 0 and res != 1: - self.handle_res(res) + resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) + self.vision_resolution = resolution + if len(resolution) != 2: + self.logger.error("Could not determine vision resolution.") else: - time.sleep(1) - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) - self.vision_resolution = resolution - if len(resolution) != 2: - self.logger.error("Could not determine vision resolution after 1 second wait time.") - else: - self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) + self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) self.available_datatargets = [] self.available_datasources = [] @@ -389,17 +391,16 @@ def update_data_sources_and_targets(self): if self.world.vision_type != "grayscale": return - res, resolution, image = vrep.simxGetVisionSensorImage(self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer) - self.handle_res(res) - if len(image): - rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) - rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster - self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() + resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer]) - self.image.set_data(y_image) + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) + rgb_image /= 255. + y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster + self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() - return self.image + self.image.set_data(y_image) + + return self.image def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feedback=False, initial=False): @@ -413,14 +414,15 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed if self.world.connection_daemon.clientID != self.clientID: self.get_vrep_data() - res, joint_pos = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) + joint_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming]) + self.datasource_values[self.tip_position_offset + 0] = joint_pos[0] - self.robot_position[0] self.datasource_values[self.tip_position_offset + 1] = joint_pos[1] - self.robot_position[1] self.datasource_values[self.tip_position_offset + 2] = joint_pos[2] - self.robot_position[2] if self.ball_handle > 0: - res, ball_pos = vrep.simxGetObjectPosition(self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer) - res, joint_pos = vrep.simxGetObjectPosition(self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming) + ball_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer]) + joint_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming]) relative_pos = [0,0] relative_pos[0] = ball_pos[0] - self.robot_position[0] @@ -431,21 +433,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed self.datasource_values[self.ball_position_offset + 0] = relative_pos[0] self.datasource_values[self.ball_position_offset + 1] = relative_pos[1] - res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking) - self.handle_res(res) - - if len(data) == 0: - self.logger.warning("No data from vrep received. Sleeping for 5secs, the retrying.") - time.sleep(1) - - res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.clientID, - vrep.sim_object_joint_type, 15, - vrep.simx_opmode_blocking) - self.handle_res(res) - - if len(data) == 0: - self.logger.error("No data from vrep received on retry. Giving up and returning no data.") - return + joint_ids, something, data, se = self.call_vrep(vrep.simxGetObjectGroupData, [self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking]) movement_finished = False count = 0 @@ -483,12 +471,11 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed self.logger.warning("Joint positions: %s" % str(joint_positions)); movement_finished = True time.sleep(0.2) - res, joint_ids, something, data, se = vrep.simxGetObjectGroupData(self.clientID, + joint_ids, something, data, se = self.call_vrep(vrep.simxGetObjectGroupData, [self.clientID, vrep.sim_object_joint_type, 15, - vrep.simx_opmode_blocking) - self.handle_res(res) + vrep.simx_opmode_blocking]) if self.collision_handle > 0: - res, collision_state = vrep.simxReadCollision(self.clientID, self.collision_handle, - vrep.simx_opmode_buffer) + collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, + vrep.simx_opmode_buffer]) self.datasource_values[self.collision_offset] = 1 if collision_state else 0 From 4a253cbe18927af8b685f7c29f1d59876ae07894 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 3 Jun 2016 11:37:00 +0200 Subject: [PATCH 201/945] fix --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 29a26c01..8f652d73 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -197,7 +197,7 @@ def call_vrep(self, method, params, empty_result_ok=False): self.logger.warn("Vrep returned code %d, attempting a reconnect" % res) self.world.connection_daemon.resume() while not self.world.connection_daemon.is_connected: - sleep(0.2) + time.sleep(0.2) return self.call_vrep(method, params) else: if len(data) == 1: From 3ea5968a4aaf3c2027e7f49bdb6fe607caaae837 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 3 Jun 2016 16:17:22 +0200 Subject: [PATCH 202/945] use resolution from vrep, better logging --- micropsi_core/world/vrep/vrep_world.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 8f652d73..d9d62752 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -190,11 +190,11 @@ def call_vrep(self, method, params, empty_result_ok=False): res, *data = method(*params) if res == vrep.simx_return_novalue_flag and not empty_result_ok: # streaming mode did not return data. wait a bit, try again - self.logger.debug("Did not receive data from vrep, trying again in 500 ms") + self.logger.debug("Did not receive data from vrep when calling %s, trying again in 500 ms" % method.__name__) time.sleep(0.5) res, *data = method(*params) if res != vrep.simx_return_ok and not empty_result_ok: - self.logger.warn("Vrep returned code %d, attempting a reconnect" % res) + self.logger.warning("Vrep returned code %d when calling %s, attempting a reconnect" % (res, method.__name__)) self.world.connection_daemon.resume() while not self.world.connection_daemon.is_connected: time.sleep(0.2) @@ -393,6 +393,7 @@ def update_data_sources_and_targets(self): resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer]) + self.vision_resolution = resolution rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster From 8157d6f910553c4d0b01212442b6af0b353482a1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 3 Jun 2016 16:45:20 +0200 Subject: [PATCH 203/945] fix that pesky race condition induced IndexErrors --- micropsi_core/world/vrep/vrep_world.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index d9d62752..6bd3e4c3 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -181,6 +181,7 @@ def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) self.get_vrep_data() + self.initialized = True def handle_res(self, res): if res != vrep.simx_return_ok: @@ -196,7 +197,8 @@ def call_vrep(self, method, params, empty_result_ok=False): if res != vrep.simx_return_ok and not empty_result_ok: self.logger.warning("Vrep returned code %d when calling %s, attempting a reconnect" % (res, method.__name__)) self.world.connection_daemon.resume() - while not self.world.connection_daemon.is_connected: + self.initialized = False + while not self.world.connection_daemon.is_connected or not self.initialized: time.sleep(0.2) return self.call_vrep(method, params) else: @@ -208,6 +210,7 @@ def call_vrep(self, method, params, empty_result_ok=False): def on_vrep_connect(self): """ is called by the world, if a connection was established """ self.get_vrep_data() + self.initialized = True def get_vrep_data(self): @@ -393,7 +396,6 @@ def update_data_sources_and_targets(self): resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer]) - self.vision_resolution = resolution rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster From b93e37c7024fc80b6a8c0c860af2f810675bb090 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 3 Jun 2016 17:05:59 +0200 Subject: [PATCH 204/945] deliver resource_path to clients in get_nodenet_metadata --- micropsi_core/runtime.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 4d4f73fa..63f76bc3 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -391,7 +391,8 @@ def get_nodenet_metadata(nodenet_uid): 'nodespaces': nodenet.construct_nodespaces_dict(None, transitive=True), 'native_modules': nodenet.get_native_module_definitions(), 'monitors': nodenet.construct_monitors_dict(), - 'rootnodespace': nodenet.get_nodespace(None).uid + 'rootnodespace': nodenet.get_nodespace(None).uid, + 'resource_path': RESOURCE_PATH }) return data From 65b8497f6bcbc3affebe6d2f7059e054bf28482f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 3 Jun 2016 17:31:20 +0200 Subject: [PATCH 205/945] wrap the remaining vrep calls also, adapt the wrapper to void calls --- micropsi_core/world/vrep/vrep_world.py | 47 +++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 2cbf978e..6dfeb04a 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -183,29 +183,29 @@ def __init__(self, world, uid=None, **data): self.get_vrep_data() self.initialized = True - def handle_res(self, res): - if res != vrep.simx_return_ok: - self.logger.warn("VREP ERROR") - def call_vrep(self, method, params, empty_result_ok=False): - res, *data = method(*params) - if res == vrep.simx_return_novalue_flag and not empty_result_ok: + result = method(*params) + code = result if type(result) == int else result[0] + if code == vrep.simx_return_novalue_flag and not empty_result_ok: # streaming mode did not return data. wait a bit, try again self.logger.debug("Did not receive data from vrep when calling %s, trying again in 500 ms" % method.__name__) time.sleep(0.5) - res, *data = method(*params) - if res != vrep.simx_return_ok and not empty_result_ok: - self.logger.warning("Vrep returned code %d when calling %s, attempting a reconnect" % (res, method.__name__)) + result = method(*params) + code = result if type(result) == int else result[0] + if code != vrep.simx_return_ok and not empty_result_ok: + self.logger.warning("Vrep returned code %d when calling %s, attempting a reconnect" % (code, method.__name__)) self.world.connection_daemon.resume() self.initialized = False while not self.world.connection_daemon.is_connected or not self.initialized: time.sleep(0.2) return self.call_vrep(method, params) else: - if len(data) == 1: - return data[0] + if type(result) == int: + return True + if len(result) == 2: + return result[1] else: - return data + return result[1:] def on_vrep_connect(self): """ is called by the world, if a connection was established """ @@ -334,20 +334,19 @@ def update_data_sources_and_targets(self): # simulation restart if restart: - vrep.simxStopSimulation(self.clientID, vrep.simx_opmode_oneshot) + self.call_vrep(vrep.simxStopSimulation, [self.clientID, vrep.simx_opmode_oneshot], empty_result_ok=True) time.sleep(0.5) - res = vrep.simxStartSimulation(self.clientID, vrep.simx_opmode_oneshot) - self.handle_res(res) + self.call_vrep(vrep.simxStartSimulation, [self.clientID, vrep.simx_opmode_oneshot]) time.sleep(0.5) if self.world.randomize_arm == "True": - vrep.simxPauseCommunication(self.clientID, True) + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) for i, joint_handle in enumerate(self.joints): self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] tval = self.current_angle_target_values[i] * math.pi - vrep.simxSetJointPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.clientID, False) + self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) if self.world.randomize_ball == "True": # max_dist = 0.8 @@ -362,7 +361,7 @@ def update_data_sources_and_targets(self): rx = r*np.cos(a) ry = r*np.sin(a) - vrep.simxSetObjectPosition(self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking) + self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking]) self.fetch_sensor_and_feedback_values_from_simulation(None) self.last_restart = self.world.current_step @@ -372,19 +371,19 @@ def update_data_sources_and_targets(self): if execute: tvals = [0] * len(self.available_datatargets) self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.joints)]) - vrep.simxPauseCommunication(self.clientID, True) + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) for i, joint_handle in enumerate(self.joints): tval = self.current_angle_target_values[i] * math.pi if self.world.control_type == "force/torque" or self.world.control_type == "force/torque-sync": tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi tvals[i] = tval - vrep.simxSetJointTargetPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + self.call_vrep(vrep.simxSetJointTargetPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) elif self.world.control_type == "angles": - vrep.simxSetJointPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) + self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) elif self.world.control_type == "movements": tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi - vrep.simxSetJointPosition(self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot) - vrep.simxPauseCommunication(self.clientID, False) + self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) # read joint angle and force values self.fetch_sensor_and_feedback_values_from_simulation(tvals, True) From 019ba100214a4830a02290cf6bdecf635c039d96 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 14:27:20 +0200 Subject: [PATCH 206/945] adjust tests to the non-0-activation delivery --- micropsi_core/tests/test_runtime_nodes.py | 17 +++++++---------- micropsi_server/tests/test_json_api.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index f43f70c0..22c7af43 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -56,26 +56,23 @@ def test_get_nodenet_activation_data(test_nodenet): nodes = prepare_nodenet(test_nodenet) uid = nodes['a'] activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [None]) - assert activation_data["activations"][uid][0] == 0 - assert activation_data["activations"][uid][1] == 0 - assert activation_data["activations"][uid][2] == 0 - assert activation_data["activations"][uid][3] == 0 - assert activation_data["activations"][uid][4] == 0 - assert activation_data["activations"][uid][5] == 0 - assert activation_data["activations"][uid][6] == 0 - + uid not in activation_data["activations"] micropsi.set_node_activation(test_nodenet, nodes['a'], 0.34556865) - activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [None]) assert activation_data["activations"][uid][0] == 0.3 def test_get_nodenet_activation_data_for_nodespace(test_nodenet): nodes = prepare_nodenet(test_nodenet) + netapi = micropsi.nodenets[test_nodenet].netapi uid = nodes['a'] nodespace = micropsi.nodenets[test_nodenet].get_nodespace_uids()[0] activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [nodespace]) - assert activation_data["activations"][uid][0] == 0 + # zero activations are not sent anymore + assert uid not in activation_data["activations"] + netapi.get_node(uid).activation = 0.9 + activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [nodespace]) + assert activation_data["activations"][uid][0] == 0.9 def test_get_nodespace(test_nodenet): diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 7037a6f0..39474b04 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -795,7 +795,18 @@ def test_get_nodespace_activations(app, test_nodenet, node): 'last_call_step': -1 }) assert_success(response) - assert node in response.json_body['data']['activations'] + assert node not in response.json_body['data']['activations'] + response = app.post_json('/rpc/set_node_activation', params={ + 'nodenet_uid': test_nodenet, + 'node_uid': node, + 'activation': -1 + }) + response = app.post_json('/rpc/get_nodespace_activations', params={ + 'nodenet_uid': test_nodenet, + 'nodespaces': [None], + 'last_call_step': -1 + }) + assert response.json_body['data']['activations'][node][0] == -1 def test_get_node(app, test_nodenet, node): From 88ce73d280dfdb1d7d03e4274694421898ed0824 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 14:32:11 +0200 Subject: [PATCH 207/945] add line_number of nodefunction to the native_module data --- micropsi_core/nodenet/node.py | 4 ++++ micropsi_core/tests/test_runtime_nodes.py | 1 + 2 files changed, 5 insertions(+) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 3935550f..49425315 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -601,11 +601,13 @@ def nodefunction_name(self): def nodefunction_name(self, nodefunction_name): import os from importlib.machinery import SourceFileLoader + import inspect self._nodefunction_name = nodefunction_name try: if self.path: module = SourceFileLoader("nodefunctions", self.path).load_module() self.nodefunction = getattr(module, nodefunction_name) + self.line_number = inspect.getsourcelines(self.nodefunction)[1] else: from micropsi_core.nodenet import nodefunctions if hasattr(nodefunctions, nodefunction_name): @@ -633,6 +635,7 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._parameters = [] self._nodefunction_definition = None self._nodefunction_name = None + self.line_number = -1 self.dimensionality = {} self.is_highdimensional = bool(dimensionality) @@ -712,6 +715,7 @@ def get_data(self): 'nodefunction_name': self.nodefunction_name, 'path': self.path, 'category': self.category, + 'line_number': self.line_number, 'is_highdimensional': self.is_highdimensional } if self.is_highdimensional: diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index 22c7af43..bf6d48c2 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -182,6 +182,7 @@ def test_native_module_and_recipe_categories(fixed_nodenet, resourcepath): micropsi.reload_native_modules() res = micropsi.get_available_native_module_types(fixed_nodenet) assert res['Testnode']['category'] == 'Test' + assert res['Testnode']['line_number'] == 1 res = micropsi.get_available_recipes() assert res['testrecipe']['category'] == 'Test/Test2' From 8e984517a27ed5d3fa2b4e5d9937647d7b25f11d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 15:35:59 +0200 Subject: [PATCH 208/945] handle all vrep error cases e.g. do not reconnect if a wrong handle was given, or the call was invalid... --- micropsi_core/world/vrep/vrep_world.py | 46 +++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 6dfeb04a..e70b8585 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -186,26 +186,34 @@ def __init__(self, world, uid=None, **data): def call_vrep(self, method, params, empty_result_ok=False): result = method(*params) code = result if type(result) == int else result[0] - if code == vrep.simx_return_novalue_flag and not empty_result_ok: - # streaming mode did not return data. wait a bit, try again - self.logger.debug("Did not receive data from vrep when calling %s, trying again in 500 ms" % method.__name__) - time.sleep(0.5) - result = method(*params) - code = result if type(result) == int else result[0] - if code != vrep.simx_return_ok and not empty_result_ok: - self.logger.warning("Vrep returned code %d when calling %s, attempting a reconnect" % (code, method.__name__)) - self.world.connection_daemon.resume() - self.initialized = False - while not self.world.connection_daemon.is_connected or not self.initialized: - time.sleep(0.2) - return self.call_vrep(method, params) + if code != vrep.simx_return_ok: + if (code == vrep.simx_return_novalue_flag or code == vrep.simx_return_split_progress_flag) and not empty_result_ok: + # streaming mode did not return data. wait a bit, try again + self.logger.debug("Did not receive data from vrep when calling %s, trying again in 500 ms" % method.__name__) + time.sleep(0.5) + result = method(*params) + code = result if type(result) == int else result[0] + if code == vrep.simx_return_illegal_opmode_flag: + self.logger.error("Illegal opmode for VREP call %s" % method.__name) + if code == vrep.simx_return_remote_error_flag: + self.logger.error("VREP internal error when calling %s. Invalid handle specified?" % method.__name__) + elif code == vrep.simx_return_local_error_flag: + self.logger.error("Client error for VREP call %s" % method.__name) + elif code == vrep.simx_return_initialize_error_flag: + self.logger.error("VREP Simulation is not running") + elif code == vrep.simx_return_timeout_flag or ((code == vrep.simx_return_novalue_flag or code == vrep.simx_return_split_progress_flag) and not empty_result_ok): + self.logger.warning("Vrep returned code %d when calling %s, attempting a reconnect" % (code, method.__name__)) + self.world.connection_daemon.resume() + self.initialized = False + while not self.world.connection_daemon.is_connected or not self.initialized: + time.sleep(0.2) + return self.call_vrep(method, params) + if type(result) == int: + return True + if len(result) == 2: + return result[1] else: - if type(result) == int: - return True - if len(result) == 2: - return result[1] - else: - return result[1:] + return result[1:] def on_vrep_connect(self): """ is called by the world, if a connection was established """ From e28f6d222845503a062ad1184a0012a05ace4cd4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 15:59:47 +0200 Subject: [PATCH 209/945] reset simulation to a defined state at the beginning --- micropsi_core/world/vrep/vrep_world.py | 77 +++++++++++++------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index e70b8585..19118f89 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -181,7 +181,6 @@ def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) self.get_vrep_data() - self.initialized = True def call_vrep(self, method, params, empty_result_ok=False): result = method(*params) @@ -218,7 +217,6 @@ def call_vrep(self, method, params, empty_result_ok=False): def on_vrep_connect(self): """ is called by the world, if a connection was established """ self.get_vrep_data() - self.initialized = True def get_vrep_data(self): @@ -321,7 +319,10 @@ def get_vrep_data(self): self.datasource_values = [0] * len(self.available_datasources) self.datatarget_values = [0] * len(self.available_datatargets) self.datatarget_feedback_values = [0] * len(self.available_datatargets) - self.fetch_sensor_and_feedback_values_from_simulation(None, initial=True) + self.initialized = True + + self.reset_simulation_state() + self.fetch_sensor_and_feedback_values_from_simulation(None) def get_available_datasources(self): return self.available_datasources @@ -342,38 +343,7 @@ def update_data_sources_and_targets(self): # simulation restart if restart: - self.call_vrep(vrep.simxStopSimulation, [self.clientID, vrep.simx_opmode_oneshot], empty_result_ok=True) - time.sleep(0.5) - self.call_vrep(vrep.simxStartSimulation, [self.clientID, vrep.simx_opmode_oneshot]) - time.sleep(0.5) - - if self.world.randomize_arm == "True": - self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) - for i, joint_handle in enumerate(self.joints): - self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) - self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] - tval = self.current_angle_target_values[i] * math.pi - self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) - self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) - - if self.world.randomize_ball == "True": - # max_dist = 0.8 - # rx = random.uniform(-max_dist, max_dist) - # max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) - # ry = random.uniform(-max_y, max_y) - max_dist = 0.8 - min_dist = 0.2 - k = max_dist**2 - min_dist**2 - a = np.random.rand() * 2 * np.pi - r = np.sqrt(np.random.rand() * k + min_dist**2) - rx = r*np.cos(a) - ry = r*np.sin(a) - - self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking]) - - self.fetch_sensor_and_feedback_values_from_simulation(None) - self.last_restart = self.world.current_step - return + return self.reset_simulation_state() # execute movement, send new target angles if execute: @@ -412,10 +382,43 @@ def update_data_sources_and_targets(self): return self.image - def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feedback=False, initial=False): + def reset_simulation_state(self): + self.call_vrep(vrep.simxStopSimulation, [self.clientID, vrep.simx_opmode_oneshot], empty_result_ok=True) + time.sleep(0.5) + self.call_vrep(vrep.simxStartSimulation, [self.clientID, vrep.simx_opmode_oneshot]) + time.sleep(0.5) + + if self.world.randomize_arm == "True": + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) + for i, joint_handle in enumerate(self.joints): + self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) + self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] + tval = self.current_angle_target_values[i] * math.pi + self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) + + if self.world.randomize_ball == "True": + # max_dist = 0.8 + # rx = random.uniform(-max_dist, max_dist) + # max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) + # ry = random.uniform(-max_y, max_y) + max_dist = 0.8 + min_dist = 0.2 + k = max_dist**2 - min_dist**2 + a = np.random.rand() * 2 * np.pi + r = np.sqrt(np.random.rand() * k + min_dist**2) + rx = r*np.cos(a) + ry = r*np.sin(a) + + self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking]) + + self.fetch_sensor_and_feedback_values_from_simulation(None) + self.last_restart = self.world.current_step + + def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feedback=False): if not self.world.connection_daemon.is_connected: - if self.block_runner_if_connection_lost and not initial: + if self.block_runner_if_connection_lost: while not self.world.connection_daemon.is_connected: time.sleep(0.5) else: From 73427da3108321400b3a370c203ee2fd40cb69d4 Mon Sep 17 00:00:00 2001 From: cknd Date: Sat, 4 Jun 2016 16:03:28 +0200 Subject: [PATCH 210/945] removed redundant call to fetch_sensor_.. --- micropsi_core/world/vrep/vrep_world.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 19118f89..ff5a08d5 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -322,7 +322,6 @@ def get_vrep_data(self): self.initialized = True self.reset_simulation_state() - self.fetch_sensor_and_feedback_values_from_simulation(None) def get_available_datasources(self): return self.available_datasources From 49a4004ce93e4d086d1362fdbb1fd704d64a9535 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 18:13:01 +0200 Subject: [PATCH 211/945] add edit-modal for world-config --- micropsi_core/_runtime_api_world.py | 12 +++++-- micropsi_core/world/vrep/vrep_world.py | 2 +- micropsi_server/micropsi_app.py | 30 ++++++++++++----- micropsi_server/static/js/dialogs.js | 6 +++- micropsi_server/view/menu.tpl | 1 + micropsi_server/view/world_form.tpl | 46 +++++++++++++++++--------- 6 files changed, 69 insertions(+), 28 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index 966fa90f..e24a6fad 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -150,12 +150,20 @@ def get_world_view(world_uid, step): return {} -def set_world_properties(world_uid, world_name=None, owner=None): +def set_world_properties(world_uid, world_name=None, owner=None, config=None): """Sets the supplied parameters (and only those) for the world with the given uid.""" if world_uid not in micropsi_core.runtime.worlds: raise KeyError("World not found") micropsi_core.runtime.worlds[world_uid].name = world_name - micropsi_core.runtime.worlds[world_uid].owner = owner + if owner is not None: + micropsi_core.runtime.worlds[world_uid].owner = owner + if config is not None: + micropsi_core.runtime.world_data[world_uid].name = world_name + micropsi_core.runtime.world_data[world_uid].config.update(config) + filename = os.path.join(micropsi_core.runtime.PERSISTENCY_PATH, micropsi_core.runtime.WORLD_DIRECTORY, world_uid) + with open(filename + '.json', 'w+') as fp: + fp.write(json.dumps(micropsi_core.runtime.world_data[world_uid], sort_keys=True, indent=4)) + micropsi_core.runtime.revert_world(world_uid) return True diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index c697953e..3eaad6b2 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -30,7 +30,7 @@ class VREPWorld(World): } def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=None, uid=None, version=1, config={}): - World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version) + World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version, config=config) self.robot_name = config['robot_name'] self.vision_type = config['vision_type'] diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 7962abf7..79df8782 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -640,11 +640,15 @@ def export_world(world_uid): @micropsi_app.route("/world/edit") def edit_world_form(): token = request.get_cookie("token") - id = request.params.get('id', None) - title = 'Edit World' if id is not None else 'New World' + world_uid = request.params.get('id', None) + world = None + if world_uid: + world = runtime.worlds.get(world_uid) + title = 'Edit World' if world is not None else 'New World' worldtypes = runtime.get_available_world_types() return template("world_form.tpl", title=title, worldtypes=worldtypes, + world=world, version=VERSION, user_id=usermanager.get_user_id_for_session_token(token), permissions=usermanager.get_permissions_for_session_token(token)) @@ -653,18 +657,26 @@ def edit_world_form(): @micropsi_app.route("/world/edit", method="POST") def edit_world(): params = dict((key, request.forms.getunicode(key)) for key in request.forms) - type = params['world_type'] + world_uid = params.get('world_uid') + if world_uid: + world_type = runtime.worlds[world_uid].__class__.__name__ + else: + world_type = params['world_type'] config = {} for p in params: - if p.startswith(type + '_'): - config[p[len(type) + 1:]] = params[p] + if p.startswith(world_type + '_'): + config[p[len(world_type) + 1:]] = params[p] user_id, permissions, token = get_request_data() if "manage worlds" in permissions: - result, uid = runtime.new_world(params['world_name'], params['world_type'], user_id, config=config) - if result: - return dict(status="success", msg="World created", world_uid=uid) + if world_uid: + runtime.set_world_properties(world_uid, world_name=params['world_name'], config=config) + return dict(status="success", msg="World changes saved") else: - return dict(status="error", msg=": %s" % result) + result, uid = runtime.new_world(params['world_name'], world_type, user_id, config=config) + if result: + return dict(status="success", msg="World created", world_uid=uid) + else: + return dict(status="error", msg=": %s" % result) return dict(status="error", msg="Insufficient rights to create world") diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index a8d086cc..770072eb 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -328,7 +328,11 @@ $(function() { }); }); }); - $('.navbar a.world_edit').on('click', remote_form); + + $('.navbar a.world_edit').on('click', function(event){ + event.preventDefault(); + dialogs.remote_form_dialog($(event.target).attr("href") + "?id=" + currentWorld); + }); $('.navbar a.world_delete').on('click', function(event){ event.preventDefault(); diff --git a/micropsi_server/view/menu.tpl b/micropsi_server/view/menu.tpl index 9a56e52a..2b2b607e 100644 --- a/micropsi_server/view/menu.tpl +++ b/micropsi_server/view/menu.tpl @@ -40,6 +40,7 @@
    %end + %if world is not None and world.uid: +
    + Attention: This will immediately write the new values, and re-initialize the world. +
    + %end + +
    %if not defined("name_error"): @@ -27,7 +34,7 @@
    %if defined("name_error"): @@ -39,16 +46,18 @@
    - + + + %else: + + + %end % for type in worldtypes: %end @@ -63,16 +72,22 @@ % if param.get('options'): %else: + type="text" value="{{world.config.get(param['name'], '') if world else param.get('default', '')}}" /> %end %if param.get('description'):
    {{param['description']}}
    @@ -84,7 +99,7 @@ %end - %if defined("world"): + %if world is not None: %end @@ -107,5 +122,6 @@ $('#world_type').on('change', function(event){ $('.world_config_'+val).show(); $('.world_docstring').hide(); $('.world_docstring_'+val).show(); -}) +}); +$('#world_type').trigger("change"); \ No newline at end of file From 9061bf32fef0ef455c4d164fd44830b40aeb5c30 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sun, 5 Jun 2016 22:38:15 +0200 Subject: [PATCH 212/945] Ready to run from weird nonstd directories --- configuration.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/configuration.py b/configuration.py index eb147d8d..f6b23b96 100644 --- a/configuration.py +++ b/configuration.py @@ -12,10 +12,13 @@ import configparser import warnings -if os.path.isfile('config.ini'): - filename = 'config.ini' +configini = os.path.dirname(os.path.realpath(__file__)) + "/config.ini" +defaultconfigini = os.path.dirname(os.path.realpath(__file__)) + "/default.config.ini" + +if os.path.isfile(configini): + filename = configini else: - filename = 'config.default.ini' + filename = defaultconfigini try: config = configparser.ConfigParser() with open(filename) as fp: From d84b88832f90054b1a7e65b7db8441c2b1463bc7 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Sun, 5 Jun 2016 22:41:42 +0200 Subject: [PATCH 213/945] Correct default config file name --- configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration.py b/configuration.py index f6b23b96..5f5a0985 100644 --- a/configuration.py +++ b/configuration.py @@ -13,7 +13,7 @@ import warnings configini = os.path.dirname(os.path.realpath(__file__)) + "/config.ini" -defaultconfigini = os.path.dirname(os.path.realpath(__file__)) + "/default.config.ini" +defaultconfigini = os.path.dirname(os.path.realpath(__file__)) + "/config.default.ini" if os.path.isfile(configini): filename = configini From 7755d15aa86c36a006d2b74bc50a356e3069a1f7 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 6 Jun 2016 00:30:39 +0200 Subject: [PATCH 214/945] Introducing startup script for MESH embedding --- micropsi_server/mesh_startup.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 micropsi_server/mesh_startup.py diff --git a/micropsi_server/mesh_startup.py b/micropsi_server/mesh_startup.py new file mode 100644 index 00000000..ac23dacd --- /dev/null +++ b/micropsi_server/mesh_startup.py @@ -0,0 +1,15 @@ + +def mesh_startup(): + + import sys + from os import walk + + path = sys.path.copy() + for p in path: + for root,dirs,files in walk(p): + if p is not root: + sys.path.append(root) + + import micropsi_server.micropsi_app + + micropsi_server.micropsi_app.main("localhost", 6543) \ No newline at end of file From 16917ef0fcc773123782f2b6c1c675794dbfdc39 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 6 Jun 2016 01:02:50 +0200 Subject: [PATCH 215/945] More robust vs. weird directory setups --- micropsi_core/runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 63f76bc3..49f4c1e2 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1632,7 +1632,7 @@ def reload_native_modules(): # load builtins: from micropsi_core.nodenet.native_modules import nodetypes native_modules.update(nodetypes) - operationspath = os.path.abspath('micropsi_core/nodenet/operations/') + operationspath = os.path.dirname(os.path.realpath(__file__)) + '/nodenet/operations/' for file in os.listdir(operationspath): import micropsi_core.nodenet.operations if file != '__init__.py' and not file.startswith('.') and os.path.isfile(os.path.join(operationspath, file)): From 5da163e683f2e2322def98a784effce9f3dbf952 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 6 Jun 2016 15:26:34 +0200 Subject: [PATCH 216/945] set pipe parameters in create_node if given --- micropsi_core/nodenet/theano_engine/theano_partition.py | 4 ++-- micropsi_server/tests/test_json_api.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index d93285c4..f3a69e34 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1344,7 +1344,7 @@ def create_node(self, nodetype, nodespace_id, id=None, parameters=None, gate_par self.allocated_node_offsets[self.allocated_nodespaces_exp_activators[nodespace_id]] if nto.parameter_defaults.get('expectation'): - value = nto.parameter_defaults['expectation'] + value = parameters.get('expectation', nto.parameter_defaults['expectation']) g_expect_array = self.g_expect.get_value(borrow=True) g_expect_array[offset + GEN] = float(value) g_expect_array[offset + SUR] = float(value) @@ -1352,7 +1352,7 @@ def create_node(self, nodetype, nodespace_id, id=None, parameters=None, gate_par self.g_expect.set_value(g_expect_array, borrow=True) if nto.parameter_defaults.get('wait'): - value = nto.parameter_defaults['wait'] + value = parameters.get('wait', nto.parameter_defaults['wait']) g_wait_array = self.g_wait.get_value(borrow=True) g_wait_array[offset + SUR] = int(min(value, 128)) g_wait_array[offset + POR] = int(min(value, 128)) diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 39474b04..02252ebb 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -819,15 +819,17 @@ def test_add_node(app, test_nodenet): app.set_auth() response = app.post_json('/rpc/add_node', params={ 'nodenet_uid': test_nodenet, - 'type': 'Register', + 'type': 'Pipe', 'position': [23, 42, 13], 'nodespace': None, - 'name': 'N2' + 'name': 'N2', + 'parameters': {'wait': 3} }) assert_success(response) uid = response.json_body['data'] response = app.get_json('/rpc/get_node(nodenet_uid="%s",node_uid="%s")' % (test_nodenet, uid)) assert response.json_body['data']['name'] == 'N2' + assert response.json_body['data']['parameters']['wait'] == 3 def test_add_nodespace(app, test_nodenet): From a909cacc4e98e7a600e0747f5285760119deb611 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 6 Jun 2016 15:40:00 +0200 Subject: [PATCH 217/945] make parameters in add_node deal with strings --- micropsi_core/nodenet/theano_engine/theano_partition.py | 4 ++-- micropsi_server/tests/test_json_api.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index f3a69e34..1e098aff 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1344,7 +1344,7 @@ def create_node(self, nodetype, nodespace_id, id=None, parameters=None, gate_par self.allocated_node_offsets[self.allocated_nodespaces_exp_activators[nodespace_id]] if nto.parameter_defaults.get('expectation'): - value = parameters.get('expectation', nto.parameter_defaults['expectation']) + value = int(parameters.get('expectation', nto.parameter_defaults['expectation'])) g_expect_array = self.g_expect.get_value(borrow=True) g_expect_array[offset + GEN] = float(value) g_expect_array[offset + SUR] = float(value) @@ -1352,7 +1352,7 @@ def create_node(self, nodetype, nodespace_id, id=None, parameters=None, gate_par self.g_expect.set_value(g_expect_array, borrow=True) if nto.parameter_defaults.get('wait'): - value = parameters.get('wait', nto.parameter_defaults['wait']) + value = int(parameters.get('wait', nto.parameter_defaults['wait'])) g_wait_array = self.g_wait.get_value(borrow=True) g_wait_array[offset + SUR] = int(min(value, 128)) g_wait_array[offset + POR] = int(min(value, 128)) diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 02252ebb..ffb2173e 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -823,13 +823,13 @@ def test_add_node(app, test_nodenet): 'position': [23, 42, 13], 'nodespace': None, 'name': 'N2', - 'parameters': {'wait': 3} + 'parameters': {'wait': "3"} }) assert_success(response) uid = response.json_body['data'] response = app.get_json('/rpc/get_node(nodenet_uid="%s",node_uid="%s")' % (test_nodenet, uid)) assert response.json_body['data']['name'] == 'N2' - assert response.json_body['data']['parameters']['wait'] == 3 + assert int(response.json_body['data']['parameters']['wait']) == 3 def test_add_nodespace(app, test_nodenet): From 95e443d1a6f928ccb1808dbb6d6f3ef8196d6f65 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 14:27:20 +0200 Subject: [PATCH 218/945] adjust tests to the non-0-activation delivery --- micropsi_core/tests/test_runtime_nodes.py | 17 +++++++---------- micropsi_server/tests/test_json_api.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index f43f70c0..22c7af43 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -56,26 +56,23 @@ def test_get_nodenet_activation_data(test_nodenet): nodes = prepare_nodenet(test_nodenet) uid = nodes['a'] activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [None]) - assert activation_data["activations"][uid][0] == 0 - assert activation_data["activations"][uid][1] == 0 - assert activation_data["activations"][uid][2] == 0 - assert activation_data["activations"][uid][3] == 0 - assert activation_data["activations"][uid][4] == 0 - assert activation_data["activations"][uid][5] == 0 - assert activation_data["activations"][uid][6] == 0 - + uid not in activation_data["activations"] micropsi.set_node_activation(test_nodenet, nodes['a'], 0.34556865) - activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [None]) assert activation_data["activations"][uid][0] == 0.3 def test_get_nodenet_activation_data_for_nodespace(test_nodenet): nodes = prepare_nodenet(test_nodenet) + netapi = micropsi.nodenets[test_nodenet].netapi uid = nodes['a'] nodespace = micropsi.nodenets[test_nodenet].get_nodespace_uids()[0] activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [nodespace]) - assert activation_data["activations"][uid][0] == 0 + # zero activations are not sent anymore + assert uid not in activation_data["activations"] + netapi.get_node(uid).activation = 0.9 + activation_data = micropsi.get_nodenet_activation_data(test_nodenet, [nodespace]) + assert activation_data["activations"][uid][0] == 0.9 def test_get_nodespace(test_nodenet): diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 7037a6f0..39474b04 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -795,7 +795,18 @@ def test_get_nodespace_activations(app, test_nodenet, node): 'last_call_step': -1 }) assert_success(response) - assert node in response.json_body['data']['activations'] + assert node not in response.json_body['data']['activations'] + response = app.post_json('/rpc/set_node_activation', params={ + 'nodenet_uid': test_nodenet, + 'node_uid': node, + 'activation': -1 + }) + response = app.post_json('/rpc/get_nodespace_activations', params={ + 'nodenet_uid': test_nodenet, + 'nodespaces': [None], + 'last_call_step': -1 + }) + assert response.json_body['data']['activations'][node][0] == -1 def test_get_node(app, test_nodenet, node): From 6996f6a56265148753c8f34bddbfa50fe3871312 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 4 Jun 2016 14:32:11 +0200 Subject: [PATCH 219/945] add line_number of nodefunction to the native_module data --- micropsi_core/nodenet/node.py | 4 ++++ micropsi_core/tests/test_runtime_nodes.py | 1 + 2 files changed, 5 insertions(+) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 3935550f..49425315 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -601,11 +601,13 @@ def nodefunction_name(self): def nodefunction_name(self, nodefunction_name): import os from importlib.machinery import SourceFileLoader + import inspect self._nodefunction_name = nodefunction_name try: if self.path: module = SourceFileLoader("nodefunctions", self.path).load_module() self.nodefunction = getattr(module, nodefunction_name) + self.line_number = inspect.getsourcelines(self.nodefunction)[1] else: from micropsi_core.nodenet import nodefunctions if hasattr(nodefunctions, nodefunction_name): @@ -633,6 +635,7 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self._parameters = [] self._nodefunction_definition = None self._nodefunction_name = None + self.line_number = -1 self.dimensionality = {} self.is_highdimensional = bool(dimensionality) @@ -712,6 +715,7 @@ def get_data(self): 'nodefunction_name': self.nodefunction_name, 'path': self.path, 'category': self.category, + 'line_number': self.line_number, 'is_highdimensional': self.is_highdimensional } if self.is_highdimensional: diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index 22c7af43..bf6d48c2 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -182,6 +182,7 @@ def test_native_module_and_recipe_categories(fixed_nodenet, resourcepath): micropsi.reload_native_modules() res = micropsi.get_available_native_module_types(fixed_nodenet) assert res['Testnode']['category'] == 'Test' + assert res['Testnode']['line_number'] == 1 res = micropsi.get_available_recipes() assert res['testrecipe']['category'] == 'Test/Test2' From 3991502d6a9aa66064c437e0c0606a51bb1bbb8d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 18:18:29 +0200 Subject: [PATCH 220/945] offer interface to work with keys, not indexes --- micropsi_core/world/worldadapter.py | 99 ++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index f5bac472..fd028d31 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -138,9 +138,55 @@ class ArrayWorldAdapter(WorldAdapter, metaclass=ABCMeta): """ def __init__(self, world, uid=None, **data): WorldAdapter.__init__(self, world, uid=uid, **data) - self.datasource_values = [0] * len(self.get_available_datasources()) - self.datatarget_values = [0] * len(self.get_available_datatargets()) - self.datatarget_feedback_values = [0] * len(self.get_available_datatargets()) + + self.datasource_names = [] + self.datatarget_names = [] + self.datasource_values = [] + self.datatarget_values = [] + self.datatarget_feedback_values = [] + + def add_datasource(self, name, initial_value=0): + """ Adds a datasource, and returns the index + where they were added""" + self.datasource_names.append(name) + self.datasource_values.append(0) + return len(self.datasource_names) - 1 + + def add_datatarget(self, name, initial_value=0): + """ Adds a datatarget, and returns the index + where they were added""" + self.datatarget_names.append(name) + self.datatarget_values.append(0) + return len(self.datatarget_names) - 1 + + def add_datasources(self, names, initial_values=False): + """ Adds a list of datasources, and returns the indexes + where they were added""" + offset = len(self.datasource_names) + self.datasource_names.extend(names) + self.datasource_values.extend([0] * len(names)) + return range(offset, offset + len(names)) + + def add_datatargets(self, names, initial_values=False): + """ Adds a list of datatargets, and returns the indexes + where they were added""" + offset = len(self.datatarget_names) + self.datatarget_names.extend(names) + self.datatarget_values.extend([0] * len(names)) + self.datatarget_feedback_values.extend([0] * len(names)) + return range(offset, offset + len(names)) + + def get_available_datasources(self): + return self.datasource_names + + def get_available_datatargets(self): + return self.datatarget_names + + def get_datasource_index(self, name): + return self.datasource_names.index(name) + + def get_datatarget_index(self, name): + return self.datatarget_names.index(name) def get_datasource_value(self, key): """allows the agent to read a value from a datasource""" @@ -156,43 +202,48 @@ def add_to_datatarget(self, key, value): index = self.get_available_datasources().index(key) self.datatarget_values[index] += value + def set_datatarget_values(self, values): + """allows the agent to write a list of value to the datatargets""" + self.datatarget_values = values + def get_datatarget_feedback_value(self, key): """get feedback whether the actor-induced action succeeded""" index = self.get_available_datatargets().index(key) return self.datatarget_feedback_values[index] - def get_datatarget_feedback_values(self): - """allows the agent to read all datasource values""" - return self.datatarget_feedback_values - def set_datatarget_feedback(self, key, value): """set feedback for the given datatarget""" index = self.get_available_datatargets().index(key) self.datatarget_feedback_values[index] = value - def set_datatarget_values(self, values): - """allows the agent to write a list of value to the datatargets""" - self.datatarget_values = values + def get_datatarget_feedback_values(self): + """allows the agent to read all datasource values""" + return self.datatarget_feedback_values def reset_datatargets(self): """ resets (zeros) the datatargets """ pass - @abstractmethod - def get_available_datasources(self): - """ - must be implemented by the concrete world adapater and return a list of datasource name strings, - in the same order as values returned by get_datasource_values() - """ - pass + def _set_datasource_value(self, key, value): + self.datasource_values[self.get_datasource_index(key)] = value - @abstractmethod - def get_available_datatargets(self): - """ - must be implemented by the concrete world adapater and return a list of datatarget name strings, - in the same order as values returned by get_datatarget_feedback_values() - """ - pass + def _get_datatarget_value(self, key): + return self.datatarget_values[self.get_datatarget_index(key)] + + def _set_datatarget_feedback_value(self, key, value): + self.datatarget_feedback_values[self.get_datatarget_index(key)] = value + + def _set_datasource_values(self, start_key, values): + idx = self.get_datasource_index(start_key) + self.datasource_values[idx:idx + len(values)] = values + + def _get_datatarget_values(self, start_key, length): + idx = self.get_datatarget_index(start_key) + return self.datatarget_values[idx:idx + length] + + def _set_datatarget_values(self, start_key, values): + idx = self.get_datatarget_index(start_key) + self.datatarget_feedback_values[idx:idx + len(values)] = values @abstractmethod def update_data_sources_and_targets(self): From 8b695fcfd53eacc06dbc44a0985f018b4f55b4a5 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 18:20:38 +0200 Subject: [PATCH 221/945] change the worldadapter subclasses to use keys --- micropsi_core/world/timeseries/timeseries.py | 13 +-- micropsi_core/world/vrep/vrep_world.py | 100 +++++++------------ 2 files changed, 38 insertions(+), 75 deletions(-) diff --git a/micropsi_core/world/timeseries/timeseries.py b/micropsi_core/world/timeseries/timeseries.py index 1b508844..2534e8e5 100644 --- a/micropsi_core/world/timeseries/timeseries.py +++ b/micropsi_core/world/timeseries/timeseries.py @@ -182,17 +182,8 @@ class TimeSeriesRunner(ArrayWorldAdapter): def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) - self.available_datatargets = [] - self.available_datasources = [] - for idx, ID in enumerate(self.world.ids): - self.available_datasources.append(str(ID)) - - def get_available_datasources(self): - return self.available_datasources - - def get_available_datatargets(self): - return self.available_datatargets + self.add_datasource(str(ID)) def update_data_sources_and_targets(self): - self.datasource_values = self.world.state \ No newline at end of file + self.datasource_values = self.world.state diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index ff5a08d5..5d53df87 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -176,10 +176,7 @@ class Robot(ArrayWorldAdapter): block_runner_if_connection_lost = True def __init__(self, world, uid=None, **data): - self.available_datatargets = [] - self.available_datasources = [] super().__init__(world, uid, **data) - self.get_vrep_data() def call_vrep(self, method, params, empty_result_ok=False): @@ -264,51 +261,33 @@ def get_vrep_data(self): else: self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) - self.available_datatargets = [] - self.available_datasources = [] - self.available_datasources.append("ball-distance") - self.available_datasources.append("collision") - self.available_datasources.append("ball-x") - self.available_datasources.append("ball-y") - - self.available_datasources.append("tip-x") - self.available_datasources.append("tip-y") - self.available_datasources.append("tip-z") + self.add_datasource("ball-distance") + self.add_datasource("collision") + self.add_datasource("ball-x") + self.add_datasource("ball-y") - self.available_datatargets.append("restart") - self.available_datatargets.append("execute") + self.add_datasource("tip-x") + self.add_datasource("tip-y") + self.add_datasource("tip-z") - for i in range(len(self.joints)): - self.available_datatargets.append("joint_%s" % str(i + 1)) + self.add_datatarget("restart") + self.add_datatarget("execute") for i in range(len(self.joints)): - self.available_datasources.append("joint_angle_%s" % str(i + 1)) + self.add_datasource("joint_angle_%s" % str(i + 1)) + self.add_datatarget("joint_%s" % str(i + 1)) for i in range(len(self.joints)): - self.available_datasources.append("joint_force_%s" % str(i + 1)) + self.add_datasource("joint_force_%s" % str(i + 1)) self.last_restart = 0 self.current_angle_target_values = np.zeros_like(self.joints) - self.restart_offset = 0 - self.execute_offset = 1 - self.joint_offset = 2 - - self.distance_offset = 0 - self.collision_offset = 1 - self.ball_position_offset = 2 - self.tip_position_offset = self.ball_position_offset + 2 # because ball_x, ball_y - self.joint_angle_offset = self.tip_position_offset + 3 # because tipx tipy tipz - self.joint_force_offset = self.joint_angle_offset + len(self.joints) - if self.world.vision_type == "grayscale": - self.image_offset = self.joint_force_offset + len(self.joints) - self.image_length = self.vision_resolution[0] * self.vision_resolution[1] - for y in range(self.vision_resolution[1]): for x in range(self.vision_resolution[0]): - self.available_datasources.append("px_%d_%d" % (x, y)) + self.add_datasource("px_%d_%d" % (x, y)) self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") self.image.norm.vmin = 0 @@ -316,29 +295,21 @@ def get_vrep_data(self): if self.nodenet: self.nodenet.worldadapter_instance = self - self.datasource_values = [0] * len(self.available_datasources) - self.datatarget_values = [0] * len(self.available_datatargets) - self.datatarget_feedback_values = [0] * len(self.available_datatargets) self.initialized = True self.reset_simulation_state() - def get_available_datasources(self): - return self.available_datasources - - def get_available_datatargets(self): - return self.available_datatargets - def update_data_sources_and_targets(self): old_datasource_values = np.array(self.datasource_values) - self.datatarget_feedback_values = [0] * len(self.available_datatargets) - self.datasource_values = [0] * len(self.available_datasources) + self.datatarget_feedback_values = [0] * len(self.datatarget_values) + self.datasource_values = [0] * len(self.datasource_values) + tvals = None - restart = self.datatarget_values[self.restart_offset] > 0.9 and self.world.current_step - self.last_restart >= 5 - execute = self.datatarget_values[self.execute_offset] > 0.9 + restart = self._get_datatarget_value('restart') > 0.9 and self.world.current_step - self.last_restart >= 5 + execute = self._get_datatarget_value('execute') > 0.9 # simulation restart if restart: @@ -346,19 +317,20 @@ def update_data_sources_and_targets(self): # execute movement, send new target angles if execute: - tvals = [0] * len(self.available_datatargets) - self.current_angle_target_values = np.array(self.datatarget_values[self.joint_offset:self.joint_offset+len(self.joints)]) + tvals = [0] * len(self.datatarget_values) + self.current_angle_target_values = np.array(self._get_datatarget_values('joint_1', len(self.joints))) self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) + joint_angle_offset = self.get_datasource_index("joint_angle_1") for i, joint_handle in enumerate(self.joints): tval = self.current_angle_target_values[i] * math.pi if self.world.control_type == "force/torque" or self.world.control_type == "force/torque-sync": - tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi + tval += (old_datasource_values[joint_angle_offset + i]) * math.pi tvals[i] = tval self.call_vrep(vrep.simxSetJointTargetPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) elif self.world.control_type == "angles": self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) elif self.world.control_type == "movements": - tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi + tval += (old_datasource_values[joint_angle_offset + i]) * math.pi self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) @@ -375,7 +347,7 @@ def update_data_sources_and_targets(self): rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster - self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() + self._set_datasource_values('px_0_0', y_image.flatten()) self.image.set_data(y_image) @@ -390,8 +362,8 @@ def reset_simulation_state(self): if self.world.randomize_arm == "True": self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) for i, joint_handle in enumerate(self.joints): - self.datatarget_values[self.joint_offset + i] = random.uniform(-0.8, 0.8) - self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] + self._set_datatarget_value("joint_%d" % (i + 1), random.uniform(-0.8, 0.8)) + self.current_angle_target_values[i] = self._get_datatarget_value("joint_%d" % (i + 1)) tval = self.current_angle_target_values[i] * math.pi self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) @@ -428,9 +400,9 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed joint_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming]) - self.datasource_values[self.tip_position_offset + 0] = joint_pos[0] - self.robot_position[0] - self.datasource_values[self.tip_position_offset + 1] = joint_pos[1] - self.robot_position[1] - self.datasource_values[self.tip_position_offset + 2] = joint_pos[2] - self.robot_position[2] + self._set_datasource_value('tip-x', joint_pos[0] - self.robot_position[0]) + self._set_datasource_value('tip-y', joint_pos[1] - self.robot_position[1]) + self._set_datasource_value('tip-z', joint_pos[2] - self.robot_position[2]) if self.ball_handle > 0: ball_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer]) @@ -441,9 +413,9 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed relative_pos[1] = ball_pos[1] - self.robot_position[1] dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) - self.datasource_values[self.distance_offset] = dist - self.datasource_values[self.ball_position_offset + 0] = relative_pos[0] - self.datasource_values[self.ball_position_offset + 1] = relative_pos[1] + self._set_datasource_value('ball-distance', dist) + self._set_datasource_value('ball-x', relative_pos[0]) + self._set_datasource_value('ball-y', relative_pos[1]) joint_ids, something, data, se = self.call_vrep(vrep.simxGetObjectGroupData, [self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking]) @@ -460,15 +432,15 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed if targets is not None: target_angle = targets[i] if abs(abs(angle) - abs(target_angle)) < .001 and include_feedback: - self.datatarget_feedback_values[self.joint_offset + i] = 1 + self._set_datatarget_feedback_value("joint_%s" % (i + 1), 1) else: allgood = False elif self.world.control_type == "angles": angle = data[i * 2] elif self.world.control_type == "movements": angle = data[i * 2] - self.datasource_values[self.joint_angle_offset + i] = angle / math.pi - self.datasource_values[self.joint_force_offset + i] = force + self._set_datasource_value("joint_angle_%s" % (i + 1), angle / math.pi) + self._set_datasource_value("joint_force_%s" % (i + 1), force) movement_finished = allgood if not movement_finished: @@ -490,4 +462,4 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed if self.collision_handle > 0: collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_buffer]) - self.datasource_values[self.collision_offset] = 1 if collision_state else 0 + self._set_datasource_value("collision", 1 if collision_state else 0) From 8a233f9dbb2c119f04c8abf78600756767b72d67 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 18:21:25 +0200 Subject: [PATCH 222/945] remove semicolons --- micropsi_core/world/vrep/vrep_world.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 5d53df87..cdd33e3a 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -450,9 +450,9 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed count += 1 if count == 10: - self.logger.info("Robot did not complete movement in time, giving up."); - self.logger.info("Joint targets: %s" % str(targets)); - self.logger.info("Joint positions: %s" % str(joint_positions)); + self.logger.info("Robot did not complete movement in time, giving up.") + self.logger.info("Joint targets: %s" % str(targets)) + self.logger.info("Joint positions: %s" % str(joint_positions)) movement_finished = True time.sleep(0.1) joint_ids, something, data, se = self.call_vrep(vrep.simxGetObjectGroupData, [self.clientID, From f7bded8bb7f0d0239d1689e6e49c089a3e9d31ee Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 18:58:43 +0200 Subject: [PATCH 223/945] more efficient rgb to luminance transformation --- micropsi_core/world/vrep/vrep_world.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index ff5a08d5..ef38f1a6 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -374,7 +374,8 @@ def update_data_sources_and_targets(self): rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. - y_image = np.asarray([.2126 * px[0] + .7152 * px[1] + .0722 * px[2] for px in rgb_image]).astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster + luminance = np.add(rgb_image * np.asarray([.2126, .7152, .0722]), axis=1) + y_image = luminance.astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() self.image.set_data(y_image) From 61abe27cf2cd0df8f35098c8073ad9dcbcba506d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 19:06:03 +0200 Subject: [PATCH 224/945] fix the fix --- micropsi_core/world/vrep/vrep_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index ef38f1a6..cb0bc3c2 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -374,7 +374,7 @@ def update_data_sources_and_targets(self): rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) rgb_image /= 255. - luminance = np.add(rgb_image * np.asarray([.2126, .7152, .0722]), axis=1) + luminance = np.sum(rgb_image * np.asarray([.2126, .7152, .0722]), axis=1) y_image = luminance.astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster self.datasource_values[self.image_offset:len(self.datasource_values)-1] = y_image.flatten() From 69da53c53c3cf1bce98b4a84656da705f06ab241 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 19:14:57 +0200 Subject: [PATCH 225/945] empty datasources/targets list before adding new ones --- micropsi_core/world/vrep/vrep_world.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index cdd33e3a..1d6911e9 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -219,6 +219,12 @@ def get_vrep_data(self): self.clientID = self.world.connection_daemon.clientID + self.datasource_names = [] + self.datatarget_names = [] + self.datasource_values = [] + self.datatarget_values = [] + self.datatarget_feedback_values = [] + self.joints = [] self.vision_resolution = [] self.collision_handle = -1 From 9ca9489d21bc5eeaa3c2b6bd64007727f62ea052 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 8 Jun 2016 19:35:15 +0200 Subject: [PATCH 226/945] fix reverting vrep world --- micropsi_core/world/vrep/vrep_world.py | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index cb0bc3c2..d1534d31 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -257,12 +257,7 @@ def get_vrep_data(self): if self.observer_handle < 1: self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") else: - resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) - self.vision_resolution = resolution - if len(resolution) != 2: - self.logger.error("Could not determine vision resolution.") - else: - self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) + self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) self.available_datatargets = [] self.available_datasources = [] @@ -303,16 +298,22 @@ def get_vrep_data(self): self.joint_force_offset = self.joint_angle_offset + len(self.joints) if self.world.vision_type == "grayscale": - self.image_offset = self.joint_force_offset + len(self.joints) - self.image_length = self.vision_resolution[0] * self.vision_resolution[1] + resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) + if len(resolution) != 2: + self.logger.error("Could not determine vision resolution.") + else: + self.logger.info("Vision resolution is %s" % str(resolution)) + self.vision_resolution = resolution + self.image_offset = self.joint_force_offset + len(self.joints) + self.image_length = self.vision_resolution[0] * self.vision_resolution[1] - for y in range(self.vision_resolution[1]): - for x in range(self.vision_resolution[0]): - self.available_datasources.append("px_%d_%d" % (x, y)) + for y in range(self.vision_resolution[1]): + for x in range(self.vision_resolution[0]): + self.available_datasources.append("px_%d_%d" % (x, y)) - self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") - self.image.norm.vmin = 0 - self.image.norm.vmax = 1 + self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") + self.image.norm.vmin = 0 + self.image.norm.vmax = 1 if self.nodenet: self.nodenet.worldadapter_instance = self From a79002509909793f5635516dc4cedf25fbae8c12 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 9 Jun 2016 19:39:02 +0200 Subject: [PATCH 227/945] first attempt at worldadapter mixins --- micropsi_core/world/vrep/vrep_world.py | 274 +++++++++++++++---------- 1 file changed, 170 insertions(+), 104 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index 5befb3a3..a2ace2e3 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -81,7 +81,7 @@ class VREPWorld(World): - simExtRemoteApiStart(19999) has to have been run - the simulation must have been started """ - supported_worldadapters = ['Robot'] + supported_worldadapters = ['Robot', 'OneBallRobot'] assets = { 'template': 'vrep/vrep.tpl', @@ -171,13 +171,168 @@ def get_config_options(): ] -class Robot(ArrayWorldAdapter): +class WorldAdapterMixin(object): + + @staticmethod + def get_parameters(): + pass + + def initialize(self): + pass + + def update_datasources_and_targets(self): + pass + + def reset_simulation_state(self): + pass + + +class VrepCollisions(WorldAdapterMixin): + + @staticmethod + def get_parameters(): + return [{'name': 'collision_name', + 'default': 'Collision', + 'description': 'The name of the robot\'s collision handle'}] + + def initialize(self): + super().initialize() + self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.world.collision_name, vrep.simx_opmode_blocking]) + if self.collision_handle < 1: + self.logger.warning("Collision handle %s not found, not tracking collisions" % self.world.collision_name) + else: + self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_streaming], empty_result_ok=True) + self.add_datasource("collision") + + + def update_data_sources_and_targets(self): + super().update_data_sources_and_targets() + collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, + vrep.simx_opmode_buffer]) + self._set_datasource_value("collision", 1 if collision_state else 0) + + + +class VrepVision(WorldAdapterMixin): + + def initialize(self): + super().initialize() + self.observer_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Observer", vrep.simx_opmode_blocking]) + if self.observer_handle < 1: + self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") + else: + resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) + self.vision_resolution = resolution + if len(resolution) != 2: + self.logger.error("Could not determine vision resolution.") + else: + self.logger.info("Vision resolution is %s" % str(self.vision_resolution)) + for y in range(self.vision_resolution[1]): + for x in range(self.vision_resolution[0]): + self.add_datasource("px_%d_%d" % (x, y)) + + self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") + self.image.norm.vmin = 0 + self.image.norm.vmax = 1 + + def update_data_sources_and_targets(self): + super().update_data_sources_and_targets() + resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer]) + + rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) + rgb_image /= 255. + luminance = np.sum(rgb_image * np.asarray([.2126, .7152, .0722]), axis=1) + y_image = luminance.astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster + + self._set_datasource_values('px_0_0', y_image.flatten()) + + self.image.set_data(y_image) + + +class VrepOneBallGame(WorldAdapterMixin): + + @staticmethod + def get_parameters(): + return [{'name': 'randomize_ball', + 'description': 'Initialize the ball position randomly', + 'default': 'False', + 'options': ["False", "True"]}] + + def initialize(self): + super().initialize() + self.ball_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Ball", vrep.simx_opmode_blocking]) + if self.ball_handle < 1: + self.logger.warn("Could not get handle for Ball object, distance values will not be available.") + else: + self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming], empty_result_ok=True) + self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming], empty_result_ok=True) + + self.add_datasource("ball-distance") + self.add_datasource("ball-x") + self.add_datasource("ball-y") + + def update_data_sources_and_targets(self): + super().update_data_sources_and_targets() + if self.ball_handle > 0: + ball_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer]) + joint_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming]) + + relative_pos = [0,0] + relative_pos[0] = ball_pos[0] - self.robot_position[0] + relative_pos[1] = ball_pos[1] - self.robot_position[1] + + dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) + self._set_datasource_value('ball-distance', dist) + self._set_datasource_value('ball-x', relative_pos[0]) + self._set_datasource_value('ball-y', relative_pos[1]) + + def reset_simulation_state(self): + super().reset_simulation_state() + if self.world.randomize_ball == "True": + # max_dist = 0.8 + # rx = random.uniform(-max_dist, max_dist) + # max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) + # ry = random.uniform(-max_y, max_y) + max_dist = 0.8 + min_dist = 0.2 + k = max_dist**2 - min_dist**2 + a = np.random.rand() * 2 * np.pi + r = np.sqrt(np.random.rand() * k + min_dist**2) + rx = r*np.cos(a) + ry = r*np.sin(a) + + self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking]) + + +class Robot(WorldAdapterMixin, ArrayWorldAdapter): + """ The basic worldadapter to control a robot in vrep. + Combine this with the Vrep Mixins for a useful robot simulation""" block_runner_if_connection_lost = True + @staticmethod + def get_parameters(): + parameters = [ + {'name': 'robot_name', + 'description': 'The name of the robot object in V-REP', + 'default': 'LBR_iiwa_7_R800', + 'options': ["LBR_iiwa_7_R800", "MTB_Robot"]}, + {'name': 'control_type', + 'description': 'The type of input sent to the robot', + 'default': 'force/torque', + 'options': ["force/torque", "force/torque-sync", "angles", "movements"]}, + {'name': 'randomize_arm', + 'description': 'Initialize the robot arm randomly', + 'default': 'False', + 'options': ["False", "True"]}, + ] + parameters.extend(Collidable.get_params()) + parameters.extend(RobotVision.get_params()) + return parameters + def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) - self.get_vrep_data() + self.initialize() def call_vrep(self, method, params, empty_result_ok=False): result = method(*params) @@ -213,9 +368,9 @@ def call_vrep(self, method, params, empty_result_ok=False): def on_vrep_connect(self): """ is called by the world, if a connection was established """ - self.get_vrep_data() + self.initialize() - def get_vrep_data(self): + def initialize(self): self.clientID = self.world.connection_daemon.clientID @@ -225,47 +380,21 @@ def get_vrep_data(self): self.datatarget_values = [] self.datatarget_feedback_values = [] + super().initialize() + self.joints = [] - self.vision_resolution = [] - self.collision_handle = -1 self.robot_handle = -1 - self.ball_handle = -1 self.robot_position = [] self.robot_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, self.world.robot_name, vrep.simx_opmode_blocking]) if self.robot_handle < 1: self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.world.robot_name) - - self.joints = self.call_vrep(vrep.simxGetObjects, [self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking]) - self.logger.info("Found robot with %d joints" % len(self.joints)) - - if self.world.collision_name: - self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.world.collision_name, vrep.simx_opmode_blocking]) - if self.collision_handle < 1: - self.logger.warning("Collision handle %s not found, not tracking collisions" % self.world.collision_name) - else: - self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_streaming], empty_result_ok=True) - - self.ball_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Ball", vrep.simx_opmode_blocking]) - if self.ball_handle < 1: - self.logger.warn("Could not get handle for Ball object, distance values will not be available.") else: - self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming], empty_result_ok=True) - self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming], empty_result_ok=True) self.robot_position = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking]) - if self.world.vision_type == "grayscale": - self.observer_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Observer", vrep.simx_opmode_blocking]) - if self.observer_handle < 1: - self.logger.warn("Could not get handle for Observer vision sensor, vision will not be available.") - else: - self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) - - self.add_datasource("ball-distance") - self.add_datasource("collision") - self.add_datasource("ball-x") - self.add_datasource("ball-y") + self.joints = self.call_vrep(vrep.simxGetObjects, [self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking]) + self.logger.info("Found robot with %d joints" % len(self.joints)) self.add_datasource("tip-x") self.add_datasource("tip-y") @@ -285,24 +414,6 @@ def get_vrep_data(self): self.current_angle_target_values = np.zeros_like(self.joints) - if self.world.vision_type == "grayscale": - resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) - if len(resolution) != 2: - self.logger.error("Could not determine vision resolution.") - else: - self.logger.info("Vision resolution is %s" % str(resolution)) - self.vision_resolution = resolution - self.image_offset = self.joint_force_offset + len(self.joints) - self.image_length = self.vision_resolution[0] * self.vision_resolution[1] - - for y in range(self.vision_resolution[1]): - for x in range(self.vision_resolution[0]): - self.add_datasource("px_%d_%d" % (x, y)) - - self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") - self.image.norm.vmin = 0 - self.image.norm.vmax = 1 - if self.nodenet: self.nodenet.worldadapter_instance = self self.initialized = True @@ -347,29 +458,12 @@ def update_data_sources_and_targets(self): # read joint angle and force values self.fetch_sensor_and_feedback_values_from_simulation(tvals, True) - # read vision data - # if no observer present, don't query vision data - if self.world.vision_type != "grayscale": - return - - resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_buffer]) - - rgb_image = np.reshape(np.asarray(image, dtype=np.uint8), (self.vision_resolution[0] * self.vision_resolution[1], 3)).astype(np.float32) - rgb_image /= 255. - luminance = np.sum(rgb_image * np.asarray([.2126, .7152, .0722]), axis=1) - y_image = luminance.astype(np.float32).reshape((self.vision_resolution[0], self.vision_resolution[1]))[::-1,:] # todo: npyify and make faster - self._set_datasource_values('px_0_0', y_image.flatten()) - - self.image.set_data(y_image) - - return self.image - def reset_simulation_state(self): self.call_vrep(vrep.simxStopSimulation, [self.clientID, vrep.simx_opmode_oneshot], empty_result_ok=True) time.sleep(0.5) self.call_vrep(vrep.simxStartSimulation, [self.clientID, vrep.simx_opmode_oneshot]) time.sleep(0.5) - + super().reset_simulation_state() if self.world.randomize_arm == "True": self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) for i, joint_handle in enumerate(self.joints): @@ -379,21 +473,6 @@ def reset_simulation_state(self): self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) - if self.world.randomize_ball == "True": - # max_dist = 0.8 - # rx = random.uniform(-max_dist, max_dist) - # max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) - # ry = random.uniform(-max_y, max_y) - max_dist = 0.8 - min_dist = 0.2 - k = max_dist**2 - min_dist**2 - a = np.random.rand() * 2 * np.pi - r = np.sqrt(np.random.rand() * k + min_dist**2) - rx = r*np.cos(a) - ry = r*np.sin(a) - - self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking]) - self.fetch_sensor_and_feedback_values_from_simulation(None) self.last_restart = self.world.current_step @@ -407,7 +486,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed return if self.world.connection_daemon.clientID != self.clientID: - self.get_vrep_data() + self.initialize() joint_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming]) @@ -415,19 +494,6 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed self._set_datasource_value('tip-y', joint_pos[1] - self.robot_position[1]) self._set_datasource_value('tip-z', joint_pos[2] - self.robot_position[2]) - if self.ball_handle > 0: - ball_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_buffer]) - joint_pos = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints)-1], -1, vrep.simx_opmode_streaming]) - - relative_pos = [0,0] - relative_pos[0] = ball_pos[0] - self.robot_position[0] - relative_pos[1] = ball_pos[1] - self.robot_position[1] - - dist = np.linalg.norm(np.array(ball_pos) - np.array(joint_pos)) - self._set_datasource_value('ball-distance', dist) - self._set_datasource_value('ball-x', relative_pos[0]) - self._set_datasource_value('ball-y', relative_pos[1]) - joint_ids, something, data, se = self.call_vrep(vrep.simxGetObjectGroupData, [self.clientID, vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking]) movement_finished = False @@ -470,7 +536,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed vrep.sim_object_joint_type, 15, vrep.simx_opmode_blocking]) - if self.collision_handle > 0: - collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, - vrep.simx_opmode_buffer]) - self._set_datasource_value("collision", 1 if collision_state else 0) + +class OneBallRobot(VrepCollisions, VrepVision, VrepOneBallGame, Robot): + """ A Worldadapter to play the one-ball-reaching-task """ + pass From 619ed1b9479ae805aa0ca6f3b4f7499a93ed3e65 Mon Sep 17 00:00:00 2001 From: cknd Date: Thu, 9 Jun 2016 22:36:41 +0200 Subject: [PATCH 228/945] add a positionable transparent sphere, and put the noeppel down --- micropsi_core/world/vrep/vrep_world.py | 31 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index d1534d31..dd0c3c77 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -227,6 +227,7 @@ def get_vrep_data(self): self.collision_handle = -1 self.robot_handle = -1 self.ball_handle = -1 + self.sphere_handle = -1 self.robot_position = [] self.robot_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, self.world.robot_name, vrep.simx_opmode_blocking]) @@ -252,6 +253,9 @@ def get_vrep_data(self): self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming], empty_result_ok=True) self.robot_position = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking]) + self.sphere_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Sphere", vrep.simx_opmode_blocking]) + print('sphere handle:', str(self.sphere_handle)) + if self.world.vision_type == "grayscale": self.observer_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, "Observer", vrep.simx_opmode_blocking]) if self.observer_handle < 1: @@ -282,6 +286,10 @@ def get_vrep_data(self): for i in range(len(self.joints)): self.available_datasources.append("joint_force_%s" % str(i + 1)) + self.available_datatargets.append("sphere_x") + self.available_datatargets.append("sphere_y") + + self.last_restart = 0 self.current_angle_target_values = np.zeros_like(self.joints) @@ -289,14 +297,18 @@ def get_vrep_data(self): self.restart_offset = 0 self.execute_offset = 1 self.joint_offset = 2 + self.sphere_offset = self.joint_offset + len(self.joints) + self.distance_offset = 0 self.collision_offset = 1 self.ball_position_offset = 2 - self.tip_position_offset = self.ball_position_offset + 2 # because ball_x, ball_y + self.tip_position_offset = self.ball_position_offset + 2 # because ball_x, ball_y self.joint_angle_offset = self.tip_position_offset + 3 # because tipx tipy tipz self.joint_force_offset = self.joint_angle_offset + len(self.joints) + + if self.world.vision_type == "grayscale": resolution, image = self.call_vrep(vrep.simxGetVisionSensorImage, [self.clientID, self.observer_handle, 0, vrep.simx_opmode_streaming]) # _split+4000) if len(resolution) != 2: @@ -307,9 +319,9 @@ def get_vrep_data(self): self.image_offset = self.joint_force_offset + len(self.joints) self.image_length = self.vision_resolution[0] * self.vision_resolution[1] - for y in range(self.vision_resolution[1]): - for x in range(self.vision_resolution[0]): - self.available_datasources.append("px_%d_%d" % (x, y)) + for y in range(self.vision_resolution[1]): + for x in range(self.vision_resolution[0]): + self.available_datasources.append("px_%03d_%03d" % (x, y)) self.image = plt.imshow(np.zeros(shape=(self.vision_resolution[0], self.vision_resolution[1])), cmap="bone") self.image.norm.vmin = 0 @@ -361,6 +373,10 @@ def update_data_sources_and_targets(self): elif self.world.control_type == "movements": tval += (old_datasource_values[self.joint_angle_offset + i]) * math.pi self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) + # position the transparent sphere: + rx,ry = self.datatarget_values[self.sphere_offset:self.sphere_offset+2] + self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.sphere_handle, self.robot_handle, [-rx, -ry], vrep.simx_opmode_oneshot], empty_result_ok=True) + self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) # read joint angle and force values @@ -385,9 +401,9 @@ def update_data_sources_and_targets(self): def reset_simulation_state(self): self.call_vrep(vrep.simxStopSimulation, [self.clientID, vrep.simx_opmode_oneshot], empty_result_ok=True) - time.sleep(0.5) + time.sleep(0.3) self.call_vrep(vrep.simxStartSimulation, [self.clientID, vrep.simx_opmode_oneshot]) - time.sleep(0.5) + time.sleep(0.2) if self.world.randomize_arm == "True": self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) @@ -396,6 +412,9 @@ def reset_simulation_state(self): self.current_angle_target_values[i] = self.datatarget_values[self.joint_offset + i] tval = self.current_angle_target_values[i] * math.pi self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) + # hack: noeppel down + self.call_vrep(vrep.simxSetJointPosition, [self.clientID, self.joints[-2], math.pi/2, vrep.simx_opmode_oneshot], empty_result_ok=True) + # /hack self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) if self.world.randomize_ball == "True": From 4dc172faf91b33f4e65aa5cdc3b15958111e1264 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 10 Jun 2016 17:57:23 +0200 Subject: [PATCH 229/945] make worldadapters configurable, and move all the robot and vrep game configs to the worldadapter --- micropsi_core/nodenet/nodenet.py | 3 +- micropsi_core/runtime.py | 8 +- micropsi_core/world/island/island.py | 3 +- micropsi_core/world/vrep/vrep_world.py | 117 ++++++++---------- micropsi_core/world/world.py | 17 ++- micropsi_core/world/worldadapter.py | 17 ++- micropsi_server/micropsi_app.py | 21 +++- micropsi_server/view/nodenet_form.tpl | 32 ++++- .../view/worldadapter_selector.tpl | 7 ++ 9 files changed, 139 insertions(+), 86 deletions(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 956579ec..30614710 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -70,7 +70,8 @@ def metadata(self): 'version': NODENET_VERSION, 'runner_condition': self._runner_condition, 'use_modulators': self.use_modulators, - 'nodespace_ui_properties': self._nodespace_ui_properties + 'nodespace_ui_properties': self._nodespace_ui_properties, + 'worldadapter_config': {} if not self.worldadapter_instance else self.worldadapter_instance.config } return data diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index f9bb587f..3087777e 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -336,7 +336,7 @@ def load_nodenet(nodenet_uid): logging.getLogger("system").warning("World %s for nodenet %s not found" % (data.world, data.uid)) if world_uid: - result, worldadapter_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet_uid, nodenet_name=data['name']) + result, worldadapter_instance = worlds[world_uid].register_nodenet(worldadapter, nodenet_uid, nodenet_name=data['name'], config=data.get('worldadapter_config', {})) if not result: logging.getLogger('system').warning(worldadapter_instance) worldadapter_instance = None @@ -483,7 +483,7 @@ def unload_nodenet(nodenet_uid): return True -def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template=None, owner="", world_uid=None, uid=None, use_modulators=True): +def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template=None, owner="", world_uid=None, uid=None, use_modulators=True, worldadapter_config={}): """Creates a new node net manager and registers it. Arguments: @@ -510,7 +510,8 @@ def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template= world=world_uid, settings={}, engine=engine, - use_modulators=use_modulators) + use_modulators=use_modulators, + worldadapter_config=worldadapter_config) filename = os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, data['uid'] + ".json") nodenet_data[data['uid']] = Bunch(**data) @@ -1455,6 +1456,7 @@ def parse_definition(json, filename=None): if "worldadapter" in json: result['worldadapter'] = json["worldadapter"] result['world'] = json["world"] + result['worldadapter_config'] = json.get('worldadapter_config', {}) if "world_type" in json: result['world_type'] = json['world_type'] if "settings" in json: diff --git a/micropsi_core/world/island/island.py b/micropsi_core/world/island/island.py index 21878606..c892c16f 100644 --- a/micropsi_core/world/island/island.py +++ b/micropsi_core/world/island/island.py @@ -533,6 +533,5 @@ def _2d_vector_norm(vector): 'type': 'cliff', 'move_efficiency': 1.0, 'agent_allowed': False, - } - + } ) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index a2ace2e3..b20058ef 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -75,6 +75,7 @@ def pause(self): def terminate(self): self.stop.set() + class VREPWorld(World): """ A vrep robot simulator environment In V-REP, the following setup has to be performed: @@ -91,14 +92,6 @@ class VREPWorld(World): def __init__(self, filename, world_type="VREPWorld", name="", owner="", engine=None, uid=None, version=1, config={}): World.__init__(self, filename, world_type=world_type, name=name, owner=owner, uid=uid, version=version) - self.robot_name = config['robot_name'] - self.vision_type = config['vision_type'] - self.control_type = config['control_type'] - self.collision_name = config.get('collision_name', '') - - self.randomize_arm = config['randomize_arm'] - self.randomize_ball = config['randomize_ball'] - self.connection_daemon = VREPConnection(config['vrep_host'], int(config['vrep_port']), connection_listeners=[self]) from micropsi_core.runtime import add_signal_handler @@ -110,15 +103,14 @@ def get_world_view(self, step): 'agents': self.data.get('agents', {}), 'current_step': self.current_step, } - if self.vision_type == "grayscale": - plots = {} - for uid in self.agents: + plots = {} + for uid in self.agents: + if hasattr(self.agents[uid], 'image'): image = self.agents[uid].image - if image: - bio = BytesIO() - image.figure.savefig(bio, format="png") - plots[uid] = base64.encodebytes(bio.getvalue()).decode("utf-8") - data['plots'] = plots + bio = BytesIO() + image.figure.savefig(bio, format="png") + plots[uid] = base64.encodebytes(bio.getvalue()).decode("utf-8") + data['plots'] = plots return data def on_vrep_connect(self): @@ -144,46 +136,35 @@ def get_config_options(): {'name': 'vrep_host', 'default': '127.0.0.1'}, {'name': 'vrep_port', - 'default': 19999}, - {'name': 'robot_name', - 'description': 'The name of the robot object in V-REP', - 'default': 'LBR_iiwa_7_R800', - 'options': ["LBR_iiwa_7_R800", "MTB_Robot"]}, - {'name': 'collision_name', - 'default': 'Collision', - 'description': 'The name of the robot\'s collision handle'}, - {'name': 'control_type', - 'description': 'The type of input sent to the robot', - 'default': 'force/torque', - 'options': ["force/torque", "force/torque-sync", "angles", "movements"]}, - {'name': 'vision_type', - 'description': 'Type of vision information to receive', - 'default': 'none', - 'options': ["none", "grayscale"]}, - {'name': 'randomize_arm', - 'description': 'Initialize the robot arm randomly', - 'default': 'False', - 'options': ["False", "True"]}, - {'name': 'randomize_ball', - 'description': 'Initialize the ball position randomly', - 'default': 'False', - 'options': ["False", "True"]} + 'default': 19999} ] class WorldAdapterMixin(object): + """ Superclass for modular world-adapter extensions that provide + functionality reusable in several worldadapters """ + @staticmethod def get_parameters(): - pass + """ returns an array of parameters that are needed + to configure this mixin """ + return [] + + def __init__(self, world, uid=None, config={}, **kwargs): + super().__init__(world, uid=uid, config=config, **kwargs) + for key in config: + setattr(self, key, config[key]) def initialize(self): + """ Called after a reset of the simulation """ pass - def update_datasources_and_targets(self): + def reset_simulation_state(self): + """ Called on reset """ pass - def reset_simulation_state(self): + def update_datasources_and_targets(self): pass @@ -197,14 +178,13 @@ def get_parameters(): def initialize(self): super().initialize() - self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.world.collision_name, vrep.simx_opmode_blocking]) + self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.collision_name, vrep.simx_opmode_blocking]) if self.collision_handle < 1: - self.logger.warning("Collision handle %s not found, not tracking collisions" % self.world.collision_name) + self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) else: self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_streaming], empty_result_ok=True) self.add_datasource("collision") - def update_data_sources_and_targets(self): super().update_data_sources_and_targets() collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, @@ -212,7 +192,6 @@ def update_data_sources_and_targets(self): self._set_datasource_value("collision", 1 if collision_state else 0) - class VrepVision(WorldAdapterMixin): def initialize(self): @@ -265,7 +244,6 @@ def initialize(self): self.logger.warn("Could not get handle for Ball object, distance values will not be available.") else: self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.ball_handle, -1, vrep.simx_opmode_streaming], empty_result_ok=True) - self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming], empty_result_ok=True) self.add_datasource("ball-distance") self.add_datasource("ball-x") @@ -288,7 +266,7 @@ def update_data_sources_and_targets(self): def reset_simulation_state(self): super().reset_simulation_state() - if self.world.randomize_ball == "True": + if self.randomize_ball == "True": # max_dist = 0.8 # rx = random.uniform(-max_dist, max_dist) # max_y = math.sqrt((max_dist ** 2) - (rx ** 2)) @@ -304,7 +282,7 @@ def reset_simulation_state(self): self.call_vrep(vrep.simxSetObjectPosition, [self.clientID, self.ball_handle, self.robot_handle, [rx, ry], vrep.simx_opmode_blocking]) -class Robot(WorldAdapterMixin, ArrayWorldAdapter): +class Robot(ArrayWorldAdapter, WorldAdapterMixin): """ The basic worldadapter to control a robot in vrep. Combine this with the Vrep Mixins for a useful robot simulation""" @@ -312,7 +290,7 @@ class Robot(WorldAdapterMixin, ArrayWorldAdapter): @staticmethod def get_parameters(): - parameters = [ + return [ {'name': 'robot_name', 'description': 'The name of the robot object in V-REP', 'default': 'LBR_iiwa_7_R800', @@ -326,9 +304,6 @@ def get_parameters(): 'default': 'False', 'options': ["False", "True"]}, ] - parameters.extend(Collidable.get_params()) - parameters.extend(RobotVision.get_params()) - return parameters def __init__(self, world, uid=None, **data): super().__init__(world, uid, **data) @@ -371,7 +346,6 @@ def on_vrep_connect(self): self.initialize() def initialize(self): - self.clientID = self.world.connection_daemon.clientID self.datasource_names = [] @@ -386,14 +360,16 @@ def initialize(self): self.robot_handle = -1 self.robot_position = [] - self.robot_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, self.world.robot_name, vrep.simx_opmode_blocking]) + self.robot_handle = self.call_vrep(vrep.simxGetObjectHandle, [self.clientID, self.robot_name, vrep.simx_opmode_blocking]) if self.robot_handle < 1: - self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.world.robot_name) + self.logger.critical("There seems to be no robot with the name %s in the v-rep simulation." % self.robot_name) else: self.robot_position = self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.robot_handle, -1, vrep.simx_opmode_blocking]) self.joints = self.call_vrep(vrep.simxGetObjects, [self.clientID, vrep.sim_object_joint_type, vrep.simx_opmode_blocking]) + self.call_vrep(vrep.simxGetObjectPosition, [self.clientID, self.joints[len(self.joints) - 1], -1, vrep.simx_opmode_streaming], empty_result_ok=True) + self.logger.info("Found robot with %d joints" % len(self.joints)) self.add_datasource("tip-x") @@ -444,13 +420,13 @@ def update_data_sources_and_targets(self): joint_angle_offset = self.get_datasource_index("joint_angle_1") for i, joint_handle in enumerate(self.joints): tval = self.current_angle_target_values[i] * math.pi - if self.world.control_type == "force/torque" or self.world.control_type == "force/torque-sync": + if self.control_type == "force/torque" or self.control_type == "force/torque-sync": tval += (old_datasource_values[joint_angle_offset + i]) * math.pi tvals[i] = tval self.call_vrep(vrep.simxSetJointTargetPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) - elif self.world.control_type == "angles": + elif self.control_type == "angles": self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) - elif self.world.control_type == "movements": + elif self.control_type == "movements": tval += (old_datasource_values[joint_angle_offset + i]) * math.pi self.call_vrep(vrep.simxSetJointPosition, [self.clientID, joint_handle, tval, vrep.simx_opmode_oneshot], empty_result_ok=True) self.call_vrep(vrep.simxPauseCommunication, [self.clientID, False]) @@ -464,7 +440,7 @@ def reset_simulation_state(self): self.call_vrep(vrep.simxStartSimulation, [self.clientID, vrep.simx_opmode_oneshot]) time.sleep(0.5) super().reset_simulation_state() - if self.world.randomize_arm == "True": + if self.randomize_arm == "True": self.call_vrep(vrep.simxPauseCommunication, [self.clientID, True], empty_result_ok=True) for i, joint_handle in enumerate(self.joints): self._set_datatarget_value("joint_%d" % (i + 1), random.uniform(-0.8, 0.8)) @@ -503,7 +479,7 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed for i, joint_handle in enumerate(self.joints): angle = 0 force = 0 - if self.world.control_type == "force/torque" or self.world.control_type == "force/torque-sync": + if self.control_type == "force/torque" or self.control_type == "force/torque-sync": angle = data[i*2] force = data[i*2 + 1] if targets is not None: @@ -512,9 +488,9 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed self._set_datatarget_feedback_value("joint_%s" % (i + 1), 1) else: allgood = False - elif self.world.control_type == "angles": + elif self.control_type == "angles": angle = data[i * 2] - elif self.world.control_type == "movements": + elif self.control_type == "movements": angle = data[i * 2] self._set_datasource_value("joint_angle_%s" % (i + 1), angle / math.pi) self._set_datasource_value("joint_force_%s" % (i + 1), force) @@ -537,6 +513,15 @@ def fetch_sensor_and_feedback_values_from_simulation(self, targets, include_feed vrep.simx_opmode_blocking]) -class OneBallRobot(VrepCollisions, VrepVision, VrepOneBallGame, Robot): +class OneBallRobot(VrepVision, VrepCollisions, VrepOneBallGame, Robot): """ A Worldadapter to play the one-ball-reaching-task """ - pass + + @classmethod + def get_parameters(cls): + """ I've found no way around this yet """ + parameters = [] + parameters.extend(Robot.get_parameters()) + parameters.extend(VrepCollisions.get_parameters()) + parameters.extend(VrepVision.get_parameters()) + parameters.extend(VrepOneBallGame.get_parameters()) + return parameters diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 9c267628..7e2be366 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -83,6 +83,13 @@ def get_config_options(): """ return [] + @classmethod + def get_suppoerted_worldadapters(cls): + folder = cls.__module__.split('.') + folder.pop() + folder = '.'.join(folder) + return {wacls.__name__: wacls for wacls in tools.itersubclasses(worldadapter.WorldAdapter, folder=folder) if wacls.__name__ in cls.supported_worldadapters} + supported_worldadapters = ['Default'] def __init__(self, filename, world_type="", name="", owner="", uid=None, engine=None, version=WORLD_VERSION, config={}): @@ -238,7 +245,7 @@ def get_world_objects(self, type=None): objects[uid] = obj return objects - def register_nodenet(self, worldadapter, nodenet_uid, nodenet_name=None): + def register_nodenet(self, worldadapter, nodenet_uid, nodenet_name=None, config={}): """Attempts to register a nodenet at this world. Returns True, spawned_agent_instance if successful, @@ -256,7 +263,7 @@ def register_nodenet(self, worldadapter, nodenet_uid, nodenet_name=None): return True, self.agents[nodenet_uid] else: return False, "Nodenet agent already exists in this world, but has the wrong type" - return self.spawn_agent(worldadapter, nodenet_uid, nodenet_name=nodenet_name) + return self.spawn_agent(worldadapter, nodenet_uid, nodenet_name=nodenet_name, config=config) def unregister_nodenet(self, nodenet_uid): """Removes the connection between a nodenet and its incarnation in this world; may remove the corresponding @@ -269,7 +276,7 @@ def unregister_nodenet(self, nodenet_uid): if nodenet_uid in self.data['agents']: del self.data['agents'][nodenet_uid] - def spawn_agent(self, worldadapter_name, nodenet_uid, **options): + def spawn_agent(self, worldadapter_name, nodenet_uid, nodenet_name=None, config={}): """Creates an agent object, Returns True, spawned_agent_instance if successful, @@ -279,8 +286,8 @@ def spawn_agent(self, worldadapter_name, nodenet_uid, **options): self.agents[nodenet_uid] = self.supported_worldadapters[worldadapter_name]( self, uid=nodenet_uid, - name=options.get('nodenet_name', worldadapter_name), - **options) + name=nodenet_name or worldadapter_name, + config=config) return True, self.agents[nodenet_uid] else: self.logger.error("World %s does not support Worldadapter %s" % (self.name, worldadapter_name)) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index fd028d31..1b36d419 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -32,16 +32,23 @@ class WorldAdapter(WorldObject, metaclass=ABCMeta): takes care of translating between the world and these values at each world cycle. """ - def __init__(self, world, uid=None, **data): + @classmethod + def get_parameters(cls): + return [] + + def __init__(self, world, uid=None, config={}, **data): self.datasources = {} self.datatargets = {} self.datatarget_feedback = {} self.datasource_lock = Lock() + self.config = config self.nodenet = None # will be assigned by the nodenet once it's loaded WorldObject.__init__(self, world, category='agents', uid=uid, **data) self.logger = logging.getLogger('agent.%s' % self.uid) if data.get('name'): self.data['name'] = data['name'] + for key in config: + setattr(self, key, config[key]) def initialize_worldobject(self, data): for key in self.datasources: @@ -114,8 +121,8 @@ class Default(WorldAdapter): """ A default Worldadapter, that provides example-datasources and -targets """ - def __init__(self, world, uid=None, **data): - super().__init__(world, uid=uid, **data) + def __init__(self, world, uid=None, config={}, **data): + super().__init__(world, uid=uid, config=config, **data) self.datasources = dict((s, 0) for s in ['static_on', 'random', 'static_off']) self.datatargets = {'echo': 0} self.datatarget_feedback = {'echo': 0} @@ -136,8 +143,8 @@ class ArrayWorldAdapter(WorldAdapter, metaclass=ABCMeta): Engines that bulk-query values, such as the theano_engine, will be faster. Numpy arrays can be passed directly into the engine. """ - def __init__(self, world, uid=None, **data): - WorldAdapter.__init__(self, world, uid=uid, **data) + def __init__(self, world, uid=None, config={}, **data): + WorldAdapter.__init__(self, world, uid=uid, config=config, **data) self.datasource_names = [] self.datatarget_names = [] diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 7962abf7..9bdc0c5f 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -587,12 +587,13 @@ def export_recorders(nodenet_uid): @micropsi_app.route("/nodenet/edit") def edit_nodenet(): user_id, permissions, token = get_request_data() - # nodenet_id = request.params.get('id', None) - title = 'Edit Nodenet' if id is not None else 'New Nodenet' + nodenet_uid = request.params.get('id') + title = 'Edit Nodenet' if nodenet_uid is not None else 'New Nodenet' return template("nodenet_form.tpl", title=title, # nodenet_uid=nodenet_uid, nodenets=runtime.get_available_nodenets(), + worldtypes=runtime.get_available_world_types(), templates=runtime.get_available_nodenets(), worlds=runtime.get_available_worlds(), version=VERSION, user_id=user_id, permissions=permissions) @@ -602,8 +603,22 @@ def edit_nodenet(): def write_nodenet(): user_id, permissions, token = get_request_data() params = dict((key, request.forms.getunicode(key)) for key in request.forms) + worldadapter_name = params['nn_worldadapter'] + wa_params = {} + for key in params: + if key.startswith('worldadapter_%s_' % worldadapter_name): + strip = len("worldadapter_%s_" % worldadapter_name) + wa_params[key[strip:]] = params[key] if "manage nodenets" in permissions: - result, nodenet_uid = runtime.new_nodenet(params['nn_name'], engine=params['nn_engine'], worldadapter=params['nn_worldadapter'], template=params.get('nn_template'), owner=user_id, world_uid=params.get('nn_world'), use_modulators=params.get('nn_modulators', False)) + result, nodenet_uid = runtime.new_nodenet( + params['nn_name'], + engine=params['nn_engine'], + worldadapter=params['nn_worldadapter'], + template=params.get('nn_template'), + owner=user_id, + world_uid=params.get('nn_world'), + use_modulators=params.get('nn_modulators', False), + worldadapter_config=wa_params) if result: return dict(status="success", msg="Nodenet created", nodenet_uid=nodenet_uid) else: diff --git a/micropsi_server/view/nodenet_form.tpl b/micropsi_server/view/nodenet_form.tpl index 15441e3e..be38b92e 100644 --- a/micropsi_server/view/nodenet_form.tpl +++ b/micropsi_server/view/nodenet_form.tpl @@ -123,6 +123,37 @@
    + %for type in worldtypes: + % for name, adapter in worldtypes[type].get_suppoerted_worldadapters().items(): + % for param in adapter.get_parameters(): + + % end + %end + %end + +
    @@ -134,4 +165,3 @@
    - diff --git a/micropsi_server/view/worldadapter_selector.tpl b/micropsi_server/view/worldadapter_selector.tpl index 850d249c..9d336fb9 100644 --- a/micropsi_server/view/worldadapter_selector.tpl +++ b/micropsi_server/view/worldadapter_selector.tpl @@ -27,8 +27,15 @@ $(function(){ var val = el.val(); $('#nn_worldadapter_hint').text(adapters[val]); } + var updateOptions = function(){ + var val = $('#nn_worldadapter').val(); + $('.worldadapter-config').hide(); + $('.worldadapter-'+val).show(); + } + el.on('change', updateOptions) el.on('change', updateDescription); updateDescription(); + updateOptions(); }); %end From 5a7043c727f65b581c23945b142313992584129d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 10 Jun 2016 18:13:08 +0200 Subject: [PATCH 230/945] consistent naming --- micropsi_core/world/vrep/vrep_world.py | 18 +++++++++--------- micropsi_core/world/worldadapter.py | 2 +- micropsi_server/view/nodenet_form.tpl | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index b20058ef..1d63f2a9 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -146,7 +146,7 @@ class WorldAdapterMixin(object): functionality reusable in several worldadapters """ @staticmethod - def get_parameters(): + def get_config_options(): """ returns an array of parameters that are needed to configure this mixin """ return [] @@ -171,7 +171,7 @@ def update_datasources_and_targets(self): class VrepCollisions(WorldAdapterMixin): @staticmethod - def get_parameters(): + def get_config_options(): return [{'name': 'collision_name', 'default': 'Collision', 'description': 'The name of the robot\'s collision handle'}] @@ -231,7 +231,7 @@ def update_data_sources_and_targets(self): class VrepOneBallGame(WorldAdapterMixin): @staticmethod - def get_parameters(): + def get_config_options(): return [{'name': 'randomize_ball', 'description': 'Initialize the ball position randomly', 'default': 'False', @@ -289,7 +289,7 @@ class Robot(ArrayWorldAdapter, WorldAdapterMixin): block_runner_if_connection_lost = True @staticmethod - def get_parameters(): + def get_config_options(): return [ {'name': 'robot_name', 'description': 'The name of the robot object in V-REP', @@ -517,11 +517,11 @@ class OneBallRobot(VrepVision, VrepCollisions, VrepOneBallGame, Robot): """ A Worldadapter to play the one-ball-reaching-task """ @classmethod - def get_parameters(cls): + def get_config_options(cls): """ I've found no way around this yet """ parameters = [] - parameters.extend(Robot.get_parameters()) - parameters.extend(VrepCollisions.get_parameters()) - parameters.extend(VrepVision.get_parameters()) - parameters.extend(VrepOneBallGame.get_parameters()) + parameters.extend(Robot.get_config_options()) + parameters.extend(VrepCollisions.get_config_options()) + parameters.extend(VrepVision.get_config_options()) + parameters.extend(VrepOneBallGame.get_config_options()) return parameters diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 1b36d419..3c2e9c9b 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -33,7 +33,7 @@ class WorldAdapter(WorldObject, metaclass=ABCMeta): """ @classmethod - def get_parameters(cls): + def get_config_options(cls): return [] def __init__(self, world, uid=None, config={}, **data): diff --git a/micropsi_server/view/nodenet_form.tpl b/micropsi_server/view/nodenet_form.tpl index be38b92e..1a5f4e6a 100644 --- a/micropsi_server/view/nodenet_form.tpl +++ b/micropsi_server/view/nodenet_form.tpl @@ -125,7 +125,7 @@ %for type in worldtypes: % for name, adapter in worldtypes[type].get_suppoerted_worldadapters().items(): - % for param in adapter.get_parameters(): + % for param in adapter.get_config_options():
    ' + html.join('') + '
    '); + for(var i in wa.config_options){ + var op = wa.config_options[i]; + $('#nodenet_wa_' + op.name).val((wa.config && wa.config[op.name]) ? wa.config[op.name] : op.default); + } + }; world_selector.on('change', function(){ - get_available_worldadapters(world_selector.val(), function(){ - $('#nodenet_worldadapter').val(nodenet_data.worldadapter); + get_available_worldadapters(world_selector.val(), function(data){ + worldadapter_selector.val(nodenet_data.worldadapter); + update_worldadapter_params(data); }); }); + worldadapter_selector.on('change', function(){ + update_worldadapter_params(); + }); } function showLinkForm(linkUid){ diff --git a/micropsi_server/view/nodenet.tpl b/micropsi_server/view/nodenet.tpl index 84ddc2f2..004ecfc6 100644 --- a/micropsi_server/view/nodenet.tpl +++ b/micropsi_server/view/nodenet.tpl @@ -75,6 +75,9 @@ + + + From d2877913f61d1e3dcfd8fb5025ec67f04e2e7617 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 15 Jun 2016 17:12:23 +0200 Subject: [PATCH 247/945] disable collision mixin if no collision_name given --- micropsi_core/world/vrep/vrep_world.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/micropsi_core/world/vrep/vrep_world.py b/micropsi_core/world/vrep/vrep_world.py index a66e307d..6fbbda06 100644 --- a/micropsi_core/world/vrep/vrep_world.py +++ b/micropsi_core/world/vrep/vrep_world.py @@ -152,18 +152,20 @@ def get_config_options(): def initialize(self): super().initialize() - self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.collision_name, vrep.simx_opmode_blocking]) - if self.collision_handle < 1: - self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) - else: - self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_streaming], empty_result_ok=True) - self.add_datasource("collision") + if self.collision_name: + self.collision_handle = self.call_vrep(vrep.simxGetCollisionHandle, [self.clientID, self.collision_name, vrep.simx_opmode_blocking]) + if self.collision_handle < 1: + self.logger.warning("Collision handle %s not found, not tracking collisions" % self.collision_name) + else: + self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, vrep.simx_opmode_streaming], empty_result_ok=True) + self.add_datasource("collision") def update_data_sources_and_targets(self): super().update_data_sources_and_targets() - collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, - vrep.simx_opmode_buffer]) - self._set_datasource_value("collision", 1 if collision_state else 0) + if self.collision_name: + collision_state = self.call_vrep(vrep.simxReadCollision, [self.clientID, self.collision_handle, + vrep.simx_opmode_buffer]) + self._set_datasource_value("collision", 1 if collision_state else 0) class VrepVision(WorldAdapterMixin): From ea0369d8ef0f97fe1ebdebb3f8b6b6faa156de02 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 15 Jun 2016 17:23:32 +0200 Subject: [PATCH 248/945] fix world view --- micropsi_server/micropsi_app.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index ae278db3..55df02a8 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -161,13 +161,15 @@ def _add_world_list(template_name, **params): response.set_cookie('selected_world', current_world) else: current_world = request.get_cookie('selected_world') - if current_world in worlds and hasattr(worlds[current_world], 'assets'): - world_assets = worlds[current_world].assets + if current_world: + world_obj = runtime.load_world(current_world) + if hasattr(world_obj, 'assets'): + world_assets = world_obj.assets else: world_assets = {} return template(template_name, current=current_world, - mine=dict((uid, worlds[uid]) for uid in worlds if worlds[uid].owner == params['user_id']), - others=dict((uid, worlds[uid]) for uid in worlds if worlds[uid].owner != params['user_id']), + mine=dict((uid, worlds[uid]) for uid in worlds if worlds[uid].get('owner') == params['user_id']), + others=dict((uid, worlds[uid]) for uid in worlds if worlds[uid].get('owner') != params['user_id']), world_assets=world_assets, **params) From 7e21b314d1f2b8b848dad03766c5ff303eae5830 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 17 Jun 2016 14:16:54 +0200 Subject: [PATCH 249/945] fix worldlist --- micropsi_server/micropsi_app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 55df02a8..2323ffc9 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -161,12 +161,11 @@ def _add_world_list(template_name, **params): response.set_cookie('selected_world', current_world) else: current_world = request.get_cookie('selected_world') + world_assets = {} if current_world: world_obj = runtime.load_world(current_world) if hasattr(world_obj, 'assets'): world_assets = world_obj.assets - else: - world_assets = {} return template(template_name, current=current_world, mine=dict((uid, worlds[uid]) for uid in worlds if worlds[uid].get('owner') == params['user_id']), others=dict((uid, worlds[uid]) for uid in worlds if worlds[uid].get('owner') != params['user_id']), From b66c3b0a97a5ba0dd33846e6932a1c465ea5edd2 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 17 Jun 2016 16:02:03 +0200 Subject: [PATCH 250/945] fix treatment of the default world --- micropsi_core/runtime.py | 4 ++-- micropsi_core/world/world.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 61c85f01..af306e1f 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1498,11 +1498,11 @@ def load_definitions(): # create a default world for convenience. uid = tools.generate_uid() filename = os.path.join(PERSISTENCY_PATH, WORLD_DIRECTORY, uid + '.json') - world_data[uid] = Bunch(uid=uid, name="default", version=1, filename=filename) + world_data[uid] = Bunch(uid=uid, name="default", version=1, filename=filename, owner="admin", world_type="DefaultWorld") with open(filename, 'w+') as fp: fp.write(json.dumps(world_data[uid], sort_keys=True, indent=4)) for uid in world_data: - world_data[uid].supported_worldadapters = get_world_class_from_name(world_data[uid].get('world_type', "World")).get_suppoerted_worldadapters() + world_data[uid].supported_worldadapters = get_world_class_from_name(world_data[uid].get('world_type', "DefaultWorld")).get_supported_worldadapters() return nodenet_data, world_data diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 7e2be366..911a6de6 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -342,6 +342,9 @@ def __del__(self): pass +class DefaultWorld(World): + supported_worldadapters = ['Default'] + # imports of individual world types: try: from micropsi_core.world.island import island From e1699bf04f008692290f92297c8df1a138a3db00 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 17 Jun 2016 16:03:16 +0200 Subject: [PATCH 251/945] fix worldadapter delivery to frontend --- micropsi_core/world/world.py | 2 +- micropsi_server/micropsi_app.py | 9 +++++---- micropsi_server/view/nodenet_form.tpl | 2 +- micropsi_server/view/world_form.tpl | 2 +- micropsi_server/view/worldadapter_selector.tpl | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 911a6de6..56e598b8 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -84,7 +84,7 @@ def get_config_options(): return [] @classmethod - def get_suppoerted_worldadapters(cls): + def get_supported_worldadapters(cls): folder = cls.__module__.split('.') folder.pop() folder = '.'.join(folder) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 2323ffc9..3d16df9c 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -728,10 +728,11 @@ def create_new_nodenet_form(): @micropsi_app.route("/create_worldadapter_selector/") def create_worldadapter_selector(world_uid): - nodenets = runtime.get_available_nodenets() - worlds = runtime.get_available_worlds() - return template("worldadapter_selector", world_uid=world_uid, - nodenets=nodenets, worlds=worlds) + return template("worldadapter_selector", + world_uid=world_uid, + nodenets=runtime.get_available_nodenets(), + worlds=runtime.get_available_worlds(), + worldtypes=runtime.get_available_world_types()) @micropsi_app.route("/dashboard") diff --git a/micropsi_server/view/nodenet_form.tpl b/micropsi_server/view/nodenet_form.tpl index 1a5f4e6a..d7fd3e1c 100644 --- a/micropsi_server/view/nodenet_form.tpl +++ b/micropsi_server/view/nodenet_form.tpl @@ -124,7 +124,7 @@ %for type in worldtypes: - % for name, adapter in worldtypes[type].get_suppoerted_worldadapters().items(): + % for name, adapter in worldtypes[type].get_supported_worldadapters().items(): % for param in adapter.get_config_options(): diff --git a/micropsi_server/view/worldadapter_selector.tpl b/micropsi_server/view/worldadapter_selector.tpl index 9d336fb9..71ed994d 100644 --- a/micropsi_server/view/worldadapter_selector.tpl +++ b/micropsi_server/view/worldadapter_selector.tpl @@ -3,7 +3,7 @@ % if not world_uid in worlds: % else: - % for type in sorted(worlds[world_uid].supported_worldadapters.keys()): + % for type in sorted(worldtypes[worlds[world_uid].world_type].get_supported_worldadapters().keys()): % if defined("nodenet_uid") and nodenet_uid in nodenets and nodenets[nodenet_uid].worldadapter == type: % else: @@ -20,7 +20,7 @@ $(function(){ var adapters = {}; %for name in worlds[world_uid].supported_worldadapters: - adapters["{{name}}"] = "{{(worlds[world_uid].supported_worldadapters[name].__doc__ or '').replace('\n', ' ')}}"; + adapters["{{name}}"] = "{{(worldtypes[worlds[world_uid].world_type].get_supported_worldadapters()[name].__doc__ or '').replace('\n', ' ')}}"; %end var el = $('#nn_worldadapter'); var updateDescription = function(){ From 262f8077b116b3d109499a940ec0ff245c22fd40 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 17 Jun 2016 16:03:32 +0200 Subject: [PATCH 252/945] remove cruft --- micropsi_server/micropsi_app.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 3d16df9c..4ec36935 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -717,15 +717,6 @@ def edit_runner_properties(): return template("runner_form", action="/config/runner", value=runtime.get_runner_properties()) -@micropsi_app.route("/create_new_nodenet_form") -def create_new_nodenet_form(): - user_id, permissions, token = get_request_data() - nodenets = runtime.get_available_nodenets() - worlds = runtime.get_available_worlds() - return template("nodenet_form", user_id=user_id, template="None", - nodenets=nodenets, worlds=worlds) - - @micropsi_app.route("/create_worldadapter_selector/") def create_worldadapter_selector(world_uid): return template("worldadapter_selector", From a49ec63c0cc33a9cdf03b5b3a233abe276220b56 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 17 Jun 2016 16:03:43 +0200 Subject: [PATCH 253/945] sort worldadapters --- micropsi_server/view/world_form.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_server/view/world_form.tpl b/micropsi_server/view/world_form.tpl index 1eec1c6b..41866c20 100644 --- a/micropsi_server/view/world_form.tpl +++ b/micropsi_server/view/world_form.tpl @@ -41,7 +41,7 @@
    "; - } - param_table.html(param_html); - $('#edit_worldobject').show(); -} - - -// ------------------------ API Communication --------------------------------------------------- // - -function createWorldObject(type, pos){ - api.call('add_worldobject', {world_uid: currentWorld, type: type, position: [pos.x, pos.y]}, function(result){ - addObject(new WorldObject(result, pos.x, pos.y, 0, '', type, {})); - updateViewSize(); - }); -} - -function deleteWorldObject(worldobject){ - objects[worldobject.uid].representation.remove(); - delete objects[worldobject.uid]; - api.call('delete_worldobject', {'world_uid': currentWorld, 'object_uid': worldobject.uid}, function(){ - dialogs.notification("worldobject deleted"); - }); -} - -function setObjectProperties(worldobject, x, y, name, orientation, parameters){ - if(worldobject.uid in agents){ - return setAgentProperties(worldobject, x, y, name, orientation, parameters); - } - if(x) worldobject.x = x; - if(y) worldobject.y = y; - if(name) worldobject.name = name; - if(orientation) worldobject.orientation = orientation; - if(parameters) worldobject.parameters = parameters; - data = { - world_uid: currentWorld, - uid: worldobject.uid, - position: [worldobject.x, worldobject.y], - name: worldobject.name, - orientation: worldobject.orientation, - parameters: worldobject.parameters || {} - }; - api.call('set_worldobject_properties', data, function(result){ - redrawObject(worldobject); - }, api.defaultErrorCallback); -} - -function setAgentProperties(worldobject, x, y, name, orientation, parameters){ - if(x) worldobject.x = x; - if(y) worldobject.y = y; - if(name) worldobject.name = name; - if(orientation) worldobject.orientation = orientation; - if(parameters) worldobject.parameters = parameters; - data = { - world_uid: currentWorld, - uid: worldobject.uid, - position: [worldobject.x, worldobject.y], - name: worldobject.name, - orientation: worldobject.orientation, - parameters: worldobject.parameters || {} - }; - api.call('set_worldagent_properties', data, function(result){ - redrawObject(worldobject); - }, api.defaultErrorCallback); -} diff --git a/micropsi_server/static/island/island.tpl b/micropsi_server/static/island/island.tpl deleted file mode 100644 index bb19a333..00000000 --- a/micropsi_server/static/island/island.tpl +++ /dev/null @@ -1,33 +0,0 @@ -
    -
    - -
    - -
    -
    -

    World Status

    - -
    -
    -

    Scene Viewer

    -

    - - -

    -
    -
    -
    -

    Agents

    -
    -
    -
    -

    World Objects

    -
    - - -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/micropsi_server/static/island/juniper-berries.png b/micropsi_server/static/island/juniper-berries.png deleted file mode 100644 index 32bfbde9c67e1509787796a71a8386864e81efba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54112 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

    TPbjW13s1KFIvUasJLv{4n9*eWceG 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 a=gsdosc1BR$>ZL@e?W4ovQ^T?!T$#^w(Z>j diff --git a/micropsi_server/static/island/palm-tree.png b/micropsi_server/static/island/palm-tree.png deleted file mode 100644 index a1f261ff82091a2f6a68115dc8b8ba3f391f9f7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57205 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

    6aF`UtVQ54uo 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

    Sm-M$~%=61(m*HiGn6Z`?f>0(w!oD{Hl%C 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 Uo1G$|8vuV=YPzb`80+x=0fDJ=EC2ui diff --git a/micropsi_server/static/island/psi_1.png b/micropsi_server/static/island/psi_1.png deleted file mode 100644 index d5b5be5686e15cd8d0b23cc2a96968cf93b69751..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1093786 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

    dBy8@$_PS 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�(Omz)xbO#uQ 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�RP6Z$E<~oh+b(lBk|vrV{_+Yo8~?}vOTp2 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 z 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͎P}=Tf_Kj_;7V^iPv=T_UB32 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�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

    to4ZsRjcAj~@@Vd475bXNHAR?b_fY?TndP?wx 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 miMT)s%(G}6?jRYRG=V`naQ_5u@8a0 z1v^eX0ZvLXworAuSQK>nIVWP-PPjcE3{Ayb?s4N zeIVwCn5`oJJcc)qF*ZN;%L;_#?eQl_&RnDvs*OD7)r-9{#EG9#)|B&GOamsSp8$h*dFJhwr893nyS%>y(HXHyCjv$@F}KQqLA`^kIPb=8jX4UaWjMA!a9Lkt8ZzM-)fE<4oQLGV}lBB7BS0HPur*W zWT;5fI(y;Pckp@mr>x(ak^56#ZW&UhvyX~&6nm8MCBkedR@tEd zm<~ey0-$VPBGr6vD&K&Y)Uz0l-NPs3MvI{_oi%19X8~Zp1M*Y=l<4K=R!WPNE>En{ z5l3uJQ9x*$jKJAC2w8%5_KyPu&G2i+c}}1XZkpC_zKMaZhjGf~ z6GBCL#ky=&-q&)2`=aYe=&79B4Qo%e6}SdfoT-;&J~-dcLg@7uuuBuwg5Eb&&0IW- z%NFJYfZ@(Vf!k?)^AgQX$ui>5^?aApSv6 zNVdhzF;`2@%;DZd{L8hRbCyhEy(rd~ zQ?9?&I~+z!l~NMOm$Vbmvb@6|h!3h=OU6chLVHzp=U(-|dk-NU0kx4&UYi%wJ;CJ? z_Nv$dcRqW+E553>8)N3TvoX0mtSL>={>#^4bSC51&c7=yUD+GT$iwL1&-rRenSbmT z!gO7pe@MkCQX=7LIybJAZUPaES(#&NvPwj8&+9bYF6pBEH&)%2sknkxn7aITJB~{r zW9|v|zc%eCO-3c$U8{ZTc(rP}Qb-1rxs6yKk-pT zRDwD;_eOAnmVX*(gA*$pR8)ZaeQ4UZB!S!Zk6;4sN2%rTTy_aY!|{z<*kuBrXskWZ z?a+X>A|)sD39zuk{L3LdTqz@Yo=_(EeTx#Q+R>~)RK1jic^Q_g4y`~}E&KTc&pP^U zZgm6GVEQZ_smg({?#@<+?d>&};;vlGR|*2cOOstUL+;J~nZJe#tF5juBqI^4zto_< zixzK0Yk!Jzf+O2efw8MJV@Hg0b#*KR<)cDjn28fup`TL75cviY5oI4-uy zV2G)pA3Nez_HM*Nnrdpq^^pi4{T3{|^&fJvj{cZSo;J7z9C%FCpR-G;RR7v(8g>?+ z%1|pnuZd;dw;)j(h~Ulap9%UZQw=z8_SZmXe^)=w~9jPt5n`G>< z=hqiiOe*aNiv{UkgZsSSylTRHE=>2|MQ+6}&z&7Z`9CgczjKn&WA2}0(3_BW6Kt|V65)BkgILQi(y-1 z0^Z~IIO7Y6H@TVjWHg9}i86+~p3u7*>=u?Lcen>8(wv`%9=k`sINa;xDmP7s%h z`Bb~+T^kO<@C(+;+dbiL>$Mard5*s9^{DaVj|TmOL*fYqB>dg`mx2V_A1YW-^d-_J zCag(0+Vj#*)+}m5Win8JFy8I$AiN%-b~yUh?Qf6?>EASu2QBT93wr%#9Rpjhbs@Xc z@FIPIrXYY>j` zat{0*Ctr#sj}i;*}*e&qwP{j+{%8of5vS z3!HSv`5?4RZL^z*qn-$Tq=7R3S?lgwDecFHHPB#~#jw2Ztv}%TrlvLZ%xl+5!=b#u zZeZEkIPI0;cUIl|`)??)?*9dd4>24vzBKRZ@?payK9o`*zHq^xwAJ6l=v{?l5)!Sx zZpzXOhw49+WYwfcBbS30Kw1I01;Dpia|MtLJ6a-{OfIW zPc_zj+S?_Mc=UJe{?0u&&-jZ7O{ph_)NWd+p7R2!uu=UKS-b&J+IZk^1>w@& zLU+LYPk2Emt7Iqa*wTHIW+v+66NztRkp@2>`jEABk1TI3)j$45_+LziXzrId6q_Pf zxxWUI*pgUBeHkdPdnUGVmG>VTqE%}>tj5%iPIJX&Q?Jf1*8qUv|FaTeaC=wKK3d%; zFao4a2cJ0+#HteHD{qqu4S*UvnE}PqAJ`TJ*1aU@c~ezfg{H*cSD&l64cB>`53CZgRu~*i(7R-;jFHLH&TD~XUZ~ZRf)qR8nM7tWuazofR@V3=n$_z+C(Sz@{3-Eb-gH1plao_`nxoohIIxTAGknGUO(XsoJk|C; zKLX~-J>E1#Nz#QrtbBhk5Bzn}{CfVckM~=vaHYm3loO#@L$r6fROCPbjJXrkjc+KR zryp7Xd=}Qe`5eS9Bcq2t8!a3GlY1AW6AgsgNoEuHETP@4TUM^u2yEsBBAhx1=DdAH zHO1C7HEV=Un`4bRUY*Ry-Ucwo&f)Q}r8Y@qe0U201D=FxI52(;zyNU-5grSDwMI(P z@EDEFIfrVH?T&xY7TlLd7hE52xNgulw6uo{MsM72KZ|@_?Br;!q!ti3Y7>)cg%6^MnpqRI6lAiSbjo)PH0 zBe3EKl|6yiAaM%KJ7Cj*5}Ac1OhzAV#naBWEl>R2#1~Yezuq4%ylZMvN<}jBgP?*T z4Iyu%6)-^0$Xv)8eDN0f#Rw7I-Q8;;tN%o7|Fzua%f1}MuE2UgSjYM%z9hLJ-f|{l zupYc;Km0sgb^7C4I9?qrJX3$PCqoWZG8uA?#BgNZ~{6S z4r0OcwOleo>oyTmfkkS5?6}+Y%(u#a^sVQbT_w~Ry$!i!YQQe?vU&^%$v>L=NB9H5 z0HyCvD9tyb&$TWUPKHQ#GMXUD$mRX{F(_{#MWjffhFyzL40nlUC_(T_5DjU z;zLZOaji}Z{VXE^fkqekQrt(M%UJpO0DsSp#wCbk=Z+g_JPJ?dSx&TB!I6}WaMp_r z&AuR~l9pRMP7odCU#~^PZf&?T*#SjESk`d8E6wrVTUP7)Lbr1coUWAsR6_jTWlVs- zuiDc94iQA@`RJ{}ouNMH`CL9AVp>NsA+Ws7n+j< zscJqvZbm8giWAT~FFjScj(N7Zg$}?{z%uWE6(`!JTUn+QuNY+j2R&r2k>QJot}56d zfj%z&Gp&bc0V1yeNR+;1LDG&ao9*19$}38x1)9Fxf{#7$?au2UFIhu7p=$vomynB{132ifa~m+im9xNte#mhpd9>G{`PU*Se-Sj?ZMq5izp zZ{F4XdGN$@FibHtXX9s3{7Zgcr52M;g@h_gnB`aJ7m!B^5GEeK`0-?t=?C2_q)yTR zj#X*}96Mst+e!6WN}C(2#-$jTLk*3$L!{AJyd*$$u(u5xV9GI1JdljcvjS817YFC= zikE52JOzjL-!Yw{^)EGT{l#%G!`{F!D*I7a1cJ8RZQRv>7DP_Bwtt{yV&ACo^%LpC zMhQtKY^H}Vr(%kL24B&-R%F(x4^(xW@5n<+q9oxtR=z4bqmmWWo10Y7$PBN#>38-K zc9yei2x80}onqZR{sgKNxBcayV(L$ID{puamLue^x+v8!GlAtCOKhu_^ea@_=v@d8 zNG8cXTd){dZ^g@YK4`A=QgT|E+dWRsuTC2T)NcuDwG--+8cvz8(yO(>7F_lPx6&7E z42&Sf!mUfe>K!u*u+wG_8{#2RkYR{ECGln;!Mkp`pBDBp(8NhZ_uB6M??6Bj&fT6& zpko5apXx3GQ{%%umJYi6X@SRwa=2C$Ruxay&+v+O^78NwrWS-$inzV+1G4?S#qh)# z#sf}&IHqfM9KKDSuc1Ft;fUh~XA5i5$G13ywrAigb3shf191Xn>mf0pO-$a})zGC) zin)lCARPZWWaQQ5Eh!;V>4QQ6QbHbrdXq%NinTukQ47H z;HD8i&YCl_;O>IDXFjV#ulV5KPz68my7zo}QhOA1n!7bRd({8szSB;v;h&-2|D*(X zhwyEg`>qCy5E8O9yRM3+eKGp+{11y~<)nWao5R>~tpxCYofZ~$!^i%eMjX$pFbc9f z4Q7-2GOE7y6lo)RFe49q=n-*0F5eg@p+i}s#=8I`TRPgl_X;9qg9GBie=Y$U-~g93 zV0yGUdv|kiq}nv?ebO$(((>sj*Plj($6dY5nr~wR_)*_dDcP}BD8T+Y z^ZhlF?AFAbs5AYX{IDXO!I2-1bqS*aO>l16MU)ZjszxLp9psJGPp!T}g~X4S;7hUq zz^f0vb;N+}JhE3DBqKrxkj4Yvk`0u#J=9uAC5F02A<~s9-sQ6o<%ZCka|hhuWB<(FH|FXVHH?* zUih2t-zQ$m@sY%yPgc;B`^Ti-y^*pM|7HBy_v&KL=!cWG#u7>Gyy2ie7-rX0&k7Z5 zZDl{0*}wjp3?9(NhEZVDkjjk30uUZlt*DtwlWX1>usk+w&yf`?wuj6z*t)o<9qW+` zU^V3924pyi^7X!bzHaw8NW{wbTX`vv^O6~ZR&iTNM`bQweo$97&Le^$;R4-Kx$N5^ zIcKlz0{ZO5xRkt++L_ojCOz@@&>HfEMQ3H*Hz+%T`~qsfrU+z*7WUL+ zUfgvzTr+Po%(pywGr`9IIVd=s7x^FOr(jC^k^Hs8_{Bdr+Kk|5LNt% zg8|{(4?snPMx21bs*|)!lA9LM#NUH*oTQq8yYvhK)ghHv&P#|aSL)R}Me)m==+htl zcjgHxoRJ2lY(Re9$6zVl_D|-J9d2wucM%~4hBm)2BwDk<4h?}8-o7LN%E$p7ZUEo~ zLzR1_UUnBwL&{o1x9-%03lu+8XoL!-WvZmiyh}Yx7rb`x&0N6t4=Q`_CIA}k;QaHT zIx^=#?-+qLjO|0GUygi@t7)~~n6wBk4G!u7~No{N{ti+z-2aPew z$cN)!di@m1A_=fkrPP+PmN1}B)*4cR!3|<}; zAM5*Y@Y%=+9qh2)gfxduR}V4Xee9|od^}xGjaha!Y<|Bbz20*^(5K$ENGsZYG=*Ikq#lI0 znj3Tkq+Xp2HUh%46qf!>txZAX1S=BB*z}rut%dP~0bfY&$!mK%%jOt$Uk)aOd_#!& z(EAhY4c^MlR_kTPjv?~QQ<62+n&Sr!h6F*U0ja1#$XB+nn!b`zPV<6-%FSX@eI)$! zx6W#*o$4o$Fc9b#Wm55#%9K%QkaO|FxxU@x?)sCy5T-rh_Z}gK9czqgxba-IqFD_j zyWLpe4PsaY&#N$Qp;yZw!1D0wJF7Z_VgBCFuS1ax+F0E1sz-3_U#Gz1*CwhS-niuA zTLnSEWn~yuYbl>ogXKmX{R02Mj8BQrW6#bnmL!q5xM6~Tx(P*a+ml-$9vLnbMnx$XL~_h z6llHJJpTmOm>?7^ZSTViq3W2sy{xXk&f5)8Zq&sZL_8W=hh9J8fM~-X07S887E|Jr zmnlvxB8~E~(U^6aam0@yMAcD>5@YSDFEkglame30qP-Srdt3hdL$PcG<_vs{U> zTQdN@)2n~Yd&&!M8Y5Ua{xc&9*hjstP?L}XNp6~qQNGB*LdLL}yJNEr>FN&spwQP! zrx)CLxa?V2@_N<>Zd7>4KMc|~9Qg0X3%G4@*DC(l zJA1yYhU9!fJFib3`S7t6RBKCyyl+V2zLh>xj|HJ9oXhQ{f&b6SMptra%uLx6;)K5o zov(IWzRDmq;V*una$}kyC2c* zdnKt2B)@;cSyqb;?0q{~7Px62`Y5Zo7%I>gnCf6>h^=Fww~qLhf;QB!Q;j_{JbGdk z6uS_Wiv{;_W;}wJjOzRBA;_+^KMV3ypsLTN$F3nnRErYof1avcKk)#K;@npN3{tN1 z@k0`?Y1g^Vb-xOk0{87eK7t`F0z>apF09H8Nee6j^3f|uZA6Ft&rQ+on?ME+7jAIndibC74S;NGBWn71Z&3o zCik9HnG|KJ`r|+zy(Vj-(000j@Fr#%8oO7_?}Rs7G_LY!C!L)sr8Gyaa*L}e*1`e4QN`E2;td$W}%y?*E7k0ZGdO&ON!tRz+iK>!f&7 zk)Ka4gZwUEUGwv!OuQQ3KZ>%r%ttNX=NxFJCNz!LZ{37>TkvyC$-UEI`ZmPuG6)OK%SnEch9wLf zIYi@$NwKF*rQi#nKEHdKR*3Eus>`897_qt}=@Wgg@7VkE&!JQ+M1Pub%m93~~Tgf0$Fhy48kgg#jcSPjK9lMd<#|M#$!DFe-Kf3SwUz{a1_gkc*s)ZBo65fi?1U(OtxZ~qU z&9CFd%hAeRZh41Vu}da>0RZ3Yq>}oEtlap?X-c0{zJ0X(X_`AANo|7p?3c{WJlogj z<3F;8{Y%nf?8*uO_W|5U$TPT~tKA4ezSh>jSK|#u@}oD|PkDI&WyZ!&%U@5Y?|R$n zxd65@HJJGmIS_m?s=Xf9|AuI;Bju4(@*wG$hx+f!+;@ZT7OAX`qw}q7O*;0Z6_xRw zRpVUhqLV4Y7rva9<_aLkHR8h=_$3kkTsG`a2+P^u%Oq?jHNt_k6%W$%&M3a7&Wjiy zqUMLJQx~zwJbV-vq2$A>Wlx+kYe3WFsk_2jp0EIpX8kei^$YqVm=zn@rEv7-n9+ae z%SQhaxTS2#Ly%nOq3z)n)3>oO{!g`nCaLS-rw`wGwK zGnJSJjf%^;xZ-U5SWaKs1V%m6`zz$0?`?2pfhJoc2g#Y0@XsX%-cwxU?myP0uq>Ik z`@Xrk*D?|N9hZnGVziqFw43&bC5({T5$&9jYs2 zSPY6S@#I@6S>jy>kj6vKNiR4*(Y|`BW!ebp9>e=>(^@NC0Gg(~4y0^5;P|K@rjo3& zGP?La{(=2XGt06|ZtZahKpT)GyY_8V?_k>3_y9<5GId{$8QzS2XQos0oQyCG(VpZi z5ZXu|yP6ld_2#95)t8C>&=E$}i=Q)v7d{*O@06`3#BGvVo>7a)DNzkq|sF|{<;pF&z zep1#oB7GqK7Wr3xK)+-$?~hoMI%q9AxCC9g##BB7NM0k9uA;7o=8wX=2y0eX?_Ihv z6Uo0}y{nFm^%bq6<4`=;tXRcuC@urx5j{$%Jv^^oR?*&5ka_Pl@@S0CVbU&59;3E% z{O5jEV+M8q4gcn@@@L3@R}L(*C!%GXFxM5cDygONm@64V@MA7NVcgfK9YXs*b)Z91 zgMX#YOUQ*!+}E9V&el#?M$ZwD1LuNqZUM?j-AN-=j>p_sMfZV0U6dCN&~s-^0Tc1GNVW0dLL*)37-SM@`kN_qj)d<}$1LYvptC$6s=&jvr(ixS9NQ~o~oB6$}MyCTXbUtN$AhAc0q zrg?qF&7=TztNMm8Yi|%}$BuqYQtJM@8dB>PdydG}Z&2=<(!KnE9Nf3*3|R0+ZDN$& zWPl}yktk)h9vSfFG99~Q=T-OH)WQH0@^_S}H^$@7?T_9rA-3nX%AJU(y?hkLI})I6 z-<${l*S6sxN)WK)AF8%dxXWGs8Dw({;rql|IfH)D07?H!^J`?|QV}5Qq0RsgPt!vM zfzc@j6Li?rWZ0ydX|(wVW-eMy;$u;QgR68Z*~w-pJ!Tv>{2SH5IBa-(3#*VXYMKdh zx1-`@EcLcWYYp*LE@V^fImeamx4oa%V0J>__uL4VJmt-gb=FlBQHO=(ofMAcl1j%J zE00W_Bl5FK?}>VGG|1E)T|otZ?q&6_opAg7AJS$@07FBu3i`5ot$}tz+rTrfW=ED<6`j^ZkcrisD3c;Jxg+XO6#ax z^%tdeQ&hf=&sh}l7`PIrNPXCXkRG~qwpVC)YJo<}GSym9e)_r|YHEH#+7d{WrGUQr z$Wl{nTbr#Bf?s{l)8+B6^+T3Lj#0hfqSL(0oDR%LDcmL6A)646JYIZL^#snKAfa?! z`2>q^xA}8W4&nD{KDCyhC_zE+Wh#=7=Kdo^ItEE659CI0*6-#cobR~xjYAKsZ-dv0 z=R;bS5rwF-@)39WjcZ0Q+*b?tvVssrb9zC8Vpf(58(&VriEoo%^|(Hm{iUi(x>EJT zBr#Pwe9%i((4&N3+Vy}uJ;(!W^;Iw9Sa#+wr9!dSvmtAlWG%r934i08q;zofdnVIH zCiTJ41j^o(>PFB+d25x7u^8S`@1SevA@g(+jW=MvkF0dRgZEs zPLiL}kX(*zXLi-#*cBu2&?6nW*p>iVkx=pz7L*6q-Vw07af`<8NYD)@Dd{3HTs%?B zITB2b@z38lF#O|OIsBhoA1x7zi8XVcoVc&@nafGbEuOoLR-FB5gI3&qZm0yC;agf0 zbGKC9uaq6X&(7}F@hRTU83g>#Q1id-n~-Vt1`coI#T{v35XGwj1K>k{97P+YTGCaN z=sl>x#U|-U9NypwzX)5fW%oNhY$cP|D%)7W>O^Fuv`Mu%#NU)g0}DbIp79oO8m5{G z1}pKtV}7}_c~w(QhJASJ9&u4UM*!7O8`#zcJeou?iHSfm8 zdIM=z#`Qu_tkz7-x6OOHRmrHpmyp;UfG5?1oz?OE+5(okeN&_I&J@B1e(I$ACS^N_36^d2$XVtZZ zeL-bVJX*BtXA`9bp1#LAjGvSm6*Ps$oX-Z@uWXuepZS?}e9ivZDvRWAZFHXaAHMsr zl^1A3)eGl4*2NU2R4s(Ht@0wG%I4%iJxA%dx;eD1(i@bY_Vnvwsu9izXXjG`y)($X z2R(-J{QLgD!K9ZHn6}uHJ&qM*0s=X*QO)kL-b)}_us2qvWi8|Jy>h61Qk|MZYo5!C z>G63`douvHrl*;ay7yd+WH(bqM8FxQ)6vu>b2ik052D z!@16oSPcxz^#EQwW8G_2)WXFZE@?Z?CQ;t%s0mxb0kyU{Z{1WEOqW)E7Jj@%=|`mL z7|_2ad*Q60M)DgxtSf>0s_sK4tXOk9fAo9Z52mr;!IW(vcgEuhm-~lowYFidG?NKF z^p{KwV7Uaoj}l}SgI?w=L)zz-|LXQ)aSUK_#6i7v9PmPSR5x_@a|oY6W?hp|)F{Bzo+h?h*BD%Urd&j%X8>u}}LwloyX~DPz?{=bQySUqFsv9*& z!f<59y29{!>-`DTyl=WbL752tuZvlDtH{fO85~vRY%Y)?R~B%3Ip?W``E-CEYe!a8 zpPpSp;$Iw>iIY6y%cn84Nx2**=+7TeM*dQ2r|0s`SRum)femezI^ERO)vbab;7xd@m0A4N*GgoO8qE8tYkN?@4Z}qHr}R~Vb(w-Te-R% zEbCDQeUQn^t^{V!=SsW{P5J+fv^SAiF(vvErk)SCXqDc5CX>)dIitJ3kAXB~3E1vU zqvW+i9F*j}Cag68zAnTUFw#sXImj-hl)!SkK#;P_r@y&`$mZ?$;E?T#p?MdQ+ui+T z5+1?hYdKZxY;ln3JbIh3Ds@Q|1fXP3w|KeqhfY3D84C zz1{CzwX8C43e)Z-{*6a^TB`8jRxl72R7s-$?DmOIxKL;z8KY_G?wNL900&YNsF~sm z#Ae2L8ikCepb`P70SIbQf`b)uV`w_lS|9wa477j^^mu5dt!8C=B^iX^#4yCNe`SBp z1mCzz{lxixz}@>ZcUy%G^L%dC&TZC)@?8)Z7`YNA+#*e^pEqMKSq`sQZ|r}QcEP#v z@j69<^E^f7fEEl0^CM}oW~OH-xJ$r*Z!=U%@)HekItNGJyHHx<^KoNJx%;~sJ{f|2 zWPTZH#*>D<(sDcH39Rln9oUh|C(1_0XD0;E&)$(b=2QVlP1N)jT4`12Q}f*bI`)0C z+Zsf{@4}B9YQ+I3I`v1RzQRE!a|sdu zJ0<@S6SiV3R?x6$i?6XxGF-KM)$dWKJX;ACF&mxYhr#H`Jd&2Dkr&F z#Vv&eG}mAj0+!7{;aff&HN(;5RT9L zI0-eG`3$)yw9d8jh`WQ{3P_RVrGHD{Uk5t}gkiecGJTs7UjRM5u1zjet2iWb!)R;_ z8y?n$onZHt^Xamzry;B~Xk=Z;M_w22YFXiSnu}UYs0vkOwmje{*$3V)L#aN}<0`7R z#_gG)Q*+#$%8>(!$rZ|&b=r@LF+Nj7i%NuQjP*%1yaYqdor;M%3SfKJYHnCJ(@9bN zP|FQamS%&OCESjvmpo>;b^omF!d!PXO(g(V?m#4<ev^0K{y#4WbDj2o`)IG&q^%{Wdsa}7civ*vC`IEO!6{`h9|)-0HlsSQEaFoJkUUtE{qXx8oFb8SeT<-JDHAS1uXxKh@}v4d zjqGUsy@LJINzvo$k6O!WDGxOW!noh9*rZ%{(j2rIE4RV%{8-v308Q^dp5~+NCorGm za80lz9{`0onvqMaa5Kk$y7}skqob1bgpreC7`Aru+(p2Hs&bX2q-}X4H#uusB0(_` zGIzIQ)00qM$2&?-2#_`9`H|mX{lH;q)fv5Vih%5$Nd`qAv=Z(AP&xN_(e343`(n$v zDG&kPEwZk660Wx*N*1#o2JRkTBN&;P^EI&7r)~*v>>_++ItYNL&dL%gk({eN1VHHg z%$@e_>~iJ_B673ov1*^o5mlQ$DLgYpz{fRqC$=TWf;A4nx`X) z`YZ3G24a_>Yeu`!I^_pm5Fa-6h+zT>c>2GsjCm+NR+hm|aZ{)w8Z-4> zIq7Q_X<3t?HLgYdJVsDCK(MAJCM@UZ(iaWJ)h0*i{iyXW3D=U^6^UPUJ+K6jcrQ{Pw2=`+lbHNw1dRIZs=MLfzCr}gOJT z{7N4^*6f(_odSO%x>>zhrUqFud$@9M)dtZmVr_MRb??8SAfsbi$L~eG#Re|<0ZVF8 zSsb8LRz#Vl;)n#+lesvX8^ZeD^*Z*>mJO#Sy@ZZ(fQ!lImf@9o8S)B|y@!{wns!g| z3k$f{Ph)$HeRA94I$Dh%ZsT=IgjV7nA2v$DIt?IN$t_0r8QImeqIIuf6*f}1Q#hB} z6cJHilz@(K5jrLe&9Z%TLN$VQ0V1zqG&vGDCcOGnsbkui~~F(Snq3z)3mr( zefg$L6xM&cU19&-1hTd4A!(TBfg=lI%hCfx79#ktbe0DQ4?h3dj2cw_O{z<1i6K;O z&?Lve(nHicpt^V`Cx zKM7XW@4|BPs=^)HQqiV7P0d)NUF4RHw`TZK1ibZh58WOUh3+iJn2YeC)@Sqz&{2Dh zwBxTVW5v|osYERH%}3LGT@?M|&_SLm>mt$HJ|4h!yKSn7lt?G6ApzWndum6cO`3;0 zqtwbNOvYc)owtXt$0Zuv`R2Xu6Wm2pc6;$s8!@eJ5h$Kz7ODF|&qmjWl(UEMk@Oz& zTzW_iDt2mCl>f3ySSa20R^evPUA#z?&{gH!+#*z3kNDz;I0&i1MYd;3_{5JmF0{Ht z_LDLT{%+0E)1%V#P}{c%i;WwFJ)(m1YFCyshId%V{PT={&uSND@SvwxFq)}#cJ*B! zny3Q3XzhE9n7wtodMWH-1$}uv(5QRMyYn8E_of#yOaabM!-}_t^Pqnxwq#STL!#MH zMQ4a?x*E$f1@=i|Y;YJXQTX2xFW|nS`|#3bt|zhf#=@Z|Q(UWq(Z$)h2lq4B7XN%K z^*M-1hHUBNWMD&J1useEuXm8^rf>WnakBDI<5X*-D#CC~i$xO}78F+BELS@!x$Goq z>~QN)>QQsScF{j%*n4MRc(o!zxzQ|VZK4?+*C@XN5o2QJ%B>wexCmqg5M0w)dD*ES z;uUGa`3OR|eKi~(&@={Dv5PkQ4qpNSmbd5B3@2zaEuA!S!fH^%lEZRdKweS zFVcVZ#Z+!6(3G>8=b?)#?^9Yv3>vD9R7Z15WD~LjX=h52CL3b&peDM`fD0MCp*j9= zYGwH$m`p+%=TkKBI5q)0jXbHAb6jXE=v9>Mvezj=^=p&sbfTGFSbVdty8wS3_rcFeUXYK*t&3 zM1gEfMlUeqKD|P~S|ggKv#}2wm^6NsrvcDKJM%*3*;n)1?2N-X0)@hymTodyH3E2I zF=l9P7Nm=>g(!TpO&QKP%3>jtF{piG)!vo?j4Ir+p7l5xFazjmNJRrkqrLMhwhiwUo>Yhz12FSKqqiIH4_-~(y zJo#Q2S=#goeKh-eL<;YA-Rq|GQ#Ljk-G#-GilaK1?K?seU5tB~v9qIc3X?w9r##e- zM?JoT5oCWHTgaO|N}>X!)BtzPQBCJfXZ&j}dWMS-Pd*$vopakl!%Rnp$i8CF2O7S4 z%`>6V&`d@#Zy7&SGxr<^_z$l9ztr;Ik5-(EMfvmdkd7=+RjK03UstR320%A?{=1h1 zi&1y5pzYvld+Yv}bq5g{&vta`Zx6cer9I=EzbV}AWBh@@qawBnaM*LXp@|M87qa!_ z-OV^yzRU2?GcP~L0puU^!vA_g-X{$>zVi_BYxAZ0cEp5lX$saHKDle<_*pzAlxL)x zCk6hd3s@tBuJRuL!HF$j3-=)Z#l&~+M;rO0tdXeW#ZdoaF-*G@ChZOejIvJeCpylPcieblxFljdUpewn26hV#e6X=TFaWUzNWgH z1`UT%e&*2d$Pe}Hxo5|Uxorb}7RkN|?D~1lkhDo+m`Q$L6}jtzZJS&-_06$sC<(!o z(kvuCAT`FxnTImR3rtt3Ffkdjv@CORNnqnE61_@4>m3nnGtz2b8+Wc(tQj&fjmHvd z`Ah#9t&nG|pOFA(by)RZ6MZz|<0gK{kOwbXv^RuDdGw z&KcTq7ya%Ut@{_Yj?GpgGyGW%r<(jvx}^2jh`Qi%I2^;4_NV{nz4DHW7elqD-&}c8 z6`+7Zm)vqZm+E2|l^4{gGh_c5wjC$Z0TyOcZ^QVh{$IO1Og;8cdaK5qtQOttGbZ+R z+~irI^^;TV2y=0~%_=U8{I*-7+xUnn<0glLZ;_`~ivZZA{hWr+riA7-kuQA+ER7x9 z(7wFEs?B|HR?DHlbwyJ;z~3c7yqN~-Ed_nM*))wifwUaYbHS?*9yR10K-llCSTEjI z6~kVsM>sQeLPA$F)sgqyhQcpaHkvoX{Bo6R9;|LNam0xgNnSA%k7*Mnuf=}h8k3iU z7qE7Zz9HVhMX>T1pOn{kv>aM#Ez#7~DWNu}&By?<1Pu^22nwOk_qk4Tte_mi0iwG2 z^U$;O&rLG1Pdh_CT_pEM@R$n`y+{wrQ~vqlWk(|!S|~|*X2d|_t?DnKe94_LFz{L{ zbt(m=4b%+0LC=eS!oPT6`H|vulCngz(JzLsUJoqpjY`sc=52N&5E>x0mHx1?On(U$ zznY-oK&)Xux^eb-6wr2s)67-Z=~3!s;r<#rwqDW2`x5= z$Hz2Uh338uNZD=Brr-f7K)L@gE1kXcgHeMQ<}F@~p|Qu%b)vgL($@&T#R6Z=k+bS- zbWzi*4pEHXVJFdV9!wGTf&%6XSzY_ zdT3!v;2aC(1evsTei?WIGwwW5AeGrjfPfSJ4`J(_4f^YO_F~;j(YmT z|6_K;rv$lFD6MFG(!i|fhCQw_d2s0oWt%)=Vt%vW@i9U+R0e+HNZ+V_r8_6n^X z8R7JjO1TD69QF4?5T9l|982oiYkzOC$chE(Eb;MXN{S$L@M%#JxJq~e8#3WHOzu?} z15ra2Z~czI7Yw|rxi_jX1tPX8&L_6N$btSUt0y-tq#M$JA+FJ<^xw^N*;)N;03c#b zVjE(`PC)lus&k8)8Y&5biZyUPzgmgs+9Y!Uo`AkAzB%W$zYjNPki{uu4Ev&Gz}7!| zSpS#lkH<XY$twv>+Un zfe-xh%QyOfd#5djqn{5yCCOL(EZxPea>d@wpMNU34nTi17(52=x03o)d%pj5(bI+9 zpm^gto7X+OZnL_XHyKXf;w<5|(B}2`2IDYYKkq>IwxEU?IwyF5c5MY-n!4*2{4Dbb z-IX*lF=l5E<|?Wzv}O1>-Mty?3x@yaz9BR{MXMglyob4Zz>4fOaNGF)eDk^xh)kOo z5ACPsc(9sYTa428gz%m_UGEOZA#NqBoHheYQ1#IPOu*y68sQ;kt>zgcQ~2_@SS7S+EKox z@AJEIGIxGEOb}mHe{A5yf-9hth=F{@k0q~2Eg4Z(*l;xzR#^`yv=2B8zwc+mfF;N0 z({ISB;*`D@O-(d5e&?i>to73IDeLc; z6m7)4sQA+GO0HClHhV1_#7<|&@+SM@_6FpTHn-p)$^-H#C- zzj_PkB%}NiPzR!D{yQ}Ooi^nu#J(eusWUY`pXZ9y8VrT-_Du6)Zi;^`cK)$%xtcJ% zsaRY>s;x@xUNq|r3x&ilX)f-0&v9{&Vhy141(zeO-P3;iLvD&Z%>$rD?O(HC!Z+!2Al>;n<$F#!&HV#7U4t`2) zNXcCC#^e1}^OpC9T3x4&jcV_&{lU*-s)5~MkpL0=x2*c9ICwNR-o)lsSsfu~(`U0m zwZg{YY6D&X!{95>GuCDE^NW)`;&!;Eb+{~dsW@tUeS<1L;8dS5I9$- zFI>zL*$zm$$Bso|7y-S`qsoUH{CE#=zBsm+%Zz9%-iv8i{qFX=jkObz#9>c=d6uYD zl-`+xji2m(D5FDC4FZoowd~;rp!LhR$*RUplU=;?smzXpQ2EsLq$}E}`*F(@+BuKE zEYYxZ())6C9&7SMeWw5(fyhk{f?9Pwc%*I>cJlO1vGK|Ct-wmj*y`XK11++6STjps z0J2~ul%7_Rp{$YZi$j=N59F8SYxJgPEkj(hwl0Z9NAuhf{b!I;HyS4$_(BP}4>G;b z*o;d@sr-W2xdTSq=u3!l0Q9Zn=*{4V)L6?~&Tla_w%JUltC~kmo707pPW2HxjU%LX zoN~WhnSvIlqY*Zif$v|5Y}A--{~N`$=~2K~d!0T+qYF;3INje(X*k<&zalxS&|%EC z&n$u;)7XKb94V??6#pPZs$A`qc9efvwH~$(VMnQqaT8kB*>FtdE;itqEIBne3*ta? z8V-h0bt5ERb(fhbBZAbHT)^O+Mv$8;x^DFsf5eONmS&Vd7U|HQMFPqs?~VNR#Xw{3 z(;6SJatgOHHWZ?vE-P*JPzgZ51PLrt$tD$XNsI~T9ir=F0N^M)r|FJ)!Oyd>;KLxR zT`Is4-|)5qr=)(l-Ca+Ls}%)U&T%;kUYviS9f4p=S7rA9N)y$mV<+0(Ms%7KyD>D^HUNm^(BPOVaaPL0C1Ru6J? z9)SM_cbabj{RazYdQ%1rzZZUlQSr0oM`MAdJkt@T>ia)G>M{LD;=uVy{$=o}loQ=t zgd@o5#;MPPf`^Lb4jEfKfHHU<|RM(#9g)Ti4&lJWrbr z@UDX$0tVTH$GOedt8Q3L%>uGR-~K_t9`vMuBYj=&kD-&k@AJ{KP398Ln2mse%rW$; zk-9LZ@n{3t+uf-?kiiJ+nodM@c!ASM8SrB06vSPeFsDKy>@#`lzay;f(#Oz*TzIYM za6e^nu$y0@g>ecP_9Q&IGa4R5(;mtK{k|IWrp7+I80Rp)ck0D@t&OXIjtNgQyRYeM9LA2 z?)Ic+O=MPv!E!kf2;=!B(%|#9S^iSQH;@e3cBibJl0e4;wkJd0=m&+MzQIB>(Q?2m zCJK6SPCCm2`cfk!U^zu7OnMJF*go3k|Kg+=@>(V+)0YxU)|buLYq1n>Ln7@jP87wA zm3f&U(F$`FxgCdM3DLd5XF>3_caLm5=G-ZJERR3BVCB_vf-=vOQ&^iuh{uI-0Av~t z6#I37)!>k61l^41tJ~G!RwxY@Fp}Hzfq0a`c3~hu&<}x`RDGj^-d+}b?LM#D2|4lb z3|U<1p4-K+;a9eaY5CqRKOB8nDrE+F5fybQ$YyhSGNlk+b%l>+crEjUI*QX}OR@(G z1VngZ=omPs;g{AFeQuHE;exR*rl}ubgEi|0&!8XuV->z2-`M`4$DVg&KIZ~3uAXZX zhqpgC1??;kS}8aYG(q9TL~{2N8?6Ije70;?p6jkP99EfFkHMpZsFApp{I(%a2`bfx zbqBoXtm<0hBj<&SHn#`aze%s}&7WgFNf`jyOy9E&d3&ojEIZyCg;~dvV&MI1&dGmg zoW6~DEvj6a{)K{o@`j5LBnrKHF7Z{L(QRl-0yLhW_RcraKv#(ad{4*Y|ML|n!K#(MNAE~x1gu^< zLX|o^Id!~|n++^arq**wmEbZTltty}kG-v;qu<$^y^O;ojs^%}X^Ul$umt)dG+;kO z2COwQZhiXrBtD_DWhlYvV@$ho#b&y3cIyV4-Y2h(oab$xBav#`aZhg|r5ZTwe9zxy z82}F`-J>l5gv+%*AV<>R|K@G7g1pRZeYk}@@*(+nzaFvClG$L{K~`nD*m)@`h()lp8>7OI!Ti`-J$IW?y4c-3RLNPhcizn93AY#czz)f?<>@Ua;@~ITx3o^t`@9)((Mp2rN72DQh(T)bR zrG&b|iC=z4w^_Y``MQ4d`Sy=A;?i<2Tx9YYZzXBa0`D#w<^ZXQlGBPS`5A{L+6`5l z@SWpgEqDAt1R{)BS+#zN$>^9!dP4G`{{wwM6TaYKWDp?PS#cxZ@IHtB=jkKJWXx@7!T0h59*=*M_>H z=?uPWC67Ni@zGCy&uTSim7h10-(m1(#I{FibKGlL6Ox60cr4yA@Be1A=U{D-^oL}9 z=*eaTzq6vCSxDtQ+vMr)uz7%* zk%2Dr)AhxKn>(8%fX#)vCL93zPnIZvngS|#@sH&a6K=rJ7X0A&W8?Gfng@Or@{M>5 zt&W=8Ur+QcP7;q7`)1VMaw>0gkp;eZa?kSQ>_h~_w!GzI!!ffP_?T|BT!6JHI3dG> zH0`(Vb3(Wx0rIre6%4JAs5K7#o_}kFl(-L61k3)!r48i;15^7=M@=?d(fC?I^*3+h zf+kX$f#*AagoWvJi>IAmqHd-;J%|7H7(re=?6=B{iXUr%kx>KD>Hs}~lFelT6Pl`% zfc_`feH+w?2+&M{1Z~5z)UCu8CS1Q>2WI~xbKL)rGyQ-A9qt>Jmu&2zl2{iA3fdJ` z1@5`xfSH@ax$=BjQzhMzn~7 z0C1Hhb*8kL^nqVWA{67^V9@>CFQHh*Bc)_ z8k~LeWi7olE|h8h!_TFofX^puZq_SHZ6UI6r#D0V(Q}a*QEs@XiO!_OZE9sJE&%($m9bnmmSSE!{c7axg4P?$hIYhr}t+@7iiU|V3hSx5&Yk$Q?xKI2!Q zzcz&gd4~2Bf1hK38iTPNC@cn{gSWPw8!bwU8E5SDsF z8nd3qjViD~QO>~l09f=I@B*t$4Xv+r4MB&+)-%DC>ORCkT9>i`a0IOD=D=2P9m_PJ ze#V>brFVz<{%@)6;s|3Hw&g+p3&yu$D^>P=UTF06H9-h}x}rv*i_Zc3l`2-iEQm5# zsnJ=jdoIh!?>}MWpU(R~)P`UeZ3+W^HGRljl`lIOU5|;+7Q&^kT%X_f^-6#)$~n zY8BSV`n_NXk4 z%Ob)P4By9wdZ_@;{l-@6nxAR9Ws{mtAC@X3pwFuV;C@Mf&NMdW{y2`PEF{<}e17al z(Sa$h&ymCTrq8#P6`W%v>>tla_>~pxD&rpxTd$qgt-CV)5VHA&n#mM3JyicTEC1`x z?kNTYN$#=q*7tyrrG;2h4wq_^xV6OxnKO0?^e(>Q0s%>6z_0{QD5b*<&6!733J`G( z`-88OSb^|JubVQt_8%GNr1OCUi7HF)5PQT?!g>@ z<+V+Cf+;h6WlZ(oF^S*)b0yxBAnt%#EH{qKpkXUKm9}rd@{GL26Fg@>c(okoUbC-o zMV{n{f!HuvDay+8Bz8~L!TP_MY8Op|k{ExE@;f6h0lj12N1T+@FWf3rBwsYL1ZcG| zG)2#oI6BX}DkGo--q&%?_VYEb3vYZGD4y(`+H;`d;l%>1jlkO zu)Hma_G97m@T4^viF@EmSgP`>FuET5o;0~dk)<3{2_@3M`ab&aUv<>Unaw~{{N2GX z^sb^8@9b5Q6;j}=nyt8<0QEF%i#F}=ayw-PH@vR#SP zozK>g2b*Bve**(1K4^X$HHzyediIjUE?`;Y1R>R`rhHN-qA+u7P4Kf)$<_FeXi_S{ za40rP#8Ig86e{x|vcrfl=XoTMI;tsz=AOnavL*ttKWtX2&|$ZXxl77&k5$@zcuyHe z=c3QLNF3f&@B|@bv0Qz4g~Ezj#*sIF_8?GFmqw+=!?X4>Gx?f~%& z1lL(u-x(E%m?Ub4tXQ$w<=pVI$WA^S_AqlCD`@|@@ZM(L=1l_(wq+>HbIE)n{tr`U z9Tn9VzWXzDx3n|@N(s^((j9_=gmjm5%+P|;Eh&PC($Y0_mw0Xr|&K`mPA=LfL?*x`6cR$#|_Sx zQ+*Nk7Yv2+Y2p%9Tq12NdCgP2p9Yv4gO0LY8(d1Em*Pvds3^3n>IR(;PU7_&d#XN)J*Eg1N^fU*1aqlW{b$#;T&sdR4|G@^1<;&fh~qOVpc zK6m0X9;3bG376AE1IzM1tPwKzxgY3>3{h5nw0C}~ie%b-_jw<*tA;Zwq^kgs2^i*J zOY_^Zso~wnS{IskGBUa1p&WCf=E0IJf0%s>>D#c4n*{gwM=*iyy+nAwXEy-j{3U&_ zl6~nxJYK9zov3Rz=sux2rLPwchOD1oNCU6!rC)iZY4%DEV%4C3WyocE)S{L9qczu# zfyg2}_iNT%8?G){4ZMqXJC@HPi_|UlT?lb6S&GvDmS;gQiL3*vl$f5|p{!jZj0~Hq z?{(~(`VC)W>mCjRMdCiJ80>P(4;pU^>x4c>%6~0;%K-bkyfED7q7|I36GD!|s*#_M zP33d{CPVI?nVCmsvRL5*RmT-Q4&oaZo(oNZE%;6zsCw$4s|s&TwOa>APyGC%-DR!$ zou}90w3lZ`^S*;En>cs?h3uMG0l2+Ru~9-e0)FbGmq)~T;87o6lFmFpc#$?3c>RN> ziEYnYUwmO(y-+SHzFE6lSh{kn*A+2xNjI318>wo$V6j>eJBTZhI&#@OYb#M?JBDRYDWpOrJ10vVb2gkp?nAESkci0`pB= zZ8cx=vowshxF!vthF03?>|%p-sWoTCH$4mx9E8_yIhGrc&(sr`&pYc_8MLY60a@l;H~@v~07fi8btGM#gpdfL00?6<_5^VsLs-;pcq?ATJCaU#vh5<41uw*Dbru zy@bXHK&0>RiO_*EG-(!xJa7C8Oh=Who|aDQw^-8c?%-bnT0E?^mK}VdoMmEUi*%c$ zqHsHqC-yP6b>9ZMgb1L|V#nCZ>7CmTd29L=wacPz-2aDIhZHw3@+c2sFuwn|3;*h^ z%2D#WYJi{Bey*KOoGIGML#g3(BN2e#?d>jFAuWi!d? zk^qZos6bcin_EJ+rdg@uh6h!5&!uTU_-GIMxM;gKY)Du2qbIOE3E3L`{jVtQRt1mC zd{}knojZ8(Vg}fY-Mi-*!Dc^4x>-Mzu*a zUxWyihGqn(AVQV1I{2(sQ)d@0shXq5il)Oz=@VM5j(#^7oL0VUCqZj%v?v{TLKMKj z0bz(ovybmEwzGeV%5Jzf!Hnz4|Bmua!+}K_B&U7Fd6RZRUcaM*vXa(IgGU3-E*(> zu6`vtg!C*7v9hkmA>uG48uBOxQgu<^Lw2Vm9oW`_p8cfPLwV15S03UTJ-YMw@4UMT zk+id(6c0n_Kq-T55o-QhTM)92T#D-b{n)f{mfe}wd9V0YN*7xbW^!%k*s_^0N(Bu= zZ7~s$WRbY*!Omzn-{s$X!&DM9SJ2%1XWg{ z7`KYJ*J7+dr6vm^4tE?3pOb0F3px%?xW%L?tbZ|enzMSfT(kn(_AaU<&I($J0%iTfAA4^Tv@`!@>!DQoRXWZB)sa^nmyY_#o{N|z z`~O8KX7yXSBBulG&L~J7|2bd1Dt6q~)_Bn_5ixN+2(Ku_vFdLAiC1dRRTnb{hqEn@ z789aj)7>|BKY6Yk>tdbo^zgE_v`%^MMyb(=O zxz4l4m*7VEh#?yBd^I*q5!KUYQ=udKd17L{A|(;R&??v<|I)LAq=^8SIoM(kfqN6! zj90eY4GFG@MT2>F6awpyJ7yzxdE7leJ1`yhW?)pa3Ex^P2BZppuO=k~Fx-d2q zTXIv;`GFS|0S$L~sLR&=tIOKU6-w?uy9J@uX@PAfS2}jC3UAmdv%|())P6c*%e*1# zYG8~Vbwm5V2AYEaWh`9WkLJiXS8+RB`wb(j9L2FXw(P#tA~i#PZJTDJuy;uw_2vwR z4AF1SmLZByrmOO~LKy4O4dj{8Atm+ZR3DAIUsF@5h_pR7{4xu=53$`C=7PCDMS;Q2 z3isJ;PbeXEoBXDQV1T66$=1_`TDF*L4;>WY%=^nX5h5uR#e;X#8Qd-gx6LDUgGmn` z6{xF(D6nmrtFjWM`tV0XKb{dP ztrV`rb9yayVGkDlq?JH9*B^ZLDe3wqKccM>m*o~Ya z=w#Dm)OhKIic%}KHDj_b@=VHn@JwLoe&ftmqN_5Q=ha|?Qyk`^9!GE=Uf6HEEjAXm zKRi1&ecamN8#xoD-a^H$Q1&*jAgE5idZ)lf)UMPdmG!ZQa~b^IhcqoN>=z}7qZ5C} zQD~l|O|J26>R$|cW9`Cyqg?N@q&0oPWen_l&CB1spg(_EAM&Sng^zNaL0f6qv{ayLAO@3p@7vXEq3=i=j#<*yU-+G>TPic>&T!R z9Z}=Y%g$f2d5$o3tcn4j9`gSmJBQKv^*PIt?ep^I)g)5B?0VvwUKm&fN(T=N?cem4 zV0;v<7Ycy2*Fpe&nUMKKHiVS|P~o4J_YKMH50dIUvY}4ohke{?4d*Mr-hu%XBBT@_ z7@#KH7TqkTZs~o!W;F*|7+h!uus2cAEkC9521it@Sz2!>81T1B=m^6#&KV%Z1f0Fy zX2b!L7`fCt6o@48v68M;I=<-s8bobJ_9*4^mnDwA+llcR7&+ zK=CsFbJdB~$qwiZovTV9kM18wCMSZJxtd9>tk`0|fSL#Y#JP_T%bIM`V4~6qA}JZ9 zPdTL*BLIT-9F7K!7KAvBKGBwxfXZ)<6~m$Kd;%?t zz4W`cI)u`X*=10RFvxy8vm+T9YH_#uL}-uWOa%YgzORq|)mEPwITJ+$JI3v(E#G1z zz_`8h=Mxc5m6crmf{JojUyc*QDCrYB_6uFB53he%j8$7%J@4Fw6c=WR;mU}bE{kdV1n}X}hw!=zL(<05X7zbO&`_lF z&hC#s`t?zhqj}sS5cwH|1&Tl8&9~gHk$(1)t#sK#OF8ht#>itcN8D4)uUpXm8TurJ zRG4_1P&^G5@S`jxe4y+-?kf!GXo!pWXBuVWzh5AD3i{(+iPGgf^NptgA3EKBQ zyK@PVIP{`a)P zz3B%{c}z3rn&{2D{LexjL7g$5LTd=~z1;K8|Dm4a7Z~tRS>3lmsTsEODLK)YKXa*$ zuWL~wF30U{_@pUOh%u~Z{*?VchVFkw*%PDfbekSRxL!qSXJeCrce;&ihylS|eYd@- zA?}Odi*9UbQTA?h1|=5Y$D!&5kt;Jf3U6>TV;Bvqx%A5NVryE`HiHQIhsS|l7}5_n zttVr;WPnYl(O^)QzY4~a4Gac>s`dN5JJmVK9+c)lOCf+GN`v)7@AVipA@x4R6fMMh z5TB|}d}_q$Sj|}C*Wt1%P6gFj^+Km(l)PhEu?-3=;(VDD7%Ex!7F{HsW&)5?66(|D zrIJ6z#v6`1E2D)U=i2bZ`z_0pqZvaXCRldFnh-L!M+X3drQmiw07FHAwfy{L`ty_V zsT@NQSOCYl$d1km)|u$3 z0jD(i^IJNpK!~*H9N&LK!%&+4zc)w*4Q6q~RO9w)g6EkfaznuCrOe}`i64)-Uao|B z8a6G}MA7trL0y>Y1|^%*-}*YBK>b5Zxtmq>dwACBH)VvG-9D1PQ@IOx^sMW`)Ks5InGJ253iNJ!n)o&xIkmjDg^$_B>V zKAcL(mtMs9uh0t-P(r?%L*A_Jeyr?wk;NMFYK(N@d5OSD_WrhTPt8g=LVW9Rapq6s zQ=PFFv?H<~uAGdcfGKplgv}<6yYFob?zcQjXY40mSzxwx%43qzfMM1j-J|1Tq5HB> zi9Y@zp`j##oaxaCsG)16qM2dD?_^90en>PnP5YigTnn3jv)X%0so$<=&%VsEPxw_m z$;9Q@NN-zW$Km<)dEK16^dKX119t&`BR+R=D*^p6S2yk0<~%%oqTZj^!OI_DK5PU~9) zIqCTR(t!U>J81{o7TD|ApV?One3#FSxaSGj?`Xg}gzk|RWP!3zIv9s=H%*Cw+qe%% z)3!2@@p$l(2x){1Fk%}1rB9Q6Ff)!TOYUzM0Y`D>xInoh;otpt*`GdbAtL#h*T8PM zhZ2YFLuMJqoZcUkE6}u2qB_SFkMdm=T8(B)($kt`>d$#B9o{w%u#f*Y+Hkyb4#4xc zM79Lz-A5%rGDn!k_folV^$K)F99&GUx1G|;8xbjgzFHWE*DuV}V zpB032>MD>KRXp-3Azu{p`(bP|N4C$&#K&a>E^lWR5OHqz=5{T<&7R*Eh}ZPQ1{LtXz&)@_r0~i~&j#awn7KN||6)5%|M=d=t6N>6 zq$C`#7Z!pBRiD#U1F-G}1TX=8q^<@MD!|t>nX%%nd!ws*nYI;^f6P{NUcOAR2i3n* zt=Wa92SVK->_3nrNVqOSs6(0+qPGX7@!&+&%8jWvCC$3@_Q{>m3_LYGddtB8gcW|H zT*ZHw0<&nTho_R%!U%Ip_Cj>)`cuq%5Z)dxCswUGXPC-4?{*7}Q|$qEqA^Z430>od z$hRnfvkwXwgUFb}eOc$cIQ7fCqBf+=58J3A$#?kiIptM~;-35Y;r3K+TBE1`P!7vzqbI%ZaY;n=ke{@a%YxjUDgN5YPl zsvT|89HedXJDNXQXh2M5SgEl^{>xY$AR^Tl3mfu)9+{C>(b!sHT1|kowVi2N;U_2< z%nKP7NFDURd8j%h^RK?#?I&WniUvU!fA@C&3X3o7TdfH}Y>fFkTJr z18-2O+C29B2Wf!<4-7*Fh*`1J(EUWAyb-|zy&*S$wR~TE^OBqhHKov4D8gb)n#(PA z!l6eE@A1aRM3j7}_BmCSIG~^UOqpaXEI$hyATXmx1^-HhGd6TnWbaUsPDb*P-v`KD z2qwlnK3&7Jvs{W;dQLwOX|D5OW3n3kQy|VMxF=$c!LCk9z7F@Kbz}m>A|U1vR~bK< z6MFRS^6#J=-fdj(@@F*HV?IDp8Kvz#w+yJ+n-oj@a))_OcCZaqS;r!lIYYmR{UtI9 z|9(r^MelyWbmj<|4@;Ro>bXVw|GLpwtBt#JUi@+SZ$pszALIswgaj&#b_FM1MWF@8 z$bY?vl^QQlsf(iTI~tu+P;cUhX=>iuas~b|9N|Hg*q+Y+1FLNVySaDfEmS|8yo zT%QV)K^T%w4L{&lIes~r)hwvJdbUH&*904-_3cM4=JUH7c5cVb` z6h~1+N_r|0D2DH21NU}ixnBwqju^h2FEj~hz794>COz1kVJ%cTg9UjWqQP{&0q+T( z7SfItsLAsrS#k4@nn+GfycwMZ`{VEXiT15bm2Z+KA$cUAJ;HPl0*k-fN~%@fl%m(& zgFbu8yAFLi0w9H?4FY99feC|;Tr1dK5HGc0omU~5iO~OnY5o4SYin49usXFq@=j=Y zOUNEU3r5b--oF3+a)PP^Bi?Z~)m|2xk%M!fsC`EE!{~&+y+M)B-yry0QQ#XyH_a=_ zocZk&Gv>80D=6l(JM@wRfI=g^-cPb8n>Eb%P7vxbbWtiv@w!IceC=|fXfTZ8o^8$e z$6q7O>(H1Q{;OkG^WN`0$}hCWqVMwg$n(8ISSuqb+Py8ITj6j?|m!3{uai=$Ff+c#3FV&XZ8Y5;9{7-K}C@OI+s zD`{Heyg=V9$q=O61K59|%O1`AaAog&>v?54T7#Z1Qn#}_m#`DrQfiVDdx)5cI65D7 zVuJbMqJ%VlC7U7^&pD{T*pCDUV$ zKD2HbOgXobEjAia<(lY!L=nKH0QJ;-p^PKaM<`D(qpj!wFcXn!!MyKXf~nVDH4{Z}kY+Ot5iq_Uwi@R(6mMjeKbJ>c=D_p*jjER_$MK=c zgz$>s@sk>S-jDswYz7O@@$x_Q6=tU&2T(N|-E6lBQJ?p>*lIeRyS+~4l~0dS|N<3 zQD?dzMsCnh05ln*|2+_q0ff`s1P#6)y)Qc~NlV}B@i zU(qMBkTJKN5ZeoUX(nSk`|TTs7hl)>EE8=8i|8lhi=&)93-FvP`9`@X1tXraHx~i$ zJ=w8RpZodRbvXk=D@ce2A}h>_Vrv8mC$i+1b|O8#rt1e2zf0W|Z?wppF;jBrXxOi!b$+zTgarX*T8~4r72(G%K9PtA~^eJ{3_u#LH zU220K7<4qsF8zPvOkBYjQqpAm+%6c!n-Ae93s!= z>?o(4m8vr@>Vs%E2?Y+4z(I0?Uo{QL>j$?*3WIqcsRXvUBHGyABr(d90$5_lC>pSP zb$9!B9kfwK-W}bM1EtrdiHP)9oGUImW z$wis|3v=^=5uJo?$h3qomP_@nrnP+OEMQE0n6M@;42ndz zHr9rBVevy?G}qtLODS+lC?rUiU=5AtTL3*|MF%V3DP@EMdXiX-TDc3}w^z+Ld)piB z2m5k?n<*bRE`&62gVH~oA3ljrA|k20jFS3Y^iHeK6+G`9i}f+4<>r7GooLKz>Cuyv@lot4K)fsMOj+rr~!oaV9y`P2JbKSJt97u)lSfDlWTm zxf7_U<$8y2h6DuoeB+LDoq+VR$W*tDmV&gM<%<9Y%#58^H_HOR@ftobd(U+ zMO7c9`3WxPG3Nb3)M=xh7QKjQ6Om$XUw#G_KMQ#8Lfi6S%>n>d0Qzw6MV}`Q;f=41 zyaeIZy(ac=9w{kFk3I6NTU0$TqIez#WUzPy0&UtwwU~rIG<&J;!5greol_%dN&5_o zO>@#@T}QYU?ioE4L7gc^K&)wlss623L&S#msds=>WX!Hg;Caisl5;9eO|fUq+bou= zai{wJ=2`Y!YksAl_A@`qid;w++Y#F>Y6t3FUw@+cO{{euG%&j*dHuG5y?Mq*8cRiQ zTpIthMkHkS$;(;iH*F!?20Wqk+eG(!cO9-?U+5 ztk?li8P=8cnsIK_H;?#i6LhzB3Cf*b@*SiH>a5{pDi>;^P{Rh0CwW7lLKjtO`NW>| z+aL925u{O%zaLp>LBBO9TvGQuWvqcx1wvlsG>o%ZeES+muw3*2W@?_>49H2X6u z%4rIam0!&E54E3veoK)iDU{Qvz_12;GLB%h)BtP+Q5Uji# z`x{C~9+2#4Ouh2{ID*U(uS7|=R_KOwC}tx%MJyEnh7+BIMtQ0TGjgh(<% z=o#GcS4aOI+4|c~Kqn5{4m><4aGi9>GPQ~D^jm{Ro&VP=IS_ot#Dvf#|d z`oZp4Wmmfzd+I$A5*#hG;2&sy+zKcUmvh^%n7B3&Wnc5(wOx@L?6;~pb!u`G;g%C> zqm4}n&v`n8s|kIuc0Z;eikLoeC=G>3|}gj73zFfVXj#2sviDcwas!gYEFlSG`lT?y)9H ztdf@dD8lESwSK`XNE)rJ#zbBjs5+dyL;+a6Iw#f+ER5*CvsIRec14k+K#Vr&F@gIG zNF#aN$pZK;?A^1Fx{8%s{1RS;s-=G4$_m*Se*#x4c!$HK&s_GWI0-D* z^@X`HA^T<}H&-d_381@`;06EI`SiHVNJcKT3}|JEFh(&`J`^&C5hIom@Pm8$e$Wus z?-lxNBlUg3t0Jj)_rZ(!>ps<}Fo{(rz~5#jix(@@d}-Hd`3i)tOCC5kl~$(4qoGWh zc+TOs8n(P)(rSA$nz@J$yq8ksc}QOEh{ODIw|#r4PPsZ|+T|g5wMZRtc^c|ByAgpl zy$Je04w@#}JlAZ3&sQF~EzTdsIjQD4=a+JlomSMjiduF3WV~k=EkJ!OSx4E9U+h@b zx2J=L5{~yJD(FNWk#yac1XWuZD*)Pl0KwT1-msm3?9c=S4fpZ(0;;}(qV@u7S9^e? zMp^}(_ZeyyDh7r?0Ikx_&DlmIi~sQXw%S}TDMp!(WtIC%$+Vo-MQA$R$^@FStF(0J zH;R{lt(aaYMD6K{2*b)A5rJ-Z?@nmlbZ*v@uv&lh@Vp?rCXxV zhOR?^wR6$$F-n5ZoraIJcf*8NU-u?_(~~yj=QOfVY4hHZ?`zS#_)zm?*(Li}u)YJD zK>br>7eOdB6?QG80`LaYdcMqg&#mnJ?Pjv*$M+dK3p?_rZ$J}ao_BD+@34=^b>typpR(>v9HE~b*BQX@ z*7`Itqb&C{a$!dv69{3z`rpF`^V1{tgQG$q%dHOoubo_9@Rl&^v*6=iW-lJr2lu5i z>>&i|`5g{uIf$m52*iw;{wS(-nQ7j9pSaB_C;G6wlYf_msXs=O9lovO z44RRuA^n-g*BJBOnabxCwb^xL>KFRo)^IGeOV>?2-plev)a0A!_`hqlk)wgXeC>+? zR~+dks5qQAkY~~BkDVb6od*V`%OerSuzoS3I148PebG3C54(_?l-h#RQONDnc=yb# z3cRl5$l~nG-0)xkO`<`icqVSwaPLG-*F*AaTx^8%=9`ksmZNZ?aQe*(ssKY)?{tZlVfAIvFjDbPqv zjf3w@vE5KvBrk1WId1(8yeHdRx%swZK7Au}IaHy-x&zXZrrmwZ%n8_;9?eDYC z($F*=be5N#)T~nEn8A)3B_jX{o>ts?RL$`rvwaYaCD6c`t9fNLJJCsHGAa}<=F^=0 zkzMNs_33aQI+NL6z2w^gD^Duvq^C5BgpBe{ge>)cke?gAiflqJSzZj~NenIG7v_xn z%_X!V$mc~gE)azXm%U(fE!+y1zpnL?Zx#jLOym5*zFit-e|Mnc8VuCZpY8ozTWfv! zQajN@(zl~ly275YC8&YDk~n+S&rngY6P7{uSF1U^5nJbbtm@ZWUIN}pHT8#O+2c1~ z6&11Hfiu5h-;X6X_)DfK0(|RK)W(XdtL<3~ybyjp;v>~%bW`bRAa4#g7W{Bg=tdNN z*^t5}XHgll__gK=Jh&{ImKh15kJt?&wXM4LVv{mC`gpeSL|~NKn1KbH_kVJ1s^L9X zTOtN?YfxH;&)kP9y8 zLFkGHMwNb%GXcBM$qVuf=?~B$RD6@=fr85Z4U5}CB?_YJX92xHRN2+}L-FdP=6`CO z2$_fQKAt-e%&P9y*Ed7?RM}qg_Nu~OwCG1-UO_`L3(3|ytbehl*phgnY++l z9Nd?wr)y~<4vOVrPrv6?<}zVyPq!pv`$+Mq1Wj22i}0PN6IHCs-0ze(I=vIvv0;K| zO|4bx&4{1lZf$!~ey{}bg)B2uk)MjHozcF>*eVOr`RgP1$UD`0MD5z_; zW@GX0cEmi;+t)?{ruykaBAu-YpyaEJrc}lgJ%Jn=c5<1Ma*h-Q)2dJ`Uj}TzBn{G_ z_U~W?P6fIoTTs3cQ06*`4IE_{FttEto=FXtK{E(WO;f|~H6b^-%%`Rx3jthhKO30< zn8lt|@^}cBK^~s$O#ihe%gTynD6y;uKOsTWA3T!6_OBS}jbp?-AffFHpz!a zi%qd4mO)^M_Gs5)g>(ZN|=}41}w++ zZf_v5N#`jN0tXG77H-K+nrmLcVorG;e&&6T!r)T26KU*C)|i!@3N`?VUN0dsv)8Yb6FB!vw(Q?`w5-3>1hcAjxCS*^`jTh$=TD3G7vc5 z(I*4``iXyO1Yq~?gQ#uu~Jmn(r99Z7vgujfnp-uJ~RMdy)a`B@j&l zB#8XP#G@f!;&b-fdZ`wzVu^7Y%MtC+YpTmT9Zs(^t@cH`W`n=kR@+h{#aZ+9OgkAl zKS_EFs8ebt00EQ^@mYN(RnZwn$aforJ!z)4WF+DF@tYA{kpOuL_A55biQ2>l1mv6M zpVg0QH~78bK{)+UB1KN-^al5nO1Woi6e{|hWY6B;TUckMj0aPCb4-1RUq;L zz?Hu@!jN2UC!t%@@$R-cs>Z#!D6Z(oYea_e0=`XshwIm#wt6#sa7d|#a(9zs?aethNGp!&_zhq#}OX4S4s08V8{-`H#GfO%YidnK$9=UH-K-O`i_Y9IU; z?=JnL22OOc*>eMngp)g+@6uYt5TCzp{O1oR>hqx@|MsvEX(wGc_eu;>@$Z#=CX5l? z?A@K_VI`*?Y=o$O9Q!ef_5+!fXL2Rm>x<|UlaEy$=TOzifonbZr>10E1ETnVFM#Df z=%gRpwAes#S|9?C%6-uLve%wcQ&dAPLOfxt^eH8dh-F_qV`Pc< zcAgiVwfO@6iwfe)jdQi;BUq{0#~8F`C9-G68fNsr7yiEE$@{F15oaQ$?Uz~{&iYOQ zt^Yyr{k163-7F*h5lkwlclkBqlvnLOoQb?BQGr3>FzI85Q9HIZW?oc#q{uJ)u$hDf z>-#0iP+in`*{NAbu#7)@mqb!1MGksjkRz$Fl-A$KBoxX6uhqZC!O7Ea2>_(?>$*oi z{l`mp^Be&IqkL9B`C*b$m~q?}$=-X*)y=z`iA90CYqDE*{G`$tFU{}PnhM7a4H34w z3n^X)vQ^wid}*j-Iuldu8q?=QPn3P;g%vr>q}r%Hp} z_4+sEIz7~O+#)jyJR8)Xkg&@s81>Z2pAHJ^SF^?j%KV<*dscCqmE*8o7O-M7w9WO_ zU)vLdu)9P5Y_8U$J(l8@Nx14k(a?HDf#g4oy^%h8S_ ztECOohovV#3JgF=3_WbrED&-Zbu~)8?IMrN)n5n4Az&Kr3iNmdzMv^+g&kEI%^mkt zbBs)g-3X)7>M@0w9=1lr{nb@TOI^ory{IdbKst)oDqW9kro(9;TCRadxt>SKpl$4)DLF?EhYTgt9*3#0E)P#9>4l ze#(+W(TtVJ(Xu7EGyC~Pf>B_u^Q>pvpS$(aG+4!k`3c=TO0&9^&vxrKNU{lETWxm5 z(;OCt&lp;U-4Ok~#X5P|yF0_7nz?XbffQn>%*Fk4&6JXEs!ouc!sKA(W4Bc=3$XFQ zM~T$drF)xKP?5!h%}&x(1(>;8(1F}u)$yoeRL}=PIa>yT>HI0)5wSq}(z}ABHPw<9 zqRJB5#h7pX*lfx~_JvMe5{_b6F-VRe6V==#(HylqfG+x^=%_Cf=6NN@1hB9aY4E&HbpCt!jP~+Z!2#6Osg_>Sr-tg?+7ir5&%b1N# zi4?z722~z1#}dItde_@47TCbby^RMlNerZLMe+Qu8!P@1_W#(uOtJxp{-fd43BeHAro|&o59+k2HN_k%bT~d*(Sf;=S&bXvSL>76^XKG~S3c)PXmC)9Ea=frvq6#I7xYCm{v0e|&6lwM$pJ3`7RPsQ zRmq{7H=03X+RzmphDn!-C40mwZ^~Qs81vC4f@H${5linqPLrO>@op^qJfU!hVS{CY z&_NYxZE1g&iceVN%pUE=LbqvAmM*%6_1H-qmsSKltiaeX51PeG*DgBGp!_RB%3j~K zL9h6mqx+;wsZC)o+n{}P`oerf>C9J3$bec%Jo=p}KbV{CgEn{6cc1-c6Z#d!b|3m> zAE|J0GXK<2^BO$J041+S*I~lfaWo+yQvtD59w`#{1DKyLjmoy7LGRjnm@2@313U+p zB@z)g;ym-Q_YXp;|LYIh2{L=|1_g9fdsqKqjye!l=E0tL!7`GN=gAR1PP5xU`LFQ! ze}|8tqtR1UJI(uRL>NS-l$a_8KcO3Ca5Z|r8vhZPHhWZp&44{Hj4Qyj&?1$1ReK+< zY6Oe#N6L${@4#)Dq}6~tPH{uw2GHU$3pzs~gylJkm0Nq@+|^V0+n4zP-G3CzSb4Bd zatD--fAUIGX6yZ&o_MLx|EHiO1C5Idre>VC17VDYB*Vm%Ft9==%8S*dor#a#jAZdt zec}!v-w(r9yxCk-&NGilsO2n#>1(5aI9D3o*xk2GS(rACgsp**E;$Fn(K#3d2MI2y z!8;?jx$os|!zp`KYgAy|Q7ub}3-YMxXWpuP8#<66kKDSL&8ye|&GVdW_!U7o*A|== zNY^!%OtU`~7Vb!E5a~kJt9C;d#IE3H3vZZmeJ-vbm*jx_snjSwhwgSx$YlER0Z9*PjFj-dv49X$VBYv-blb!90q$^zq;x z>@0V&AtcZ*Q51aerY;)TUWT9p{Gf}8V+*4^1ZMZ||6ju?^k7OS(>(g8yQVlFb115* zvoOM9WDq2%O?uMr!)F&v$-7!P_vcIm$Qy}&^#s~tQtrWqY$3pj9no|eoE-{V_Mx9F z$^O2JX7Le5iXfO`wCA{n5pQ_TJB!8@sK$!mm`|dn30CYifl(8y4FtOnmK4o|8T;jnipQvrWH7QCleS=b@ z-aKBQUS;57D-v(7nR2d}aXZ zm3ER}xib;DHsFg>N+n!fL3=*rr@GC}yPtNcO8$ZLo5#B?PSSebUysQBcw;fKJs#hb zD+eYS?q@@8w1<+R1& zayoxUv6GT&4G8}HC08ybR0}0P9*ZeGzS#mMwruk1m!wASXcvJ?zOB~WO7V?hj2R2+ zV)eAJ%Xj}e44xUW_0QU@HKWP+*m;HE)=zrt$oemJj3`-mx8|GRw$?XV2N@v&BE8t*l&kcTO0m04k~%){6~q003(LX0`nhd=_wSqEev~ifY`?m- zXwvk8^RRpDhYNU55U*2OhYIJM|G{`P`9*51QZ+aNk6oMO!hPT8Sc{HGSex^8E$jU8 zN$er8@;}6k|G*i&k9O*1W6ikXB|z6X1er09S*ui5DKykyKh z149~7oKrj-GZSeCGh708oNJ&FSDk_X2J?ZM^?A3tO67+ATGd0$+ z5E~L7{_>_dV$LKt5irZ;2RK{l11W}WG3ahiqeqrmbsWHT^)39op|Yer%*hSKr*Jl)6pa)B7C68!i`?r6I|6ka zqX%y?!C%CoHnFp3JHD=hguUhNb-vA<7}Pv(mMIw~xr#b7dL<9A0l+pYkOqxuH;D1~ z(d1Mb8K2IDYBHGp6>!@F+TY!oCW9vgKL()smxGJ(rK|M1G|9?0+9&iWNIhkQ8uDrY zw7rsI29J0W!3B&|fyy{rO2llD_={`ivVZiFvfh8fTB`?PEmoC24*~@Dc+OwFwC~iz z@S>VpSs#wwD##UScODe+P7N+ z+-3E`GLhD9?7WvKjxbOGtB+MMuMu63N!o*q{a_3mB?im&RU!-p$&;+OM`J3|v8Cgc4WFN%a{I;jBDE6rt6$qVG`HHaWn zTkrHwVn~EXXg?j_8@V&d+aNaqh#5KdSRDnh$~0%3b%DqI#+?%oZ4dC4Je#kz`c3Do zXI%IE(%@AraCp5^hPy<(^4a66elfq>#i**!lKVRQ7h6Qia|7gHIIeiuR=EDcSF0wJ zh9wT!f+qCz*Eqz!5$2==z!);*(TZ?7i%!d$FilX_sN3H@mZv7;NO^vk;h2RQwQ^MD z`(w`>C9I~8Dxc4$Rvw*ALJe_s4$C8~y(a^bT=ytj7v(FRc|Nfa4oOgUEdqZ>0+cDz zCHfm=d{znTDXQ+0A8YrcPBM=zLxSKjT?Bx?EUcDi0Gvz#OU^N0Oi2@-=|K>B{O;~d zDN@gnPNwg!>|a#mrhr=uocA93*EAL7E6BPao^I^6|3lSV#zhsjUEh0x?gl{_Nh@BQA-{&N22>+G3x z?_-^7t-m%z765%6FRp*sYM%uaPb>0@9+AJukhCX%jy6g}7gS=}Koi2?eq7+N+TqTX zBjPYJmQEZ-;I#(GKoE9JUX(|)9=@zeKIWOC;TI-?wd;%u*}o*_PrQnS{u9mr!_@w_ z5I7(6xT`8>zYi7IOTE(Mw2&;}kS(+Fq_NC}ooY>9e*aDKpWEjiHeXaaJlx`$W$bz} z(oq*{q;^cPh;U_1#DGg)#Z= z)7E$kp!kH95SL9dHnI_NvGaPej#0hZv18;2>If=7)q z39+Txe-BT^i+#2T?Uk9)h>MpF!5yEcC$@8RVYt2Fs+W9!C=-vxv_(e`WGf>IhpUs`f(wdg z=<5WGHZv^Ah+w!scMXhQ2WS6)@8|C%oi8r*|5l@7!!Ela$A5P)U2&=>#L~f`VlU7{ zC3|w3sANCCQ{;^FkDLLGn!mg?cx(-kApox(t?J%IAl;lt;Nt9ixLnW;=}bne9*`OFt{`;fob?_6A?`kM>GH@a$x|m$3ja&>mX7knD)#;l1p-P|jxR^O zixZAC^d5{<)@&dB=>WX6FYoBKxg~q#WPyKY8$cIj!Kl3fJNzLt`ZK^?PJn4n|2Yx>xl>I4rcO54(=K*HrM7(q*EQw=nl&!@tQBJAbH`qhHB zpzG{kJAx?T0fvtObTJO;Q5^SD(GdTdy(Al&FGF@tpzg#UeH18|5&0-DgVm>{S90M{ zhLm^tUv%*&*-L_817ayNAO_QvhGsJRu#Gf=%2_Kpw zN;HdU!!E`i&~3)uZmoyaxgZUmH`6I6&8=IkVIevUsC91D%r3tI_{-6;=|clb0Mb^) z33)S08Sc_d_hWy%=W9Vh??LC>o67cxWx|QnScgsC^UKXO>%jQ#5sQ60w9X02-1IZl z73`VOhF&L46r@CaHS*9{tl7APMt3c_mw6;7cV4q}ih|aZPXPyk^tX}k2UY&K-<%Lhb@-ue^HNMca^rt|O> zzi0n`YM*lz^}9{%6vwZk%-@=g;B?G%bg!jBA#L3MZ4k!w#I+K=dj4GhIulM6y+>{o z*3Q#%PjcGZ+mD(YysaKq6C)XXT|>Y40(hlr5&Ga&aGO70wqPmKbm6(!w;{s*eC`Nv z3L(7h1cYIOKS-EU{aI;kE~9lYUW62W*e^y%+!Ffy8nPJA%ZTN8L5}6Wfyx1ye}F%l z9&^;S-+KyUqEiZcN3rHFp;|%#G2o)VC?MhacK5?&^NHC3$Fk7Ql(N}$d~gh7T$voJ4N5Zk7$C8!k3~usm2)?RpkZ-11F^ynWx_i(KFHibUY!N z$l(C^zO%yEf_%5U928ecfSpP+fV#K{50`Gt46=zHd!JB51!R_?zhQj(!F}@8_8U)m zdg2Urf2D=kFbDE*bO@j9t6Kl7%eoT%FOWVtL)p_ij%n3Os<3(o$1c zs$86k_st@j?!=9=s4(#hjZ?s=l47i7BxO4Oo_bYh+$a<3X ztcG-Xdh|iV*Q#d^{T_@w-{BUxq2_iz^V9te1oo*77Il+^MMgTqqbWw&tA*jor;2na z$&+S@?1Pu`oC|M~nkQr_O6EsyCYY(bZjV9=3PUW0p+@xVmHBNjDa|{8xN9h59Wv`g zzY>c*A^pW2i5Ghgm|T3FEutW&##8_(*6zG(;x?GoHl||oT~(l!lZv!s7J3eYlV;|V z{f+OU(EMaft~~K_ec8K$eo&{8gyU(lHdWSRY!S^Ibh4!aI*V*qo5z}wH2BtQeLm^g z>6uCjsreyHW3*-Edy-yb#YoEf=XZ7%Q^53_^|apVWA$ZY!&keXey7%?1QK3A0`D$o zq(ZQ^=!cmr!uTo3fZ9AvppZYxs#oK0LrcwdWJdZ`R?^QtR%emMU!wWXzh{|UdmD+G zD&@82T47_hXem_H__ew&F^0Oi((>pfXMUKfGK|Ox(vvI<&Wui6Hh3;|SsWi?q-_MmsQPI`5cBP@ji=W5wX$CIdq2UN z+{-psq2di3Idprm9Z%pMAvsiFg9l=|EhRhF(q}F0IvR_)g$vrfvYx)L*4V)-msxo$ zRb&+cE~Xhxux$d{>cdi=rH`evQqOhHNu_jde4>9jGLoDEO6wj^9U`?!q=R1wTv|(q z=EN2)^{#AJc=zn&Ap|kt!%X2z?2v#4rikCUd+atUN zP`>wS0dj4S>h`%BET#lZ#+mD7f_f8Q10(R}iFuOX)@T?@6C*(ykJgB`W|D0TKfh>4x<1L@!R~|F9(P&o`#)3OKfg-r&NY=b`$!k~FM_Gy zKTD^2*W)`UwK+4k-qCevQu!6P%m5G3 zz+qEt&iveE+_zGoW1Bz~2TUqnS%$>u1hSA+j-ZVpw8>}{a|p0rwepkg$(#lpYf}Wb zW(2e+0fPXD-?YsBi%yBlGOX0r9Wj`XvOgo+-!XXKu4kjS(1DaIiVBb<#AScBt`%Dt zdd0BCt1%wEx^!VFpLjAR6?~setv_hV0UNePR{0jD4Ts{|5_|s!&X$@>b|^X9ER0+s zd>!pla1$5&V@6N^xY8t4In&{(qF4MoKdk)Lm(OnfJ~U^8s}FCI?0w~G&8YX8sUZ{? zUd``_HxbOp^mEcieFK|E^|bJW7=P1Ss@WChmzEMW^H080f6vibrFwpVwkp`ErLz9a z?!B-B^KIPzF!ll>z(93p9~bB^6+2Ge)24lll&fzNB~N{8Z1f4eJi=SES40AQWd}uB zI#%f&UJ`ijAg1E)3IOMH!V~5xe-$^|XS~&=W0~);kW%xOD$#E$W$Gu=U3L7j zu2-t6k`)=a^j!5NKU879y1CpHrFQAAV#+&T--_oGJmuFPb8tOE6iHUr7{0Ti zZs_J@#U1?2-asdIlN`)WMFIW%-&U_aF!Vo(_;#XaK#dq$R73vqjaq929%K zFDmVo5WE^F^|IjI%<;((KGfF44PsT{L<}-i%=LVl%O3ZGj~kjQURh{8;cxMs zWJ>m3RR88iZ}9B9lNORz0FwRmr7j2J4G{3QVw~w$cH-j)G3wuXuO8lfVgjT9`5Gr^ zS=0S{Dka7PWxS-P}vt|ju6!MY_Zf?y^DF8UXa4G$h=~-LYm1)(}KMB7q%|E|f-V?Eo z2wUA9U-FI&(vR3TU*6vcBlk}8FuE_dMdK*@dZ2ZVHh25t&;2>>?9I@CVegqi+}DY7 zH(!&7+W)a7L`?iq&&+?V(Xtm$>sPa&K5gLR9e8cNb#6OGp@`ns76tVzsU zpVqwZ-eP;0I$mb(C!(NtK=&COJge@3aUQ+e2;X`%#0dS-=4EsI*09s;J%(G^yPZQ& z3(ZyMof66%#vyW4>Q)*`wMn(h4IZ&rf}e$j4%L*Meq&IMxQd0R$xFF!qqf=;miB{$ zFQ|7#Izk!bF_4pXmL<%D8sN!5hSkD?rdVWITXY6HgEMP~-1CW=YGD<4N5_JzyO;{P zlS+1U`$&MxWac#D_z%+@6PNeg%*Le#*(D@es0$(HVJ^;X8ejd9?VrEHF~R`YT1c}$D+1}W!=s?OqMsm=@g;3g9VfQZDf2Cg#3cB9ektHBvWL`%9yw@+_<$lZZ!7H9&9Vu+0y zNKTHrv&`m2iSpw#dJkUg4Z7_HWf}9|QZ=K*81SKPFM0=QumGs5Nmw&NU1%9$EDR=Y zOn;(~^GBphrcHT#QRm(&^`P84D(MBh@U(rr>&X%j<|x=|-O_43j}KhSX8pXeC*wBI zbFU+k_hSw`vWD&JfE<+m_ID1?C9%o5o|k#h9*YrHD0xHqT<;CDM?0;&_h)KNgH0FO zYw3fZY|yWs^h{KYII4=;V|Mi`n+}P=jR%{%&PeGqWc|wDn)=>;(jqrE1pIWF? zxazZX^tWSlgXrd*Ix7E5H$j4k-DFxn3w?lyoRTL0bur19FYd_m@F@dsRH7m#^e!@YytD3|giT5Z4xc~l`BRyhv2{xIgkM58|B80Ig5Ahxvwoy8&YJIq#Ob0Xo@ zq(7)erbvOeI|Jm>A6X&XbK4t-=_3LU0jiD)R=Nj&A|$!pEEx}NpHON)s5v1u8S^YY zI7`M^n+jQfd0TfhvIk%5mlM!Y65-A6I?H0-NT?5cAJu!msrP67EQP)f)?&cYUA3v( zWOd+}iim-yu+q!drZWndW@>mGTn-tP@!Bk)(|({Z?bs@-zoW>eS<+lSGMMls$~+f) ze;i7c7idX6!8QJIS5uIFc@zRDr?>Brk;!d%4N$JR(c|>ZB&(NiRL;Hzc3tOKcLpN2 z>xxuGBuM1&*6;pq@~_MAPII;*t(ORu2QFWbE*V;u^AnFl8mkG)zl%DLMTk$M8lvP0EAk;?2SAL2tzpo3QvG=H z4Pk@MI^!lUvM;}c3es{e1r{^G|G%SG;<}q5A)=*3-)+!(bLVI^fiHPoQ0E!^RqBne z@#i`;i~?UGOt6f5B3Z6(kV_N;AI-3Lx5YAK(AGCGJ|r<|7hPZTqG27z)&lMQE@iv) zkW6t@S!a8XmlKmwz&M$hju1)*-eJIQG*bAqk`?;O09k-5BYgn|);DQFeFm{_ec} zWfSgccCLu6zJphc3=Q8$oRpJp|MtDy_e5EqbW;}*dQo{rsJL064x_2}E{krb?hUux zs0)~qph*|_tQ*d@gdb(dFRxAgnObNXozhRiJ{bZcMoXWqD_JY_HEB*PFAj$OWkaz<+-pv04 z(+J4gvd8oT#-naKn7RiA&7w(*VUcG+M314_O+&TMcgYi&xZa+;Vg;lxz0iC!Ma1ggj&HtV*4k4i-Vq?!>XTnT}y+6jlHk;tx;m@r=akoQy=E zb}4{M?dUW>+YYBEf)e6E^dLm_I}Vh`hZBO4+Lhh&8`x{@4VP|&kV94aN>y%6ROrIlc|--E;fVRb zX9svL(UY+;9@6{?%l3xaFFd&?w@Ug>RSGdOzn6ITYz(dhsEASuvw4RDDpK@f3E8CZ zTXv|r2KBn>v2O_&HC=Snh|wSa4`KXm>11jWQ{K129!kQB&bp;PbdODEGy{52i~hN{ zzSolbOPtJUan59(@$$@#=T+Om3B_VL_&7d`xeq^cR>Kp%$BV)h(S|!pVjdO`6C{)` zJ@)wa1tChmGII!~Z%KM-LEru0}4XPLd0b!C3?SLtT z;n2-U$f2 z19sLBr$^Y^2*%XZ86B3!X~@Qa+3he}OE%hJVG5z!^4dYIj zqImUx_+3cMPW1B?(CWv-IbDY zKQcNN=nVX{CTO_G>?3+XdN(&eL1OutqjqpwGbmJ@5bB~eeS;PA^<`~uu zt8<^lu$6*NV)eeC1 zBWxtSxJD$T_%~sO>kAWlgfo|OY3*E z$#$re^OM@V7B_;O-!I(d*SmyLlWC|~O8Cy%-*oNmiHk8QY8qnXLs`-YA2faYFlM9n zAfPV=>3iG8Oq@rTENMV9eJEwj$}>>M%LtvDBuy_V$dnacpmdq1(ev6Lt#6gIX7@A9 zj$=QzNMRe!yjk)a9nRb<($XU&T(IkE5>dKpn}enF*|u#gK^V2*D|bvqB!xt-iFnop zvycM}BOMI_@s=PR$pvO(5^JY#6riXQx_DfWt?=lXP;vmmp(wy-u6Lh!7&YqV~KB98%+`ggib6 zU4>PqVFv05_>9mBWa$>hV;C~sbKE}o<%_t)56rb9sOLJ|$in28a`Dz>SHjuqclY4! z2@S9ebefPfzX*PwmXR3cVb8}&G53!=RLamLF0LzdF~xUCFkjaiFmfu0`ubS*<-_mk zIW#pjsT>x`Y5KYS5l+&uf4fEiq?=eH_wSl-U1|Ruvh}X~GFCaKsGb;PzL5jzn3I0c zUjq5Xy>oxR-L%J0YIXXk4ef3A=h3xO9g(9V{r9^!y~HFwo}+c%@3+5;qlY^V>*D#7 z@wYe-F&>eCq()dFwfMcbFo(BzHc4GF*SbW?3DsmmiYcy|9x^@&E71kyS+Fl|BQ`1g zuAG?hhHPMH+Nkbe)gNMW-?niEJ$YhIq0(l3_)Eq5JFro>(b|3?X9{mHlkYG;ug3=- z_#$gPl5)PAr=u}$X@iWe!R;0l*xQO(e&y-LJ*EE2Pi8#TtW=~fyGv@b&z8};(=eZK z=?s`>^%k(8_VNdo2K9Q#Xo1?BSkY(dWMNl5W%9t@L;fSI9ozq({AKfXxY{E!dl9C#43@sw_!vhwnj7u5Iz%T#UAd-^Kj zCs(L)_HHu(*REa5WizzJPT85c>Bb(#1A7jUeOiIWa2fY02oD)gr zx}W%S$bIC(qjz?+-``)Rl?k*P*XBho1uX+U6^AIe`a^np@hgrtc*`K@N^>2qX+c8| za$b2}6h0Tso$Shg4PtW@+1J#Q(7F;5dO(I;LIjr7q~Ylq%_Avo}^J z)(5{-xv05=_P8kYN%61!vGZlM>YhKizehAVJo@*>uYTvdz3X6ch#ClH{UF#4Mw{_f z-9U>N536zi7>pX`ubt`E+3iY8kOUC_KkU-D6fDttJbt;DjOh|HA@k&thk;=+WkwOC zOjE0;f@-l?9|0Wxb;JllEBhj&8KECu&A!~E-qO3(`~fmIRS?#m$w<-*70*1Ee08A< z1aL0d3x^FENS^{afU-~lBVbh;UgU!ki7F~Z-BRq@snj*) zjiBthmEK*~nL~`zxyv=IuFiYg>+ScyxfFuF`=?gvCNLlTUX>)Bx-xrYEQpF4WjK|d zo1ys}k+0Qp8XRz+K?30}JCGN7@ce?YZ9z)H#>e46C5QpycTXrmDX^x*@z>bYyBrSs z6UiiUamh)twprW5`rt-DN1dQtso`~o?0#STg4B5il?w`KZ>5qpt3S%LnDT^7xH!a{Y;`x5MUi zujMVB95h^c#7wXK7Z7^n`FZQEU0IDGMNHr~!T@SEVhW;j-ApKH2c`DA zLJWBu+B=bc_;?XLk+rcP=V<>j)6p7M-F3e(lgDLdSiFnn5a*H_`C) z7Kga_$_n|WM6;}9_@0hJ`Kzyc+RjgoO1jUlkDi@39xf}U9tuhsTn?YhHg3ybXnuI{ zsBhP!r-@?&ly)Fwop7cC^o#8i_>6ck?FdtU#7zN;JckmnI783&VNqFp;$6_DOWna5 zGAIB{g>jO7VRD-HLSl$oGY+int{{4=hukB?scTA)+d!}p|2bDy|BFf0(s-#!ewMU; zQ;<^uf5A7U-@PvzJ;zjr@#y?NH0!gTzPZjU7anPmBz0()7$9vZ7<2N z0TNWfv9y3pLvpqxIZW*<&Q1W9`H6jg7!GV|w5KjZDJ8HE@fD6I?E%7&i(I|!YSBC7 z`Q^ZUY{!flA`MQ?ODcH#3z9GW*H=AW6K_58e&7xyyXJwDAb#iJqjXQk3Fe~+k3Adfkxk^ubjL|&NTi)`1H?N4(=Vo;#+uFr_%Jf zV>nS=UQE@K+U+VCa3BA{VDo!rUSWPUtxGSR%lclhxx(@lsbT#1{b%R70=@< zO>lBN9_C!&&{e465@$&6?Dguk;`jSg^gFKr>@gSMfWZ6P8y-A?m|Fg!o5VhmSY%=n zGmr7?L{j5qEO5oPhLphJ@*67Jx_EGC9gR`X^5CqROZez^<&)!C^A<)3MlXq1Uc%78 z!CDVARWf*~nh@=FR`e57p8m2Fuo*&@>Rw;MhtI5Y!s}rAy<; zih2Cbp8z<<$44`ORr^zxKGt|xm*ldQepZsc8sm8VP7WQA*}R8ab>XRV_A7|irusVS z?mw~aW!vm-Z6N>s#ubCb_Uav4gNBdX*2P?@*XT0LfGACt%KgyAgEmD?Dw^J&{I=9rN8w49@Fe`+0e-ISy=C7-usS&0p4@cl}@jFhY@ zU#vNJHJmY)htW3tJDaLlXS*F0<#a=aiR}9psoi$NZLrXliyWhZ!gy zGMt)qQqL2SK`1P((%{r66mpG@7v-wA{01jaN}2@Ka#{g+DJ7vQO|5%;x3nsn(Y#L6 zw=#@V?Aen7JWtL)woRmC_`P)aUeW-K7I+!q55;dW%21gcIzihOB&)4!X zn^T8g_W~uEA13&%7FpOf_=HO4)12b*RuG(PP!d`mn^jhgUe>oVXXS!KX0G2`=ae## zhj_xyP7)`p@C_T3O|uuK950{jYN=}-WtU^xd!af<8lIKfXN@GSi|3An_AWk;4a zAMVD{)rAYZ*%0MH>%aKM)p@uYbI>4(ZwvF4S<;|Fg z0E(QfLge!Nn_B9Cr`c{nPM`RJ2|6Wq!Jq&&MA{ACu*(#v6W)CU z`08AVF7RD;?m<-OQu!vaw_1NcPr>_jzX{$)gaT{L0AZ4*J7oa!Js>D^YjDXovw#K~ z_3+9}G8(!FC9qj&t2!OpLpmT7s1>nVmX1DV0IJTxDwBs>U7)l#zoawT`)c?2Y_R-B z4J(~8T}3X}m15Ol?z)qz6Dx#xE&ly?P-t&Q-#hw3@RJ*JBx$9z`hz{N+O_!)2zAEX z!oT^;O>DemJK#S2@>BX*zvi1icGr#$KQTFK7(R-jV-~91Zbh-S z2<_*Q@}fRaKcAPTTqt(AeuL%iOE7~vm}P{d+plYX>_E$_E$s&4IL`4f?*|>#l-F|{ zu{h4}0si+!UMaAt139{MOPI7WPpK4jQ8jk?OYrEOm`D5}=#$wWT#;&~uFjoR&!1y| zwo~Wv9WE_hKespf+@EPouTfa!M2C_Oor+B00nV!;(Dv{8qr4{o0xB&5WBy zR~^O&K68~sh7}EQep~-UT}`>8H-6q#Yc74%Q&%L^0QFEv#X39E!+9>5FJawFUkvEY zq2HsRCxYq#(4hT_A31?*Gpv$N6@IUm^?oF; zsh2y)lH9g*puU|N%g=Y!hHed<$}ka1V5X( zx5%MMaGk`SCNB&gT7a$so2Wak88+UZ0t_YJBD|crIH;HTPxl8xo+w^K-ljEQJ(yxysy)5 zmZr=ReaK)(*z45>oW~nv%H59y~ziwPv)v?rLp0F@_8n z_Lxy4@Tpf}4JuRnx0y)Cd^OkYryt1Xkt4nx^L0@m$g@+$u?12?p?qb3VS|!n;;U3E z*5_}r@$Xl5l{Mx0?e9Yb#kgYD<~`dNr;D{wOmirk5d=+#p+DVNf$5oH%g^*+zis^I z%c_ycyO~QTVVd#+JjiWEQ@+kC+bWlyVk{a=znP;g7S@u-5E zE_L~F!@huqfhmKxWZ^;HZs{X06}oM;HNCgUIG}?bC%Q7n^+;ey*xKXLg~u>387#O! zGlV=T!Fb`sdeP5z*}~C!kZd7-;PekRSJYGSEKd=PK#+d-5f2Amc41q7xCO}m_wMa1 z{re5L{cE@X3ZaM{m7oO6S=-YL=y8s;uK-U%UU`E#pZxIaFWyRE+zjr`6HZim--E zXc){iw|{OYzEudpO&(t8CjcwPE-(8MHjSlsO&pnrZusi}Q3B&Pcki2Zw-_)PT|XA* zJ-sP}mMDS)B9yLJ?a`^bY)1pW@yx)r9F*D7=oS2cFLg)dSN89B4U7@mv*+&uP}goj zlWzJ@l z-%w^&VORHkPT&Bew4@%Mp|h_eG~vB}XHZc21q%Je<&VEEnVLsrDVyJWM<2QkMsTVrx;RVN44A%8$t4Vw$6sZy@t%{y zc(76VEUG3Rk_kIwXpU2o*u*nrudz`Yp)J3!XkW2$2HzO6QvGoG3)=r74A z!@v&TgdiJCA(wEgO|K_WhDdR45t|Ao@2n4162?;Onw;o+e{R^nW6s%l=699cL_&H? zDpBIpl?2lowl6+XxEG$dom4mRzhH0=-nDMB9fREUJY4wU5R`sgxqp{&pLiZV)Sq;~rc3nYp6+xhc{G1+l%xr^@u6?pdoK9ewffB| zwm<>62PdD2{4-N#vQq1(!FeY39kQ6J$(ViLw}Z8@LM$y(POSD&n;@eQjmhh0i{1B# zj57lU6$Wm_5ZoL}p$G)ta+{CoswoRJW~p8G=I7V&O6(Fkih7MXg`qBX5LZEI^0=}mz|}xTE;!`2_aauMFOot5y`A9C7#4orlN0GmF)CB?2%Ic; z-3qd2f=Rcu2B#B;m)82RVh^-D{gyl8cje6vyq7dZwOqLZ`HFs5?MFRrzj}rEMG`J} zn`42>6j4~Ayc9nOjOw`xvqGLZv=XJ9jcNGNTeG{f-I$gj3v zAkJD$EN|VgztuUk`W4I&mmBbh1D%J_{h*{G=#pBvI|c`icfv{oK0Wol5EFfj@xC}w zF5L08zoYi*itOCGv_ta(6kjLD8~m%=Ek?YHc_B|A#HaxU8MLRJ$s~z83R|J}LqunX z53D7%@gZ!~gf>&a*>^Zs9`Z(jN#O2N7N6azu;^p4ZG?tb*Y{U*%l};M+S8DK-NQE~ zUPPcOdk-9?K3Madw2x%-aeml$h258)x2WR>7xlS9l2A0Pzlt*iftFr1si~u9t5$Ct zJ%U>MqEA9{m8v!CwOcFovmttEa|6l&-r$$7`0cdPuMO%Ls||&gk3YEF=6z*58c#kG zo?3h;ke9t$sVjTwo#xjfx=SCP(Uc3*kHM$oaJlUwUM|al9y7u9$5gYE3Bk#$iU+3? zw&5?(TBJL##+&f7ZdZE34%<#pzZg(US{(t@qj=i4{t@L25VD8PIODyCeb<1s|GmKL(R{ z5pLk^a^MV1Y`@sf45ohhAn}*G%|}NSOyUep!99nJc<4`JiQLtG$`yV<_c3Xt()3GG zAKn-9KlMaR{59E;4((>IXW}p=6G@T$V;4z)_^CVFO6d+GIaj{SkJ`TQ+d1xO9@@GW zHeL^CMm#@JKX#Mqe|Sv`v+fy8E8(Wb6tZf4d9fwgPD^a-)zp(u2|4Y!o5;I&aIDiO zULl`K&QI=u@M|VO5cm9j<-9b)MA$ozy*ab}dpXzgufny11cJa3d_Lqn2M$zt)QY{J z(xns<1`IlyUPY|YK=Li{dY7;|rUccT_B{BvJjPvQK^4JV5*WRViiH(fSK;$Qp`h?v z^2@-IqTU)gW1<4-=kOV%!-d!@g4bMh-Yp50?C1rFsiQkTaun};sT)PGw&4s9ri;e- z>O>Po=B}U}7F>!3>VeIi&;PK=>dSvXq;K?$On&vV>&dr&^iR^v*kS{8rfPIt-ZmNq zUx%Xp^QK+&e@CKK7v5uV$f0i(@%a|x%{Jq1LYhJpkLeYUcP19x3dJ;G)cMX-EM1Ua z+etWoupmp`gEY!roZfSO#B+m>8&d&2)LYsWSHihk&hLOGtP4Nol zB={(h-CsdV4@o-w^<~Yr(}9lxM8$^`2$p^DZ{xHf!GuLU>l=MV z{pI64^K?PHM4zE4$D-rzXvtqRhIXQbX%@ppXN-4poeHAZ5_>@p+PiToX)*$w>tO`h zC3NTEYBzF`z%T{Y?mUBI<8gW1E`_9jR3l1!)+9rxQgL}7I8Gg-SCGDTZ-2FaJO9d8 z-E2;jO^b$+qyM@Gdfg*X)HSiDN7Eo!hf_~fPIc~FJQ`=eR$Ki33PsfJB-~G@JHW9C z$RBdT^{yuUkB?$01BHwJIAWKVTG|~F;BZk6HMCqUAv72|x4IQXF=2x}#^M@oFx|7F zxa;3oJ24oMLoPj$$X2$^|5tzo5;K(tGsus#EUeII5L0jT=&hvu0t?_g4!xfO>qyxQ zS`nQ~R>TeyoF_iDyKhy*icQqtEDjf7mR&=%r?sKut#ikIX0!ObKZ$yN-G`MN!7ye5 zy5U_!d)BUfM0}cUmi^&(useTtE1}6<+u2X`iv;^vE-#Tr2FzBZ*v<6|ss&>s zi}pf_4o}|v8MyJ}W$o>VE6ns5#JewXFkWY|B0Pp@%UNpTgWP}lx7@eAt*B}rIr}`7 zX~6JUX)KSIIfQw&)o5~IQJ!ktD~gv5zRCd12atNH!y9YPPb{J)8&w&JHDEL1yQt9{y9$GV{*wEeZ=V->y$cx-(PZt)+ zO>j#kr`#LbvD~ib_p9>Ua3Y=DQ%V);p0uYNX9H1z!Un2*dlgL8Rk$O5n0X-({~h46HuYda z7(nOKX**_lQC(!!^5RxDzI5=4c$KZaX1foIYQnk->_xH+;E}% zIinx@T=ecQ7Ehw#loB~b`UJ&99{`3*B(CrK6%A~-uDXAOYQBZtFYZ%xS}0Vf;@X+h znzOTc4^zl1Y_M1T7_b3*-hg?w(H+jFONJn~X1~l^?yCXFHMJ@+0)r=BmjDC@BgC&d z@Wq9Tuce|SKo6m}M^cl1^Sbw`*}XgGV8G`CwH^Dlm(_2&6F!TNcy0?q0g)s?w>D6| zJsfc5yf&p%XzP&|N9HdR;#`E$R|>}dt@wf`n0)^29%*wM`JBl9ctyB){pd+Bal&N* z4eJaYPkTOAbYapPOt(i$CXuA1TqgsNsvSqA2w^&AKzy_LIbp8qp|cu97dGAnUHk}5 zuUkj9t>9PM$>J7iJd@<8WRp1)yFiiuN+W3ezXA*Xn?z|uc&2iR;J-x9g|*ea!lYQ2 z=TMxJu4Z!WV@c0hX&om{g%XO=(CD6XBVBxM@7}`pG;sr5nO!$LR^vNnXn4zX<&eN9 zYUs47sG&5q6Iv!F0eavcvp#JZVROgw{WJ{W+y*w|kqVoOzet3M?{DlLNSej9t02Dnt5hele39}$3 zIOg#OMtM6oUqVtxk~^6I)2~HLj#Z5&CtcD4YB3)jAGFoKJ@tR?ZDMx+`*7Zrxq=q2 zh|vQcHPSV!96;lj%Ui`q5etDR*266+a?{%1;Jpq^^^Jvs z$F@gLs97xr;*ER{hSK5U4uZ1g>!&w=_{U4p{n%M~LIw4mxKm)hIlxIta5x!|w|>AZCFl5tJv~8rac@}mUUFwNm`Gy{Ls!R`g8}L|8KXIHtzD81E=K8 z-jFvVzhp5aZEc+LQ%G(fJrNT4Y=2eWJ0&dr_u6r*zfmYI)FoS_31nLsn0~HsCenSu z#sKvhE=s{oM+S2*1AYwro9uf7fwb?{D#r?YY@sMauEDkf8$dq|g<|#fXG#JQkwPgJ zbR!Yh@o$hW^wi=`GApa_h?su7T{pW!X z`km9UDy)Q5IQ!U~M@CK&fY1al%wHT5aq>dI?lN(v87NLn6PhoM6IHGxM^>)vt()94 zQhPH_m&!&QTpsBFYDyIv%mt>vRY;Q8`N>uV_rL+W{-)=5_0L2EfgN8L>)$QNq zt8BiFUv(2LfMb&hq7a+@J8XM&k&SE_@ip+)bngkePar`>0Tld;zUoD{hrgiFfDE^V zbcUjiKd^VCUTV-Jc%OO*6pLF4t&;0O@=;j3t+aoG3_n5m-$P=O$luNR$zSd|zop&XdsYJi$*^sf>$+rPqx61t-v|0OoBNFE z6SeQsxyt1$iGn}%T8E}0WY6AhUwZ$nio_-<&DO@HHEsrg8ROx^u_f9i6x@FJzQ-3+{QkrRc z6x`(dtg`3cH*0dM2~^+9e2WzhGpm6jp8&w(DV|T=3NYin1D7MtF;vL+Pz`Zzh7LNL zIP8)BkFMx>diq!3i@Wg3_mK0Tlyd#~fF))c$tc+HyqF;F*>!qDj&T>?uQ|Kj;!iRA zx{z~X%B|%tTux^cc2^j+$y>q|vk!~j$-%Y$nWAd85`mo-zbm~?MvdbBxqZeb3{z6x z{Cnh*aWo(?w=~0()wIgx-P@3u0J~B4MAgSyrv-Q3_)P6cu5&0%BUCITS=cbxnTx48 zgMys1R5|RU(RI%oqv(wH+_c-p;t&PQ?5Et{MBKR*Nm?J%5SIS`Fm={pQAS<6e+Gu` zP6Z^b5s;Fu84v{Nkd{!oyC=^0p6~t6nQLG3{5jX` zJ$v@r_gd?B1HYRq0ZT=p<$KA6$=-JjllGv`$m^kzqomEW(2eX;iG}$K?eX@urD)$5 zP}#L*In!H~CK~laHJs>lN;XGYZwomut=^X1sa$m+e->Ecm=SQi!irB|!6=~y1bmwi zMRkDhzzwV#hTVLXkA4OaJB5}GqLL`V9HyM~Yj*-qRY@N^X1$ z?$i71Flw%(kt{dqcn1B4?EXOJZ4#u!24dXjNUb%?eVj4M~^( zO0*Mk<{scItrQd%rW~3q97(oENc*L|>2)%`aQOQuMmv3RZvCirL*9QiS^s?<%GAAR zgw@Vx9>b#{?=-3M=#n;ba*NfDng_(nm4%Eli27EsT&Pb*yBN)73p=1sW5ZjJ11BT$ zktozdm&vk43ofMjFb{8G4DgVP8;RLC@C=if(xOPliN#2E#Z>Ety|4f*EqsR4t(J8p zq9X~Jwb}2Ne<6G(qaBxsHJ$FbC$#W$w1kh)dS`%?J(n$jyK~4)P2`XG{LbTpf%7vi zxi7vyI5j~B(K1{^aXRFNd~`V*o%924k6*1%Sj#heKTHE{qy2t(S=`GKFU`tm>4phU7?L^>|p$%*XSTt?n)lv^efXt=npqi>SR5@Mk)~7@3x82xS06E0ax(VCC2?;KOK7u}_i^0}v5`V}f1bYCb$BdV?ajO@ z`!Fk*6Lu=`UIre2x$(<<7}B`fe)U3c+Zs1EH_@7br<1aCg`!wrb09WhbbEwDa`Ub4kiXYZR1B{>@k5{<=}Y zEB?@0E*8-3Y6pTX@Yv=w(a^n&lC`$!T-aG}v6_zt`l*gpNZL?SL5Z zOpUJuLZ$?_zI)|d%+t}ko_b+uV?y3%-gF>UxE>&}1TU$PoZjO44-tZ~R6KK4bZU6Z zj%wjcy(I76=6CMHUi6HFG7}J{ZBDP)Ib$0hfr`Os-h+hYmnuu?=yD09&D9;%Bgk)p z)7FYitYKd=BxrBg&wD=SF;x&_b$*-dBS2JkoHJj{AK@ANj_Kdx9a4ER!P_PJ6PP2y zN}mS77qOo0>Muyj9?Zm3L^SBVaFROtc8`Sn@$K{PD-NuH;MtJvKr+5llV-p9CoRWx(S(Y6{muK8ObAGH(&? zf`}3VUb0e1HBFuGNBj?j*NXI=ODF4Kd?C8zd!ly$Nc7kKA))wb32XQe`XPxwjC933 zZ|CfZ2`n=p!4~JGw?$GE9QmU@oiiCYTo~0)#HDd9ZEu21Y}2khdJ#52lMs=o2PJi+ z+yP>-Ks!EWZ=0Rpog-$-%&GX{Pbn(oyssh)P(lBO;A(p3`NT%X7GyXi@(DKnx-p=9 z6^d}j?cr0z`QcwTc@JGsl#Ng(0PRL}uiVE#B7!=fT+02h8U6hX`(m9IuvAGY`w12u z#cF^=d639+)gQ3PiqK6z9;h^^E)R{>Y?HZ-9n$0#Pow5_pL-9CzH(#sb66-)w;fB$ zCVBXb{-I*E67GE84#Yt`EC-7=JntS@Jvr=O^($XVG?PqN}W-0THo!7G;f6q;0 z%@^ZpUtQZ2Z5*t%dhHwSXFpx-L^m`ZomJeV6IT$jNeulOC&bvgZU40g+=yD1udki& z(NG+}OmLH8yVLI#e>`$7%G*&XHlB-b%7-_o`v~wQWY*>6A)(&F^z`E~Z^H;LzSRx0 z6ch=^g2<`Iv4;|2ur79C||pxIHHkVQdF5Z4oZG!-St z7_&)zc(jmfbjv(+FBGQu{=FmUlD@|M=RK_poDq7XkN{Fpf>Y4xM=?D-Hy;{C0|&t_ z^5NK8Fx(C-sCyHArwn`PosRXyt~nPIhkUJ~rxN9_kN8wQQW${e>Cs)UWxo{N#}4Qw zKmK!0HubZsKFEWaSs3YSTh6?nuMEr7j&WkhAqqQIp8q|sLkKF# zXmL~^e|u?6T#X3zyOCkQs2K8jht3BkLj z($7~jmn;dll>%|M%DW2I~TQr#-H2yDOhZA zaJaxz($B;jhT$vbj=L;On)Z!j@$pa z|69%FOG@Cw?gb??2 za1MU1E;ggNDgWdlJ9jZLe6yi>y}x&+vQfpPNmBiZ)bP$(0v+%klRpfw_uF-{ar;Qi z3cx+x+Agyo0)XXv+JYJ{YGP8XYW9u)dhm2~+AB{1b;VvK61hmV^C7dbx-A<$zt4)9 zLq0v2HXc6c61HX*B~*_%g|Ohs>SG?a8;6LDOt~K*Qm%Xm!LD`=sg&FobmK60_4kug zcdysjn^tX#w+i67hMe7yfETcD!+?E0KZb4xd%pJuU$Rr@rfEtU_)3TVPJsjyb<=}? z)w-0t6IX67xlHDqO0kyGKVf<`W)kPu%Gq=2j}Kr2_HZjR_5zn-<-=uIaar~jsP%V- zCTxZb9@&F?f_j8f{vGc}>$tSS+nr5&HCgA~PC|2m(di8Hz~(Ek;ZSLtDC}f+BJuHd zWz%AZ@M-PVvCZJnQ}grdD8m=%IDx6hBGMfCjm+gUKiNAW3JWSxZ!!4J`EdiO_(Mhm zg-uj#KH^fAS=~{ya4SkcZZp`}Ux`sThaz}#=7I*4ZVDRo$W|eT?h+XY5TW;fs$&ok@^^0nA%s-8*QW%O?+`+VK{;?Z?c&veO|4y+TY; z+udC@)T6cZzW`SmsRyeo(EjIl*k4GtDjJb8gWwbA4w79mr2cyumx;v56*m@(RKs_W zcQ6^YVQO44{V?BjDjtyJCq%=oC$c)v@1hiGxKcr=oUSJem1BamgC{XZ}Sc>Q0<}b8+|OYuF=#ut-;8h zn$WtnSVHj}i~Ty`v00ckNbg}%Jv~>iIsU}E?6nmV9uNtPYtaKZS5Jk%osl9G|Ksj? zbTKj;eQriHdF6L;0(ZDBIyUc#fiGGfn$tY^Von-n1r-hG z!!4kg3`7Wm6L!MW+UoNaRpwjpK{52JTzF6)g#}VE;*~nnkV;jP&6ndC8(w8Gh%a8R zj@Da3M`6Oa$f374vMr#d8)Xy0W|rie$&5a*74QvmMUL%k^9`X= zE0?A)*DK_@K}+4hMw+*`h)y|@kGUM1($uRF@iTxkGpiX1I9E-KG;v1X@3L&x0l30m zS5|`TW8*7#y0*TNVt@KKV6Y2B4(UO^FS)yfrF-g`!AivpGxt&neptOmyPFC*?Y?57 z$AiAWVIF?|ylza|2<2Ecj)dJFR%Wi2b@+uAv^%Y?;yI6$B zQuPEJkD#oxo==~Oih1-g(NSh&+Ki^Lo-G-`n(!kIH8$<$F3GABacby>>+-}*Ikh<> z=8Yy@ySIbeC1%P+Qb|;1CZ~ewKzhFR0gNY{nixYFlX$+@1eUW?SgjIAzWNnZj9^5i z&RdH6J3J}vuIPkc!W!6KcM&7vWCC!cJamnGxnWIhj?Uv{^mBC5MZV&g{trMG*VQi# zm$Z0Dvq}J3Z}Cj=belVxSsqw>QGRIBjkCE`#{iUKztmm(%|%QLKn5Sk=Y-36_7O%|Zn zL5r{U65zRM{0jL2DrJWLeNE4WR0uz*xe$%HS0|FdMf2wks6$nWMsd1EGgZD6I4mSqwg{vB6Btl;#iZ@ko54h>hyK zk_E|q9%CPcl_vm>%kZD{`hOlr(xWpS8aFg!1%Q$g;*4XX(c*j)r90-uMzloB3Li}% zUC^RENQgAGhJR>b9U%JU!ytG|wDcmWWAvKi#a?SW45YFU`m$ks?|@9ObvBEU>-kvP zEuqvKg|hbfFC^N)8^O9nvYS7pi=Peh3ix=^wP=OXg)g0LIMb?iIH2JW88%6QaDP`% zLCYAP28kobkg96enzF|8=}=UEo%pn5IN5LI7e*nRdD(0`h8&;<#r8!Cu#)MA`g&~a zU+mEzm63z&Rwiex`W7fRk%qH0T;bzmV3Qo?ha-?;i+JEssQ|Y!SKWkl>tle+(LzlX z(yAhp5}*C`sr%`x2XB?^&lYaicr5kY+j`gauzp~x@LU=qOLebPBb*$Og;$(Ddu5Mw&3 z)Q&j@K*5`vobXlOtCge7v_t~y?%v$JRBwBuzKe;#UbAC*g_j(cZD<)oUOKXDmuKqVAl>gq& zi$4TIFt^dUobH+8h3o{IU0ab5$XFIyzLGPtYx|1AuS|wj>i47b`5ak>gtMi)()=9V zo_Y2*;ScxnHrr^tJ02%Tb3wYAtNz$ zxH18)hSM&+x#Fq5^>Jb0R2Ph5h;bjsyF3`4)qs`u1>DjU5!{n_0-S&Q5dBw_2AwyN z;K(n{(gJfhW#eK7g15uDs~HE`xh2!Wv_hccvRpER^oCRlh9YmO)-J&7I%7T5F6OqR zaLeYy9MC;>@Q&yrf^cuu4{wW;JZ-7% zwv|YtKpN;6rA5lqGYQ>ojOOSClXRm^-mW#kQUGC*@fNS7J*+4?zpoFl@dJ7Re|-XC zE-$V|Q&``LxH_)yRY0Q6@5<7Z0@x4nAkz=X<72H{J7fIboXs8dH-Tk{j72c!>_m1M z626y70xYSCvBD0?0VP(8M_X)w1>RN4+ofV!jT4b4OJfQxOTT<@w}B1GD{atL9~=dW z$Un^j6n|=9b5?NtUkO*>bAA!%GKn{j4#uO;81Wj&$zjo`;hN!o(0w*rYrs`vrfmD- z=l$1)wY%pMZivc-O6#h(#)?UdJ*~G~7^jnRcUtS&n_k;|;(x54d-MoDQrgnggnQ{5 z=L+mU&40I}tP1qGJF!@J*Kw;C;@dEFb=-j z{8kR=jnF<+&({xDh_{3XW$mhrmZRol{F%K7pecXa-?Av>#Fa)o+k!wEIgF^&%C1al z6{cUplIXjEGeu`S5;kjFD+D9GpoZ7I(q20UbXB%*5{Exb`vxM~2&X1!;#&T>g8!Rh z#Tc->DVt^Ck_Y*>vIZ<^1{2SdsWpoM1C};CS|{jyBSqs%p0l!KxsyEbArZhNv!)R>MS^B0 zpx$_t%0?V0vlt2fGH#J-sAlj0lU)W={s_}te`8r-a3dmgzOc>-qjOvO(1X8$FMtP| z(VPKJXn%&Bh&%B=Kc>$rdQa;^VrPk7D1LvvVcBSxk1 z`6W?=R^$by|w&j*Be)ahYH?DwHSTN_!Dmf5pZ&$H$c0i493AAXt6sRzkiVw zW(XN}Q>010gx|wk_IFc4q>%9ZQZDe;wiNM3()rvb=&A+b*|BOIl(!n2ZXDGI-JcpU z#Y+#z7gyrTPJ0XjqwvAvo`^7!ysG@~zvrb%=<^$N#kq7gf)n+nP9?&^T0%SpLJb%2 zg3({NESqx?A|9WGW-u=I11$`33B6`sI7b5glq*)ZJP*#hSW`Jt#A}rvZPon^9kNjI z=k1>mN95`o5%9&d)3>D=V?p)C9OMS#**$|0Q*#3l^ji!J8%*JKkNCFX*Hu|!e1#t& z_ucG|s=7$|c7JLHzGm+;!bk)x(4pDgE}hc14)%D(T4C?DL~S z>8q|pv95pFhL+OI0q0+JoIBR7W-FXKRZyf zko}rEoC8uyGwiL36vJo{$>3=8e0W&=xup#&6~JtM_wx5}$wjcRhso2c_Jy5+uff0b zT0oY3_-fK@wbOCW9?08nNZjp6Ph|s~qrO*(X62tb&^iR4_ zuFV>5WiG<}WQOv>DGsPyt}s`-6G<*lcl;jd&PZUgZk^~DVmaa=Q}tJE)bB358|oG? z37-L_a~5&*1%|ng^RddWxF@i|k5o3$mBCP9qQi$Fjg}$t3$^#qk7h9T6U}w*!WyN( zqn()EW?u71eD_f|<6twt|9B`8S}{9M zn3-Q-mm2clNjjC)_0Unv_x`y5q2$Znc3ZJ7G52a{ViIR1hG$a7tgvh3qHRAUDXFTh z4c5$QSh&v2bW6CrV=DNIO5_L67cj%Zu21W1@+-ISV-gZF#&#Ky9>36ihZvzj~V<$iJu`Wl=F zJJde$ld_M%mE@s$`i4bf(L*H;p*~0o(e7vNy(Q!VD3x$<9MMV${RqE{U%&?DA&0*T z^G<`7bpTiVuY`ekD9tats32m(S#X#%6i6DrRB z35x@&Pn9d~pTASzu<5@o7)D7&xA;B={0d}+1w`C!;fe*YqSvoNyLdW(-mtfaaIlj& zrCdxaX?1I2HkcYAgHx_T-=_quDG-Uxfju7fJ4D#&p}uChR38c`iZ~Rb2=-rwLkG(%YzkKF z1Y#u!+SQ)X3+mP++&>a1JNj5|ZJ#ESxTk@!75lDE4g9a8E*J5&MOo8dDujZEv5;3= z8jHo*3`U6H6z1c4>{}{$+{#K9C&8kaw9vM=-16c5O5nGfGR!0Wa^S8pXmwe+@)6}# z!dpD3edf;H-lF_B1oS49{^FgC-vPfgi}D+3vIlA?L=o6kn7NvW_#G?L(t#BotN+Qv zI56isX8U(6SE)bzo)|NKmY9-BU!dx4ATW+P!b*jM={H2efRekI5H^;L8ws$^OfHjl zRNWJ^eY=(rO5>h0Ty1-S%4qC0Jpaw_Md85TSv$&v%Z>w}7X^PF`{(5>47y>J6%qGWL8}(i)6LsacjxBnj65NDM zad&LYfSe$-4tl09jksy1ijr9?3uWT(y9})^>pXk6vz~%`=uhq;Fyu-c@yh znw>4~fW#x21QYJ;PDI}7%NLMNl3aJeCN&}4eN&v~>4J2R($}?Ka(+JJD+?#yhy2q< z(3SrH(5b62%FG%%+E>^cy#N($SzaQ!HS#!K%eDlHFYg}f*$moK7i1c*umPSG?IA@x zT^Vv)ftjHQq5JF_u51@)B9F|%2PxG);ylSy}N%O9aLp%EF?HA2U{*U`an~q z<3XjJM^%*Xhl$6t>{(&lq)fjF8t@Vw^Hue4Q|%_0?ii?!2c$InjU$7rF=M;c=_A{N zotaO>?cR&soqKFxR$(Utf1XP)i$5(7zK^B9Td;TK(dnwFEuKVpoq&R#CpHd_UIhRh zoDuw&1Z5WCG*B>_4C3BodDA3I)@ z=ar<#6H7@Ea^_tuDIE*~6FX^Q$X-8t!Wcp_+8*r!B|cA`+^jiObDX7U=cCr9%N)zd ze3AsKkk4X`AyyfexuLMCFbqFCNvt)wQ%h)mfW`35&0mdQ$27ZC@7q4^W)Xt<`tpzC z5G1Rv?q4V&k*`31%3(&*{*@r?%d0Dlig$_5V%xLpC*##v(+y>M3M4DzkMt_F2cPK|6y}(gEq(XF?--}qxcqiD zb+awM(;^l0^wHA6&o`enVIhw=pN9CcTb9dZ7x|txZ{be(N;`F&{5y1ys2Gi1nn%pw zf2IuKJrU;WK{tdT>_BfG2mmU3LndkhW{O~Jb|9)Z1vl2JREtg|JtU9mPBE%`$}Q}uThHVLi#|vZh!r|v-M;Z zz0>}Ns<;rwr(#nJYzb3>M(94#SqA%jAcTIlF}VhKw(V-R)twhSRxw8zGC-4|B_pBd zJdt+PO|{ZlVDv+bpK=$?0_io>o|}LSPgITKw#yuTYy=|S`zx2Y#h2Tk*sJA{Ke5`g zX;P5S9}e4RFcuZCcVzwgmoJ=?e$ zy5ymDtoq3Pdg`Mt{5KwLm;Bss^?}d^l!YGfE*r{wltmA`{{%@D#O9oO6js)y5L(Ir zma(g_*i(T{HB`>Hn9Nbt>-GWN^W@r{I?TcR_o?n3xB&T|SmzEPCG#g)VsX9YJ^JnT zue$!$=u`enTp^y2K_zvRl|cyxJMG)m{-%2-(air=&^S1ZjGLDu`JO%i#cAUIGrF-o z^K<+khGa&Q2BeNd4^ zJLl$wVs*%i8J%v6S5p7{4Q&I0=PWtKg0x=+-;H4enavKn)MAn@dX3K*W?{5juV_{a zd7&9k^EF^+nOJqX3j;K_DuNp=zZ1GMo$wQk=NulUqlq12`4e-r6cZ|)^>;%SZ0NBE zaZTE(_=4x3kr5}IfW|UHF?G5m;kaOC^AHD~d0Ds0MT~ER!A2kS{H`N)#iP$zsq>?t z%ALARNd8Qtb_WEQXYLkN2*0XnJMO(s5b_Y#?mNN(6u%}EHKXsLGQ)8Rd||y+n6*Ig zOmHyT-s}>2ay)DIKw@w&4kA#=h<7oM$gd>Yw=y`b-7dj*RJrm)V}Qvt!m|e@^tfV3 zHPn_dvxJRU77aPy28#A;S>yYwz~cE@Z%Bxsy3z#bP-P74k5*x84br|QS)$hMWwSCs5fZ`fh!J^5Vig}e%??rI|NsW`@h2%ztD<%jW$Q@3|=2aZ##0E1_Nzksxn_oQL2meQVy z>;Zy0Hov>sVaOYSSziI~=d)na-KRv0{5VP?JxAmY!G1ly(Bmc2ijdR}+p^Rph;hn& z8$C4ZmPW72P)CzWN?_@1WPR3ja@&NU#UBaJ@(aOsx13#loLI6)`@F;kFZfPi=o#zA zoLyE;Qb!EZ|3y(P9AGQ1hVM*REaEJZ)sC}$p3qTJtR_T-d5}EOwQf4?-yZxkAweO= zkNd%Ni3m60c4Wk~zSsf*5F4DtrK>b{wO(4&b8aQ_<31F6!#IgDaD3vf#+aO?m*4Q-gD32wFQ5UcFlBpem}bdKYwueL?N64pw4^! zr1&8ncUKBNvMo2M_9euxO$aD8!X(L^_pxBkWYGL^xUN+5>-bZ(Oth^Hr2kxG^_=zo zanU5KZgYueC$`w)vl@ax9Lu{k;)@lW*6DbyEXY)?em!0Dsu<#thV0E%y-1;#Sr)8c z2J2R!jXIyNDx{zq)7&3K&uSRY5O>K>7)7RWp#=7EEqGs_Qih8rU#?-WR7wr{Ldg-! z_=QD`4sz3hcd58(rCXaV0OX?S3mf?hpy_JL<=tpiLYz6N>FTQ_#0tfXee38pS@U*a za8TI8kp4R~Nwt81f^FV&LQma44>j6*%8>aP}_NdJkgP zb8$dW4ha?{WC||Ebe`jSqrxeR#lsdi-fM+rUgsxmxLG?<-QEe+at-C6hF>x3g^=LC z-Ut)Z7ROKod9+|jceDB_V%8$O`3qM;@9&%b$~Xy+-BW$wveXGU4Wk);AowP$;6eH% zT(D|uDMZv7VoZ!oH#kL^=xMA0{hearYSfbS1k9<*g8vnH@G=x)^Zq*xey)@Dt*$k+ z>1bQb80tJAD{g+p}bR%zl^Bl?}z4G;b@#V ztx@xz)<}PbWn$7`uA35P*E4#X_x62uj$wnHCeerr-~W?~iNgJm)jzRg*q^d_EvQDVR?b&!1LX82PW8FPVb5z*DPB@?5RiC za#hStij1c`HZ%p3*F%tYirCl%R&o9P1ml^6k+>=b{Kcd?B zbKB!QA^`o4o8e#h@#m?+e=5)DqpS>Taa^|S2``r&nL9)=>Nf;mirIFilXqok|NkZ6 z$~mm_^{T%#>h|+eAPkq_4;u~}8vuSI9FFMC9!dUwuQ7S{M1q4vi4niM`Ipu-S%4YQnA{PASe;LF=t()kQ|I}17=vDaw!#$4>v!;$k|AA@tmMtra^1zc>J z7c-`eY&-$zuHCzD@|;ucC$yQP#flm!Zh+_yW}A4i ze{WZ$x@D6j>hFoJ+xY#wNI&&o!Brv@uowUa2ME%BZcRL3Sn%~AyNb=bTQUOJIfU>L`dqe zpSl1HW9L*xFU*McgY$)FqQ+xfqTc!M)9zeimbk9zku;6?|Xnf6N!RFT>wA{+~tjd6+)1^0CW3V zWV*6;X)F5;sdV`RfH(JWgkAtM3wUBgC2!#w9CH`LRcmP{>c7Ri>`wG4e z`y{dcIg%HO4B(#C!b84y!Tjbpnek@5@(k)F`WaLd?)GWrqlm+SqpzTbLXrz{NXF8yX-TW^2OPFTlvc$11lx34%Xe;E@Y3Y&IZph@%GVUt?$fs1Vo9p zx0V#r;~Xnqg2|EGs%x2Of2NxkLb=8*RJzN%q6VIiTuV!|h>O9l92ubHG8&}bIW;K! z{39=%^wBpSP+hPZ4^XjuLrVY*9Ml~spiE}a{0#6#I+!fxRWYc77wB1ftLQp zw9C|2V`z=kBu*b62r@~+aD9}voi)RIQk|hn4JyD0bD%|^5mdZ>LvYqV~K6O-K{!=S8=kjS+`eV$`FF4z{X@sC`xGyWnY*rQHCi z@}*_ly8ckm(cw$3(9JJPk*AFuH|?Kc1C5|lToIBdKfOktby%n~>l86Tt1>KY5q+?z z1D#oBzvAWh!?kmSkpsIkwwm&fd&8>f4)a%j5N45W{T|NWSgG7OajXF;@4od3E_2PX z7YOdBz)U0@nmmbR%H4Q8Kl&elTw<3fxyvQiqJtH5D`COaFKx6?^L(Pgm%(L%#xq9- zK#G#DAh}eZg>RVP8Zd zQc=x{>s}l|B#fo|Lm)hEzBcX+n+10jRNP04H-LTDh2@PfK7d?#m~xA&?m6@qnpJS7 za7_=Cs>PEkrq-|LFbV;;J1C$+u#TZ^fOo^OkD3)}&iotfqrOiFyuZu$NDKox^}n%&QMA^W8@R52v7bjAvqyCS``(rL)|%E!YuC7hSjS zwY6n$^5a&QV%<%A;dH|aG_|p%F{A|yOr{7mh}_?gq9$L@vpG+1@J)NwZ$oFDHi@6oQ1g;Jh~P z6n-Gk>1BTj1d;ZS@dK1DqU_}XGZATJ8cEvXOQr(?9ljcbv=X%ES$jZM>S!O7X#FJ5 z_io@fdJM@0jWX;Ev%g>SICkS)$@+Yd}B0Pr_H*cLL zi%G+3HfBtSlAJF>0d)qSC!5&zy+Uv>nF^6Kl?$n*>m3vo#-PeW37Co*PeBip5ThU!Gbe{85;9%N_0-x332*QYV z>D^KtQ;N9H-+#hK)rmm>@TD6njM5yZC z38IrKqiQ)U;IO$??Bm=boW?{=Yp4C>=9Jd~uS@3_hn2v+l)MYs{(kMDpulf@ zl%aD`%`md0=uvR3AgvS@;sOuMHDCsAqx@3NZ+Xh=?w4&c+`f3p5b6ZTSh7FIK!yOO zWysre;7cTYav4icbehWz#FIdjVQn4j7S?>He)BD86Db)#*XR2q4J=9^E=AMuuXx3; zu*h$Gk|15&RQ<}|+r0y6TjR*)AnFzDz9dSUcZIe@dSFo95;D!qE*lu|GN<#r7+X2d<%kvj7CE0H^|mk z4;(M>-hL)}frkdnK?pxn?E+iCj>Ri)1a5ejr?|sAtD(B0MFRrS3+b=oOf$Eo-ELzQqr6C|P zNk98I{sZwS3;5M9h{mCVRsKhe(P?zz#?D9V^1O_M=H60e#i+VT$?o8@izoNClbj+~^4rULFWg2WgU(k#WEydp+4n6*IbY>X} zr(^XJ37Olu*_;!-eDG37^}Q0yA&69yyCXiG_!Gay)-VDlt}B^J_P+deSh+e z(%I{q`*Tk9*-QQAT%cMCFsaIw4cdM6_c#` z_uGmvH+i)mL_8fRzHuPyE6u^cuA%Pwh>HW z;kV_cmF&!lbMyPi^Lw(D*m*@3g&*|eW&Ot2;P4+;#gR_68Lsnz*4&(Ie^y7&Ub~H~ z-nU@ID>y_tTw=2HAjMC|%JaD}sxt7cBQ@etIHTo`PBP{j6D$g9S!s8d0bTJc$qKA+ zh^eJn%pZ8n!ep;Ki4+ukSC>cmyoIQLmZayKIoa@%7ugXJQATgUc@_EhIl-zTO{lAY z_v!iZi==0P-_ksy8TI4c`TDX)nz1b`VO#b$kyxB4;{KSpOO)CUjny%j_9~Wq+GvQA znu5%et)L_cs@b+HU;r2QPsV-x&jmp2N6wcLk9A&V)7S2Q!&OsPY9O6Vd))ly?Dzb6 zUcA@glPgm@aTO=zbL;=^lmF+!EzG|mve9y{nEDr9UdE;={`~XQbEdkd{hV-3_yFVKClv`l^7u^kk^2VVk{ zE|r=IWYXwY&y7#+?CiZ){!T+jmE&9v?_hE^)ZUHUw=vHXHGLZ)+ZOrdn4soh;;j(A z`Fvxb8wn#njP~B2t|x%EV6>NEa+?TZ0=V_NeTj6EvlS7T)~bK0m>lhn>pR#&gX@UZpkl4r0H9{1QtnG$rAAP6@UjCgW@jpLx=TMtq4Tw*hk(nERW#q7YY4gN93Eio0gQPDC}qG2M{b9(}C zN(IcwqPk!1o&^C9$g0H!mh;Yl8J(_5^!mY6dv~#PA)cIw*TJyY404 zxYuRSGB92K2}ou+KmTF$17^4L9MCg-19tUKUycyQ2gwo9>h_XqSfa8XkOx*08Pi7p zPJL^s)ImV_1VuGq0B#jJD5Nxp66oUlDCK9D{y%&rmyHku=;Ci`I&*wz)waJpoM^zN$?Ds1SQiP|5t?$RnZ&4b$Y5;1r*2xt}xa!Nlk_rih%2zQ42> zmnOD=>_$uYfu3c_M<_0lTWXUyvi}68d4bTUItE<0BKze7cEXa|=qZim1M8b}XkNOs zG(b;55ES>JfC=rC%8@1rXBW!*z}TT<)C4QBCw#XC^PqMV{;~X84Ul54^EBhNc{4cJ zyTr~Fw19OiM08YFzs8*1OctpCN~yb>DdSQfm^zZ8`|%JTS`K>AYLHk|Zyjb3w9JD- zWP@A!bZ7u{+k!QLaosg%EJnD&^6vG#)Enm^rPd(>Rp^@S&~LtbqhG79smNGk-w)CNmxh@2)hVs*qch9A_Z_=ZYCo zi237jjCLuOO#UYa;0DG$?RFx!-a+5F*zj-{XX>!}%gyymqOKrF=+tq&vs8zq0;iZw z>GNdooBGe~{$YjPRob^HT~UZfi3S4dQ@I^)i@zzP(&1e#MMwS4_UdNx?7yIsiUI#< zs{?~p-Xajq^;S|uqH>rxo+$x!!F{Pm*N^urMW*5vi2X+HyKC-*-{x*dHVPoUCX?xP zpXfn7K2bj;v##RH-h1%uxPRS*X~#u#6INP}%pSEXObqYaX}q^>>}JMHE8m3a;RpSC zvx#NwwV0&ANNEzli{WPXJik5^QG5uf0SrG;z6vsEtKd00-SrI=^W>Qd@exMcMeJ>( z?Qaitc+Ugvm;uoK>}{nvl^nT8Mb^Y7V9N!E9SL+kH7V%d^!$zXqJy`4&nh#6KMH=w z!n}KnDF^A&H%ophK4v4SA0G-KC;#7EH2~=FZ3_>Sp%@mSP%pP^XK8PO%hwhwHj5sA z1lqru1|5K_*Y=-(a!V6YPQy$E)2ItK5`&xztsUP80lcDrjSwtr~ zRH+XR=ug?DMaPdL|G-Jd5Ufl}2NLo7aZXq#K1~Xsf*-&2Gvn!i@WmwG4Er{=%GQ!g ziZDKUo18&e>#5D~*5PP|>im2Q_j2_y)`VwZ$(+l}NJK%utjYm4sB&yk#qjs~v@;#pOFCU$=7F-Hx`_!nD2&GPif}66@R{C znp0>I#OxHFX<{>}-UUaGhnb@wT$lK`$w>P1ubra-7mBGW&SPQ6yQxnLrNac>9s38w z89{q?U)^+gKm|!g>_kak_ghw`QQ-|nujPnzPc6L0z4_4N#JzoLUzv36Fuf@IL@zp9 zyq4HYBHeDoky~#Lecq9XRTfxCuf<__kuMu>)Sm$|7lF4tWWV;VD`R96(2q8DL=EMf zv{}APfr87RhpJLdBjuZ+!+KswV#A#;jqGI%0q`ny@d85?NKpfXkVb~@g7_Fi8pR%c z8Kpz%wM14D9++;6GQ!h?LKw){m@(wv@{KlJ)V`Z)6ztnUAxORsGf7uA%X1q z+Lv!u2J}Kii;F5)kyOPEOZloM3n5fH7=Cp|fq^MGk)Wz3QB`+T(Q(CL`du zhw}UW9s5r9B}?`~D7%;;J6TeQ7^F~S%a)k23)xCYC@o0Yw=84GQc)<`_v||phB5R0 z-s$`OeV)%hzu$T0xzEh|KKIVK=iKFN_uM6^d|5`8lB0&z zT|Jn6zoZ*m!DZLfnZYB*+9&rYsk_r}#>_HGx5P&AzG(JI=GEFDd>yKe_LrQD7^XoI$c^)7l_ zb-^@Kn4pFBzl$sz`2CvU?1Fslo)JtuQX1uhc?LL#Eg>3H2GPmLzL4F}D)dKHPZ5G> z)Id~BL?Z6)xEPD=r|b&|3gy&!A>&zLF*GwwhL%xbL3omSSQq4Hn2A_k-tg4YnNegwy2pPT$>xdEbuEDf+lDi|IB zM)W}IUX+mLObKjoV`~u|h~nONOd{F56}%!zS1#WHH+mJu*#f$%`+S+$pAZg*gy~n^Wg|QfF55^Jmk%@#N_W9VydX zX&8bnrlNds`lz7sWFOq_H!8&fXGV6yYx~}S?BS6w!dI!AIBrFVXvz4Q2r-`fX;Kp| z57-+Y>#*j2x<#Am``ZhoJSuPI(H3TgZ?|NptOxT2{_$!>)6$EUrr%vje+oCi9iAa4SYqB9pIuRD^IF{2t+$V7;F?8gOV5E#448|UKD0)lSY106pkJxyiAUzf@V z?*Q!4=bm9k{M>+lObjvj@^v+zN(_Kl-t5$s?s$DQvsP?~v%0428O;pUud(!=opXk+ z^LnYB$6ss@I7q>#^p77~ad&C?!;-5zaF^v3ccm{q+3y091blU0Nx))i(2uQ-59nbk zn3?;}H$jt0fZBASyx*_|;bB@b@ z_Pm1X`9Ikd2D4lqxm^Ewi-z(l+tyj?l{z)r4k8!bzJc1{bq88kD3Q5<#%4*xos9xi z7xSuLCpF^z4aA-A2)59>d5nL;hkaf5yJgwuO6ZQf>!wrnY_7G`zt5+XKd%3wZ~~}< zIVaOLl%dj6CIHJJmXf`|gd}EQ}@2sUd;ODp>ZxBKKs-z z&mzHJa^{W+kDd>Z*6m)EB-*y_6(8J|*3oM~HsZ0WK}5P9%ogJ!-P}%qSC#fY`mmuP zguA+ru+Dal?*9T z;aBF#?6>pzp&X)_as@F4iTclf%VsYzExb3sj01-=nS0evtK}2@SyDm~sJxMo%$FXJiEHM>5o%;DPz zIZDg1v3Sgr9XU^{4-$56r+@by+-Byd6FrzxmzPf>-B!1AfQ|F!gms;@*UC>1fIloU z84e#VKmRnchtdQ{%+5E7V?`0ga6VtQ9Ty@dK zJtuLW9LtsdH5-p*+!>Y28TRw)ZFPvuZJufmoExf+<~`YDy7bevVwaaj0IqIi39O^AOGNiGeNYFOpA0(*QNL=6xPw8-#cay1okU7a2~OcZ%yKtAk9kf2rJ& z!%uX{;&37D6LG899-IO6lQUTzw8}oxWo1r;{wWORf}H&OY4I*V|LZ zA1=(;7drc@9yWAF@dL@#^uNc)oTjs0?j2QwWW#671L*LkIz<4_LF(PA7qpSHX(M5jEgX4M#?h6pw0% z-^s7|EnjB(kuaS=k*tjB*#+%|^?`IP2G7fWaHTHVL09x~)2It?(FW?myxQeiI0K;D z5Pj(Y$3_!6l!1mTLWJjL};-!idS*?K-TC*Fl(FGfY4b$`8 zy!7cwC&F_kbzuJzvs_ssCW3=s9q!HMnuWYqp8wFT*Eg)42pU%L^qrAhrUWEefCUO< zsy4wOfv*xk6#&gN<%F_zS*qXs9Z?XxC9ofj|K*uQxvuI3dT0OfYDYq3#xR;oRJTrN>Z-B+ zZUWV`EB38!_#>Xo@;8K}cA^)?&?pwvJ&m0w;r1y^PSV95z8P?++(;Y24S$0Pu->hq z30Y}+K2XPWTN{zl7avghA~qb@Y(5S1C}qf;L5_Kh6bNMb-4~zxtV=~_{-L$X_nsb) z*s@+z(G<@@Y}J|6)UwL?XqN^%%qP9L83L`}&+XV(CodgoOpmV0Uq^a}Fkq)Xt|RE$ zEeF>i2mMcaQ9J$wH>ad8>H?3ssUJ0L@9X@XBY%ryscHPh5vq+B=y$K@h50(&B?)!3 zZ#Lilsk?_fLdY<_{{^ec&Z(}l)+jLis{AW=n?K8~+W4&;Y;e53^VUkEL6{E4qqe0_ zcNFz=WV4ziAC_RBwpKo+?uGFZ2z`%o>XDedL5;SB)MAU`&cTRL2SQg|`#lfJnR=73 z4fD3vo1~_I+v44%5tf(Q!lfH@P9M7nYdzkE)JR5bNe*~suGL<_tig}4b3h|ORt~j$ z$=ANh(YvtGG){oMj-{D~0GEN_X|IT}9Bm^JdWBQh1OdByT5pqY4oKZ+S*iEXK_%)O z&bSeq6PoggVUW38k#0mpsL)19o$8{m)OsTc>e-M1B zUwYKa%qTjGsqhX=`F+f-#j>e}+tXBbkCj4^ap9VqvmE)=2!(9=<-#X#S?wzp?=(w+ z?xIp>>sAplN(r!VY&&H?+;HE~X%&9at9Un>;sU9gq4Vv*wFmKcl~-M>o1U~>`XqI& z-bz%6UwQ^`HKuJ0f4-tSN(vi_z($NHyWK!jA}A!-?km4=#z#}r1$ok-ny$WkaWj(= zFDDnHU+6j9nSFh8h)7(&|JnFRhwwe%CzK=QyLY?}8(m=v#QUbb%j;+n$xA=80)!;u zxH_`<+!g=fJyxp?SCCzS1O>}ZDo;HO87}4vlcyk+oxNJ^EgEVwn52>AnXXMi_@hGS z_6dDyh&VeT^de6sOwO()Xz>!I&PBGYL^ZS}=Kv{ek729BnFh9Yy8JeDUcJCzH+6r3 z{iTAM+K$;d{65}OVRr&x%R4gF*F0|ALg6z?6G8aYIHWxpt@+v?$P3Z~0q3IQYqKiU zCz}*i+ttkrOfjnUtO@W3zuEr4prG>ZX0rd#pDb?K;Xu`38Ln!7k+hoP)tig$k>XUY z=KE!*|LGWzy&&tbdJ9)&e|TeIFza&ZyAUKJo9hw-TS>k}Y^{_R{fq|}E}FS(e@t=H zs{g!#S~klc2EIh^<~)XHTEj|&>h+TMMbckLyJ}LAxaNDV?!KBTR@hdhrq zJ?>_dJ!3uST3aseucPS?ORfnahR9-W2|r4nmPooV5BNDGursAk`w;~vZLujQwKoNp zG#!K^{n;TLNu6X5o=2<;TU;SM0YvWKtqN+n^K&e-4ay0sU&?yB*I^F29q}KSc0O(7 z1Nw>f7vK3Af0^J}+b2BzErY#EUuoK%r7v@#@tU&YebLre%4}khF}Cn}6JLzh;p1;I&kK6qww^5$4z*>V zqextap)Utj39VeQG}@Ok4e!k$x*sUg8d-CjEWRj1@8*Mp0^ zE%sI80kb*nryZ-_1**5{M2x)DS>hTZXWe{ozFY`^hpH=74XE(ZMSV=0LQL)gZzEgR zFpMQrz4>ji+~}ma?{4s8y~L(Z)+U_Sq+njB(X6fcJzcF|NJTC0z@51u@7B;E@Z!86 zF)LAr>-lx~poOiaQL6g{H`3F)hz`#xJeO%4=SOvBkZVzI_b( zsE7)(Yj`jgMjfoz*qv~MF5dZH9{Q?=uL+fDYsm!(ZGfq?N1Zo(OHb5>n^K91 z8iwr)x1`{K@ZuO^Tw?_KzfL}8SA(R^yy1Xw?t8YPq^7>Z8(VFTjhJ&D80|^U1LvgS zO>@mG3$-PaC^>gdu@TG|LizmeM!wy1)wIAXDn0936X`x1q@q*cFJ%WPa_Z{OhkJSf zFB1W5Mok7y^}@>`f?!RFmKW36uNTcW^rkE0K+f%G7@)1_bS_0V))?5|lz=dfE0p#D z!|GT(@Kg*Miu_wSr;7ES*K(343>nb!)fMT7*Oqr@8iP)`B%M_eOzh5DGL)EiPd{e- zwGa>=_^>^22|tJNP;KO{MJ_~ct4j5(jN0bh?KQ-`S;JH*_b7;`7gHStzP^9`BEJ-r zycnXw4ALA5wGQS4Ttpbq?+;o7sCGD1OQr3;m}MDI*N1&cfA^6Di>)WgCMNIJ(d8wv9><+pxyZ(dl_2pAUK}< zf<uKnKS|vv+_6EkZXz1PzhpwHXS^CsMm_pQ2=eNofH6 z4?kx;*Q-|67bO5-);fpc#2rr5xuPi$Y_kjRdup+lqD+K6X8Ofj)Mz*}syt&*ZLt!{ zfC>4{lkxe{=)TQzjc-Ns6O{30$L^+$jgF5X5?x4i3LAn~APay^8_f`DNxSvUmK*qQ z`QBK^@#6S{faA4AY2|6bHIX|M90lLfi>|@07p+(<>2oH?CmL}|0o*EBt6f{wl;N@i z>iD0VNh*{EXE&er;4JR?Kz*4|g!;k+VDTW_f#*@jYa^GRO_?+lUN!pvA>ISW@i$tN zV?v$ywuaB$NJfgX;$_*Lb(6k2zgxaV!q0lBDJxZc6e*SXG3Dz%O-aBQlGwCXbfAc; z0{~PI9rk<68UVBb05pr1?e%1*BtHr;XWA*f+atGM-28z88-GCXx_9$}IbCBcspsj@ zmE896H+~bBW1Ut-Q+s|ZeDUEG4?1{~O#z(z0y#_#r6AV{C*TRb^o}$slrC(7t*`h* zasTD?4DF8H&GP`IStBz48S8s53 zm#2*HR6D*j;*x1`C z$Cx`fv<+H-{`qnz?Zt^pD)Z~#n=iWu(k?O13-sH*=OM`XU@_81bc;M@#;}o(lzdQY zXdTkf0iHpeEMfiyCUG)ir^x({h7c2mg;nkWw0~dC_KPx-)iK7k{(+o_?i5TX}2EJZF};ceV$xtQjq~V$qXQ( zNZ?I@=q}N(jpJiVT;uP`fX`)2OAX>Ie@2a8%3E7XEfcpard1CXw)_CXqPliZm@+bq zyO|YzAuRWLV8Msr12Fsqa|Joj|{>SO;C2VrEhV7K=ESycJ3kji)`DXYXyVe=;lNB z+a$HOTZIZ*ch~wj?@njR0Gh!fWIii=o<0&nM*ig?nJ(x^h@~msknsRI<^cZkI(r@Q z!wDzq9&~V*2Y0{RKk&Yv z?}xLxSD$m%>gwKIy=zxRtEtFhd?ft{001!L<-TeF0C4YJH~BUVg25qI?L&Q z2LOn0|M!8j$)9=u>Z7Wil$4s9t&^M6cUvcCYI!LsYG+p`YdZ%J0N}Npt7)U9c}OU7 zw{|V57!{PN=%hh_LaiYg8-$CgTOo?3aEU;@A4ggmf$UE~)Q5zGpg00`AbQNl zMdTftqJ+@msF=~GP2Xbs`L4UY@z<6G(fwanxlNPEeJJQ@a$nSWg3v1^DRDMJhkrvi zci6;(Khiq`uu+<l9@(uL1A!x zZb|%#C_(rDai0XqGJv=YTu^StCrv;B0>Es{(qbE+#tJZF4BVRq1m)gm`oaMWQ)vj` z3X%ZSc%Z1S09zrzuSuO4X@DLF0N+~iw*X*~6~G~{Yb^(;Zw2&CV52nxP|yJ!>QUj$ z00duv*&jMOFF;r(0AJ=(SNP&{E%rXk`$}at3b)e>NCz9Du(}}Y>atNXO~?~{#^*JM zH_sAh8}QB~;R@!)+PL})02Cw#^W(tK+&Pe9elx;wq($mP}?IVwice+FA!beGpye0n~pJh4W2ftuVIy zaI46Ot>@Oabq4@kb~^R{V?y~5WE;9U>G^gh_99!r00^>{Pj&$SOuo{yX^l3C4Wj@6 zUkid+>Ltjo`-y>lNaX#9EB$ECX1rmN^uGrru_ZrR1(Ca&G5-pYWQrKCr(iK-|0hnt z-KTCFk>ZTWHlW>xDdLRvXpWNC&(sx+iXia^1<#yjISS4qMvpE5g(x%po~B<80XLSO zdL#;8omMf4M~+h?MvGcoj^a|p6<#3HNTwr6=nsG^`jh5nKZ2ti--%z48gF7QL$kJ~*M=;th{Es^b=UEP6O z2hQqGN|p2}-I=jN*@N_h57(aP7!d@a!%;R&B-zBs#5N?J#B~`;WhAq)B*R)9e4NJ_ z`0c|O{Y7)T67^qN zwRTmm+OZ~?rcI?#MP!vtg_D+8Nr3)KG?M2?>C8qJcV$qPthk5L`r z9?bLmQ^WX@p9f24#tTJftVmU5p@7sEg)h2I8ds^-V#wPWF84WZDyaKR<>^-eF>Cl| z%v;Q4W!y^boFF6nsqCx|H#txKye6PxJK6u%z~B;o>=&cp0oXJE+k%{Wcg1 zh8BN9`b6_dQTa&uG)qIdy^Og`NJ&U(c>M2p(fE8OPqql>0@r@_e)dB4daId%mjMt` z0ciutH|`je>7PQn9K#I+^h@=$4UC&)YI18B<~wUT%W=xvRZ>){^X6>^>n|W9kjhq) z`rSn&SSf4=^C3D|BG?fA>E6eWrZ+)M5C*z!Tso16D`=M>kVp)v3EAzhp|Sn* z-;WxayZwxbqzOCBaJ7Jg`6*=cJ@eSX0CdLi$Z*{Vu>|KRJ<@5;bRN&?6{+v*Umw4w zeO=48;ZzlqV&FQwoUoD&7but}t`w!hpL#BV%~C-a$8d?~$&m2Tdw^oVhL(Mr zcRFfO%7}$hyXEN*GG3*@Ah5Ssv72E)l_Q&d}tf zA1;zrG|DLun=e)z}-xSltw7(vQp^nb^rP%&?xNU4g0uFfcQB= z2kDn^TMUE+Jf~WHqMKeD(EZ6r**@5$e>9}O1pltu%~1eVhr_848hVFYXm?>jIUJOpFVOX_aVQYdvc5p3bbcm@nKrPM?zb zcHMISHwQE9x_c2F>49m(X)DiJ&w-xgpPKH(YNM+T@6(>Z#x*Knmy_WSGap`%=`bM+ z?f>Q|W<<0!66Zf3%VG{w^}Obud9q^gVra+bK)2|ezk2T+-l4AFQDO)#d&K$v+C-kE z@}RJxVE-nWb(tlTt(o;?Xkpl)`d4&%{%FnTSr$fxmBTHp@2zm9HDb4X7&4n;b2%B8 z`!m-+fbC($3|0j3-M5@r|Ayss)`9J|IV1fRyD!uwG+}hv=62Z#+r`su)6J?AGzA~I z9SOgt%&HD|=5%J&wj0^EpZavS_*&l`lI8JDtX_9}Ul6{{&*ce-tae}dINc51-K-O? z2Dmc5OpJ9dc`}~Xbg6ckUW=bdEJ>|K&WRbmt}LFd1b*xDEV!Cpk-b9-51o8Xx=F;6 zLQKp|9E-q+IL_l26%g1GbA35{E|;3Ln*5YU^jh>fR!c<49&kT*yf87HFN_T>gFy$NlszL}UpP&!b)HW8Jy$#_&WhKuRCoNS1DW1z9~y2nr^=sFNK#VX z5I$sFJzIWZg}w7lYD+l{WdOj30RRXJ1ppr2-rGX}z?}mCI5GnO1T)@ws8h1(kPHBh z6;uAJgqGLxe=nZRpe6rx*qk9ns<;MJO@&viQu=dw-fk2)a|j z-ti?M`7d}l{=(BBz#~Wm0Xl*m-F}^pZtiBQV){-}waQ6w z4Z(?9t*O?v>bQ3soN2Fm-rYUlvik)N5D&%>d=9Xr)n)I0@4)FbSPUrubbjeI;06(V z*7=2CHuQgX_$T1q3=0OpyBT_@eeY)Iz}H(0#r&Th{{QdFdMZ=%=?8d@>U7JYm~{@6 zJ|=49>!TaJhAqA|3~@MCwK|j7_Z=TJAB!yf-$e=dVDdpnzVE$5w+^TC|3Ja|O2wSw zInURq-QnKY`Cgx3DDjWp(R{vM=>Dt!bLYC$vrzt1%WHrs6BH=+j+_m?smfQr$iwvY z=gV~}TTm085{x!XoDV| z;fHm^r2?;L)jg4m$qZHl@)G32+>+8H_2$z}!1H_Sgk?W7-Y0hais~qOX7_mh{p!Mh zW(17DtG_gps%d1XZo}i$b^e_n5O3|%HCcT2Aa?n?^-ZO52O&;epSQ!2Q|sP55yl=E z7P=rR6X+j#F@nQovR!$0-+gZ$IsMAB=5^YgvnmuT9XrpQS1dOZ=C~rO7@2PB9o*#f zYQiRZ>lot~!zIK}%n7XzG1K7f{QbO(dYvU_eE*lHjG=(SOmT;jI zRo9=qJ^Fyzg&2AR0}U4cO{9D^)O|O8=$b9hbw9!i{?vf!LcNJp=&pA6i?mkvJm!uOU>R^rsa4x?NxFi%nyXus1Ru*J)btF*K0 zqL|1DL<$q9MS1Le`_#ylf9JUnF=qr*0QC6z1>8HLKllyA+la+A6V!grI2DU@_;d-wmp zH+rLQKnbfuPhxGWa8X9XA`Eo8+_W(`+mip@>c$? zn~%t?CgxMH-I24G!De@fMN?UCHWT~+lMu!Ftm;P=dOa!`iG+1&`RJGh<{2D!R4%v7 zQ?beM1+k&+vsbBKvTP3)e_xJ=3lx#E(`{#PB9O))>YGUBZ_k&*?D*i$u1VsWhL?l& z{H~WzxJd61Q?zFEv^z?jo_0TSlN9Yp(B3^*rad~_xX%S;I4)IxzrP`1Gh-&Ikucy* zYkWuXj0QE3yjPajH|g+Iu?-^a;)*5}{LybKw>*;)MBeoZHz9lpYrFlgx2%7|trex% z9chr20~ZV|;wY#^^XW{ad-ESK0~G7RpT|3&U%9E|DYf5b-r?NMS01PC?2yiQ9AHe) z{0u#o(V9hsmQ;t{##do=3^j_Z)F*4rLBv4}YR}R*P4U$yER(cLP5rMa$nB|^JHEhkFPA^y_r`ykSa?;YZ@LjqqHy<~ z3hvSn>dbil)xf0xuHo2?-tdPREqPy*Q$aXiIcwMTbHfQt7lU1-Qi;sqLodO=N;?>O z*x*IASY$s(&lB!w{8Ia)$I9#4CC#<_GQq<*10esdIX~~e6r1m&uZ4logAKub_S_+R z^d2fsJv6nS;u+s=5?))kK_$D-qi+T(O2vo+K{$k@^0DG*(Gr#=k2ftJ15<>Z!B80-CuMw z;<3&LDHF)H1aZ}Eh-WX|e>M>1dz!ini*FA3a}<;)8@3W_(&ZA{brM^OD_jp^&!cuM zCL~@;8q}n}z!4*inZSk0CwlhNN0W#5c~}eJH@zM6N0fYp;-Ty!QPYpczB?@1rft3h zuxY919?A3T8kg$1J1xQy*~|F?Xrh~pokOsFta~BPk}H4aD59cC2K%Og0?X~{=KDNr zI8N`%XS%)&aI-hmwg$}i3)*Tb5IC5+o?ZGy){MkZAOj*1mCSLJOir~SKv~9w0wkMG zTM2yaZeA|PHo6}C!8;k=99IgP`8Mt4VH@+y?z6}|j4Zp1(wGmq?(cL598DN+4T{p{ zEw4u%@KnT?t^LiVWRALYYoI6C zDNS1qY!sZ`kv#e&9i|k%qf45E9x_k-cQ879p17IN_DcB^A#@|`13Jdxh&!Qd9bS!b ziRZ0d$Yyw?4rWH-aY%WdL)`O z2B}K&l9Nz|`jX|ErtR{t)sz2P9^lO5@V0XJ+I#SQCU^hDPi~oDpuEPze3fbr3T0U4?sWBY{0xC;cI_vav-cAySke8H!>OJsn2`i+|ti z$wE3tljH{UX}-YgtVk$sRf6QGoAZ>$=Y&}Li9oje}D?*F`AS+zH?*}uvN zuZ~svF*5;gxr`cJ&K*UDktH&(6}CW=aceM?XM?o3m8FS2Dynl_JG>MSaJ)<9XAuj} zw)xqL5SbBXD$b^)Ux-Rq!wQ4*p)h#K51@;{J5xK0zb>$|jH3$ZapnP0ir=<8ZUj4#U!N(Y&U`9H#X- zgSs=*;1|P`ZQsO^qI*#h*!p7N#m02w0$rw(d>Mfj(xw4-8HgCGl1yrjG`VYIE!1DI zn@tjy69?V#P5><}LVo*i1Wo&zCmKuR){m{F2$=%$qu9DB&VEY;X{KU;^Y8>I6HUDl z)~`k`Q_cBIUZ2AO-0p@+`^+7`n8qTiszzdxFT6-*IuqgfCBaK`3Ug?^)^aj~Y zwMo6}yz_xdpK4iH4>B^Q66Lc~b$HAS@s$;^f1|*p6k<7ID#@k{z6ekx;WOu|ATPM$ zD93{If9L&fbTEPy+L2Y271k<@vJ71KQTkr9NRn1@$wtux?-$uCr-7O~`mroyG=Sm# zt(C_g#m=M1LhB8d%L>6F1^wSWqKg197p7PQJJ#xtR}Ud07a79r(M6(9Sk}M55njQP zdZ_bR(s?Cq=1Pl)*d9JW>n~x!{0Vj~&_?=*QWT7TnC_gG8#nrfFzME;;uIC+=z z2pTY#lyWG)wun3{ckju+*tGH?2P)Y&%;U4R%%^%QY#{yv#}eIZHTJvuk&;@=NAGuN zwU`G8L$%q8r%@(7m)4fbz@%Z0NzMPc>K9)V`Cx9o&6_WQm^-y@ID|4@8*Ic z7J2wdN%qUUAWcJK{IJH^H0sPJ6@O5&e4%r5vX7PPJgF* zy-H)7(8~*!p$k*aY_$c^`AIXQ@jhnfB-oTR!CRv9Ijs%dS%!f)vssD5DAjl9nkAM9 zo2&-zuYWL|M}?piKSxeWMx8wU&Wp!S)<7w#t{BasA3LbQV2VM9eLuKa_>-Jd^(!fD z3_4GGMPKZw`0h_-q*iT9?D3$O5*wU(2?C3OLADIF+ePG9Fc-UJsiHqN{Qp_%Cs)|C zJuZ4nZf?s5YcXse;xHfri}33W4G!u22@(Ttf);E%Dps@qlAD>GS&7O!BRIx?G(LXV zo=txfXE<9wFm8W@8@ml4G!HNld{rW`7f61=n3=E1iLwX~clC;&Sj+B>M%>sG2^1CB z3r~FEZ>{rB=2AVp<`YxJzgc)b7n*skp%8myCs%yl6Q-auA`^?sn8ZufLSH~`3;2C_ zpGvQ2p3Fr#&J=^h%qrZgn6}Hr;GFuqfdIlllAkw~|ZscyC&y5pV*z1Ln3U*fQ8>B0+y7GJZde6YtFH0*M51|EAMpj%lAa$zxgJB3eIh__1ArIKyQ zLU*^(f**?76tnD3SE)1?5J9DtpU;KK#A1luBu9lA#x&y)9kJEZBW`NIYaS0D?X)$& zox?{~WzA?=(zXsRLw~$F|Dhdc%1-NGVm^vHbdcGAUUwdAX{+EPdeOvv!ecR3n`1!9 zo)ob08gZG>N?w6{_Za`nVv^d z=hhmTzI~Z~d1G?2pXusvD1O{{jGYun^Ojm(@i4wh$@qlB?HS(eI(Cu15K$Le#%Z^- z)AaQB&a|ZIF3o8GWus7F=OsH|Y6;V1v2>!$^lFejtZz!gs(4+K=wv-3eYbYo%PVh^ zmOVphzj;|r3&Vhj`Xg}nM;Wubc9Y!R*aYv1nvQ@sM3~93PRfK==@Y8A{!|k=rS}dC zQv7iTFd0$aL_-@R_)N0|HFT47m0yxfSsxOnj-0fo*CNB5Z+4(T$N&_ z0YvGb2{rSkYVnU`!gQfXAF)yZDUgF^&whLLDC!f+DY9n&Y|Pn)IZcuMD&B(QHj|G& zdyk^TTjvJ5B9+PA?Y^at9E3Hhx%nC!bxNS2(a}RuN`4^ZCE9dS7gQ(!6}JRpiuFT| zW!`0}EFcPeVj0}}MN%2PG5zg@+p`J`W33X7-T7_Hg1g!_JB zbcglz*L|%ry@6bm4lVDTHhs-bq@B}cBRc273wk&dA#AcVVt>q9n~dX>vnwRWqE{U) zdI&5jmtEiQz!^X>*sT}KODbm=m0K<={e=ic38AJ&k=lzzluuS~;Yn*`&VFZC=L6Wg z9Y8`fH^XVN+NOo*rS;y)6WHD2ih(5;r6sep{f3+0sGD#Z(Xc}u1~UmBf9V`Xtak)BNyv;}8^)`Is?LcHJprG+YQK?dWFH*8f6&YsVQUTRw#{14M@F zsga8%U%0ZB9{1}~&1BV!BN%l*ynfP4i#&O}?ljISD`Qa%J3qv_<0zAskLTd2?fq2{ zr>dGX@M-*8bFI1tE#)oYzqQ6LyU%c==3KeB9h9|h{kBDpapBa?@bK$4NhSZ>Yc5lw zaf`gu8*@D=dahk8gzi-h@g03!@r8~wL3W@H@GGj!dJE8foVuxQLTc2^m*R5i1EK7~ zK>*@+I8%SPAIV(VuKROX+g>>CS1lOrVa-f2;rQjPufI&J0=3zf`Q19{^+&u!Z5rk3 z9U}WK3gBuOB(RHH3adzCTvc>-P{uYoFgtxa+j=68+j8>ah!bUqSFxcu>Wn315lY5n zv#BL1oLIMKv&{rHlX=C*pGg-u{(C~n)c_$vZI7z9`d3lA*Mc70B5V}nf`=R=8Y|9= zFnjQdv%~tX>RH2a3C)SAcahvzH`>_J8r2|0HQ$UTp*O-+RTlpCxoxxMdONno4XNUa zXviEjG-?xf%+W~K0 zK*n6e(6_>5#H*41a#lMNb;uQtoj%nd*FtHkX+Ls`Ogn`&R$loN!CyqbIYjYR3`Q)I zVwNw!>N_3+0GK;rJ3#ge)tlJt!*%Aj`FEzLhW2fChWfkMC5D!6 zQlVL&t&Zs`r0iRp+XJp)I=kfR`C;NKJQ#20%g>HS)8m7YC_BZj$N*wwkW{Pz*OP=YgmEj6kfF%+8ZTU7dOfvn*pd zzOwSl;YPcty}hB*r!S)&GWwzr*Y_(Rj*;%NYRH`;x#Zme@{5RqrV{nYZl|vzpda}xxxiS5F?WbQ99`iiZ;T&iBG)==E|~q@RdD!5B!Z=<8 z6>$?C{sdKRvPM4{lXPNBlxLLSzJh6Ryas0X>kmJ!%{d~|t@DE)Wxt5?6U4=1%eL>m zB%-L=^wOvk{Mn57WoLQ4B;=O==}8t~FKVdGjagIg`gjE=&huz<4QgRDi1o!Un0?Q- zZrJd8`y|Q7G+AaJUScF;9 zfF9z;SzPt8>m0sfpI+aGCGoRjKl7z5TFe;P*9Ak)M& zhlgwlxabig5ig>#R$uGZ``srBQVVLnRsA|xGhTVDVf?b+>cKbQ>z?v5SpVhT7n(t~ zHAa?4TWaCAKl_zTx0lu*?x@=PzM6LTaewX3S$R(PZOw^8j=_cX*t5>vC(xhxhE08N zRS20|&SN5PP={^FBI4}5jAO{txKUb0e}2p_7_^B@ncEgSA5lDrchpf-i?;Sq>*?a> zI*KfM9kASGWeJuuo+s{09wX3~Py3Rqj3Zf0Pw)M8=^J6Jz&@MF)@6#5^fIOv;?)K5 z^t;3Kq;Dui#t+5(DV&4ArKL#Xz(i6>} zh{4-MslRbU92)wrj8aRPj+42S58W&-f#vXfDa?cYL2X15_7zoW|5<%qC2nl~eMS2` zRV=rUqB;E!qc&Nt#yR9i?v1jWA&$GB%(iCoPzYnbUup%6Zbab-TCb->@~b9=ky$yV zNIAGh80hfzbA?j$Njr+A=3G{hZ9SMQT3Vg7ES6cOl}T|XS~4z`sa2vqLWSGRzFt;` z0oX_~dzj(YVCE+Xvaye>)~(~3zhRDgw0S*nNvC}|cvqUe-+b3qlgq!F%jx`U2FHRwk3s_U zTIbs|W-k1;IQkk1wnuWziYF)VRH=Po_`}2ht#`;L0d0@7I>6YFVK-h&PC>+PMvAk? zVQotYDh1O>!%f6)zFOf0oS1|J!TX%qpg8E47y~myIE4qGwdKSqc=iyQqayp;T!~A$ zQb}QFnOlK2j^=#+;I~MlkA%*r>nD+9X$gFvHkJ5&Z@2A?!$W_!8s;oQMP^>!2sy>s zd4eRH+2b~JAE9d`Q4joSJ>F^RuV$Qif7?7>+{3u##?*8XUCJYEB8ko41>aQ5S=2~= zh_ng*j(mI)lH$j`S=0nPYv?f7LCMGIVUF?cDGy3D*A=poT?qnz?!PzwuLg>h+>?`EuTS7fpd-QcWl`E z(-xd~obR9K%jc-ZT81#+2$(xaE0`^1#RYOpn{X^1xOh0FH~tY26`(;EN*xM;ArFho z{fn+>%#k;?(1)7|+Gp~c9O7bCAReV1T0Qvk-&b4kqEOTLo(@$hf_#>m4nyq;O}Mxn z+OWruw1R~*b$n7<$rlJ4JGB^&QLS3PtVm#Zk)!Vom{(K6jDRjg5Os)(~i;x`!f+ z^r))r4_Z}O2@C_3oT!!TOZGT(6Vk$!i*Y))qKlRS`CUCQUs6vC9Lzv}JV;ZMv^ZK+ zNm^A!8}k8Ze^{oa#b7{O)dh7S|5}ZcCSATT=&ER7ml4OPmnO+1tv?^uO{_Su29*{b z-gEPdvo!=NPN8+6faRJ}f0^8JhUHS$yax-$@j|z%^X?Cfano=4?6Qc{jUq8aG_ixa zY2y-mWI*^S@Zjq4r1}mT?eOC_qwA!Z0Swx>X&Zuh)9jP-H3` zwhEI7N`;?Ibt?|C%frKZyxxvNzPqz)krAZyt~vZF73R^98p(@c|3di^5ZMvos5Sd1 zt%`-IDW)ah>wBJ|*AP2gRJx|YFTF@lZI2}HtjK+EWCSkZm3^9?Cu9jQh<84S3qsR0kDB>FX&ajM!Lg$!9J*r&Cp zRv9Gx-IET-AxvXc)8G`%ozfb+EZvoZmrMn*^U?9K7I4U5OS8EfrhF36P~e7NzpJc{ z5FT?*9YBFg8#1$xZ%SuZC**;++PX`nF#u;!NBkm+>1)_Xu6?;{tVOfc|BGsZSU+$K^nL{*yfiVfzcz||ozvYu6ccTm zxZfd^SRB@BsvE#FRZAoBbo4uIy@x(@>M%@t4n&P58o`rg2@bc=$bY%T!W?NNpNy?s zPLokwCfw7Pfk85K@H{Lakk)qRP2mxX39`E0{#+qX=T%UzkEYe4OBg=sunt14Y%y%N zq8dyW2gOx9E{A->Aygvz!e-n(x~Fx;ME*8t#x%l`XNEX{*KhO-eU|w-a35-qkjJlF9 zl-rS^7Z_z9`k{lR)pxxLW2t-)dXOb&2pNLG(=ofx5td7~HH)H6W6m~-1_+CgL`PC! z;SZn@Mo07OB!E`Z%8IJ<0@gIrKiyFhtNtJO1)SjL_9MZBPh?D zB&T>^=J1wnk@uPzEL5-^&R=U`qJr8%8BE0?Nckmmg86eikxX+9y%S4J^Y074dA!Z96JV^U)|vXO?>k-W`TJOrbP8r6qtHj|)$S zpt3PNrSVhX!D4_K z6wZN!7VJoh6bh$pT!`V|bXNaI6aD-i&uPHbcu)VMb$el(9qEyEre@~2HG)Lc8DcXy zv|6~N<-a_%fvBn~TNvTwdm3?k~;lzv?%@Q5rTEN=yv#1thGH|cSws>io zT#fJxrm70|K>yu`BcF%6Inn`s3rl|r*p=P5D|4kIS+Ya0Bab<@g%EE??FBh9Q{L$8 zA==iue%9lVOba-7cF7klRJP}7tOarDVN@D`^;THtzxRQB-S_9o*t%rqWba3J|9#`K zJ*QX&(YdoT>^4!2KH_SwdHrT;fr>cZNikpu!xB4R;)JX2V7lBe*_q08am*^kIz8Q zUn|upS8@FASTl1HDzZo??gSRQ=~lZAo^!c>byuWh&*9AIaO}*E{SBHpJ)1}GX~7#z}=GEaO=(|6*S_; z%XoT&4N1!RIfJ98*>~Vk5gwtqq`A41CeYbm|rV2YVj9eL&;D zHClL4EV-;BuwyP>YrN5Bl1gJes2wh8Nm^KC%b-hKawD={NVL+K~^Pw$>J#VBCMCE&G~s+4o; z*e$7}aE3tH*wxufu5JP3zi89-2fJqIWt#-jz{lo^LGK(v$N*npSi>w!*wDY8N zx@zqsOdBJ3JwKnpfF^qPJgGU42+?FOW(CT`BC_%n+N7wyAGBDQi|xmFr%2$8fL*3} z8S9}{0;i{o12JYNzXq->&T1}evUJ3B$b)b(i=m0vCWxH^=`)0N|LPR>Cn^G@pfUEi}g+k&Dm5>yK<*hLKTMD$mvOSHQ>`t?1c1q;Ws zY`HqqHDyenjeu6nJwuJp>)8uKV)(rYzY1_DhYtO>LKhAk#2z_5-uvJKnC(WB(?wsm zo0X$WbLtK@%X#)o(gGEUEw?1$) zTz~gccK_?;)Oz4rY0{R<1K~@nu#)w`7pEJ4Lo!+@v*YOw$frKfEEh;(bRkbK1AFcM zOxFkQ@sHAuyT;HZ!lt9o*8SlMbmlLZJ)Mzd zpvwgMVz>j`!(%fR9HXsiutgc5jAX>yi%o5^VQcL64DP(U6A;a6Y7(+?`kpOA<`aq; zJCx&=R75t2=hP{;3Q;2Z4uN>sx{vk0!qslS|B-3f>UgzcW4gIawjj{iH26;28NrUL zU6Vtf_th0HCSO~rcEM+DBTg@CXR>6<>Q;IRz_VwhqmCgBUJ56?g~`ezTxNRdX|)9m zu>JW-X|O`xz8sD1SQ^Krvr#1V(Z*tO%L&GWG5@dwZ`m)jFi5*+AXTOCgt4fs!QOpg zqQv*RQ-2T0!XcDqb|h!S>$iiTguh?mkVX+$W^l0|~nN3s8YSbqmL zpxe!I7w-SY|7pghjIA-n%g}Sy)X)RnxsO^Hrb|7uSWYv+o@&mrOEM=7#^wEyY$Z^; zyD4Pc_^XJlqhusQ$P@RSWx>?62)wl|!rq10*bYa23T1v_Ve|dJY)-y+zV#wQhZmf( zcT^h&?`N8)>&AVcMLEYAZvitS&Za~^8U%|YBDwO0UTmVC`RxmF{@goV?F=PH3mF(PRx4fJIzgA%g*tarQAyc?BbH&^6j%g2R-D+J1I2)RO?&qD(?w935DxEu znBE`uD*jBo*k*IY$GL`7#2~1T_Wu_evq(cW*CCVWK${`WACh$CB-X*OmG%f*;T4BeK8 znAvtTLdJfy$|T_&2~JvDZH_=c4fl}}(l`mse68rjXnvu2Kj%!{&Ri9_d7gt1w zZ}&i{yPGgIh=AOAXoiGM-{B#r0?B)F#%A(8s}6j@a6^+Qmo>3ZNKj?ILNNR$Oa`y( za(61?xZX4Q_(&qNO>#AqGd?c*(ldF#lgb};=q}U@4hFLe;T?C#P*Y+W${z{Z@aEJ0 z9J`$tAO~44XHln4mbCirStf-h8lcAp|1 zaQte5PmCVCzughiw#{K0=XKg{!)Y+ZJr&(a--Jl@^kBLHz0v)1iCc-){{1B5CLTit z(}Z#49U_;ksdJ6UR-D-zj~F%eiz~~PV6Yr<#7dWU3O{8*>R64?UBf%+W^vQJ6zSa+ z$ypMTO4i0#9^4(%<@a&MGUsURKu45!5%FSU?fqzXQA&w61`_NTH_iVdOVmLEHRf2ZK(oA=%tccM+`^t&$Z?OT1ia|R}! z+!(7Q%3gPVn8wx_fS61Z;N@QKs8xuN$*Tko1|$Z6*BGMbW#A!uKN2PGb~)- z$eH*ZN_*USpbCkcQjvf6aEfdxoOJUGrLM1xiP#r?(Vs9{dwrSCx;rd;oC$F(HY|CU zI9?a@F~t=wAENdHUWXTrVbDf=pZ3keF}*$ehSH~Wqm#E5m7Rv}?h#<7>+zoZdm{YQ zJ!;jt-F$dPOMCF}f+n`PXxdD^Mfgu`JeX|DEPm?l+&H`O?-X?E5P?2cz`O80f>&cx zF!<*lZ-;1r)>p4bPfcG)p{7lcmMVv+pS2~(MrKd;{&s$hOKX<987nZ?hQuy8d0!v_ ztVtlyQ~l>M)vERoE3)e$vwU%F6=Z4oK{2QqVZro)9Lo_Qh~_lx7seBlot}eRx_m>` z&f1G1w`M%m(Ek8xL6yD~t>*QcH_%2f9?b~D9+qWM<~6hFvdVGc;sspSrPCd7>&A69 zwzl|9@A*zX_tMM!ga7S+T3(Zs4dsNxyGK0w$Yc1nN92XbRzuU$^+O)La)qt!9gYr< z$&-xPe9mk(XKihR&<}Y3`+k<%G-TixxFk zuHIvMa75FTRAtWE`X;Zw_BwS9LD*q5n{oN_1$y0x&GjCp)mTE~$d+VsOs!mk&_gLj zT{To?g>GAvs|mw^qG)g&hqd)B_76{KkZf&lbL-9nZr@Ij62dsdbz9QJqR5l)wF)Gb z|0R_K4H8|}93Na`^Xyx`P6XiLh2~Q~|5Ma;g|eOH2HP*=Y-H0m1a9zONZa|ZJms>! zIpqGqU8eIXaTwvb4q@n{TpM&ty2v>lPpGN}ttCb(+A7C)J>p@6kS$$5z?OpPWJGV! zC5QtMf(M7kq}d5%hjtp zD5da1kJHl=s-i+UkY@?UlT)PUK&iQV@0k6A6XI^4UcdV*?Yw!KF*-VCHl6Xdr=OzJ z>GELzfTBow>Y1mB;tucozz2E%`#;R~?jEhy?4CJ;D{NkW=~Wyz!1DsqMTX-pTW3$l zBd%V(O6Ui4qL{j@aU7Snt?_(Cmd~+mc>Py8Zh&Yk6n zH-8JkB4w852-{(I_Y#-3&v80FrV}kYGp|1SBze&i1RYuod7ffhhAUUD(9{)WUNJtM zptYcB48jr&J3}U7%6K+oeQih>#YiP^0}D3@FlvqyM#NFb$;p^*x5NIueY(An{xF~| zTb}-wXJ~bD7rwp(;Pl`wpZWODa{0p5FLPVJq175C zoj+%p_P=i0n*R1CX}ds$l5_pD=&GfxN<7cQ4i!g-Bc`J{ai>olb(qf+;!cdR6;-uN z)CpsVg9u9qJln-c!DzG~uS$}v!0~*vUas3RmWAWE*vi839NJ1REv)47_qEX|3(C5n z)hsQp<#Ljze&`*bjX_xu1n$!9McF9JBArh$G@RYp;CHC9zzKMhGm+S)FtJr7@Q;T;jXG?{S`b(>X3*ID-(9(aCh#0HQ0R zz~januTj^U#~yz>#e*>#7u~ivVaS?tnaxI|^BJ}uVLLICv_&d`twI+0oNhNl*M`Y# zjO_%=XwgqdK?8n~AVtR@fJpCnvHHN0Cu*^eaPP5D> z2q|babi>Xc>bTK=YuTWD393ey6>%qAuBbv1`aWS4fCY73Gg#}fKHOkBU0}Nk*K?Mi z7lPTsdDs%_a&Z3vZPQTJHBR8u?G6dUK6Rrx9*rrIlpqMvx~3>gf*{6seRLF3mK9Z% zV{}7P8KjU1DN)LPI1#v91Gd~2Y-rn>x)$`}0kipp(dh|Wn?s&@%hR0M*~IfCi`gkh z2L~kMDUO7#jdi@hTQ;QlA-3%?osGEr;5CeCh{AyJY)07Y^W^!feC%VNCYc@Zt?zm} zo*OcsC*)2%gOIv$}9$GAa2 zmgU?%c!2FF`onehkB-<{-@^BOl4OBKFO5x$l*MdL7>9&$j1b@(pQ_5)-QGZXhJXAo z{~nHOp+ujfTZbeQ!|CXh^N*gz4ZEDo$J9bG9nDzGXDDIu9pCm0Klr=8oAY}evgDNU z$$cyp(Mpe|)pWZN{q-)c?QreIm)KrEN2k+ce>BJSI!yB#TIdaT@rslr&8e!2rqS3! zE&B$l8auC$!ou-=+Nz=~Gi>3|>vhSBlDNC9%oJ4#LLh{VK~S{~%68}sI-H)4NEb8K zhkdqpw^_`m6h%Q*HyrQZ;laUeE{yGotfq zMmY*$gAszsY>bqOZq&h6*24+2mTrH5)`qgK3FC;4>~M5^%%~`rwS8$(w-u_jNV6Gv z*3dL9k!xe=7-iX%O~YcgAe$#Nra?+~nSd1yzGoA<9#UJ_j>9~gQPnMu=h4&+wz8G85#QA8Y{mi01{grecw0oQ@_S z1W^=_Y(SK4oit17!(LPLFV0 zhs9!!8~O}~Ylw$KPeqj>;zj1EGM8b5}{f;Js;n9DU$># z6rtzx`l~PS$eW)gh$K~8;953S*|Io3VQXVZSrpWHf&_}B0wL*k24s0gk}MFWrLC4% zoJQAVRY6ggRORx%Z!3o=?4m41S}b!p6gk~Nk3}*?T84hVL(?osiYZ0i((4Y`-q}If zEspZf3g!P6;3vM+-nU<#5BSJGy5M2qeR&@%qBCkd_o+#xKTv9$f)a*w$VsS62&oPS%NXk(7de}b_Q6sLs=FW zZJ1A{2sBg$fvf07101z_CfU1swM zds{m+7}oo1*vh8J60X1T3J0E#D{ThDb$b0CaS)@k8etlyvk~L@7$Xh6UXL<`*I&Jk zqxw9!k#Y6XfZzGO@8PX)zs%d7zKA&Y47G0f+zX%M>{^d^y#1X#@%UrxY;ACSbi~1f zV>Y+?v~`W+fDtWj;NsaJbU`|;m`z5@pVQervbk4}={`+0B@P2z zM=UMeb4^*4WNJpYJHWARrsENsS=|vXfBPEuhJg1j}fQ07qHqc4=)lIUS*! zhThtcqmv_COQ0MdOSWhsu^pdt=dR#I9{J?{f8h7`e&W}A{=Vh{@Zj3>jP`F6cKVcG z;)?F&6`-a?qT7b1mJ}j+BD8`JwkGIhd5PoOw7Mb=eL8`Qu9u^_#sY`sNma=K})I^YHwD{o|$cO)m)O1|j>0$6R~;8ig>-<{7^0 z(TzJKS;5x&7TxX|2uRb6)6tyjVZw7XJ|(d&q_LRIQ{L8lhUY%@StheFTW8id934`d z1})%po^f(~ier1+-9O-azURC7Gk@|A(jP`BPjcthP2TrE|79MzdW8?X_eXj8#g|#k zW^4?*ET+cB7u#_#t)|Kgl;<%BLX?oG+MsL+q9(cf(H|#RMwBnyxm%i=SW-9@k1Ox zrmdGHBc&p`-R{yGKFe@z2jBOZ&F2((ffx7)vFuHX+@-6GvIJFAQRU0t?j)HJ1pdS2 z8#(4-hg_>Gn%b~;W*0y3@mvSfwvMQf+ToKQ`*}Y3^X~^a``&TqvsaP!(V4)*uKwy7JNoarq{@>75RZ}9SKuW|j2H~7#8KT1)yoH;)r>Ns4w@F?H%&UdmXGL#b_ zj7^>{ptX?NC2%Cd7G&iziRgrqjkR?i+`P*tfAQmNZl56veJo4x+G}qxo-DX{{xazz z!*^4JfWg`(X_~NDWMC=K0%4Y2nR%YmWCf;akwVZLbWyg&$>|hNIp})1uDxy=v|Mg~ z4ug;=j>)TL8KEi*AqA#wk&Z`I%)HZ^zxW4*ckaJ2P4-2fou)3S%7X0+Pd&5=GhbZ+ z&`raQXFo_bJ;ib&ApbHPeglHDflxo!mgdsIgF~7+ryuvIs~o8`mavxl=cP;a(8Mx| zW7dXi%w{tj*ToNf@?z=6pQG zgEcHmkYx#Zkusmna2Ke!Oj}T z`v>%UJ@T?-Fc{J_4aY~vC?y$9M)<0Wk}k7(#>r?x82RYBWip;}us`Ct&pgi)k3UM= zG`L}i<@xM7KEt+Uk>)H;65=Sv79O{6-(xXPh`JqY$0yAas@mZC4jn(jaRLIrhY{fV z7H%Ap&NRY_==OTtxqF|r!5U|_dPvc*vC-ku`AfX=%5Co7z0RxG-r(b(c!MAQp+Cg8 zKDEnZk6z%;tp`X~@u&Y!e~OdiV~nVY;*guKU+0%T@iCr%=@l%?<1hTVzl7M_^yw63EnS<9rO{1`Zd#hE#`it6hNdw@afGrZMUgXK%&~2o))*}5QB@@vLECDC zk~qqx$qLFmCG2)_y%0IR{-@??`?ur%&f=G^5nu507r|_NkIgerVma3NO7U#x<3=E@KRPb7#CtcgtkW4hO%syB{Tu8sqll4?qC4G>3Bp{ zFUMwQi=~-no)<(xgQaX-$0kb?z@RmhWx;TLgUNhBe{G#!Z%AD?xUP@Z6%uHwayg$M zmWwr%t*GjXGRr9Pn)z%-+g8kjIb~TOjDzdD9335CNy(FsJxbM7gpo(tZXasp#@Z_U!g4U211%4P)6q;VY%f*YA zsN0(L%`K{`Kq-qR6Zoz}U@zT#>bBwD{rgl}BN5bD#phmrg{Pl>8=TfZSeZ_do+n4nMxKj&7b%q{}Jnh9j229y!h;= z*g1QFN1l9)xEtfg%U%+Rq}v-Xos4+wvCDk${XfTl@`wH)KlH;t%)7tiTS>E&CR^H* z$G(T>SiE%eCc8UZ?3~-CEE|N@%Us83%;hVO@cQ+e{Of=H0e<%n{V=`W0Lzv%db!xe zb{&+GG}W@lztiawZH1)sg!yz%5C%v~QB*ZTH8i?LDMc8Dn5HH2Jg@`^jc97hB1b3- zM;d6BvmYlX$E>Y&32k-83*-Of%v0a{-!=K{iw>2gWntOwuPYB={nfwnS1gtWHeXb~ z|BD~~3Cd)IMp9NaWmVD&!*H-h==!AD0)$>#Cpj**7tuBqqAl^l9>ze`7@DS`%oaGV zy5w5c2YsQgidInQ5-l{Y5V%TU+U55sn+D$x@jRa>jvm%sAC7xk5+jy(-m0odlN8_c zm!9~r^x=;}f-ofR3>a>15p=pZPK1#TLO6JCw5)MUxttP|0;L@Mz{ho#{&RIvP!<(y z!*xQ>XEvMR2O&{Bpsq@!sJU?d0&8mUE#h^nq=jiKM^(Hg_S$uX1poT9FoPZQSqTTAy@OJiAr>1<4|-^F!p z?moE7v(G=raM))!Ttf?R!ioa~!F7)7A65@yaLZ zMmaA(`%zBr-NR9m=U?~~M&!Kht(Q5s*W}^D%8vQK!qyy=6N%;c`%>(~0Tzdf2wdVv%rsc*Jx(!L$up zw}hTY=z1Vvduy9+e~1_O%avFHI=wEA?|>yKYp7AQLSoQ(oe9AmuARGrj?qfM2 zCx<6I`>D_J6F>1YeBc8gq!;&i;_)Y#j3?}DZ!_%m8J~`D9G4)9D61OB^{86|2o}kl z@qEg7HfEM2IJV2i`Z{&h(Czn;uET7WP*uy>0$m#%+a(Ml%B*0KWPqS;;PiA#5X96? zLs6wz5_(=2U%9+~`zA;8IYHQE=&j+}F>inSx8XaAz;3v5ex1?LbI`v%|t+rOK4z5N;f*+2R*%4xzo-*Se) zmHhp`_xE}4&whkI_9y-rRjHYbGdgh}%Mu(O9HLuIpQc$-QX)*(0@H`LQ*3@N9k|vyvr)cT1ckUc-e(KFcy&iY>A8>GR zNUzhSYAQbc?5Fv$pZJ%YoE)>YwZULG#0w)n_<^6}r+@M%xOMXeLRt(513Gccd^Y8g ztCtxJ`rLfu4KVGprfx~frlDz<`})R{3AHv@&eDh^?sRBd%_2!L1`dvnXxf&+a7dQs z2n2Oi)9?047IR*C`30_Ay-ZU>7=&!^?9l7=I6OXNZ8*e}Hai=;m{xE)9W$EG`N1Ff ze*O=?`M295sBdiTNyzUL%knrp zI%Q*HohS;(^NbID;Ac5GIpp!jAH#QDKKhXlarN?9p8xD;_=%r-FMr`L{5jtJ?cc`f z=@{L#IJP8+Jc1y^bv(+vVL05t@gh90N8Ii3?596XnUxIt1Cm8bRW;0K3);reiTjL3 zr{rbAczlfOIb1k@j`e<@jrDbW-$geS$`aU0VObK>7=#AP0!v8J`J5_WcB%?%X_Q*m zHLVs)_vyAiD=hgxJFaVNrLb-L3;IyX!V9{@>sJugmvo!AzBoSsDY^Ihi+u1O{!MoG z&f<6$Nt!WBQs&8=N@%WLdX$Y$pX1YgjBKzS8#jtS&ktZqcjF=gfuSFiG?|Ky(_OEa>(;8V{% z&r2`8hAkzAc4?d$hD@hZ`rVkVtu0>o+>1*G-liprJ0K)M7}B(w+qZACzOl*n?s>|x zW-^&P95ynvO^tbIRi-RK7zP|39^*O=SFT(kjAP0o$8#lbdFxxbbLRo~AMA7C(iKMI z2}M~m8J$wh7iuM{pc>X`*Ud)J4LLYp-A{%~NlA64$eN{`nWUe`m~mzF=!-hl`gk zbMO8U-LA{p#u`x^VObWFaYCLpeC!uL$$LNa9HV)PvK-#>_`1Wu6HjWMsdGV!}xOewHw{G0Sv3;tx#t(f0*Iim>Hw}($QI!=* znj(}(QB*X#K|ahw7{IiGIPNSb%WR(q4-QDPjOk=b7c0clfja`CsOJ|LTKWKDUmeYu^3#xAN4JSGj-xfV5ez zM%!55pgS1wndd*tU~Nd6B{-JF)yr3whAxNqv6SM>*>gCKPhD3eWrh@z*b8Wznxp-D zJooG;kz%=3*-}F`H@B&ql7IG3{{_!Ica7io`@WZp7te9~{yx%jX=_6i^@!sk&%SVt zMOxDD_SoItU~R33)-~CD0-3 zE`R4ACtN@OB?-V69ybU$xceG6UwxKAu>Qqo`GvqzmgC!{l%&;AZCKlcK*B{{pX!RFu*X0sXN<6~&x2Y<&8@W_)_`Pc7zKWUoLG!0d;+!HvT zEtXwlO-rL%>bk~OHj8BO@It0&v>=K*#GQEgWDv1*{#Bo4bHe_i=o@HolXpBZk z!O3_+)D7uU2;J>g4KnRR6eBfXF_q_hO zPqNiNM;v-zPy}Gv3O9;~`s?3aC)t0QO=fSOpU%jW6xRut9(;blaL`35izJ&91U^CF zU1*TSFK{oSu&O<-1=pFWB7LMC+Ete9m+>V?IrJ=3nsHEy^SHa z?>}ICI;G!i4b+)!TJn_U8_VzaU_HTVB$t*=zC2eDP z@s%6gJsh*h3Jwkq*zCaBwE$_mT)cdh&Fvl5H#QheMkrg+@AXOZ46QYFRUn0+t}2RR zS>CKng|sZ9u#0Id+SbsN6;)PH)itwpf$#bF?y`%ostTIAL@Ger7;I32JkKbK0!uB; zU8}05t}HBRBQ1lamb(cp1X3HyvOo)ivJ}4Wu~;mYU8Rmk)7B{4M_C?4o>0{Vd%GJ* z(I7-c+zGJda%&RCkY)u&Ko~}pdTC~=+YIWK$PKBq!SAj8XJ;-x_CILde!)pSqZ_0f z5$wHn8Kr-5{+nOm6+lW3ZoR@!|D(T->-Pxo=|sW*Uje91f#KV_^KlETns*i@CZGRx-9TIJ+y%z`-vZC z|K5E~FuHwXNXiMStu4}6rc zn9mnfT4THJ@=4s(gkA`(CQWmyreQGX;s!3sVn$on*v4AUx-=E`L&xT(X%IpH8Y47K zSudBJuoRI-*LbdtCBgG-;=aFh9}bqU{dhLRwmrIoHD9fBN5X`}NP@`CUR0{MrK09ddkfuj2;JpWNHp z`isi)JJ(*j#$q<7*YDG|hPKw|+ECR6m#>^BO%slf4+ujKA;A@rD$iKk*g#;D;IL{-+GB-n|K1wpiB$~M)6?a%{dJy@X!%YL#YM>s-yqbXaqLR6@uERldnfgnK; z6eth?fdZ<(_ttGsn{&}iDOMAH#QK5>v>ONUPNBuPM)<&=3%*D2a= zK&gjej5Pg_br>czZ3n`lDqB>iu`TOv&LZe_hvV7!j!o!$RJ)QiP0_ZdC~{oaC5aMT z&!H#^5Rk2Isq-AAB%T|wm~T*8(e*u^?-7q(maAL3re|;ekcY3lMlzlfBq7RJNEy-g zb5`3Mrh9J?Mi1$l?$24S_21cUfDrcgb=MkWuxuMEOon5_|L6rkN^Z_S;cxz@|5t)2 zK`Xvz0ccRX{^(7naq{PAs{h;m(ZSpKE@!@2v05(igJ4+8SrX6jNP`s1mYm)@rEV&o zpFd^0+mMc9QYS&xJ;JrPT`sAc2G2|AYDKTi9cHV$15YTFYOrmI>D8d)#zJY${YMXw z4s15dp|}V|v0LIf4we<%g+N$1wvFjp+NK~#W8yUClTSY3{PG;n2>=H#i1_>ue4eBI zDGD{@(hI?p7tgp|WZb*=0O>lMK6uE(lY96F$51tFudk47~}gP zOsRp&x#S^Q$Ws%M~wQyyV08KcOlc z&My{>Mk#;sU;9_tpQcnz#^*lwCbnY{Cn?5pd3pYV<@|=eF3@d-YYDWf*gty6|L_mL zM!DPZhkyJ>QMyBQ-Q8TqVbBr^j2<|MNtz;Um#XPGI6k84Dz@1UV+?5&(|0|(uW?am zW6-XJ?RebG=gcPi#6iUTW{xXudLgM*M-+N2mN(drP2l+mDXFRkVL9||M_rVI9oKS* z)0pXWMyFbq>nm(&lLQe(UZE6hvMqJflZ+w^n#lI(>IQ?2(t=ub10C9ShNrqPpnIC8 zB5?0go6->J51S?Dj;`$cK6zeH*Td@_$0>EyuwD&tlCD!o%Vj*7Vx*!fGrF?k{N)9n zoAT(*x7l?iC-+Xt@{*g23qsdpHa%oKJ7MqstKV?agC8k(>&EmYw&Q-^ae(S-yz~ew zNdIXEf!}8cc-Mw}``3T&PAO*dy)?_(7^D<$JBWWrx6PkdeaF+MPsz4BEXyHHClo~q zKpI5&zDrS8)OAHxlx#K|!Z0H8573R|#e78&dU(Re4r98eqpKUnqZvYJ+N#3yEVS{E z0_p~K*%o2hI8spMOTsu}etSV#RNQyW7>wg@^L*M zr7aN7U521y;)h7bMkzzK$q+{1xDv}Aq_sUg*>20!88J;~)J2Xm2H6WDFTr=gwQagy z0~V9X6lpoMP0w_;Pp=JioiQFA;Yf?Js`=!Tb7n`Utn-XrzC&0xqtSGbfD4PF>ewu9 z-tlVn@4fi&EB|J-{(!yxWAyjAsdeArr-z(=`Cmj@_CHk@@O=xw)$q<}H0M=uJ!2bTnNK#wOo2 zRMifFO^~?cZO`@UYFPg{E|!$^ZG$BQeY+#@Vhje?b$EFHO{%70K3}t5FYp47G>xzw ziE1UT1XGu+Z*GvL$914(!QTefyk)g9C&VM3IMuq0BR~Y>l*iusm*;OAHo= zhi8LM-HR}Q>y4R=dmJ|)bYfJwBVTR!tN+Pg;XChri~r@n@~@(>SS&XjAI@0Z&QZ!B zvG9Et%avSTzoaO(M4rWRy=L#=kPMq`+3>k9e4c;v_yM!=A=P@zx4!c+k6ype8*jhC z_3fPPc8fqFq(?kTY3hPk9-eV}bVyYdxSq@P^(95oF&&R_lbBu$Y%igy3gUQz-M37d zo;TijZ5Sj8Nm-T{rAeX`$Fo^)R%q4ZIUZHrQf-!Wy{6n%L}`fUJM4B_=C>IF1*SvV z4t=Mv9C_C&`!rQcqbdZDrYUyslNCFPqF^?gVcRx~#S+^Juq}%)4#`A@1f-C-&Jgdd zl)`fZ-01;zkrQ}6uItd$EspEbv<*dG(zZ~dd2n`&Z9}IUELYNNMVdyq4m5Q;guR9# zmb7V_nx<~hy`XOmp`TKf1zoEN!;q%$*%l?!*#s$js;VM#Tr9`Ma)-}+82JdJXxc&O zbZ~gYW|Q&Yl?QzCIf}_6kg~@|Ak|`Ki_2?UD&YQtWc&W z3MZ6hMOiesu7zdUD5V*XQu@AUnQf5>d{?lT&*^%_8?U`V-6^D0AYmLQq)9~R2V~0? z>-i05C&%1-bi#I*ad>cy=XqS+EV#U$qqOAs59UpLZ{}qmo&anLmBV9)60ae@484J&I zIhdwcw!?h!3;g=8y~hv!;5$T~Pcl9rne|Ner`+DWbho}j?h|hefgAZ9g>blgno$Q*(9SQX0tt9TM|5X z$+8W$Yva2ve(xcqg>alZ-eE_Qj8Mu@Y__!RfWNKtfR@cBBMd^04o^9MdC9iQshbMl_mDxrZo3^a90A9% z@w_3CSvn5WgFS?h^a7ME@sf}<9&>%O;N|%XTo=Zplr)Wrk{HLeF=(U^1pc5Osj3zs zB+?3o!K!1SjG*oe(hiWepr~5>DCBTD1*53Dk|;?j^DSWt+TGO$Ljm}w z0EK*SDX^5xFCX(8|NUPhPN(110%&b;g+mkuw0-khQ?@^48u35bt#;S~k~AdSZg4!0 zrtUFDqV)ik5JDj3kaBu+JEv`XX446q^^!CVdHXZ3GnyuBiWPxt5!*J7W#L&i*?P@p zz2g4SF|$cZt#azFq^v4F{p4dVuP*7-KyWSUmT!M}&T6|upn2t$dz_vg(6l*uvEl6O zjHc{)d3nvv^$p+o&IfF>g5#qTk|-sLMx^NsKO8Zh9?`Ut)hcJZt8l##Hwx%_Mc>12 zTYzu~!i4$lE%W7ytHql2HX|9u{LmkG2gjD&UcTfDpLw04E@_&U!~H3n%?d-$@zEZ) z*DqNvZa6$R!?Hs(78rqT84QM9S+HE(vb=f8;`*BHdNs@|0++1JSnbw8hie-g8|o^@ zmI8@}zQwg|dfn1A1!cLV?JCN0i{n~Ekxy6GOebTaC`2j6`HSZ?b$y31FWBxjD5I&W znzCsK!hrE)f@4YAwqd)@Da#VevWcT1PCJfbd^f_}iO5{r#`9d_Fyi#|4BK%i$_m@N zD+t13dxsMb!$Gqzg}|~Piid81yLdCLda69f7>yHp2nj`1Qq>j0k|g61VUpknE=LDr zT+e2{n6p|f$+sn1Stun~EjP4ngYE~Pz;?6a`HOQ_s||TkPA&E&V^SfVKV>E=bd)RLH|ECoA&hhv6f9fyu7gv`QO-<}1++N*sd3A;Bc@%k$@7e56XROw9gfDPyn>dID z>P-;SbcSc=FM0ann)g4w;tQXBiy!^Lw>Uc-Trh z>DfcZ$%KRbLu}il?G?rd3=SG=2x66jzE?C|h2wZA4bpNrIy}a6U9PULxVl~NOTY4~ z{Or&E0!6;&_-M=@`r&tYaC*SrH08Co-oo|*e8{r!D}FiilcDtPY<9!@kU}Fe<7tn0BIj$El+1ulGHK)l-`m#Z5gBSShpB!O%Hendx z**3Z#ey8ipm%|aucZY0>-VE6fkqzbxR`RFFQr^cWw5dC7F$49=x(oUTz4Y3A5RKv{qbPT+ucyeb;kw z`I6UPdqk83EN*Uaq@gIcJbn5E+p>7@>T3u);FBjW$g-Ruj=8$MCC_ux@d(hYSGN?! z4krk3Y@2DEa{u0aHrbZ*%WKwoiS0!Q$3gTRZ$G+6vt85IIf)-|a(cviRa2HbPVbMH z9nARCf99XTv3%OPr!EIwy#}P^A;d6{5<((fo4)TrDO@kWc3hMh!a@uKFK#?eaUG9O zAHU>he&!eWM_+rF>#JwnJDX4!TR!{d3IE(r{8`Gp;Y;s)iQAhSdQ}regAG-flon7@ z;Q5lkF{r+!(_&~>rKWE*S_`zX25D*RQ??C`>glSAZPj4(pg|wbq9I}6Qsncy?od+X zC64W(wPv$kBQ2jaouE{M>Kl%Z4%ltCG|do6CY^vVN^ygLw(AB2R-O%j3(rTZ!EogU z-ayHxRnB_5W;~h@g)y$+LrOH(@)h8HFcs!2=G zAyTXFSuB<;ujh1Ki|GgP2Eyj)#TCylu6cHGfgSk7am;kOhp=sKZf|H+OBzR%)sDWc z(Y>ZFTQCMAEF8}xj#H|xqpfouo}J=gSlrxlJUbu`Bi?@FjDO~j{UJ^s+{X`G@?Fk4 z%cyEKOzXWN4M<{;LFixp?^!sWX~#$4~ympX0SxUuC!3BItpRO_pKVKAs=as(whpRV|Kd5l1P? zhE8f6;S95ayr8RF9NWfq9X!uPNQ+i$8l?vcQrpmV4UPoM^jNlq(q?$yjiIe-JjX_3 zV}yg_SRf^JQzHl&|i!u@^2C-0)XN*S)s_nSGy21}bT;Jo#vnSj;yT|9= z{tO>{=Q~t+juevhYBe|tlaL^YFj%xrh2tgIuFwAA1l2d}wi&nA*Sze?+fgk%E9L$Kv3DS0`>zux+5m zM;s^gTC-eih@zNPw!!fO#-k~57;*FR0^1Tu$EL`Kgr;Z|6DA{?UZ8O}JUPL4eHPcZ zNYxU?{G{zRf9v<01pun6ID6-Ri}d5)PZ#jJ6aXP;n~Il8hPZBMDY6ZY(VNLux$J=VVhStu7mG- zpcI{HY1c zpjAIuXc73)7{?1ql9=^;PE%wE-7{ZoNk%Dd6cEG_aTwCoEm^k4G8R#gva5#ky5kQ4 zE>*e1XvOD0_a!_(K%0`H%DBF~WOcjb`Ljz*D>=G<%yfUoX0>6xToXkhOKa{X*9udZKUf`*Radh_)hV}i<~d?__;9Zd%|_K>Y1E< ziP7i8B{MwClm6rV7=L*q+~YR zqpm7?rP!a$29NoRXVlFQ%wanYeb<4qXq3RVhs-~OL|7IEm(3>QXfNU6YmaaPoB8dM z*<=RFke3^hMVNq2DZPOA4J`zLMYm{o~iw?`SF=mJhv)n=D*NPz}Nom72+cKHC>~=ers}-Ru zaczfD64I+43D{DSZFj7fE0QR}ab1Kfn8q_U+YK6n)&p(l`LlCA{_uT%zfTh>S6&>XsV_}+Jj)MycC`FLz8bC#lI>oE!{{fDM*KO2?&C8OEbE=bBuH#DXpM1 z0|z68iIWxvOmc*DcfHU12k-;ip8L74bDi@!p{y1du4Lswml%WFo}o7>{jq!x{~2lj z(=c4h@)Wh&0~W1%GX>084xsJ>Q&5f5738N7zPm{I;wmuhUH3NctN+Y$4KcN)|J}ST zh~7(xyg!Y+@N8}m)+MiseXeynQ*%oj`A^jIUSfMKzu=SzWG)*SG9vgq2^%-g%MJk< zVlp^6*3oV;hYw0*Bb1Cj{3s_Z3A^HhY%E{1!zWd2`>@BfhD9p}3w#^7v|wPHL2nS* z5F-d<%ps=&h`szu=;~Ute7p)Lc9rxNy!`riHuSlvJjOSp_m>L4SZ^8wFIwww@Qlho zGogBWUCoR;XE~A zge#cELJmu%j2R)OoRK6*E>BwEWpYZgL2Jc|6Flnpx>T;$mGBPNR@R{nL~xsfPiDn_ z@K6?6#Y;4eI{gn#z;s6D_y~rg%V=1r8)J}_7+7k+O!2>SCS&FDmCn zy4rF<9jjIT{-K|-rH-()?R~4lkA04qzqdthuh@BEzDEN{@I=n6``~+N+Z^s*tHPU` zspI2|n;WJ$6*!9nPtcuDFOQzp(H&CHWP_`qb7k}R`H0ZE1?WY9PJdV{{TtLo>89Ak zSyDF6=nKg%ZJ}2jytPE%Ldw{Be3{CG%UtJVewOt#ft1kd{wZ+k6TCQoS}sRoh?jRo zDu}rLuYwBQ@Y)(zN9X#LqoRJBg^&kt4GTu+5Re94ye-n_JrXy*-z+QK@o4b$JK9pE z)9alzFw?qd!eO@G3m*xyqU>oVE$(yX-wq&zvjs)|Q5Qx^}y5TI}|Pm>a{n&il!F1@K~F|%=) zoKp1g8x44sx=XBILTTYUQcX#RoIQ#G`#!dTfN*zu@(_+V?@gI#F;dG~)A~S-O{@k! zRglLkru4&`Wjq)^KA)vKvfm$Q-Rb?Oh*>-*vZpebQ&9wy_X~e_&F_8o?c|fi+l0E^ zgmO!C^t*Owya6-FycvbZ|9uy3QkS^f>Ff?wMXw2iU;sJinxbML~d399Eycz>N;f*l2GHLqfQ}CafGiqp)Ku#O=Hjc#->q}}KG%eyHuO)+NMQ3L zw4S^5z~=CNXAS}1(L;sT2W2PMBoDtumOZ*y z7rGU*zOU@=eSh!%3WO^U;kxg~IBi58mXj?w8L$2*ryp$cE)&@gKga{_M(Py)=ucNf zXdl9ujIufXwwI7y_JLP{mV!{py5aI`ryZy0iMso=li95|~j1e>w^c|-6gF5=~}`lQ-x z-}I=vsAg`QgzHh^NqQuSllp()s00dwm54*{yY?w4f0f0kvA>KRHcC;9uMQ32a>1~a zvlBgWA7vgh=!&<$&U-iRPhvn>^yvvGohqTq4KF#6E{ehrp?1z3ju|^+hV2r+L5G+aNyE~F@yw+q^_4tj zOkimn(yOW?Q>ff_2s;G7aYh_7A(mqKuC|9}Mn1;B@Wl8(}h6gZ^%D|W9FYou}T z6uic3>_2?_jG;+Zd9JziqyMnx3*Lqk1Gb$KoogsFa229W{+;04E8a3r93aT9P`He7 zq)RXphk&ph{rmQW4VC=?#=;-Lhq3I2Vnp^8SYiXmNxK13wHcf{3hbs^QqO)W>(=!za0K^r@ zu1}DU25jEI5Vt@l;a$F_w9+Xas?oWFrIOmLfIY!N<4U&_laF=zMY|YLG>NtEx=QoR z-e5c;`S9G;M)T4x>IxUm$8&GviqG7jIZgu4;kUX}oGelw&h6hHRMpfeLnY5rT` zRb4qK^jDdkEQ<(3nm)XrP8HqCTREo1QTgMsK?oN`-hLHn#~pd-`V1+=jUDIT!kb)b z$jeu{`pjM16ZJeXYTKv-KUiOYy{yW z0qXGNIa@{SF4?c)1>QJeLx)~LbK7mG7=*Aqs>+oQ1+`pw%AX!PO96EdwH_uci}K5# zI6t*f7MBBrUqyOV3>qgoeG7*mc_UFn00>2wJyU$nnP4>~S@+{yjo&lSaE)fPHG}v@ z6VnXP`wdcH#yk7WpoSkaPquK3^%>qkI*hS?x@nF{M^5{qS12?bLzy2Rp0ee%pJb|uESssWAY6Zf!11_QH3-V}DntHkb; zM}HNPzIO)c{y!60wQV+-O|QB@$wL^iWNV+)PlxeNa1{~=FjAdrjnhnVrOFmtM&2K! zH;mM^!y|fQo8N}Hj2rgTk2T5VG#>OPoi9Y@b^|D#=&Idz1Y&j7<_DtLo=JOUCia1< zz5cX-L!(2R=|t;Gh`_(OlrE4LljeL*i@&-#nxD3fZho>=;1b5B^TZuhF6b6E%0=AQ zQO%*!y96>e(+_0+CT>!kND1&TqOs+3#Z`<)WTk_LuO91EQ~jDTfyRq4t2R1h54+4U zgRD6$qj$Bz!WYHJAaS4=>qWt9jqK%%<=}pAwgZ3>@TQ3SfWsX%(8N43lmk2!3SL72 z&yVaLRoQ=UuXNLfqPSSYk3=xL`N9WzqnDOM zQKl2zWJu~BBFLv;{8nLF|D<-Bz;zzZ9r<=`1KyzZRJWR%_Z>4!TCq-3Vc6~N-|C~q z4S@$szFn+$XiKBI>pFS}wdFO&7&FmEtxL|aw;r-sLd1_#aLIK)A9;Fj^Y)L0g>`9z zRWIq?QFiUdv}*@2F^4Zqv@m~nO%(l1`+}t!j3lgG+tQ`t*!oUYQ8*1X}{e!e!?-oBx?9^g>5{6O)8Bc<#qk(~wor<^6set6HC-N@we2B6&E@PphW<>i;X?lY=94#JWh z%$`cH6IPiVdCW|nn^0Ojo`Zkl4Qz+>Kxv9;GgbJ^$U^ol8!IQN9a<|rQf+t36C0>1 zL!_rjn#nOpUk-lT$!WRp`c08fRK*(;r@#`)&Xk1*$$7SJ;M(!cfL+ZRQHI9>5r zjUJ_w6u^27No^?=lHm6!ZqBzJgK_DVAeI~>da=kw+g3Oy*uCNpr)E!pKk8U-fg??B zj;Vb*!YTPmL%1sO1=h3dvR2{ogj3 zjC5nzzcq8g9yR;@;s##ZFOk*il918OA-OEhl`~gGQ?Z=ze%Q~CI%gW$BD}F1F6!ss zpo2G^u8$4P?;y3FzF^mbiSRgwTPxqFMbrP>QOjG0NbunGYbWGuZuR>_&&Z2~bcNu% zJmHg1<9Q28lJXs0T^uu{673*Ozj{;8!1jnZE;&QvSipFZ+ga25w^zD?sqYCusBvB4 zv=yWG_VynRYkc(xXizkM39_6F5O6S`JR?FijTKex6(~9_@VaRL^+W*#ogjCR$EJJ_(Jg zKKMt)j$31_;ti(7Y1i*dt$iL+c?tMsFBt(4!$F#77cv(AJm~tHgmZ05&9j4WGGuEb z(_@S;K{>g(796HbpqPUfWc!M$njoP!*U$Z^slvmR3Eg`d#_yETM1(3ePN-vrJkl&& znTyr!g$hC)#y8nTdwp>RdE=-g>-xtJ*^50ngjd&ME_X$LJuBxIcb7aBVE~XV> z(IM!kfM7Fsi5j>F6e$63OG%T*W{$0~p&>}Gnxw#82MRju#Ie;3SWDnHH)wmQrF|pp z&}i0V^4Df;#xA2L*S|N2ilf!W*nM`fq!e5QBBASbed*-g%&AlTn(RCS8n6FQJ>)A? z8@RN55}jv6G~RKrJACs?d~q8n^o<(y_CGv@k9YD!QRGDI7lZXTOryn}=?(7S1|&gp^FrOt-gf7Lq(=I z$vTuEzcZVGE%R{x);zkiWbY?@~aPndfp!T5;=jJ;X8rsH~Z&4UGobUuNtiRKdD{ zkW(9x+dTily^Y?0;j4_|@Kf_+-~efnYeAH~W8UOlADZ;Nn_j#O1wZV=X2SrP{1c69WmErX48eQk!Y95>#uOH06K7a1->S z`S@{0)_4#Y#Q5-w_ZU#E858t+3KLf@7w2_ZDs)fa4{Ar40&N|trpHUYa=*$}u<0e< z{>_7QkI{!0TgKf(!(l^13*a$tXH4;-vr0x4e;ZeGe<)KiLaPusc{tR{2^Pn9pc}6r zYIYR(h6X1);BtDW?84of1<=N5F&$)B1gZl#@!dVu{$VVvtX5_L{&W`xuy!W&KKT5> zu6bzIQ3?U5tg%}UI7IpeeZnf%-WaY$fzs`W^Zj8;r^;6?TbPctIL92w7+wn8ro(bE zxq`_83AnMrTRkUYARP?My^X7j);HoV%O6%&!+3T^N}u-RSD1SK4-0F{%-sMn#L9AQ zJw6uEU)_UdLayJRHwk!k{wGm~26i zOy>ndX-?HH)kHuNHfJ_S?X7WaGVh77*7!^6Y;Ucl;IAkm5RRG4ubPTykIQ)9!xU6M z&3+SI;Z+%XqRx&G`71c>S5zUA&&|VRDc}>5PloNF&aJ)72*LRvoCO6}Fbxj;_0j)%4rg}T{ zs>+T@`d`(L6NXKdi}5&kd>K;{Rrl#|VCDq34yA`@9UrQ&>Nmw;X`M89S`tEH<{K6U z-k1x5=*i{_=kNKR{o4K;daASItR^Pi8vqQdKVW80>#>vP-MwqaNN_moDCuXJA&-LN zUVK(z>#rKwIh@HK!ISJo#{GxOEK1Mws~m%y>EsKd(`kqaM5LmWDjk*qejTx;0s484 z2v7+!iPq50s1mV!YadEuMkniZ;B8@LP2AipT(N)BKuUv$q6uUq$+h}Y`q+m{rc0)Gf>CWc?Ob^wVX?+`}#^csB1L!nAQZMOpbG6nJBVk zLIbj7uVf<#D+_YYwRBmwP2$K6T)33IzP`ShGwym(s3$->daQ9vj&7unCoEN{SWT!) z=iq|x^Bpl85CJJSeI|+o!D{tHHnB8=K(Ak^iLM=Zh2{p&6@gp!9D{9pZP}Pk06Egnq&pQ)C-4ppA4t?1S%tn-ny^<9^J6$&J+E};@fNi+*lAX zVl0OT%gV40X1x+QRTDu)auJrqAXupz13+gDWDW?1P|9g2192TA`OhyNU;qw9!}G^Y z^9fbY4<9CW?F(0_=dqxh@yEV2d_L+W8=HQF*ICEV$;L+Xuw`>IpA_YTUzA`^w$sDS z3fSNp=4p0dYj1Yoq!LMEa|b6_z}2Psix+?s~Lg{zRW#TA|t~cs!80 zrato8KtShvFX1HntCRD$|8-q{uwLI95ar*DI=f3R7~b>ON!+8(rOo}L)g;KURmD6) zZ<(4ABMBMTBB7W-;EDW4A3Bx2AF$KJR4&GYp9mzW-{eR^xW1%NDuBOirKF;?D?lU= zNs)#*zv>2IusWX>S=@|kn>{6uthCJkoe#yg2tK{yuwNd=A{d#f8b%mKpm?J!4N#ga zkd@C;Z0*a$KyRPKQ*>?EnPzSJy=F?Jx`fP&WB=KdT%YTienUH#Yi3cQV9s+Xx-QV# zLRlg2`KYe9VD%>%G`YD#2?B#Y@vpMM1{J#vW=@jD{XybYyk-%*tl>rVk-L@=2igif z3z7~Iq4E2(I1xVwnx6x=gozi1{_ztj?g-#(I(mUT1HB?1YN%5uPXMc7Wk{9Mwy$5G zpE(ymYYW`^##HALva#W$gYx^`*8+kgCD>q6Z>Hw?cYo_(vI^a0-x$PGxtfgW%3TVI z;YKU0;J-Z5N1GG1}wI_OhY^93)A{gEpd>pRC$$A3F$Uoy`7l)3V6ci`{kUMkRP z578SEy^?UPdH)Nm9}7dz<0h4x${aEs+-oUqHQ^Cat z;AadYf~9Slzf0v(HJv!$wHd}s@zFM->JPQ;^@&h67dK(Ze_H(v_K9TEISw@Ir=v4D zP2lnbY0usKh)GC=F&<>bq+K@b#cwI_Vj5Ej7X|1mVO@QJOoQcs(iYd8NcJ5LUq0JP zr~=Wk{-zsUk*c~?1%-e&Bd#e^>P8N-AGzO$20ZAt{O_u3ZDrCED@{D(ym*g}1cu$< z$lU#;n2Z7pE;FS=hkJneKe(;r)E~~$^sNL!u_#8)IG0}m9YB?K1_bG!`A~^2ZJcq=A~}8Ty>|!WJ8XriwhMTn=S1LL!F;oLtxwE(DwzjVrJO% z!QZ6kLHn_KpHU={S9*7cr5sx?p=(|ZkxAl2@lTVKfOIc?C4o7m3OlB(!iCC3x|~<~ z-8R^Y?}iPBeSD6M|FIA;(34zM18eWdsxSRUvByJ{}DpGRg1d~(^G@5S(ByZ5O$V##& zKOrSc2fS^VwJbKmUibU9B2g_lerYTDyQDCG z6u-_)$&QHJ4Du*UTvb_K&GV83Vs4Z&T%dL00FMa!X2gl5l@|A&Qhmqb_|LO6@JZa zZ-~~mmWkMxska0gyQ{^lHF z;QeWSET^rkCVje^m`?nzbWp_4I;N3X7)N5WyXE*FfiA#7A?|Nv>o!u<=XMp4A*hY4+Lftj z0Sd0VBn&N;6IbqT0>-2nRSu9qKjeqMM$uidfkxNJz+O~Pt;9Wjw!4^a4K}R{j0*jt< zO{T?hM-fdizLR(ipKh4@@ME)g$2sKW91fRnTeCU1`ljuy(;f-l#b4}x6tutHJ!pMj zdoLtjz??d;OADsF&(9Fwo`+zjh}`i<{e?l=He}nCnXJpM(N3BZ`Z3t=arzpVmQLAj z&Xj7f)8vDVRYI%a+Xs`gc^g8-`r_0&P6^aD(sr#(ugD@+%SFU2Wh%~8IG!r3cy*30 zIR63++K9c2(!hnPy(Ni-P~BM>Hk>ZcQaGdzlyF+^@B~l9TXyBl5#kyGuK@xq!ucZ! zn_f#m)nBc9G}b&QQ_slnj}Z!majU6%Wk#H7Pkb4GIt7+Fjl-et3W{^$vjM}g_@Te+ zr}>TFoc1p@3i`fAFUbgMJT}y0h;LVn3J{RLSXCD9czM3he7v+EVixE;pT+rL!uxa1 zf2gV&R}1!J1UZP<-`MUtYj%M^U1?Q@-$Q7|0rddlLU`t650wzQm_|o_IA6Tg;l|Qi z-`s4F!#B8ZUYow>Cs)h z?_g4Bl$4QSOH*Si>C_Yp{~3m%&^f1ojh2Vjdxv%Eq0t2Fd@wnODPOOK(SBoVWrA&8 zcC*9Yj>SPS=C+NEznww7J|f67i>z7j`zF1`o}i}DXBeMw-&kRq0+22_w|i6J?X5E4 zY%p;GBpO~q?{CFExyw=_-abj$RjarE-Kl7J)YV;H8FxQumkn8?$R{dy;}qAB3KEW! zd(S8S5-va=Ompd~Y~?)@MhG}-+EwUErL0eVJhuCV1MZpI5M0K(~(UMV9fp=ha zz*_N?SQbJ`MwedQ@HoMB6(Xn42#$wTUmm$gN!HJ1aypu z{~aEVHStvfe8#FTN#*2=rvNhQ)F<91qxQ@)@i~6-u5%70t_WtHh9f~XFs*bubp2Qg z(CH1+lmb76$bA+81y?6Wl}+=DOIgvqX$7|3w&1clMj$V!NqNIou}uX>Uvwd__Yug{ zY9Ui6DHy0BZDoB+D!5o*g4#GY7Z_9>T1#J2YtezZO!?n^_3+f}%NRI)nF%^RbE_BC zv|s6Q@b(=sjyy(xt^w#TU@`Gvm~C)qzhkqiwEP_L1wXoxqQ#^MQO@1LxtZiAyL>q5 z1e{@oKbV+VY|Cc^Jqh~BC76^mDED_8yCZ)i@auiOu83NJIWWj;(3Jd=M#uWaM=(Ku zp0KXw1Qq7V>Lv8l4#M>kFflC026hh{cOSXE-{z{lEYzpW<`xmMk}VhX2?NF^Q&Hx0 z=luNH9p|1n>qVb{v;U9*7YDJxhp!Fp0uOI+l7~GIN;NEBLLV-!FP8(|tdO9y z75eU@ZEMx5Cw-EYwU1>*czbBJNxq2#K{)Y6ih&0ZIblqzNT<)fypa4YRqQfg0+~B$ z2M~FlzkFIN+^G@M?a_L*iP68=Qd~Z#rc}L*vyI~Db=EO5x~S_1l3iM+v=)CYK6!1D zm^ky7HwZ9(1I2K%kh?vs7{7q1NKntZjyQf_z0>c@mA@vM49YOvXD&L}Vk}W+>-`&GX0N~FcA+~~xE)f^;kBr%r|G<%HNM!PU0AQa!*r{8nsHB3 z1Q#DKofyG=5^G>TG=J@esMmu4atcGgqb~|E$(Jow3NqU=^Y%L9KX#HsyrXbxg=W%^DtLtKeuk_(jZNs&*U)0?1+Sp6 zU#Qevx^EZJqhZh?&UG)L(q|Ix#*g_ixELO$O0bhX_fTH#G2of|r#!ex%!kL^-SRRC zI@QDpRwr;N2ir=sU~OguJ%)Y}%XmpiA@C=vzB1-lIlv;L95kmCmiQ)00?8F+>;_eDU`RRBV%XlTVGm=Ld=4B!342MoH}1H zkg#kDDe>#(@P!~O3BU+CAzD2CwEra)kB<{JMlj_Y;?qOhbwRH-Ng-s_wUk{wn(3i3 z|B*e0$P1LpMntdU)B?g+!{W;WH6F%~wBW`mT`rn^NdQ2>Pj>y(n3W{X6t|1^Xmipj z2c(Aq9%FT_z@L8F=Ux`9qpgHWtU`X{aOAP9g@TQ=+@Q@*6iM);{j7K+E9sr*BO7*5 za!w5V3w~VoG#i%{xot3uKl*TG&6hJl;ne9)OyNrXj)3Q!1!&dD)>K-YzM1O2n!G70 zxlRnb8WOE}hs75(K4y$q_q>(uzWU&KFnr#vJ2mB})!1G%DUsidO$75?gR|leCD$f- zMiWXc$PJ9Sx}G0cCc800^hgiFO6o+#EcK-6$*QO^eQjL9;;S?l*^mAXJs9dGoM5)C z=&#!{aVLtyQVj4p$M0)8X6JNC@Y++TUrSnP)FRjpdd1#;A?f4JO2uo1g@qJ)$f$UGToZ`8mEq2>1o&p&Yo7NV0vn^O$4hs7Jk?Hs z8vWEgbv!7J73b?adNXAx2=I%I2c>MQwnu0<5i8O)DKG=_D$HTLRY1qG!Rq&S*~pO~ z≦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 diff --git a/micropsi_server/static/island/tree_small.png b/micropsi_server/static/island/tree_small.png deleted file mode 100644 index acaa865d2b44e7e6d83197938950bd7a9ddd0e1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19019 zcmXuKcUY3`|2~eQpov>ds%7#{!qfBsD4ou4}Lrcw(qg=TfmJL(X z$}K3Gdo)}*aGN+IS*ZytZJePCP2qc^U zKRqCWKe)-umS!l}(f;IDMK>2$N$Vw{BG>V=)A5dqdKRSlhWo&W)i<1MkHUti;v)wS zM_fv9q)S%vsW_a0k&yvVk{1h4*Uts1;QuIhRMW--{R)SI?hk)Cl|{of`7A7W`LNn| z!C3t?coUA-LSOCHi0emM80I%dUcrn_PgIop`H?oBeNK(tP(I#NGPU$EzK(JAybnBX zH%LsS4$6pa4^^_gb#k?*qj~eJ<+Ypwad|Qoi1L`4^YoZnm~v7p8GDt zL$DZY1lYygA|-7_xsRRi3L4kv3b&c%G^ZvWuaiDY#l(dokcLq}e=E694WupSB+uvV zw9`3bdT(z^m)}$*MUBwb^PpFUy!$k+AG$7enWBTl;e495{v}&EE_`$UGJOU^9>zs`j{8lZ()|!ueO28Oy`(VK5o!w?M%i%rT<0dkh6sMXl~spdXz}%X?^NPP zWQ^6H(5}YpGJ#$9AJXS~0UA2Rkw0yRaT?1g+de8)zW5OPm+tYrxMM{9D!%#<77sdj zBF&0$!4CxRfcV*ETK10KBx#^&d*;c{asU}#Gg3p%)TIcR7f=F=B4$6R5;iW#^t zI2_Ao_t57ibBE5+a=ZeL>OldhXY`7nS<%sXosebC8<=Z*zmI+_|3f@? z=_-&@n7CK^*pVn@+ONw4tKTjvr5rGu(51kbxVdJ)s|YX#7cddJKbJh z4%1tsf*i0^c`T0CPj30vMsD4L?xnyivRu30SU;ASlX0wG2sqw^?)pfwv!((O-{@=G zzHtZ~U>I0IHwdhdFiZ&iNE18Sd2VX)pNI<`+OpnR$e5^^P*&K(!l(O<1zRH1vWdCz zmO05ZeQsy=dxDocpT0cr11;?vpDbrGm>v~&NTfc_1G1%Y919+!;NVnjv@U&y>Cx7m zs=Hj!5Hi60nX1v$^-n}z4a>Ta));v7aY0hS>O_4t4CQL>jTSvKu~-ejb4E_!SsFw? zZx4E9AnJhXk>I4XJSo%-6lt09VZj7fRK!6y&WE_S|sf3af9RU^sq*$>f8( z;gmX=;6z{-)|214^?15{5e56}eiBx=^K#7P!RrrZ$*=N~pDATk7*_DX-g0p`Am^p^ z0e@k*3TX8K^Iv^X{5@`NZo2WWj0TrOx=(Zy$LBH3_;YKz&w^CW*AQPF^=@sL_Zs0I zuk9w$m)>Lg?K-;dX8i5y4LYhU!?O}+w(VuqfM>r7%^uuKaJX%BAV>KS<$e4d>{j^r ziSoQh-ENk9{n1LYS#|24IVs~mkF#Wh_hrwDCVR#BgtxqH8qk>PpxFL-bf1tG%P5gV zp|aha{Q9d-zrRsB2+h<+yqI!6DaFxIF3~FVQR?2*f2A0C!WvP0_Po~X)wzbVY1cFs z?WttoU#z%FC78{jA)*|MiY8vFygo?jJhHLfo-Hk{#%dex5Z5a3;k0Y^Y^2&uZI>Wn zKgR1YN}mrJM=E%G|GooxAQ8x9Hb!rcq!BiaC^QmE9?Rvnk;$B->E2Wym>#vsi^ZKc zr4%7Z@Q2|d@*~c*@JU=)Kb_W(LLc!4Z$U_g2@(m>hQE&f?v8N?$8y(ixUFRkNtVzs zj=R(5L2t2V;5)*Xt9~tumA~{z!qNP6 zjT+77ap#1BDVJ_P-yb61bEpCS@IGICu0xYHvzpEoaRj0=-* zYG55u(R&+WW$ns2{7J9@@#m!GZ~&m+z=|^YOMh)Wk?mUU=E-8RAwwtSyy&^2Y+C^^Vj$U>tQ5UqZq^is!zK;1SNFq5e}XC>-PxI~{YAK-WLjo%qIC1= zk^;DTw*n>GjtXzW?g;gWYm;pyJFKX9oxSKbaqR$XG}Jf^NHBDI7q4BJC9!7YaQ}w; zTN?3MX6CB&|34oL37J^XRDExvV7MgA*(pr^vzl4;?Zn@@-XahH1sQlD1(tuEm2-Pm z?&1pd4eR<=$F94WOqm~J#hPxPofGX6r^m{RBJ_|*AKo{*xKox9I8tH-Ms4DGvNq>c z!CFjYToGAn7lj7Eu3*!RuIH zEzn1X`Tc_hz@=eSfpg&;CX;*LvIj39EBY>(zCG=5k^L(9S<8bP5PdO_$nH)8cd_4O z-GoNVIh^nZQDJ}n?$Hd!ikFu=TU^3H{jj%+o0#m#QI1MWIWhKWY&NGksr@UCFJ!+` zgn$JFG027-DCIS@T;8&mh{ECYiQ-(K{F6p6dyS&>(YN2O%{8o&%A}w!53@A%FQ0&0 zMD6s_w)qjZF2IreiasK^=VeksY}XaEFZPh!UXLa3eR8j=s)E}H?6}WWBN0xM(GmSBD&%$*RgLBs?{vG- zcVG9ae9|Vrc&F3M;xFGyS>Be;A@O)u7ZrYL#F?O)KzWZ9VN{&(>+IZAtK{qaM}&vH zHZp3dDaLpC!NR5ge7Kcz>h0|gxbp6qec@+l2Kxp1Lef~4} z>v0z9!-YGE)y^)pPhNG}s%d^QWyg?Fc{ImAfi4tj9F-P?Oek2Finpfqda2E?{87jIrvRQ zLq|H`0x633gn$CccTg=1xID1m4i6gQI*Id%;;`O)t$54(7wvpuTt({k`4y&VdXh=s z*r(CTgTZTcOG-&5+V6gU#zZrosNS6UN*l`voLzXJ2lZWK`<0R*7LNsm2jrFtc&t>8 z#AmmfEwGt`88E+#X0TUL!mtKKGZ?9f?;XZqh=LU^^M`2&a^fDR zB|`(DHj;4t9Oks-p#S!+^(1P$cY8S%d7`K(;x_irOxd5zo3i_az=q-97uc6BnCRM) z?r*=Ef7+kgDyT!+8F_3CRaH}>iP%xa5zW0r*MCw~H4n1!*N{RY11}^zFL&y8YTy=@ zes&+QD;%Gfle^}YmcG{~Y?G}?i7x`=MZ!kqEwJI8^uL=XaiKJveiWyrmE84B_~IWN zL6d<1YYn5~Fi5I_{D3go2$N%_B!u}5LT&VrT9J=W5+UVmpJ^9ge!j%_R!d@7L z&P(Ggf|%kAa9*MxY`ioICY;}bbmmu8;C~M`L^Z>)yFWfT})%716&*GB5yU{^IlzNDa+@+=GwNW?v-*=zAPmK=O z;l{5+WsHavRow%y}u5Mr9heQ5Hw|JDj}u*i4>+gpq!+ z7)V!&FL(aO^LUzt+3b65-`dU1`qYS?hVmQ>Zd5OwM{|s$k`veaL%rkd5F`UJ@!*)T z!6HII8Ul$|lJmBW!;xy=>}xyLkK^pb9;-j;Lwi{TEyWq(eCFnSywLLyV?yLWI8pnu+^>^23op;F+*vkvbat386{NaaD$(C3Td^w?Rq7Y*5axkDE+2i@bZ^^_rI*zng#?7a3nmKSS)4I?iJm=8iQk5i2$xJ;T@Rpl z@x_l|1R;^#PXD`lA^quasz~`5$$~YrdLbz46_@-AkRB=EgZM#=lox{#qb%KS>CNepiLW+pr2)ExdG$X{=iq#+eNs zD_2Z5L)PZPT^pjh=xpX$^c2zy%inoCHC=U9ySiXxU+D7pOJ`kmClQ7-hSl;1l&n8H z%fCgYBFrmy?@O!u${UEYgcOFhME!dZh9eNTtmX@G?b1bt9bKIU4cJY{2-Zg^lnsXi z|JG5FIAQRPd3$gD_M@7xT6m802)w2?Opqqw2@;-0F<0q0v^861yv z%UeW6_5b;iA`wE)isQ8F(bVwQTTw!vMU9Jv1v5IYg5$4W3Yz^MJ;vV(hy7jc_zw@T#XMJrd8d}}-)%Kfl zC=!-1WDAN+K*233Ha)Nx_&E^dAh;%ZVQR{IN(f>|`~*#o#gQvHdiomr23#Ms(AO%Q zp=)Ds-_%TF@YdOK#Smo3dRq6AR{Lr{^@Kvy%=pWP0^fb7v-@z--1OW5Upztue|Xni zD$2q}xfZJ%{bS+!Q{zG;oUBXk+fD_K9Q6{7pes;FVv#iC6Ur4I?UXHkaCkgZ359`e zq?Pl|A_k%yq73myf1KiwP%t^ntqf%Jl8XvdL%RAy!;CXpk6{`$G(U7{44JGAps?6- ziZ;t?db+9vR*f-7v&4_aI;Q1WUHca_a=^_ijI*Fa>Bk_+Y@yEyNqcVV9{z&m!CGH@ zH&?ffBjEreE}Qw3u!NAHlw-(QViMa3`&(7KA2UIiLQ;fwZzouL>_jfyNj_Kpc&!tD zp6>{WLjofKlZ`J!*B4HhXxc2@%xL+sZ6m26h?;t7gwcDzm!EfjZkIYW%#ZK~97)*6 zxA*TiisG_jrkQzXVdJ=3+@bo1^X#184@Upw1$-GYq3Y-y;=A6oozKsD^-c0Cuu_^2 zjOMo1JZkDrNCd8DIHGaeY?;|XXmu&Zyk`3Sk@BdlqC7yf<83GnNjJyw&eOXZm(@dE zQO~g46*WqAITBtIJ_2v`dL;kQTGTQY0V@)?nlor;*b2%>pXVhaE0H|)i_7hlj?+W& z|Ft*%FL3AbI6sjPty_7qZ|=N-zqitqEL+e`3Lgz86`cqj+G3XI^#Hk6KX3WLsD!&f zC8$?qp`jgvNKiTveLRDWU?P;RPpB6641xcbYi<3E%zU7+wV`L$(6sP<+k}ggcZPe; z?}Otr!IUaawR{a0;1^cEKK*NRKrooOx}_eX&|)6r6ITblRQ1DP ztgF4(hsEdEu|rWvgD5UPYkDW8d&Z|Mf7e*BXVLKdvYe7dH2*L#O}Ux+T5juuStJni zG{qi2Jywl@nVFTsX0E;>XFbvvKt>vrVQE1P1ky4<4?+qjyrC%y!D2>cIM!nVThE9B zAO%%5eh(;l?k1s+Q02?Kn=z+0A7B&{@0|<$eZP#z+LPJHUilt8Xjg>W1?=$S;lHNY z(4Kug+%-Kchvca1}3lA>Qd~baTu4-;H(G#Eq zY%-nQZ5pRXB^jB8@t@89<=qTA+pqK!8S(dFCq*Om4N!%f_4HU2l|2T)u>h}6kO4KV?&qPb1f74p0Cp3jR=ZgwU&r ze!A3-iZii88P)ja^*#w?__p};wcX72@BaNU+YV)6veh;$-6H&tdoqAmdW=9d36GVM z9h%EYu;hy`UY?hQgx_ITe~VK;+lQ)s0n_ea()Zo%wYX1bn?(i2fA=34^!{g;eZ1TRClEE=+s%Pp{c!}=Qke)VCryI0FbFuG z1Z+}IQN90t6?)Eq_qc?kWNffoclm7uAm9~c;=IAK3S_T{vro#RA&&^-;SxX$;AbP7gB3c@EeA zv-}s_?WT?v;i1DAEKy3kG?h@BBr~!Jk-gQAubmN;VN?{LUz1Z)+I``O&AjKL@K7=i z{vzpx<)zbMDint^ZlN3;x?)}0Nt%xRb#3tY-Qthcrz|b$6Wzn*r$oX;Y{k|kkY1iG zN*w|qOg~L;yl(A_Xa824o-?kS8W{m~IvQxyp}7RUDGI;~8){nUK(BOM*l7!X%qt`Mp6&$k zqr1iYZWdFCe*$Ap|-mMvO zqWDO;XozoPuhMWto~fN^#Ei5jHjQ1~6xT;k!BDNOutG^?<8@OU?xsSlG@{AIJc^Dd zBt4C{8YZVrn-cvjqBdZZPMKl(09R<;yd;q)k^EecZT1?WL-Us8v|@ALj;1p{A3=lc zzv4w@M|v)*^oW!?IpfukT3|g5DpXfk2qp{?dUz}i7X|boahz9=wVwDL0lItpwzilG z=oG%P)6FXVDE`iqX=XeJ=7O}~1^=wf%anPZ)%nCHd6PXhTqjXo?$6z#nNaieisYS~ zatWV=Lg@RUw%&cDrAdP~87=PH_rlitGxM@r`X=gDI9s!kal^Dp10pflC@_g`V1@(! zaWVsGHB^)2n{eQe1Pj281+o2T1~a?ZfV*|Ez;T~O_8|$0ga+k5LV9>mi)=@7aCTpw zaWpS~uhl-yI2zeyb5%h#_8IXASb=D>vB^0bM@1Iu6Qj5-PVzT$)DI?{Jh-_tar3cO zb=l2Nzbg*}&u?A2=cqi<9m!-q{lP0qg^$!6htJJ<;(PT1;^fL*4e6B(ZqF{8cWyr3 zY~j+n(ZWXT`-q*LH!NusGSQ_my!#!7@$7e-mMq(~A-I4^$<~AAbIo9W5TN%TS2pg_2V;AL^oOYimfbxGUCz={(QqA1AFutTaC` zf_R4rYKR+3IaczmZTI0$qR;UDmmDC?NG@sq(Z2rh-!(mM9$p;A-&NnG*CnZ(z8m!~ zuw7l@Iuc*YdKxwsvIb40AdQYFWKvT&@MKz?c>9XOHTS6n51%kPU6=B6(T?C}>u}|c z4f|s?W(;G|K)Tc38GZe1Kp}lH(C@-rU2NW|uK{Dlyd9p8t-6CPL^dD{LQQ5?KCTDH-o0RBbpys1-RH(%jvqgMRTIclFxkIU#K=aQ%b}&7w)JfkQ9c z%FG*1E}v+0i&$WUcQ0=Tz#GDt=LrRmHWd(hRE--VhL#{VNl#siNhC+FAVqZ3j>L;@ zrq9!~1T_@llCi5Zy>Fn|%xH4F)UvM=H6auey52wNuLj8xDf@b4JIpqko zwWi39ohgcHoHWTo7`%+Ut*I7PAyK4{I36f^hy^cJE0%Y1Cfu6G6v^|2#wY+`-yS0) z8Dg@J=kC2TZd1Y#ZafBa!gy^=kv%hU;q{m_ZCQJyrr~qs&comDlYAWeCWr^4SJ!mD z=WWr%eusrs_dmaNlRLCJxxXo5vun;Ax?PKm;yb0LJmnRA&iQ42SM+}?G6yYJR#>U8 z)JR4>i=Ad}BVafr&u8H25BIKoR|69-IZ0enzi7ZT1Va=mIpIL{Xr8ckRFZZ($fEG(Ng_fts~z|MM}&BfCj2;%m3 zY*KvoCSm|jCEz28p;Ke-7_&OBp6*_r^O>t#x-yRx?t_HIRMSw= z5g#Y(YB0@D5&a@3!ouJdjRGh%^Sjq>4{!8HWS|1&)1nD{lFuTO)ee1YZB-LnDQE;q z7ZZiSsRfU+2~X{Jk65{hN|xg5s{uYAs($WFoHT-p2XD(!5P}i}R3!xFn-HVB#XcAU z6(D<#FTL^ul7!dE9xj5Pu6cyNi@#KfB7>lBophD0&_6FdoJJ0iNCbL2YM^z=?nN#@ zFh7T`?Sb83i|@w+U{|(~_GSEzfYq%EG$aa-&E)&}!Fou!@)^SJ3nmMhUT+_M+dOR3 zWgf<2HFai}(`s{^4dwQhd0xCOuN8)AZ-l3K-vOgVRax5V;Ra7S&%||!2QemX$L;sHdj~h6BK07Q%>Vvg_hQ6k!i@-Skwj)pc)cZ>vua-?8iu z2obh-p8wW>J=oYPYzRBJu{vy*FId!G5;>~9tFwpPOz9)<>N$ZTHD!~&tdI9>=prvCOuFUWyU2xfy+?Z5h!`5(*lGzH4R|9} z!<5&?QfV*l;2=V1-RV73?z_`v-FC~$`Rv{cdV9^*H4vDZZP$Y#2ug{12&+a@EM(7> z193=l2O1LMYUw+3`~+e38oF;sH2L`DE&xc~Q&}qE^e_;lh4iNHKduW2k&=Q$s>Z8s z-N;g?a?kj!%8ki>1;rO9u!7Neq#1TD0kMoZ;~l5JR>$o<`_hg!+)8ZG3$9-)hx zWLRHU2U$<|o5~S&$cm1>VFM)Ov^?R`k*_q8{L1|m$C^JUwJV2%e`~6$00Y+4knmSY zp>eO{nm4K(ZEG#C+fILxmyt%q>n(y(+?$?ib>1Pz$|WP#>fFO5EBTQHFCp&-7Rqq+ z9(v}q^jPS?)r0g8C+|3mL*RF=#BCS3g6-EG(JDp)$K2~K}b%*164rN3Xx97EV;p%+KwpwCV*Rj#@v` zOA`_nQ%fs~4*L{2)AuG&R#|r-(LAo4eEu=~kERvcQeVhE1EhZe$74w5sGWd^$&UgG z7krJq>d!7b_x+17y7tp_?Y4XUsO7E2;~H9H(o(WnMg11Yq?-HNl?QJ>-~ku_ejFRj zP1#BsY3=N3+2AZ|xOtc1k3L zt?wEa1$fdQCLG*Bd>lZe6{^_B^49m9B#quI2jCsSW zkeL?3pUBhz=X(O`X>n#Ivaj<=G)5rFXa=+YWU4;a+QI~I>D$$GJ#-?QAxK8Wh>Cmn z$%v!|8GyBP;sBC-82=u3Xu;kB;r=&kMzGnTzj|9mx0J`0LpLY)eZr9wQ}WVjm~a>D z`7GSOZz>X}-(qOUL^1tcWX&=hIfMZ|8`?-v5jh6Agd4E-5~oEN-du&y_0o1556g$Oup9F%2F8N@gxI`z*@Eo zdPStA-?=9)EiTOQZSTHOYt`aTo~8uxD4f*KMWpbuT{m#e--P@|1Hr+^6^LbZ(XLD5 zpQ@~+U6GR03kz6vwS&=;(dJr~AObjix#AQ27*oL;$=h_!xnzl-mLU$wP0c_%f*qvufqh zE*&)F+>_Ki+Uqxk6A@>Pw&$}TPZWwzd#(jnR)F&)l4(P)toSL3td*4A<+dj10=*;d%>_g)C2e-1<5j9GBnv(Ywi1Oi^ z({HApE=vDvWn=4bar&^t)jh(yEy_hYWq2%OCihpNOnmgEqms67S>Jv^MUpDzc)o5--NLMBlB+)!4T{O2q&kTkRv7M~z^iR#um#dq-I zaJk64PG5hyIk_mw-Ii09P5h0-*+C7t(i(x^hg=?$b#kEt9(=eL;@V%#oYPdPzrM|- zGXV-3T3esWMug#?{e^~EhK4mf`qwj8&JOP6`pU%_Fe)Dhf~(k7W2tz5G+Iy8X^jtc z_tr)40hP9mjMU(j{xN`v+h{^8u&!|^Dk-+VgT-K*Q~Ie?JcAsfOBCiZnI=RJ@ZBe1 zFn>`m?(8;Vxy-4t&jVOIz%!3SJod@`0m9= zp&ot=tbEPfEsQ;-2^Mg4=ca^ZCCNKedPFaZ@QPo@c`d_pU0MdjSmRYLw}-s3jR+fE zp;3zncP%IU&^tUfV&oY(WRCC&rW=4Zx?-6yJyB1)xQH3gIuW}0d~4n}+hu5VWOwzQ z?JaV=UD0hibG>Pw+@8quHdlmKxLlciX>{f@f! z^BXwsH+ZPqF;Ur&0Nc1!JCuQ0!~GP(k_mAy1hQ9^_>TwJAqap2gzS#4BZu|H#F7Mz zA_R5~t&l6@WK0U@DK@+i{e^{wAPBc2Z3EH34pV023B=*q$_i}7B}zaYecCrrDC;nA zSQ!DO%$oF{PAw1+Ie%JTvQkl0-`u6V01!lW)MM|9$D?DRn4F2|uv>s0Q!kGCqUt18 zSf)938~@O^)>Pxl?Iwlt{9<4Nv_wxGeHT(! zNQg~RYIr--zF1CPGr4_9fj9{={y9mC6jeUOmxy`MRk-KZCm&HgINA$p{SVO@>L*~7 zME$x9@ICHcQ*twC&{hSWk#G4$5r%4t_QGy(-2lSy$**DC zn~m}c5+)QBy6VT7{t$CZkO8L+q98IVa!IOMOq*#KbW7!+3PX{46!w+;NGHtgVd+1o z({Wpj+_{)z5=MVDK5q#aw-a*+X;nnEzBE`1stVSSHjPbqc9@c&h!CHm(D0T0bGRR- z(O1CO;m&K3N{ct5v93*_b0U@R(pWO~x0VMF3SZSD;rP8T%kSPl(xr7k;9zr7{ZCe2 zy>MnWd3UOcRqXm3&oh@xL^Sc`$eNetm{o31U0GbNgo%zgc6<4CO+v$Quoh9SUCb}B zLO9Ry#rf6M5<3`XvcYRgWJ}+w_cU4#JksFaw_s5#;oCW zdwVa__1|mU12Tn$IzwKhix=sE0rB>B@%xkAyek4YJZ=A zrkCcw_OX?BsSiJP=9Dwz&)GwluBeK>>_^p-imp`S+#|c0IopFecm`KWr*q&JtV`>iJJ!>0- z9%hd@LLMBm9_UjSQ2zk$@kT0)$6wOue{I@7$j0wGAfurgW^Sk_r{@F=m!l3aBgnF1 z(lYHpMn{XM^S#mbw3nCOkI7*QC89Rh-hh7MWa%o}M*4=MR+$IhC zV@eYF#45aFwL~Su;7`Z<@nZwO>hA-i9-;=~nt|k)w;5P`RwJL+Pv54Y0x>BmnXy** zvwxCaXZAia!4%qbcAhuFh5;$lypP++UG|Km{7d=64LGLtPUo`5PC1DrBBwc?KR%^Y z`l9UEyLYV3S&dWg?CIlyi}X80(9HY-=a8oQgPoZ#Ny-9~&3i!fl%Zc;usC6F2wDU7 zC&a;cAZ$Qd(x5EIj*6RFW_2co^d(-m~S@SZ>u+Ti^ezeczP1I zU*wCA8xsq7+sg>Zv3r9zkG=mQ{wJwg?a>>AZ}&0=w=wf_ z>-D1EUW+K6l+?A$e?>(N-(5vRzjgyc6D{I?6GX`4Zit(G$4k3}M?V%IFhf&!2In3NJx2OJ4jg0?Yp?1xDb9YTG8W+hsX-yFQ|3 zW*0b7oOO?+yj118Q6fq^>)TM`*>;$pBR_Vp9NFRJ*ZhTvzRC}WUmPB*>s>Y~GK=VB z@Wzl3JIB;stnQkY+w29%hlsQO8>J={MwLLsHluNpS`PEWS@7I0|8vFSR)MS1$3vgO zpuWKuoHkPOzO-L;HmF@*xu^(3AhBsX%vtGpG2$41k!mMkEA_i>P~8z-H_-4}<__bC>7+UCJ!@v&Hy2TatK@R{ zPpxcj@V%l#!fk4Nr~W10LRm2UO)Iq)SF23?03^@-oZY$n(pSmRM{F)SZM=V#+_Ddj zj*od7JFB6I{b%3aa1WoqH4X_EHWmh-x}h14xo70=j`&JC)^loc)+=m# z{SzPe4FEpxyDzoJ*Vq!2uGm2%MNKW=IPUhn>?dd3dj2%Q-&Hs8b&--&p}4q>VN}#s z3Sm}Z8znCEUZd&jBy-p6AO`H${eE)9BjP>J%f}8K(B2wvZ}zCDdPIrvk>E5=ZUrpw znhpBvzo|35hw*n);{Co0vI|@m?AGp4EEiCP zQ=Pq(zM#cs56ETvJq<2GPG_ow>UZ?04fjPVNm||S6xGzN|0hdm@S`vi-_$#L(}hgs zW5|f$SHS6kU~& zR!05`+uCWGe44FCP&({AQ1~yz7>&uymHd2pQIC=W9d@Bcg{ytkejO2$l(MX4!TdkT z0DtOj2=(us;3gu^O)fh_nD%%UbYq7jeOqn71ab<#N_kMRuQ1WD(yr$C39Or>%fa8z zNm!m|fwAw6qRNb!+1Gg{a|sNF1eePynARXlXltqR+FE+tO;4zL7b_Cpzbt(~pDZv9 zFO?N7B$qm9j#1!1=$j7HXn=+rNQ4FK3tS8dxaeS_n;+c&%O>S4nP-?{O`^7 zF(ch`Ynvr-?+8k=NC{2p`aJYy?93eywJG}MW=FaQzw>dYzi<>6 z;hYN?k(zMFGYHc%v*|{u_y?LsV1!=3hLJsU;YgBZRuIqJn+DYG_pd}bt+ z1hrLmg#-w>qD5Jcr;Tnq2#=hu`bB(>m{uOW@V>-yl+BOjFtQe8<2}7Rn|sTVNaQYT zM40B|q}ZaR(L4ZlyWJBov#_x9Orngp?W9}E3k$lL_ z?ag$h5idZrR29qTfKRSG?{EL+BQ9qeMmqozbiBn78rU5RIVZ+sL#rehLSu<8upgBS z7z)exWU>it<5wdVA{+43VZW zd&a-@JdEz!t%9dR7#Xo2?KJ{&ELM>UGFqt#Vqj_g7bLzwK)p0;2f?cALPRar7i|i! z)CMf{ebo21c{lgn#4g|_5EiXB$6Tbr76Tg4`c;*#JXWWN^%>crZQCb(^+ZwKPr}X# zsEvvziPPuRjiu5B4DKCV1n_usdzcD)-gOsIknHl-(5TXVRa{tPcc10tgXtGI-Upi` zEVv#O^Y1H|aLX%lDJR}3kbBcB!ZNC^EThln#@o03w8AAIjHB{rA=U>b zSA_J^ul_O_2&|v7xu8|g+y8g}@Sn`Y>1DkfXW(zg9sj|rLzDg7)O0p=n>4fLpe*xw z?m?0eqzb_ z)4cmE*LHbjN5Bm?ZrcTj+^Xj!O#h0#2|M^B1HfN8<1Asn{N=~hA9nH(u>|@qq``2- z$ZgDB(ty*%eMA`uBJ{u43A!S@CZpFb|M-YQowjP%z^k9LIu-Ncz&Wjbyd~@zUFOn! zt{$yuIVvhbi}jsp0<*(=P4%O2pC5@@H$mC)#uZf+fSbG}=GpqWmjUd;DWcuoa{j6R&Mig%gtqEI% z$3FQg6#M1T#NKG7*XDEgPye~qfCpKErnR5WTNjrj8zRT%HcK2ols5Rm0K%8|d#gv{ zFKd+_v9fK-{w(czCawsHCAwOCZA?yCdfmsVrpX7G@3exn)ukYi(|hj)2Jv4U#V_p} z5Xc&g(yU_DjxdgJY^insY_9Xpy6!pS3PDCM9=#w*nb_!hQtoOYH(rmOzJE{I`>ULt ztI;@4PPCg1~~Y`M0bGGow%3WB+?t23&QyH289I>KkTIL{_+< z5S5Jx=eU&Beia@Y6Z9qqm~mTQVBD&$Zk<{-`y!m9tSRUep+gM+SQOh~km91pBdVS5 z?q7Kk+7`rLvFYY4N~Owf;Fv7St~r1Zmnbp7^$rASI&(ZVn75r}IZnxp|dPWvHp zZ{!Wp~vzO0zf`pD9?*GfB zPn%PpX*RB&PuvwMZ(%0MU+35MKYlw>4*AbcA@^e<(q}X%HVO!ZkN-mmXQD5{GuX>gv50j2w;IN#=1X5-9bIh zClx7dUpcu)Mk~+%^RA-sAi>>=9WP>`*j?tNP}3RCc4~5Jp@qlP%x<6ESsBOx zbU>}g;bKqu|87+30QlUw`g_wZjP6jq91AS3i@vI06q}RJwTU)LQY!WwB}fTOy#E)0 zyVK?|ilG%A0(Q1C1m8aK@t{0KX_ zoh&)BUMwQ94u~rdco7DCTw|6EIy~_mZ?vFTWhiBd2r$lApwjXdoqm_#a9Vj z<~E<+2R9HJuq}S=h%rp)I6@*4gIqd#dKg7H3;WgDgundPwj~${DS~~PvHr*m zQYsGTaXgr|2V9<`U0#vic2P>I4Ds1E_t&ofTMgL7Y!c1oWX4-{)n?`vPa*E-FwKbm zxP$vuzq+WSvw5>XS?Ej({=W^O)Pclt(e+qwySH_(V8B>tIb3&K=&h&Q62c*COXfrZ z%=5gI#Y>0(&meXPDt)s4e$7Q|o4DE4ykVG+9#w!PW^nI#ET1G46wE@KEY*wymn!I@ zf?AuO`Jaj$NA{MMks8fipiYL8L6c7llZz$yA6Mz8Ctprv`HDf0K+{j#(aJ zgV{%RU0tCzVcm{L@|3{}sdT%Wrq!LTd*!ZtJ(JvQv$R)Q@yd;HfhnhSbuFT&nsCh1 zO|O!7bNY)=_|-Wm49E!sg!2Nc!o&A`T(1z({rQ`1bOL|TR;3r#DiGE4it<5k&)ogL zxzi>a_eS4mUVD<5e|&5)6#?&~Ms$$L-+-~9A^#&~mJm_>w12?v_9{bAcX1WI5bU;< z%7QRtOD@R%&`Wn{wxA#Bzb(yZlSx^u?tk;XBgrUwhh8}S<5N2faAxDiMrton_H~f) zb3#|2So%pw`h}}xfoD|Sal5BlH!uQw&AAYklhp6__FPp%2(ensN-&=mWX_D{*5`JESaSFLRs>nj! zbyfHfFFVLgY`0Vq=l=z_2uSye>%taFdWQJq0Ro7ofeZ{Z8z5y!Kp7_xt3p$r22c+l zFrdL358u582J+o=Ny?HUOy9qJX1IL&F~dJL{{L2XE(`eh1)pNE59CuVkU0$C?epN< zDnZ2uXn);xAbyRjA|yAEiHRV90HSpuivlTQ610F)p$<@=3=m&-^z|192eOn16X;0a zOLreLFmVh2v$S=Y%ge|A3`Zb)0(@78WW_m|{(Sq&aQ)6J1_pNif4m&LYim+tnAgpl z=?^V(96=abOz5DQ#gD`W6&s*Hen_YjLr#%`t1<)#AevWvKo4C6r5Oprt0fMF=Yg zD}#iT5QmhEC?6Lui;1iV7bED(`)6-{GQ9fu^FK4w|3_axeEjhreCykPY)K4sph!Is zgVtx91P1JSU}SHFmG$%vWPku7E0BSK3u+ArGk`9)V_@(DVh?BqM+{aaWIhuRvazu+ z@$vI;3JM75$O-T={P^&R;nDNA46na@WaQ%!-Sy$^2hhomO0j1^l^m{$ z4(JDNpdY+}*b<02f$E-6J8)q|3_t*p6v#k_Ndp}u1Y~Lgu?i5U0kISf62L!JR_4F_ ze5`W%I$H97-o9h_@ctvi-8WwuUVjAESsdIqKYsem^y9}5&|$vl-8WE+LJR25AUw^+ zK%g3Ls6RkC%><8H4xO4yhpI?6%SpNV21G-&=5%r{g%sV+L1FX;l1&I3J4t7o!@OhZ%VN4gm39V>B6}TC+ zhv~9z{;Y?<-sB7PKn4gPq5>II1_P-?A|3D^R3-ug6%;X`ff`28m;tDv0mS}5%mxZX zJOK;_fB*dd{P^Wdsb36CVyCa%V!QnOE5i>~K89~Uzx);E=Q}OP$jHqM4qmzkFesOT zj$V5J#q7{FIk+_g4CMRhfea8pLjhqBMm8n}MkXo*+9PPf_y8>$*a)a+ zgO>XnfVuSmFqiHDhR<7Q`F;Ty$dAwi86bd&4dma@p3?;^_#(9YeGLs*OCUCac5lU? z4kx`JWMN@bkX63H#>V!Vn~mcopODDIzyJUJ;9+HX!UrrP*clmrGC}Hu@8sG61)zbZ zUSNRj05$V~egz%xa2LscpgKetO%JGTZU$6)6<7{`00k_ts5lN3y9Z?dMGs_v03s@o zK{Y85Kfs287QKcBs5!JEV}_0wut3KT-hhUN(ePKKEQYJ?BY2ei*nhRi?)6@?Z+e|!WrLP_NJu*ngn-IUE5E%0br9 z(Zk2M3LVcKnyzd@Cn-3#dB!05flk)pedde8erIf1Te4;V#&#{fR@CPV?c=olnZcP zIsgzrI0G4IXf~7y+5~eJ+OwlZ$Ab%+>iMB%wlq?JyoFXkA8_Ohd;nCjf;QM&Ayrmk zaX7vK9W?L(S{Z^WkB3lCN#Ln;HbR?33xSo-exPGPHTD5euL|1mCl;_lBk&-eF>DrS z+m;tLIzi=1d4+q;0Vl(P<#kq0D}NP0O1SdJm{2-ECJ7frslhW_$agqw-*{{ z7ol1BJG4^7295y(b`7XJL!J?V+W&(V0)&YUQ{sBrTKzt5b#!~&*V1NL^7s#O6R~k8`QV__X ziU_nVEEtp{2&iXNM?S7-Rq-zyLv@;$YSz R>Z1Sv002ovPDHLkV1lDGg8Kjf diff --git a/micropsi_server/static/island/unknownbox.png b/micropsi_server/static/island/unknownbox.png deleted file mode 100644 index 9bb9263d3103ccdaa5718f20eaf7bb82e6783a1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4329 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 WM1LPXE7Sd!00Q@h_}=$sWc?rUs>x6Q diff --git a/micropsi_server/static/island/well.png b/micropsi_server/static/island/well.png deleted file mode 100644 index 2d2d2d200b4d5c4584ec0e679cdbace34e379ea6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66215 zcmdRU)mL0k(Cq|B&;Y^R3GQw)xVt;SgS%UT1b6pf!5sp@UBmDLf;)rT00Rup<-1S! zAGi z!U=$zaBD1G$)$_NOvmp48rm9^F>Hfk>=b}`I5v*>a6}ZEE*}1;Jb9g=(0MfZ{HUFu zjP14(F;RxGI>v(MlxyX!LH^LQv&Y59u7kXTC$Qf%*0)bMpOg2C?EylX5u(CNl!k$YQf2BIAz-PPtP%wf z{|=DNC`X4N-U;}k^Iek_p>F{2vqqeT9jUMG-8X~Z+;Rxty59r56ZDg?#J>Q(WlOUD z1yBhh0Hi2zrx2->5r1;d_HI?0xkj|hzXSA5rcY%m&GO*2r~gOdb9J$`L%lQL*eGdE z$b@Z!x=WLnnM;c+e}JeA#sC0F?q~X61VKp$`@7rwHV2N->*3-ThYJ@PAob&vSD1;8ywJiE59B<$JE+}XQMGvQzoNs`MMen9&& z8WHlGz%;YN+i1*+A9JZ6-L6ifGV1q7Gg+E?Jbee}D1`fxGUFLF)#oFi=7(CJ>Tl%M zNyon6^LGfbnEJEc0P$cLTO0{`G636_m)j2x0HpTbCyKMY159O>LI8lSLn7mfG(zPO z6aYZ_=Xd4?31p0ZyqZ4DyngboK0*ZZ57OcobbX@Ok{@=zk)ah4Wk?Wf_X#f{wKgD< z{QQ)&jos4ECHV1R3&;5Ti7VlUFGK|cL|s2nktD`YaLuWOqdr+AEYW~b2(uHAXw2n# zQ@)i_9Yx`3&?JF>cqR=74e#1RQF%8Eaa12dKlG9h#m#UyYrES6KK+!`D z3+EbOph>MzYoYhYxgYYi5N=G;m#z7HPEA2wKyS)`iEEZBPlq|4va5nf}>7e zUpbCEJ5bldC}cAK%bayML_Rt=3cL2f`1s>v*r>b(Be4T<0C71{8!<<=@~`)Enb~97 zoQvF~*|u3&SuQ&I%(u*e_{kGE>B8x{>A~p-RoXfpI-q&KDs=5?9jt0o-JoBxI?d{k zO4NS@)$^6)I-QmLbzn^&%|!KS4YQISv**wU%a2VzB4mI5kT21;Y#SktrzRNTw!YMt zTyWP#JYjXnQn4^j1%8^UAg0(6E_fRh)6KZ)3pOVIM-Z<6JqLzZ3p%tiQi z#iz-y9mzvqOJ)>G7}`IdN7_R8ddS)aKR>dFMK9;_cuiGR-pGXgRNNoj#qS zo#vc=I1{=xzoox*x}7HfONK+fAmS+!=!fs>cd&A(a_~scmejV$BOk^` z_)95i5+~C;lTgd4Jf_^a(0?+0kL0{#wsr2NB)2nnt#;Yz-}+y%;h^eA74}@xag#wY zsKoUJTz|*yFC@P_w|oGWbiiTiZE9fp*iE6}@LXI-e-HF&h` zPPk00jQb<9YX95a+qTBuQPa`#G`Ia*dsX{)-^e?e%S;?kTsqXV=-r{EA+czP2f~44 z4o}{0j&_EpW!>Id@gKtdBk*K_kEj8kK~kV#L>5kF6KF4tN>ZJJNNum&S%n<;Dm*tr zO{S+fzF4ENtzA(xWj|jx{Z?>YBuMJv>kl4lA&>1DTN zHlehH`U_q6Dt1cBX24eIR;%}edR2A5kjy)vByw6R)*KI~j3 znjKcYb{DfjlV2yBGM?3U^fh&^?ETiR5;VhR%gUr!LiKplE7Qg&o$Ia*|5)Oir(5Yj z?K9_%bVHa4)6HsJYbxsQ8f4q|cl1=v<^ym1oK{vg6x$}wPUKHYPn>Ju2EFx)-S!gm z|NXf5SW9{tzej53q`2W(O>-gTb<7-kqbzQ_hN?X;_v#aGXvTPEnndGq`OuB8QrVBuXp6AMKX7F*?#zCUVMVIR}4cuat zzD=oCtWHcW*!6i^zTU{?#2P+%rYBnGd989HG6S6pd9d%B_gK7JWH%}F>xPb;lD0X2 zUMFj(@3^`1zC9!KK|5DoGx1OKt$*rSc|Jydz&Z|Td1_dLmf&)$hck`e5^U=TT`||7qz0 z{XTZOx6ZE;<_@i{tQQ`o9i{97`-xrtda4Dl20`1Q9h;-wl-Z0=;xFp3nFq_!^8eJ} zz=Ob-Ck}Wx?2&%9kmn6;Qd!DtssI4~^Z>xOFaY5G^=&)?0K7N>fa5O!fN&N7KQdn-y~kfsP1WbvZng1e6ZtEmlSJ;lF1d)m|{xhqaD4QbGzZ|Ckv*~ms8tp zP(eqgr^Qf)C3DktAW8fxq0aM0hYl1bcJLJ33x%T`kg>6`g@`7fwf!tB!~d3%k)aDP ze0$z~wX?H(|F-tOW$}OW-v6(S|CieR|A~+P4>9xNy8T=N+~T4P?`*tO#7m!ei^aq&*TIU4(;pVub|f3h5qyarXX{}DBUS^!c(EvfL%1pP+exfS ze!j2l^oc{Q1T|$Oi|eB$75bgLF4U6=u1>Psr$?_cQi7Bbn!CjB*#OLbq`Je<=FzP5;zDdb- z?B)A1LU6!Q09kx}*|hwvd_ST0iU&WZy!;A(_qy_WYql-Y3v;rW;{v_Hdiz2yD4)x_ z;4eY^0uN7X(?JD^z>Y!E&5*D~AWS&Z*%-3e^O7s-;T)NNqtbI*>!LxVvUUdBiRIwQ zoz_3WY;Rey z(L4JQ>(*e`qoZi=7>xh&5OSGI)Eh~O<_!n#FQ4d*WpWy6r|BekU`un2GI#{;eUkp+ znT$J*X?C}ElGoWjvI^}|StB;*J0;(+7F4XzTv~1i!=_Dl;K6sDWQ7Aq;}je{PrMwx zSE8?H^)KWpuJ{a62^pP33KQtl>$_h;mhhLXGY??w={Hh=g6(uA4tM7p1ec%-)R2?j z2acmru8^ycdydAYXNCtSS+bSLct=BAoHLO!nfrwBAS3-Wc5< z9KA1gz^9qjENR%9$FzF-JoM4;(E|i4UnTFOw9WI-cX+3+xa~)=U|7?NLX9mcdfzqL z>jO3q-ic=^3%n9e1W}3uG#SY_^TA_7#)qcogn6i)t+ z&YsUV@yVIt$--hn8NU{as}#2%fzPH`)37YCt>E_IwM8)<-pR=c0`;&jce0_Lq$0Jb znnjE3BE6#6%LB$%Qd1E6tF{k-E#%JkNgcFdEre`H8lDtxjwGIZ@XsqA>gQV!dGPqJ zb%aRDWbtcB0#+qfn|Ns&piz=KJvAuhNH$FP=xjbp+k3 zK8ZVo&OnYucE<(i=JU_M^U8zcfB<++rnGZ_CB8$c4kOPo*58KBw#UwZ5txO*p-+ul zs3vM1(8FLS(%uK&gMq+=#HEqt>h5_c++9FkThlQdxIN){+>rhe^4|vui+p@lQWZS8 zv)p3*Z=f7e)3SUI6sC$Qk}^>Q5CX%0D^ZMB1|~`V_FE#EAP7QV$+t{GiAnFvM3>mO5pPUmYbccpqM=KR4R5(CH zK|>4Z))kPI@%hfqa*=Jtkhx{wI}w+ViiGmc;j***quJfmGnRuUDwMyzKwO<@mf5mD zUNBug3N>asr!ytH*x`R4+ZLpq#isQ}UtG&h;GKHb(#1ZYf`uZ+>j~HSn*iF*o*wfE zG1s>&(v;8f>vxO3$-N;-WqE2ga1GR8P9$80r<;4LGKba&62bb-MIhbG=JFP4Hu=Lu zQU!z<5TGVnDXxV>5M#ADQ|q|(9(Q)87#9WQq(m>vty;p^Rm25?8c_|WZY&kXPy+}r zb-a@E7xrSE6Kt)yO%UiC-rNv$P6O?F{q$_U`LRQa5cRZk^Rb}Q{lxEaSPyj2^Ed)u z8$SnT)|=@tr25%589Q4|?(He$n~Y<9KEU5l+<7$jNz3X!;=Q3*FZ?L7?&&VPhy|zk zGX%}mSmVGI8U+sbScc#za#ElBwMY)Hod7}lGWU038DB)ty zjN!Yx!JGdCR(<3O_5>L|NfxuH{D;apg0cx?^k8#j8~WOyijAbn$V32(7F}A`TC6uy zFB`Wq{HtBQm>ib=eQaWdr&SXRxN@*_Zz)p9W}IC<3%Bq)0E^8>BmQpFU0#Y?w*|q4 zt#vyEHvP3sJN%Wb(zQ&n(Msa8ra$j^6#9(^-Hyk$p`2hYH-k?UDYJ}-j-l%7tf zwQT#7u(~EaHQ1Fs`2;#?by%zDq{!spKO>W)Wsv}6e}8w24+@*}^xhT;)#t74S@?Dj z+oP0I$lbNWttSFndJ)6hGg95%UW57YMH3xXc|yy_u)*$!N>Yvkk>>uUO-gnIGd9rO z*7M%N!fjxXr4xI)!G!eQ;P&rj5~t`$I5a%tuLzK0(Y(oQSff-;H6fp}W+!@=vcj^Z z1>ph&8RTF(4&C@lit8_#NlWdZRqF!4$% z($7fVrs()Ro*@@=8q$>*vK<>7#B#izJ}gX>-3+5{&*>Gmj*p-1tV^`XzwO@C1nO9m zo_c9|IbYwE2Q^ka_Sx)UF!Q^7pDpqU*wuW%vKKkY8eb6nkbStqvN*h_=d4W%rzi#8 z1@vlQ>LQ!ku0!ut>*IO&RYzkziZd2FPI4DzrZiE zKWv4P7m}@qkrWDk=^tg`~t5A3NmV+i&&l_w_l4Xizn(`@ zzW<@%1v#3x7u27J15MivjSxUT=fCR$UTmxXI+VCINpfKOgxrZ=BT#eHnP?|3!ZD&Z z&(OU%gBZokP&4Oid^#+=oOJT?ROM~ajf{@`71Rj)2(;5<7j%7!1vdOyuF7kCA17HB z1qyrlq82%RJO{r%XiZnd4=cL0fDH0LA{67w%46v6sPOMI-1@AFEc@~|3I%O@%9-%Snc!X|tmwAzZx9J1auSanM|AB4!w4D46+Rw=UzcW6b9 zq=LP|uLfGrzP$Q=(svpdw_1x8-adYQY+Ljn1RpDLn3zE6yUC3v&JrzAi#CTO8ErWmYOt^Rd+~yJ@t>f%uD%vaB`u;+IUx$__F-i53B$ny)hk=0O3CpNLqFk*)~6S^Wy!?^}#KEBsz zEAGr`-o8-}mQLp2(n)ZAPsnaea}=(wXB8V)UKsqN_s-TQ3X#&5FLgXTSQN?a@AeTE zDov!eo*+3E${T24j$=rYW?8%K!=)D(uLT$nEeTfyy3Dooi z2l{G38Tn$n$DuKZXw7nEYDe+hbv4d)H>ocN92oostKFF!iDE!Q)ho3;_=SW)0Yzf9 z{i$SjJDq9XhL}Q-8D4$QQwq3>#kG-ht6bS3O zqvb%V1w0`clExGdZL3dM8f0kepn~53q9kv180FnJDF+n;U1^P>l?RgYfXHiqvEngE zTkk!&o7-cC@{W_8(_XD-*lz#$ov6%#mdGNwr>kOYWyfp@U%EBQ9OwQJq_c~8(IEn8~r;a z1JXiKZE;gdSP5~rARWegyFErFm7D`AN#)v$?kH#8zDz06UfCS_cA)}I<8Ido(>67t z@Sz?oFGU7j-)O1wM@N%WnzbZ>;0v*ob9!f!KMK797H;g@u)=#{0>22UB6Z~iRlkEy zxbO3go=~ql>a_o%GHm>8)%cIsM{1iW`)F3aH*GSk{Lp_|GwnS~qwHj!u+c^K$;-v; z)|b27-uw7Tn88Dh?O@`SVK65+Rt=M(6H5;{b8oVK{enU)5DzHqtFB7e>ECd?Hq2u3 zMwU!!u})V;eg%02eXUupn)_pmndm(=vj1y!BjfxGPO%d#d1v(Qj@FYuQ?oeDKDZs> z;h922WOdEYSB{my!`s_??Re$K-PSFG@-N&U)zmIs&r*%uUde=YrdC}(QMR>${;r7* zB1XkpMkzy42L&ODJ(mGKrLMj(M~Yp@U~pTfTjk=WpO+|8@5N@&S-xKHqXK27zD?@a`$;|5uy52r-ASe@Amv&5P;)U~kB?Ba=MktuH! zzTsbZZ!zA$vt=8+KS@XJ=yWY4L^2m`5z4tc5TRmh!YP2yj6D=Qz|Gd63HdKPIx^+c zBpxHVAN%rg*O=M8NBu`k%#mmL91pC-^R+dzyuj;2-^&un;3=en@~pltKer%nNk&6% zx&N|6^Mg>B1|9sC;=VP@PtTcN@ zRupDrI-=wHUBDwGBtsMxNNL9`oNTpZVk->XKZ9>r_b4Z$5zNUM3khsZ#PcnLWm&cK z|1=#{w*MT9tGz{(VA0gHj|}ade)OSC#?K(lq*Z*U$8L<|&JT8?iIdIE(I}q|@V;1e^|EUXd|^V|Ax8E^5}k;8U9Xjk;BvZCYu(lf93 z<@jK7^Df=2y64`g*D{pDD-X()mPX|8BHu#vs*l5BQl88{`t; zu@(PNl%s-aJ$qA%o18^KcKO@}{E}1??Ql>4iQjzuea91WtYmL#%ix)=+7xjT_hHop zcx^v48Dg6irpdoS3)LSJ(sEMLXf z^-2CzkJ8<6PgxCVZxHM5u%FyC=n`#)ijD)%tw9n`E>BrapH-`>W9QB)Za!l|U(;yr z64BG}zY};x{I;w6Kv*R5p&}O%J;sNi{YjzD&Va7x+g#DdwMo46y%II&lBc8&=J9ddyx3Z(beqA*knB>I(%HbbktQ&;%rXOgfRO{7E zOrZL`?BLh`rSL62_8nn{KGf#EJj)?Vc%>n$nFM@3tnA79n<0!Md*f)xd7LmX_q;r@ zzwiK_h)5@oTb&)d(KCBJUE!GqcH%tPQ^ls9kKEpKQqjcbZ$=B99`d5Ox zyV!LaTZn4)No#pTgm?NoDJ?4)C(?MucFxZpjh9xwl(i**Hy|T_2nC7N?rVEr*?#kL zXwLIB52wgBQwFhaWcNA?EB_ijXxaiS9Xy;+M{TKf|~WI5~WSqMDf$pq$<;yUC?FZ+Rj z<2q?!PzP8(io3rY2c95_h-eYC00M_co7&(}(B6A~4JyCmeLmSRpc@fv#i6ilG5XK* z4B`$Ln;PGtT1_sg&_xb;H!;C|BRg2HC4;j+L&`^S5=G!>e*EK0ZG2V!ua;CzAYg5dlS$qQFC&>2N=HN5^-D;@*%nun(8-LSoBr-WRZ4{+*)o$yMoImN zs6?Y6(ko`=YpH2*Y2*SLH3{{MfAk@1*j+#b&Z=WM*_#ai$Odn_9ft+EN){q(edMxm*|xyqx3U1`6%YT&ArE zvlp)JrkB^_bmRqx46RHsN2Y4YJ`-(=yXhHDpMjH z-YAK*;61JAV4g3L!`eOH=;?(Y1|Kw=J^eiv$KtSp1KSBwjiDpYN|oM5cDmQ9YUSoB zp2Ih}0&Bxci@UW(0wuATU>Q5Z4|W9s&6nCk8ge;?F{*7+1|QZ|#qbH)J^k(u<#!{j z3kdEsy#9=aOF^&{;BH;=6<3Nm6#b^&RMpQ?J?F|B?Da(-O{LHdS{by97(f~ zovJl}zvEDm8r5iSqze4R{*o_4>a&lHXKRWWJM+PoT$5?nfJ<^ ziH(dLjMoex11lu?;#c6#HUltE0_Lr_=Te7jdU2fZ?MUI?z^&d9TuxrnnC4RUlcb+K zq~X4LX|4^TR|sRLDQ~i<*kut=;QUoErCDZf&otQ4E96Mw8bjGc7=u0i#M{ zqU0EQO>^T#`5fMbM87F4RpKeV43@ioJV_mQz0%SH5`#_)%Jof5whwo;pB`MEem%v8 z+)q!gmA<^WHaLTQaPT?o>l*~#rJpGakn5agI_hrmIC`o*^6jCGKX$5dL0%O(kj^1+ zbUNcCac*d88u1j~vkHvEdH0==1`&(peGegb?o_+mGqZq;$sV{nsO1x0?E4CA21Eis z84W@_FR+!ra)&-QP;Bq+Fo{)->qCYgnriZ%&~rL|%}2l2?f~h}G>ZPLgv13g4GTL+ z@^3o0MeXxtU})>t+Dp+yP7F7&M#dhlTu~Yh3z;fP;Ix#(GiZs8*r=on;GhC~t&mku zPhwFB(4oZz zUwJW>B}sDEDs2S9gMJKB4-hN&Z1IG=OpBYi`}z)bi?n51Gr_%~9w(VE2yQ6J%zs?Z zaeSa>bCk{&#sj{FINrL4pUK(l6~eFdSvBKaJs+r72vAc0)0s|<2F|65H5sEE5e#lT zjBmXh)kB|FNJC%~WlG(T4y4=9`Z9&u$x)M~h1$*WTN{u2U%EoNMBDuO_hi9C7Dj_qPFYU5Xz51hmPcC$WyrFFW-b zCmgvTu8-O3i+pUTVW#~2-Jaf}EDGwRwXBOE9ScVDedyc~6Xp7I)Sml<6?Z>L*!)yg zO>6%@^?xLU;=LjRxWM1oU!-RLsqf)`r?j+>=gOdp;#38Vzi(Io+Sz4TE0`>LcCIt@ zQ+kEJIcGpzm_Euu^uE^?z=vB{*OS5$~8mPV1lzPp>cMJVA*6zKFap~e>Mve7fg z=sBS6vOyOuoNoAfODTr1jv`fy7Rp%qJELQv&vM%tLAi-$P(fWb^{fm@Gf9Qv=^Xm} zjZ}}lx#{=!d3qUO2`O?EGvWbH0h_@tt#HXF(?xaSNNpTy@*)oT4zavueKX65;b=51 zyEwtJ{qXy>t-m^_bV9j7f}a}2juSyI!S2xfNLpdojTP;o^kALwv0<10Sq5)9Uqr7a zbh>$GW{?CFSKxR*7dW0d4U!^@(AIJDW_*)N3LkWP>m0hqZ2vP#JrG#b5uWO1l-I_K z*l5jdOL^TF2zK&<9_}8SJq-8WU5Cj<3J_%XoJxBJdpQL-SaTkOydVUb{2c!&2ooV% zn`~ExQyo)@$>2#EWK11@rk7-@al@18cLd5se-pgQO;04T^q3t^W2N+X+@L)_1C~~3 zUa^*sp)+gkaZC{ zcdw}-_XrA^ROaFtcAymX zt@hyz7F_CXTYP>Ify&HfQiOAWe7aRrrpn599(d|QHfm}dEgd5~k~rLTe~|9VY@7pL z?C@_y%P!LBs_mIgW+)TFlZpb{Ls-R#?|p#5hhp$+ow8EsfvgXQ@t>Bf13J3l8~cL|p3TnHtVJJw8Efo|inM59J(mwP*`VFQx6!vGa5OR3O{us%y_Q&II;zs8n4V#-Hm1wLxH4ta- z{avzJ4x!*Sbs&}{PhN^3>k+p#S0T^o?coS2dqU+&N(W=bB-%9eC%OC{b$+qze=*T}G* ziu-YuELg+T@Avf$5ru0G4i2J%{9XSx3;5j4)ti=;*l*lzKR#UvY(S`B^1Gea9b6l$ zKtiz7<<6WydrK?0L4+=bCGeqn>;I0D*vJ(9_1m^w1N+(=9Ba%%6C_EeeBG1} z>)pH_>79+Aw7(h$-FGK$9Ct50em&dRow^Bh7wU4mB%F1IR_=|A?6IcdlsXR>c93{v zbo@i>dqql1TUt@KA}&uw%{_4_POXVO(=nqUC%>;=cXmD6BOFxitp@fy7u{XlN_oXj zcIPYQW}Z`s`kK=xm_C|4@AO_gjUQd!#*+$kEY_K&CKG;Xp)O=gE{KF8V zocIyxUC&$h06|uhjU|t)UqTmfFD!9mXPB@MGP^JQNGKe5(iS{s^$>N&A|kr9Y6wc2o=&K`LS7KqYe;LjqL(FgxmRN*nfT)vtpe`2$UU5#`^ww{jJ zfr*Mx*u|JsJYodSo1E~G_2sXG|wYk zV+pURR2((2m@%pgo0@LK&R&bhjJ@4`53QQ?WUbc+tJa}u;*IoZ5LuYO)bGth7mxnS zOo0^lp}CqvK>Fmh8l+05yMhqL>Z&7MqA(d=kq;%PUiB%NQJJ#M3;%2hU3ve+CoorX{u3K0cY0&xc~{W?e(^)tN+ILF@%0qjLf-TbyY!vP6tK zV>_kv^*Al*NX&q3+z&>+?h-myjP+8OiLE8@=mrV~^ELduZx|Zaz=+z>y zu50L&wRyVoC;Y>et$W}E`dhWHu<6*LBpO15?|XH4D6Tw$>lJ)r+`vurqq-zGaQn&o zRDt7Z8<~=cIHG@jy?bWi_~FK35u&%K7s=u5V%+sG(9D6g!Q9r=@>B@Ve-~Ph#T_DwG*rT%>o3Ofkj27T#60pA~TOlE?}Zaw*!b)q4i?rHIfwK$EHdzuE7vkLk&zl67gw60FnlsqwpOM^`z=lq_8-Zn zgmCVqgsFLcDLZ})z1MF5^^75|G<7mmWA{BY>Gj{0^mX|Z7^vqMpz>7 zfzz)cpd5BT9l|b@mFIb!((l{2Ca6M4*!whJzp-LKEXG!{JF#$lV~+E2cw;>4KJbS} zMsUbC>>t;M;p43I(+;71*huf9DXBimZ;#nn%7(EUo#d1}<7)FCY@dHR3RK21Y^wRf zziIjURkG^+0G8lV?w6WGx*;rtP%4W*yhK{G;fwz;Jr&4C!!!%D5MZ=YI%y7 zJcI#{xc9cUu$hX+CK+yGMr+5{*CUNP{lXYyu)jKfHK=JW;UZ6GlN}CXqY3+4MicLn#icc}Ni}g*ou8v0!1qARr-i#^&C<-l8 z8D;%rG#zHnK1!<>1u~?$e>XPSybZ5PReQT)tG~$gUf#IWc!`9eE2J6sx}QAMey^<; zbovc8>sYB(8E#_DQcW12%@a(hV2(G_#`u6}0~69%Dq1AfBw#8CzYmOQ14{d3ZZjHE@?$-jz32Q*2jb~?2 zD3!Df>RWRY7j$zKvQkFgbM=58c;%Y8?(%|J1a1D)II!x{D`8-uI->NOe}Pzyq3d-! z@^2=SnwXc;E;=pH|J6ij4dugliOBS+amSqncl*oNo@-12?}hl82%*7eD>@PIb^x&a zzOurxEXICY<<}_9v9G@kT97-hcWd42-5CG@^rc7NwLz55|F$ab2jU-HzX`(cX~i_l z`h=A0tE6eem-D0o92*b)xtqNkM>q977i$iBsY{?XKR1u-E!WLLvV6{L71ut&N%`WW zH?KQPeJxwgS@4=_08vdVa^lb0#dt~{+T~Rf-_lQn2R`*1i7Fzk{39zQvosmw=0Gi# z4MF~zkCB0oEeyI`C1zEM-i(MxJ%f7Z7SXYd|a z-)4^9{BZ8bvqB|)ehQ7m|J^Ts!5QAo8a_d$WL_43lihF)1h8g?OtT6hg3m&3rlvXP z*OFx07aL0qFP;3UR+gOvHHIV3!s!_u4{2=8FUKTjih{Y!flt*9p^n+btv15hG-Y%# zG=ZD6O6V>8ci#tAY(7i)#oXL@O^Dix3dxX$as7DimaD0)Ejo$ms5+D;uj+U^IPIzG zi})q!_{Y464qURdVu`AG>clEeis5{M$P-ve6NAV5`bDMo_9TAM2NA4IagwApV_8#b ze*zxG9=h?XtiiVq}a_mWjo~Wx8$Ix_+F)_GH7N$ z>{{2B#qM_v{xEKiSG6wh53`*y$NpXrJZL%a8-b<8LdJ-#jVt2?g*V_&>aue*u_Mh= z+m7ib?Y_Xd*uTmNYN!KStex~dNxl0h|6=AAk!l+UT z-8EME=qgQ0eb7A%$IIW#?ezoG`+u!4!nXRj1wHm~Yr6)7I@3<+9v}@Tjzk}0+!5SD z*(CYky*YN?O@1z$ZuWFz#{iuC09^YiGBk1H^-~*YZSWtaNybvcpt}Z*wXy*|Yo<`} zVY4{T6#1m#j&@vKe-RhE6cdW1T*yCq6dHn91Zs!bH_G+<=R$^OHpK5;!GVUh4D4IX zui?X%B2EIcJCs$&xmpi3-C&3HRJ-n#uW46cQLNBFA-P4qp7I=%U4!F>1rmCERZ$=g zuLq*xqT26$O}`{zMW&qK-k;gREnCJO_1^#u2$6$?6j3(52Mj)+4GJLhDXQg(I23jZ zqAjh?=X6V;B{?CKKuRXO0k}TchSk<1e38s31NPofU-qP0(1|fNrI@kN5E3`Sq|ghZ zLXiwCnOV@-dvBfd>F%8-U+r>-yZ#hpn^HgKSy6P%1o)tvKVAYM?Xly&yNB`XfP)#= z_MiD*oR4x|$W+t6r56mzWUXYV%b<^MTse%Ty4y9GQjR%J%Ex@``4%3$C9$p0zcFtl4BXrIk)54WL>v5f70se2}U_qX9`~qKlFhrO4Z6 zPuZ|$D)~PH;n2Wv2G;tXE3(uQla19*C2YYD-RzN*^k9WNOd&A}txlVz;aE$>1CLj* z6sD@pKUJz^?Q{v>j2S~oqB|>V*>Z>_Im%I%iib@q@reEaXVK<5-6UF*wTi~xUC{t) zgD&7(LN@iL%2_2Q38p-|{i}4SKqo-U+$&!6q?(paG4xG3InR?qQ`oAMMag@& z{a?qgKPGaYkSGM@y!glAscp#f9mKbv+Ie8BmNOsDlwrcNgD||2{3)6t)UAq1oKFu( z5>c%rdrUFhawQs)=96d)?{!QBAY(m|Bh>bhzX|Gbli4&kD0?IMrjj%d`NCS6FF18^ zrGxO^abJAm%cGzW2$Ko^R_BmrpJ4pUzJvaEU2-w1c|8Ho{w$7nWFDB{sn+bJo_Gid zdYzxXTvh{(o?fGvlZRYYo9!HgoU6Hd@_uGf#KcMzTI!Ulq2U6mbU!{)!e>)2RlK?% z296b*928+;hGP-h1BNGbBYp**1!)C>qJlVLn_3cNkI2Z#ro8GkF3#xj2wP+Y_Q9?rGz86>G@52cXNkg6W|Omp3)!ITT=~V= z4N~kRnd*P&9KscYfaNoB4}&znbDaP?&0S zXD80wm0Cw6e?oq%-u%K%5#K45V!Q~9z@_*wzcyw`j*N^e{w4k=yM10GBQ>h1f&nXI zEEyBN8#Z+~)$|pER?zKb6)T1+Q67&xDwrfi@T;yRQsK9(6$& z<3m{v!k1>Iuet_I^kBg}T=}94LEQKA*Dn!x@=OK)&`UMd5^zzzHxe`mn*UceSb!>* zZL4CWhBn5NW{;Kz{(em-|GgE#lFZA=6(!Hj)&qrtv7$_tkhnM_ertT>r?wb{Dl;qQ z=*R82f0#irg{pC{V4GQn{<71DP+JyIACgI^?1==0|=?Hk$cZ?Huu7#Z5`X@I0&8tz8+X?8sdWQPWK_Y~f zRvp6iULal{US>I7C|@qnzIkBM)0^WZvt<&skkr=rMvhu;vZIFxgYY}$9*bwoCaZ-7 zg^64=V#{4jdT)C04Y@l zk0wvBg|O^l4lAf=6;*I$6Vkc)j9K6Pz!ZF0_{zhYNt|icqrefJ1y-w2Q7IQGt-*k- z)84{IF#^aN(Dovfn^XME1%;NS7JoSR#u7gK>yUj<;itoCW~Hs8#bx$atEuZ}9YzQL zUBLhxueFXiQ7uP%h4X72%$?#_li(c2?NW2?@9<{HVp+8DFM?Fl?kRLdRfP)!H(@<#%R|ivZz6#UI zT2pUtq;|?%Y0lon&C_3TNlD#YUf8UmiQ9@RyQmuB-PBfEBMQ?rl22R%rtL?9XzvWj zc%0cwx|wyb|B%_;@d;f%uw!g|GR8P@yhXxZZa2WnOR^t3ZSYW;7 zs|dQ&;QcZ}Q~$lfSC5eW;|PzFFd9lo#5>q<-1uEupB_>^<|MCmkY)bO6x;C%j*mZ* zLgF&-h}4df#6P~gRnzU2VUIX}E&&%~$U-a!Y^whGv!?bnGHpVFQk5bkJW`WlHXTWG zTOsjC)Y`y+nPnM7u0=X|yaHfAnuRg#Ckz+c@T)Ohk@5ZyHh&LRjBCU*h(M+P!W7fQ z^n(e@k}P%@Cr|VQRY1%(h<`ouO$oJ(N)2yfER4HDqmC+$s;Wwo-0`E(sk}^sm-=4& z4nQv*NK3dr6bk}be(2g3mQzrIbMT)TC|oOZD7<*QO^VYb{f4eRR=b|}wMr0YkI!r7nP&ca+u$1&N(+x4XIf6ge z?KCvH2!JTz$Q%HTJ2l_S9mq*t4Q&FgO>?EZe~_23(8pp%wD`1*vqvmx15yDo!6<^G zrzc0Q5c+9?!gW&vzx$)|f}W=*o;I9qv<#S53U=B-qp^*nlOvAYr^rzTc_xKSVvQ8_ zv>!75(%3&rCxskXq;{zKuz1+bL?dGYo!7chtcPdT6Mw2!CM%bhqtK5jPK{f=3C^Q1 zv0|bp`K?DT$}_Q@?HML8?5O_Ze}V&4U;Z!(3FmX0?Y?yrhtK7ys)FmD5ggW*je}L3 zkTAn$rrPVEkJ>u(?L;4Kzf=62X1~-fSj>CYfjp36eE3>B-gK&f=GME(kYE`Il0%99 znLVgyi}hlO7=ssj{I{Kl=(?~SX^`{oay_hg*+UTCKE_xmcs*nwlGwb3&P1|$0Wqh6 zZL*vKxk&lym-MsjHvSI)5JB(0!%_-_wFt!%L(}6Jbtwf-2&`_79s2CgbB`w#&n`OE zT2ogQP69#_g%R`loXKR4F@_?`Ss9Gz#2q}T7_F=zeMO$^gR!|%8BgLTC1M3(LM$D!&X0v0c zHGS!+dYP`2%jHU0>8FAqte@HJ<dJt{d_fWg1ir`7@iEIZqt_o&6(#fOoFGhaMsw%(U1DFdb$Ok0=Qf#59uo|mo8RYo{^X%+oO_KaCkO(JX{!6yS)7mrq2Gv&GdN?BvYAzA9^$H* zO;8>n@w}htTc7nj+v?@lc%YC>+hMK?)Dt zk1fs4*{0~~Tr)m<)~;<@s~XQ!#7TluiZBSy9@rXvcC;w6oXQv^l9R(jtgfl6nkbIB zbmcNdRWqGUN#ca9^$m(RB3mpmy2d*zV*0+1Qq8WbEOIQ(sP_H+BmTzU{1t9pImh$2 zuJVOXzr@<=2&qDr^U2xw%aJV?bd!+48(_3XDb-9-_CjZ(?gld)7vg<+g*XUpsr&!t~Nm!Gqmr!IWZ$ErQQD$uKtaCh>pq-$bbV;J5F^@=(B#M}$*;pBH>-r9{SA#ia)C-Wc zB+qk#(5KWDxi(Zz;VVT|*Z3-?tTcI5aK0PT>&6^TbKZadeQxg_6Zcl>_eMBvx%=Qg zi?kvPBNnqc2m8k?mpMi^g+Sj|XH|0x4t&1}y|50fkZ^YK=i`h*fFKB-JZNzoor!}1 z9988oMw6x~gTVl)Bt>2{;U8xYuTlYV95)wSNQI+0)At8Gv@zsaQ;4hT3gKK68-BL3 zJJ+1cm1^!S^gV^K=4>l}R)a8S1xX2hpm3F;ENi?lXu?8d@Z^f|vMit6fw;1|N?jGr zyu(?)-`OTO!#Rf%Kei?}jzHJgmW=@kQZ{kz&S{(zlto5a6eLM<=3{8ar?s_IwnC_; zqaloAdVGu(a5_0*kuK;aJrt69wSI+(YMeY6Q_KDqE+0Zx7-EiFmRQTh% z&L4;X{O|vzf9dyc-cbr|3~#>oE^oZ~rnC^y=>|aIrj%ROOCa-epVo5KFsI{i7a%PK^**qoi1aTA*#a$L@f%F54vS2)$k_0h_dwXbG z@Zt+M7>)uKC-*59Ii|LFLJ}yCS(;KASU-0j6(rod_mG_n7ZJ6_TIlt=tZ$82TV3ay zZ@$a--})G*5=KH0NJ+LxN#`}w*_<@pV>+2NXX+rL+dqrrRv@3G@YrU>rFrNS&8i*- z;aL^G8JaOh|Aa?hqU*ZZ;^GLS=*e7uUDb_nNbhW=Z0<0SvUy`9aYSB}=)A=D-Bqh zp8sU$mt|SAtHDuO-5g0&6GGD1mn~BI;9{z(LpM5{v6M@7vK`+gH45l$D zMG>AK;CYHbN{n;lWl2>TV$UP=BZ{gd3}cqdCB?EJ3L~6=@nk~J8f4%zSRGMkn#Hn4 z>$7kY=LpY?O=Yp9Dtx3|;iXqz32*Q9g%PES2PQTOCOq@bVAMGS6 zNHEq=8;cQ=%z2znbBbk3m6xn-tngR<%K!S>#)Zp&@oV4uwfhH;%HP`A9+ss>&6lOO zI`C{i>DjF{-z9^vnfUz4Sf%%2B)${Kofwub29rQBkp8 zWa;x4FJAoWNt%DIv%0?dbDw{f7cO07aWZB;o1r=p#t#s2hbZu=7Ylk`#JO{qsC32B z7<63|L13F=cyvmz!3u94IHGTZ53Hgj;g|17M^OI(G;hJ-C5JaqRj(D(ppXJiv@S7up za`?hxP?V*m--(-G3s>WGjWo7ti3Y)w(ORP|MO_g^F-mE)uBfYmZns0X6EzQ0Ra2K1 zD;=IEF{VN~ho>CI0?H!@f~JmOOj9V7l2C@2x^7lt-zQ2U9^cv{gJy4%QH-bz zSy>=Fi7po0x%&xW(qXi*#*0_hS?_fTgQl43hhZ}_QP#~Ebl9P+HFa8Ht)(anaA&oU zvL-+i4-#yzxrOQBy-a-RGe7gGFsOdvcp+4;2dGDA>aM( zyOfn6i6cO8a&pY0-96GY!}EQ5{T^W$o)saNfWz||54q@CaTFo~$+Dhds|i9VLK9P$ zHO@HFG-YdRi@GYAE>bGj%mjp?M_n7F^l+Zf(O6U35m%qOit=KP_V-9aNfLQXmQzZt zkzUARF=M&RiNY9(LTQc5ie}h|0_WiJ&N(L235)5RPA4G>6xnjwoa;?YlF7cSzw8xC{pq~XAvyu_Xl+Q9S{k5 zo>P|PlQbmHQ^;lzqH!BqM_DiF4*CoS1L8QOGe|fcPsx@!=JD46DADXps?xG|aL8|c z;~P}ttNh&0e2&2Xakr!?<{IXU1y(4WP*i1sF-=XRsw%=Lq?;s9BE^e5CwG=C%Xn}< z?R@16U-`M&VzK-0{q=wU^wrm2c`zLI$(FgF78T>uMSW*?-=>A3uB)Hu-24;;VDtLR zzmKtAiE}komO-?o(r%|W7Tpb(tZlhk+#C+<=jAB-)MiHxfBuCFbUFrYGAbQly?{b% z%G}`Viq(xR%DQGeJ|$ab^x_W6NUU^plb9k~BCRD)7j%-4kQ`|t3c!{XLNuWhuCi#Y za4sM(3jEMxIbCo%JL3Ay>%9A;kGZ$E%Y|!~D5d7kg9*D2GeWh-U{I5#DUBSl2lCvb-XRlSVA+0x6)b3aoPkVN53q z5UyhP;ayCf6Gt(D1?ef$yreQ3BLPn$oTMAAva;T#C`;D1LgF~4*GU*{o@4dgCH5aZ zYC=G~rsb%VWIoSXUEAUEwW}Bd&em+L^@t-yRV~q4b9i{lgWX5Wml;9@oSu%U(kcT^+=ND`;es>`2N{MWMjTk zLg9qPR28%7gtEx!4!d+mT}q?LYeVfEx&}XpfxIbvO2x_PDWMq9>kp7hk!30Q*>R*S z%O)r_3|V9;agxyM^r`Eb>2ylq!^*IWa82rT)1$aG)YWen2Ip6{cL0!PBiqc7`~e7nH`xAtwf(Ho5II&l zqew;3%CfL)PP-jX)h`J(d%4$5SI%D;GOh2DWt#2vHD==jl;|@YjCi!aOI6pr|IsH+ zE}vA4218B{j_I%V**|*3dvE`UB#L?M_elB^IWT|7Uan?&?_eUy@nMk{DtBZZ_l9D)~c`Bq7q zWleTKRuY6Eog^ZN0%r4+lfxr6Hdg6uuF{JYv3DMYqR#It0U0bZtXPJM3 zOEo&p!1uKYa5o(k%T4at@mr1P|#%Gq>TIv1rc^P|71Ob54$yBymEw z+am}jw;0dYr#>g`^u3Wd*iuijlqm|E!*abx1Ed zON%Ng%ZkAB>GgW}zW=0^=_!xVuy@w^aC8P;qR@L{0iH}|R8>t71UP4z&8AH&Hw@5L z@#1r@JoSIQdFz+{<$v{Gp4FNCnqE;0f>>(l`C-zx+vhI0v@D!dXB~^5rT~QUANTI` z=F3EeLpq6B=ZqPg{Fnl)p*NgUGcbg;TY#9c0)zrnf7FECiUN)WCfY=E{UgB#yfXDH=DN^ogv;GCv`V6=`8{N^&%rP@9Y^7q%#+B~J^|q9iRe zMP;dKOHo$TXLep$S!8b=VGILr!1m5I;lq0be#q5p*Qm82Tc(XzS~#698TKPS`?De6 z`OUw>dvAP;YuhV?zG9&baTsE4bLU|cMJyHz-hA`TW*YIV2>4iJecwmB$5H5<4MPQJ z-9&FmVTl3-&M==&a1P?QLzKjLp-0&7lg{TX<_m%-B<^%LJUnDFIU$O=T)BAzPx?&9 zDN{W|S2+UR%s;4rO4kHo%+~h##vyrjKSEJ7osP$O27|!>V>LxykmtqOAXd}zjH1Sw zs+6MN@3X(ZPruhANfO@s@Dq*>5BSWhFTVDF`rrK@=l}PA@2@Q%-hKG3or@PUFG<9A z-uPb7jUu z(l7lIwV848*{i(!gJ0+H^fu-6kd91P+9le6wGtscs;b8K6}}IKnlR}Qst&XHDVjC?+>4}+N^Dsbg7yl7*B{MMARCr))=D^!lU0!ntpuK z^l!{4QsBiQT^pgx676a_(TFrR6s4xyTcx{xi5pKn!)R@l2fMqJ#e#e}rJDpKgAImT zmxx9?9L#gV>Xgtq0wu805eAAPTaqm;ancA(9EE^EsiuIaWE0pqnT(OL#?ES-i0O0& z;4DY`Q%tTI_Io%B#yBSPMnIy#$7+qQ1le*9SmLC~`4_&&bg`h9bm(?_WXqJIC{W60 zF~zY(gFXB(qSp;@I_BhXhN}yLZU-kNLP}1@r%hGA)+}dp;Wb6j z)22{*b}>s+i;3`jk8ZC+f6!w#n^6`egJF;3<3lQIv4P_7_=xX*`#b(?uf6hxtLH9E z|C`_Z%}Lzv{a{wks&q0_8{MJ5zPf7bvas3XozqWC0IZ$;qYHo55cmN%uHO=y7q2K3 zzVt=^^t8VC(Fbom8+iGxOIyQEa{gr!-{*yAUSezKB566{cYp9b9zGg#?%FL}lyJPr z2%TZD>fwhG*3`(rXIT^&t?BEU-A51T_a)N5!RFRFdx!6n=LM08(3&&Xmtr^=GS8MI zeMx^~cs4d$^444Ll0;pyqGn^~0v~<&fRn=oQ4rzzf~wAlBEN~EG8SW-V9idab5`hV ziZFSVA%y}ViQ^t+S#dg^vbMfToFq)gV{};%cpgd!a&0Iy_;HVEYKgk5eE!8RFdVI6 z!Vy~+E>qPNCR!y5ead2q(-kV{Qd&V)S(N8+1=`3nobm>%bJ6|eY(9ax~Q0*j#(@g6jjBGFTTLas7E@R zkd`@?M)=*CPh&D&Qm+mf^t$+ui@}QpRaMgK_R!isiB#`&62hptS=d^GHkLqo_(8zw z=_#|>44g#;5gTi3l)j=~F4)`OPrm%cFaO-#y@w|idSadTk*}m3jRq?8J+D|!tJqg( z#{l=!5rE!c^pCLhO2F~p(S33Jz28ym5B6mc`J&RxZk38l`E2*rr=H*5xVko=$Th1Q zYwRB1=e;{05d;B`9v-oCA>!z0mxsH%oZDI_O;beJqn@PXS;5MR<#aZoABW`WjOU)c z$#4{6k9OHew&><7EN3;QtI5h5X#}B=cs}%!fX;f3q2j^ALyiwq#$(O-7q9ahUw@a= z(|YA^j4^Kg`)%oz8^Cf^btZ*6)oi3yESBciov9Wm>FB*^}J^H-_U8c;_l85&m(Cc;aoI;n5?TyPE9vx8` z=p_kdSr9mZun+}(l4L|yj47)MXCd%IjA_oQ%SDRt6;cY4IH0PU6|&Rm5P2S^DA81S zQDbH@Rw4tRyfzfo9M2C(ItkEJ-En|b1YwNl`IJ@7bTUII$;!%zveYc*v$J-qCQX-Q zX@+rzIEW~9iM9qWP#hf{FzEPv8;Ca}xW>HVjwq`!lboyO%rFrL_x4HH7Q*7>Ral0tcG9vL~bg{rU1}8%LtLp@m zI43bxjWIRdI3W&vl<%MAENY}LNjjaT6Xhh6Y11VM0!7^EGM&#+fk&^CP^A?rRET82 z(in!L)s@eG`h{Qky&t`OxOcq#+xFQm1AFcI#m!4`==o{7!1ub$7b){>f$}5DMa8-EI~Z#y@{(IO zt`qu_aJ`SQlFU^EgMKq9r+flWG91SE(t>i>LQ|I|m#<%K25{;cV>DqDBa}i$KEuHP z8BW>Td&IpvM=Vmuwd*hP+uwPM|L=eJpEEBMe&8bli68n%;qW|%6OvwMrSW46(TI`| z_+fx61hNj0(&2w0IQRX?BPN~X@?y!fh1fSgggeZ#XBnf3+ zu$awB`n|K&+zmT7`r#@)?d+ zh@$}G0xDy8?z3NFGC9HbJUZPKq@R!$HGB8(a&Ge?pZ}A8fnWWv{su!Yq$5g}Wr3q{ z=@|)HoY|XfV+DR(tn<9)IyGmTTaBE9qA2hKkEA|x2_gP;Zu(7d0H;EaIRv8TjSW^?28Yu+xG;PM6u_R&G+!x?gESF1+3Fve> ztgo+gaI{Yy)(l1+^kjkO`y3sPxp4j>tKF5$8>5v!Cwpe*Y4dHIJ*>)lsg2O4vZm7I zPj3fs|KZX9=*X{a9A%=rI+C}ZzbTFNYu{JV>EWF#m)4V~zxcW5Mi1_PNRg%3D8y(@ zr`N+epIJU96bdO|&>zt2by?0&=?(%G<0Y#r8)VCr*{q4CT0KAF{KZWWhH83(^uS3+ zzds-^YH)_6lMuuq+Cnt!5U!0lyt~igqXnatOH9%Uf9>D<8~m;R^6wy(&!E4CGZldn zbfN&HI9s*6v-5xBL<@t*#hpI2S5V9|RG^xyJ5N#>4X&oJA;uqZ@!A#6U40g>w~199 z3gbpSc6lFT9+6TBBdY< zeLSz3(Yal%4mQCX7dGQSrJB26N9ZJWmPqfLulG*eu%DX%DkqoELK+pP9cH- zKME+Tnq}I=jg^HXcbZyC)Uw9+duS(_PjZaZB(4T$c{DC~?N9t7(?!L%|L%Xz_Q0o` zG!=iR8*hPx=IpMTdwF*Gw8;g93S(+hadCoGq>>l%S>mdM4`{l+6Y@~lydlfNL^JdvK-$J=yf9E z(8pSbwVEgl8FYv2@9$wZ2cR@*n$wM94tMv-r#b7R!83I^{;F^Ng{+C0yGIsy>H;Xl0SXAQcpP)-nb5*c)qnx)&yG+LmKK#J*s;d#_xlkJU7 zw59o&$Z^`Ce2MiPp0I=w{5>!&udyewXVt26eR^w7!jRy z8)nlny>6ETOZQvMkw3(HX3_&#bS{XMIC(4 zBMJk&z$ddA0*kIS%lRBrYkcKV>59>C06j^l`n>i1AF})C06JOcaR2>hzwr7OZ;$%J z{jdN1Z%pRPslT&wvF_WBRi5`#Is(+`KV;g^68Hfdo9lEsu{271X_b5Pl%sR&h0xCwP0;^jmdP{m|wKUXv6x(2%~*g2ODJR z0)arun%I|gIz9-CD=R!Hu(hG844$9xXzvq*7ZG&32yGzrsEuGgPl>`2lS$71=db+- zeEWCa=F*kRByoVK3qoZPpZdHG$_f+h9KgshDIXTRYbo ztXxER11_Aq%F>HCoG%$K3p!Cq>_q(#c0vqG&b#?(mJ z`0cG}wyjQAIBn^~O*FMq60MtDfUp-*>k2F=2$ix=X3n9Y1=smDhPyq?B ztnyjkUTX@#MS&+3bzL!?O_@%nSm%hMm|nk6Q5@6j^%)GhWch+@xg?z*BZGjaUVNUC z6Hdzgtp~U7-CVA$y*20$?;RbF{rkK3RTB1fm1*}=5rEnF_ZIPEo z?jAh!r|HbQcja<8IOfoWJ!q|5HA`{eYWKJq5y1EXJ(#6ME|d#;2N2;uC~EECr2sDZ%ME z4tZv!4GEH%d@*LUa-Lv#3FjpJwQag9J1i@~i3liXQ~E+9A`gMVl#a4&dcKcs!D!b+ zX+@$*LM{r9^Am!gIiDKc1a=}UbzNdB!DujSD$Qw%z)_W&FbeQ|fpC%}OgK52AYEf4 zURhaTkxiJ+CakTkQP%|zA5QQ*MY_x>QjPL`9Dt)kT zz%-Y6ni?rVNgpiKRnvzz!eOzvCKnBf!S@4}iwxUI__<&DS9t2>xA?*De4Y1x@Gbfg z^agzbsi-t(!KwDir4vSLssJObZCYlm@7N>LJp7~3Q(>#AaHbCV>RGU$(3rb~{; zM||>O&ey*A9Y%uzohV^69OA2{8}Q{XzRc54U1K^gNFtv&iO7~2d0yZN#h^DN%`$w^ zgt!!0fmA-XZy!=t(kQXt4b_$kgh!hXVNZ6LJErO=-L%#B-KZAdba0e64&0sohO{om$$Z@$NHIHIaDK(oEx zr<(*=At}Rv$WI9T1W%^;9@J$kQ=yYEx5IH5QM!+ov*yY`$R7O9=gdWty>=r&yaIJw>lSq}I(DxHdn|gtx|GtzbNE z{@y_tP}bF1aD}0^0ww(>TI=jyJp@g9@nXs9>MC0sTReF106{aKAH^}!Go*RhT)7p6 z=&EMA$XSS*z?Vc(Op?TeXHLaMmXZ}U&Iy!IO-it}BvC|J6bRcyg9$0|mBd-7bcH*! zhOe%zQxyfX*_7U(*F5~SrYf4^Ce~tIjg=7kvI#}8jbEVC>r&Mffv+g45^I}lxEpJ4 zijb1&a)AsYZocv=*Ppt<51x67?|3-rPeKMLu1lGEd39_=2GXC>BI%1RT)K4F|NSu8nT z9O8Ej55`Llrwi(e&CS)Vt0#-uy7uI}DC%2OWFMm)7cQM=vdl5M=KJ6O0aq_y z<@(KQ1fho?G?zhk`#n0n4uf8Yx|)-xImzZ4)og|}5@{t`8&s^ol&o$hq&5e9Y%K^9 z33kT6{crwTeC-==a^?C}mh)3Ofn;lQmB3e2b%lpwV{IKFe5$-A4tzYnrVyGuQ%I8| zm8H{P=v{i2NovUoO`7Ext?@k%${dp}@WOyXD(b4F)9*K-9mSk}ua7I6RAOxn zVbmn?R8@vBg2k*UnpvZpNGjLZeVZ1KB+Ih1JU!Xu#S4ueD3lFI(~PRrIP0jD##qht zV2U3o@}l75Pd;HjU*P)zQP{zcJcO{Aym9dPo`>`WRc&$jLXY%(VZ6he9UQDc%ZXVa9z zIr@VEwLMECmV(+;ly!wKBa{rlGy$m=M-<0|aYUYFWciZANlg@VdHG9!iEGb)o?ri! zzrlOo|1JEpx zA}^-b8?rY&WcTDzb3sj~!~A%czTD(!a>B!teY}pOTFkh8{{h`#KoGkDRer-sbMYi! zoQOCo)8lb|f05SzB$;9A-}pEFjsI~s;5WY8*eyFfI^90~!3aP8Wn>hosZx!o^GMA0FZRA-8Tm zMG%DSY;6+9O=0V?wKZML*n4!Jiqp zbR^57B3iw`&W%?YZrnuc7+-o=Qy_78XE))Rvk(a736eNOIM>|yCj?GgbX8MU74mF! zIEo^Ipa}%6s+z25+E-2HzkPCZuTmay95pMR_v71t!!SbEjWyc3#vbdiXSe$peBVPV z!E(85j5N|a+rep!sj14UDQ-G})=jO!^8|qxAdqJXMNJmJvrQbgrxd+z_sK2I%JYfi z5Furg5Oj9Fe$taZbK-%+*=BCv_dIm1DTgp`sTu^IGUF+uW zXPTTrYwNRQp|dt+5H#HfDbaC9Hqf;In z9P;RBpM&uUK{w=Nc0|${kxyZ9G*hN>+6ZxM97lmv%fOItZf{k8^{@V`?w@`U(Ea#w zNvj>6g1ji@@ySFLl_oRFRMw3)R!3jDa_Ri%pSf|hvzQzqoa4^@1HSgPZ!j44SzTLa zV{?mMuZON3f$x#$OD>+j#Bk83EHXS_l4m8J7a-5P=!@AYR$M}cih7!#wdH)IuMkmy z4SlSVT)FXCR=)QR2Yc^ueegQ}=KuNs#xMWMud;S|6=`auRcvoJ|LKIkGC?{_%o1sPKg&?#6g$MK`MrQ4k`KjmxYq z5YFPu=5E7I5|O4$;Bo4WAI;wg2O8~XQ{r8>&;Uhf@TK2D9bY|bVwj2&TGOvk*ayn>Kfno@O_Uo z)n`T6|37j68LZp3=VxKh7-Qx+-?-w=C#Rcl_mw)|ZZ))80tsW4!J;VPDu1yH8<%8c zY^(gmWo#S@hr<3Mp~3+uBVk!;A%R+0-KddT>c08jb5GuRuk^;5(-^}a#(Rz^DANjk z@v7QiPTJ?Jv)(z!`2T;u=W%P$*=&N6l2u(IgMg;(sY`u;eRu~euyp_aSo*d{NQtnT zp&y9jm^g~)+U@|28K{a9PdPwJ636U@9c^8s`vKo`2(uvY@bPHc21RgaQubJ3X_|(* zu1P1E``(NikZ2*HcZ?&V9$YTH2_GWeUrO$oNtB3YSvjy1Z`DZ*=Z8XAI;dx5E^6DGn zJKz7_FiI~xJ6e40{A~UQ&yE)N_L~(7%P@}ket@Sumdhii(+T1r�S%EKLzeHtQP} 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�qsdM*oF_HOpISSPd_2b> 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

    Pp8xO%N0+#0{an$ZYDzn;AQ!9IWGv9m$GG8?qK zQK$XrXnI-<=ZGeDx_(@Z`b_^``=FZQ64I}qefe=)>m+~JdiCh_gKO6hjYjiOuO9W7 zhLcEV5~80LMuBT_yd0;Ir~6$q?i&_{Jr~1%3_?+eq8LF)5Pm|>n6Uule(je(i`T!uj@MqhhtxzCbR^)oKEi{pjwCH1t1xh_7%XUz zAcE@xAr)b|oCe^~-~3m%*`NQ?1#4z*mHWfj-yiDcD4fb9lveC_WukBy-n{s>{T}V| z-O3ZIYxA$WcOLv{?UnUg5BG2M+N17NqAm!5J%8y19Mc?&ZTn84|)e1^Tb?feQd-5iwM*z3O?+qbJzSnG zz&1x{^mI&2Pl9KGt~&s0H{tsZq*4~LtU@q%0g8q_^g;On%ClJ{Qw*Bz5FX(WIShJ@ z7OtE+1xI4=!$18YR5=ADKaE~vAFO3#=!Q^z9<)FZ83Ft_#Au*_I22M-A(AN(4?f%= zf9-$$Z1+F>t6$b1e7G)taOZY-{=|H`J&e3(XU{1WmCXNM?ZG!sPoEt9R^`iU-{@Z3 zeC5&G+dK8`+6s4Cd3N@h)%&B(o2EkzRZ)=YN4=vHrx%u{h2+d(qkgQXyGC|O0_zcs zK!K(xuoQy;)JHWJ$G_dN0Dhdzm!>~~Stw9cfH<%lUc}9bdG^Y^-p0VG^*hr`nZJC0 z>+oA|?BBZf>ldCl^Z9d+J$`I-N1>B(H*H_t-Tt7MEN8H$mhk>C#;H++=gJE>*gwY0 z=Pn_qDA;Q`aD@&=x`EuHh>`6D|mae2VE!dLIdU1Ib67M1|gyF@cKG(nH=WN zS74huSU&;-6@d?E1qm+aU{LokF`Gf-P)Cq3@s&%T$7j!<_W$&~&E)%Ut&^X9F4L}e z+R@p?Wj5h3sRwS~xQpoBUw!Q7sAsjzrT_H$*MEQ08||&koRCjxm8HjL&MtjG?p@#M z9aIzL3NtAbXP#5np5Nw;Yu|hL-X98SKQ%R3nljt|&bPR=O5d9)co&q(nRK0KlA0k zeeeGI_nHH<&$A>`)EFgX1bZT#i?_QidcgDnCd7yPHOv(kA!lgZJ=}w$WRVbDkbs2{ z(omBe28}lQW)~jCU|^V-Vri^SRdBs+A<{SmO$N4poQ3zVD@a}vl@QoH+~*Tp2I(Vk|Y$8(vXFb)bVMBP5bMhOH2W;X`kCFl^T`bRy(S1@ID$`fdwpMZm#P8%iV~ zon+AK#Tb}f{Njr*;5WYXRlM@{Yxv%^*C6{^cyX>cV#*`u?Tt8>%qiBPZN!|V;CG&V zDOjA7*sJ$HR3{Uc9@h1GmW1uqr=Px(sd4_^o9{k&XJm%MpS^VMS1<6{7yfMX-QWLq z^WHzpFlt)iG_l$@j}j@0;`l%k1(qj17l#y0MY79KZp4$39K<#qjavqe1@L3V9)%x! z4u)o6#}4IgXETT%0aPC-iolQudLsv)nJvMkTvY3A$Wj^p(1+IxalaqI^GEoVzxY{v z_2+*P|NI|)1Mk#6z*I&-{kFvB=a|SL41c^g$c&~xTYlPajc?1}G8`MdL*}7Ql}W4&pX2(Y zb)0%^8gTr$Dp{d2N5yunLZ3gC-d-w8U8UwH%%#v=A8bx?twi^pXm0Upf9bDY`ep9e zbdNe)kMha5U>_9OExU6V-MP{IK|z(KBVF-3qv&`ksm!{x-|il_rG8*zeyRe)4cNuC z#lpdUO>afIQ(h2wdSIJ&;9}f0WGsLmD?u)G z0xo7`o|$8o=W5OVcD&k;TZbkSjGT$nCr-7)R3PN&h-)^hBYy}_;%S=Ab3wWwBZ|jw z|FHmm%t$roM;;sbA(XTVacKbuWawlnv3*LIIB^)5p%t-OwX@wNf#=8qqef~#O)-U& zdK?a~-&ucj@!b45Ych}8wg88>Kr1nN$A{1a1^aXwvxzg9((mA)*2Af_3G5GD6ln@M zg~K3-G3*f}WEy-*#cux?fny`TR7NT#B944`R)B=Zz_$XZIS!5LA&w44AT$m0$O0$E znE2u{EH=OozqSt5O(8oU;GplJenesUXVYDQ4}u?jt-g>=`MJp5m#C7Y7b?Yiwvn-p z;SO{8K}s&CI`^(``ja-PNU_!xPM^EwE8S&tyCcq*r%w`!C6noyOxvh6{DDqCsyFs? z{K9;G?L?>)WN)`xbxG_x5}|29VUZHkvTo|#tdayp*m245u>gL|h(CH`ijZNz3LHvM zCRn0O@7oV*VI~uv;3f(`+}f<(tv>8bQi@cnl+G1tK3UH}$R$WkkLUzPmMd!|CUyi@Z^d*NN(9O9MF<9xSo+c?iWMYQg-r0QbsWssX z&s{k4ctYd1cZQozqy)-y>gi_gtWS_lDSR){x9(ke{fa z$__yog4pE{1R`QO0H_Fs^CfJ)K7e;~fVJN`4fnHZV#7jo$ zS=PNVTAV#Rs5iSuU7Z?LdbIJ{%8aFTp9E1Gia&r;OfRToSa*M6ejU- z_dfCyS)}qJIM#;Q4KcmUBWT)i8!Ymg2nqt;y0wLpHi=f)1E)|(WfWAKhp?;wl9)!+ zH<2l8;PW{|wIPy+BlL3#w1ywG4yQ9B+#v^+Gl>4cX~}uw(_9bzWNs?&%c;WD81{>-n#)q zymX-?Q%pdX)>g7fOpAwy_p6P@en&1*Hmz}#AW&qW4?`$waO&&|)^FT~(;Gk#xbXrV z3*g7g<>k|V?lCk?gV4au*KSB29YZX^TVz*G)%%Xs^PF0eV$I!nz0u*7@^7v_dGP~- zsXfp~JxdA6t?p3F*h*mddO*Z5#d@tcbdqMo@)T&A0z;2OfUy96 z%!J)P{|uy0P$qr(4~j*iWn5nUW)5J~a#d*o5s3K(q`D zV}OW>F|n9~)ebRgvoL!e=9gsDBZA(*1Dlax*d3f+IgM|;e+$rU%+V~od4R}hW8UL#$eFYXnmi@mM4-@UZ@^0^!5 z=QI7gZ@v3`$Oeh6op7_A7LTV2WWpFU#G{(kJ6)V9jzDp(-Vo) z`!?x3dFApI!)pnz@3!7}t}=V-cP^-fe{$=V-qBO7$!U`Jng?{0pG0sNLf2ae&Md;( zHZZ96F>_KzWKy902y*CPa;AvTqTqnTR#?SMSHw&z1>bg{$tqNez!(IuY9{jK1jw9# zYNHRy0ZLi{hob;tii2Q80AXXjDUce`)00okZm(#CgU$EX&#iv$`8QV+*}eYVt1s5{ zR#qOBuEst$T&ZYD;vr%Q(^vCCcB(W<8E*5uXZ7^jhWEn<`7eEq7mG^j*B}4<$sZovp#R~GKW+J@q7$EWS7psrrzClh(D*&4+dns%nL0NZ zq(+XQ+tjHcBekjRAZ(ZIwEY_=PtLyopfiM&GBe&Fv2O228k48ZkwYC2HL7N;Lnu9$>;qY)58qcC@5lDiBkQmTC2bN8cw|GqCRjfx07@CEw zifDx{MvjGwm_(}+(hW2_z5vIDF z5f9vcJgI6?Cw2!vxKXX1TA>8(fiDTW`yl|;Df6bdhL zV2d%P{3NdTYfu#hjHh7~`qEF*LXbk@byRu9wj zIjFRNh~dfAAMVg6pPJ~buAMo&^?UDJIrkSTJC|Nq{NSCh{n@YF9M;wny~?`xu# z60+%2X=AfdOJ?PSMbx>wi~fa;*WbT;ci47LEUr$KF8uuENz?J|Ywvycc5_hcJ$K>q z3d?bEuSJhU5>oseE672TlatbL`$pH*jR81*yyYJY;3v$Ddz*j$5P)NF+_Hk_ z4k8kn)1`FsR&I3s;obUraZ#K+lL@AcUJ#k4Pc2lkp&q+z&*;(#mO^5UP{TK4I4cZD zio>wyKnP_BDZteoq@prJrUOrjAcPW{v>6);@13i;N=g07mwxVvL`IWEm3KHvkFI@h zeNZ_S4kDO#$y_45$bz*QFwD4U)mQ*OVakmkA#oV_m=!0nGBfA>qg!w9?+={txwA8; zU8`qUAn^;u++U_Xv*k7FOZofX5eE8Ki<9lap#Jdcqiat*`P_-QmBp!1XV9%a+B-IVYhd|?mn*V1F_Jc6 z6cCj`6#JlP3gcSfV*&icnOD+(?lBYq0W6O6ed~DGWo1iMcXyq=qMK$ZNyu7(&nUc< zA*L04;a8t|vbTBc6&tbAXj1J2PfN$bo})W0Ruj2&-Dv2Vz@0W{dBhyMTU4idIY*dW zm9w~MZ!kPxAiWMD;5~>w4`ty)3D$h;@n$Mi;%pU0dHfd#O0FP*f%cukO7cq>*Q`N;%wYcKDI&LgPfDsuF1)x2Yrq zgb^VTOH)b0gD?cL7=ja1=&%u}PJn>-q2*M3``{te_5fc!{W#7qt>RAWLp*FBBVDP0 zvlJMi3BrUC7Y5<56Y(=&O_Tg&a^O5PV|tS2&J|9+mA|RoInoc;8Z*|aP%duprn32B zx~Px3^7fJ077sM*h!t&xF z-#XYTR-0{UEu*kLub}0D*ujt%^AW`%rV=botzYd>%Tg9$#s{keh+Pk(5vVw#k(<`g z*y?~j<}k4!VzQElWiVK`s`$hA-ovVt!C!sxEBN-^eO!C=5YI1M1Un~SU*7{WV53u) z7pqgJl>YIZPO4EKCg;M5+ljl}-rL6BXYD0to|mYYkCTUNoV&Tvsik?}&ahMATAdyy zJ>9mN^=>Tka#RK#b55U+O0wkmrp4cA?WnzoiVL#HupHW*cFzOrvr+~D!y{q^M9h!( z`L!?p8`LBET>6A{VbDOv&ac!ZpkLPST1 zdjT|#!`gfre|Y;1kP(I7`K4j$~a@a!`u@XC!ootTy5#kF*M|7I;!`H;Jt zQj4P5ii0D`SnL(Mr)g8_75$m(QLiVA67~yDD_*}ER;O2`9B-QBI3Z*Qi&G~}S!HD{ z8!Zmp$gCYcI^c$|VlhLEA?YP*bOKo?k;zM=TyffyB`u1gam~N606s+qhut4(909-( zph)uoO(S9kP%VLGs1kH<t3{WW=#eyNhYnQ=AX{5 zc3rkDZkUH}z5m)u@`)GRWiE#!&&I%Sf$FN%vXZ2(>s>PRd?d9Z=2w$A*cd@^iWD2O zBoz~?7a5S96p~Sb_^F6GbS=DWB`|2~2n`c{CPIc1kd^cJqt3hFUt{q*Pk$NzBG`u2 zBbdw1MFY92+_?W}@uIQ#7MZ1;S8v{VoQZsTFiDsC$8D7Jb6qYI)286&XTnqem-z)c zcgx>6+4s`lpDZSWfudiae6uaYVz1>hPXEAYD4n=(F+?g}%3dY{V-F67ZIw|7FDH_b z?YBK&2N~D=8Vlglq_q2S7vM()MPd>o!81uTJ>D}9p+>>*s+>|((ee3RnR3MAu!Xz`Bt#ipYkM0karz_!?%rCKT^}dA%K?g}U z36TaRCT0j1_;_XGO$_!aq!TH00w0?`h1ui;Hk&reoQxOqi?~_tLGK%Ihdu<6gQ^gW zjx9)mi1W`);~#(LEgZ<(_}p*5j5pTzV_NiLIgrr$z@$Hvs%!c*Dde80bV)j5%&VL9 znpi%1uCVg;fjP?e>P})XZ>}7q{N*fE!4c){Cni|glb7#&}j~vX0PY75*1SnH7@uw7Qm;=(&fiLk$E_dN4s4Oxc*Q)JE^F! z21acd^CwQ7nmF9JTfcF9cyH$7w2OS{m+D7*qE14wFki&&gK8o(ij3!`PlRuD4yCJ= zAF?!_#|!updY#|LJL~tcdSMy7tWm@2Hh6(1Ro5nd7*U8=sy3wXx#blc(iY`X43QlU zg$Wjx&`0Aigd)gD3OqU^3QpDo)8N5(6L=@l!E*tJR4O9fniJF8Vm#b2G;7)u62}R% zmNP1K_bgRF=upI73H#KlzXQsb6AV*z~XOiWfjk$FTx04suw zVkX?bem7(#D#%r)1TD!Yvx)L_Y)3{TjJd#3_XBbGwB2loN|Ww>_WbOOacI@z5|l|JbvTpUq?~7iaYfoTxK0fPDI+`sHDn*LJEFt5>2M4uHD1^-6N!4 zm`5RN>imP~(lxoX~;^z50^p>#NVDY8p&s%;Rj&=q=NOie2uW_$v(iiD!?flX#n;1t+LCYHDeCLKaguvEWcBFz_xri73k zo_h1>7}W6yzkKNo4&@E-DS&MW(9T&1cn;q60Q1X9$d?tY@4knPJA2GBJww0nb5A%A zs3XQ|5;|K_?Fk{}+})`a8@HS3mUnPAcVco{qPSKC$#(^Z8QERd&nksxl|A~EX(s>L zXPn}9ugN=wUOyVyec#DW=hJga#x+}=SWMEqk&Xm&$McN6Hc=}X2$B0{E1f zS3jA)0E7@IoPhdFN?iDl&dpL0nSJ~A!%;*r)ZGuaxBCMtczk(g)ylY!D-KgD&y=Iw zXHkxhS5D?ilZFxtWD-XIT`tEu8m`UW!=0Xjv-uoyofMcEK(6qZ*-D_t`3Ou3 zLx!N`3@C6mj%ya?ii>#5xDPsDkP;Q}NeXu8!ea@Fp^6|Ep|M2Zw+wXjKE)>$OcvH6 znx~QWlThsewRvYtSxjG+{B51g%WDSq+yJGTCrPO|$;n$6u& zAl$lJ+bt6@%JS!na&xO@%%m%!Osl5F}^i~x#dJ}Rg>W-NeDoAu7& zCpMSsdnhJy;%afxZc}#u3qSY#lzq@Y-rPDk8ghq^4<&KL6mh9XTL1Ad#SIcMIf!!{ zs|-RrA5C7hyuB-Zs-EE<&&26ok_H#TiygE;a8tj5Qk2BAlQVd5*h5^VA-X^=mT?$4 z_~jQqgP<~nqd^s+8$uOSOivea^Tsi(tcx6EoZ}|(_Vgxbj)y9+uxCY>k65hlJ`faU zB)N6~X%PsA0riIS2&*qIf^Ic<)VUEIS$?vT8l+|>&7GnCI|faip3W`*APKFxcd+*t zI{p4}Ud$XMd5RlS;ZT{%8d|afcyS2DwjeMnLX1Ozu>d}0gw`i> z0fnY%#7+PCEKe*=HBnpk=yu-&k+^L&7xPKbk+14%@4jP6iv-?|Xyp5E(U%z;>VxC}^_^ z!k8H^z_9>6UAT|a7vSH5ifJf}$MP}aTEE7ee{Oc6<00)doP&f#zopdp^Ox5q&PwMd z&sM0?)zYE&R!Y%cy?XoV+28$#ul|+Xg~HmE7w5RgE9c*D+Y_a_Tbpz0Es5fzI6I-i zKAu8LJ;au@2Vr3b!W|VategU!P|+J5;|KRXz?7PRd|5y#r(wNSMZPqNrPU&?zIlx9 zfFR`i$UK>Ykl^q$6Tg5zz402tW)H#z4sn`Cc6ttiDPhChh2Td3Cqa08B1{rFm`asz z2exu-xOac;sfAO2QlJZWceks>`+v6a0g;48KO+>Fxm051E32oorOKb^)zSO6zV-fR zxZYIo;^%(`p6h&+^C$Rd`|ncY6*(5br%x-;KN$f~6dcc?j@ms;Oq3E^+hJ#T+}|GV zz8iHSCP4|IdvdXG50yf$mTFuYY366wJ5guTwz(69wQqf~u=bx%aM=?d-dKO)%EI~D z`xf^*_xasZf^R~jT;e)ON;EUL%XT3%Lp=8@PoQWep!XdNTNJd2!sWs=9?~s%i4aaF zglp-DBMF6G5@J39@uZDvYX^yI5-vTpitNideC3%LOk^eOKWd`&S{<%mLm^+m>iH9h zmLx=NXK&FduCL!c%*d|)*y7^Mw|YbIj#2TSG)^cbP0p@*#;EuHcW-Sc-V=6y@#$05 za?HL?qpPb?CWNddP0P)`0f9&LNgGO4AVYtRxG?!QQK0`|5z`m5H!4KG!r;(Cp5*YAE0?fo)$lI|yEsKnpm*4WS6~rv z5|*MIt}S(8JoJ%}Mi{1f1QAeNWpNw~VP3V7T9B|Fyp0hCm{dw=?6=^F9!@>61~&9z z5*m;A8tj{Wun#?EVzQF)Rexz1SVVv2(Yg>ybbcXMx3f&3&I;#QE#)mu#ZQZE?_kpz z>~zf0Kg#L_TP~gKbm`N{uVsnw0Am-GVRRr(kxrtON zFU8Nj`1z)1kK*l(9pThtXL}bv{|ok;@4q$WcAA!8cDX@yyI8lGCt8|***-dAR3&lf zaBg0Wcv9UP1)R+}J_%GO=|fqR@Wk>n;F1|!f9F1e6obs=GI***&b~SfA@iltTY*UP%5adKRkrRaK zu(s58_9z33LLbgZyu|@~t@T9niqfS>k z>{SJNlJ+@SM$>A88!y4J0Dju=%}*u-2m&9Pn#B1tPr&9Jt-Eud5{Gp4-OalTa!K1a zy^+y4I>-^0k1W}#Jbrp<{nClgANM)??CI6HqvoJL<<@(l99gtWISFBTYHjuzy4g5r zrSfVKraqFw-XXS(AynRkz)27;%E-t(_BU%dl{`D@ISrvsAi=+Dom@oTC(nZwo`oMbQVAyFK?agVG&%b!_8%m%r z84ANx=4Lh;`u0jv6dmfA>`G}VnfTJ=TB4eWo3}c2m{zlT;AK*T?_NH0!fy0iLfx^% zuFG0M7$Tx#@GL)GbYlVh^dNmg$I&^Of*ART=@GxRw@dqnRZ5hS;@RahyNpkEx3?Q| zPR#BtFP^a{%Vatc$A*y6vP{Ib2G<7fczVAmRS$G8FRWzqYNNhs#?76<>D;91Z9Q0@ zkptRWDKEskqn4y(6u7A*(vgEds(lmY(g+>)0OoNHjk`8n(#JwcLovMwy~tpH^AO5b z7w0Za<38`ARd1o}ilD;?8}SItB9BFqfnB4}tnCpw<%!$`x7v;@;g!aRr-&GsUK9j@ zOH*~Ld8U^XE(JN#H4l1WLA>~fiDKS%f0_BJhRnU_U{GtfLs!+`u2Rz_EzkH5hw#s4&>pssZBp2B?;%${kBiT&;mz&?n7bM-Wb$y{?xIG8 zD4$g!5CN!ma4u6o!sDU0eRxy|o#@aD5#~O(y#B-!x!vviN6RB~xUA69meOEe6E$_N z8~6T3Kj?p;^TV`KqrBzG*?nqBnc8#ezE;)MfvPG)1};m~UH%V+wL4VGsq#Jg;FcvxN2s zHMF%IuuU3l#>M%k&tmkN31&oL#Zq7`3$SPpZr`hU-TT8AR^&;x=CysPlzvqx$v$ydFPWTu6b^NNUoy`P|RLu9EerQIS~Y9)d6g&k2afCxOQT_-UXR2%pG2G(%%}I0)I938Kpk zckpmvg_cVyMLC$5EISERJ|3jgYt)py98+eYXSn-rcwC9I)av!oy_YDLl@q#CUC2-F zOPn%s5LD;-W}vY_q=_->sYMydQj$B|+)ppt}unZzv4stHs}I?R%Q z`Sv{g86WW^i@p2%c>cm=kX?X@JxsG@-1B|#UJP61kYpt+Oyt~<6K){z#8=*2|M}FZ zi+}UwpI!d1^uRb{p36Qn`D9@ux?R17_eQ6u*@>&2eKTxi2f5UF(JiHNdjD>}!Kdls z>7V(0BdJGUyHmful0BJF_aCfh+ncVJF$6-(L&8v0Nc<>{;e*0>2QU`EPXTl?`H9V? zh$2dhYtfzCccQJmJId0;1bgQ3&)7#%^VwF^JCl+{n(S`aj7JR#A9_0nJEuvKojfnk z{(dTpRMPj7ap2}mlT#3PNhoQu1?+A&+4BsdZRY|W>hDYJi}TsGkkL=SUi2I z;pKvKnb$;O=nyY5P)8x)$C!v!9M}4ox>$kOq;T_%`*{8r&mw#$08I~2SXn{qum{sV z!1_U3N+c6%rINq<>}Sr_RYt)Z-+k-<=s0G^rkMJP=PzAPJb#{LmXgAB55wp-L$Q!< zE%vjGw$y*pD4`Nv)o<>Tc1#mfNKu&EY3adj9Fl!ilN5P&fNj-D#%|kGtCw`}#Wy87%fJ zFaO-KIk7XWEr8=%j8BDwKY8^R zUeF)^7c(a&ui7V^2{r4f%yp`z*J46E@$92-Ql;r<@*5#yfzq0DcOn?rwY{^DqpHz;mdB-5q9WaV7kj3t!NK zL7Q4XygjqC)w>t8ql4?!-OS|+%bj2R?Bhp-hZ3o5qK@7#H{`J z2fC4ExhQ<`*~kB%en9+TvzOo6+&Xpl=Jn5#tK6O9sRf(963g{7y;c3qwkX`Ej#nqh zgTmR9%&ph<5q0{pW$-9pc|uIyQCy>QFgn`k*?gAf_#B@SC2^z(ImB@^UVLK#eEQ^5 zpG*#bVOcnaMHbT2=CfyC#)J1ifHSCyC(o`79^BZ4t+&fR_v>GAmY=$0h|~PaW_9c7 zN1Klp=3;+Kyf^%l#S@qJzW>^t$4ExbO?-A@nW5c$^7rc>^bQzKKgF}^i6~dGG=I2f z#2w1c7GhpIu7;aS6_R#v~%sSd&1?v=lDi;^7+pO|MHLDTYTls*Dn3$@BQjrDi${5H<;gD zD4bYJ@U_WD;ox$)%3RF!jjmFj>Mq5(o}R4APKn70HM-Nw`%8(Dh?wRPGak?kG4oUw zJH{dU5BiX@LA>_lA~=z!Hy@eUJUqfv=ki!}V0$zjkIH;{W~hDrk<|LRo@Gpnf;t#F zqx{D4@%fPO&Bx9@^}f+-=}V;?9VboB_uWXl73uD28sv!>T$4u}QzRiNFccXKOQWy% z;=VOPiWWgqEF3Y4Ws3KsD1hU-@Vs#?fUyAnX9opP4627Ux`4Lu@o(! z5&Pkd{Y}O)&EXf%pR#?`XWseVee23!|5EX|)ME>WS!zzq@>hkM@~M2WX(r0$H(&km zGjr+G;lQ`EYCdx{UCP{dT#pMJTN$~{nVFfAu8DM99>t#HxNx}`(m;dcCP7FQBPm2c zfGGNCYtOM9IEF-F(x-;Q>Cd(cRgS+wQXXgLn{Dt8iJb2^7SFNe# ziD>51Qt7kLJhPfe1}Az!$a6+aB9_}`GR{)qgo)Zad-0=#;e~i6KQXxUR~6rxI#F(UBLgP2>ur;c(=)t1eB^h**)N^c=FL3>y--qJnnZ zM@V9XQ8Yg3Hx|JEkG!+#PbM2p*#sy_U|)B$;%Sx~U$2_^4(#^meWl zn}sPwiKlY~J1M30T3mbL)YAzsHAwz`a&g%@ELSer$93;N_=R8ieJY?!_nQw-tuD^C zaR>vj9N)XWGiQ;|;S)kDu!B&Q5}{>TzLKIN771i}F&^WRm{~oI`&1uZ z6pkrx{|JXIIP#=!p4SHFDJ?f5Wi zx|yAXR>;mYo=MMtU(re~Xc23t7kl2b)Wo;`$v3mD<9281sml7hw{D++ncy z@0^h&(Z5)k)6zBeJ6oG=GP$WOjTZaQEI&2J1yN5_I7sCrIS;& zoyL}#nV1U{p(Je|-1An%1b5^1hIs1a$JW&m@tg%h=Z&2&fnoLjxFR z{Ko?LDMd-7KC$p0AwV_pZ)#rf5s%iW2akl&ZAR9&zOV@;W z70qE6n>+8M&6uBFIsatz{?75!dUaoHXw6wx%Vb{6FZ`>md)rGq=OtW99o*UJDTz5T zFDAsqOx_9mn#HnrN0j>c6wh)rYCE+@!FK&%W@%#i;gRttgSxxTd0f`%wE=Geko_cf zqJT+t%A6}lw$T|XVTk(9D|>%@E4lgk7rt=%Kfh2s^M}1%)(}I{idjz7Esx10{p$H=kZ*lswoOxO$!P!u$d$Al*$nXP~eD7YZwuzxIopH2eb`(!8l zx9VIHItF1wP!wR84lFl-X?dU-o^XPIIAri(XBU6?$JbCPDC&jBRu?zds~5ii_J3O` zPG#+9UVP@@R`2fl=~TwG8_kmMb(dq2D<4~Sg%X8>;#_g^-sW!BH+1;L7_J`R(A#A^ zy$zL@xY>o%-*M^TIazjQEjO_G+eiNSRME8M_+;&1(3w+HYg$eVHrfUmP*HZ?L16qN1F#fY=3=1<_~FK4(qy`Wf?-+On?- z8!^X!bk#Q|1Omf>jR<&a0PXY)Xoeo|1I7Xv15h9&28m-(AkdN-B#I7JPOg}vj=sOU zvtHo|-7JdPkt}e2H1xmUt+h3p&)E};Q`e>g=?~3tG$mSIg3;nWtqVGz;e|DMA#zt^ zvQ3U8*;UkESbgqKogp^9_2&JzhEBJXpQucy7qXeEZE*{^k|@>E|GX~Ifs{^gYAi)588a5Zm@#9<0vI!9%vbkk= AtpET3 diff --git a/micropsi_server/static/minecraft/block_textures/Acacia_Door.png b/micropsi_server/static/minecraft/block_textures/Acacia_Door.png deleted file mode 100644 index 1e73fc3d3973e6885c66dbaf31a8976d9dd6377c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 846 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 TL9np100000NkvXXu0mjfyrGN< diff --git a/micropsi_server/static/minecraft/block_textures/Acacia_Wood_Stairs.png b/micropsi_server/static/minecraft/block_textures/Acacia_Wood_Stairs.png deleted file mode 100644 index 884d810d287c10edd0953399c483e4f56d28cf52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1370 zcmV-g1*Q6lP)P000>X1^@s6#OZ}&000FcNkle{^{wZdcdhj<#Z!isLjUAz*YE%8d(Zq< z#OO{h>UTmUJk4A2G{t{(XKV7x-ss@=xWC^qf~NKSvuKX~@Xn`)e>$1H=|Xh>7eomm zUw&qE{}`OlOI1no&=b^H!Hs&nW zY!F0xiK1f^d7_vuTZ-DElwz3a@x8r`$;-DkChwn@?@o*ML|+9Fz^b;i-qTAJ&I_#f zlr0o4GA%5ddBV=9!}dTRfMw%Y)|Po~d3RduD=B^sy!Av#KT}vQs9m78mO-{oFI$V# zie}X?t1P>t4uecnIM3hC3O-rXkB!uo5CmwYupu%`H9-i<*3&r8u#+Hw+B!l|{QYdn z*{We)TZrqoK7bGL$&jsXa&2C+Svc+Yv+BBUThC>xIy zkZSqmkn6o<`*%GL+glx;+sb+Sqd5;2HAYLMk>p0vF%lxI ztS7`sssvI?5FiAP`l11T@Z9FFfA-Ct-)(f0+*;4hMow!z4;D4u)G+91Ub;EJg#jn$ z6(`dbN=62m!Ff+F(ex6H4-p{*tq&B9qp{(0NH0;}|6pF{)1qaRDiAQpQ@)}*EGoz8 zte~4Hx{2o5QO2{QjN@s^$*iQCYBD47AyQh8^%0{a-9$5!m*kQ{&{#+71S{+Lc-e4k z!;>0CY7{p|Ip<}|tZeB&?9JWnK6h^PIi420KPxC&hZKTAsuA+@$$A7?HkQ_<&Nkpv%@dr#GRlDTBiN!Z=U& zCAnfG)sj5PC>qDvyrz>#GA#)qQi(td%jOqfh8;}+ zM7$(d?ZZ0R2L_$BoiA(4qVaT%A~zE2JavqmoR%zVM~o3E1(jX9fKh^uQAi=y03oms z`=S7>4^?jDJuMjvAWbwrMp7kdTwvLFBw&Lj6#-u>s+3Zg4ZuJhfhuOZA&`S-$d29$YE>g6f(s`zMOTKf86e;uZkz# cqW_400SSJk@EFUk{r~^~07*qoM6N<$f-X9o6951J diff --git a/micropsi_server/static/minecraft/block_textures/Activator_Rail.png b/micropsi_server/static/minecraft/block_textures/Activator_Rail.png deleted file mode 100644 index 3fd80a6cbcd1340691a76f831a9726cf0cc20b50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 705 zcmV;y0zUnTP)P000>X1^@s6#OZ}&0007rNkluX(v)g#p+bv`Zj{!sZbInZuaLd@6bwq47}`)#VmE3? zjs4?*WSZDXVkVh%lFYd1WExs*6HGnun0LAN%$Ya$aG#(Di9})r2&L0$fUS5uo}y1g zB9Rp!6buFdw!-1C$CWPBhJcv6Lj4L{#}(a8?B$9xKD$KV;S%X|8ZDFIIFlg&Ogx@u z;>iq8=O>Y68CjOe|E!^Y*hl?%fDYVO6s8n~U@(aOW0$hJOZ0V00LWbtO*imQS_ItK z1oZB4tq#80DMy70k|ZHX5^B*zGn(jb-B{0;8>WS(8(!>*9P0)F)AABcpzI!1KJDPI zSsXbPFR$q>bfZbxt()o*shbwZJtAID);4ouVKEf##tVuPErQ5_*gpIv);4n@oy_tz znWd1-vc8=cJE|^nAc`Os6y=<|7>cfK=ENnDo5X(+>C0WS%X7>=pXbrcDAjtMYNJl} zyG|`v!q_Vf$6dFKCTJDfR)bcjeYRR1>b5;RcL}3OORMm^3bHKW_xotKZR)l&Ja_G= zx>(=Ni%%PQzP;U}kj(OCBQM_V-+;Tu-KTzK)nyDM((PAyK#^KO`$fd+2ugH*5p*HaQF7PyGoo5 z$bQ@SEy8)rDFrauFWkTS{83GZ{K00000NkvXXu0mjf^mtY@ diff --git a/micropsi_server/static/minecraft/block_textures/Air.png b/micropsi_server/static/minecraft/block_textures/Air.png deleted file mode 100644 index 0b9a93b3bfab094dfa5bc9ffaae97a3e981c7a0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2801 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 Z{S9(Atso0RGB*GK002ovPDHLkV1n>kBRK#7 diff --git a/micropsi_server/static/minecraft/block_textures/Barrier.png b/micropsi_server/static/minecraft/block_textures/Barrier.png deleted file mode 100644 index 6ad397b66feaa350f56068fa5f20de6a9a165d07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 793 zcmV+!1LpjRP)P000>X1^@s6#OZ}&0008uNklAnZBO5Wsfsl~6Xkc*AsDwowbuo$y z!9gI7K)|RVP-3kRX%jv|Tl+!^t-y0=K|b4OjsC}zd++c5&pr1X_zy}s7|1)J6kW90 znvy+m$nEZ(3;3t^#-u|KMX|S*SKLk(^3Cs`ISAS3Z+1={`tq-(o(7EemuVM~( z4&5U#5by93S*~u$&S}eCTC_-!Fe)I9SeJS*pn^IL&Jh@R4{whHYo4 zL1QEsjLd>ljoxve4mlZz!RwH)gx{PiosxV$GNvX804{JlpztyDUI0g2NFvoM=H>>) z+?4Uv#bzu;_o3Yl&g1s{F~lZBVT0N1klZe|fCw-QgL6Ao^tX`NiYhJL>mhw?lvQ8| zUU%q7tu9EtVs5qxQJC)au&t_ufG5y3r?bR$K^hcuQ-j&;NFN&|1cZR6a7WKxIHH92 zDqo1A*@`Odir?puV<~zFU1di!)_6MBNr^01mx;_u$>(D;5@w6&8|Y1uE=WsuPFtf8 zj*O{ERH9X+=aF?mYE7oP{@k-Oxl4r+R8G^z&v=np?znb?4OLLll?_o zo26&gek45^C&Bv(Cho%SWIR;wHPGAvFO!AbgR8^vYH#e&n=F9y1#~3~S%}k(|EYcf X^-c*8RSXnW00000NkvXXu0mjfNEmRl diff --git a/micropsi_server/static/minecraft/block_textures/Beacon_Block.png b/micropsi_server/static/minecraft/block_textures/Beacon_Block.png deleted file mode 100644 index 70b4d58200c11cc0da8f3d1cb586b5ff404b18c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1831 zcmV+?2iW+DP)P000>X1^@s6#OZ}&000K=NklwSzuw=W!+_eyT8}%9~dT$HnHc=d++(q z`OZD}e3!v}8qf3eo;`cEA3l8eSArl=BO@cDs;ZLv@ulwvytA|O36IBfx}c!I-qF$V zHIvDtYj1CF*u8sq8_)Bfiotvm_|Ba>HFGIF zw|;%iGu_=k7X$&H#EiPvqTB7x>Fw=(R>^X>Ug+uR4$cImGrc|k`sf*Fu{)=6>l1C( z47*ua3MpGQwQf9l^5kESA3y$zBuUymGv|1ou4-vrcl7v+Kj?gFZ}Z3Zg{(|#<)MS! zb^ZUmRdMKG_r}j|uCJ-7ak<@Y#~tQuXJ=5(hs!00gf+4U=A@7aHxs%UPi{8*XU@9z8l zw_dw6I9S!PsTBZN0H|^oeZVz06;h#_UZL{d+*UdeRIR(pa0FyLRVW_ zz7C5PBc_()Si>7(7Fa|8Z~z!Zfj<#P>UIp97vhMGjhQbGUv<9bdD9(N z{M8^kcIX6N_FoHq`%_84mLYdKqMQAWF3~~fLcu+7<6m%4Wq^3b81e&HLHBD0# zS(X-NXU?pa%j5C9Fc%DZ7v|@~0Fc4!K+`lPR2fc|gP;L+RtK*r!T}m-dIMqL;(~-5F;uUhzg&S z0u2fhfP_X6l9EVi8f1E96joJL?JaP*)~&6p|M6;&bLTxL8n*3d2Y@u4&hLDm%SWY5er-z>K-*GyM_W_V*=H@0ijw@I# zSL5a_hbFw+=FW{!wUcaMV8^_b&y#Bv#A6Wq9LIu$Y(eV zcrGF}JL@WVsJLrr=yI80Fc>L%MM>C|iv~S6{=pOgNJ#gcd^yS(Xz0BeA{z2 zyS*qBockatCMz{X5o9F=F|>eUDh)O1nHWuq2q{T~=OTy>TtjSR6w!qM;!9CzM1#?0 zjYp&57mLasZgQ2D{dDTa*dJn%$i%8>F$_aLTwAxRy0N)y=;FIC4ZnLagJHP+6-`a% zvIic3=Z&|JkW(mGUyWM>!w3ZY&@|``%E$DQDK{-uib4PBFCqo@4i-jkZ9DY%z0 z3`6HS^IVpU48x`!JCEjl=Aqx2O!lb7ltuDgWn}A@ze*l?w1o(Qf$+RO#`C%(0A+94 zy4_>X&26A*`Yz4COXCS4Bp!`M6iHHz7K>pycKe-*hI%PDGkuBS*b?`;YGLTY+i_J> z{#;mAdfD#CrRD?Er+wa=e_4*je1wp@bpC&SBnTlSIPD*~9f|nOHk+w++xA`Qx!Hpb zhoe-HlCQ3Dx!l3&=~ukhhMyyZX!ipDzl~uSy7JM+_U+$(vM=9V(m~TS`{`aQ_8(u# V6OrwAx5xkh002ovPDHLkV1lV!UhDt> diff --git a/micropsi_server/static/minecraft/block_textures/Bed.png b/micropsi_server/static/minecraft/block_textures/Bed.png deleted file mode 100644 index 289e9f77745caf3223fb8956620d8716c995e7ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1096 zcmV-O1h@N%P)P000vR1^@s6Wpbtk000CJNkl!ZX*A!l*u80# zR60tr{>2w~cHbC->(*kdMQel6nohUN)aA>{_x-OkIeB!)%eyYz5;;_^H9+q#=CfnD zT%lHNHYqjg%ui1rvx<+mEOMk7}#<3}N$w~Vxc=GwJHK|pJ%MK_2@GYGZm2YjK4GScHJoT9Q;@ zgvB+GcU|V=hzm;5t2fxNc{5Tvv|F<*OkX;}FpBVlfPNGr ztiX|Ati@Q+%A(O&Ymlj-tu+^efVfZ~m0%HgOY`{4Go)dM(2sD2UFWmdu+dQ|IRu4b zo?Z~q4}F9c7zs*Q60K22qp?_(&`TBPtYH!e1!ydZV~|xzw>=m1JDra!^-?wsQ{gHr z2ytI=tOTi{qZN}%vm^vkfYAn(5jf2}WSk&O(oaol zkDa~RpWkv{?JFt8&#pUC!=LI=&gSqjOr(lx6tE@^sbsQ9EGAWSb;^{11tE|^ptQzj zByx2gvN8%|^SM^~*u-pq=Eg&ET)X?XOKs!UHa2OMUBaaZI~U^$LB~qE#!`)9^1?8s z49h~`WL%^&BvK=rMMU0-;zWI!M(XJKS^vuazrs(yTr~hL0y=&&?&h5<`H|{#%8R9% z4RDR5n~_Y$hBT8wSO~3xW)5=sB#hMw0&{eHu0MIZi{U)6vH4)X8)i*no=*~eDsaX6 z?7H#;GK)xiF|jcO!e9q;*m_O{MxTfhz2p3>|HWcIY2OZje{a{8hX-q?#+N*Rryg2G z*)f~kynCcxDQ=pb@rb1`QIvjTjXiwkYJcL^l`9}O%h>2+8@%P<()6`H?RL2Q$KT%I zotEE-TybJi5@9k@;7FnF765Qo8vOd_wa&=!z>i_97O=cAy%==w7WoJ1nJ~*UU|&W6 O0000IXL diff --git a/micropsi_server/static/minecraft/block_textures/Bedrock.png b/micropsi_server/static/minecraft/block_textures/Bedrock.png deleted file mode 100644 index ed26577743538e7c8f0f164fd6ebc7bf37d009d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1608 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 diff --git a/micropsi_server/static/minecraft/block_textures/Birch_Fence_Gate_(Closed).png b/micropsi_server/static/minecraft/block_textures/Birch_Fence_Gate_(Closed).png deleted file mode 100644 index e69dfa0cc43605a17b7b76f86ec45b98c9b3514a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1264 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 O0000!`~B diff --git a/micropsi_server/static/minecraft/block_textures/Block_of_Quartz.png b/micropsi_server/static/minecraft/block_textures/Block_of_Quartz.png deleted file mode 100644 index 9c986e57c84fee4c8ec1d4438e1ec84f26d7f2eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1108 zcmV-a1grarP)P000>X1^@s6#OZ}&000CVNklIVxsuC$@cw(_{r$aJfIvzgIzT9wEBP;POifNt&&<^8_2^iT#^5JT3}i(F z!VCc*%pf9=QXm3^1%LnmHp-R!jj69EZ+?4grd~gY{uL4gZD!YK^wo41n62mppJy!qB=6Q6u>`sAss z_{@~jP{sfv;3$H%5kv%}6trmrDb*~22p|H1fXK=C(Z}N-{`k|K+Xev6oj*77~f<4Qd)0+8DjFn0Gs z?CkELtK)eL4xU0!CIe&I0XPXQM_655!`#mgP%3TsDg{u=Wy{TCsk5@JiC0`JkQTCN&A(!D%H2Sl3#!OeR_Lq zGw)`NYc44Q6oGW?&GgcxjTZM#gR|ILFk95B)%@z(`gE!k0Ijtddi9murHjMky}i9z z(-t6|14s^UY;0h2>sj(!LjGZEVQqxk{(gR8VR3T#&%z8dM@^7M*lR-$mg&x9mDV7o zAZ)?RD3>d!Y;Gr##DH40nx9|DHw90WI}B;A?B(30ix)i1Ktxb3mr>c=PDarFU;Fum zh5Y2><<{UwLRw+N$te{obPb>v^6 ac>M#WD{{Ya%XjDi0000P000>X1^@s6#OZ}&000CgNklVk8LiIfq-+7SdGFa&%i2c5Nlo`FJSQkc?BeP2=-ATZ<05Nh(-`4 zNC;tUF2=_8+)j5@7JbIInBbOLI=BA6{;R6%2xlCBUY}Rsmbu?mk^8{o@BZGmv#gb~ zicic}cg*`EU{Qn^<6z$NJv{^LlaQJC*nRbF_WpWp3^QD;l=DIPkTVxmSW+4Eg53K* zXa9lB#Ha47+vfev+S#a$Hxpw%nFa{Kuj}QbdRl>ElvJz#`lH<-4VD0P9%`25W=~Ut2 zL`vKfFwXF?hT4iyC6Ix!gL}EIt$^DK?`!9;sr9$bAG-UK$19ziP~P*$#BX0nx&LkD z%!|-d=Uf%JQaPK`%)U9vm39zb3^P$c1-b#gjKX3R+CiAF0&|}Eker`Kx&N}Z+rV0! zh3iG_yv)YcAY8AMsLm@hf`S>0v$36^9l#t!U{0Zm(2j6Ez|J)D`#AHwHWJWnl5qf; zxTD#5mSDXWu0~;{5~>JaL}A-tl%3ka-RVW;LKRpNV`A{jZsy6@2m(`gS|W7A2}tdb z9HPK_ZLHVubRt}e!uOrP)plT`PE5=ho3PRnMrQoBmw7fdwvtghv{YybM3IuX;}B;n zIm@lcc1ozgyX3r0!qDMbG`?vm-AZ_qoV7jUpQ$m*#=gPON~x+j-Jr;k!*Kvn5t2FY zrkPyZ?WmX9&JIJM zqcAZ?s}j~3AgVH|6SW`iIyX7fBUTO|0z`$B4;MJ4vPR}CN5zi_4b2H=%r=}F4G|dm z@m8WouJPtWZ|3Y-TZ912g#gVV9A#%W8`I`KG;?gFx>Qa=9CnklTOWe731Os!ss@HR zAJ`Uwqa7+Y6H0f8N5Z85J5AP>DrifAECi^VFqBr}2O-dA&Rn+@T2h_`DUNEh-%vPAvVYt57L)W3iasNnT2O zDat_$=>=gS2D%DeVF2>z0P^F;cuj;G9hE!t!k~*YA4->9h|r6I0plj|!(0S=QCN&h zXxuIZp^FdZMDB>ljR)KH+AO5U#!r{$6u6b$SGThF9mp!|C1W87DZ}%rF?28PQ~{?2 zgV6k}9&FdA{~138Gxyc0NPj0AEnqjptL&J2X@tl?gj2;o3povzp#MHO>)BD~v*Krl loP@njc6T%R{j-SA#=lx&F|G!`{Cxlb002ovPDHLkV1hQ28M6QY diff --git a/micropsi_server/static/minecraft/block_textures/Bookshelf.png b/micropsi_server/static/minecraft/block_textures/Bookshelf.png deleted file mode 100644 index b8b0bc1f255d7267e34a6337e27b9501bf4f1841..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1631 zcmV-l2B7(gP)P000>X1^@s6#OZ}&000IiNkl zO>A6O6~}+~zIWey^X)g|vB!4Y+Od*rT$S4L5|cGwFTg)vbyp;WM$naX2Z z3QMWFPSdO1Ah`CucmDXLurEM<_0GDqlw2Q%;T5!8+iCW(Et5*gA?ql{QH(cCX!m?- zl{{gRU|Rk5Upt8pN^Ylg)kRt0i8V zn<7dQ!Z^b7V|H5u&Ma1OY>BN*Ov9`z)BG=(0r2DRe&gFh$lvF)*7tk(Y~!x3+gUz%AJ%Y0T1m8MoxH*YeohZ81~Mk;^Df)r!Q?gvM?cAtYZpGskOZ z4FrOAZ$z`}k?NF(+r71&R{s)k-F%j=zx;iJxp|kRd6!mu%<5uFv7nfp&QYFnxZ7xv zbxcmpl!)R9uP)D$Bq{aA0aFEss+(gJ4`}QRxUy0bWXn{yFt8z9l_h8{tpS zEL{_pMlo6cQ{t%vQ99&7UoacN7+__YZYw4j#T1rTX(bUlG@1A&T0_T#8AFl!7B$Dl zbOg5b86}34{O#5bI|l<6D{$IY6el^XjLmGMks4-HmWwmX=oE~tE#$!g0Llp|xjtqk zN4QwTFf;V^G@HA_KncyWn#6r3zjqWUuHm8F_5bBOOP)kyZ5L z2vi0~2NaVbCCcc$rr8eY27v1z3OV{-cmi3TcQJP!QJBP>89CG+cllGZ$6U3*%L^sa zfyF|Q;Aavn%OuJu9AmJNJ;YnJn?(~N=v+szT9N`{<(gLc5yRtHSMR5e4fd=B}fLGM|fo>r>_=m#NM zoOFGie+g|DId#6q#jh>X_BDUrouk={m?`Jzdt>f3+LSwEY7>nV0;gmF6CgPXtTn?h zIf3l91|Rt4nYx^E(S`&maViE$=pe1H^J*l~TF@JeNp*@BL==pag3xG{N9PKRdwtUA zpP*xMX@|M)VZA#TesB!=@UI)!k;VnfEnjXdm+NT>oNR)>|A@)>Bji)roij`L!G zQxwV3Fp>(V2x%k;M_EQCRkz~9%aZiMt$X{|k0DP%=^t*~e?O7pLN+(PTpFr+&dHO; zO(1<*t~P~?gJm4MAxW_kgN#+EhojtOWvL6BJKgv1Y_^W4qrvB_@ua6Wb~>M|EV{px z!l{pj`SW3FR6DAI;YO_JkZ!Y0G|{A)3~|w@E6EQ*{OIHR%{%*TUw=O4bGMZ@-&mCc zZ@iug^GZ59wRYBo`}ZFcOI4p}uBK^v?X$Q3GJ7uMnSJZ^B`JhhAIHht!7zRgxOVH_ d{tNA0{{bv^=NFR>^^gDn002ovPDHLkV1n!hCSU*n diff --git a/micropsi_server/static/minecraft/block_textures/Brewing_Stand.png b/micropsi_server/static/minecraft/block_textures/Brewing_Stand.png deleted file mode 100644 index 83421d88ee8260c2d2fd682836d0bd80614b30b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1092 zcmV-K1iSl*P)P000>X1^@s6#OZ}&000CFNklluCkY-z4?QU2sjW#c*fb%5?5b@vVb;9eeQ)#r&Ui?pjc#9J#P4bN&dl%g`~7Bq zz!61vWgY75Q6T{G08ankkVET0NM~nfOP>%@QcC@INM~o~IRML#dS8F}CjI8CH!ild z8CMTQI0ynD4Iq8?>{)vL{Q2VZ{e2ggRv!H5JKHxum>zg`Xy9}f0E)%pjiey@L0J$& zo(AAbDR~e^`1CUyPv+|Q`GEtIXWuH7N_RImH;0Z4aa|XL5DI`-t5pC1=LWX76+F*F zd%JqsFpNutka|*_#C8=lg{L(6n^LMPm&@6@uA~3VF+A6+qi~YJ^E}wL4bSru<0Nht z+f1Bl<3YJxUgw-kN-1LB!xP5|h>F2EM<$bbDI>$gN)p?ZO>q#iyEY61gb)zQKs7+g zCJe&>=iEM-HiEUILYScg4@ZFz0zwFMZh#swsIGy=6oz5oM2aVcNrnVbguf4*lpbUZ zfc)i~j3q)6-y{g5jWCL@@4G-500r`}kKNtfufcN!Z_wjg7SnA>MNw zhZXV?j1g@7>LXSuq|@mE;_qZ$eC5?}aB%R4Teofn|EiLSi3tF}#>U1CDdjPx6rcbV zDJUs-v(z-?&-Cj;h^u$*+_~N=PRlY!M@QQ`Iy%Osl^I#YM~aeMl)mC;?># z03eQID5WqrHwW9c9~*CU{P9T-y2%7g` z8owiur$F@rT-Qf7n+0RcVT^r5DgAI}W=1>;`Sal|%NnaztLJuhcEA{eQVNt(aL!>_ z7F^fG{QLqm{W!Y1hY`yRwzdQsjRxxVIvmGg0EVQLZ};~0wigP8@7LDW{C({Tg5VcI z2z=j1qtQSnlYwcPh@uF$Z{J3}UWcyhXgEG9KWzX2q?Dj^cOaEg5JI3{uOF*ctM9w6 zdlSI7``QHn!^6WRrPP$?d6#ruN3+?4>$(5{(=-R)0(DzVObW!FuV}Q@sw$r zLwl8kVF=r{_XhM{6h+ff6wQ{)W%*dhp8ERwo~hMpm6es1eg43}066CtbX}iv9A`)f z0Yb<<#@KWeMYGGx%d+L~I*LX{Mo_EOE>x@493f;B~c diff --git a/micropsi_server/static/minecraft/block_textures/Brick_(Block).png b/micropsi_server/static/minecraft/block_textures/Brick_(Block).png deleted file mode 100644 index fcce16a7dae796407f1d8db461bc1f069922ccbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1626 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 diff --git a/micropsi_server/static/minecraft/block_textures/BrownMushroomCap.png b/micropsi_server/static/minecraft/block_textures/BrownMushroomCap.png deleted file mode 100644 index 382be037a38ab306d0ec2fb0958b8ba01fd1128b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1328 zcmV-01<(44P)P000>X1^@s6#OZ}&000E{NklDCz+98R$JS>OJh-&%VKA2WXR zt*>cH`8dXSb2J*gHKcU*#+$#fkMpg3)P~d7zoK-x0oyR+~9 z?AQMj_Fo}C{`S|ks_WzBa(SYvM{V1(TyI!+1EPYJLfh1gn`+i|-D%UbXK(!cw=aY} z2l>J4uPAWbG|fo}dQ?hb-3>H#P0E=oZB5}@TaQHc&mJOxyVDPu5R@2{E*0)P zIA=0%xG^0w88ut8vtz|5rSN36;r6{pJUqYHCv|taAu(zjQc6!DxfJRU5D^YXEh<70 z!OWPiHZ0a1ZrXSMQu-%8* zoW*8fzV1;`*qQ0(2#%TW4K_1|R4_9@P}OHV$r%lip%g+;Lf9^*oLO}}Z561ih?(KL zimECl=Y;NV1$U>4ks)QwY!BE&LI~tiXkz5X;e>V9frIY6Ih>AZ>x#u{LovgwAR_cb zrk&K}Qiw6`Kw*F7Y!&Swmr+Hm^rJiCujh+s7F@ifH+bW7d5rmX7DP@`}fb;AKn5pX9d&n=}`SYz@%F811YK(DKR~6T$W2U2K zFFy?fc9~#js+m#DsH#YefntV;%vAMNhnN5G?t8c31(WfcU;3QpoR6)Plcs5oN_lFo zsPcHRV!rNib3#yZF$70dXXbtyLOA>5UvIy7GJZI|@%hiF!?CKK;C=)UtBOY#3+AhC ze<7OLEa!ZxqGx}a-T8Pk{=@Ow7eA|Rb}XtVB64)GTywGNF8}#yF6FtC|KE^jVIp!o mUv)R1EH`hN+eaq9jDG{dJ?*wg-rlbO0000P000>X1^@s6#OZ}&0003FNklP000>X1^@s6#OZ}&000EeNkl}F!9XrPkl=wpe99>|zJj2uUm`v@ zFyH`+ULa=$P7uNC1dsqlpkvXZD2b*-F1a()U6n&G$#rDOYG5$CGt>3epI=o6UVZ6c zLE8>@9;#N&qDDVXJ-w)CG0__*jW_Qas zyA&6DOMYw{>3;LG_+|ZPfMUWpZ;#sL;>Q)%{+zmudYTaO(teOJ57WEfzXILL4m=+mG08#2&VY zK>1dMRDm$pL3%>kYmr{S9!JvGE#5lXRZyTdq|lqTA2+AdMVK$oD&#+7|(&P#i~JFOiPo<{&~? zDCu14lODA+{~F<^Dix$3uxAn6Pkr>UEduXNLw4iCrjN*}uB9v3al-beHaCVp&3VVc z-I1>3t0Ng}j*pGFUH5_AX~?j=P;F^X^G6f!b+*X8Ac6!XJ#K049ATrW#twp%f^-8S z;K@NDEO)km;WQ-QCy|iyPw5EKRZbdM=L*7diIgBB$Vo2?kR}NNxiN=Qctvn2%02Hmsw$A*m(B4?v8?KCUE<}~DEx^mZ3ZZ@fu^rX$e zv4Q32(l-%1j9{Lh*(CZ>kiO!QEqd*^a~Jq{8uEVpZ++S0nPTi&gy={=qM^Ltm{lM)GDBZiHiZkY(93q zc2LPuKbrh^AG-TG#+CXXhu51g&N}vmXMB07uN177Z&z0lSwYFC5%Yzg5p_r^|C0Ei;ef>p%mqzmc xYq0vd+BmJI`|a=D{f~Ms+^GL{@Iu4W>wiFQXRJadpgRBn002ovPDHLkV1g@eZF>L! diff --git a/micropsi_server/static/minecraft/block_textures/Cake.png b/micropsi_server/static/minecraft/block_textures/Cake.png deleted file mode 100644 index 1532a8f96262240985725895eec2e0ebeb3cac49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1280 zcmV+b1^@bqP)P000#T1^@s6vnxdy000EXNklrP>F=(ne%)1?#<9UjG6i>l(iC(l!ib)SUIAK5r}43lcLYD%TjRb$Mov+gD53gkW~c#r$w z1MK1=`L%1PDsFcV`TO7Ch0A0E1LQuZ=lXRHCq6}V`HLW6mY4Cj7sy|EDZ|pPQQ-b&W>j+1=e;#>U2&nEn#wkx`tn7-Ip5qKN#? zzX&HzV1DpJl5c$z`umafb^OQ(?sLy!P(;9pMSFXBaB#r;fBXXr3k&S+?LF&M6=xmI zW|Q^xbw)==nV6WQTrN{^1?1g#aXWN?(5;Tbo<7S}lf$hnbj|puc~Bjg1zyT8*WpB}5D&h9rqmRYZh?d$$OO zJEW~;(zt_DK+;335!i3jzVkkyXr96Omlzm40YH1J&FbnZJ3Bix8Y@ImbaZkhPg8c6 z?;zFzitp^-vdg8lPRJ*Tqr#u2@&auDUW?<+f)>_)_HpX;4?;PwA zmHLR=Yh=xj5D`ErSc5890Z<&UevoAJMvj6t6s!U5#&>x8&t$usBp?5i{L>d1{LDoF zLQwYaFOjS*kqw_A+iYM>x0AXH(%j<}K-TRSMoWJRN-fF5Wah)TGv+8nfsm88@1wz^ z$pM@^LnltyX>Sp)E@FI&-*00Dj1i1LP$kX+KIlzVzK!zphqG(u!jMtriU_ktWV$D^ zl$^z@Vny&liE(U4r`TFs!QziB?p{3013oB0bsp7q%pZ$CcGMr&H!ci;^0ElCR^)2Ib(>># zJE0T)>w8dcsA>gxZ+_+Qky=mu8D}mGq41)>tiWsSj%OzcfWCuDMTOdY}VnY0000P000>X1^@s6#OZ}&000BZNklZnSDz9+~N>3#C7+G>B;^mmfx{>V9JJGi&8O)U!I%r7j?*+16$?zPr; zZ~n&L_{&h9HB+AT%5N_2RkPQOy{7&QN0F5P@0VxY1Ffx&m1ot`Qy$39n!?v{wdWh| zNj55#?x*vA@(Vg%b<6=Z4B4UK1sgD0T;=+4?>mnD#W7RGvF=3c<^z5#s9!g3T=VN4 zJP2OGbfLIi%r2VCH=Br8JnE9lx))VKZZgTlyl7wc6SkP>f6GL1yO17q+#`fO&Dc^8 zC6aYw>1CDdj7bZs?s6^p!-e#qPxH;H_%NO{5Ec!r88cbPREwz-^N)MZ7895*+-VX& zqz8TKGhf7q#Qc(H3pI*&7{FwHMm3(tqDdTfz}q*JtMnMf5*Ao)lQz|pcg46&T<-2k zzc+2#G{%*?bIBRPIhsvG+q~nD9V?kAfPaEHZR7E(fnpbDAh%hrUKk||n@i{Ib4t~?2^_{}zH~gSQq0jXsNTPo&T;Z98g>~k&w3NntU(L@ph7cbC|;(N9?gJ{l<~BfqCNpyqE8>?Up3i z?KZnx74pLBLegoF{e;Vevi-@sz31pkH@jQm29o?$pM+7LbQZ~5=*d>u#$=y*ewugz zi{J^-!yxV1EpSugJ2SnXrC&+H20H~ioy66i-F=FL(I-Sa5p0tVHLn$prO;d(b}cO~p9 zJo@BdJp@M$2217W%Su~5l8Ga^dp{cabz?N%7`2k(~oc93Q(kS`NAM?dW`CjWxrW%5diC2WYlatxoFOuLpLmJvFkSv%clQT4LjE znZ?1D|6J^I`fSN}E2_D<@w>`G{h4}jbt(1ZS^oWPMRh1u9Zo&JolEnn%L^?Zd^Nap xwB4uH;;AZ|#xkeNZS5b{D4r<$&iqQyUjT;fb9tFfpO^pu002ovPDHLkV1hGT2v-0A diff --git a/micropsi_server/static/minecraft/block_textures/Cauldron.png b/micropsi_server/static/minecraft/block_textures/Cauldron.png deleted file mode 100644 index e9dc79ccd818442adce6ec6cac07765c0c118023..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 962 zcmV;z13mnSP)P000>X1^@s6#OZ}&000AtNklg5=6g~GnJF(L=P8v2XOYM{sjy>gv18^z=&1U zkw&yqKcq$~QEVr+ff$+tu_PLc#klE4@PBB}rYKuTFrMMUsC z4@B?!w9U6#tqSbo;^M_@Hv5$UKoA69W?41{fNr;oD2m{DUJU?1wYD^At)Y~H)*3{F zR;z_D4578g{r&xiN~zBQz_VTCd5*Fy5r!f9{XPbR0p{~L(lmvXvH~#1)YiW5S7y*! zDyL!PO|^}j%b5I0iFNyFlAl~R9fjkHa?*XN7j kaQI6Q1Yet<807*qoM6N<$f_22eX#fBK diff --git a/micropsi_server/static/minecraft/block_textures/Chest.png b/micropsi_server/static/minecraft/block_textures/Chest.png deleted file mode 100644 index 84461a767988f20eb829f7d4ce91c8e016152f05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1363 zcmV-Z1+4msP)P000>X1^@s6#OZ}&000FVNkly5GSBW35a0|Kr#iAzXt^cAAlNJDJW@ZDQV^#Ok;|X zXh#|hNhVS(oQGdy`}+Dm&bjC8v-hH~GKqme23Eb+|F@o6mTzDmJ=oY>>dpP;#%llK z_}Cj@!;`Cp&C6ZUT*A3eCr{O+pVy1yFe6+)f~3N#_mNfSa- zGpPf|Q^V5}!@-5wi7F32-~CtEHOR-W+tQb<@2x?ec@_#sE{4!}Op+kZP* zcyZ>qcSG1%ab&I`w@Qadtq?+>(nu#sFvd}pEn{!lJ+wT2)i5q=QarsZG8#t!-0Qw| zZOaEMHN#oMSL2*7_8m_SgnR25cUMy;QZbh(1qwF7tK;}0@a!wYlUEIwlbX!7eAo>X zHZYp$*R%c_GWL;9BCHgVem>)DYB-)G{N=gw<)PuuO3wXtgXB<_@aQ1$_^@R(tx3dl zvj>HXgf`Mf!AC&}Zy-^X{WD=YZ&_OatC4Y8vp%nMJHr0C@aUE0*@N zn!SsJC(m0F3G_P+og|Wo-~$94=aax_CL~svFD$jcHWQ_ndisT-Y7^?{c>Hva)6!wa zftyC?Ca}KXd3hT6a7ihg64l_MG@8(4Boc@)NQ1VFsX%6rC1{@6wpZ^wUN{bKFDuc z2pFMk6AlKRHbjCk^t+HdNK7Dg;M|pPR1g)Mxk~8T3KNwKFfPIS5q-U+*HfGrd>hGp zWHu*UObv^L(oG_X10`UsrD{EfD{&19Z_eaRB_?r*6WXRFYNXac1ZtpTEsI&gpuF0a zJb_Mbh#CPng93qNJi25u(Ddg42=G%av{_ zhzg<*fkuT8EoChn3?mnlu%l6bUweOI0VdPxWIV0@m}bSFPA>JAkT%bN<@%X4&xA{%t{sE9q V$s*>d$?pIF002ovPDHLkV1jeBfF}R| diff --git a/micropsi_server/static/minecraft/block_textures/Clay_Block.png b/micropsi_server/static/minecraft/block_textures/Clay_Block.png deleted file mode 100644 index 805daea3caf39e36534490c1b3621e6972c569c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1312 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 iG;RCMn>Rmncl{R*gD~QU{WR(T00002{j diff --git a/micropsi_server/static/minecraft/block_textures/Cobblestone_Stairs.png b/micropsi_server/static/minecraft/block_textures/Cobblestone_Stairs.png deleted file mode 100644 index 558d0da744732fe538bb9185a355f19f5de8a3f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1476 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 diff --git a/micropsi_server/static/minecraft/block_textures/Command_Block.png b/micropsi_server/static/minecraft/block_textures/Command_Block.png deleted file mode 100644 index 83d31589b26d8364acde58b4f49c8c35ac10c65f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1593 zcmV-92FCe`P)P000>X1^@s6#OZ}&000I6NklmU8-y(>S{N+wT^hxao}_H}B-%O*HV>i6H=+`4{gaeJv&t!u4Gl7v=&()i1MZ|66k z{q0to+2ntMI7;dd-dNqb@y^BVYAIj8-!;5(?OTLVL_U|pbv^cEqr$sOx;F$5; zyRR*8y;on^8il5N&>7#i|7PRaP8=om2X9{3di&Lt?X}gldS)StLX?}My!IONm#;D! zPnb0BQL5J|mrHb??9zR-OKKuKFNf#4oD3(8U;pX$&Zl=CpV=q?-nlscy_@g9{_|QX zSAWzVF}wLq^5rRXZ4FgjVtmvB9V62b#@0xVnuwVxl9>W?b; zAXYHy4bbyr78d4tWqAe1 zacLYr<(0BvW3Ixi7?bzA?A?3DLH8R_oPh^lu$;F{=VIveh*Hb#{Wgskeaf>7cwP=? zVTnOt&LOpGh3V-Tl+svhDOP8B(LH3~8yqDl<~@)O3u&9U+r)D;GIDA62mJA``=n`x z1*DfpYlkQbkw6$l=MaySc+kdiT!dFdD#b8W_+h{>N|;|>qo84B(q?t4#ItsX-|ij| z=@Lel@Ph%?W)vzOiBRBZCcPHrLhc-bwUo4_*EwPJ+Up1*5Kf+_5AU+FzR6Uziiu-B zfA|Pb!u_K@vC1)1%Hg^Wf{a)=6z5k-5d_^9k_?&t)0uqPC*Y3>0v}@~`Nd5x&#e)4 zUf}y4uOrf+!1QFO6SuRm&7uv@7Tjx;Rtw$b1gSNKSg#N*SvZ zN-3r*HAX=U^pOC|wP}=;D5c2K4AVDcse{Ppnch1jGYMoV%he)zX;D&;*AgKFqyc0G zS*>DxADgBCWQk!knE;$ZL=r(TAPhq+7EvyPwWRG1;l>5hETcaflf(hojLcemlYx+A zsU{6GvdI`xtrEmB0?TkPpjyhGLjX#SVBGHzwVz{*PbegEd5N^!1#78J&(I4JEa1lp z(#s*`m*tLwuQl!FAx=EP%_8!yf+YEuJPFoorR!;8=9JCoA3cL`OlL48DOQk5K{TdP zD$wb6=mse>wRwb_#~_G<31ZYDmrO8mgsUXh#Erey;HRU|JU+`4C567ST;003G_!qa zdA6Pt89@|s7)YorQFbg#l^l2O-N#4|$MvvYk#Y9~6^|+C)1{h&pz*Xb-f8yzTbVu0 zle1*Bmh5;K-q~yRe;LK*-eTFUmvio1PNrxnC}>GI88Qh%`hy{vwa7Fg_J>S)4ndqW z?jH7T-aYKz90ukK%XvEf*Nj(E=(pBuTi?07xIHws?sz$bP07-XlkR|FXs}9?b5!HO z^Y+eudvq(ad}YSJ^tYm<(3|s>t#4mh-(Ig4>h1oJ<6*!cFpay1-JRoMcxKE0EhUA% rzA<;>2iGoqvN>12A;tNV^fLVe1w#kjc&IWs00000NkvXXu0mjf(A)aP diff --git a/micropsi_server/static/minecraft/block_textures/Crafting_Table.png b/micropsi_server/static/minecraft/block_textures/Crafting_Table.png deleted file mode 100644 index 1e6c61c26f08fa3ba4d59c54b4ee9c2a17dfb455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1538 zcmV+d2L1VoP)P000>X1^@s6#OZ}&000HaNklcWI@0aV2D@AG5t5v${~?M76}-Zp80DYquT^k{i=BzX=pag_y2wtRJhE zuK)1ziRK$8tMo<*t@eO9+v4@(RW=?CNR-Bfm5{}}81Q zSjKv#>|X!TmDT3g)~fWPlz(@|)B>BedWp}r`y^>W$+x(&I?u!XnEi3ex#c;QDn7R! z^tpL!t2LU;Zsdi00n-3%%lJ;U?Edn-cbd&C7nF;F-Y}tN>zrH&xZNJ2lt#Vm(L7$E z_4t5MlA(}%{nR3z(TwgWW#Y!8Uy z8DBXTvRw7(PX)V^oG@^>a6F_pO6eS=k_ zE}pD%Wv#}bAM?k*Z=q`jtH&yYbMAl7;rSq)$B;hHrEG zVV{3KI-pUqdF@o4D9I@MHh;Ol&+afGNpggc#96^|J)kHggJ_Cr=!YS-sz;h+jN%O2 zh0M0-WrE`?As1E`=?!8&y|c^ea>(EA?clj4<%&;cr1ba3=%z`WWQ>)hQ47e0cma8V z^K6@Lf5M5SGAj!qSs}pCSzE3%%W}fN=JhiTKK|={%7IJCwMnNlf|3ooPF55+CWL{5 z(iDbn99Gin$LP9_she1)j_+CkwC;BpMhO>AEm86ulu}rRK_LYDqm+8Zz%q3VL&voY ze9t8-1jA^0SjqB28Gv?o#5l=N3T(^ZWX)%i%=pK~Hd{{y0Q3)HqH#*S;$dh{0a8j1 z(t?LihNQECYS}xiB**0JrMFNRw6|_^FrE_5IYhI9(p*S1Iv}2A0JyG6mKBIXv9s4l zC@6#^aBcjmjcMw{)7fDq2ZJH;phFM@gn>&KxCo&br-BcD{8Qfk_V+=1I^EE9q=Z8A z@mw3Fz;|qX-y)f2h%6`1jenAN2!D~*s|KPH|Q%w~edQVG*Eu`T_XYJ+q- zrs8W%$6c~qU>Z7=IR^!px`BA9jF|v=AwJsN>Azho+0AG=r5=`XT?@m|m=y&ZcQ;wi zYiQ4Y3QdD788c2YT*oBv9HdfMhKW*&ah$O~id#a;kKkGPl~O&}=|&$WS#d{5)iiW{ zVRtaWcWlm`sA21pxp0Ba2M-w>Ot38j*EXq@Jv`TD7)_B9T*u@|Z`^wDczCU|H@PMi zo3Gdv~;p7DD2BHq%L$FmM2|p26-+SP14pR znce7*lAB7sS{c8rk&vUw>|f&~`!LJJ9n;X8re-XZEXjD1u(KPnQ1uZ~5~mrxVbbdK zC)c8Qb}dQs&pBcT^GfKNW-ir(t8ZRf+4#Y?&Z#-yR#z@At4n7WH|n8xReQ;e|2s4d obD`>A^B$2gXUz!m?5n{Qv*}07*qoM6N<$f`^p{$p8QV diff --git a/micropsi_server/static/minecraft/block_textures/Crops.png b/micropsi_server/static/minecraft/block_textures/Crops.png deleted file mode 100644 index c422915670de74cdf4c7bc49974f11958e39a38b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1461 zcmV;m1xosfP)P000>X1^@s6#OZ}&000GiNklvL^%0#qD`cd6oUG=^1fA3zSPThlorE`1__}$(|ynCY{$Bxp04zKxl1|gnX?VsqSN>1H3uC-{FQ5UUNQffg-9H_^ zy|Tjk{rlBRAeWwhy3O-Xw;#yRs9PE}%U}O>yx#5d_I)H;$(B~Kd2TUST3HU4Prz4i z%(uULeSU2))V%zo{?^h(LAT5kpZ^?d(@uW+YWr%V>Q)0Sn?>n~LRRi0dF`!(&DY*K zxc24G*0#U;RQ(hbVOc3QL|6r=T`V|qDd%IElI2cY(YAY$d zJyx%NCD>5#W~XHbDEPzQvzy&6&w}8aV3stmT%X@S@!sn{+y9Aw$V|I=^GvJKed+6+ z4dB=7zuMo_&pk8WeeRk0C4gYoQbulBDr&9W30FZa*DA7A7~hRT*1Uj^6uzZUTGmSA zzt-*2m6omOGZui~|B>65KGnV*1hSSD_KS~PpsP{bLGdC$mPO=Ygn2aL?8CM7!szvH z|8Q{0DtYJJLjgq=VeLqeQ6wAtsLhgoSaOtP7u^DNAHcKi7a;Euj{-g7w2o+V@> zE$nhwb>x{NTFT*Evi#(y*+>)r9&jy|XQg>bN2WoLU-h+8)nzB5eK zO@V7kW;PdRLCwFhjb6(my^f)W%riG?y18Zq2+rN3oZJJW!tU{d7SS+cwErEtC z2}lOfSS<3Ch+4h%#^Gj?+rKsI5LPb7tmGc66pe~teZbtqH9GSd&8kB^5g`ct+0vOa z_HB+@;D@@K(#T^X0`69H-ttD!az{UW1Z%xSH%qVDM?xijq@k{)Diw?t3_ET=9d+N zzGsklOyZb1Q!-QY?EZE_a@=bmINI}$yZ@}wi#_$4MJsh`V+u_=bllx4Czmk@J+>5* zBPiPeWdV8W@J>(xRGYzRMBafjg2_E1U`ruBcIXQMQDsEboUs(%iRYc?COJX6;gq`iFOq_$Vgfu;ICU^(p!5K(~6Ju*Q z#cU~<0?GtT38pgEpaD}rd^AO33|I@!!|sQQ!fIyELR1A2g`3Eoynr$Xmw_>0GjPY? zOTmwis}hYKDR(&8r_&fr22wIAmVlq7fqu7r3f8g9jsJEFDXJe8;5sizIB|?_V$4LGMv~~4;Nri{j P00000NkvXXu0mjfM`6A= diff --git a/micropsi_server/static/minecraft/block_textures/Dandelion.png b/micropsi_server/static/minecraft/block_textures/Dandelion.png deleted file mode 100644 index 2e4720a79d2c9733b0c3880275b6bcf4a3710dea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 475 zcmV<10VMv3P)P000>X1^@s6#OZ}&0004^Nkl+ew}cQ!pocIzBwmDKUh3~CLKMS8$6(;WZe5KC z+R#84n-w}nhow`>xutG<_I$iXhkYLd55ivO_YZh~KG2{+g9aB5WfBYyaW_1yMtoP{ z&5|tM3=z70hmoD1&=G*dXI4LbX~}xhl1$E$?2eLG3&`~j`rY`oNig$-=!-cu-*rQ^n7>t7;ix+bW5~o(^kr}e;ns^pi{_flQv}-5C1o<&q(Pu zLbW|Ig%}f31vw@!C*d3)Y`M|@%bC7Qx6+HkD-}+h;{O$%8>hJ@m-{Km RYl8p)002ovPDHLkV1mhv*xdjC diff --git a/micropsi_server/static/minecraft/block_textures/Dark_Oak_Door.png b/micropsi_server/static/minecraft/block_textures/Dark_Oak_Door.png deleted file mode 100644 index 050f4fbec7267c0316090749b5aa8c4035bce266..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 689 zcmV;i0#5yjP)P000>X1^@s6#OZ}&0007bNklkj*j^1w(=3- zqN>~3ST(i=6iJd<`NbcZf51T0LAI}7m_501arS&Zt?mr%9f~ltc;@uiw_7~_y46)78~ z6tstUPZH_oW2J(zCtL?6O-&HUW@xWW`lEw_`B?Q_RP5 zJed;jvn)Awqd7s9`}bO|UdyO}dsohlm52zcf*Z6c<2X$-;nnM!Z(kygKnRGmn?sI= zxf8n7IeedJb7!IoP*vd1YDRtH4yC_?pn|)T%`r2~y;Cz1!(CV#k_&ka=t_t?s)9&2 zAHxhnDlviz3jHN}_l#F20s2n!4@z<($GARCq|06bW2Nf1K{tZLl#XIb(aesSk#&y| zfimvJVaSiRynOp{v2*l2?gf=70*hr!E{@}v2Sh7II{mn1bMb@2Z}IWl!^O^rukBvS zCAxcGBEw6f6#F;GDpK)-Pv6tygQMln=SA9cCt5K|v2|zo?@_5-Jv*zPZZ-N)M*Q{* X5C2HNYf9O;00000NkvXXu0mjfFtkIq diff --git a/micropsi_server/static/minecraft/block_textures/Dark_Oak_Fence.png b/micropsi_server/static/minecraft/block_textures/Dark_Oak_Fence.png deleted file mode 100644 index a13611f787591e4d7d0deb63be5e27a2fc510722..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1028 zcmV+f1pE7mP)P000>X1^@s6#OZ}&000BaNklAP{F^htrb|E4Ng6m>& z<TUA}T*@P)HpRWrpBvSe2Yz%GNXOfiCdsv! z1>IoLD;ze)aWQ56VA{_;D`433qh3MpSZB~AK@|n#Dj|!4MUMDBHDsadMwedfeK_p< zk5n-K&rXdj3QGoA6qyy~SQY*^WY}}VrBUZbVd;8sG~BB)Hg+4ZD2y?f?|2w6L=-G~ zI>Z2Uem*QO&J}O}v9bTHDro6kxPE>i+;E15RT4ZLM~Jq;4g&7h2{8+TCnVP6G5@od zhhceX)LB)*yP}H1(`Inw$|etY$~@7#^-Wp8qPgF3ZV)VD7F0<9C=%=M>)Xw%PyCW| z{o=b|>52;k17N32i`)BchAaRXAh94Oc~B=_>lV)S3cAJG)&u}Vfy}4KRVT()sc=M4 zD0ZthdxQ31%(C8OS*w$L{nA2M9t3-<8!YO?sG}gWoB*7FkO5TNZ0p3xqEJ;N*7dUC zwJP#Ai*w=i*Dm&cbk?k-toZ9ug*q|1g@q{4ok%wmQbtC{D=ZO=X^Al_>cpQe^!)wH z7Y26>%*tM!vEMLutBl|Y!CUl$MH$=MC3=cngW$m`h*>6ePy|mH4g4o_9k-+cY?Tpv z4Z}NwZm^Jvk0_u(d+ScCi2+bJLU0B^22YwyRhJ%Y)yS;yj!-y)stOGL_Kq%!2$CAa zBv8fS4LmHv^nz8P3XoAaGEe|n6yqwxQk%IkK$H3IqrLc6lVq*yErPR1SzyVa@Pxvd z*^u2jqfU&N6$*ew5tD$70g*wS`0nGq_{PJX_+Ay|w}W$qv*;8S&JrvcID2&N9+9gb zEP^9ciBUG(W?xuTk$>4P2o5?aB#WZX z^22ce0PHvUdoq|$EH|B{#U?RgmY>RoS7$KnlER40>43@&0000P000>X1^@s6#OZ}&000BENkl?Ig4&=sEc^xWFE|tgo%Ku{1qT70=|2cK5gZAE`VX84 zP8>7{Hb|r0N>Y_n)qR|^_jYirZqiACcE}mtd+z$qUTfXsa7!ysc<;44{ZH?1<{y+n z9+g3#F5l5@08&qTZ@x76J@u3m2-7O!w?F6a&TIJr0Q_x0w%4+^Bw9YM1VIdCEil+% z`JQhoOau&je)P(2_xt@}_9HW3W{6_f_TFfcjb1W(dAoDi%iMt{#(5bro(G7o-v2_7 zwcL;PH?qTFKRIwrU=U8{0cS-(5QFC?Vo`%$0{$<^_F6i6b+>oe&HTZvia1}0&T9b- za7^$_h+?QkVTR`_bq(Sj4Wrl?0MyTkL`DiWo56|ZT-nr4dj@TdOI4dH`AXut80>pr*h9Fv&LBCM7Z+6zRpO<;S z0J`K?TVY-J7!^&B(eTGffzw%ls3B@2Py|I(gLeeS^wX@8Pr8{)nHsQThGT+Z#Uw2V z01*LbI-huks6nLp%rn6!1SW!+?cCq)e6>Bu-cvP9tJvlvtJ=l1_?h(L1VOZ|VlVSx zZk{d6lO^uK-g@?6T1K2qYs@ME5y3eEUusutR}~ahR76pPsF;-zbyt3G*FqH(-cfln3-{IV4->sFX06X^22o&A zM~o4Wc}B-O*dq9=7fTCqOz33^GOtLSLd;?(bzAqg@-NqNw^h^%H6V&1h=Bm43BfbN ztjRHoViD(R$kGSL1ciYJ{nz({LWG4U0X7?=p%)MIL~6)$Qz zo|f@O;wvCcF3pu-AfS9UsouEk`At{*$BWtDy6C%S0s(*B2H@Hnbo@K_HuG;cddWj3 cbZ<-dU#9<24OG;}2mk;807*qoM6N<$f<1}OssI20 diff --git a/micropsi_server/static/minecraft/block_textures/Dark_Oak_Wood_Stairs.png b/micropsi_server/static/minecraft/block_textures/Dark_Oak_Wood_Stairs.png deleted file mode 100644 index 0ebed97a7762e452e122f1c99d89173c9c07f201..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1169 zcmV;C1aA9@P)P000>X1^@s6#OZ}&000D7Nkl+U=TVJwZIw#|R8#>*eB$8X)KzY0V^PuJ%gjubV-S-G&4$b#?n~iKyxRk(2ULZ z{~Ip9zuLb8_Fj-bo~^dkon}T5W!*>CeZ<^p=9FserpD1C5mX2&jMX;({&D{n*n=P` z$kDT>tF0>RrpB_1h=b#FN$GPyMR0ds-xgw+o5)d75pKu2`TNV8zifB)Z}GvIh%gkR zn$cy&%<#jru^9Ww$Wb|2XHM60?|#F9o1vbo@EPzh2sm99qA8`#N7JW(JGeSggw~w@ zZU-Lqi9Rb1{4_T9Q+un@!%Pr@D3etdNdaN3MybZK3!p+XBdT&U6n?mF)aG~l9cbq7 z2kDZY?xso(N>Bx0nFBQnV=;!wSmbb5aH@uiVE*PBVo;#H3(`eBdv><^@?@31P!YDf z%In*SJ}D_G0xWW1D2?6J5P=+p+8k$AU7rFXP{#H)lT{bae*a|o<;g04@zYS2r5ZUY zM~lGma>>i9f&IRc;@n{lN*B31Mu4asSUZ9sa&IP2j`HWfJ6(LU?Ba4LmE&cmnRBx% zbScnh;WsBe4qWeNW{R-v1I>&sDWwI>98tl{8H-Vy{S4AY{qtCDIo64w5JO{`BXN}( zYvby6A_XDGz`9o+^$WJU!u77wCnW^I+?c9i4k0LAQmWkp5ulnOB3Of0+mYkt?2jCk z7!udJLT!~k1ytZ(`=2nMtU3l)C68Cm3jBV651 zlxD;*Cv&q|zCq>i4wQP2Ni|0inmdc6t8QAt5*KKtPHt}s@^-BA%romC{lT@w3& zQ9H~fFx!6*{7IB2B9KBr#1Ronn^gls2tqZ(4BEljB9IP^1QigWRA)cc&2DO6HRGT6 zqdu;?@O<6Ji**;zV}ztLgXT_abILl-P>dAh5F4|SRD`KEhGLuD*e*-+Z{gwZNnCZ| z`J+Bwtov}D!ht@Zs_e!_X+9TLL4dI|O0&&wvdgj9UGX>TeLoU$)rIFri+Hg};d~yZ zGgW6lwRzYMrTS(+wac--Tl|--xX9tf(?|KcKRjLfvyWDOcHH^NGJe-ZeZhN@{8xdk ja(HpNN`LLrFDw3kIrVw=+BIE400000NkvXXu0mjfO!Ya& diff --git a/micropsi_server/static/minecraft/block_textures/Daylight_Sensor.png b/micropsi_server/static/minecraft/block_textures/Daylight_Sensor.png deleted file mode 100644 index e901dcd5c3a0c5fb47535661417fd16010fab0d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 949 zcmV;m14{gfP)P000pP1^@s6D7ps@000AgNkl6;37>A$lJLk-DW;$C7l!8SR2?Z1_30z4uYD{V}@ruM0V+`?sFy1u1F#Z>6(?DIK zA-y2PB{6EEF*;Exh}bf;EG;l}nXTu{Ip6ns;Svj#MSr(%-rw`C4)>v4HZ{0qQ~q^h z<$BnO=kGO7-K+VI2YW|`o;_5~Bpq6z;pAsuPEKE37_B#h%H8tayP5ZIE{yaPv*rDJ zck=u*Pm%Jw2-*!+>$P&ucgwY<=H%S`@*QF8c9P4c2K)PpBQFj;SAJsOqbOkN>{;gK zX7Q9I5>0>qHuek*pp>HW(`ine{B&~e>e9L}_g3?V9@sF_TS}Lc$t0V5@{|fW0J`&8 zDzzFmj?u2m;A49!74x{ROW%e94{z-$Z{5&UzP1!h&dgn315*H9ndIaBeZ|rJdv=tM z9(@fjnI>r0Ieu)M#pM+MN`(T$!$(MG@`PcV@v*mATwXybMKPaec=!lOzl)$%=iRX} zemFBdIeT?!q}d98bOE}H**6soHQE6erq7XZ9fB~#2=s2+gw~26>@ZuM!E+sAD~QtT z#57?irqNy{icQ&1d2X{6zTg6g2#yk_&QPu5TH>dV7ye`GfRGryi;eBwQ# z*y6dGSB76&6B|2roJKq3+G3MNGr)B;*2>zQBwPnC>(CA&E-fsP@smuR`kZ~Uvsi1m zyinu7frF$xhaj?i_04xws~51=GIjnU)0c0uTwf*OYSIap$k^K#FF+Ivg2GR^I7;~T z`_oi@{)Ix$=it5po<8*C-zWU>M;~(H)EC@XYSL^4c#g((6t1H&RuEa|y28jJB8Uh| zDIBfo@)OisA=hhlKKZ)B{LK}zNry&Ys7#%w(G0L6WRf1n3fBHnthIvu2gL#CL~%uF z-QzgUZV{ALpp_;vLSzi3TncL~b60L~XWcjL>2+RxlQfHE6AE5Z-QnN631eDbNC} zhA~)6Y%Er=;X0N7C#6%~uC2X=iJjXwnr&N3(zh{ZN_l@G;W@khm+`;Bx(k`N?$`Wx XQ#aF`kb9w900000NkvXXu0mjfbOg$& diff --git a/micropsi_server/static/minecraft/block_textures/Dead_Bush.png b/micropsi_server/static/minecraft/block_textures/Dead_Bush.png deleted file mode 100644 index 4923313188a4f48a35a3a96a02e14665de9e98ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 921 zcmV;K17`e*P)P000>X1^@s6#OZ}&000AENkl%==?~Tv8nuRqs;452?MTHmO&S~g13SS5sYj%tA$COCVM5a+ zxYKnzpbAw*Od6Zi9JW%nB(M!!Xp$zBWUmwK#Eu<%2jpOyrYLNa_)3=S=Z8Ojp67l2 z0v+o3O!)J!KAp5X$)P)iypG;H$hi~I)I>jhdi{Q;#WywVpF4LVnwlE+&;2V%gzpkL zFBPt7MvISQ>7EGRC64gSu}BZ~66il~*VG_$c4pLb$!+tr1P%M=KAIIS0l?ErUHax` z?gO`fFx-Q_kRNg_lX7g6(E)@4T|EHsYo;-O_mOdZPl%&H7j?kwrn$iodq`HztY%np zp6KZS#|Urv*l>hrMJJZ;ronn_RJA%7ngYjqq3ADKO0|Q$f6$wX@T@pJIf^T{a;NTW zJX-*s?~~&*;m_Q$>-RHo<`f$fP`AD5FTUG2==hm-5Jk78l49Knvc|Cio*O+J4bJ(g z+R2GX_j>~G>;Lo76O?2XC0WJoKdSSjYTfG((6e_l`j@{*)yu_a#)DV3D_^mftMx{< zB&&#x`Dgn=0qB(?g1+a)mt?gWnY?0#3^-h>Z=j#OI0L zy1D`!BLJZCL{FF1dWEu#k(ySAb3Mpc6FFie4n=#Ldm~#QU}zd1hQ`Au`U+9%Pn|qf zGj+{G4gjTSjqR4f@8ju{NB4WKxotl^G*4|3Yr%bvtgcH<|5DP32Yr<2n zW371M##-@w%fBmMwpNysMto>UkcNf?X(?&M^JQxV04^>{@!M(g^=>V7@|KcD{Kvn{ z58b|&!5bFjvS!Py*Q)_QS+kRlkEKkJffaV1)w-Q6Dt5wX*cB%a04SzWS(7S@d7`Iw zh3uuH`#sl2dS4V@RP4lV`$SgF&8t6bHSKL7-zT=`ciLiO0(>0X$#?AyxtCLyciQ?w vfsO&dn|Q`&3It`P_Wwe%1%isAbqx6jRAGL2oaz+000000NkvXXu0mjfx01!} diff --git a/micropsi_server/static/minecraft/block_textures/Detector_Rail.png b/micropsi_server/static/minecraft/block_textures/Detector_Rail.png deleted file mode 100644 index 1eaec79986834956f179f11276f752ceb05fc958..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmV;B1aJF^P)P000>X1^@s6#OZ}&000D6Nkls8&YbT#=YH?G zBS^^Y+qYMAT`xR%@BmK0|Q*Xd|5;~31Ir#c%FbU&$Y6V z!f9=tfA8Wsa#@L^{R)G*O!$E$)6pRTpcxL8n#p#~A*0!qbOOQp~{ zU<|>WXXzk10r0HGb>KQg0mXqKl2g&x)!S1O%mGEg8wok3;Vpu*%8S;Jm80EEUJeB|~+f{**NTr5=E`T6NZMa^bINGNPa<|*KrrVT&6r)n-kC{8Blvp}d-s%G$4h`9SRu)-%B}{$ zQP1DY4OGp-Y3S?_Fg!BK@aPERwoO)+$)?jhT3^TC+(apt+0{(6hE8wKK1QXcwU=wF z)&vBu=Oe`+CUsS(2^=3C;l#V|lF>92MM3HAM!9zn?f!k-@+y^<#ja}5o5*nM&maqn ztMh=ExNF&UDweKgKfjWNTsMs5|0Sr@#f^oH!hZFXIFb$ zd7A9*JyqMLrD<(C$8l)6ZftYp8GfD#Qjb6!hgPcvseQ)VwvFyMvCXq2qVNS^#x@#5 zAOn!nH)dXY^PH)C`y<)v4r{Bc)E$R(I*lyLq|^qoWdShs>Wec$ebp3wr(?&{*Tz?X zPl}%{eJTOeH6sh6Fz9;F5rP<|l^2#n2H4rEP+49ka2x_z21&wG8)%hExY(YbH%yyM zvI?4600!breyh|0!LaKgb%aJcbsb};i2+p0Wvb;eCkfXnDp4-l+rOg8tivAKRXI6%z-x(;RYKw<+2lRzm3Sy(J@0P_=9x~5${ zJQw-~j@|uy{;ReI`S587tG_e#`rC!%)ZVrL{^tfcM!-$S)bbIoRsZ$o`xCFcb80Y0 zE}JIT)5+TQ6MB^ly_s$R4BKS8p|V-^&>BBy=^c(8PhTCM1HLHUTAGgq|DO@BRPo3>8|I14-!*F i;QK@|Liw-$yZ!)lW%VGU3_{!h0000P000>X1^@s6#OZ}&000FZNkl9Mpe-zr=+~m`tSW`}A7Ih0S$O3oAitN&vTyldEWP0 z9`!`d@!I#^wC}w6GqomT^~M|_ghwfrM+1L(V(jg=CcpdiGtHyr3YXfOT%5h!+MfR1 z6n{Q9O_pWh`)(x6=0Y4ew*#a8>*?@#gJA0JBgAW7sL zufH-e{`OmwuRL?2dAc@2$7jT|duDcbIJYp@+Is)DQ+)XGH0ix$ zaxeI+6Gz|q!Q}T(e6@L|R!4!&G{p$4CNTokjiW3=*$B=UV(-a-pm1rg!zb64Tf4vd z%~bq{52i`e><-D9=U+DO{pc4H&967VJ<`A+l#Rh1+%^O@vV?Y)k*QKQmYOjb5rRUh z3P7xZUI<)|`}}QTuC@D#ETmQYwTUFw8d6@NoUFPHYD}2)Y z8ZRAtniuM$)SX2ELrysE97m#v%`{~rP4TKI%EDfk^H=8S{qq8k-QHrvSgsU}%1Mmn ziJ>Y_3{~m-jD@+Ye6)OxbH|VKjpJWt%tds9rz8eNIpiE)a1j*|{$np~L z#ZMXA>GGx82$kyapf;ez!+lDMm-}7`D5KWlfU+1OC!89t(ea+8#TAy;RvB5l$-PC!-Z`iw*a26AH+tOyO~AW8XRtX%rdYU|#F0V>I1WLvX_Le;$3!cq1rQ5d>3&BU9*j&sLTZRPjC=^wkzZdW(lf&%{wQ zK4k5n_kC585ZLvey%6Ym&yB4up7ctsSe6FCEA&I4on>sMDRJ00jhBrj)4*nyaxICu9{0JG#Pqhec~XVQNb#4uEx_ z+i7um_FXPqe3#wM)w_do=UD4F_1uJe>4nK-M;>dItYsrj=!ZZrO$nc^@hzYG7Et04 z_;=iAV|SmTscv9lQCKEEhG Z{{cT>sSCbN+Bg6J002ovPDHLkV1jhitw;a> diff --git a/micropsi_server/static/minecraft/block_textures/Diamond_Ore.png b/micropsi_server/static/minecraft/block_textures/Diamond_Ore.png deleted file mode 100644 index aa0626e26560ca2b32d009aad1c4b1cb88f156be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1606 zcmV-M2D$l(P)P000>X1^@s6#OZ}&000IJNklZbI$ETwiP1@oOaf8=9~G=%o&@XX|`?4 z)z#HcZr;54CqUKf^(RS^n4k03dY^+3LawZ=6s}*tzV3P6GC(SoqEe|8@7=q%Q7jg3 zYpvD)1+i^gUcGv?aO1{}b=$U=Gnou=9MkXjNv1C1>n^g}pwsCT@7%ewvAMZ<+Zgks zF#E@Xudc3sa_iQuzkF2q?DK(_nUMXEUw!x+j#?q(qa)OJobDNv(5K($XHk(B$MX6LO-oJl;Z!5C_=I7@>DijKTy>jKs=b21q!X3)d?{?6Z z#9z3KY*#T!f)_PWdr#S~9pDZIObzEbKR!mg*`OCioLc=ov6q=RKb0>mEiDxqjmEQP zv-!dXxNzaZ9~{T|I7t#LOE5Hhj@I-UQLDNNJOE9QuG#inG^#%f+!Q z_x|-2VWmuq1Se5UY(Jpc@8kPEjYfkY2-x1T_Q!bZDr_=bp zPd1xnH)^xzOp;yx9rD~V)^~T22YbYEOgf#W(P;4S;X|S*A_2U~SyXU@5aM0P@bEC* zZkO)JEOP$|0JU0;k&zKH?Kb|i$F$;@WP1}mGK?{Xa=A>kS|toadc7V}N-SYRFMvUh zIF3&sN+|||0a8i^FTbUy6bKv7^N8b^y}dmul?s!SlVms`ezn8qvu8A$O&rG|j$`ap znz-9R2#rJXW@1kusZWX@m476RY%P)cFjHbMweLs``544rfqtqsngLn_g4SHc)` zQb`m=I8x$}5Lq6U)hBB-r{)&e*?NpVGeyp}3B&O1xQ~yI<7V@eo_~uI_gGw6Whd6C z?Pu7QMUo^827?ob_c}u%1or4DQh^~qe~H%oJY&`)!rd42wI-cT)9rR~U6-SyBa*{I zEMpj+oTeCd@DFQb^JBCfiCsOwb=?!WEX%@mU9?Hi`_GBvnEfXY*!%Jyl*Y$NEbHwA zN-1%(dE(3j#uzNiB5WSwy?Bn>?_+YO&`t&+<+~$nj6rM7`1BmL(L951zQ(p~rd^l8 z!~bA8F5Pa2p`jt9l=ON%MA#-7pT!F*D5aR5UBDR~XLoxWCv2dS1g($zV*?~f(#z#? z*9N_CLN||a0FxwKTKj;Zp)6uyiB7*m@_Ms-od79qOs9p>8m%=0KTXAG{MI2Vj!{Y# zE0xM0qbNH5g~Kp>@$%)%KSxpYc*u2^GnveUQi^iFM{wzvG^<_oa1OUwqt$4jlsc~7 z_px7ROVq^;T6BV-ST2{p*xlXzqSxzfy>qnQm=HoPE-n@p7Z=xk-(R+dM=^-LzS-0gO0Hk(9IR6IC1 z*l08wKN|LWNO3*VJ0i0~HRs{jB107*qoM6N<$ Ef}<51x&QzG diff --git a/micropsi_server/static/minecraft/block_textures/Dirt.png b/micropsi_server/static/minecraft/block_textures/Dirt.png deleted file mode 100644 index a0506789729996882cf623fa25dea26d52c0dfbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1542 zcmV+h2Ko7kP)P000>X1^@s6#OZ}&000HeNkl+Z$CKR9`*{ZZH+j1GC@g+51#&TjYz4nUd{H7 zrpx`Gzw_R6Vb4I`+WD$9*6(y$>8`PEJ64i@k#kmiq9kQ{GC?VcF^+z(%d%?7BM2dk z?;TF}d!%!{Nba= z>vtZV*7uHP??wP+qiL+;cYirxt=nR4)W>Cg9vvT3Ys+L_p;`kzdu0uL;P%5~K6Pop z-N&aG=Na`{Xyb4WZoYpPA7HwuvF`k7B7i)NIjbDnIOeN{II9VM#d;^@@~BJI7>ZbP z`~D22BzY1OB`N*Qbq){i@@TT)-X~`q&sGQ^1Q>R5=BwrcQfWh!cNj&`&Js>nj>WR# zWIE@AgJWJAb!cbjvR&Qi6M$e1fBNvRXk&RWS)d4{v7%}$Nu;Qaed?|VU{N;Ix@5H8 z<7Ba7zO3oCQr3C}wXr;!ERaf(M3N_`3xt&1elX$SXhyr!!FWNp9WyIyq9i4Fi`JIS zVdnz!(pr!EpG>KZMH|PU+oH%48ta&qHBt&1V|j42q}$FoIGS-fujm&U%lVvMJEPVX zV;$CeGT~54QZ@zv3Gn1}POK!>I+ET7bz?XpY89!>ZOKkz$a76oMpH80#(|QVIglX{B_OMk&c!w?$bQd9?!{=h%{DsAJ`lfSnHY0Cd^&T zylfCs;F}sFVv;PySwp|b@dO&{2m$&<#>xu3X)Y$|wo^g~^x7#(3MR7^$7dzeMTPaA zD;r%xIPc4nn7Y=iYK=@;$El%mm=VW&l}$Or+1l+=yEScj4V;~Zu6PZmaq8(9*Kw?-XA za2%a2fqw2_dTug)`}*f)JiM~wmM6QHd+~NzX(qE3gCb+lZE^4Lj0eXv(pcfb`6h@| zM7I@ diff --git a/micropsi_server/static/minecraft/block_textures/Dispenser.png b/micropsi_server/static/minecraft/block_textures/Dispenser.png deleted file mode 100644 index dea339862013b86fa0a20dedefb705e1c530bb75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1420 zcmV;71#|j|P)P000>X1^@s6#OZ}&000G3Nkl z&2HOP7RP`5rYKsptPrpw+cAIF}laJg;88(tY1QGfnebA;eF> z#mA2y%7Cd75aP^n zobyhnb21nV*xA{^wrzA>CkO&os}-K-q3b$P6mfNRMV{wFDdl^m)WwGnALJiG-oAaS z*L8j7y6*Ym;o(WA)1lw*<2Vk6VX)b3NRouQt^ug)8q2b%s*0j02!enwU%oINkB7_U z^1YPu;`;hpHjp=O-u%V3?SDLd`t&3S0xZkIFbo{Wp)5;;5cGOIlu}exMOl`FVTff} z0L6BitM;M0e?(P!DF-ejz8jTvL zZQJ<1k7=4{n#N|cX-v|L%XM9jkB{l~dfeUJF`LbJ^5hAcrZJz-xxBn&JRY;Rw?`bu z%;$3ef*_#XZj^E_PF#dTfQ z>or0MTCEmJDWsH*L0s4U4-9Am9LJ&G?<1w;*|TRjj)UVk*tX5??k=wDHYSl$Hn!fL zt=Lsn(Q38OH0^6hzu#|eB80#+O$LJjLI@U%1&-sO>pDs)TCG+y(xNEPb)EHkO<9&G zr3k|i-}k>3emlpis*qALnM@cCha4Rp@$%(MI-L&d^_nzIQA#y>0XU8WfaiI%+wEpc z%;)p3Ev%GkZmEGGQ#4JZswz624pK_?_V%zWtJ%&{ zN;FMt+@)_jsp}eD*D(yEvGQ`cY=pHetIi3v11 zWLbvq`?T9_-o1OrcswQu0;H5Er3ium!!YP{I_&T7BZMH&^QPGLyI!xG8~_8L+wB&U z$>d#@Wno>{n5N18{yxXY$Bm1G5KO02rqgLN`?{{F>$)jc6a~xWk|aq;({u>@J zjbHu$bEnh!ak*R$CzHuH%jGiMCU4h-rfDQeLLA3gojj)@|hUIce6h$OSGF-3M z-zlZOPm<)O=>%Z0Sg0(^{@rf3e=Le(n5OABtJNypZduoLnayUzaolWyvMkB6j3h~j zqG(tY#dpB>Ns@fZ^IUCf|LGflbabQxXTW){*E=~lI3Uk+K7am9x7)=uO=hzhtJP{K zrF;)uBuVnPH~#mh-|y?1rkz=qb-ug1dorC)u`KIR#UBm%eP>e2Z>y^MNhx&^$MK{7 ayM6)aWF<~0*Sc~50000P000>X1^@s6#OZ}&000DaNklf(?lI0Ok`mBtRm@@gRY$ z@wnTMez<*~8=!l!fnCs6(m~sG>YO@N)eOHGmzS3WUgH{S@p6kY~@H5$Aj;gcuzi9S!n4hcPC+*IL6l z2hKT&2vHPau~?vKnu*qWthK&+@#2O3DTvqHIX5~wIvU)$a|gy4SZhHpD!QQ+)dLDI_<>?8zEHk|cqY5^dW;DTS`hzLbd zKuQV5Sg^IXs8T9e``*2K$g&J=+lF3w$q7JcviG(z2J7`2d7k6ZqetMJqbQ1CP3Ih% zrU4P*@n{y5+2G$;nBWr`_K8b>wpf zkhX2ZdhqvxF}An-x~@Z(WjH)Mgw{H&49P000;W1^@s654Bdt000CmNklFs39OXijjmhY-E6jRJc-}` zWL-DBR?7N|u3N`;dluI>PsN44^hIAbul*n3>+7WqALQIeDMJjyCRVFWY&P2{Wr&nY zJg+Z)&~@wUPYgdlKiAc2^H$#v-(62n$F+1ba_*z=hgh#SQA!_XIdbl!)sYOg`dXi-_ELSULeHY3OhIt*@KZ zS0kLA?Ahsk%!CLzWnUuW1XGM;FFeH!_PXPq{y=NK07b|-{6@YknlJjgOd{l=Z2 zmY!sM4r)|Hz)WywsiGQGVbqaCLK@UG0^q**iU`v*BNB+nb{xmc1dwwYT5HU6Q0ojL zm@2A5M9`YkT1BK{npoErd)YHHQ)-7Kq0dH}8iyE(2+R~S9e_*SY_@NU=(iCd4~;=< zPCpJR|Xxb174zVZHAz;#JNhBqVOrFzrit+ih=owcUo?sEqA??}wfe3(Q!m8C} zKRhs75(;%@nUFBd6ma^vFv$c0nv!6)ND&c4_)$~ipa)$1B-N70a{j0(*V;{rWLlf4 zwGjlPEJL9pLLaNsb@!#w)3oS&7JuL@&W=fNtoACXVU}p_{71fLW;}5&^%9QLsQ|;g zIXXe-9mNY~f=KX_++&}16fMW?UNp}+0|7ML+bQr>kwF{>D6o{$zxq17wA0-)W#nGP;13Kh=5Ec#7}S9Lwo)W@Qwfswml2+dv1;#N3`Yn1kq5= z+c}qZ$;9|bPb7r3ueqNtPf}j_MR4vu|7ZYV+Ct(+1TTj~YSK;+Gog8K(2>Sc#e<#z zAsEIi6+uvxAP&GsBJo*-`y;P#&i!){ylci^MWiesp;mv8xdbVU+8U&wwFgyo-c6?Z z=|K*8i?wYdTNh?|ET!BnP000>X1^@s6#OZ}&000G6Nkl!8R#NBxRA}pO@@mBL)GsTX!xzaNc=l=6PmD<(}5-^~Ax!!FQ9%vDN{$#gnBj^p@M7>2Kt zB&l1F!^6XGZQK4~JRTppu8Xeg2qCa6i#*SfQqu4DX_|(jD9H01-}f;L1Ar)sST2_c zA(+qSgkeY=$Hz&M{O!}HPybc{wzjrjm1X%&mSq4qj>BLuAdX`IhQlF32sBNj+wD?S z6+sYS+cr{4s;UCOFbpm(F1Wh7B8s91X_{(jn*LJ*D9e)3Xha;xeEj&4>2%88-X4?5 zggnoQqKLY#(KHQ3QONTgRaLpZzNV@wgbujnHf`F^5D{gLX&@_#$tu0hlrK&3G zy8Z&PAy-Pta5yB-b2Lq3I-N3|PFXA#^!t5=!y((-+k|1rY&K&$owlHcVbJY%(KHQ! zs;XL%XaGe~U|AOX`}+(A11>Huh@uGJ_fZsuD2g~aIpON+ihjRO7=}bq1c2+hIF3V_ zrc_mhq9|Wg+JbD#({-KO+gl97pePD#+osd$(CKstf`B~FIXgQ8Xxry`9!=A= z>+3Hdn?4pr!E(9e{rmSE930?z9-5}n>-EU8jN98=nx>&FOKjV22T)NII-L%V<6xR* zJ6lcj>v9(D5>phKrlHg6U>F8(-n`-M+qWzhi&BHhSHkC|2r!$?2*a@DXp=x$mLy3+ z7>0ylc)VJz{t`veU(+=G&{py})8_8(?x#G@f2`~J=lOj8^zQENL0Oils)}vfn{$if zICMIluQCsU;5bdwP4MGpBQ1XC#+#-oeBXZ|gm^g^42~v~32B=0;ll?!&qGz!&9OSJ zs_IpeB(J5E_q*}G1-8At{lYZOm!9Vx&F6D;UB6fG|JzK{6yx#u`|)`EFW>jSS5;N~ d?^nOte*nG%|8PWgYhM5W002ovPDHLkV1h5g(_R1o diff --git a/micropsi_server/static/minecraft/block_textures/Emerald_Ore.png b/micropsi_server/static/minecraft/block_textures/Emerald_Ore.png deleted file mode 100644 index 79f5329e63e5457da690dbaf30b84a969aff0f34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1529 zcmVP000>X1^@s6#OZ}&000HRNkl)M6~{mC-uKHh&og7svr}V?V>^H{YQb)Zh>C(#QK?ATT2=mwuDkHc+jP-?qhi%o zRa4P)QKg}ZY9Sg=FbJ@X4VdwGJU`y=``+zh;xROhLVMQdp3nV$&$;(V-Y}$;=JN9L zFK*np@oPY}+wJ>W>*7t$8gEkk(xpqQ*REY#vu(QpAcUaRYBe`DHr6*cH*Xb1q5dz3 zl+wI>`SR-Z>(|$$lnu}Gh@yxz%Ls#jAPA`az$Y98%`ZQ{v)*hrzZ3Q?$jQyGUcI{J z`+fsJDMh#2<6kyQ!XPJ_=n|JQqS-*St?yWq})qjNuA~96J4Dwr*_<$Ta7i zdxk8_s8*}I2-{rz$uf_hJfd=FAf=?+?KW@UzWvMX?d{tV;QaaXzjAHo=jLuecWa;N zv~pq{!Zx{h)Qiy#Pi`0(LWzu!+g zozCygk#B~9>ZaI2Fm`T?a=A>BB!p!AcI$Uksy-L}^K4!TF~Kh9RbDV$M}~IG-~3`xAWM$MZaD zwHjHL4U;>PFaP*woEMVMcJI+1i*edHC3g&srdS%M_p~$07XW#llcp(JYbw(dv|b#r zzj+@a1X-5x?AbH6wze1y1^`$}U^X-Mo7*HZr{~2;Im``;gy+ctGA%Go^HhoJx(Fdq zN)fzUp}V75c{-0dW)eja0C60%zrT-CidL(IVHgxe!ExND{L(-Qi4>Adu`5K2E?JhLwI)YnRmTuQ5JeGro|ES}QcB1RtU!<6lPhg1V;({X zY}-DC09clV<2cNQ6^^?BUpYG@NkUN+^qiEhe-zR!LZp<~wv7;i=?imYb2e$iLmS{Y z4oWGMQkbTRVHmGBJ_rJeq9BfA=FBQiD?V?VVkwe{_ zM5C$COw*auRNty|x-840Qno)Q{AMTD$hvRzA<3|seO(t<&x4E~sxBl|w%WoC` zUdXGkvuDq)E-o&vd7jtk^?Gcpmsp3Y**QE~?{qrfS^R$vA%wZGu<-H9%F3q`6B8dB fhGBmHsbTyZ>FpX+hn2XO00000NkvXXu0mjflZW`Q diff --git a/micropsi_server/static/minecraft/block_textures/Emptiness.png b/micropsi_server/static/minecraft/block_textures/Emptiness.png deleted file mode 100644 index 88ea7de95b1464f77d9531c7120546eea97acfb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 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 diff --git a/micropsi_server/static/minecraft/block_textures/Ender_Chest.png b/micropsi_server/static/minecraft/block_textures/Ender_Chest.png deleted file mode 100644 index a61ae21942b310fbdacf7c42089fe36b28aa0da2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1087 zcmV-F1i<@=P)P000>X1^@s6#OZ}&000CANkldl?gsPDG>y;Ym!rzd+mH~vNu@;-AxT>O_Oqy~y-q_l>yZ(nK zISd(WZDp#OdFOCV!Lh5u5q|(Mp_usETx?n>})<^vb)XhqbFz#%rABs zY!5jRq8iDwl&mN?fBsdzzxe|qLOI-{v#^LwlKlkE)68aUZa<+M4vE3Da(0>RM+2&9 zLk#g`$aplN+?&zV6^|Z2B=59Q4fv+!@x}&4yF;2KGXV zX0wwau5sMoctDnBJh=ZWtyYW9d>dmE<`=r`JsB|@kBAyrKDC4op7pgJ7hZdr>tEfV zu4{r1;QbMZ;VFqR_zPuNyL$`^JDj>IySf`S#`xBRhvl4JI+io{^^@N>Sv9vE+G*wF#;Ljf#i?RfzE*v5nll zx5KSFLsTF%0YW@{QpX`gMF0(v#f3h^h=#Ba(xcWG17)a~R1@-r4)rJ^F4F0=k6r%J zk#MCdOGG21(FotvWLZk9C=m3(tctZ3UqvJ>$ci>$rUdVpj>mvrIb8P0Q?DU>o;@^8nyjEbEyCT002ovPDHLk FV1o7i3#kAA diff --git a/micropsi_server/static/minecraft/block_textures/Farmland.png b/micropsi_server/static/minecraft/block_textures/Farmland.png deleted file mode 100644 index a7e5ad5f0a01f983588a9ac444125ce4190d7536..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1499 zcmV<11tj{3P)P000>X1^@s6#OZ}&000G|NklfqXU<;l&9QqsyEEI(^DU-+Lwa0`b%D zzZy-3(d&zqe)lK8_=A4d_r$X-x_$FT)Q|j~qtk46nd|A7wnnpSTcbVG+5I2?{I~xT z`&{JCkG>Nfo}@czS?^lwroG5#G+sxT65X^sn6GBrlfm8#TciD3@4WY4vHytNzIh`8 zb|maBGdnD)gH*ZANx}D{!+|_M2HKybG_L%iy!o8ywT&zd9xiJXVFqbe1Ua7!C*M zs$!W{guY^P)SE5yYVT_=KDU4Co%gf^=w$eI-En7;RnsK!`11CI)=54(UXqnH%dBE+ z(&O7-zeZy^{_)_9##p{OUB@`Vvh^tImZt5V#j0f3i>PWtV>&PyD)5j}Ts;39@dHKY zEZdVI3mcO@d_dJ03f-WTBp#0GSxr9)xqp&kg<^e_Q0SH<3OLU-N(#n+76{8X||%D1dRF-YX}xu`RKR=ZIx3IB9k$;SW?#QMGkzA z&UCB|Vp1EiTAW-WQVNQ)W;{sfMFAhqPC9RnI%LMk5kg{%3O5*#9ylSZGmMHLW z4w5*aY7CWb*%&3*N5v=shW*GSp-)jYR82?c9L7R07_+@G#8Z;zH-?;^7wFpHoZ~Dl z@s*?(`V5i~7Y;~sO=m6rB%rmHw(HDe#8dLktg3G%p+D12N2wb+(_vdpT4;1*(57Q; zvW^hod5V4%(2D~GNr-L?u3F(MiKir`ZfC8rw*=zN&&-pz-gqtg`zH@~bkpq)dcm}= z4Zf#nO-B%Th-k#}^dVJaa6sr^RJU^uYaPzHnG@{k+U)=7-tm+3NQ??3as>tD8U0E{2zp^yLo002ovPDHLkV1gkh B^M(Kb diff --git a/micropsi_server/static/minecraft/block_textures/Fence.png b/micropsi_server/static/minecraft/block_textures/Fence.png deleted file mode 100644 index 25b44147729956acd8707a45bfda9cd29fe1e9ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1244 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 diff --git a/micropsi_server/static/minecraft/block_textures/Flower_Pot.png b/micropsi_server/static/minecraft/block_textures/Flower_Pot.png deleted file mode 100644 index bb4560a6889478adfc8e81f5f7e2a4d4c9f1e806..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 871 zcmV-t1DO1YP)P000>X1^@s6#OZ}&0009nNkl2+{ z;TX#?zRb*-+qvw$7sVLI@=TOih%T!_pf!d;6wdi)k@>ey<4jfT4~(5PM^Hb zON}Jz+*x1Vy7Te!W+RE)s$|a&Pj{Z~A8ilIs`sBv8cE#w>ekBE?Ul}E?tJ^N{*cNm zYpps<^%{|p?2o^@J5TnHwui;r&E}dk)422X#@f~wYm1ws(zW-x0|rG!&_E-JS%@NB zP*$1?tTbvw)@08QPj`Obdp(zI#^lTOAt= zLk@Z)yapm;&;TAq=jhFtC=gT;fy5e&5u6Vkk8;M9CygvGPx=hY3Jr?2=1mH3+0_}7 zpo*&EL%0T-0W8jJ{DmCAbnN*8cm z5fKD1MlL>VN+QS{oEekYOe2b|C1|*aFtQdALE)TqN+BY|#=N1JLo>TwB3Ka$=TQY~ zga|V;P-_G|kIRU`T7#RjS#wGL#djf~su-BArYcsZ!d?T`Oj`xP8abbx%$@#>Nq3w- zb|GY=vcjBaoLECWjxO~J&U-XKP$--yOk>POMfLcq=FA`8ofOaZd&5V9lsB67yUuIs zR7oR46_nBiDjx`{Q$oP`kaZ`;!-g!^1Rkg@=Hag7*u{GDaj34S+^$)#oq`94xo4No1002ovPDHLkV1nmhq}c!f diff --git a/micropsi_server/static/minecraft/block_textures/Free-standing_Banner_(Small).png b/micropsi_server/static/minecraft/block_textures/Free-standing_Banner_(Small).png deleted file mode 100644 index c3054ce62c29ed5bc16e7c850362e165823cb12f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmV-90>=G`P)P000>X1^@s6#OZ}&00062NklA&R0*rBW%4GJu&U zFQnCtH%m!%U`L6w>6>aJXA^oG86wN_R63p3001bZK%5p#KYlK~T3CLa9FsIgYBQ$Q z%-IeO6S1Tq2>-1KAz<1J=GWg1Z-3p@-fqmFjts?~Vtry*vJ(sdz_Ki`Z3nI!{IE&c z6;_Wb@d7yK558`$e%Ja%TkKyX2wE(LD@S*^Q@d)~ZS$(a0zDH!5FRvYx5^Lf7lBH= zYWx~ThWJ5yiAV?E7f2_Iy!ptGUhU}$;uqfO`=TTW7FKB>gfZ6Z9N{5;({^(a10kGq zpp^Q;+>08Xb#h|zr6@_jIR`=r=(>JTE|>F>ARbud@}l&&es%UGHY8t+JsOuC#}Rd1 zKQIiVKM()_Q2-Vb*xb?YKi_is;`c-%Fjw_YQIaYTM?`iLF(!)Cw00000NkvXX Hu0mjffoS&m diff --git a/micropsi_server/static/minecraft/block_textures/Furnace.png b/micropsi_server/static/minecraft/block_textures/Furnace.png deleted file mode 100644 index b439d33aaa778d9490b0db64ce71bd0d1cc1c0b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1471 zcmV;w1wi_VP)P000>X1^@s6#OZ}&000GsNklUY<7^Q>EYSg*;ipdgYjnMNHE~(=-~520;)|tya-A4cB!!IXNK=!y_r>fl})5_3PL2tB{?Y z9jz#eJ=?bTH#Ro9^?IF^l@%<@qEe}lWf`95Q4|FLMNwcF25FiS$1#mYgNus`uCA_* zrqk(xl=ATO^i-CRr%#{$$~4VCt*)+i8;u5rVNj`5uq=xtNyzh@cDs#IiZo40l7vpD zgJBo|jK^bUvl*JEF&d2+4u{NUv!nTZ{`-@YlfPF0YPH%Q(lq^b6h#0mEG*D!wU|sM z0CYMXG)+U-b!xR5d7k6CE{@|+6a{&n17MmaA3uKN`udvN+uP23K99pN{HqR-Wf^O0 zYfL5+PESu63Br%hlBt zH#axvy3XR_BATX==Q&c!&k)lzsZ=T`rD(NU)a!MEAmG!dPYebFMxzn!cAHM8!@Ya= z7!HScp2uJ?D50ilQmfU_bsZrDS(cR{Ddn;(3r*7qf&kz5Szcb|{{8z1AsCOx93LNZ zb#+Cn)nYgtG9HfsXfzruEG!U)A$gu7geWIfN_~cCnue5;D2g~cJ7YGRad~;k;^HEY z9z9}pbrsijNs@%~^KHltdtV%s*hZ4*Tid7k4q4oWGclx09{+x{mEz;PV%Jjb#uc6WE# z-rmMEO{A1er&A`A35H={+jbceDP0mX zdGh25Z{NP9-|v^^-i5<)98A+B%QDh5Mbk8*C_)Hk@_`Qc5;AH%XF&e!tJs(h^CMP^;DOeIFqNX_`{6*O5}vYPB#7qukC?N`w$!rVAhp zLoO~Z7z_rb-kY17?C$OoMG=>mm*~2VX`1COYc`vuY3Ovsvj$>ZFd`Yj@W8Pn+$*LCqc z?(#4Q+KU%2epp{$|J%~i(hr?ZNBh>Z Z{{W~P000>X1^@s6#OZ}&000HbNkl z&2QXz8OJ~N_+>n=%_L6JWI{z7jjC+Y^s+*$!b+7ma42wzcF**@D~=qi{t5K7C%AWW zMB;)(1wsNuTXj{2CM4-JX_ATKnaqqGzx(&&u*6lD?$YnYl4U<1KhO8;$5wbxbvhlj zR;z7jn)Zt<%YF-NK6&y)ywAPL|GVJ&_3LV-QrVDF-fK3SD_5^xrQh!}7!0~W5Zsqi zZa#nh{5xUa3RzuURe=r5vhGb!Pp@3Nc8&A%a}2{E&vT+EVmKT!7!10e=iN_|WOH|S z_nol+fvm2ss#%t8n5KEJ(P*qJE-q59*D(wORaI#=oA|y@rBXpvRUF4*dwZK82)aUu z`%=oy$B!S2cS6?J*VR1FH_GMmy``n4l}4k%<;#~bO_NfoM4G0!u1lWh0OWa&uInU8 zLKH90$v?$n%^m%K$J8gZ=$|`u#pQmrfJmcbyQU)%QA!zZy<(YP%4#>QqpR*Xfzu5zR$tI0f&c& z42MIer>C)Pn@g82ae8`+>$)5s9u`o;FsRjPXqtwiD5PmxgrpdkX_~02itqaj1_S2i z=9rn8K~WS&qY+zMTO1!B(`vOiJw0VK8UfI3HmO#t1VKQSWhjbLbSkBM15s5KAp~I< zvb(#B=Xo3*9Z|2>xpL(S3kwT4jzb*B^m;x18x|BrVQOj$DP<7~fGCQ%s9Qj!lw?^( zk|g-PPoC!t1_MT;5yRn-PN&1%+#Ig!;&~pGN`-Q{Ou1Yp3`4Rk!?G--ln5b;fRxMS zuVDa|WszkWrfG8X=1n@C4u)YMgkUn6FdmQ5b)9m#T!cgjQN;Q}7iK3(f@N7Kit?rt z%d%KrUdFa+7tquk-BLGhV%VRhWAb4$HDI41+XHNs~YJ@9#4=H%A=D)M_;bg8_=7kR%C>Mgt)P ztyT+N*Ng2egg{Z0x6@TBmC!Vezdm{dUwws{=Ol`Pcl|m)x_ukVve?_(L(?=2!zgyy z)YMclwu_fjQIuk=Z+UWI*37^D&ieO;NEO0=gM8w$Td&jk_+w^fW_bGaDMv>~G#U-& z=jX|?j35Y#Vi(6S3=u-S-ASJ3U&e9#Lop>*sy_vM(E4C}l=1S9kNA`C^Wp8=MLac4 zBZ?xVl;n9{6px~aAP5+b$9SICRaNy%fD*uq7ccf4$NAz1`;+dBnSDTlt#v?IhMBDd z<>e5+(?Lo}uh*kesZgm@3S-kWWipxII1aAscEd3IOiKB=>$bp(? z+}%Xn{Ra=Z#2R~iDvqpfp65s@@jQ=%gM;p5GWk>pvF5t&L)UfR>-Xq8_29t+_0F9; ppS0WU-_Op@eq!79w?5!E^&hM~CB!F}OuhgB002ovPDHLkV1oXs?1lgU diff --git a/micropsi_server/static/minecraft/block_textures/Glass.png b/micropsi_server/static/minecraft/block_textures/Glass.png deleted file mode 100644 index 069c05ef93b69a573afbff0f748f5ac9bcbc75d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1844 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 D?vaQQ diff --git a/micropsi_server/static/minecraft/block_textures/Glowstone_(Block).png b/micropsi_server/static/minecraft/block_textures/Glowstone_(Block).png deleted file mode 100644 index a6e89db0eb295386cf2dd684caf4f5141512de9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1769 zcmVP000>X1^@s6#OZ}&000KCNkl z+ix6q6~{j_J3Bjj@p^Z?Yp?Bfe0OZeahxj&&8^&mLenDQ6^I8=pAZ2F@x}vElt%k z!spaa!z1F$6K&yN{_ym#d``jeD8}M)Tx)!ux6)@NTs+z=T4d{_Dk@jBa%uEYwGTmv zd>v7UTs+o%l?K!A%;wD()v$b8VD*i#SPyiBMM;`cOyhE2mp?Eu)QZhWu%0&P=-U@K zH|z_an+Sy`27J52M}`;g%xyf^*(V^EUmXy?|D8+WBd0G-In|}hv4RoE7mA!Y*u$BN zuhYH%3s}ktR^w?NFGmRsUkEI$Y=kXNNBG3C{@rhWX=w4^Gb@efLVomvFN-IR_lMQ0 zF}1n$@bdiq`9O0Z#OnGJjCup7Sf|~kBj|b7wqlf0+Z;RCjikrP)~$^Agg~oohQD{Z zD;z%4zWdrsU5jsAf9gyC`1y~|e-G0oSWDB?5u%jM&}6e< z)^s$^2h1h|B?V4m3%n8 zmKq%$@1UlHFeN(D!53_OLrAKBs9*;l#t?iAN%enD?XGsQ|3doV;clOb;(Cu@8_ zF;%3a9b$PaH)ob8Nlsea4lKHk%iV>jG|>{jOQEjdc6&(0(!l>lxMp)QbN$blq7|jd zP0%Z|V{@QeWb$>1KfEEiG{J4UItn~E^AecI^^3+L7PcO-x zEKyZv;^bF3bL=37p%Ll~u#wjJa4k#c!3#8b+fX%uhP?xq(~iw%WqDWUNi<4(z>R3u z(JK{#ZW+^T@ZOyTfTxfiuN8Ys3$bLJnqDE;Jw~vlnJ@M_aMenv^#%`b{|z;tV`U{u zS*v51A}*VV&mm)%Ewp;vG#o)>yF#H*=Kh0+baw^zAg7L>=1^x7>0*srH?LDm%tBo! zT@PSbTgYb%%&n!#HDqkf9^&zBg8n83`+6B18pZ2rW_2q`RZZfMEvPjC(QapdK;G-g z?fGT8TKCai7Fk{2pi(N3tO&&3zk}#-Az5X5dV-91IJo&BLCI;yZI@BJ-3WRXU9XZa zYYcVyi0$Ojb&zD4d&@g}kb8G$h}f(MvVzs^A)3w7lFI5O=5x!KmW2N#x1~&ek>#b=uj@ z6!F;vFf_I@4UAj=WI2|>5GyStnHI|}DWQT)*&xIme!DgpqKu#0MRcdU-Ggz!L zW4&#(dYoXIdt11_zwJ`3tQ{Rb@d{4G%7+h^Xmf}ZEN;|jgq*6O3leRuUQYJ7L29CA zLdB@#X?8H^mk>Q&` zt#bE+Sx_8EvK$da@oK{~rxzYRcdMJW zeFxZDdrT~zjA(-8s^W4@&o68}_n`Px$VXwR)s;y>5U$u2+i1y@FpV00!KO$$U%6VU z)~08dGB3PVJ`?gxNBSM2>~v0wF7NBaL00000 LNkvXXu0mjfZWUpn diff --git a/micropsi_server/static/minecraft/block_textures/Gold_(Block).png b/micropsi_server/static/minecraft/block_textures/Gold_(Block).png deleted file mode 100644 index 40549e6bf72e6f46c366ba4e9ff0dcd588f6dbcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1314 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 diff --git a/micropsi_server/static/minecraft/block_textures/Grass.png b/micropsi_server/static/minecraft/block_textures/Grass.png deleted file mode 100644 index c2e2ec956381386989fefe7e23cd913a1ea7374f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 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

    !&6xuIm|Jsv?6oCgJrK&KPpHsn@`BCz@( za_3;NfM^BDT$mP~rE%2P3@>yHd;e)+8wH%b0F8BFIZ>`1R;VEZLeF z&QAl%4M^J#E5dB1$cd0Fg-PX^;;DCq&7Q#-gQ!qBH?)oqpV%jtUyjW;UWv9>1UqM% zzApi!Gg!?WCx@Oh&TzgFa1?tMQ_p9q=R`c6HAAf}L`G;el%rKJG?v{vqjGR}-})yY zk6>Sk&9}c6Z955e5~KZA-7z{bq!Y*TP^p&63{Tt@!logPl^`%wIm3G0?e5)jgHP|d zXK%)*^~S3;^R3sz?M_p7MyseF19@68oqFFj diff --git a/micropsi_server/static/minecraft/block_textures/Hopper.png b/micropsi_server/static/minecraft/block_textures/Hopper.png deleted file mode 100644 index 61b2cba4ffe0369579e4d34d5b259bad9ceaaf12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmV;t14I0YP)P000#T1^@s6vnxdy000AnNklrr^ul}p z&(YD*Lb3ENhn<}rIOjIK_xnXre39pQ5XUhqrG94S?$XlIUk3*V|Bs~8=>WiobMVj8 zG*#pA7(|3PjuAx>TCJAEar|0Dx+3!D$;ruLB)hx2D9iF=X5MG!&(btiNs^!}O8`I= zMX=VwdygOpV68>7*%Ynyf!4YU;P>O> z5kVA1h~pUEdk_&s1jZPIVTeYfA;y?Pt@RID>)#F!4{u4fx3@ovqNvNvpC(D7Mxzm& zb8yZ90F+WSnY9*;Mg!h^7-Nv1wvl&DLT5A|%V66oaVK5jVO;Zq2-HD)@NtJ1q zWhl!M#u%)wu3~w4xlTevz|6G(0HED&gPAcH3^18Y>I$psYmy)c>h>t5pp-(EWpK{H z7=u=;1@AqwEW>OzLl}mL;}{}>e!q_-NnnhrK}7^20wTI4@!sc3seApeoQtABmSwQk zqTOzz*=&NDadUHnG)-%8X0GGzSbn!%-vampz`pl>t*Xj72N6LK1jzFoMNuHnb7WZt zrPS>n5Zx08fO7y{B6|78GHz{c1w~Oj)mra+??0%jsQ{Hy7>!2tZdBixxgz?Th`In? zU0q%A8_RgtHa0c_rPNo<{GtM?GELL8o>i6kT*A)){B(JF$^XTB^mc7-Zni`uWoBrt zK}0CavKB-T1oaH&VrgGoTudIqQV+en6DXD9D!@uH)diRjTpq)DD+v)znE45S*UZdi zSwbm=D2nPCi3kID?Y(~j;K3?@1^y>%Yil5)uSMj?s)}mqlv3X?^UHp}|5l7gE2-@K z{2U^3Q^^##y$(ST+`NlmArb%}qBEt`a{&4L9_1qP9KhK^@ryWBw!XgpURjn;r_<@H eUa!|%H11zMO*c(-!NJx50000P000>X1^@s6#OZ}&0009TNkly1oMQ^=u;aKr6px$~w9Owa& zs#1xfB9Wwl*pTnoPQ3ou-Qn=O_KZJ>pS1GK_|5yhdGE~|#gy{s_hSt#TWjwqrCtK7 zj~~os)9h8#8aJ91Yl$)oT0v&10?(#_O@!peft5fL-MR+N4xF{&LJ`)hebsYeBM{ek zI75`CV1aITXrqEsNer~wV83v(KIGRwj&&Tf9LKDr8IiT49vK7H3ShAeaTmHNoVGl* z5tspZ@Y~wW#Lhml8Fzv5I6w};g3@p?gfxT9LOb!)%D`>l_V>5$eR}ia>3#sPyYYIt zzP}aCM>hz|3*L?(fMNhYUxv5??+@XmDZhEx!Wju^no3dZ?rgHX_B^WXZ7u`rL2fK= z98!7v7h(AuTv(FG_iAJO>8ypg7p{}NO}5vbQ`_6}9d#%3UqStBJS+_1hXts|FdITx zgunlXBpq*DDRIDqTwjzpuU8-gY}bdu0{?#SPmvoxJ=h{0DqdqG0v@E*fsH+(YYm;w zxK diff --git a/micropsi_server/static/minecraft/block_textures/Inverted_Daylight_Sensor.png b/micropsi_server/static/minecraft/block_textures/Inverted_Daylight_Sensor.png deleted file mode 100644 index 9cbf20f09b12b3a64e2a38c58b34b73811b155cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 934 zcmV;X16lluP)P000pP1^@s6D7ps@000ARNkl8=`h=L({1LLIWz&LX%&P`AU zK8hGegdj#~wakjB06-Hz~366hccr@+78;Qi={^DFi8PD-YH3CPTRv&SkZHE)1HjWVZrXH&{7T zE`+1)*wWDvk}s8sI*Pph_WShr4+3y~>KaGB`wZ<9MCI7|*1l!h;cxbn#!c$=IU3EF zz-Sf|`@`&fYxkY&iULDeG_>Q4zh`DB!-d;&Z4GFmHcTsp&s=e827ZaP`u`tL`O z#Suh27tS5y=8enL7Z!;EO=lF4Ik~gzI}i_K*5gs+a{)$qP8>hR*;BvLRVp&LWf!A+ z_G0wX34j0X7o7e2Q|9IyG+QyD(F8^j7>yH;_uLOjGQoR~_a3DbMr%s>h=o?dY+9!= zd5|!>Nus)lehjkti&+T@K#^c3f#pA>g1p(Fx&Le4RziPIV@icRPEc=st<6l_5 zGurE{)7^y#>pYqBw37_&1t%US;KZS|mN>cJ{GpJQI#ZxMu$Wj7!8$=alB`hqe~Qjr zxOvU$@_~)(d)>OhiXZ6ha+U7P000>X1^@s6#OZ}&000DNNkl8^wP!ckXrU*n|L98f2jLego<)R)`Ku)bzVrBoJFh`3T#+75f1;6#@~Ytq>Io zRLFu@-UvlPQKI-?VqMRje{ZuhIPb!%jC7kB-E*FEp7YEIF1TL4d?}SuJ+1YFv$L~5 zgb>^J@891PH>+9=i zt+CdU=Q%DT;z-vq`O1OEzU$hEfV^EzY?_7Rh6ej4>P^AM@$cCtkmP-Tv_5!(wl5@41Td z8;u6T;gExa18&^7!OfdDX}8;CS%%h{5CU12A*G}&%cN8Yf!3PIWWsPbhA%vvt`t|E{IvuWFy-Emy#bUwf=_z}Ad%S=Dp7D50 zQ54kcbwUVOYw_METm>M6Kq*C*WmHv#wHBommo8nxImciyU^pDo>2&CHIt&H_c6WD~ z&1N(j4eIqe-g~_F=WIP^moWydHAPXRxGpz24&wg)KD)cS93CFhXf$Xv8VOhkfe->I zB_V`!c1eId&skYn!FzvBNuK9vPfAI%*+gqiyd!Ia& zQj%p^8bo6Z&N-ZO2q97coO6T_l2vhboO54Eq?D=RnE5CSw*xZS`+6a{wsvQd!H0V>;5$-i^U=}KJG>NMN#0rrz}g1F-Z-OQWXn` zw#61Cl3J}sQ55MQHa&!ZF$V8_+KWKTsaoEPpAh2woJ6V7_XsSdq$moEF;rERfS2#9 z*!X4A_#DI~|7qr*M@L8f@pz22HYr<<@5;&wS(cqs6>r~YYpi@epEH?E`XPir0WyF$ zZ{GZU`}XbsXs!EaXJ5pq3(^FJHbeo6SNHxCVR00000NkvXXu0mjf28TWj diff --git a/micropsi_server/static/minecraft/block_textures/Iron_Bars.png b/micropsi_server/static/minecraft/block_textures/Iron_Bars.png deleted file mode 100644 index 862a2efdc1233395ebb87f75b6f4f0f6d7965ca8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1302 zcmV+x1?l>UP)P000>X1^@s6#OZ}&000EtNkl3_-W_EY>r@P(VwgD^}ewu;>iY;7tVPZ9H4J}(@q$GldkQg+nF`A&4#9N5* zLZg5QULY|NO>8fW0)o+SBWzmFcwr~!5Cn^WH|y!J>^79OZnzDOw@-Ppc%ZLBf*DuJJQ zspr{y?H!%hCMG5x3qltjG^Sf%m^RK|`2E9%SnQYePjBp^;pFISrJU{U@5}V{_y2c@ zqNo4>o7t>VDiqKCd=9q552TXGmP9Nvsw!%B+qQwsz`(%cLtIt`cWnTG@B7fg8m>-F zL8#Bs2TvJ6tkv_s%66WydKWo z{@G%F+_kF^oa3XR^OnIDnAclTz%cUubg?vCENed~oKA%rSOr_<={-1J;~ zdwVJpjz9>GY&LtE`0mHHZ9zf;06Y=|_X#E^$0sjNOnze776L)F?oG80LVVa(6`Usr z389F^8lb4Pb*ktlgfHPq0sxSbKuQWq2-3~VP%f8Y7{)`y1%Np&a(8l9iql0)2mwNB z5LJu91A%SVrcV+HNdS-lC{^J5K78pf4WUX9gi=)W@`+$5@B?=lm}TB~Nuekz6txyW zEE+{96oP~g$JXHq_dzw+#}$o65fULNR6#zU2Y2lGZ;whiD3PGFR$Eh36O5{XVsRQ( zU5Dei;4WX~klz^1A#w&_~uogxlk$`P8I1Y#B*T+@o z#P=l}&QUfUBvKTCKmf}9QbJl3%oX#wTV@r$l!%74ZOK%6om(uy7h-8AuEW3`2apnq z6!1VHNMJsmNF+LcmkSTfW|)~NM}))%LI?;UOFLnf0o!(9nieFb@P!W{JOF?e4x=#< zhsSHVmr5m2NemdE3lVzC%3#$f0bD8h&OfX3JT882VHjPc1ou5I73^Vsqg%O^TII@(>I z?ECx9oxQqIX%w!rj=AEiS-nbK&NnAwQA|%4r$U;RQz-3KC^?ZY6mBmX*PPz}-~dKO zM#cicV7JS~P%4!yB;pM_4AX+=`Q>yvz16m?-L_EzArx9DgiwGYSeMNrkU71%e?JZ% zIy9yVvpW@!4y|9;G2YtRIzkD(5!AF5GgTMTmk`2(#a!rS{njrDnbYUTzs8Z{C&wry z-LarDw6Zx~XnShKVZj}^E^Fd0(>%_B2TWJ}P(P&JduuONKKWE87)u|#_2*w*H*Z|` zr>3T;WwYK~E_e2Sfz0X1(W79NnK^&v^cz>MUO#Cwb}E<4{cFVj0l_|5K?e0R5dZ)H M07*qoM6N<$f`k2G@Bjb+ diff --git a/micropsi_server/static/minecraft/block_textures/Iron_Door.png b/micropsi_server/static/minecraft/block_textures/Iron_Door.png deleted file mode 100644 index 94fa4fe9ee829c18b792f5d5fb30c3d894985549..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 877 zcmV-z1CsoSP)P000>X1^@s6#OZ}&0009tNkl_9l?MOW8q!1YkPNRJu@jTwk{{#?!`CKYKyl!zj-rnW+)O`KA+ET zZ*PCKv9a-`=Xsa6x3||x-@mKTq+qF3`gCPw<@;i>IJLUE`fPD=vC?cdU)E~1|Ai$Y zqobpbrlzL0>h(I}IL6r6Sb1h<=2^L1uK2$Hve9U~7nX=*GMPuy)6-j>PUnSXSs%Nu zYxS_%+1WnkI8Gu)l7fse0Kmz~$rlF)2Ma+ETuLd?Y&KzxL7`B1vb?L|1iH~^!1Fu+$PSEHmIVMP7K=}Jc6R4=kJi`Me_C5x`?*vqeJ6w%@;nbg z5TtdHoCyFJV=y^6`J`H{o`zv~d47KW?fCfkW81brVT>V@$@DWG7$JmUX=&-}-QC^4 z^7(wZTCKj=+uM8Wy6$h5Wr5Pf0tJvVU9Rh*)oMWq0nT}e5Q5Fk%^y!sPrsp*!Wfg9 zMJh5hGz88$lv2=IgE0m|NU2h({Lt-o;ks@rkcwopS>$p#Xsw~OhLjSK0 z#-Q8n!WaY2IVh!we!uf5ioiJs zV+@qi_c~Hi5g|lBquz+)I0ok&*=!bC>wb9;j1WRV2+9=%t+k!YUUrtzee00000NkvXXu0mjf DBL$r# diff --git a/micropsi_server/static/minecraft/block_textures/Iron_Ore.png b/micropsi_server/static/minecraft/block_textures/Iron_Ore.png deleted file mode 100644 index f941e2f6294bf7a4f9baae78b0aa253240d897df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1487 zcmV;=1u*)FP)P000>X1^@s6#OZ}&000G+NklQ7ujAI+@!JZk~>ZmsoOYwh(=e4&(5`s&rIzrJSFT*SbK}O1%_K?I0a~pV2L}iJhYufa_51z1-h20dL6lN@V`Jma z&6_tjl~U`SP6ul(bzO6&C5bwv4wC6~+P{DQ{??;MkM4#LJ`4K<@-Fi0*ROB>Vr6lC zR62%L;O9SF;N`&xB_+Fq5m{>JwGymuk>@#8RrMb{c(C>4$&*hq6W?{dv9Ynau&}V6 zrYTKRqkMx`DU!gCW;Lse9d?H$e;-tw??{TE8CQ;Ow~KR*-QC^(Jy{ni-ss~96f2$p6vEfttpnA8Dd3`W3GVp%`m!I*?$ zX=zJKsujo68f_Ae%E+X$oSdBS{Q2|6$z;+Dhr>VVcjfZlGZ+jILZDYyF(v_OoOAqi zsm1)63;gxfA@vZN+Je-KMVrI7C0Uj+91fXGCcJp@g2`lp)|wb20Pi7nU6W-QS(>u9 zw@16(Cd)Fq-7cdlpx(ab$6d*CC-7pdDMG|rOS|1>I2`iy=~JqzBF4Z%TTwWWQhp4X zo13F33T7xds!tyWg8{u>kCRS^qh1ed8(xh)iGmOmdwY8v9v+hCIb~U*wMG)D16Y3= z#CwSMo~CKgTJt7vaIQuKN1CQsYuVr5=iuOgrKKgHLs^zQd-jacXoN8aYb~kKRE<52 z-Wc!}t@Q^;mSspO3ErcWL<-IFY)Vlz3`>>N?KfRGCBEGr8MJ12q3WPZSwSj1#u!2fBuVn|pRCRsULB1YAUN9=%%`yL1dH7^b%2+n5+6KDDUu{1 zgh1^)%e@&I+mKs9QCUXL6Qw3e5`;LNb@>kR&B2tiad>HXHF69JkCc+xB(mHJNTo>A z4Cfq5DWsINQ$sfuC<4JdTm+S5??)np@FA0`s?f#&hUjX95Ga$d*w*a4J)xT!luF6- z{C&IkW-?}s;Gf3@E(+Gp%`zRra6ytKk{Ru(yr)tcrPK#V2m$XsOS2i$TlVt?r6jYx z1%@t=B%BWF%nVv@g*lx4}Zs?l0gHXi@)040H{vD9dsb9nFj2L}hgtE%c- z1(4_Yw>vvKf2^wNZ&|Co-syA}opa=6&4Te1wIx-8GDt?FG0r(c2q+OjoHnS75*sCX zKJD-C?|;3$z5R7rmjC$ZXuXq^Qm?J8-C0{(+sv|Ty_IOn#?exN!bXOpF?pWTNhC%_ zj?3_Fy8EwRzup=Q26tnOpTCTsgI&CM@y_MTmp8L4Tle0RrYV!jgzqSv88jYx`s{iKAo2}t+_+Op>&yrH=m6esR pu3fwK=d)+eekFv^-*0uQe*pzDBV^{-7$g7y002ovPDHLkV1f+R)v^Ep diff --git a/micropsi_server/static/minecraft/block_textures/Iron_Trapdoor.png b/micropsi_server/static/minecraft/block_textures/Iron_Trapdoor.png deleted file mode 100644 index 8b3e0694904d2b6e79a9869e4ef0662449360a02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1097 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 LNkvXXu0mjftht;Y diff --git a/micropsi_server/static/minecraft/block_textures/Jungle_Door.png b/micropsi_server/static/minecraft/block_textures/Jungle_Door.png deleted file mode 100644 index a4fe724a0b13cf6e559921178692d36609e05ee3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 927 zcmV;Q17Q4#P)P000>X1^@s6#OZ}&000AKNkl<^=Z73i}G>8g1_x=OuBBV4lDXD2Kp+H3% zOB6`ykdP1(QiLL~f-Kv@UT4>P@9urglft5);M%Oea%S}X<~wJOhND8QhHQWO?rWdF zeW(9fR+<;%X?9d~{=dYlHgB)=Km7Q;!B-ngox5+Xb+>y9%|V=IdvRL+FIXj{7j-tS z*Vxv{OrzFZz{YZW{hiy3+c$fiL6VhwXS4iLuu4b}h>f*Z+gqkAkR%Bb!@aPQ4<_4ki+kv{_^nXlcUk>$1KkqtfH8vg{$K0kS@}aK0yyW9l?IHl`5_)a zsT2aM-e2jqABljv01&6eQ_cfODN(OUIBVyJgaHS<2Xx-Je{c1{{mr#MZ+6>{taIIQ zl7isD87@px+4z@}8I1EFV&KHFx)?Q=qUM9)Wa_TBniypY#0j`cxmwKzLLopz(8j=f zfD<9QCW(lFF$S#-j4|^=Y5{?mM_%NRp@h;JBH++L1KN2QV_;o%KQ4rL07L}u0pbCM zQ~gDu?J&y~j^i9sO4Py-oGNWDnv-)#C<5eVf#-+A-QV_)pZD7#mRli4lLSdtz{6MB zR5zXo(Appr5}tWTA;5{w)4`$LY@kpM#=7~FIOk!kLS7ai;;_yl6atI^3=G5=0?rTy zm3OU%49{j5B{?`T%(4OippAvK7DNQ+JpgvKC){0Vq2GN4X8hu99RlJ{up8Cdva)+QNJPruQCF5IAvI zYoU$3uw0ctV_K+#7k`a*a%Jj`kZ&$U%}>1dD^at-toL}C6i|*~G)W$(h5mgm2mol~ z;?p?Y-9MS^VA$ULp#svb-aWou1PRlDm{*kdR9#4zigJHarmbxBrY=St? zmBHcg?CV0AKQ8NaY1PBv)}4OzWw&!}i(N3(e*+oXnWJ83cwqnl002ovPDHLkV1i{E BwVD6` diff --git a/micropsi_server/static/minecraft/block_textures/Jungle_Fence.png b/micropsi_server/static/minecraft/block_textures/Jungle_Fence.png deleted file mode 100644 index 99d2bde491bb1ec22e3b426a4b7987e42351af95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1240 zcmV;}1Sk86P)P000>X1^@s6#OZ}&000D^Nkl>Mou3NVrE<~6fGZG!o?s9+UeD~aQ&%L+6 z|6tpu8uvf_*umF#Zff5)H+0VVkrXTEmGuPQK0R^AGmqc%s!`I8EIA>L=dqzx9ej0u z_T&d&UwZ8?ow^=Kr(ql(xZ;Q#L`=7gW_ENuN2kFt3I_ZW*}1vXe&W!MXLrqXpOcwlp85GQUX)qhxlt6pzjS#@R!aseu!|dEcHb z`+F0{>8;&5OHRO&9T3J6KN94t+HVX}Eb63LjvIswTtO&e(kv$oME}OXKDA)`Z{2nZzV%x;CUY)gcIdMqYsJ3jz_- zHmmI2+I?~BRC78`;QX}(t}TsdR1KykOr(@_8U{%clVur6k`ju4Xhv00s2gP}B^?w8 zRaL0yMY5df&wso^AW|yjB8_SV@-$OmwpuRsA|XiA0wXUVNi&2T&t&ZGij#yaOVJ7{ zl~Rd3&xvBeuXD@jdWmkkPC*6B^FRLK_x{158=q>|4Vu*wwlB~M8qHee&qb`2$<_HK zCL0C{kYylbf){2OWt~z{V_-WU|2A)(TpR`818mthkHjtT$<%Sdr1uD6^He}PJ zNwZdG(X#0;yXbn6wpn4pvcI}GyL_T=x!kS`I_k|EzEM zZ^TJDSQlxfRm(+l-?r`xd$)8yoTyjsSJwHX0r&$f+DQFh@O01s00001TXuEP)P000>X1^@s6#OZ}&000D+NklF0YU> zfN=pS)ssQapQzR@Uwm=CGVatH9TzK`ZLG8hX!b`?&|sv}uuKaxGXhNScqgwhex2*6g> zgP0;tIFwST9xTE%47kG)lv2e`|h#A~rgshd0e_iWOCtP-4IZfVWEP+&V>wZ##GLDWH;PzSvc{_v2@dBij zh?5vXipN9B#XO1vu(Kf&M)_nRS9~fRyVs)0LB~}Im?6)93iFf1Ii7< z_-3k=YTspO-8haj3p&>P0kG!5-R01XP000>X1^@s6#OZ}&000H4NklD)3>d)J3svOAL?hhD?aJKuYT|~*=)>j{PW(Ut+myq^(1mRn@llA_X5w| z?k=wEZ2tT=9}7Dd^5(a`gfH#4*Vk6Jl*-o!la##Bl$D{C1avzse9y;ogC5k|qv>ep z`@ekWsjzb)Ki>RGLzil5s*>lH7FcM<#Gy~23Z}W?Y&>CQp-rh3zAH(hq}NEA+ZO(3 zvX6z-bv-Rqj#3KW@wmLQz@_DRW_eDb6s55oo{qSBafLV#_+Ef?oSx@;AAvdNL9TRL zzL4T4o+BBiIqg=1w9xDy_tCm!t{D*pKAmRB&4))!W{NQI2|Q;>2(dXBjW^f2joW{_ zeR$tJ4-y4FgVB^iDdt-dWvQ^%Vy$JI*Yxim({4n(boDu2y6PZ=g2BDePX+}t~6zTKn|2RKr&+-WfwryTZ&IF3Ucx=b?-RH#z3 zIM+Z*sO##Zkl+6FJ6&U{wO|G?DsA}Wy~w1Ovj_$ zBVtbw1!FEOb&0Mx^oJ?$-q~ZW8PaKlT&z|U#jzgYjNXJ3fE)RSATzkGG2|bcHWPNRwJCBaZ3e7^hh3CR&)>gUx!YViS z``kU~Go0qQj!U}{;5rUkKUrZ1V6~HcQwsTV=zEyD!VdyUQ!`ErthI!m!-a)5kuMO! z;plXVF$T}IxUwcM4W$vd4&+J`crLlB$dtZuH2gr<k`%y z`I2U`z$DGMvv)+h5z>e}DpOHth@yxaxAy6F8X#TX-#sGDa*`;f)QY04P+Adq4&6?Z z+8VI-$)Aj8Ibq;YsDhwkJ^(1f+Ja+5*A^#doNb{0XR|KAml#;=CiqcijTDuMG{NusNvo+&aKYLlaj@*!f zt!5mrcjsH!+Az*aj?X42tx;t~QJNmGU0b{J-oyU?*YW?Yagr7rp6hH~?9Q(X>9BWl z#w;s(#?;$YZ9mjpS*hgk`zL06qR{>R_M2Rik9>8Z+!&cq(nuQ-~a#s07*qo IM6N<$f(r)lcmMzZ diff --git a/micropsi_server/static/minecraft/block_textures/Ladders.png b/micropsi_server/static/minecraft/block_textures/Ladders.png deleted file mode 100644 index 918a9214fdaeb56de2039e4ecb94a946070a4ac4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1247 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 bdUyQ`-h~0n7t4mY00000NkvXXu0mjfNQinn diff --git a/micropsi_server/static/minecraft/block_textures/Lapis_Lazuli_Ore.png b/micropsi_server/static/minecraft/block_textures/Lapis_Lazuli_Ore.png deleted file mode 100644 index 8edf9a8e079ac9ed9c3817467d491879f9534473..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1581 zcmV+|2GaS7P)P000>X1^@s6#OZ}&000H_Nkl4*6q@O@5ue<2BU!Vx! zFHpeU87T~-Mp3)8WBH=WA}NvLeMrvCIWyBmk}5_L#O=F(pC9m^13VV*IF@Cp-QC@f zZ{EE52f%bXoo88=rSGyU-X(l{dwc)JjT;BbcDI`0BJ1T5=Vy)Dg9i`p)M~Z6Ns^fV z3$ZLq?d^w>YC6xnZTM-qLpTX)_wV1o^YG!ryJ?z!FJ^sT z`0noR$G30a{S7*-~XIrhqHPwE7lH<%CHD&-!b8+1_BY_$50_#~gOcTrOFZ zoGvpnGZ)c^w+2!YJ=Zwah zm%SAh3T=ciR0=8oI#E;?%B&R!jK?8=d%Db(l`gBL34_6)cJJQ3-yaCz?twWS0sm|GG z4N9pC#Pd9)l*Bs0mI?38v^bqOoQ+FdnQNns&3GE4bsb|2?RFb!yL5Dka2VrQ29r1} z7853^M=lPKQeup`K(yBU^GykXg+?(p@4%_owU+FQOj))Dkrap7d zghbfPItD2QG@vO zG?r!IxfUz=E}j^ZiHv@j$FdX!H>2VNBuRph3L(VX5C6fX7UK}gMH}BaWTI`Plr#q} z&Di03Il{ITqtOs$StLMW6y2yyUi69e1f4*j3uG9&5fsz(0tv$qW!uc!8Qnoj!4ovc zODL<(e11qQTrA5X2m%12C?bdo^rj9gex2CZgjtTxG|yaqf=(rf1pW2zk^rvjG6@s@ zepui|vrM5-z#`_bv&6qgvxrO(MG^UY9sr#ch?9)fxjs1+Gtn-M-V6f6!b}RnkZQ1P z`vMU{P%J2}ulAUq88DuvY*e}gV~0stU>G@gUJfZGN-3Isn<$o~X^Ien1%F7@*Q{3~ z5~ESV@Low^S=I%TrYT91kk7eH<06M|a*(C`>gtfy@{ntbhNpF#Hv@%Iib^5iQgOuD zz{hbM9F;)|uiH6Hk}>0E*dioJk_)6>uYW!q4r@^q5y~m&u}f#H_~P*#X{L}_gtVX^ z8jLZ_co{*ICkQe+ebCx)-c=Ocki6%RrYXjlTDRN%9KJ5CAP8O^A0PiI48x}@Rkc;A zRTwtpO6 z@m6VlGg3;mxw*N&xw(1ZdES=E94ul+k>Gr&D5*1oAV3JgN!KSzQ!bZ66mhNDY~E?N z+jp}pd#i?ju;ZnaYHe+8|Jt={2cG9`B}szox{O9627>`wYX;MdV$P~H>Tm9}TCMMd z|0wcpY=4NGa9&`ueAPdwYMWR;!;1 fA=Hm=eKr397UvMcE6#c;00000NkvXXu0mjf!d(Z7 diff --git a/micropsi_server/static/minecraft/block_textures/Lava.png b/micropsi_server/static/minecraft/block_textures/Lava.png deleted file mode 100644 index b004c09bc8e71df5b383a80c10d6baca7aad6d26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1382 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 diff --git a/micropsi_server/static/minecraft/block_textures/Lever.png b/micropsi_server/static/minecraft/block_textures/Lever.png deleted file mode 100644 index a7abf8d025d6d38f0d94747193ede55149d484f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1320 zcmV+@1=sqCP)P000>X1^@s6#OZ}&000ETb=9dZslKX@xyYEQE1(v8_14ESBR_4j~9)oSXu|J_KTjF1a{|Mc}aK z;6ofpuDOJTg}r;@#o2>=$wj^_u}ywfmSl|_X=Zx5XZo+YDhFpKYkPyod7+`3`gwTY z@2hITm$b35QM1}}S+1?E{YVJ01#Dluc=5yY=g+_MI*1s*+`M^n$r$rjV@zc@9Ab>&@#Dut zQS>{d)GvMC|GrY{S5nHAJkRO(`)I8JjN>>TDWyKRfB*i&*E#vMl(O=lh4lM2!s#-!f~843kwVD7cN|Q_uRR2pFeo; z;P8dGmY0_ag5am^cKbKgYSlkHJVXeA(K$)?3$j9!W*R@3XYuW2!f-&nUPnsFa5$vd zY!Up^;mLX%{gyeNsH z0fXKljkzYuaVM_IvYbGrl#E6r5k=9NG)@1hcuBo*a1_+UAEK~Zw`Km`42h6JQ1u+L zq9oTPT5I~KNr6f&Lc6WE_cDo26u-4vr1p?5E(y|{VpEyGQWt0}* zt5w|D(pr=fR6Lig&{zw?q%0g;A_xLbpFT~e)1lMp5JeGEO02aQWA40sPF~RN!Qf7& z%{TYD>Ekd?5kfHIIlR^Msm{3gHIKEmHBO#9IcWj_-}jlFoyA&#!|ih?}PUk@2Cn)})!$G-jzdP9xY zB}xihrTFZ#KeN5PjWGsmEkX$5IHuq4V~hd7nFdm;)v(qwKR?g>TZ5fd==FL4D5X$JO$RB<64!M(d-g2D;gB#4S^4&R9PIuL;WbFp6l2Wf z0gzJSIM27ADy}q58I4BtdOaM+p;D<(sZ==m?zeCp2c;AT2L~J-9kH{sGXWW6D9ds> zh?J7DEP43wA&(wCdf`fGx7+x>Pg$14aXgtnZef_RCiQw9tu@wK(lq7p@Q`Y?isyN_ zuFK-$B2r3>F$6(Czu(7NOQX@CUawCFab5QX`{Ovqvex2x9zhV$>-A_f8qCejy;)6G zR#sSEUZz&7VT>WqbDlhTLKud?^O`+-_Ke|h$kEXe&1Ms&6j_!LhT(Khgb;D7)#CW^ z&}y}qnVDfU z8jP000>X1^@s6#OZ}&0009LNklu0xh(5(9k_=7x#Z8$PX~kz3>l-7q1;VRa1w;PKS0& zh8D8K(6+?Sdpb~-C5uv=p*;WrlIK0|dp_<5{12wue%g^80>kC!+hm;lx{Kfe_A(hK zmk%*|N9-~1dilKiK%(o|<2JxnC3a*l{4Wmr{-@CZpnjjH`CrLf;QM5p{JDnv3OHZK zmVe*=Q@*>IJjAwDGDI;TR1hk}CzgPrcns~x9xVlql&^=nXND@!144xt`6y2hXj&Jn z6SlT1&*r4F3F&OYoEz#d4S1Xbr%TAG^7VQ2?Ps_rnshd4?c0$TI+L(u6$7sXy09z^NVC7j2b%2ORs)dgzXUN}v*yC_K-g zy9V7gu^ri=1jNn=&)&S3!d;plX^3=kir%iAj)}(bo{B<8UE1qY6 zOfOttBRz$lmPq6wk=I7EvQ`lSwF(q(Z2@{9T>-D@!5v23)=1=G`~c$zR5}IRDy((z z;_LeiJ8O`ohm(|Q{1wDy@4vg-3nB%3!ckp~|s(ms=}#vS_Gj@+THO32-|?_)*KYAajg2N>g{ zYC*}GqY}vWi#~ery@QxeH`@BIa)(ZMY7+rLrt VKDmw;7SjL#002ovPDHLkV1mvfj;a6v diff --git a/micropsi_server/static/minecraft/block_textures/Melon_(Block).png b/micropsi_server/static/minecraft/block_textures/Melon_(Block).png deleted file mode 100644 index e90a9a5b0e3f152f1b19f1eadd4f32b287a52b75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1422 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 diff --git a/micropsi_server/static/minecraft/block_textures/Mycelium.png b/micropsi_server/static/minecraft/block_textures/Mycelium.png deleted file mode 100644 index aafcd0873ef43bc3810557d616cd8c7f00ebc7f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1519 zcmVP000>X1^@s6#OZ}&000HHNklXf!J}VG#)l z1X3aqSY?)3vSP<32q8Myk?jyXxb2>ve%!v#^J3vd6JZ0U)~EE7FH5{I3VsUU^l%Fy1*_wDQP0d!ThqEWskC#x%!YeeUxo zKl<5E|10c2LcagqYf>q3sEs)aWpLm{OdvE>*J6Q4N}iisCRV|8wrP*+u046{cfWor z>keCEjshhQtg}cJ6R8+{ixl9z$BT#{j0jvuV=RdZrrzme1Sh}v?Qh&u zA=h4cRiIKK0 zvnRiL=P^tG-2C44Zw~Xpo3o-i@JOuHC<#%N;bnwv*7TDQ=PUw2Wi*|!NR{Bd1@FnD z0FOrsu--Ar`qQ%5{_u@=-uk@)7>_5fS55mZ6=z7%plwGS26U#wqfls!v-F~f#!9sJ zD5)^6qcb&~>2M-MJ5Lz&Sgh7eKfJZ`!JXU9gT>i<0YDVxEVng_^*shz!j+v}Odzq= zV01-eJMtu^vH~kZ+PWZ*L#%KpsR#o}q#T=K!{2WIm8vS4&mSOF1V8}L8jm&xV_Q~R zO(-R;7i9U6B!DCg5ze#iYLuwC)a#KaJ){U()+Gelq5Mo+G0H%w~@v zd0;8Dz)Ns=R&7IPEUV3mQI@ecnJ~z55^LxzXq9rh*|1zKXo`Y|%X6GH^umzEa!!9R z!4k4v&MuUEaO-_`_Fkac%;*hwsT_nVV3K9jT66E8r;PHP(O`&{32iy&#;4Mv{%t*0J53ktgZ`(oYoYp1Sw90T;6|?e6)w~Agsokj(WSKYfHL%gKjIN5JZ8(ScmuiqFn%2 z_eKVYl>|N?h)DeDzp1{8H{!*iWU9A1m_G*QxU`|+2ArxH#pN0NJSPa#{C4Z zqQ_g}Qzag7&N0klbXU<-1icku6h6$oON4SQ%m}7K9l20CQ1l!@P#(6~&2tp4}tM ze;DC`b=i=Kj;s6Qi*_j`8AJ)KHVm_vI0&en*(LD^_#%Yx^x zzJyYeEQ#nPA)_oNO+thK*Vgz?ibFu%>glFz-UUzqf4=+h?zdn5(k~lrrXK&5TFafH zY;ewzB@s<$5PQRCqkkeiqy%vSR%|G3l-Fd1re&^*c%5Z$; zFtNpvsmlZF9A~SFD?0-&4N^Xut@y`$O<$7qr@t#1UyEr^dSDwlgR1-P000>X1^@s6#OZ}&000D#Nkll>fBN4!w~?P!&ea zSOgEP5)q6!u}+1t6!xuh=zX{E_4j{&|L#o!7<2xEoBtL-Mc51jqh=1RkyP;roIrK4 zXEvina8?RK%B)gm-x`xOnj6(R_jUUD)Y>%l_UHU)*D4ntdgY;4HhDkkPo4OU(|g@sGP(a!Mo-vMK;4h`X^a14)(pX=1X*Di>aiYhJB4 zs3snIP8SWcPyN&$|`5>>Jif15fRpW)V+dnapROqn3?_>q!+?4IJnstjB_d(OVjJj^pyjOIqMm4&ArNqxxYwp{8N|ZIRm;x4og)^kgSy`C{R>+9MLC8 z068T_g(8VVU+hp7iYAgOpQVvQ@2ChCPBViD`lBjMh%74osOE!R9!${@6$D5s%kA4t z1XSybp#dV$&4HkY5wWP%5Rxd(?P#a}w~}=!By>s2;-%w@GD*zd>28E5EtdM2J4uyI zE=$4)LY7CER2hz~euB)_+2+EF^%>PVhgK0;26RqB1Pqc;5n32Ul`*AbLoCqdMjT(f ziqOocZj0prBJy1XyQ#NjV=fd`su`2@(`FGNiy|WUv5TrNU8SOo$J-xTqnhmm`R)Yy z{_ggT!B@by0lO9f2xDF*tcsl4__3g~*uTzIgsp z=iaY+`1PRaa+NZr6g-?mt4zJq%^33|+~^*7#BNOKCgAlC`}?PV#*gECv(;5fS6S0_ zbH6MpQB>L2iXH24?4r}Rdp}Q~_QyY|%=?vlTxZqGYK8(q>_BdK;^sd&&No}V*gpH_ ne6#(_tM%D8uQpr#)U*Equ9x)u^?eMs00000NkvXXu0mjf^tM-0 diff --git a/micropsi_server/static/minecraft/block_textures/Nether_Brick_Fence.png b/micropsi_server/static/minecraft/block_textures/Nether_Brick_Fence.png deleted file mode 100644 index 5f8bda9a26f132b706f745f185b5e7b846667fa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1018 zcmVP000>X1^@s6#OZ}&000BQNklEku zBFt*V)@*lUn)BQH{o>;S1~IOm8?U{VIAACDux!XGAR=@zVOGoILx!nYe{y{K9T2^{ zka|kYkvJfPh)9fu)`#}|XUI4;lR->ZyVP6}_(W8}Ob}C;8e)tnu26Rn{CEffyxxs2 zbSb_1=WP0wy7AfJ=*mVnzM54gvr@2-f`$SFhGjkgbw^Q!xkKCzp82nx8?IewQ*&(? zBH?k}>CI>_n;s$g7eXiwt;t*l@Bobr28=_Z1kqR+giDadOhcV&r#SGKDfU4r| ze1Js!~uZ`y(|-3O+IQvyt6iL zZvwQrQwoX-iU=Hzz>Giu6C*Jp(lHP*g%crhfS94^QrmGe_;78|o-eAH*9~&U1~Cxm7)Wo500$8P?oeNTStR~( zUdnqrra$fXFQ({4z|3elVD1He$j@U%u5mL!7ZcPSy$T3~CPY-TZ|ea70A!JGc1!+1 zRJR?DtcoJ?{cg!`9vMkdMP$Y(HLwtoRbcMGfq{45)a~B1^dB!O-%xAcnw5N8%x)j_ zTOBKV)j{{A+LP;G2OkEz+s{8eYJGJI6VR)mafZX8W>7mkibt^(NFqiP0~S(YzHBsN zf2g@0)m9RpRbX(mF`^3*Oa!JA-8BFJVgCz(7(^En^H9u?O<`_7WR)B0zI|fa11j;U ov)$N;+LL$y#N3J07*qoM6N<$g4IOTs{jB1 diff --git a/micropsi_server/static/minecraft/block_textures/Nether_Brick_Stairs.png b/micropsi_server/static/minecraft/block_textures/Nether_Brick_Stairs.png deleted file mode 100644 index fd95d3715a98d9661164583ddcc3bb2c18b16ee5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1186 zcmV;T1YP@yP)P000>X1^@s6#OZ}&000DONkl!5r)62bD8OxSrX(fmIizQEWpVEiX8YwC@OcPOrgWY=62-A5OXJd4Ua=d0wh5e z#hsn$?sKVfFvBG%a4lLVuI{fc|6djO$oKp6OMSXpfA(Zp|8<>m9a8?+zRdnH-^$Mg z{P9=6)z@`?uE_Ne!qqY+8qN>nek-CkBJ%Rz|M|{-5#-m8pRi8(MHanQk*nKUh$5&c zLyQb5Gq=X;>98GJy#Zc+dw2Um*n^N~XOA|D?1Cx_jcjsekrKOkW~zl40#Otd;m|6z zw{121AnboaqUa95%s5*tS%pN3k&qHU-0j$vnMZlS5FU|r31|qv5 zW@3npwJ_JpCTCKNEHu*H+1G-*vxtEqBo--gsD-h$?V*(~zkc)jReT5nU=Fo+=GIt* zK+KujGGVi`&Py(aH9?o)&h0!gw?^)jZpK}iujbZ110IG98fk7ET1DhovI9{ANtD_f zU+;co$eC40Y;tC-m2Y;p#1IG~L{$JDgfuhs6c{yds0GKtA*IX^k8K<4%$N5-$8!!_{pCY z{+>lwt6{}xo!vYV^(f0{!v+U#%S>&RW{pLNhy)&GrSwjhfar0Cr8h)`=JpO!dizti zzM8eN$qRxi=gT$wGBaDFd8fORV`P&u`&KBu<8D; z$UQQ|#N~R!)GGU08SBix6oM*A13{E(9S6GG`;`nS5m5GZ#yR%VFk%QSBrscJ*CwV` zIkd`LE4z8b5mqrBf9Q_O(S$*j9OJ!DBEZ}mo0xbqtSP-Q)`G}!cg7&NLlQ+D-i!y_ zog5;~40FRyrhV&p6`}S{vHs4CBJ!0x+o_#aLrf$+N^|P%K3GJEqKF7~Qc+Z$k}8K%=%+cHRCIrK@8;?MyYcT;X6?C|Uq{ue(mM(`z6H7A z1DXHgcfQ)_<>u_g`D*izi(&oZVzqhjO#Jly7YgREy{_qxzyJUM07*qoM6N<$g78U5 AhX4Qo diff --git a/micropsi_server/static/minecraft/block_textures/Nether_Quartz_Ore.png b/micropsi_server/static/minecraft/block_textures/Nether_Quartz_Ore.png deleted file mode 100644 index b948b9d8ee86e9818edd3053789ab1cf84c2fb94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1755 zcmV<11|<23P)P000>X1^@s6#OZ}&000J}Nkl zTWp(W9maqDY=586$JnuB*KN|A<~B(Y>om0mDuK8_Ljx(@{VqX6+btI;$}Km5#Kk5} z93(F276d|wvQo$pRq5yaX6oG(0T4=v&M4E__Flgtve4=KN28`_V!q5K7xk7<9W# zB;uKKbBV>}o7+pjynWmFvKiJdI{3jWuLxfahpz~O!PTB7{_xWImm{~@FAE z>zPPbwO;8D^ea)Pvo;Y5J>F?F%oiegZE8wz+HCV-muq3OR!h&Mr3i?JXMC>7Ki+vq&y4ZD3nVK4@W7ND#Q|TY!*;-o!oP-G*LAjYS*9nFoR9|``25_w{xOd$ z)%1QYe+77E^5B!ZluI?d2zdP>f7;CA6ReDmL`cn@VJHx$+Lm$qJgnaOg!8jAB+_TG zdENB83gbIFe0+VG)oLBT(kEZ5l5#odcNIQc*TeN~#-dSlUFY9hS)v{ltteK0sC@gx%H7C3c! zl+435teVE=(;TARj;3j}9S;109YyZaZp+k*6$D8l6$ulYNwaeIJ|3rw_=yC$-F*Un zKO>1aa=8Q@<(Y}qV&RjGE#k!@VW*pdHehP@Ea%db9M&3?3uSt>8oPJy^W>n40%(T8 zR5C%MR6-l*BqtLT_p|hs9x`AIpPl)D`o^%}Wsp6S^c4i2gmN(UrIV#H#@tlU_}WtV{NGZU;<{7xI; z>65rdlBoKC%{vdt?&fG79^yejQC0GSfTz>JYOyjpH9>ak3F()nktC7)L5;B2iPz_4 z`|$>f+@U8)$0QdgCpl0ROkaRpHp|A7eYCDZR1)bMCLX_sp~xxDdEG4keO>NHXp5nQ$p7?Mo*jDfDv z?sSO;{l_FiB=Dh8Zd}UN>uH~^GclSXnI30zZ;$+bmZO}&{q+qz)fzUJ3%{X*Tja3S zM(y<&pBks0&5?3DDevr1*9;JZ%&A1|!%U%coF_3+6kJZ{d_XW4S_32PmF$R`K`Io& z>i5wRMV@YKVmchS{9gJ%HD4ec4&m{6DCf&~oOVQ;HB&h3ED9FOQc0E#_`mp-b=_?C z`|GO7y93Br)xnwMNG#$GIPt|tFdPz%!xlc53(*8s=@B+fy6S*RvCMcPo)PTATh*5G zqe8pACadaaQT&BP;+x5&Fm*aHzgey>7_yx1SVT-96DX)Y!M zfu#r4>gN`U=eCvE;b9@-mF5f0&Vp&Nq+^jVfn=PU|GJg&*=>uJzPi-av@dNd&o%i} xlcA6h84Au%rzhS#GWhk4zus6ne9rJs^*=Zz5-L&Vero^#002ovPDHLkV1hcRUO4~& diff --git a/micropsi_server/static/minecraft/block_textures/Nether_Wart.png b/micropsi_server/static/minecraft/block_textures/Nether_Wart.png deleted file mode 100644 index d6bee99053bf90867b4a1c90058cda299ef92c3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1120 zcmV-m1fTnfP)P000#T1^@s6vnxdy000ChNklh9^e?5xLTZL_wJBg?W$NXW?uEeOm5V2K2g?gM~uPJ)DmUjQUXEFmF8 z00|Zuwg|vl+1R_QHJa`1>F%lPA=ugRUK~?N>l~faU;Teo9rz!XI+^(N?Aiywt;5)b zfD7R;{NzqsKRHtS1#tN=Zk;710r=-mTYr3*&yE5%2BVv~F}I2=zj>IO3^Yb7rftVg zT|KxB_s@r4613`!BeV z40cvi&FW+4u(V4=0Qk3Uuy#%}n~_pN5JUv)-LhuUK-YFS=Rhlz2(Fr9B8-ZX%FUM` zz34zG#keSt7};wYVvMYf$E+45D#Qg4e2R3zgHps8=@0vyJ2#=Q4u{}K#FS8g9ET`G zgw-M^7r|PK0BdI^tdu=;^vebxBb~Ff-jNUn;}MhdXDORGwwKcwXo7RVrh;(9t^*}c zXAPaTOPLRRtH7yX`k)42TR9awRtgmlE@oa7i#gx3D%tkd&AL!@OXnEO}8H=;o*p?|&WBH%B4* zXfTDJW-0#J`oO?Onj)jg3?3K)bD&O0Xoll4 zM(btK3X1hb(e{xuYDiQ%u{s=~^~v8ItPJ2bTm^o(e(8<%X;ZUXRm`f2(9^hHf!7*o z?Xkv}FGk^k7v9nrIkoF0WbwVSpB#+=SLSqOhLqXCoi~~u8BN!MB$U=wT^(sgC0&fnlTgh&Y^G2vee~)O=^T4&lY`Oo z`1vc90N~0Jr4?DPhg`YxTjtD@V6-7c0fctYCn^P)P000>X1^@s6#OZ}&000JBNkl?QcoQs_}NdrW+gy;fMBoL}p7B(AJU4TSHMRzQtY}g`p zR6?p)Ms+0?2qeTsNFYH=l}faeOH<1p&)A;v+|Hai*Z*8tpu`jcjd+*8zF&{t?_0dD z!gFft#trpb8ylONqP#X7jowPrbo)E+ypuf7U-4N3-!KgIXFs~N)$UI2ez>=PD|Fq9 z`@P;)wwT-c?kgLEg~Yu7Zl{y}Uy$!@Y^XPDwJlZ3yVfkdwNx(G9~^ZV`4U@7K5Dh< zZlG=jQM|Q~%?>VAtM?y|M$d#P|NijG+M1fxw9T4n-hMP3t}j)q_`c7auA>6iYc*y< zkS8T?2>$Y?zcR?>Sn|BqmtT76PHtgg`xuDO$A`2M}Ps$S~fB5yE zS-o-vK}@q$!}WbUcSbJ=sQV#UHiI}}u(LxgpXbB<9vLC9L!Tstq7WSVK7N{#H%(At zdQrqQ2u~nH3FDcIjKB>;6ivfuRFKmdeb*)F_0S7hYV~>Q#h6ia0?9X<1Zj#ZCHsQ`MigSx`*tK$MkF)8AmiL6>Qr^QB{1`Wf}zN*(?Lg;_BHm1hIc|c+2y+R48yc zmqQ#Ka_w{jO@Vt49^g9;dPXHtLXspRqpMsx)u5Woax@;(8I1^}WE=#Hf`Go`usA=@ z_^8Kl;{MA`gdlaMaC(XUXhhC&xY1mqXFGh<@3Ycq;K`Kxen@3J!7#E!F$f`OE-WBr zNUc~TNJ5UM4uO=!QsODfiIXp_u9C0T2^530QsH>wa?tCuerbvPoWXE1;kbWHEsC&* z6Qqo=eIHXwDn%WU#LVS0_T!(g{P(E;l(}E@4YaDNXS$i{0J?kuew!9j2a70Z8JQjL2Yl-U+1L?{mM~<=WB` z=Q9~}A(&YfQpP;)44F6%FR!hUH!_sYE|SDCjrkgs%ebK=P7*BFrDU39g+Nmj#;(Vy zQt1S;SS-H%;<>p`sAQF%!!| zm6AdllcWh#DM?keb+OTS`)Qv1t<%}L&}jVhU_5TQp?9@sP3wdqP1EG+!UEBBiYOH5 z&1}MqjvGq6IAv;^!hn50Xz6KsXSrP7{^0odneFAjLZ0Y_MnkO^ zjLq)Yxt%J?x~^%ErbN*4XZD?55N^9s^gpZ7XF{H6wOUmdE0xWaTJ5#&X!O=Jinia~ d-F>d!^$-1JTT4S@!BhYM002ovPDHLkV1l9&M&$qi diff --git a/micropsi_server/static/minecraft/block_textures/Note_Block.png b/micropsi_server/static/minecraft/block_textures/Note_Block.png deleted file mode 100644 index de430511c85eef6bb39738a023953dd11f155a04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1425 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 N002ovPDHLkV1gWWhspo| diff --git a/micropsi_server/static/minecraft/block_textures/Oak_Wood_Planks.png b/micropsi_server/static/minecraft/block_textures/Oak_Wood_Planks.png deleted file mode 100644 index 83a055970bd215b1635f815f75b4a5210389b290..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1530 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 M07*qoM6N<$f{?LOF#rGn diff --git a/micropsi_server/static/minecraft/block_textures/Packed_Ice.png b/micropsi_server/static/minecraft/block_textures/Packed_Ice.png deleted file mode 100644 index e696d496693ca3e8b5e4ef93a54916cdc62e2749..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1137 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 diff --git a/micropsi_server/static/minecraft/block_textures/Portal.png b/micropsi_server/static/minecraft/block_textures/Portal.png deleted file mode 100644 index 817c5da134430cad7c660fe198243cf8c3094529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1090 zcmV-I1ikx-P)P000>X1^@s6#OZ}&000CDNklua#j+wHLKLQJ`fuzxNv>`?zjDX{&o@i zs22pwD0DD-d$_!IZQ<4`Zrl6y_8U2yfxIY7Jz+4!PuFcg_GZW?(ar3&R`h zeH|?35Z* z=;LBIMw=obB(#I0bqjk#I@4JGH@_D!FFVR^zEhs&u-tP7zUp z&O&CCA`?UuK~aQ200hLRFf;4~%B(j8b4O%4vB(00kRvDp1P`>GVLlxP511R~2^A>9%&C~| zNl+ytr|||+#|%`3UMfg9N`@k|7D1p4f;p&!qe`5jT%9HQSwQ~qM<>9ksOl!Q|Q+?A@4w)xuW1jtcafQ27-d9AU++FG9m^p7=}1U563CCu;fk` zT1qd;ERa&7Gb6J=P$3TiRiQ}{^+2Y9h=2$uJWtaW92d;(;b89UjS$LFrtlIz< z5Lc4LoxSSeX&^7dp5A}7e{Ump)#k8weko-6Cume^FB@&DHW&IAmO{``Z8a%5 zDa9Xl4}br%?#2(^Ebl8qMD^0y<<%RDcfVd3yz_~0!W9hs4H#ED!Q$u;Q2+n{07*qo IM6N<$f(_>U3jhEB diff --git a/micropsi_server/static/minecraft/block_textures/Potatoes_(Block).png b/micropsi_server/static/minecraft/block_textures/Potatoes_(Block).png deleted file mode 100644 index f3f8c24e141d171ae42d54f1fcb13f65272ee7d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 894 zcmV-^1A+XBP)P000>X1^@s6#OZ}&0009;NklY}j6o#KW-!uM9oP^YNqBghHv&bu5>FV5b&U-#yUAVvnE^vYW z5mpLiR*I69LixYkG-~-J4uIbjKz_kOqY1!vzKoB{0|nq73-z^gk+u9Ss_{IJ{v~rQ zzeHU?U7!KW&pfRH&Efrh;Aw@of%7D=h>1%(2ITW-PV)-B&r1#^RAZP|s+a`)3S>nQ zMWopk(21I~;(aQ6*Qw=i0gEqhUYYU#@U2?)F~ED=uzv!+=4uQ%pn*$6LL)}wNI*|Q z2nb5VKv#kyP<4f@OyjBEgzxwh0J;6l7~lqeA7Vi+HOg@*e7$!iDDp^gIRSdZ(`S=V zZfdjyc;c+dKCmmH4~A_pE2nwl?C~TK0x)b6tdKwtVLO7q=^$3#(t9%?wDffg%+jv^q!1>rt3Ct#(xfh}Eb$brW8BzzYz zHCbS4vcM1d2auJJ1%4LfyezYFnyqU!4)hj$8$(Hf(IHVI9xi^M>6=JCVqF&LOV|mZ zuEL+s5oV9U%=crC;v=@QHB!3{snDU;c?{vO?rH=ft03iDCCYf}4usijb zcPtYvWulIp*#X|LZQ8nyp0T-_8zGypNt8_H_L^Mk^|)KfEdlrV{9sKR#<;-pT*BhA zW|7Hc05BDUcP$GumBbuPo!SBBZJS7uIMXAH<+3=A1Gy3Ax<1#BLhj-(GyIPJ28D+` UV4CN45&!@I07*qoM6N<$g02jtbN~PV diff --git a/micropsi_server/static/minecraft/block_textures/Powered_Rail_Off.png b/micropsi_server/static/minecraft/block_textures/Powered_Rail_Off.png deleted file mode 100644 index 4da867312ae6fe1de34ca4a49fbdedff56592ae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1232 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 DWOoZG diff --git a/micropsi_server/static/minecraft/block_textures/Pumpkin.png b/micropsi_server/static/minecraft/block_textures/Pumpkin.png deleted file mode 100644 index 829d235862272bb8e867b256b7bf9e986f687c88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1476 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 D9-lzz diff --git a/micropsi_server/static/minecraft/block_textures/Red_Mushroom.png b/micropsi_server/static/minecraft/block_textures/Red_Mushroom.png deleted file mode 100644 index a081c62e0252133592254c8115d6498fed81312a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 579 zcmV-J0=)f+P)P000>X1^@s6#OZ}&0006CNkl8@=UZ3yt2G{rlh)E7<7~2AhS0&qyL#y46 zXnm>GdXyBWUr{3m7}KWw9ccff$TW<|B@JVnUEG_~tI6Oo)8e_@M@?F|bp9S{W8J}OjiCmUaYCj^nfM|*I zT^G;u0&;LLeZpHe(N_xqq)xvMyRt%vxb95k(!XzR#>6X?;Nk)RJC(-Of^(5tEubys z0Fc!h{&BFc-EU2Pye$FXb~VYNF3!>vI64AFju;xTOm2mlFW2 zMw7<7()j2L8T_>eF`bQEN~uajoPjKAsdN_L66pHDA<%1|&_Y|Do*+Zp_Wk6l{ecC< zr)%WKzrTIwBjIHP000>X1^@s6#OZ}&000D|Nkl}v`#0r5FnTf=AfNwC0EN|HJ9azQ4 zVizG6AQ0QgSXLZ`SB#~Q@x(LZxp!`NpQ>VU`pzYCqF6brUUb*L{*S8DiN`$CEcw;v z=U0CD@>9PGqPur8+$l~x&bRVeFJwi3^uqkgt1s-labc1!>G;L{Wx4sse=V-Q_1?o9 z#qrMtGR=~||Ml(He*Ddy{jEkXBLN5kn1ct1Kv(?j-`zR6_LujUH&!J+5$q9=tmrGx zZD0A}^Yb^JoTW=ig$O7SmhJ&lD6?~rS(f~RZ_Ho& z$?nenEX!r17D*xyfC%P}h_KxziU(bBAP7K3XjE7gfBX9FlWTwe_u|Ic$V>pf_qDB8 zes=ldufI4=mx{-^n;wB6G)XY8Oc*;a9>n-!2u!nlOqk=9-TckXqhI{>?($CxaABHW z|7dBKPC7e(FG5feRG=?T@jwJb!6Il=l`ViILUAY-^yVBa3V-{c`*v@<4v+x`3l0{A zqm{AMD$nj@W?AShP=Vq>TE{AC18ovCK_hF{aIh%6bHC$oX*f{4T2nzh2!T~GdJ7i4 zb77Krax1ajR;^_O6QE6kh`=cAhl|3y_j^8E6p95-l~P4EAP}@kaS@hfU~pwsoRi-8 z!X)wKd1aP!O%(#G!8lkNcMduZmId>5=RunjNd%#6K(Z#f;;2AUWtKC`zOd+xz62*L zk@MrZzUCJ|M#hya3}Nv5-4*$)gR81S%n z7K3x|c)%hscLHJ7C=qo?Ek@(m$fY$&!K~3bU=cMbUOQ9~W=*0u=X7vRJ7aLiqx!Mc zB#7F1-2zq8!JTPN8;~X?io-M|b|&qZf5F46SECHmBy`2eNoiD(b%KQR85*q0E@R@( z-GB(ftj)9@EQ_IR=&MzIH-lKW??e*< z^yV~aG(KuNk)YK?jC)d{t_ch1%*i73X7px=zz~5%FmGxQt1Pu>HM{OKk6Sp*N!|Ywd->>owRWB^c8c|5sd*=WTq-Nkab4Gqb%HpUC?= zlf2tou+=1vI_JSz!M%F9Dt`MvC*|thQefO*L{lUxb;nG<2 zLGift-bei>cK*3f67ox5nf&~xyBFSi@riaXNe#{8oc{qgU--xhSYd_$0000P000yS1^@s6cz2e)000C8NklX^q_(`i();e*&Gy`HOTDZAWiQX!aEEs{ zj-PpDWcJ`h<0Mk2}GT4?}PTm9G;yT&yE}55xf)1 zqOsB=pS(Ue-kdhK32&Y!#SXzs`R#k^WSh!fn=$-y%p9U7gP91X{yPD9mD zGrSWVf&pbRL|{1cBr}Sb@UE6m91Mj1^%~r6#mnE{Y@faQS8+i;ed~p-+J^_=tQ0R>3)};L!Mnjfy{x3 zk*xn!BPJtw*^86PiIteJsnQw0p`g4+>TbC21d4QJe@)Aoqb@N(2QegUV`hdX?fNkq3{O zu=qIADiS+~GA0HcgPEhiT8tAf-IHfkaya}R)1^RGcb%G zjv`j5!D=T`nUYku5YD~B0fvzJibPdRz>E9aFD!SzS?{JF#+vRxGq}mj3sH?ym4T3v zdrwS>%sE7$Ra8`JG(teAGj~!uSF3S$BE%$Dt}S;zTUaT-*lH^R>>tlgAK5c7yK68U z%R`NzcaE5pX66abvDT?*bt|G8RjNCJ0m+OY!b&GzzT1jtmp0M`dtQUwKc1a@XYbI= zj;6d>6F5X@*=T-~7^ejKm{Ga@5xT&%1_`CatW=0xz>S5RDL|ED= z&);5;b1R*6<;l#S>SVifax_d&PYr$f;-G)~|JeNlo=P~dhgY>m00000NkvXXu0mjf DUf~D~ diff --git a/micropsi_server/static/minecraft/block_textures/Red_Sandstone_Stairs.png b/micropsi_server/static/minecraft/block_textures/Red_Sandstone_Stairs.png deleted file mode 100644 index f70b30e54545562058a1c10d6b3cff408bafb2d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1289 zcmV+k1@`)hP)P000>X1^@s6#OZ}&000EgNklG2q|X-7b~)S%Ps!_ z_aNj8;sO%f5C>vKQE-Ig1Z?aO@9x+;o|*3Hx2ibw*bY`EaY}0Sp}Xs;dg`gC-^f!& zl_dS(*y@WfeRJ!T6lni_zkNF!_B7whC%upY>CExf3*UQw_2NnqP6h!S?l=0@A8$Ok z^rssSep_e%Sg@raRg&=CFArb*;ql?cK?)}k2Lu7sz#K%N&i3XX{yDz<)~m=?Ke9Q9WOLl7Vc{V z#m<@I!(W|#uK#m07&p#;Cz)AR+=U=cR;6DQEDB}TY9|DspCXQs&1v)rx}IB2I~R60 ze);vC>K6io?2h7W4g#BllF`h$aj!Q}$;@lMO*IGp1&!8RnfL)f_dWN^vn;z}>&``ki`k zXPj~Gc<&SjB2L{Z>Q0%2?UlsIBL$ybO=L5&JFR)=1_G@bQMy7G2GMm=XQff))}e8G z+_JrrIJ%P1#YR88&vj9i2%lRo*;y^QdB0(AqEv+;EI@+LD9lwKgA9rY?$p^xF>v#6 z&Oz-wQw5$~iKN(hRaS61rFES;JHvu;4I=7(@{IMWT$(G%EpUvhn@{ zV{e+-9wv^iMEXU*7Fb`k-$dPW3Eb*l}`P_ zv{epf#`S}4v&|j%E?KKe2!Vc)Xte_-bfS5ru6IZ6YcYg31;HI!b0l=$l|rPK0!>!b zl<7P(SH;|LhhmXj&B@x?PZ0uj)~k0X_S!?0E*6JydZ#*fVyk?4y-KIDb|Oe2plXC5 z^kU%s2XiK^qYHg@+ijf_o6%%{Jz+_BB{@ti zP-omh&6&3P#_h3Q`q#brqspI*Pdu#@f%KK5)!A=P000pP1^@s6D7ps@000BPNkl<;?U=vC-#MDI>qKL~Bv>IQVW;RJEtw~nZNftq< zB2q+A!G(L75TX>4ASD)(f-rG9lS!P6PG)X0nfLwg{pY%<7DQ7cxbVPPo%5Z;cOD<` zf3&HoDf!=G$WasDzj(2;(9t$V2zP;&%U{_pS9oS-CiR~*86O|_H{H5*`Jv6|rN*-m zSMLaLnmeI9bFNgitQ&-o*>E_#`=A=!VS(9e*Yp?4<@b&ch@a8hKMzSm+g#?m60(g& z)b3_*=EAGu!!0fSmSw%&+uN%R3=FI-FE3Y*#AIS(!c(u;L+v+jj=AkMUnR}(qXozw z4bpZI>XSVv_yg#A@g*pA26rNX%IQ_8~Vo|p?P%``@8`0iN|HP>VCFV zDZSa#)8h;d4>=S)l+xCMPFl+qU0uyWLkbO>0qA6@nnZHVn{jKS%nTb%3tY{#-5x!!QsC1kl{v47b}2N-6Gk9K(ifqQdGJ3id-iUc{sA zoya%bi#JrOa2yAcBth48*tQKyDU!(~;_*1Pwzfbi_47Q>&w9PysY<1CEE2H3@H`Jf z2r`)rHa0epPNzXB1<&&ZK@dI!Fc*nL_744%-<$bO)4a@a+*`$BQT=0~t*x!lGz|v_ z2S_H9h{a-Hj6qS95`Y_wvDwAN#lL+217#%?3OxegO1WGfDVNJK0BD+qL?QvxG@&R; zjpMja0fZxw$e}uRB!L_m8EF?q@ts<&_R7xAj%ZmHm1X%00PoJv&nN#0e`FN~fZ^fc nU^bh5*)+{BmzI`(Ix6lr!ri0qA}$Xi00000NkvXXu0mjfs$|rE diff --git a/micropsi_server/static/minecraft/block_textures/Redstone_(Repeater,_Inactive).png b/micropsi_server/static/minecraft/block_textures/Redstone_(Repeater,_Inactive).png deleted file mode 100644 index 37e37116cbc2d333ab7821274711242dfd1a7d3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmV<110?*3P)P000mO1^@s6000A`Nklt@$(noZSGYgP1+mC#@jqKA6$U=MzPoSW*0+pXc}E zdEWN{{)do%Qk=`>B+v5(2_YA!rlvaosU(-n`D9ssxlt^>9qH>kV7u=1cDwy9fKT%I zeEVNXCMPEYoby)+A=%~g=Tqg&mm#Yvj-NRLNl{R%)k==zoaLN4y!hHF^zBdKr*FT*uUD?% znG+`vK6nrfqrTqZ&imDB_1x_2?A9HUiHQl7QhJI~I;Lq_%3+j zrTejFY@_42NbXIdb+rJ667(M(#DOP|LDe*fqKHwc9$dds2lqV0Y8^a0Fo=CaMr~p_aAx`4?Z#g#u#p`lMsS%I1Et~(d~9&nkI_HA_|29 z48wpd%K)%MGMNl0rN=0xkXl*02o z2!eoSvx#!KjFpuYY;JCXF~$iY-!sOh0sOG@yGBPx0btPcyh+D#o;D1FYMKU3(-4cr zpePDl*WGz9K@hO9v4L{AjMdduRI61m#<(O&-vD^Ww(W}x3k!dJj5lQiwr!6SLY{3l zo0L+Do}L~glS%k|KD1gb=(>*O3kXp1b&&HpTDc#-vLewfDLBq8KeLJ002ov JPDHLkV1lGx#^3+| diff --git a/micropsi_server/static/minecraft/block_textures/Redstone_(Torch,_Active).png b/micropsi_server/static/minecraft/block_textures/Redstone_(Torch,_Active).png deleted file mode 100644 index c98b56cbe927a594aa24dba12fbea9424ec25513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 631 zcmV--0*L*IP)P000>X1^@s6#OZ}&0006$Nkl5#frgd(ky#NdmXzPxu&hnETtnyci%UEV$C{P>^$xepv-4VKp+Xl=sLCihyKumZsf zEW6apfadpsUEl{WHuI9bJIg%$@_}z#pO^*Oz&ub}g`Jy1-U6?9z!-IkB(P6<5THVb z25sQuD!jR@kAD@UAGdi9JOw@jd%!GE0eTc^P#o02I6z>eiWY>!GEjucGMEv8(zH|g zAjm!d!6UlfGxoNDBbeEST;&B!J-L{76Ub|L2Q2cd)xzX1UizbSo6Q!mxSHl&8dn6e1vl)Ef>(lxR>DJ#J0-TRVMDRS1RLBsO#BofLB&h27!&1~) zj1xpvu~t#5SZlF~b+rRtK(MOVYfzjZR+W>p2!N;;6UA|=Y?z)@Zi|)lkXS`T)`2Bwl55ne!NT3?@%Qt!iG}Ob{R82B&a60m RDRck;002ovPDHLkV1nC~FFybP diff --git a/micropsi_server/static/minecraft/block_textures/Redstone_(Torch,_Inactive).png b/micropsi_server/static/minecraft/block_textures/Redstone_(Torch,_Inactive).png deleted file mode 100644 index 8b81ffe92ac6a3aa6ba90484f7773fc591437d09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 518 zcmV+h0{Q)kP)P000>X1^@s6#OZ}&0005aNkl7-;0}0||_$mW#eFkwQh`5L*lG|KPca@7X z=A*Ot6q#{W{UFg{CQhQza6ok4QWP000>X1^@s6#OZ}&00055Nklk!Sg)P`dTp&^|-~){B5s8cB9?1c6fk?SPm34?$bVWs=3s6{$ zlbV*03{b=;S+<|&oA>{XNkos(W0S%;NmYV5%HQDqXDA3oVz}BVi+h9{2Awp!v=WtU zzTyt61a5NT+e}upayB{RBRPjr&_}jp(xEB{*J*|wg?9)GS_Q$89&E8Ut~o;t;0^sQ z*)@}AWY6h$qJ>pA_6^LYjDEIPyB9617R(F6M^y3K>+Pbu?P9_=s?SW$BQIMMPUE+% z*iml3kyn_(zdQSO5AC&F1xTu?Sg%gd;w?{OGQOj-{;xnXe2@PzpV)fL{; z|3Y?#Q>s_YmxK;3qk_>IYldA$FPPOz2FM=eq}NF7Ij*q6ZS-g-TQ;6i*3~v{MxcoA z;h~NnupJ?9V^3^~c#R>i<+bdW8y#xK?Zho>3w@wpD=NEts_Bo=w4QrU4|#GF3dRp7 zZ!>g|~lPvM+#&ZeHe$wzNexc(WQ{P?Y_sq&P6 dD=B5$#CPcXe55mV>~;VE002ovPDHLkV1h2=;gP000pP1^@s6D7ps@000B!Nkl) zzKvf9y=4#?htQp{9W9xjp6<{z?TEFKZP?$P4;f{F%`#UG=z5)9_K$OK68FGI;T$e+ z+|EmPD^I0Ta$jHHVbAl<#$vHUN+@9b`O^goew%z(XLnz3@>zAb9$Q_smF+=8ZEcDV zE;d^q?r6#J?%%(E>EgwU?Hj!Uz|_=~s3^))q9~pS27^z;VliCT zMbk7|u1l<|3){Adb+mcFb?`+2@7o`6zy5}^5AWjpwJO`d%dbtKs2cTpeX-SQ%?N^! zI(hP>FO7|j4F&>%uMESOipS%748y?lJTy%sx46iK)2AseEzvWWBw=M}{Mt|QAbx+|auLpYv~ z8IL21BGG7+a=FZsYq#64*X#8T-}f<1lSCqcuIp5*Rr2{fOG`^^ZEd0J`Zn+x z@c!J~-0JOpca+?;>>*&f(P)g+YBg0=Rl?yg*=&|drGl>Oj-n`E0PoGt&R+Y!p1VqJ zWOQ`2Uy`JmX0!Q1KA)Fe*KO&#ehxS>J3G64FMdao8yOiHd8|+i_@% diff --git a/micropsi_server/static/minecraft/block_textures/Redstone_Comparator_(inactive).png b/micropsi_server/static/minecraft/block_textures/Redstone_Comparator_(inactive).png deleted file mode 100644 index 30b81e1adc67d74c7fd012e841735c1d08489133..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmV+b1poVqP)P000pP1^@s6D7ps@000BWNklQF&a74M2Q}%v1u<>jq#Jj)Np_UNc12cplLi<4|=c{ zPbQ|uL{IfnHf>MXn6|-~+M2lC!a{&#VOiMkVP|G%J=N5vw(uO^fAdq=Bd@~# z0eE3QEM%dz*Zf0y4hDyy_AIoXhO@iPse6&Qs@m@Pe#imJkht@H9KsS*&p~j%oU)tp z>FMdVBS(&mbU)em^_{`wJ-;)Pen%~A(Wm<*?wZ?p>9w)Mzxu zT-S}~ayeqL7~yaj-w!y@WwW`kMB$IWSzTSlh(aZBQO!2qc<;oEM~@v_9~>M^U%7JS z-{naFQ&Us2rfElISw3Z&=An2zj^}w8hJo*S#5+209EZ4&=;`UAQLA$O{3R}({So(3 zlUs!vely_R6XR&QLA6?)_kI7QBuS~$r%wmU=;-J`C=@zpS=Lk{k?6K8i)OQlVHhmW z&-3eFMeDO)V4(L&aioC?1a!1Od@#lu#&y>$?0oH^;4fp6mHMQeP6&Y(q231ir_@N{&Y? zld=-$_}e20ApmGJ8tm-su(-I$?c2A9>h=1N3M?9iQ7n~8(ap_GY}+Q8OcD-vMeLZGLGZ0zP^s4D3r@(mY0{w z=kutlT2&O~8{n&p7cXu}cdu)Fe7sYVq)(g8=4i244C%T~XJ;q2Z4-$^Xf~SwXqtvB z%WQ0HkjZ3NSy`b{sUU=?tE&2KtJV54l}c^=XI=L)F)^{<^SsYmt=5OtYPBs00&Lr+ zx3?D|1m$v>wY4?U=`^)k4I#u1@E!2Qg$ox}@9n#<ejb=~jh=H_nhHun$2&5JKd^9FnX0000P000>X1^@s6#OZ}&000G{Nkl6e%e9Itj+{*cp%K&g;&7ba!>VSd2XcOcIN;szufL)v0s-r%uEx+UD9oCQ-Cg zr0Q3BqRgNw?k^i1UgfO#rVW?MLIQ7RvA8Qm*dFvU&Q>ixFljBn1b%n2to{E5dEvas z(z|co7;gXYTE+3Q#(9r(fg)AB|BplV=5|td?rUK$L0&k|^YpHFemjaIZms7;wxLKB z0zm*8Z8*|!SUa2xM3J1-jsBmo=*41lZ6NbB*-1tC?X`aN?i-uc_~y9Bv*R;N5Nr%H zdew+LODU3=PPZJl;5yA)w{P&{8`XH#)H^}Qok%4|{a${Nvj|{VWIypf{60(5cl)s& z|7dHC^(tpv6?}ehN;Meqvo~MoawfRGaf!{f0fSuecwxygYz-pnw!=9`-MO(w?dZ1K zQBm}nw)UX}u-@-j=eKp&p&d*YExkO)Scf2Ra`=oteee;>x}i!XMz45wGGl&nMB5p% zH0Anw#pbYN)p?p$qmA9}jNJi<0fdw&r7))BxP!kuTCqEOLJ*QUIWAasXDz!QJ|g+( zF@+Me#Au=j-@V%B(N@1NElZcB2M^Xb_{@g0Xb*|jd46Xt9d-rbf&`^jc|rr>p2^}80Q1?WsNqD zD0s$Y%vvwOI*SONqeVmAIb0B&uG$NoTtxEV;EXbI)HaZ&IoAuz(h5!&D|$(Ua0V&P zg%}CgtQ2{oI9#+ucrFhMP8-j3*)liK8vin+(UwL)r94~Xl5r6+Yb^Ko=iJI8UK-@7Ds5~FkY}x2uj6&Lj>MgqB!D8KVkA{pGDoG z2n>4}xf1N289v@U0ASj94x2!xB1Tn?0P3b?@3di~pE9Zn1QLY!cTQv^nQ6F2;O1*< zeDLWL{`PrI+jShyTRJDure*P5!SiKP6tf5z=RIz8(YW5FL5qKggxiaeEyyx-3 zDJv83&VO0)aqGPMfwq2Ztszkf3oBV^%eB7Z=Ei_wDoG>Qn`wH9WZYA%4@#^tXfN<0 zW@A)wvRHD`I6CVm2=Qy{{dpH&w)(v&if%WScOD$;$?nPOJoK#K+uyo^b%8;@!r1eh z31@KLqvD8BFGs1E06aZeGF`ffw&9&9lDAu9-n)ndpzTcPth=Y;_&1I56XX0FQi}2A ze#&C8!dTDBcn;>8SV9&HbZ4oJW6^XN>nCkze;(i$tucQ$)?EnYEC2X1OC|7j9ErO{ z%(eh(Co#?wd?1k?Aq0&LlMv1aiq@E~9Voui$}E*-mfqP000>X1^@s6#OZ}&000I@Nkl_ zYNM=L)JhSHG-@F!5P}V3u#JuJV?5(|-+A17=gz%dP)JacLVH$c@&C?malZ5aB%ZNe zC>F$^-@6cx27eI`2Zq^na;1FGbe=^kJng|uJ|%+idcZ4QkVU7MjQXiIx(r8@y1Mfo zI3KQVmhJxw@@RM{s9son<9zXzY?bnZHM*9IaP}0bv2i}SQRV&Lzh7$ihW{1zHON$M*9b#G|6h$tz+TCx2NspQriUlzq4K8F; zkv|nD<4JXdbgjw4vGq6N*6A@yQ$8bwFlGl%3-({r!yeth<5V&VHKum*S~o4eahVq=rMwRn!OXTbEa zQ$!*$PRzf`7q{05NL|hpyaaRe44S*tn%zWiIAe)1RowY0&=8gcIBvt(ya)7iVt zKWk1%ON{{%eoN{g$Z@un`EVZszhK5JVo-Mwq6-RCEY}LOdEK zpANCIy2;AEh~n}vHSQ-J^YZca2kcf_xFm<6BQZ3Gzp1yhw`Bmwaggi*M2@%b`MnTBXbne)KLku71LAFD~)f%{qPEBoX#96&Dd4 z*r}U1|M8U!FtQy^7DA*VGGqDU_$Czgw%2L*EOz(n{OXUF$;L&V&&P2U=cyW3SpLKB zxb#Jx{YDSlc5t~A^64OgrPFs*3a%Q%54*OEjK$uaPmv^%aLCWOlM{S= z^$sgLGOPPl{(AH0Xx$$DzCqM4^5S<+QM$cEPq!HKH2kWMTxOahu`d4j$QJI+sI~MND&u;5ckmG*VFupMQ^X zt?~Fy9z(8Ny-CbFq&;x(`2su_HPLi|a<#=+GLE<1K@trd$3d2$FzzJ~%dmF)8j4_% zpA56t9I?5tQSIEPt6PsD;-jY1HQ79{5gBo2Ho^S-Sxyx*+^;*hU2cvZng-emRx7#2RQb|wd(K}^4c9W2*5Dj^l&IG>>QLHB4oer>5)u>jh z)Yt!x&{<=`S7)Q#|kHao>|Xwd8!ghMJ5Q3=~J*spbHbO!W{5r;ETWYMACHh56!Fwl+bx?x_F0D`Jl z*LCysK~qauW)F`VV!x)*(oBwIJQSxwWb(5FWSv_Z4Z^BOCL$9KcyRfGbb2PX;3hkn zq+F@9vD2hCFiOBLb;H~c0rskmOD>l@rwv&A>`uFM_1fncx`9s(a`O1|IFc8)pwsVm z0B{KwmhBKrjFUYyi^uK8b{y8Xs%%uuQqQy(Wl5TAw!4>}aM$K`#opPke=r<5b2~M2 zv8kCQ!yMA@YaG-X$iYc$`JrPH$#FvA2nUr4dt0mY2RgcLmi7+1iS( diff --git a/micropsi_server/static/minecraft/block_textures/Redstone_Ore.png b/micropsi_server/static/minecraft/block_textures/Redstone_Ore.png deleted file mode 100644 index f6972f56a1b5e1efa6b2f7262a275b78cb84ab9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1604 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 diff --git a/micropsi_server/static/minecraft/block_textures/Sandstone.png b/micropsi_server/static/minecraft/block_textures/Sandstone.png deleted file mode 100644 index a6463073c88c503045a3054bbd87ae36551af328..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1509 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 diff --git a/micropsi_server/static/minecraft/block_textures/Sea_Lantern.png b/micropsi_server/static/minecraft/block_textures/Sea_Lantern.png deleted file mode 100644 index 1d9bd28b9c78dd669c943b3631ee38dcb1a2066e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1441 zcmV;S1z!4zP)P000>X1^@s6#OZ}&000GONklzCzZ&H<{ zVp2|}QqBbq777O85C#wu=rB6WL8Bf`ckh*p9t}4>(E_E)L zx$@4>FMM$A*Wz5#-q`xu%>3)zmDdSAJ$rHP{Iz%IPrY%bnwlY^tS>)XTD{-kbma?Dwi?Pn=}9Si*Zl1)eu|S>4>=%f-c|g`0o6v2y3z z!CnDT=YpBpcjji_|Iz$=KYX`(?(}JjfurCYf`JHThM^oJDf@BES5Iqve*14rxBu|j zjg>q1UICMD20wM_{M@CVUYo!A-PvmO)HG8gBNW2`6+;A^Iz(E&`o^~R5{{^Do9IWv3lm*=YICeNRoX0kj&DGX3a3DhCtFb`%P zGr_4M0^VYr281qPxKyH72qtq!D|6GAF3hzyw;pzO8;=!Wc)apqXSY#pH=1nkY_s0n zr4#os1C;=eM*tB)RZ$fT!^}_#Fd+6Rouo&7Ym3Hao#$UatcueQ0fMY}k0lB5Zi{=j zZnOMknR9QS<^9W7s1!@&odflN2Qxr06py3ZOW27z{B`Lo7Va$Y{2z}wC7=$=g@~%WVU!+_t zQExR_*<9zdPj51MY>Kd0ptI9p^U)Kmd%*GW2}TMfazTJf#-;$dARHtV0HkROB7~|q z1xRr?0?kt#8>d_<@o4=yYtL5cMqOILMhYcD6+}FUp`wUnjgboiMD-;I=5Znz z*{rE5f%10O<8O6*PE)H>|P{OGoVknBB{jQ@Q(*H*#B1FAjHVb6Ddh-Z^ zYJgKCr%Gk0j3Jc5a6pk9=GhM~eX$eoIW%d_4hV6Ql6vcNY1wBsun`pn0~LolhaP4m z(gzTb!#f!#hq`1k`;=}PlX$~hW*wq}WFmpc0LWX4%1c`417r-?;7>1k>5=_fjqOgG zb~hq5@B?)Wv25Q23PFI=kX#UEVf)9VPgmy7LKOFCA9M%qn}LW>->S3S+2bJfcvHNA zx0J-PjKts|i9w9i`)^R<@ud9n?eEy_88P&tK6cV4Z({8i6_JZLsPd*H@J<0Z>IA*oJ$;yl8OY60#*Xy;X z*W;-BXi!PNRv$H5_v*Ffk9T)=9&ARPYAj4@m>G2=ir8p1sMqT#Lf{++X~ORQ9`$CE z+Vb+!>OX7OH)~I?_x3t>fx)1B>l-hs!PxZ4x#MTwnx8o{Qw?%CcA5>=mse5m87dZu zx)IIY=2GLu`i;H%_9vLV-W&fam;@7Nrst;KesjJsRH}9wO`>L-b100000NkvXXu0mjfxvjv~ diff --git a/micropsi_server/static/minecraft/block_textures/Seed_Stem.png b/micropsi_server/static/minecraft/block_textures/Seed_Stem.png deleted file mode 100644 index 822c66713d3f262adc14af5c2c8894efc95429bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480 zcmV<60U!Q}P)P000#T1^@s6vnxdy0004}Nkl1wk-qV{z6Z11j{DC@j5G*LT39S;SbD@K7LKcIAxH|}eh?`i!!9j!$&Cuc~ zbh5j%YayURYHEwUA@tsJK8H|Tq&Zg`@Y~Medw6(xIS2l?X>}K0x4H}WvvNU}<#M6a z2&~*&dVYC%YLW}(^8_l(VGwktCfTz;$(7GAdu28YmQ8~2Ac~^*)k<}Gl6GU`vzakF z#+X{GyZGec`a;czGd@eJ+sz2&BEq-b^Z@5vUD1~!vIXo-;=PinGKa9E%G~jtASmjC z`z6_ThXBN*;bzekDn*m$sCeWh0M8^A_ub;!{eHg|$8kLhW~k&!RPrTOx0`ofeQG{B ziT6srzFR-_4Mn?&1*N_7S0yf9sR(m zZUQ{M{chzX&MV2EMpZd*LjskFyO||%9Fy1-1gx_!2*Z!3E;$^vqS`}wG0M>@nIU0-!cKuPJRJu WcZz8x&~Tst0000P000>X1^@s6#OZ}&000AjNklE$7{PgQaZFRj}|Mc{+`d9D2bjL8>A9(8EzN13$DWU-H z3C4OD>yegX5CV3U#l}{5JkQZ^!X!^0diba^ekxZ6KmZsZID_CE)_E95kb;8( zz#|52JT|tw!EbO zRy#+o?Cp2*^YT{n>eA-?{iR3)u3{r4N5kXszTpNTx4U#E~EVq|# zthfFFDOk8)GjofLnfb+&-qXZ(EB__SEhq$N>rpAkNJ|pV1!T&>T7tH|(Brm{J_!3c1;@7aIV+{I|NHCdu;NF?RCe!41+- zkcdYh3K*><_YM+5K&~xDQhDzn*~L%NG2Ai%nUO9yHwcU8;DyTP000>X1^@s6#OZ}&000BsNkl03IT7`((FLPSMG__7IZ1rY>s<04y`C&|iJm8xph}nchxM0?lE3wbQp#?&@@e-I=}j#bLw;h|3@nn3dOIk$$ZvvVPT=Vx3~B6 z?(Xg{l}hEC=Xt+*p7-#XwdC&{mX?;P8yg#qN~KbN{qFMevUz-be9&&UPyP{PadEM_ zv$NA!US6&%rNB7{V+=|u5JHg8=b`I5q9{VE)oLCc9eoIU16f*Hs%~vxDuA zmSur+4$e6!rHG;knx?@p4CuNJr4)n^e+f$=mSx%d`}@DHt*zCgC_)efC>Dz_3CdQ!^6X$IOih(pb5k@O@}d7zq-1D zloF2PAd~r{FDRw3Z5!Ea7I7RyDFpxsf&i}T!uNgnzK?#tkCT%VTwGk#HBEDp*xE~3 zN+~=&J)zs}Vsdg4g+c)n6BEega_O!KAutRhO%WjkoO7I=o#FQO7K9K;DaT4M26+j4 zetw4Qx+s-OD3{C7v_D-br4T}3G#a7ZZsX$O0)!A)mIWcin|SF;LI`wSA9G4zj4?Ql zgUQKB=(>)6zmHa{g-)jf#u#kdhHcx3;~1GtCRO|fqLfPICs0Z$D5VI30GxApo(Cx< z&d<+NU(2$P&1Ru#8Z=EyyY0htDW#CfWI!oRcay-CQZNhyp64Ns<1}sge10sr5CVjd zF(nkQOiBq#DRf;=VaZ`k6as+bI4Bm2&~+VQ7=kf|(P)H+hX)vj@v)LbO|lzGY1*<% zDGY~0aL&`i7)24HD1wv{uIu9Z`WlYopx5h-b=X+plk_BaAw>FYnr2!fUDqL{#9%N; z+uJYuiH#b+U)oP9D>FIh91n_+yuInO> zV+@DG=FQE`hrz#W<#M^Yxw-j$dwV-xU0wa2&*!WEO5pd*G|eynt^6nUJ36a97=uSN Q{r~^~07*qoM6N<$g8KE~OaK4? diff --git a/micropsi_server/static/minecraft/block_textures/Slime_Block.png b/micropsi_server/static/minecraft/block_textures/Slime_Block.png deleted file mode 100644 index 2eba8749c827dff81f0a03c4ca72e88745e83873..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1480 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 KMNUMnLSTYUY*IV` diff --git a/micropsi_server/static/minecraft/block_textures/Soul_Sand.png b/micropsi_server/static/minecraft/block_textures/Soul_Sand.png deleted file mode 100644 index 1f4b18da5709504be6b7b6f2edb98acc2fb6ff1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1558 zcmV+x2I={UP)P000>X1^@s6#OZ}&000HuNkl zyN_H+6^FljZ{0`T+dVzq)9rcqHM<5ZjbtQ)M1+J`5D;wu9&O4J5I7+uw49Uh7Z7{b zAt3<)#EW2QTNoJp81Fp$dEfOSn6Zt)Hha=KRbTy1opb60d`bK1hu`&wS-ks=>)XHj z`umqu7WxnNPZ#dX?DhVi!v{CN>dPp()oOJ|h+wCarC94IiekK2=68Kx-2cOP&%W!7 z;B5;(`|%I`uUuZ=EtdH`W6f=c8*N{{#PR8bs;U|Gd!tm!-CDJ~t1>vbes%M~L0vj<;Ha`*C0e*Tjme7~vdU)OcLvpyQ~`Gdc)u|6US zLmurt=K8hw@PmLq|MekKN~DY_%Zkz306dQ*3OP7Drd0~14QZBPv>tnb_{Hx(d-%KH zOpf_wS=B$vWI|b0Ocpr@N5_OBAWagiwH%(DkfxGRe-+>NSzBGjIY*us4EsH#lxSnv zzH*7le96kVORsdKBMcuHse}uD!_bzQ>oy7xY^CiYwl0?$)b&0}|C(jSq zd-9Apir88ov43#H`h^j-GW_MyWA+b^ND_(B8UO*{oI`6v5=V$A;?a}mR8>iq#PoYB zT)wzT+bZUHK{xZ*SnFeyVzyioiU4IpK7062=F0*h0>H!bpugJV>GAXoVvNCBhth^+ zUefP%5kgRuC0<^!_v{eWRA_Bj%ooIQ%;k%l@Z4v-_nhf6#~4Gm)1j;?L~xc**4bA| z)VZvFx`Iq%in1i|e1bsWc^-?RWICI%eQ|>;TbmplpYX>&jnT#tg(0a-XqBd_Yh;q( z`yN3cn9rB5Ag$7LWWqAfDXJRl9K%7MvT2ZVg~@bE-720OoH8qFCi5kpA7FJ$s}$Bb ztaBtX!2_Z&L}^2>oALB`iVtw{;uhU*hSHkhppT4WA|VJxNL~~;=a6wslEh5rOO!UW zN)w6@>l|5{;+zBLD9Z|CEoD{V`#!)KB+qk(gCU(XrLJqbnIta?TBV5Nh^>uvq)hNV zka3JQnlwqMn}()sDXS7|41pgYlbEuqh=jlx^9oYrIeSl@Vx1$5LY75ESvLp~5Q?)x z%VdHG0-`9y!^2oZ5CrtP9jtXEiNy0f@}eX^-~XBtnZyX;(a9uflCVBn!x~E*MZ{5r z2nAsn(p%}WIM1ZTGCxm}il%MRs-Jl`V>1zKyg(O4G|1Oav3&@}CNVV;L;w6=z7TePuE z=SyUq;2aoh$x_MMppWnSSZmQ*)3hzd81lU2Nb#o_9;+><@3EfUcCM8wV;^D|-%Cj#HsFgt*jZ&I83bED!XQ69a zH7?5XE{^*rd1YUNys!_y^DY16baLzXbaE$2lAYmTmFaxJ3+KFQ5MfABl(brtr72o# zs=6KvA?{{rdjHYWgV+B2w+<55HdcLO+^yBs-W{CXsp^I(6qI#Knq?@ZXq6(9>G*Uu zyK9WOe>f}NI8gX+gZ$%QX8(0O|0Is%8%?V|s+(p!91OTHT%&Cjp%CLZiatu>_{Q_o z#V2nAyi>`4+tuNUfAhx9t+Hu9-ak0{jpw-k`QEd4jzTZ&-~G=T1lh7B)&Kwi07*qo IM6N<$f|3;rd;kCd diff --git a/micropsi_server/static/minecraft/block_textures/Sponge.png b/micropsi_server/static/minecraft/block_textures/Sponge.png deleted file mode 100644 index 063d5c4c35e33cda30349f11f3704e635d261d89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1525 zcmVP000>X1^@s6#OZ}&000HNNklGt?V$ck1X;}8iF^VkF|dp!^@@&RC#e1Tcb5?F*!5ZFbs z%q|ger$|Iwv$t8cy4TU$C|dV5cDPAsKtJ)ZP`{oY^K z|M2_4wW{*=|3ZwB`qHJdt3O`-##$qlz4=9rwTi7rV@{uHAho0_J-a)T{`Kpd>mPo& z`K8!@AzDj)`SRJ-t5?6i*6B2Q<6*(VavSeFhm#q3ma}-Gfl`u(4-c4|i)gp&Y;U{% zuim}6e)Hz;HRpnTCSr`#Z~Sm&^|jZ!Yp;CkWG_i#q_FI4PdIhDjrWqt#9|_W(T>5O zqF$FYn}({8C>@v_Rya)mH}8GA{=o-#KI=>XT)cStjd$L;@b1FA=}F}&W-vOe=&m#= zW+hoxk=9eTxA*CEB4T40jw+H`M8J|yXJmPSvy!vtI&9uOAWiB2;^%*P`{Pe`ZfJl` zxBlkF#zAk=F&GnAT5i&8Xv$fIk^+K6YdCYJ!{f)>R8`6Rd<#KEVl;Vxb7wk4aYa=r z>UGUWA3f}8E#Cx=5P*A|`%JS2vm)n}^9c)!H3%?xGR0bf_t0v3PM(~{`wFE5!~G1? zPU&fD6I2Tw!~RUa2z9A3h*8! zVLUFGUrY(XA%w+y!JlvL({9%|b2estn2}|g-(G*f{re-dHk73$i6cS~amQWM{=L7TeHL~2}Jv`hRGCs^PQA|;Ig!GhUpeP(dK&>8; zO+8X7gpf3w2~z#L`o9(_pu5t*`;vA$L5LWE=3rQ$6s(-8(MSbpD$!aHMF#I2MN#6t zM{9{O0wDwf!8DuUy}-H8At13%Fd7vg1tEBps#rN0BZZ8#R28 z5Fm;m%Ph`=tzfu+h`=8sySq8w2TY`KwgMzOJ2SkO1de9gZq*r&19@Ih%mPXa%F-gG zB+E-ekXT!ywV>5XnVYX8r8-8U*bzmNq?Vvn%rwiHn`@HimaVNJm4#`Zp(G@cAT3&8rv}gPA2ooFPNE4ZWlyw#sDx?&MpwL=@ ztVrvMsw%NQ;GITFND@O;R+N=P>wr`WTb0L%cY^To);|4H9>j?gA6e# z2|*LZhN^N1sSv_qwB}$mC5{ZWnkEEK97!r`kH>ZE*8cm4(^)@YN$V2tJ$rkH3=%&F zV;3$guYT{lD{Ci~TfMRa>bSq6kWDxjpq1=;`fZ#(OE?u@Q^&$C~~ zXJOy|&dTZ!zQ44Fls#Jok|bttFJte?gwePnGJ@b#|JL94*YDjMf41}gjIaEyC?)i( zuXa{nzj$h`URS-rAY<=I&SX;bZ{HrSKYEmXY4QI#loI-z7nZJGzH;t&FTU8mDkb`> bvyS4QngHj4((85x00000NkvXXu0mjfJhS9} diff --git a/micropsi_server/static/minecraft/block_textures/Spruce_Door.png b/micropsi_server/static/minecraft/block_textures/Spruce_Door.png deleted file mode 100644 index 244aa48b0a8d2f234b490caeb3477267b0b87b1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 791 zcmV+y1L*vTP)P000>X1^@s6#OZ}&0008sNklO;){q&Jx4@8HFktRc7LYBl2nLZ5NJt5h zMOup(sC*5{KDpS`)cV?3_;?09;1dOW?F zj+@JM7hk@wvLd9a>+^?)lV@lm$IR^}QiY07PfpLyPsdl|DqOCIv=in^$);&&MpJsN z7|fGX2G2q4F zL})697Z8LdY=3n-TD22W*MaG%B2v-QlFjf=FxmD{46=f%?GMou8M+k}10x?EME2d=noJN5)>V_Z*B$M+8SXe~f4H~+npu!LbYU}70Z-_%eO{si& zFgq+9n8${c9jb1?5ld8( z8M9%YDjZ4>7;*#wwQXDXR-$S*De2&3yV*xgq>+gDhDZbA$rey4*IIm|`yu`Q`t9l( zn6!1cz=;# diff --git a/micropsi_server/static/minecraft/block_textures/Spruce_Fence.png b/micropsi_server/static/minecraft/block_textures/Spruce_Fence.png deleted file mode 100644 index fbb57cfcc7027d3a9a5c721ce85155feb714e6df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1156 zcmV-~1bh35P)P000>X1^@s6#OZ}&000C_NklW}H3?w*;Q$tJGr#tlNEdy`EBjh7Gzc=B)X<|P_(5pr6*xQBqC<{%;n zg6ClHb}ZcazNS%=Bk{mxnd7vu2}leQrfP-M@a{diw2V_#ZUejZdCg zUif5wHaonP^BY_mv2&m7=jMlI;$vryFMbsI^mI`xRMH@c{ozk{w^pwIxbg8LAKecm zi-S{DEl*cMqmu*}q#^G7J{bt1-`?r-{wrX9kj!LB^g0U|@>-%OIW|TOxH6F1OnILg zGS_d z^Lh@cEdbz|I7SYFMp{Vq50wxCdUU?G{K8!4)vGtxKR4FVrNzP7qtEruwLI3HR2;YO zjbN_!HTnZLt2Y=%O9pK-`gyCcYRnvYXfBr z2!NU!C}VXm3%%JaLMarAS^|!MOMUCsubWGctz_Xq@TMhNb0)`PW&1-MX9 z#)1)oMi^9$UMp&`+D$^QmxLFku?QJM0YDjx=1J`u)e`z)i7=pW*5UrBfzSqw5J+WE za=lSDa<#1Gr!Ow_mS?i~Vke1E)B;5ZIbI9hvLA{N`(mUwa-qC8fdlw}&Wdi7W|WtV2HuQF666WLR+YT!<*#ZWjema*atT z-k%$^Z=629aG8Rfx<3}!${Rc!HArI*Y3!q)h8S0XG!ELjy&(s?EeP!}E+uNAA+6Rt6rH@;yM7#_2Ux&(20CF?NCbvN(f-t-jH4zB8z;CN{MkH;Clo! z?EvFiUR@g&=SQXZZl^!5HHuoJp9V-HAGK6a+9GeBkS?E61Rld|w*}`M2pqV!$Sd*H zZU6vm=FOLklB^NY`6yt6qEe{1{$^A*D|juU4gg~WLT;at*%ir0;!@{#ALJk0 zA6J+9S^U$uI=_sLCl}=jY0;g3@V|aw1qK`hdh#yCSMyCAMUk31v$`ZK{=pm z6skspF##M>Wa>gtMX5C?SQ0HMzS5Gdmyc1_i% z)l{GfrPNrQpp?W?8H`GayiwndOL0jXJKPs(r$V1)hYxk$TR7OcoJ7G%?DLNX0R8|s WR!fP000>X1^@s6#OZ}&000C_NklJ3Q zBL_|jRd7&LjJ2^z?#;cqH|Kr!!$F!yn>c958XZ`h?|0U>zNNUJYSn|;cix!)?DbdL z9}d&<;eJ|fANIsW0P3MP``+Dae?^fONg>!BX8iQa<9GY}#SZ}9ML@1!9>1+C8*T2# zRHbH+mRQTkVgK9^DFrW0HkKD=$5!Xtt(Cx&RMyf-ic@dT1-UZaTE6q@%<9BQZKdW* zcJ^{M{vBepIn)0ag3OFJmKPVMR_CT0E54EhQu4SHv$dO$YeVQMz)@+7vG&CvH!hDY zzjgca>O?DC=?)8?^fK~F)8EelN1!B;C&^1qTo|l%2yr~qDM%EkvAi^U0JD?L<)!QG z)k{;&6)6SV!-BnG##TSZT1VhXln|taA;~q?I_i;6BT@|F{4}H<`Lj3YCO)}!b>bsW z@E|Glo>FRMKdspAWf*JN?j;!OaLyrcR2q6|g*L7nYlXplyFq7=Q&a|H-D$|&Wb@N`jLws+{8AhH?X@eT_i=(!8^9q? zB;W|5oy7?#tBPSFC>*UN6ox6RNCTz_Gm<)BuSwG5C}#a!L7yl@mrli&cmIU-XOyXN9Zd& zb?lcs{1|2>gS13zOIBz)gMu{E@gONazcSO@Ymd~CPh)AFqtFIzEt{Q~U;pgV-Ak!Q ziuOoI;7bH&MA-8rm9gZxCeA9tK<1Bj;;Y)&u9EVD(ije%Hum7)hTVkCPC_&Cm}vP} z2f4P`lM8tUq9pCnkh11c4<&_hdbANvE!~{{sy$Yl8YBfea}=c^D-A*jnvqB3AA|xS z$SZ>q0)aal0^pn=@D$pC6r%a|o%!G5RLdmOuNNi#RL4KNDh|!x{(9Q4184tw8P$G9Lat#8^iZDq1xkDFnrVEqa(1>s!0Y zT2Yw|A^Or+GAOmpPwE~)Ds3sWK{yCKNt$bZyuXPymS)YPG#1~JfIwTP_u^uGyO*xz zrQSGp0{cAR*-4yZ7*_}Z!=wNNjZje!4;C6Yi4;=z<9z+eZnBo-)yDH;|5rzlk-E>^ zWR1=srx|(F0(CMiI?t>1&2GGw7RP`7!VqIYNy%g@q&sv+;=`m^@AT7iiJt*E5KanB zt}RN4{P%x)izhulZyjBHWo@Uj79pVKNhEAt1mIN7Ow{i#-DrRJ@^tH-r)2%2bpHe4 W;Bpwy%iZMw0000P000>X1^@s6#OZ}&000FoNklx8_}=xd?-RVHe)p@J64(tf zeR$IPI|&Z{^tXSd*EuV`Ys0VKe^0JgdiU;=cJIwA<&H6mwvYIj4y6?PS67OIKm7TV zZ-re7`NdCefKVU2b9J=0^l@k506QU&C0fZ%DY!lF(&1aw8n|z@!gIFy&>pZiJUVTa4zwn z?P)5*+glB4qedc_FFljRK+1`y({ATv=57Q3ke5M_f>CW~YRxbtCi5N=kimtBRxtR$ zr}wA0K43It5|dfWmk+13%i-dxODidjBm?~rS!*l;Sh#>2BI{#|K+uOoWfYGmEuY@& z@gV}|4;@p+4d?4iE7@MFH%?~*g_c+)v0Aa-Sk}juS?75&St9azvND-&2=MIkS?6h8 zWL)T1SMu{)?{59@`{R#0pFXlbP5dUtC{Moz@=B@P-DFw%qC0YtftJqkr`QF9| z0US>qF(sNx<71+uh@#(pZ+DL|&>0BVigf)_Jtr_As>{3Fo93n~zvQK<| zYrS_pa{UqpC3s@%ebAE(SF0!D=jBv$FsKo;$ois=-q88CA%r+y|u>d7^4_jMISOlj6|PVI8V%( zS=&<;irOkt2A?uU3KlNXxo{X_+Sf`RJa{@w&wJTR+PS_djS{;#v$xjR9c$(J7Zy-x z$?2@e4S|?4N(rQZc^Bw?I?S*S9Nc?y_Ii`?y?3^xlwwy1_C|%?S!)b2XPk>nTE`GF zJ|sd&hnc*evpBeaeD?p%_;1?S4`Ej+vG>Nf*byRgJasIGc$ib(Pnio_F4K2Ho@?j& orj&x+A*2u85bp2_TQ1Xo0akI!wqX~B5&!@I07*qoM6N<$f}{YKd;kCd diff --git a/micropsi_server/static/minecraft/block_textures/Sticky_Piston.png b/micropsi_server/static/minecraft/block_textures/Sticky_Piston.png deleted file mode 100644 index c3691ff83c1671304f281142f69903123e8a9b16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1591 zcmV-72FUq|P)P000>X1^@s6#OZ}&000I4NklR+yZcwuK}=Yx;`{LzKW7tgBCdsq8^2Yz|+4fXZcmNzuSm1w$m*;#R3-u>Hq8?)JL z{5P*&K59 z>eaVbS6}#5t9_@j+u1>%rI014^#Yy44$=MqvAn>-H)_beF7d=i%jHNcNnW++Ogx+j zipy2TM#P!QTP&AOHE-Yk?B|y*UHVW5Xf~T0|M>fEV|4Hd#ZpG8UZsS>XE*K<6-{(a z!<|ZoUmTDZ4%y2~DY3xpVslU+sTMv7BMmi)6mXR}UUB$XhI^DzC1cLYk|5{^`fG z4!a!m#)M&nnhz+Sr~#k@bUGbovni`(n=FOSaLQP`N;g?TTYL#CA23{UNhUE1`9&%( zEKqBC_^lDqi;EO1r)YDJy9b|B&f5snrXPlQo_7qndGjVY+eCU}mMS^iqKWTM`2BlV znDy^cujKjm$tA3n1+>nH$$pnOvNAV!>uIiTLY{J$|%whO_72Vfg-U`NR9yNHU3SX=G_k7=(m@ zPj4JCixpH&5{BV1Bn(5U)hhY?iwp(>hJM6UeN5g`s1z+q6_rU4v3qBqz4q_ez40lw zZJ?H$RSd&G(=^gF#V`zf-$&PV08{|e zG%*Z=Fbo+C2E=iUX+AN?vJA_zD3{9!A!xN)pGgI9YdlhLQxcST}M$A6h*=JeF}vFmSqu!A*E6Y!!XFQj4%x6#z~S8MG;Ao5XUi6 zN({pw2!eUJ)c^lR_Bqbs)`VTD2hnabY6iYucP&+ z8V_7MI5_x$<2a3@`uF>NvMeJ>67u;xK@iN10OB}C(=@UyV>}+C>pG6(;CUXS(TF5T znu?-adupDXJb6-;Qm#uWH(l3lR4Nq=!@zMIj`}tljo91UqgX6r7zSyYB7`6e!+FL* z5H#aB-paCUdpI0sPeG2fy1J@LDc4n1-L!4HQLon#LSULE_wV1Q*X!Xp4zBAW<&&?& z_kBFiYZ`{J1#BN49zHdOpE={}^}1>p#<~z<)3U6FX_^4+@9$GA7Eu(1$z(zlMNKK? zR+^^s$p2fG`wjBTf002ovPDHLkV1m9t^gRFo diff --git a/micropsi_server/static/minecraft/block_textures/Stone.png b/micropsi_server/static/minecraft/block_textures/Stone.png deleted file mode 100644 index bc1be3cf4cde8c701ee6f44d9ab97780ecf23fd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1192 zcmV;Z1XufsP)P000>X1^@s6#OZ}&000DUNklpJm44PYOcZ@NcWm)D6i=rTeK#Y;5X=vM) z7$Zedpp>HTdnS_!-h1Zr`BX}|%PG$kz`c9-e$iV0p|$>PGMQWoA<*|dAq0#uXsvVg zecvOcL`q55bu>+bQVMG=F-EGYx+H|S1>CxJ?b_>ugM+^mz?Cakwogt@eoZM6LO^TH zXfz^(KuReyvDQ-8HC0vNy+eZ`D@87>~kB*N1)Pr0h1dfl7 z5kheF>Q$U`St4T$7cX99xm;q5!F!L^8e<)FZ&)suXst;p0gyqu zuA{0dj4`}>_l~A%sOy@^WCFm^(GgNgoO6uFV}ua&ea~n#Vm6!c^5x6ymDU;|1WKv1 zAm`7YXSG@}pU*MIV68<;$;QS842Aw*84 z2J+!yQ{VR#ML}7X^nK5V4H~w}Oa^J&7AYkFQc6Mytk>(TYv1?O zbxjC?<#L$=A%w`xQ%c#7p@7k7^!d)gli|ZavuT>ls%aYfzR$5W#-NlUrId@)S|g>z zdrt@&wwXbU zk*@2qY}VQgno`OV4M71YoZ4;l}l0CGWX+ve#o6j)W& zS-GmJ${8Pwbk60f>$=V{AG|nieo+*_=ZWo{%OfYJE5=9&fx52G(7bTr0x^D`+N;%y zrfJYxXT86S_#ES|>xePtL=GJ<0lfFWw{1JM*0Nr&bG0cYy!UwkI_I*%r+dPCp8?9U z92ic4-!sUwXV0Dk*HcQjopaN{IBPA|+Wf8z4K{!aA@ZCOLQoV1&bety>2?U=`u_g@ zbNIo_c<0U?DW%+6uh%;%rOiS67$Yeq7K;Ve+N}4GCLzRB2(b(7?eFjZ_+|Wd-Mo2I z0$adNN@+8MKv|Y7mrE9lMV4_q9#3P8yCH<{F8@!E(_mfKZ2>zP000>X1^@s6#OZ}&000DhNklyjED$G3CI8m30W2a5--66;Amq)o?(!YwILvL znQ?c!Z*|*X)rA4?t}E%J>p%bb&pCeyE_hzPd@0Ma+!aDRaL&EXvh3jD!-xJNcg6qx z@aoko3G5nU_UgJ`2qBowW@K5mG{)?gWqI)E(WCza`&W<`FJ8!_qoduMH*fBZwHzSa>HUcF{LRpr}5W>C?;^5h{XC6QS+`oVSkEUt<1RhE$cf9xHd5)A4 z=N#5r@;t{mhcSkxX#nuvlcp)dFrbveIY(XBJI=YgtJUi6_V)INqobq0G(eiBca1TN zx~^&4mZB&KA>h4d7zW01B+qlMT)D!=#sV!m3fI5|0CynNpY+m^O%QA#nN&$)W_D#ypiR8>U? zfx50)uh%q9!#Ivn6AchTplw@*VE|xbV}q{i0Lb&4rfJCY8~|%AQc6MyR8_^LOP8qY zn&omy+qM{E2qF9qG64ZdDLFkoB}ozh>bjVob8|C_JdPt(Rq^4&2Ue>UecvOc zj5U-}7-N2bc<&KH;G83b5Q~Hmu-1~MDaIJuwk1gtgb;lH{+(~%zD4bX5P0t+$l0R- zx~{`HhY5WBb5#*zI5(liZy-;-q-N-305 zWLbs~0&6Woh-eOLEyFP2oI^^95CWxCWbeJldyjLDq9}fVY;A3E`SN8>PEM%n8m%?X zIkeWXyDZD-`yS_9WbK@b-CAq0)-q)*%d(%DOd%_Zg86*TdcCHqs>m!!lAk(1JAdaK z!!Y2zrzi@tEQ|U2&B9Vjx~_|0!!S@31yxni_dP{XV6BZhP2ro^?Ck7dtz{g?*hqBj zS*mH8BBhMN&gXM(+_*tmmULYgFP#nV_V)H~f=~ZV-zoUJ9q9#@BJ>YR}{q} zO;fbiadcHxQP(v<)W8^n_kI~d*q2fsynp}x{Fm|9bNlvf3G6DR_O#Xutu<+yQr9(g zU7ro#64(b04i676_Qs#b4A>1J>?x%decw~p^@WOG801%GUDrKm+xGQEicjZnsxD4R T>`*G100000NkvXXu0mjfa~Mo# diff --git a/micropsi_server/static/minecraft/block_textures/Stone_Brick_Stairs.png b/micropsi_server/static/minecraft/block_textures/Stone_Brick_Stairs.png deleted file mode 100644 index d9bee78fca1931a57b8866f4f61e611cde052257..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1178 zcmV;L1ZDe)P)P000>X1^@s6#OZ}&000DGNklcS1n{gaSlC2bO3oS4gz^1E?eJPoU&K zh;+z8SYRPQ*gCIw?sj)(cIKr>cI+cua+z*dGxK@x``-5r!;UGM#i zG)+ejA3hAroMr#D;ra9D9KeQiZcA%DWQ<{LZ4GIfj-7Mcd7h6RJ$m%7u!WF2ckY0Q zepz2%-?G*YjWJ-1AxRPdfcGB#ejizujfrR*!05@7C*OrFgxtG#Z`FIRopZ>t482|t zN+}Q#lu`&GKnMXTC5Q-XYinaE?c1v%1WiQXoWnVXBuS9xIaXI! zBdBfL2vSNR&+{=6Z8OG3PoF*w0KgYPy!SB1Kx>WZbP6Iu2mwV=V1IufT5B{-11Tkp zF#rH-E%x{Khg$33zkK=f$Bi2|egXiw2*MbPFvb{+G4S5UtPT$kA%sA$*F%2wNfZ9KaW(liZ-2t#saCmqKV+^XQf^!Z;GzVE&lM5Fv{4kkJqD-AsNs=Ih5CfRa zW~l2LMN!0HIOh;TEY4(ebMvfHYSTIQ;QICJ`1tW7c6WE9gi=ZfA;1^|5kUxnwr#;V zM+hN8POb>KfB*j3e!sujw(SEV$~osK%MzzgpT@PhSeE9GIAp|I;=IUgv zMF;^Q1bFY!>-CNy#+V<|G|lU}hA{?NmZ5E1v~3$}G8han7z_Xamo8nx?(Qz$zkiPq z0yyV!e+U6tmLW~k`Aod`m`K0X`r=+_a0J83|k37#Ign;)x>R%KEPMtc1 z^XJdcd4@3-8zVy6ww;@>&N(zq1K=+cP9_tSWr;k`kt7L(5U8pO2L}iE{P{D!e*KDm zzyFVd&q09WF(RdmMr+#^bzR54KRP;!BW5z0#LT<8ob(Up9Nzn5h}IfXN+_kEl!^uH z4t9f4DFtH;4h|0BoQt05h9_ezUT%yzHs{}ke`$;v*L97ms$xqz=it3Z+qO_j!CH&i zY!)w6O2ue9HLSI0ng-U|aR}jMguH(J`W1jH-us)zm~rP|B0`#`kW#`q2hMqv_1g@r zwWz8J#+b49{w9DcJ3BkCfbTq7*REaTA%qP8TfJUym?TMbb_fAwSwd?ao1z=>A%rmz zZF9~?Z{ECF?q$4s^(qIjA%xhHQVyk*v9-0P000>X1^@s6#OZ}&000AWNkl`Jk3b^DDFp?8#DRpaNC_t)m#*k2 zj-*Os5Qq;63(KrU`)7numvnmyTZ701U&Z<@5P1A;jM1=H`Do zGXZ92XMX@zwAN^?X*3#X(RRCy5CX$6lHz!tM=qBe7ecH6YYPht&vSEgL!D)u0QkO- z<2Y%-LZOgG=ejPoZ6kz8T}mnGbUJ9QYrqz;*LxZ>P5^A%#n>24yIvL`sR{IC!3yu7_dB`T055*Vjx=PEsnB2*Z#liZXjnPfw}U zYBNz3eGeQDg!ICaIV7`4V3(Jdlu9MW#>R-_n5(NR&d$zwdV0##>4TW2NgT&X&S_D2 zIS7_zr7)!wx3{;{>vit#?odi)Tn&W?A#farIF5;;CzP zR4SEY;2rSWE3B{6uA2>j<2V!w1x80lv27c}Fw)mzXk3*_C8|^^JA+`!^U`QEkWyw~ z$*u1#uC-2G_RZw~V4Y5fcDtP!bhotDL{UT-hQlGkKP1HRIOH{YPGuKy6(Ujd-MhPNo)NHdj73^jaFAz zE#Md6_c)Gc>h(In7p?UlQp(TA$H!@A-wf%7DW%r#@9!Uh&x3w~{{`uw_XgAv(yssj N002ovPDHLkV1nE=xTpXC diff --git a/micropsi_server/static/minecraft/block_textures/Stone_Pressure_Plate.png b/micropsi_server/static/minecraft/block_textures/Stone_Pressure_Plate.png deleted file mode 100644 index 967b9e76fbc1ec93eeac6175fe5338676f3a6243..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 757 zcmVP000gM1^@s6+B&yD0008KNkl=B=votS5*aTExN9QF$UxDSSh6@MN#A@ zCnx_4`%lRJ{(iz3%et;h0dTD~06-~)zVBhJMc?;WT3Uiq3WN}4jG1_zmmeP={{njl z+1=euthJd^DrJmuN-4^+gb)IhQW#?Z0B=y=_rVwg=NxriBMd_&g!m;!0=#BEpU*R` zbt;75LI@N^0j)J$*99R2ecuBB!Y~A-6m?xgDFvkzf*^pD5|mQ7t_!7`D&LCR8@t(?=c#U z5XUi^ra{}bpp=3Tf}$uuDP5dhU0q>1okB|a8rS;zdIBKhoTuZJF^{7djIl+zaU4Ty zjj}A^`#z+UP)aRawrz_b2%xpb<>e*r@9$OHwi5vP`T6u@6 zJc2O>gb-M3K?s2m0-oo=7=yAb5r!cg$HDdWHSX^2l-7FE_kDhLcJ{vbkJ{SWO6K!< z7Di#Zy1L4jhs$_*d4aVS#uzw`gD8s7wk@X9DQ2^os;X+zG);bSaq;^V{;18(&7^JH zOlzI4tgP@b3?YO7=NvaTH<-<4N^3n4Lgc5Xr+@buKkBVB##rh&4xi0tN=muN{7>_r ng8Um+mgTpqs(yUt@i+Yhh1PJ{Fj-q600000NkvXXu0mjf_0m>c diff --git a/micropsi_server/static/minecraft/block_textures/Stone_Slab.png b/micropsi_server/static/minecraft/block_textures/Stone_Slab.png deleted file mode 100644 index c1d93b85a30500d52a877aa3b520632bdabf6504..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1019 zcmVP000yS1^@s6cz2e)000BRNklg^0KqFzW&FXDEjBMgs9?$iVCeW+8*P2!jL_|AG)T z!KBOXGFI2e{isXv8KR#Ei zI4AgZDdl#SWw$O|xWH<)VzF5K;k|zZ{JFck`~OI0vzZ)?MjK7jY)>YW8<#F!B7}go z79j*uO3JdNs;cM4m`6g0z5Dm?hjWq4W-|%gR7!2-dA^b7Io^AO5QGp2A<*|dAq0#u zXsxNLio?Uhy)4UiwARlaK71IyOER0y@IV@4elo^vO(v5oS(agpK`BMswg4Do zaL&cO5CZ2M&N;N!F-xsA&N&u~1*fN{FP(EcNs|1orIf#T?|+_7r%Waj0KE4&=MX}W zrfJMX2!Z!Ls#i)Or9>$OfVCE-6c;aEBuSDhM@L7$y?y)kSFMz~Y^~+^_?V(7$n%`F zwKa?}gb--kHmdEqjxpJvMp4Dna+qPJ1WBrpP zA~uT_sr*W%Ce+snkcUC``AZo?N<`-eQe-xFoZx^ zmZWKlF@}#HKawN~C?-yEb-n)Ck-wevN|&={sxj#o&gUbP*oMn|bU3TI~5H)J<~Eo02a zVBz6nE*1;Au8ZYy&e3%pUDxe}5O(^$fA;3ho9_?E?~ys@Hni5;S(e?%^E}R_rfF!~ zmb$L7);{n1{*e%3Z&3d2`r)Isxw#2&9oSCO^j6a}lx0a>*YVMM{rdHPV*HTgpJc|E pt4-6~E6egp*LC~*`}^mL{R<<2cy^g~->v`v002ovPDHLkV1m@l_P000>X1^@s6#OZ}&000BkNklJeBaNbFz|tL@1TC*YRRnM%4ThtzM^e%%Fi$8c zzQr3xj#M);B1c=VoeEqc!w1AyM%b2#50hTF%16X>T) zw75O9aH}+$kJ~e*X+loOup9#}iAE{qz%1^S9|T()(DRO*p0g$az>@4xDwTm=)Svp*GktGU(aD)O5e+>I>gJ^eU> z#O)C=Fwl?rf9@moF#AMqIhKH4u0YM{u+68pur?nVXKbQTorfaMQ(Dn~$xS<`EO7+@7*X!z*|7Ykz&OUYuK4L1HHq)$ diff --git a/micropsi_server/static/minecraft/block_textures/Sunflower.png b/micropsi_server/static/minecraft/block_textures/Sunflower.png deleted file mode 100644 index 71c1d760d07224616d3de6a97079fc3856db4413..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 593 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 diff --git a/micropsi_server/static/minecraft/block_textures/Torch.png b/micropsi_server/static/minecraft/block_textures/Torch.png deleted file mode 100644 index be07f35d5717702f97554a718afd7cd789c85bb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 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 diff --git a/micropsi_server/static/minecraft/block_textures/Wall_Sign.png b/micropsi_server/static/minecraft/block_textures/Wall_Sign.png deleted file mode 100644 index 7e5e071095179059920fe6f63792f2d0cb6cd70f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1196 zcmV;d1XKHoP)P000>X1^@s6#OZ}&000DYNklA#FtIj!f?!A3mqYd3gMucQWe2b;n_}Ex5F=mAXO^{e19Sf0;kl0v=_z$oc z35g96a#=`bVL^OEVi@Um8I#G(bl-c=XVq(QTMt9W*Sx#Rsnm1cUp;RbfQR;8+qvc4c^LpJk3RqI|AuWst}MDf@d{Y6s7A8Xa_(Ht>`XQ=#{4gs0eEolwMAmZZ!QTM z5n}+wSz&&z!$mvJ8W=Gv_dfIXzlIrreYfl`#27!*%}EnW8(RQWl@KF!9qD&6&Y$nG z>w>vK*XJvDKKc4Th8ciI_TNxcRqIES5;2C{JH#5S2nHzYfKM$kD*di!=Z>?;b32%y zon5)(iP!!XW&j@8dv#$&dDQcXXIAxkYyqx8EeO$dlFSg`;&5iY%Gp7YP?4JKu^ za^JJt$7lc^zU#U|RlNczh&8mWGAV1?5SZI2U{_QHD~JAn03 z$=Urbd7ChrlvH&~mY#x*tA>6jBkftrre##rQ$ex>W2Qf$szg=FI*7!1xjQs>AsNA4YMh@2ReR88DGM5)^rYYn|_MnoBxHRDM` zRks{lFF1B$d%92GS&Dk6fj77;_1I#OprVPjlV)e&a|R1q^B z(z%(OUY=3afe<1hzsYl2?Yni42bO?kpSp$ICuoc`A!4l|u@+Srmo;sO_{5R>6gDsZ zHzR*Mvd+nIHT>w)9~S=vIUS}^mlG!oVuhJbI_=OHFrW&PavG1%9L||7^;dshXJb?k zKlyBJ(QXI%^3c)t%R@&GUVh2^t1;@)5aU%b>Wqj%#1J>98lk=W_e5>*K?5TbH;coJav;lacI9P000>X1^@s6#OZ}&000D0Nklx2@7gR|60t!TD3jTo`BAI+N z5tR`XD3bFb5=7*PU~CM=-d)em%)Wi^UW)nHO$fqD0Z*E0=ALuUx#zwC{)1zV`k{?F ztuylMOLqDdNxO6P zozB81AE-1M5C{l>LR5jGQ!zC}CPG63JT&i~2#^4eoGem>1c%Jj5H(Myt<5_he!BPM z<_!h_TW`Oa#@{xBe`-x65iuAHW(!;T9~`_JAQlIw3a3zwP$2@)Z^;u)iiAy+U3)TRyQVgD9RUlA+YiP&_E;^1D zn_zpp8~s6VUBvgG0LmI$Oomk
    -
    - -
    - -
    -
    -

    World Status

    - -
    -
    -

    Agents

    -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/micropsi_server/static/minecraft/minecraft2d.js b/micropsi_server/static/minecraft/minecraft2d.js deleted file mode 100644 index 66189532..00000000 --- a/micropsi_server/static/minecraft/minecraft2d.js +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Viewer for Minecraft 2D projection. - */ - -worldscope = paper; - -var firstLayer ; -var secondLayer; -var current_layer = 1; - -currentWorld = $.cookie('selected_world') || null; - -objectLayer = new Layer(); -objectLayer.name = 'ObjectLayer'; - -currentWorldSimulationStep = -1; - -if (currentWorld) { - setCurrentWorld(currentWorld); -} - -worldRunning = false; - -function get_world_data(){ - return {step: currentWorldSimulationStep}; -} - -function set_world_data(data){ - - worldscope.activate(); - currentWorldSimulationStep = data.current_step; - - if (data.projection) { - - if (current_layer == 1) { - console.log("activating second layer ..."); - secondLayer.activate(); - } - else { - console.log("activating first layer ..."); - firstLayer.activate(); - } - - var height = data.assets.height; - var width = data.assets.width; - var num_pix = 20 // 64 for jonas' textures, 25 for gamepedia - - for (var x = 0; x < width; x++) { - for (var y = 0; y < height; y++) { - var raster = new Raster('mc_block_img_' + data.projection[(y + x * height) * 2]); - // is there a problem if x or y are 0 !? how does js or paper.js handle this ?? - raster.position = new Point(world.width / width * x, world.height / height * y); - var distance = data.projection[(y + x * height) * 2 + 1]; - // texture images are num_pix pixels long; 1/5 is a heuristic - // consider defining a different distance function that allows for - // better distinction of small distances - raster.scale( - (world.width / width) / num_pix * (1 / Math.pow(distance, 1/5)), - (world.height / height) / num_pix * (1 / Math.pow(distance, 1/5)) - ); - } - } - - if (current_layer == 1) { - console.log("removing frist layer children ..."); - firstLayer.removeChildren(); - current_layer = 0; - } - else { - console.log("removing frist layer children ..."); - secondLayer.removeChildren(); - current_layer = 1; - } - // break; - } - - updateViewSize(); -} - -register_stepping_function('world', get_world_data, set_world_data); - -refreshWorldView = function () { - api.call('get_world_properties', - { world_uid: currentWorld }, - success=set_world_data, - error = function (data) { - $.cookie('selected_world', '', { - expires: -1, - path: '/' - }); - dialogs.notification(data.Error, 'error'); - } - ); -} - -function setCurrentWorld(uid) { - currentWorld = uid; - $.cookie('selected_world', currentWorld, { - expires: 7, - path: '/' - }); - loadWorldInfo(); -} - -function loadWorldInfo() { - - var all_images = "" - var editor_div = $("#world_forms"); - - $.getScript('/static/minecraft/minecraft_struct.js', function () { - for (var key in block_names) { - var block_name = block_names[key]; - all_images = all_images + ''; - - editor_div.html('
    ' + all_images + '
    '); - } - }); - - editor_div.html('
    ' + all_images + '
    '); - - firstLayer = project.activeLayer; - secondLayer = new Layer(); - firstLayer.activate(); - - api.call('get_world_properties', { - world_uid: currentWorld - }, success = function (data) { - refreshWorldView(); - }, error = function (data) { - $.cookie('selected_world', '', { - expires: -1, - path: '/' - }); - dialogs.notification(data.Error, 'error'); - }); -}; - -function updateViewSize() { - view.draw(true); -} - diff --git a/micropsi_server/static/minecraft/minecraft_struct.js b/micropsi_server/static/minecraft/minecraft_struct.js deleted file mode 100644 index bbeabbc7..00000000 --- a/micropsi_server/static/minecraft/minecraft_struct.js +++ /dev/null @@ -1,200 +0,0 @@ -var block_names = { - "-1": "Emptiness", - "0": "Air", - "1": "Stone", - "2": "Grass", - "3": "Dirt", - "4": "Cobblestone", - "5": "Oak_Wood_Planks", - "6": "Sapling", - "7": "Bedrock", - "8": "Water", - "9": "Water", - "10": "Lava", - "11": "Lava", - "12": "Sand", - "13": "Gravel", - "14": "Gold_Ore", - "15": "Iron_Ore", - "16": "Coal_Ore", - "17": "Wood", - "18": "Leaves", - "19": "Sponge", - "20": "Glass", - "21": "Lapis_Lazuli_Ore", - "22": "Lapis_Lazuli_(Block)", - "23": "Dispenser", - "24": "Sandstone", - "25": "Note_Block", - "26": "Bed", - "27": "Powered_Rail_Off", - "28": "Detector_Rail", - "29": "Sticky_Piston", - "30": "WebBlock", - "31": "Tall_Grass", - "32": "Dead_Bush", - "33": "Piston", - "34": "Block_34", - "35": "White_Wool", - "37": "Dandelion", - "38": "Poppy", - "39": "Brown_Mushroom", - "40": "Red_Mushroom", - "41": "Gold_(Block)", - "42": "Iron_(Block)", - "43": "Double_Stone_Slab", - "44": "Stone_Slab", - "45": "Brick_(Block)", - "46": "TNT", - "47": "Bookshelf", - "48": "Moss_Stone", - "49": "Obsidian", - "50": "Torch", - "51": "Fire", - "52": "Monster_Spawner", - "53": "Wooden_Stairs", - "54": "Chest", - "55": "Redstone_(Wire,Inventory)", - "56": "Diamond_Ore", - "57": "Diamond_(Block)", - "58": "Crafting_Table", - "59": "Crops", - "60": "Farmland", - "61": "Furnace", - "62": "Furnace_(Active)", - "63": "Sign", - "64": "Wooden_Door", - "65": "Ladders", - "66": "Rails", - "67": "Cobblestone_Stairs", - "68": "Wall_Sign", - "69": "Lever", - "70": "Stone_Pressure_Plate", - "71": "Iron_Door", - "72": "Wooden_Pressure_Plate", - "73": "Redstone_Ore", - "74": "Redstone_Ore", - "75": "Redstone_(Torch,_Inactive)", - "76": "Redstone_(Torch,_Active)", - "77": "Stone_Button", - "78": "Snow", - "79": "Ice", - "80": "Snow_(Block)", - "81": "Cactus", - "82": "Clay_Block", - "83": "Sugar_Canes", - "84": "Jukebox", - "85": "Fence", - "86": "Pumpkin", - "87": "Netherrack", - "88": "Soul_Sand", - "89": "Glowstone_(Block)", - "90": "Portal", - "91": "Jack_o'Lantern", - "92": "Cake", - "93": "Redstone_(Repeater,_Inactive)", - "94": "Redstone_(Repeater,_Active)", - "95": "White_Stained_Glass", - "96": "Trapdoor", - "97": "Stone", - "98": "Stone_Brick", - "99": "BrownMushroomCap", - "100": "RedMushroomCap", - "101": "Iron_Bars", - "102": "Glass_Pane", - "103": "Melon_(Block)", - "104": "Seed_Stem", - "105": "Seed_Stem", - "106": "Vine", - "107": "Fence_Gate_(Closed)", - "108": "Brick_Stairs", - "109": "Stone_Brick_Stairs", - "110": "Mycelium", - "111": "Lily_Pad", - "112": "Nether_Brick", - "113": "Nether_Brick_Fence", - "114": "Nether_Brick_Stairs", - "115": "Nether_Wart", - "116": "Enchantment_Table", - "117": "Brewing_Stand", - "118": "Cauldron", - "119": "End_Portal", - "120": "End_Portal_Frame", - "121": "End_Stone", - "122": "Dragon_Egg", - "123": "Redstone_Lamp", - "124": "Redstone_Lamp_(Active)", - "125": "Oak_Wood_Planks", - "126": "Oak-Wood_Slab", - "127": "Cocoa_Plant", - "128": "Sandstone_Stairs", - "129": "Emerald_Ore", - "130": "Ender_Chest", - "131": "Tripwire_Hook", - "132": "Tripwire", - "133": "Block_of_Emerald", - "134": "Spruce_Wood_Stairs", - "135": "Birch_Wood_Stairs", - "136": "Jungle_Wood_Stairs", - "137": "Command_Block", - "138": "Beacon_Block", - "139": "Cobblestone_Wall", - "140": "Flower_Pot", - "141": "Carrot_(Block)", - "142": "Potatoes_(Block)", - "143": "Wooden_Button", - "144": "Skeleton_Skull", - "145": "Anvil", - "146": "Trapped_Chest", - "147": "Weighted_Pressure_Plate_(Light)", - "148": "Weighted_Pressure_Plate_(Heavy)", - "149": "Redstone_Comparator_(inactive)", - "150": "Redstone_Comparator_(active)", - "151": "Daylight_Sensor", - "152": "Block_of_Redstone", - "153": "Nether_Quartz_Ore", - "154": "Hopper", - "155": "Block_of_Quartz", - "156": "Quartz_Stairs", - "157": "Activator_Rail", - "158": "Dropper", - "159": "White_Stained_Clay", - "160": "White_Stained_Glass_Pane", - "161": "Leaves", - "162": "Acacia_Wood", - "163": "Acacia_Wood_Stairs", - "164": "Dark_Oak_Wood_Stairs", - "165": "Slime_Block", - "166": "Barrier", - "167": "Iron_Trapdoor", - "168": "Prismarine", - "169": "Sea_Lantern", - "170": "Hay_Block", - "171": "White_Carpet", - "172": "Hardened_Clay", - "173": "Block_of_Coal", - "174": "Packed_Ice", - "175": "Sunflower", - "176": "Free-standing_Banner_(Small)", - "177": "Wall-mounted_Banner_(Small)", - "178": "Inverted_Daylight_Sensor", - "179": "Red_Sandstone", - "180": "Red_Sandstone_Stairs", - "181": "Red_Sandstone", - "182": "Red_Sandstone_Slab", - "183": "Spruce_Fence_Gate_(Closed)", - "184": "Birch_Fence_Gate_(Closed)", - "185": "Jungle_Fence_Gate_(Closed)", - "186": "Dark_Oak_Fence_Gate_(Closed)", - "187": "Acacia_Fence_Gate_(Closed)", - "188": "Spruce_Fence", - "189": "Birch_Fence", - "190": "Jungle_Fence", - "191": "Dark_Oak_Fence", - "192": "Acacia_Fence", - "193": "Spruce_Door", - "194": "Birch_Door", - "195": "Jungle_Door", - "196": "Acacia_Door", - "197": "Dark_Oak_Door", -} \ No newline at end of file diff --git a/micropsi_server/static/timeseries/timeseries.js b/micropsi_server/static/timeseries/timeseries.js deleted file mode 100644 index e2a80a23..00000000 --- a/micropsi_server/static/timeseries/timeseries.js +++ /dev/null @@ -1,80 +0,0 @@ - -$(function(){ - - var container = $('#timeseries_controls'); - - var slider = $('#timeseries_slider'); - - var initialized = false; - - var first, last, total; - - var advance_nodenet = $('#timeseries_controls_nodenet'); - var nodenet_amount = $('#timeseries_controls_nodenet_amount') - - function get_world_data(){ - return {step: currentWorldSimulationStep}; - } - - $('.section.world .editor_field').height('auto'); - - function set_world_data(data){ - if(!initialized){ - first = new Date(data['first_timestamp']); - last = new Date(data['last_timestamp']); - total = data['total_timestamps']; - slider.slider({ - 'min': 0, - 'max': total - 1, - 'width': '100%', - 'step': 1, - 'value': data['current_step'], - 'tooltip': 'show', - 'handle': 'triangle', - 'selection': 'none', - 'formater': function(index){ - if (index > 0){ - var interval = parseInt((last.getTime() - first.getTime()) / total); - return new Date(first.getTime() + (interval * index)).toLocaleString('de'); - } else { - return first.toLocaleString('de'); - } - } - - }); - $('.firstval', container).html(first.toLocaleString('de').replace(', ', '
    ')); - $('.lastval', container).html(last.toLocaleString('de').replace(', ', '
    ')); - initialized = true; - slider.on('slideStop', set_world_state); - } - slider.slider('setValue', data['current_step']); - $('.world_step').text(data.current_step); - } - - function set_world_state(event){ - var value = parseInt(slider.val()); - api.call('set_world_data', {world_uid: currentWorld, data: {step: value}}, function(){ - if(advance_nodenet.attr('checked')){ - var nn_uid = (currentNodenet) ? currentNodenet : null; - api.call('step_nodenets_in_world', {world_uid: currentWorld, nodenet_uid: nn_uid, steps: parseInt(nodenet_amount.val())}, function(){ - if(nn_uid){ - $(document).trigger('runner_stepped'); - } else { - console.log('qwer'); - } - }); - } else { - get_world_state(); - } - }); - } - - function get_world_state(){ - api.call('get_world_view', {'world_uid': currentWorld, 'step': 0}, set_world_data); - } - - register_stepping_function('world', get_world_data, set_world_data); - - get_world_state(); - -}); \ No newline at end of file diff --git a/micropsi_server/static/timeseries/timeseries.tpl b/micropsi_server/static/timeseries/timeseries.tpl deleted file mode 100644 index d2be400d..00000000 --- a/micropsi_server/static/timeseries/timeseries.tpl +++ /dev/null @@ -1,24 +0,0 @@ - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Drag the slider to change the current point in time of the timeseries

    -

    - -

    - -
    -
    -
    -
    -

    8q`2V3{{8dE<_MaLGJ%yYhel4m5qj|Pat2o37Q9wj}MPln{0J-;7LN&$6KZr*00507$+ILR>Xhe$Pa?x(X>zCWrtKqR89%6-hCI@l47WMW^L;fil|FMVQ-F*-Bu@goR_( zU`tU&GE6of1n;|5lJYtEGTNo|YOai^0xaS>*|{p92#I&QKLEv_(WRR|_kaKHI+1m? z`nh=+Qzrs}^DA8ifh~*%h{~ZQbJ$dmp(+9~m;fRM<@*u(!}#Hshkt(k*>JG+a8g-y zjBPhh%r0Iyaq9egrdB%%0Kp;S8lvEk1;5=;Y)|2fBOXDBclJ3S=5(Y2+ zpZtxi)^4>si&u`HeEX6F8?k{6Be>wVEqHrolweN-JSZet^5|yTzkBO>@P8$lP}Z6{ zvv}de%=ydES~JLY6vB3EGzd?#_|aM#-@PSa@N)n77b%-Qvv{%Do`1K$yM8;~-?;Wq co&PWAFJ7T}F8T6yj{pDw07*qoM6N<$f)#5My8r+H diff --git a/micropsi_server/static/minecraft/block_textures/WebBlock.png b/micropsi_server/static/minecraft/block_textures/WebBlock.png deleted file mode 100644 index b1ba8e029a9f2d11774c59750c8078f24378ea37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 772 zcmV+f1N;1mP)P000>X1^@s6#OZ}&0008ZNkliZtUFrX|;5D|z7ZQH^*hwJr<*Vorg`*_Yl`o4z{0zwE75!2$F)Z0zHTT= zX^iI)59LXKRdMTfY03 zy3XfwXqqO}bsf&mY={e!quvj@4zY{l2u@u`?;n*3wHY~+VHh?S)jD9L3&5s5 z5pjn6ZL0zQ8r}CIHta)D6gP000gM1^@s6+B&yD0007gNklinv{DoWu$)YH>bs+9Vol=|B5_rHC1o%+nA=jZ2iHk%bn zsa!-vDP@^?L`1{Q&CP$seu>=O-K9|!6>%Kr%q(5kp>10*GomPhh*%;T0T^y zgaBp+5di?4bMW4Sh!z_##=v_Ir4*R?CyY-{PL9{s*8WVAhm9lZAt5fBlab7qV4RRbf0Hqby6zX0yNJIyg8;MWk3+S;=>IcO}a* zBuN7AJ>K8n7bjq5gb)@n&L4vJet|taJmB*3(!RaDjg(Tu^Yim>OBa81bd(ZNkt9hz z7z`xKGKdJQwP@QGN-1cqK}3tO`H60CZ!sQ^ZB}>hsf2`x<;}k&A>-F-T zogLZQ+5$7fT8pmhKtzb+7|uD|+}z;m>dIDCH3Bd^Jw5&P6aEMzqI_>}PqHinGoxu5 zlx2ysENxxaA2$CKGOxqK!&E6%BuSF5udmDX^|h_4YD7d!vHUOOdu%$Lewj|EUoS2$ cKK()d1&OFo4`ULT761SM07*qoM6N<$g2ekmX#fBK diff --git a/micropsi_server/static/minecraft/block_textures/Weighted_Pressure_Plate_(Light).png b/micropsi_server/static/minecraft/block_textures/Weighted_Pressure_Plate_(Light).png deleted file mode 100644 index 90d6d5e06bf72d55d534a040ecabeb54f23645ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 745 zcmVP000gM1^@s6+B&yD00088Nkl}%;u#V<-1 z1zn9TNE(FTS^|YCg`{RkW-`vld+$9iCJPg_ruf?&xZKNg&-tBmgeTDUZ*OPzM_9q) z^%G}rY#jBS9OY8)(VH3CDL`{V@{C3Sn^fkl>^$@Bf5ILM+1z?7bB;nuIUMt|(>X*G ziz6)T1>b4wF1jM3=?M6iZOTpOdtP%H`>B@n4dl$|p- z{{}k%={H&|Ll1z{&Xp#U{K`a;kl9>Q(_f|5Y zU0}@|6E9-G2;wd#EvWlQy^8_j6`TWc7#_r9W?%sk0~3fEFsVqfTsU)k!+rPF>+hdB zJ@*X~E+vbKfQUozpe?{a6+|EfBqc1RS?Lr^0zw3vBB=%fW)n?m{7R_PJC~L_(XZcm z$S*(t&hi_JoLgR`(RvD`4bq$?x*yFcH0QA}M5ZGwPKj!mQ`FpkGXNg)5lx+%dai^7cZQ|&&^}n#FFmA3^agDfq;ZDOh%a2m>MJ@Ooi2Z z1G?Q_7z|Q{NO|?fgN*~me{}iT%z0O|+l~CE-7$n`;#F^NsxMY4OjUf=1I3qVnVVfV)2p!rs^_g!0P000gM1^@s6+B&yD0009TNkl$<*0 zh#;w|3eGt+O@ky!@O_^N0ve9ft8Z*<{;TXCC1+Wqrtp0qaU8Q;jy42=*Q?bp{w8}MX*QeI^XH$~gb1}P%cT4Jd+53j!!Y1^ z9&T@M;rl)`O~Z6L#lpe@!Z3s+N$@-mlu|Yvx(&{=`m0x8cOOWu-d$N7+@4OSwK$HY z+1Xi$q6kqG;kqtLr4mF@1R(@*93zS%1VI4KIr8~DG);q|CIgK@bEaNrJ`2MZ|H8 zG)-}LcLz!-2q7q!%OHf1FbqHCT=?$Ai_c7{*XtpQB2=qYXqpC9Rk5_R1kO2p-^XM! zfv)QSfFwzfWf>Gj!FW7|X_{b+{S_q2DhMG+k_3+9{8=3UK$c~grisa9f+R^GNfLBjN0wz6jYhb>zQ*O{C376-ZJMU9 zTdh|2!Qi*Ix2;mCWG^o-*D95YWEchrAqav1Gcz;boI?-<^!t6}^LZ$Wf^w z0QIAzqmMhr$L#FvSd>zGWo4zN>$-Gua>9ZjXn3AiKR!PGe{bb6`}_Nr<2c`RI-NJ| dcKgW>@^@C>bXWDggwy~4002ovPDHLkV1f`pjFA8U diff --git a/micropsi_server/static/minecraft/block_textures/White_Stained_Clay.png b/micropsi_server/static/minecraft/block_textures/White_Stained_Clay.png deleted file mode 100644 index eccc680acad342076a0bd49486935c5c79f6fc07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1088 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 M07*qoM6N<$f_Y2AcmMzZ diff --git a/micropsi_server/static/minecraft/block_textures/Wooden_Button.png b/micropsi_server/static/minecraft/block_textures/Wooden_Button.png deleted file mode 100644 index 9b26b16b5d32f2a82f1f91ab0edb55fa18b8675c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1107 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 JPDHLkV1k4q>+}Es diff --git a/micropsi_server/static/minecraft/minecraft.js b/micropsi_server/static/minecraft/minecraft.js deleted file mode 100644 index 1bbf4e8b..00000000 --- a/micropsi_server/static/minecraft/minecraft.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * viewer for the world. - */ - - -worldscope = paper; - -var side_relation = 700/500; -var HEIGHT = 10; // it starts to look super weird with values over 20 and I have no idea why -var WIDTH = Math.round(side_relation * HEIGHT); - -var firstLayer ; -var secondLayer; - -var current_layer = 1; - -currentWorld = $.cookie('selected_world') || null; - -objectLayer = new Layer(); -objectLayer.name = 'ObjectLayer'; - -currentWorldSimulationStep = -1; - -if (currentWorld) { - setCurrentWorld(currentWorld); -} - -worldRunning = false; - -function get_world_data(){ - return {step: currentWorldSimulationStep}; -} -function set_world_data(data){ - currentWorldSimulationStep = data.current_step; - - agent_html = ''; - for (var key in data.agents) { - agent_html += ""+data.agents[key].name+ ' ('+data.agents[key].type+')'; - } - $('#world_agents_list table').html(agent_html); - - updateViewSize(); - if (worldRunning) { - refreshWorldView(); - } -} - -register_stepping_function('world', get_world_data, set_world_data); - - -refreshWorldView = function () { - worldscope.activate(); - api.call( - 'get_world_view', - {world_uid: currentWorld, step: currentWorldSimulationStep}, - success=set_world_data, - error=function (data, outcome, type) { - $.cookie('selected_world', '', { - expires: -1, - path: '/' - }); - worldRunning = false; - api.defaultErrorCallback(data, outcome, type) - } - ); -} - -function addAgent(worldobject) { - if (!(worldobject.uid in objects)) { - renderObject(worldobject); - objects[worldobject.uid] = worldobject; - } else { - redrawObject(objects[worldobject.uid]); - } - objects[worldobject.uid] = worldobject; - agentsList.html(agentsList.html() + '' + worldobject.name + ' (' + worldobject.type + ')'); - return worldobject; -} - -function setCurrentWorld(uid) { - currentWorld = uid; - $.cookie('selected_world', currentWorld, { - expires: 7, - path: '/' - }); - - api.call('get_world_properties', { - world_uid: currentWorld - }, success = function (data) { - refreshWorldView(); - $('#world').parent().html(''); - }, error = function (data) { - $.cookie('selected_world', '', { - expires: -1, - path: '/' - }); - dialogs.notification(data.Error, 'error'); - }); -}; - -function updateViewSize() { - view.draw(true); -} \ No newline at end of file diff --git a/micropsi_server/static/minecraft/minecraft.tpl b/micropsi_server/static/minecraft/minecraft.tpl deleted file mode 100644 index 0c919743..00000000 --- a/micropsi_server/static/minecraft/minecraft.tpl +++ /dev/null @@ -1,18 +0,0 @@ -

    \ No newline at end of file diff --git a/micropsi_server/static/vrep/vrep.js b/micropsi_server/static/vrep/vrep.js deleted file mode 100644 index a08ba720..00000000 --- a/micropsi_server/static/vrep/vrep.js +++ /dev/null @@ -1,40 +0,0 @@ - - -$(function(){ - - var container = $('#vrep_world'); - - var view = $('#vrep_world_view'); - - var initialized = false; - - function get_world_data(){ - return {step: currentWorldSimulationStep}; - } - - $('.section.world .editor_field').height('auto'); - - function set_world_data(data){ - var agent_html = ''; - for(var uid in data.agents){ - agent_html += '

    ' + data.agents[uid].name + ' ('+data.agents[uid].type+')'; - if(data.plots){ - if(uid in data.plots){ - agent_html += '
    '; - } - } - agent_html += '

    ' - - } - view.html(agent_html); - } - - function get_world_state(){ - api.call('get_world_view', {'world_uid': currentWorld, 'step': 0}, set_world_data); - } - - register_stepping_function('world', get_world_data, set_world_data); - - get_world_state(); - -}); diff --git a/micropsi_server/static/vrep/vrep.tpl b/micropsi_server/static/vrep/vrep.tpl deleted file mode 100644 index d5266b8c..00000000 --- a/micropsi_server/static/vrep/vrep.tpl +++ /dev/null @@ -1,7 +0,0 @@ -
    -
    -
    - -
    -
    -
    From 252ca44932ed1e18d7c594657d81957a6329b5b0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 24 Feb 2017 13:50:56 +0100 Subject: [PATCH 757/945] make world-asset lookup from web case insensitive --- micropsi_core/_runtime_api_world.py | 9 +++++++-- micropsi_server/micropsi_app.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index 7946dc7f..8cfac963 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -277,10 +277,15 @@ def import_world(worlddata, owner=None): return data['uid'] -def get_world_class_from_name(world_type): +def get_world_class_from_name(world_type, case_sensitive=True): """Returns the class from a world type, if it is known""" from micropsi_core.world.world import World - return micropsi_core.runtime.world_classes[world_type] + if case_sensitive: + return micropsi_core.runtime.world_classes[world_type] + else: + for key in micropsi_core.runtime.world_classes: + if key.lower() == world_type.lower(): + return micropsi_core.runtime.world_classes[key] def get_available_world_types(): diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index fd0814b3..8e338542 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -208,7 +208,7 @@ def server_static(filepath): @micropsi_app.route('/world_assets//') def server_static_world_asset(wtype, filepath): import inspect - world = runtime.get_world_class_from_name(wtype) + world = runtime.get_world_class_from_name(wtype, case_sensitive=False) return static_file(filepath, root=os.path.dirname(inspect.getfile(world))) From 6ab785095b3a1208b742db39a0559249db262b48 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 21 Mar 2017 19:41:59 +0100 Subject: [PATCH 758/945] fix initial display of step values --- micropsi_core/runtime.py | 2 ++ micropsi_server/static/js/nodenet.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 0034ba57..c42cbdea 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -433,6 +433,8 @@ def get_nodenet_metadata(nodenet_uid): 'rootnodespace': nodenet.get_nodespace(None).uid, 'resource_path': RESOURCE_PATH }) + if nodenet.world: + data['current_world_step'] = worlds[nodenet.world].current_step return True, data diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 6a00b435..664766d4 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -301,6 +301,8 @@ function setCurrentNodenet(uid, nodespace, changed){ linkLayer.removeChildren(); } $(document).trigger('nodenet_loaded', uid); + $('.nodenet_step').text(data.current_step || 0); + $('.world_step').text(data.current_world_step || 0); nodenet_data = data; nodenet_data['snap_to_grid'] = $.cookie('snap_to_grid') || viewProperties.snap_to_grid; From aa9d83013f2ca45dbdc7e8171dec3d4c6cec7f85 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 22 Mar 2017 16:31:30 +0100 Subject: [PATCH 759/945] notify world/nodenet on simulation start/stop --- micropsi_core/nodenet/nodenet.py | 6 ++++ micropsi_core/runtime.py | 35 +++++++++++++------ .../tests/test_runtime_world_basics.py | 12 +++++++ micropsi_core/world/world.py | 8 ++++- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 566a4a7d..aba9573c 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -227,6 +227,12 @@ def get_data(self, complete=False, include_links=True): }) return data + def simulation_started(self): + self.is_active = True + + def simulation_stopped(self): + self.is_active = False + @abstractmethod def get_nodes(self, nodespaces=[], node_uids=[], include_links=True, links_to_nodespaces=[]): """ diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index c42cbdea..2cddb5a5 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -161,7 +161,8 @@ def run(self): nodenet = nodenets[uid] if nodenet.is_active: if nodenet.check_stop_runner_condition(): - nodenet.is_active = False + stop_nodenetrunner(uid) + # nodenet.is_active = False continue log = True if self.profiler: @@ -170,14 +171,16 @@ def run(self): nodenet.timed_step() nodenet.update_monitors_and_recorders() except: - nodenet.is_active = False + stop_nodenetrunner(uid) + # nodenet.is_active = False logging.getLogger("agent.%s" % uid).error("Exception in Agent:", exc_info=1) MicropsiRunner.last_nodenet_exception[uid] = sys.exc_info() if nodenet.world and nodenet.current_step % runner['factor'] == 0: try: worlds[nodenet.world].step() except: - nodenet.is_active = False + stop_nodenetrunner(uid) + # nodenet.is_active = False logging.getLogger("world").error("Exception in Environment:", exc_info=1) MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() if self.profiler: @@ -241,7 +244,8 @@ def pause(self): def kill_runners(signal=None, frame=None): for uid in nodenets: if nodenets[uid].is_active: - nodenets[uid].is_active = False + stop_nodenetrunner(uid) + # nodenets[uid].is_active = False runner['runner'].resume() runner['running'] = False runner['runner'].join() @@ -609,8 +613,11 @@ def set_nodenet_properties(nodenet_uid, nodenet_name=None, worldadapter=None, wo def start_nodenetrunner(nodenet_uid): """Starts a thread that regularly advances the given nodenet by one step.""" - - nodenets[nodenet_uid].is_active = True + nodenet = get_nodenet(nodenet_uid) + nodenet.simulation_started() + # nodenets[nodenet_uid].is_active = True + if nodenet.world: + worlds[nodenet.world].simulation_started() if runner['runner'].paused: runner['runner'].resume() return True @@ -668,8 +675,12 @@ def get_is_nodenet_running(nodenet_uid): def stop_nodenetrunner(nodenet_uid): """Stops the thread for the given nodenet.""" nodenet = get_nodenet(nodenet_uid) - nodenet.is_active = False + nodenet.simulation_stopped() test = {nodenets[uid].is_active for uid in nodenets} + if nodenet.world: + test_world = {nodenets[uid].is_active and nodenets[uid].world == nodenet.world for uid in nodenets} + if True not in test_world: + worlds[nodenet.world].simulation_stopped() if True not in test: runner['runner'].pause() return True @@ -1294,7 +1305,9 @@ def user_prompt_response(nodenet_uid, node_uid, values, resume_nodenet): nodenet = get_nodenet(nodenet_uid) for key, value in values.items(): nodenet.get_node(node_uid).set_parameter(key, value) - nodenet.is_active = resume_nodenet + if resume_nodenet: + start_nodenetrunner(nodenet_uid) + # nodenet.is_active = resume_nodenet nodenet.user_prompt = None @@ -1777,7 +1790,8 @@ def reload_code(): for uid in nodenets: if nodenets[uid].is_active: runners[uid] = True - nodenets[uid].is_active = False + stop_nodenetrunner(uid) + # nodenets[uid].is_active = False # load code-directory if RESOURCE_PATH not in sys.path: @@ -1812,7 +1826,8 @@ def reload_code(): # restart previously active nodenets for uid in runners: - nodenets[uid].is_active = True + start_nodenetrunner(uid) + # nodenets[uid].is_active = True if len(errors) == 0: return True, [] diff --git a/micropsi_core/tests/test_runtime_world_basics.py b/micropsi_core/tests/test_runtime_world_basics.py index 2257ba9c..ddf44d48 100644 --- a/micropsi_core/tests/test_runtime_world_basics.py +++ b/micropsi_core/tests/test_runtime_world_basics.py @@ -47,6 +47,18 @@ def test_get_world_properties(runtime, default_world): assert default_world == wp["uid"] +def test_start_stop_simulation(runtime, default_world, default_nodenet): + nodenet = runtime.get_nodenet(default_nodenet) + world = runtime.load_world(default_world) + runtime.set_nodenet_properties(default_nodenet, worldadapter="Default", world_uid=default_world) + runtime.start_nodenetrunner(default_nodenet) + assert world.is_active + assert nodenet.is_active + runtime.stop_nodenetrunner(default_nodenet) + assert not world.is_active + assert not nodenet.is_active + + def test_get_worldadapters(runtime, default_world, default_nodenet): wa = runtime.get_worldadapters(default_world) assert 'Default' in wa diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index b00b7b8c..49cf9297 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -105,7 +105,7 @@ def __init__(self, filename, world_type="", name="", owner="", uid=None, engine= "current_step": 0, "config": config } - + self.is_active = False folder = self.__module__.split('.') folder.pop() folder = '.'.join(folder) @@ -143,6 +143,12 @@ def load(self): self.logger.warning("Wrong version of the world data") return False + def simulation_started(self): + self.is_active = True + + def simulation_stopped(self): + self.is_active = False + def get_available_worldadapters(self): """ return the list of instantiated worldadapters """ return self.supported_worldadapters From 69afb89cbdfb8e6bf79b45f206cd35ab284a4e6e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 4 Apr 2017 17:26:33 +0200 Subject: [PATCH 760/945] update numerictypes after modifying auto-generated nodetypes --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 312d3319..329dbd3b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1835,6 +1835,14 @@ def generate_worldadapter_flow_types(self, delete_existing=False): self.worldadapter_flow_nodes = {} + # update native modules numeric types, as these may have been set with a different native module + # node types list + for key, partition in self.partitions.items(): + native_module_ids = np.where(partition.allocated_nodes > MAX_STD_NODETYPE)[0] + for id in native_module_ids: + instance = self.get_node(node_to_id(id, partition.pid)) + partition.allocated_nodes[id] = get_numerical_node_type(instance.type, self.native_modules) + data = {} if self.worldadapter_instance and self.worldadapter_instance.generate_flow_modules: if self.worldadapter_instance.get_available_flow_datasources(): From 1e015245327a5ca64c650499bc1c55f487b8eaf1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 4 Apr 2017 17:56:08 +0200 Subject: [PATCH 761/945] fix updating numeric native_module types --- .../nodenet/theano_engine/theano_nodenet.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 329dbd3b..0192c8e2 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -112,6 +112,7 @@ def worldadapter_instance(self, _worldadapter_instance): self.native_module_definitions.update(flow_io_types) for key in flow_io_types: self.native_modules[key] = FlowNodetype(nodenet=self, **flow_io_types[key]) + self.update_numeric_native_module_types() self.generate_worldadapter_flow_instances() if self._worldadapter_instance: self._worldadapter_instance.nodenet = self @@ -1794,12 +1795,7 @@ def reload_native_modules(self, native_modules): partition.native_module_instances = new_native_module_instances - # update native modules numeric types, as these may have been set with a different native module - # node types list - native_module_ids = np.where(partition.allocated_nodes > MAX_STD_NODETYPE)[0] - for id in native_module_ids: - instance = self.get_node(node_to_id(id, partition.pid)) - partition.allocated_nodes[id] = get_numerical_node_type(instance.type, self.native_modules) + self.update_numeric_native_module_types() # recreate the deleted ones. Gate configurations and links will not be transferred. for uid, data in instances_to_recreate.items(): @@ -1816,6 +1812,18 @@ def reload_native_modules(self, native_modules): # recompile flow_graphs: self.update_flow_graphs() + def update_numeric_native_module_types(self): + """ + update native modules numeric types if the types have been updated + either due to reload_native_modules, or due to changing the worldadapter + """ + import pdb; pdb.set_trace() + for key, partition in self.partitions.items(): + native_module_ids = np.where(partition.allocated_nodes > MAX_STD_NODETYPE)[0] + for id in native_module_ids: + instance = self.get_node(node_to_id(id, partition.pid)) + partition.allocated_nodes[id] = get_numerical_node_type(instance.type, self.native_modules) + def generate_worldadapter_flow_types(self, delete_existing=False): """ returns native_module_definitions for datasources and targets from the configured worldadapter""" @@ -1835,14 +1843,6 @@ def generate_worldadapter_flow_types(self, delete_existing=False): self.worldadapter_flow_nodes = {} - # update native modules numeric types, as these may have been set with a different native module - # node types list - for key, partition in self.partitions.items(): - native_module_ids = np.where(partition.allocated_nodes > MAX_STD_NODETYPE)[0] - for id in native_module_ids: - instance = self.get_node(node_to_id(id, partition.pid)) - partition.allocated_nodes[id] = get_numerical_node_type(instance.type, self.native_modules) - data = {} if self.worldadapter_instance and self.worldadapter_instance.generate_flow_modules: if self.worldadapter_instance.get_available_flow_datasources(): From adecd494b26a067e53737bf80c21a135c1e40009 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 4 Apr 2017 17:56:41 +0200 Subject: [PATCH 762/945] reload nodetypes when changing worldadapter in frontend --- micropsi_server/static/js/nodenet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_server/static/js/nodenet.js b/micropsi_server/static/js/nodenet.js index 664766d4..c43aa435 100644 --- a/micropsi_server/static/js/nodenet.js +++ b/micropsi_server/static/js/nodenet.js @@ -4020,8 +4020,8 @@ function handleEditNodenet(event){ if(reload){ window.location.reload(); } else { - // setCurrentNodenet(currentNodenet, currentNodeSpace); - refreshNodespace(); + setCurrentNodenet(currentNodenet, currentNodeSpace, true); + // refreshNodespace(); } } ); From 40a47752769fbd1104dbf580b52d081e0dc0d04d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 6 Apr 2017 14:13:49 +0200 Subject: [PATCH 763/945] remove pdb --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 0192c8e2..dacb6f4b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1817,7 +1817,6 @@ def update_numeric_native_module_types(self): update native modules numeric types if the types have been updated either due to reload_native_modules, or due to changing the worldadapter """ - import pdb; pdb.set_trace() for key, partition in self.partitions.items(): native_module_ids = np.where(partition.allocated_nodes > MAX_STD_NODETYPE)[0] for id in native_module_ids: From 264538989ac972cf67e36d32f8da63cb0ec763f3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 6 Apr 2017 16:35:54 +0200 Subject: [PATCH 764/945] filter out agents whose nodenet got deleted If we delete a nodenet, but do not save the world, the world will still have the agent registered. --- micropsi_core/world/world.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index 49cf9297..f39332f8 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -167,6 +167,9 @@ def initialize_world(self, data=None): else: self.logger.warning('Worldobject of type %s not supported anymore. Deleting object of this type.' % object_data['type']) del data['objects'][uid] + for uid in list(self.data['agents']): + if uid not in micropsi_core.runtime.nodenet_data: + del self.data['agents'][uid] def step(self): """ advance the simluation """ From 0d619dcddaf18ab074d60e75c0c95df78a6c8967 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 6 Apr 2017 16:36:50 +0200 Subject: [PATCH 765/945] more api similarity between ArrayWA and DictWA --- micropsi_core/world/worldadapter.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 815ab9c9..a42b923d 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -98,6 +98,15 @@ def __init__(self, world, uid=None, config={}, **data): def initialize_worldobject(self, data): pass + def add_datasource(self, name, initial_value=0.0): + """ add a datasource """ + self.datasources[name] = initial_value + + def add_datatarget(self, name, initial_value=0.0): + """ add a datatarget """ + self.datatargets[name] = initial_value + self.datatarget_feedback[name] = 0.0 + def get_available_datasources(self): """returns a list of identifiers of the datasources available for this world adapter""" return sorted(list(self.datasources.keys())) From 64096a4b04520aba34af266b8f3ac04a0487b78b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 10 Apr 2017 12:37:21 +0200 Subject: [PATCH 766/945] do not deliver monitor values in nodenet_metadata --- micropsi_core/nodenet/monitor.py | 28 +++++++++++++++------------- micropsi_core/nodenet/nodenet.py | 4 ++-- micropsi_core/runtime.py | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index f3a8851c..aed0ee44 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -33,14 +33,16 @@ def __init__(self, nodenet, name='', uid=None, color=None, values={}): self.name = name or "some monitor" self.color = color or "#%02d%02d%02d" % (random.randint(0,99), random.randint(0,99), random.randint(0,99)) - def get_data(self): - return { + def get_data(self, with_values=True): + data = { "uid": self.uid, - "values": self.values, "name": self.name, "color": self.color, "classname": self.__class__.__name__ } + if with_values: + data["values"] = self.values + return data @abstractmethod def getvalue(self): @@ -67,8 +69,8 @@ def __init__(self, nodenet, nodespace, name, name_prefix='', node_uids=[], gate= else: self.nodenet.group_nodes_by_ids(nodespace, node_uids, name, gatetype=gate) - def get_data(self): - data = super().get_data() + def get_data(self, with_values=True): + data = super().get_data(with_values=with_values) data.update({ "nodespace": self.nodespace, "node_uids": self.node_uids, @@ -93,8 +95,8 @@ def __init__(self, nodenet, node_uid, type, target, name=None, uid=None, color=N self.type = type self.target = target or 'gen' - def get_data(self): - data = super().get_data() + def get_data(self, with_values=True): + data = super().get_data(with_values=with_values) data.update({ "node_uid": self.node_uid, "type": self.type, @@ -127,8 +129,8 @@ def __init__(self, nodenet, source_node_uid, gate_type, target_node_uid, slot_ty self.gate_type = gate_type self.slot_type = slot_type - def get_data(self): - data = super().get_data() + def get_data(self, with_values=True): + data = super().get_data(with_values=with_values) data.update({ "source_node_uid": self.source_node_uid, "target_node_uid": self.target_node_uid, @@ -163,8 +165,8 @@ def __init__(self, nodenet, modulator, name=None, uid=None, color=None, values={ self.modulator = modulator self.nodenet = nodenet - def get_data(self): - data = super().get_data() + def get_data(self, with_values=True): + data = super().get_data(with_values=with_values) data.update({ "modulator": self.modulator }) @@ -181,8 +183,8 @@ def __init__(self, nodenet, function, name=None, uid=None, color=None, values={} self.function = function self.compiled_function = micropsi_core.tools.create_function(self.function, parameters="netapi") - def get_data(self): - data = super().get_data() + def get_data(self, with_values=True): + data = super().get_data(with_values=with_values) data.update({ "function": self.function, }) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index aba9573c..e49c81d1 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -676,10 +676,10 @@ def update_monitors_and_recorders(self): for uid in self._recorders: self._recorders[uid].step(self.current_step) - def construct_monitors_dict(self): + def construct_monitors_dict(self, with_values=True): data = {} for monitor_uid in self._monitors: - data[monitor_uid] = self._monitors[monitor_uid].get_data() + data[monitor_uid] = self._monitors[monitor_uid].get_data(with_values=with_values) return data def construct_recorders_dict(self): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 2cddb5a5..2bf41772 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -433,7 +433,7 @@ def get_nodenet_metadata(nodenet_uid): 'nodespaces': nodenet.construct_nodespaces_dict(None, transitive=True), 'native_modules': nodenet.get_native_module_definitions(), 'flow_modules': nodenet.get_flow_module_definitions(), - 'monitors': nodenet.construct_monitors_dict(), + 'monitors': nodenet.construct_monitors_dict(with_values=False), 'rootnodespace': nodenet.get_nodespace(None).uid, 'resource_path': RESOURCE_PATH }) From 55a46c2b3b97322675a1d6c2db1821cb693af641 Mon Sep 17 00:00:00 2001 From: cknd Date: Mon, 10 Apr 2017 14:41:43 +0200 Subject: [PATCH 767/945] use the coloredlogs package --- micropsi_core/micropsi_logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/micropsi_logger.py b/micropsi_core/micropsi_logger.py index 2d0716e3..d0f0232b 100644 --- a/micropsi_core/micropsi_logger.py +++ b/micropsi_core/micropsi_logger.py @@ -7,6 +7,7 @@ import os import logging +import coloredlogs import time from operator import itemgetter @@ -101,6 +102,7 @@ def register_logger(self, name, level): if self.filehandler: logging.getLogger(name).addHandler(self.filehandler) self.loggers[name].debug("Logger %s ready" % name) + coloredlogs.install(logger=self.loggers[name]) def unregister_logger(self, name): logging.getLogger(name).removeHandler(self.handlers[name]) From cd9a608780cfeee25d4f934724db8e0f949e06f7 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 10 Apr 2017 16:26:22 +0200 Subject: [PATCH 768/945] make coloredlogs an optional dependency --- micropsi_core/micropsi_logger.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/micropsi_core/micropsi_logger.py b/micropsi_core/micropsi_logger.py index d0f0232b..a16a2c6a 100644 --- a/micropsi_core/micropsi_logger.py +++ b/micropsi_core/micropsi_logger.py @@ -7,12 +7,17 @@ import os import logging -import coloredlogs import time from operator import itemgetter MAX_RECORDS_PER_STORAGE = 1000 +# optionally color log console log output +try: + import coloredlogs +except ImportError: + coloredlogs = None + class RecordWebStorageHandler(logging.Handler): @@ -102,7 +107,8 @@ def register_logger(self, name, level): if self.filehandler: logging.getLogger(name).addHandler(self.filehandler) self.loggers[name].debug("Logger %s ready" % name) - coloredlogs.install(logger=self.loggers[name]) + if coloredlogs is not None: + coloredlogs.install(logger=self.loggers[name]) def unregister_logger(self, name): logging.getLogger(name).removeHandler(self.handlers[name]) From 6fb1c44c2901a81887784a647e6edd91a27aa4d3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 10 Apr 2017 16:27:28 +0200 Subject: [PATCH 769/945] add coloredlogs to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a12be54a..a371adcc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ beautifulsoup4==4.4.1 CherryPy==5.4.0 +coloredlogs==6.0 coverage==4.0.3 cycler==0.10.0 matplotlib==1.5.1 From 89637c0a227ed08732c819ab0d57169abb093653 Mon Sep 17 00:00:00 2001 From: cknd Date: Tue, 11 Apr 2017 17:21:24 +0200 Subject: [PATCH 770/945] allow world to import modules from the worlds directory, e.g. from the worlds/shared_utils folder --- micropsi_core/runtime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 2cddb5a5..a575a349 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1851,6 +1851,8 @@ def initialize(persistency_path=None, resource_path=None, world_path=None): RESOURCE_PATH = resource_path or cfg['paths']['agent_directory'] WORLD_PATH = world_path or cfg['paths']['world_directory'] + sys.path.append(WORLD_PATH) + configs = config.ConfigurationManager(cfg['paths']['server_settings_path']) if logger is None: From d73e486244ed32f4cca171872c165304bc6b8a71 Mon Sep 17 00:00:00 2001 From: cknd Date: Wed, 12 Apr 2017 14:29:05 +0200 Subject: [PATCH 771/945] option to drop to pdb in more places where exception can occur --- .../nodenet/theano_engine/theano_nodenet.py | 15 +++++++++++++++ micropsi_core/runtime.py | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index dacb6f4b..f6f8ac50 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -14,6 +14,12 @@ import scipy import networkx as nx +import sys +try: + import ipdb as pdb +except ImportError: + import pdb + from micropsi_core.tools import OrderedSet from micropsi_core.nodenet import monitor from micropsi_core.nodenet import recorder @@ -31,6 +37,8 @@ from configuration import config as settings + + STANDARD_NODETYPES = { "Comment": { "name": "Comment", @@ -1067,6 +1075,10 @@ def compile_flow_subgraph(self, node_uids, requested_outputs=None, use_different original_outex = node.build(*buildargs) except Exception as err: self.logger.error("Error in buildfunction of Flowodule %s.\n %s: %s" % (str(node), err.__class__.__name__, str(err))) + if settings['micropsi2'].get('on_exception') == 'debug': + _, _, tb = sys.exc_info() + pdb.post_mortem(tb) + skip = True break @@ -1730,6 +1742,9 @@ def reload_native_modules(self, native_modules): self.native_module_definitions[key] = data except Exception as err: self.logger.error("Can not instantiate node type %s: %s: %s" % (key, err.__class__.__name__, str(err))) + if settings['micropsi2'].get('on_exception') == 'debug': + _, _, tb = sys.exc_info() + pdb.post_mortem(tb) for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index a575a349..ab513f76 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -27,11 +27,15 @@ from datetime import datetime, timedelta import time import signal - import logging from .micropsi_logger import MicropsiLogger +try: + import ipdb as pdb +except ImportError: + import pdb + NODENET_DIRECTORY = "nodenets" WORLD_DIRECTORY = "worlds" @@ -174,6 +178,9 @@ def run(self): stop_nodenetrunner(uid) # nodenet.is_active = False logging.getLogger("agent.%s" % uid).error("Exception in Agent:", exc_info=1) + if cfg['micropsi2'].get('on_exception') == 'debug': + _, _, tb = sys.exc_info() + pdb.post_mortem(tb) MicropsiRunner.last_nodenet_exception[uid] = sys.exc_info() if nodenet.world and nodenet.current_step % runner['factor'] == 0: try: @@ -183,6 +190,9 @@ def run(self): # nodenet.is_active = False logging.getLogger("world").error("Exception in Environment:", exc_info=1) MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() + if cfg['micropsi2'].get('on_exception') == 'debug': + _, _, tb = sys.exc_info() + pdb.post_mortem(tb) if self.profiler: self.profiler.disable() From 078e763cb9f3afd1c22a62e381c05855637adcce Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 1 May 2017 15:28:53 +0200 Subject: [PATCH 772/945] wow. how did this go unnoticed? fix gatefunction bug, strenghten tests --- .../nodenet/theano_engine/theano_partition.py | 2 +- micropsi_core/tests/test_node_activation.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 4d8ff42f..63d03e77 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -998,7 +998,7 @@ def load_data(self, nodes_data): self.has_gatefunction_relu = GATE_FUNCTION_RELU in g_function_selector self.has_gatefunction_one_over_x = GATE_FUNCTION_DIST in g_function_selector self.has_gatefunction_elu = GATE_FUNCTION_ELU in g_function_selector - self.has_gatefunction_elu = GATE_FUNCTION_THRESHOLD in g_function_selector + self.has_gatefunction_threshold = GATE_FUNCTION_THRESHOLD in g_function_selector else: self.logger.warning("no g_function_selector in file, falling back to defaults") diff --git a/micropsi_core/tests/test_node_activation.py b/micropsi_core/tests/test_node_activation.py index 4b32ee27..ede2ce7c 100644 --- a/micropsi_core/tests/test_node_activation.py +++ b/micropsi_core/tests/test_node_activation.py @@ -42,8 +42,13 @@ def test_gate_arithmetics_minimum(runtime, test_nodenet): "minimum": 1.5, } register.set_gate_configuration("gen", "threshold", params) + runtime.save_nodenet(test_nodenet) net.step() assert register.get_gate("gen").activation == 1.5 + runtime.revert_nodenet(test_nodenet) + net = runtime.nodenets[test_nodenet] + net.step() + assert net.get_node(register.uid).get_gate("gen").activation == 1.5 def test_gate_arithmetics_threshold(runtime, test_nodenet): @@ -145,40 +150,65 @@ def test_gatefunction_sigmoid(runtime, test_nodenet): from micropsi_core.nodenet.gatefunctions import sigmoid net, netapi, source, register = prepare(runtime, test_nodenet) register.set_gate_configuration("gen", "sigmoid", {'bias': 1.2}) + runtime.save_nodenet(test_nodenet) net.step() assert round(register.get_gate("gen").activation, 5) == round(sigmoid(1, 1.2), 5) + runtime.revert_nodenet(test_nodenet) + net = runtime.nodenets[test_nodenet] + net.step() + assert round(net.get_node(register.uid).get_gate("gen").activation, 5) == round(sigmoid(1, 1.2), 5) def test_gatefunction_elu(runtime, test_nodenet): from micropsi_core.nodenet.gatefunctions import elu net, netapi, source, register = prepare(runtime, test_nodenet) register.set_gate_configuration("gen", "elu", {'bias': 1.2}) + runtime.save_nodenet(test_nodenet) net.step() assert round(register.get_gate("gen").activation, 5) == round(elu(1, 1.2), 5) + runtime.revert_nodenet(test_nodenet) + net = runtime.nodenets[test_nodenet] + net.step() + assert round(net.get_node(register.uid).get_gate("gen").activation, 5) == round(elu(1, 1.2), 5) def test_gatefunction_relu(runtime, test_nodenet): from micropsi_core.nodenet.gatefunctions import relu net, netapi, source, register = prepare(runtime, test_nodenet) register.set_gate_configuration("gen", "relu", {'bias': 1.2}) + runtime.save_nodenet(test_nodenet) net.step() assert round(register.get_gate("gen").activation, 5) == round(relu(1, 1.2), 5) + runtime.revert_nodenet(test_nodenet) + net = runtime.nodenets[test_nodenet] + net.step() + assert round(net.get_node(register.uid).get_gate("gen").activation, 5) == round(relu(1, 1.2), 5) def test_gatefunction_on_over_x(runtime, test_nodenet): from micropsi_core.nodenet.gatefunctions import one_over_x net, netapi, source, register = prepare(runtime, test_nodenet) register.set_gate_configuration("gen", "one_over_x",) + runtime.save_nodenet(test_nodenet) net.step() assert round(register.get_gate("gen").activation, 5) == round(one_over_x(1), 5) + runtime.revert_nodenet(test_nodenet) + net = runtime.nodenets[test_nodenet] + net.step() + assert round(net.get_node(register.uid).get_gate("gen").activation) == round(one_over_x(1), 5) def test_gatefunction_absolute(runtime, test_nodenet): from micropsi_core.nodenet.gatefunctions import absolute net, netapi, source, register = prepare(runtime, test_nodenet) register.set_gate_configuration("gen", "absolute") + runtime.save_nodenet(test_nodenet) net.step() assert round(register.get_gate("gen").activation, 5) == round(absolute(1), 5) + runtime.revert_nodenet(test_nodenet) + net = runtime.nodenets[test_nodenet] + net.step() + assert round(net.get_node(register.uid).get_gate("gen").activation, 5) == round(absolute(1), 5) def test_gatefunction_none_is_identity(runtime, test_nodenet): From fee219b47ecd428abec2c7ebf5a3002e28268df3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 1 May 2017 15:29:29 +0200 Subject: [PATCH 773/945] drive-by-pep8 --- micropsi_core/nodenet/theano_engine/theano_partition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 63d03e77..a525da6d 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1155,7 +1155,7 @@ def grow_number_of_elements(self, growby): self.g_min.set_value(new_g_min, borrow=True) new_g_max = np.ones(new_NoE, dtype=self.nodenet.numpyfloatX) - new_g_max[0:self.NoE] = self.g_max.get_value(borrow=True) + new_g_max[0:self.NoE] = self.g_max.get_value(borrow=True) self.g_max.set_value(new_g_max, borrow=True) new_g_function_selector = np.zeros(new_NoE, dtype=np.int8) @@ -1373,7 +1373,7 @@ def create_node(self, nodetype, nodespace_id, id=None, parameters=None, gate_con # initialize activation to zero a_array = self.a.get_value(borrow=True) - for element in range (0, get_elements_per_type(get_numerical_node_type(nodetype, self.nodenet.native_modules), self.nodenet.native_modules)): + for element in range(0, get_elements_per_type(get_numerical_node_type(nodetype, self.nodenet.native_modules), self.nodenet.native_modules)): a_array[offset + element] = 0 self.a.set_value(a_array) From d41a419cc27df2eac9031dd19e4b3ab97f3a9e85 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 1 May 2017 15:31:41 +0200 Subject: [PATCH 774/945] fix default WA: always write feedback otherwise, a once written value persists as feedback --- micropsi_core/world/worldadapter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index a42b923d..ec8115b5 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -183,8 +183,7 @@ def __init__(self, world, uid=None, config={}, **data): def update_data_sources_and_targets(self): import random - if self.datatargets['echo'] != 0: - self.datatarget_feedback['echo'] = self.datatargets['echo'] + self.datatarget_feedback['echo'] = self.datatargets['echo'] self.datasources['static_on'] = 1 self.datasources['random'] = random.uniform(0, 1) From ebf50f54276fd810692d828d22a4d6b437a5f838 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 1 May 2017 15:34:15 +0200 Subject: [PATCH 775/945] fix netapi_fragment: gate_config is a dict --- micropsi_core/runtime.py | 2 +- micropsi_core/tests/test_runtime.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 7e724fc1..fefecdbb 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1034,7 +1034,7 @@ def generate_netapi_fragment(nodenet_uid, node_uids): gate_config = node.get_gate_configuration() for gatetype, gconfig in gate_config.items(): - lines.append("%s.set_gate_configuration('%s', \"%s\", %.2f)" % (varname, gatetype, gconfig['gatefunction'], gconfig.get('gatefunction_parameters', {}))) + lines.append("%s.set_gate_configuration('%s', \"%s\", %s)" % (varname, gatetype, gconfig['gatefunction'], str(gconfig.get('gatefunction_parameters', {})))) nps = node.clone_parameters() for parameter, value in nps.items(): diff --git a/micropsi_core/tests/test_runtime.py b/micropsi_core/tests/test_runtime.py index e9fe085e..4e016df7 100644 --- a/micropsi_core/tests/test_runtime.py +++ b/micropsi_core/tests/test_runtime.py @@ -184,7 +184,9 @@ def test_generate_netapi_fragment(runtime, test_nodenet, engine, resourcepath): nodes.extend([p1, p2]) netapi.link_with_reciprocal(p1, p2, t) reg = netapi.create_node('Neuron', None, 'reg') + reg.set_gate_configuration('gen', 'threshold', {'amplification': 2}) netapi.link(reg, 'gen', nodes[0], 'gen') + nodes.append(reg) # remember their names names = [n.name for n in nodes] fragment = runtime.generate_netapi_fragment(test_nodenet, [n.uid for n in nodes]) From 38648625acec7e3c6f17cd96095d17e6d8b68cc1 Mon Sep 17 00:00:00 2001 From: cknd Date: Thu, 11 May 2017 14:48:53 +0200 Subject: [PATCH 776/945] stop duplicate log outputs by removing the default handler before we add our own handlers --- micropsi_core/micropsi_logger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/micropsi_core/micropsi_logger.py b/micropsi_core/micropsi_logger.py index a16a2c6a..b9143b46 100644 --- a/micropsi_core/micropsi_logger.py +++ b/micropsi_core/micropsi_logger.py @@ -83,6 +83,8 @@ def __init__(self, default_logging_levels={}, log_to_file=False): datefmt='%d.%m. %H:%M:%S' ) + logging.getLogger().handlers = [] + self.log_to_file = log_to_file self.filehandler = None if log_to_file: @@ -91,6 +93,7 @@ def __init__(self, default_logging_levels={}, log_to_file=False): self.filehandler = logging.FileHandler(self.log_to_file, mode='a') formatter = logging.Formatter(self.default_format) self.filehandler.setFormatter(formatter) + self.filehandler.set_name('filehandler') self.register_logger("system", self.logging_levels.get(default_logging_levels.get('system', {}), logging.WARNING)) self.register_logger("world", self.logging_levels.get(default_logging_levels.get('world', {}), logging.WARNING)) @@ -100,7 +103,6 @@ def register_logger(self, name, level): self.loggers[name].setLevel(level) self.record_storage[name] = [] self.handlers[name] = RecordWebStorageHandler(self.record_storage, name) - formatter = logging.Formatter(self.default_format) self.handlers[name].setFormatter(formatter) logging.getLogger(name).addHandler(self.handlers[name]) From 6f0097722aa90d8348a625acc6baffdc991e0101 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 15 May 2017 11:24:52 +0200 Subject: [PATCH 777/945] use the traceback from the exception --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index f6f8ac50..0d31cca6 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -14,7 +14,6 @@ import scipy import networkx as nx -import sys try: import ipdb as pdb except ImportError: @@ -1076,8 +1075,7 @@ def compile_flow_subgraph(self, node_uids, requested_outputs=None, use_different except Exception as err: self.logger.error("Error in buildfunction of Flowodule %s.\n %s: %s" % (str(node), err.__class__.__name__, str(err))) if settings['micropsi2'].get('on_exception') == 'debug': - _, _, tb = sys.exc_info() - pdb.post_mortem(tb) + pdb.post_mortem(err.__traceback__) skip = True break @@ -1743,8 +1741,7 @@ def reload_native_modules(self, native_modules): except Exception as err: self.logger.error("Can not instantiate node type %s: %s: %s" % (key, err.__class__.__name__, str(err))) if settings['micropsi2'].get('on_exception') == 'debug': - _, _, tb = sys.exc_info() - pdb.post_mortem(tb) + pdb.post_mortem(err.__traceback__) for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): From ab021c0b0dd8b1e00a2e5f54913e930303714a16 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 15 May 2017 11:27:07 +0200 Subject: [PATCH 778/945] drive-by pep8 --- .../nodenet/theano_engine/theano_nodenet.py | 2 -- micropsi_core/runtime.py | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 0d31cca6..3c73e190 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -36,8 +36,6 @@ from configuration import config as settings - - STANDARD_NODETYPES = { "Comment": { "name": "Comment", diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 45f231ee..cc8da3fe 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -70,14 +70,14 @@ class FileCacher(): - "Cache the stdout text so we can analyze it before returning it" + """Cache the stdout text so we can analyze it before returning it""" def __init__(self): self.reset() def reset(self): self.out = [] - def write(self,line): + def write(self, line): self.out.append(line) def flush(self): @@ -87,7 +87,7 @@ def flush(self): class NetapiShell(InteractiveConsole): - "Wrapper around Python that can filter input/output to the shell" + """Wrapper around Python that can filter input/output to the shell""" def __init__(self, netapi): self.stdout = sys.stdout self.stderr = sys.stderr @@ -104,11 +104,11 @@ def return_output(self): sys.stdout = self.stdout sys.stderr = self.stderr - def push(self,line): + def push(self, line): self.get_output() - incomplete = InteractiveConsole.push(self,line) + incomplete = InteractiveConsole.push(self, line) if incomplete: - InteractiveConsole.push(self,'\n') + InteractiveConsole.push(self, '\n') self.return_output() err = self.errcache.flush() if err and err.startswith('Traceback'): From 9a8ee58dcb29558ba2b6bfe079d1f45587356e41 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 15 May 2017 11:28:15 +0200 Subject: [PATCH 779/945] catch all exceptions when parsing user code --- micropsi_core/runtime.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index cc8da3fe..30b5453c 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1646,7 +1646,7 @@ def parse_world_definitions(path): if World in inspect.getmro(cls) and name != "World": world_classes[name] = cls logging.getLogger("system").debug("Found world %s " % name) - except (SyntaxError, ImportError, SystemError) as e: + except Exception as e: errors.append("%s when importing world file %s: %s" % (e.__class__.__name__, relpath, str(e))) for w in worldadapterfiles: relpath = os.path.relpath(os.path.join(base_path, w), start=WORLD_PATH) @@ -1658,7 +1658,7 @@ def parse_world_definitions(path): if WorldAdapter in inspect.getmro(cls) and not inspect.isabstract(cls): worldadapter_classes[name] = cls # errors.append("Name collision in worldadapters: %s defined more than once" % name) - except (SyntaxError, ImportError, SystemError) as e: + except Exception as e: errors.append("%s when importing worldadapter file %s: %s" % (e.__class__.__name__, relpath, str(e))) for w in worldobjectfiles: relpath = os.path.relpath(os.path.join(base_path, w), start=WORLD_PATH) @@ -1670,7 +1670,7 @@ def parse_world_definitions(path): if WorldObject in inspect.getmro(cls) and WorldAdapter not in inspect.getmro(cls): worldobject_classes[name] = cls # errors.append("Name collision in worldadapters: %s defined more than once" % name) - except (SyntaxError, ImportError, SystemError) as e: + except Exception as e: errors.append("%s when importing worldobject file %s: %s" % (e.__class__.__name__, relpath, str(e))) return errors or None @@ -1715,7 +1715,7 @@ def parse_recipe_or_operations_file(path, mode, category_overwrite=False): recipes = loader.load_module() # recipes = __import__(pyname, fromlist=['recipes']) # importlib.reload(sys.modules[pyname]) - except (SyntaxError, ImportError, SystemError) as e: + except Exception as e: return "%s when importing %s file %s: %s" % (e.__class__.__name__, mode, relpath, str(e)) for name, module in inspect.getmembers(recipes, inspect.ismodule): From e22f882143de1492fae4eb0214c3870f78bacff4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 15 May 2017 11:28:48 +0200 Subject: [PATCH 780/945] drop into prompt on exception during parsing/importing user code --- micropsi_core/runtime.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 30b5453c..57a3746e 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1648,6 +1648,8 @@ def parse_world_definitions(path): logging.getLogger("system").debug("Found world %s " % name) except Exception as e: errors.append("%s when importing world file %s: %s" % (e.__class__.__name__, relpath, str(e))) + if cfg['micropsi2'].get('on_exception') == 'debug': + pdb.set_trace() for w in worldadapterfiles: relpath = os.path.relpath(os.path.join(base_path, w), start=WORLD_PATH) name = w[:-3] @@ -1660,6 +1662,8 @@ def parse_world_definitions(path): # errors.append("Name collision in worldadapters: %s defined more than once" % name) except Exception as e: errors.append("%s when importing worldadapter file %s: %s" % (e.__class__.__name__, relpath, str(e))) + if cfg['micropsi2'].get('on_exception') == 'debug': + pdb.set_trace() for w in worldobjectfiles: relpath = os.path.relpath(os.path.join(base_path, w), start=WORLD_PATH) name = w[:-3] @@ -1672,6 +1676,8 @@ def parse_world_definitions(path): # errors.append("Name collision in worldadapters: %s defined more than once" % name) except Exception as e: errors.append("%s when importing worldobject file %s: %s" % (e.__class__.__name__, relpath, str(e))) + if cfg['micropsi2'].get('on_exception') == 'debug': + pdb.set_trace() return errors or None @@ -1695,6 +1701,8 @@ def parse_native_module_file(path): logging.getLogger("system").warning("Native module names must be unique. %s is not." % moduledef['name']) native_modules[moduledef['name']] = moduledef except Exception as e: + if cfg['micropsi2'].get('on_exception') == 'debug': + pdb.set_trace() return "%s when importing nodetype file %s: %s" % (e.__class__.__name__, relpath, str(e)) @@ -1716,6 +1724,8 @@ def parse_recipe_or_operations_file(path, mode, category_overwrite=False): # recipes = __import__(pyname, fromlist=['recipes']) # importlib.reload(sys.modules[pyname]) except Exception as e: + if cfg['micropsi2'].get('on_exception') == 'debug': + pdb.post_mortem(e.__traceback__) return "%s when importing %s file %s: %s" % (e.__class__.__name__, mode, relpath, str(e)) for name, module in inspect.getmembers(recipes, inspect.ismodule): From 3047cae673fb666a5cf6546c35ce39d5499eed03 Mon Sep 17 00:00:00 2001 From: cknd Date: Tue, 16 May 2017 18:27:37 +0200 Subject: [PATCH 781/945] decide in only one place what should happen after exceptions, use pdb.post_mortem instead of pdb.set_trace --- .../nodenet/theano_engine/theano_nodenet.py | 8 ++--- micropsi_core/runtime.py | 31 ++++++------------- micropsi_core/tools.py | 13 ++++++++ 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 3c73e190..78cfd0b0 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -19,6 +19,7 @@ except ImportError: import pdb +from micropsi_core.tools import post_mortem from micropsi_core.tools import OrderedSet from micropsi_core.nodenet import monitor from micropsi_core.nodenet import recorder @@ -1072,9 +1073,7 @@ def compile_flow_subgraph(self, node_uids, requested_outputs=None, use_different original_outex = node.build(*buildargs) except Exception as err: self.logger.error("Error in buildfunction of Flowodule %s.\n %s: %s" % (str(node), err.__class__.__name__, str(err))) - if settings['micropsi2'].get('on_exception') == 'debug': - pdb.post_mortem(err.__traceback__) - + post_mortem() skip = True break @@ -1738,8 +1737,7 @@ def reload_native_modules(self, native_modules): self.native_module_definitions[key] = data except Exception as err: self.logger.error("Can not instantiate node type %s: %s: %s" % (key, err.__class__.__name__, str(err))) - if settings['micropsi2'].get('on_exception') == 'debug': - pdb.post_mortem(err.__traceback__) + post_mortem() for partition in self.partitions.values(): for uid, instance in partition.native_module_instances.items(): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 57a3746e..e89214cd 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -18,6 +18,7 @@ from micropsi_core.nodenet import node_alignment from micropsi_core import config from micropsi_core.tools import Bunch +from micropsi_core.tools import post_mortem import os import sys @@ -31,12 +32,6 @@ from .micropsi_logger import MicropsiLogger -try: - import ipdb as pdb -except ImportError: - import pdb - - NODENET_DIRECTORY = "nodenets" WORLD_DIRECTORY = "worlds" @@ -69,6 +64,7 @@ from code import InteractiveConsole + class FileCacher(): """Cache the stdout text so we can analyze it before returning it""" def __init__(self): @@ -178,9 +174,7 @@ def run(self): stop_nodenetrunner(uid) # nodenet.is_active = False logging.getLogger("agent.%s" % uid).error("Exception in Agent:", exc_info=1) - if cfg['micropsi2'].get('on_exception') == 'debug': - _, _, tb = sys.exc_info() - pdb.post_mortem(tb) + post_mortem() MicropsiRunner.last_nodenet_exception[uid] = sys.exc_info() if nodenet.world and nodenet.current_step % runner['factor'] == 0: try: @@ -190,9 +184,7 @@ def run(self): # nodenet.is_active = False logging.getLogger("world").error("Exception in Environment:", exc_info=1) MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() - if cfg['micropsi2'].get('on_exception') == 'debug': - _, _, tb = sys.exc_info() - pdb.post_mortem(tb) + post_mortem() if self.profiler: self.profiler.disable() @@ -1648,8 +1640,7 @@ def parse_world_definitions(path): logging.getLogger("system").debug("Found world %s " % name) except Exception as e: errors.append("%s when importing world file %s: %s" % (e.__class__.__name__, relpath, str(e))) - if cfg['micropsi2'].get('on_exception') == 'debug': - pdb.set_trace() + post_mortem() for w in worldadapterfiles: relpath = os.path.relpath(os.path.join(base_path, w), start=WORLD_PATH) name = w[:-3] @@ -1662,8 +1653,7 @@ def parse_world_definitions(path): # errors.append("Name collision in worldadapters: %s defined more than once" % name) except Exception as e: errors.append("%s when importing worldadapter file %s: %s" % (e.__class__.__name__, relpath, str(e))) - if cfg['micropsi2'].get('on_exception') == 'debug': - pdb.set_trace() + post_mortem() for w in worldobjectfiles: relpath = os.path.relpath(os.path.join(base_path, w), start=WORLD_PATH) name = w[:-3] @@ -1676,8 +1666,7 @@ def parse_world_definitions(path): # errors.append("Name collision in worldadapters: %s defined more than once" % name) except Exception as e: errors.append("%s when importing worldobject file %s: %s" % (e.__class__.__name__, relpath, str(e))) - if cfg['micropsi2'].get('on_exception') == 'debug': - pdb.set_trace() + post_mortem() return errors or None @@ -1701,8 +1690,7 @@ def parse_native_module_file(path): logging.getLogger("system").warning("Native module names must be unique. %s is not." % moduledef['name']) native_modules[moduledef['name']] = moduledef except Exception as e: - if cfg['micropsi2'].get('on_exception') == 'debug': - pdb.set_trace() + post_mortem() return "%s when importing nodetype file %s: %s" % (e.__class__.__name__, relpath, str(e)) @@ -1724,8 +1712,7 @@ def parse_recipe_or_operations_file(path, mode, category_overwrite=False): # recipes = __import__(pyname, fromlist=['recipes']) # importlib.reload(sys.modules[pyname]) except Exception as e: - if cfg['micropsi2'].get('on_exception') == 'debug': - pdb.post_mortem(e.__traceback__) + post_mortem() return "%s when importing %s file %s: %s" % (e.__class__.__name__, mode, relpath, str(e)) for name, module in inspect.getmembers(recipes, inspect.ismodule): diff --git a/micropsi_core/tools.py b/micropsi_core/tools.py index 9780efd0..54c45b0e 100644 --- a/micropsi_core/tools.py +++ b/micropsi_core/tools.py @@ -11,6 +11,19 @@ import uuid import errno import os +import sys +from configuration import config as cfg +try: + import ipdb as pdb +except ImportError: + import pdb + +def post_mortem(): + """ if desired, point a debugger to the origin of the last exception """ + if cfg['micropsi2'].get('on_exception') == 'debug': + exception_type, exception, tb = sys.exc_info() + print('\033[01m\033[31m%s: \033[32m%s\033[0m' % (exception_type.__name__, exception)) + pdb.post_mortem(tb) def pid_exists(pid): From 4c17747d35b9543446923df958c7a51c8a8a2f0c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 22 May 2017 17:45:18 +0200 Subject: [PATCH 782/945] get_logging_levels rpc --- micropsi_core/runtime.py | 6 ++++-- micropsi_server/micropsi_app.py | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index fefecdbb..fe256f2f 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -282,8 +282,10 @@ def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor def get_logging_levels(nodenet_uid=None): levels = {} for key in logging.Logger.manager.loggerDict: - levels[key] = logging.getLevelName(logging.getLogger(key).getEffectiveLevel()) - levels['agent'] = cfg['logging']['level_agent'] + if key.startswith('agent') or key in ['world', 'system']: + levels[key] = logging.getLevelName(logging.getLogger(key).getEffectiveLevel()) + if 'agent' not in levels: + levels['agent'] = cfg['logging']['level_agent'] return levels diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 8e338542..936bb0c8 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1484,6 +1484,12 @@ def get_recorders(nodenet_uid): # --------- logging -------- +@rpc("get_logging_levels") +def get_logging_levels(): + """ Set the logging levels """ + return True, runtime.get_logging_levels() + + @rpc("set_logging_levels") def set_logging_levels(logging_levels): """ Set the logging levels """ From 890c2ccbb1e5d0bdc6f10f9f696d9c72e5af1c47 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Tue, 23 May 2017 12:00:32 +0200 Subject: [PATCH 783/945] Starting WebAgg server on runtime startup --- micropsi_core/runtime.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index fe256f2f..a8edd213 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -21,9 +21,21 @@ import os import sys +import threading + + +def plotter_initializer(): + from matplotlib import pyplot as plt + plt.show() + +# bring up plotting infrastructure +import matplotlib +matplotlib.use('WebAgg') +plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) +plt_thread.start() + from micropsi_core import tools import json -import threading from datetime import datetime, timedelta import time import signal From 4e0ecc1d4bc796291841e1b7d583a448510b38bb Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Tue, 23 May 2017 12:14:17 +0200 Subject: [PATCH 784/945] not opening browser automatically --- micropsi_core/runtime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index a8edd213..23ce8f56 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -30,6 +30,8 @@ def plotter_initializer(): # bring up plotting infrastructure import matplotlib +matplotlib.rcParams['webagg.port'] = 6545 +matplotlib.rcParams['webagg.open_in_browser'] = False matplotlib.use('WebAgg') plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) plt_thread.start() From a4a3b3da349a1bca82a66c0e55f2a0f861eb441c Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Tue, 23 May 2017 14:13:55 +0200 Subject: [PATCH 785/945] Ignoring pycharm-friendly extra venv --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c6fa92d2..0e010299 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ micropsi.log /test-data/ pip-selfcheck.json /cherrypy +/.pycharmvenv/ From aa59010e046604e7d818c76d5b43efd4fee8d3e9 Mon Sep 17 00:00:00 2001 From: cknd Date: Tue, 23 May 2017 15:54:05 +0200 Subject: [PATCH 786/945] new config flag to automatically open the browser on show() --- micropsi_core/runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 23ce8f56..9148a23e 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -31,7 +31,7 @@ def plotter_initializer(): # bring up plotting infrastructure import matplotlib matplotlib.rcParams['webagg.port'] = 6545 -matplotlib.rcParams['webagg.open_in_browser'] = False +matplotlib.rcParams['webagg.open_in_browser'] = cfg['micropsi2'].get('plotting_open_browser', False) matplotlib.use('WebAgg') plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) plt_thread.start() From 908bef6803a4d19e766134054e1b2b34fee4e640 Mon Sep 17 00:00:00 2001 From: priska Date: Tue, 23 May 2017 15:55:15 +0200 Subject: [PATCH 787/945] Removes deprecated native modules from toolkit. --- micropsi_core/nodenet/native_modules.py | 1233 ----------------------- 1 file changed, 1233 deletions(-) delete mode 100644 micropsi_core/nodenet/native_modules.py diff --git a/micropsi_core/nodenet/native_modules.py b/micropsi_core/nodenet/native_modules.py deleted file mode 100644 index d14c2cd8..00000000 --- a/micropsi_core/nodenet/native_modules.py +++ /dev/null @@ -1,1233 +0,0 @@ -""" -Builtin native modules - -Currently contains - * GradientDescent for 3 layers (input, hidden, outpu) - * GradientDescent for LSTMS -""" - -import os - -nodetypes = {} - -try: - import numpy as np - import theano - numpy_installed = True -except ImportError: - numpy_installed = False - - -if numpy_installed: - # only register these native modules if we - # have theano and numpy installed. - nodetypes["GradientDescentLSTM"] = { - "name": "GradientDescentLSTM", - "engine": "theano_engine", - "slottypes": ["trigger", "debug"], - "gatetypes": ["e"], - "nodefunction_name": "gradient_descent_lstm", - "symbol": "↺", - "category": "nn_learning", - "path": os.path.abspath(__file__), - "parameters": [ - "adadelta_rho", - "adadelta_epsilon", - "sequence_length", - "links_io", - "links_porpor", - "links_porgin", - "links_porgou", - "links_porgfg", - "bias_gin", - "bias_gou", - "bias_gfg", - "group_t_nodes", - "group_t_gates", - "group_i_nodes", - "group_i_gates", - "group_c_nodes", - "group_o_nodes", - "group_o_gates" - ], - "parameter_values": { - "links_io": ["true", "false"], - "links_porpor": ["true", "false"], - "links_porgin": ["true", "false"], - "links_porgou": ["true", "false"], - "links_porgfg": ["true", "false"], - "bias_gin": ["true", "false"], - "bias_gou": ["true", "false"], - "bias_gfg": ["true", "false"] - }, - "parameter_defaults": { - "adadelta_rho": "0.95", - "adadelta_epsilon": "0.000001", - "sequence_length": "5", - "links_io": "true", - "links_porpor": "true", - "links_porgin": "true", - "links_porgou": "true", - "links_porgfg": "true", - "bias_gin": "true", - "bias_gou": "true", - "bias_gfg": "true", - "group_t_nodes": "target", - "group_t_gates": "gen", - "group_i_nodes": "input", - "group_i_gates": "gen", - "group_c_nodes": "lstm", - "group_o_nodes": "output", - "group_o_gates": "gen" - } - } - - nodetypes["GradientDescent"] = { - "name": "GradientDescent", - "engine": "theano_engine", - "slottypes": ["gen"], - "gatetypes": ["gen"], - "nodefunction_name": "gradient_descent", - "symbol": "☲", - "category": "nn_learning", - "path": os.path.abspath(__file__), - "parameters": [ - "ae_type", - "adadelta_rho", - "adadelta_eps", - "check_grad", - "weight_decay", - "tied_weights", - "sparsity_value", - "sparsity_penalty", - "t", - "ctr", - "input_prefix", - "hidden_prefix", - "output_prefix", - "input_nodespace" - ], - "parameter_values": { - "ae_type": ["sparse", "denoising"], - "tied_weights": ["True", "False"], - "check_grad": ["yes", "no"] - }, - "parameter_defaults": { - "ae_type": "denoising", - "tied_weights": "True", - "hidden_prefix": "hidden_1", - "output_prefix": "output_1" - } - } - - -def gradient_descent_lstm(netapi, node=None, **params): - """ - Gradient Descent for LSTMs - - The following assumes a three-layer architecture, with hidden LSTM nodes. - There is always a single LSTM cell per block (no multi-block cells are implemented). - - The following sets of weights are defined: - input -> output - input -> cell - input -> input gate - input -> output gate - input -> forget gate - cell -> output - cell -> input gate - cell -> output gate - cell -> forget gate - - The cell's constant error carousel link is explicitly modelled (as a gen loop). - Note that input, output and forget gate links aren't updated right now. - - Variable naming and implementation follows: - Gers & al. 1999, Learning to Forget - Continual Prediction with LSTM - - Other helpful papers: - Hochreiter & al. 1997, Long Short-Term Memory (introduces naming convention and most of the math) - Graves & al. 2005, Framewise Phoneme Classification with Bidrectional LSTM and Other NN Architectures - - For the Graves paper, a minimal, almost readable python implementation can be found at: - https://gist.github.com/neubig/ff2f97d91c9bed820c15 - - The ADADELTA implemetation follows the original ADADELTA paper: - Zeiler 2012, ADADELTA: An Adaptive Learning Rate Method - - A nice theano adadelta implementation is here: - https://blog.wtf.sg/2014/08/28/implementing-adadelta/ - """ - - from numbers import Number - from theano import tensor as T - - SEQUENCE_LENGTH = 3 - sequence_length_string = node.get_parameter("sequence_length") - if sequence_length_string is not None: - SEQUENCE_LENGTH = int(sequence_length_string) - - target_node_group = node.get_parameter("group_t_nodes") - target_gate = node.get_parameter("group_t_gates") - output_node_group = node.get_parameter("group_o_nodes") - output_gate = node.get_parameter("group_o_gates") - input_node_group = node.get_parameter("group_i_nodes") - input_gate = node.get_parameter("group_i_gates") - lstm = node.get_parameter("group_c_nodes") - lstm_gen = "%s_gen" % lstm - lstm_por = "%s_por" % lstm - lstm_gin = "%s_gin" % lstm - lstm_gou = "%s_gou" % lstm - lstm_gfg = "%s_gfg" % lstm - input = "%s_input" % input_node_group - output = "%s_output" % output_node_group - target = "%s_target" % target_node_group - - nodespace = node.parent_nodespace - - if not hasattr(node, 'initialized'): - - # create the groups - netapi.group_nodes_by_names(nodespace, node_name_prefix=target_node_group, gate=target_gate, group_name=target) - netapi.group_nodes_by_names(nodespace, node_name_prefix=output_node_group, gate=output_gate, group_name=output) - netapi.group_nodes_by_names(nodespace, node_name_prefix=input_node_group, gate=input_gate, group_name=input) - - netapi.group_nodes_by_names(nodespace, node_name_prefix=lstm, gate="gen", group_name=lstm_gen) - netapi.group_nodes_by_names(nodespace, node_name_prefix=lstm, gate="por", group_name=lstm_por) - netapi.group_nodes_by_names(nodespace, node_name_prefix=lstm, gate="gin", group_name=lstm_gin) - netapi.group_nodes_by_names(nodespace, node_name_prefix=lstm, gate="gou", group_name=lstm_gou) - netapi.group_nodes_by_names(nodespace, node_name_prefix=lstm, gate="gfg", group_name=lstm_gfg) - - len_output = len(netapi.get_activations(nodespace, output)) - len_input = len(netapi.get_activations(nodespace, input)) - len_hidden = len(netapi.get_activations(nodespace, lstm_por)) - - # define a single LSTM-style backpropagation through time step, to be scanned over by theano - def bpttstep( - s, tgt, y_k, y_i, y_c, net_in, net_out, net_phi, - error, drv_ci_prev, drv_cc_prev, drv_ini_prev, drv_inc_prev, drv_in1_prev, drv_phii_prev, drv_phic_prev, drv_phi1_prev, - delta_w_ki, delta_w_kc, delta_w_outi, delta_w_outc, delta_w_ci, delta_w_cc, delta_w_ini, delta_w_inc, delta_w_phii, delta_w_phic, - delta_theta_i, delta_theta_k, delta_theta_in, delta_theta_out, delta_theta_phi, - w_kc, w_ci, w_cc, w_outc, w_outi, w_ini, w_inc, w_phii, w_phic): - - # calculate error - e_k = tgt - y_k # (12) error per output element - E = T.sum(T.square(e_k)) / 2. # (12) squared sum to be minimized - - # Part I: standard (truncated) BPTT for links to output registers and lstm output gate slots - # cell -> output - # cell -> output gate - # input -> output - # input -> output gate - - # functions and derivatives - y_in = T.nnet.sigmoid(net_in) # (3) y_in = f(net_in) - y_out = T.nnet.sigmoid(net_out) # (3) y_out = f(net_out) - y_phi = T.nnet.sigmoid(net_phi) # (3) y_phi = f(net_phi) - - h_s = 2 * T.nnet.sigmoid(s) - 1 # (8) - - f_primed_net_k = y_k * (1. - y_k) # f'(net_k) = f(net_k) * (1 - f(net_k)), f(net_k) provided as y_k - f_primed_net_out = y_out * (1. - y_out) - f_primed_net_in = y_in * (1. - y_in) - f_primed_net_phi = y_phi * (1. - y_phi) - # f_primed_net_i = y_i * (1. - y_i) - h_primed_s = (2 * T.exp(s)) / T.square(T.exp(s) + 1) - - delta_k = f_primed_net_k * e_k # (14) delta per output element - delta_out = f_primed_net_out * h_s * T.sum(w_kc * T.reshape(delta_k, (len_output, 1)), axis=0) # (15) delta per output gate - - # we use y_c and y_i here instead of y_i_prev because we have "flattened snapshots" to work with - # i.e. the partial derivative of net_k(t) with respect to w_kc is delta_k(t) * y_c(t) - # (y_c is what was propagated and created net_k) - delta_w_kc += T.dot(T.reshape(delta_k, (len_output, 1)), T.reshape(y_c, (1, len_hidden))) # (13) m = c - delta_w_ki += T.dot(T.reshape(delta_k, (len_output, 1)), T.reshape(y_i, (1, len_input))) # (13) m = i - delta_w_outi += T.dot(T.reshape(delta_out, (len_hidden, 1)), T.reshape(y_i, (1, len_input))) # (13) m = c - delta_w_outc += T.dot(T.reshape(delta_out, (len_hidden, 1)), T.reshape(y_c, (1, len_hidden))) # (13) m = i - - delta_theta_k += delta_k - delta_theta_out += delta_out - - # Part II: RTRL-style updates - # input -> cell - # cell -> cell - # input -> input gate - # cell -> input gate - # input -> forget gate - # cell -> forget gate - - net_c = T.dot(w_ci, y_i) # ugly re-calculation of forward pass for net_c - g_net_c = 4 * T.nnet.sigmoid(net_c) - 2 # (5) - g_primed_net_c = (4 * T.exp(net_c)) / T.square(T.exp(net_c) + 1) - - e_s = y_out * h_primed_s * T.sum(w_kc * T.reshape(delta_k, (len_output, 1)), axis=0) # (17) - - drv_ci = drv_ci_prev * T.reshape(y_phi, (len_hidden, 1)) \ - + T.dot(T.reshape(g_primed_net_c * y_in, (len_hidden, 1)), T.reshape(y_i, (1, len_input))) # (19) m = i - drv_cc = drv_cc_prev * T.reshape(y_phi, (len_hidden, 1)) \ - + T.dot(T.reshape(g_primed_net_c * y_in, (len_hidden, 1)), T.reshape(y_c, (1, len_hidden))) # (19) m = i - - drv_ini = drv_ini_prev * T.reshape(y_phi, (len_hidden, 1)) \ - + T.dot(T.reshape(g_net_c * f_primed_net_in, (len_hidden, 1)), T.reshape(y_i, (1, len_input))) # (20) m = i - drv_inc = drv_inc_prev * T.reshape(y_phi, (len_hidden, 1)) \ - + T.dot(T.reshape(g_net_c * f_primed_net_in, (len_hidden, 1)), T.reshape(y_c, (1, len_hidden))) # (20) m = c - drv_in1 = drv_in1_prev * y_phi + g_net_c * f_primed_net_in - - drv_phii = drv_phii_prev * T.reshape(y_phi, (len_hidden, 1)) \ - + T.dot(T.reshape(h_s * f_primed_net_phi, (len_hidden, 1)), T.reshape(y_i, (1, len_input))) # (21) m = i - drv_phic = drv_phic_prev * T.reshape(y_phi, (len_hidden, 1)) \ - + T.dot(T.reshape(h_s * f_primed_net_phi, (len_hidden, 1)), T.reshape(y_c, (1, len_hidden))) # (21) m = c - drv_phi1 = drv_phi1_prev * y_phi + h_s * f_primed_net_phi - - delta_w_ci += T.reshape(e_s, (len_hidden, 1)) * drv_ci - delta_w_cc += T.reshape(e_s, (len_hidden, 1)) * drv_cc - - delta_w_ini += T.reshape(e_s, (len_hidden, 1)) * drv_ini - delta_w_inc += T.reshape(e_s, (len_hidden, 1)) * drv_inc - - delta_w_phii += T.reshape(e_s, (len_hidden, 1)) * drv_phii - delta_w_phic += T.reshape(e_s, (len_hidden, 1)) * drv_phic - - # delta_theta_i += 0 - delta_theta_in += e_s * drv_in1 - delta_theta_phi += e_s * drv_phi1 - - error = E - - return error, drv_ci, drv_cc, drv_ini, drv_inc, drv_in1, drv_phii, drv_phic, drv_phi1, \ - delta_w_ki, delta_w_kc, delta_w_outi, delta_w_outc, delta_w_ci, delta_w_cc, delta_w_ini, delta_w_inc, delta_w_phii, delta_w_phic, \ - delta_theta_i, delta_theta_k, delta_theta_in, delta_theta_out, delta_theta_phi # cumulate - - node.set_state('current_error', 0.) - node.set_state('error', 0.) - node.set_state('updates', 0) - node.t = -1 - node.samples = 0 - - t_a_i_matrix = node.t_a_i_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_input)).astype(T.config.floatX) - t_a_t_matrix = node.t_a_t_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_output)).astype(T.config.floatX) - t_a_o_matrix = node.t_a_o_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_output)).astype(T.config.floatX) - t_a_h_gen_matrix = node.t_a_h_gen_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_hidden)).astype(T.config.floatX) - t_a_h_por_matrix = node.t_a_h_por_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_hidden)).astype(T.config.floatX) - t_a_h_gin_matrix = node.t_a_h_gin_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_hidden)).astype(T.config.floatX) - t_a_h_gou_matrix = node.t_a_h_gou_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_hidden)).astype(T.config.floatX) - t_a_h_gfg_matrix = node.t_a_h_gfg_matrix = np.zeros(shape=(SEQUENCE_LENGTH, len_hidden)).astype(T.config.floatX) - - w_oh_por_array = netapi.get_link_weights(nodespace, lstm_por, nodespace, output) - w_oi_array = netapi.get_link_weights(nodespace, input, nodespace, output) - w_h_por_i_array = netapi.get_link_weights(nodespace, input, nodespace, lstm_por) - w_h_gou_h_por_array = netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_gou) - w_h_gou_i_array = netapi.get_link_weights(nodespace, input, nodespace, lstm_gou) - w_h_por_h_por_array = netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_por) - w_h_gin_i_array = netapi.get_link_weights(nodespace, input, nodespace, lstm_gin) - w_h_gin_h_por_array = netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_gin) - w_h_gfg_i_array = netapi.get_link_weights(nodespace, input, nodespace, lstm_gfg) - w_h_gfg_h_por_array = netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_gfg) - - theta_input_array = netapi.get_thetas(nodespace, input) - theta_output_array = netapi.get_thetas(nodespace, output) - theta_lstm_gin_array = netapi.get_thetas(nodespace, lstm_gin) - theta_lstm_gou_array = netapi.get_thetas(nodespace, lstm_gou) - theta_lstm_gfg_array = netapi.get_thetas(nodespace, lstm_gfg) - - steps = T.iscalar("steps") - - # adadelta hyperparameters - rho = T.scalar("rho") - epsilon = T.scalar("epsilon") - - # activations -- post node/gatefunction, i.e. post-nonlinearities: y - # tgt t(t) - tgt = node.tgt = theano.shared(value=t_a_t_matrix.astype(T.config.floatX), name="tgt", borrow=False) - # output k(t) - y_k = node.y_k = theano.shared(value=t_a_o_matrix.astype(T.config.floatX), name="y_k", borrow=False) - # input i(t) - y_i = node.y_i = theano.shared(value=t_a_i_matrix.astype(T.config.floatX), name="y_i", borrow=False) - # cell state c(t) - y_c = node.y_c = theano.shared(value=t_a_h_por_matrix.astype(T.config.floatX), name="y_c", borrow=False) - # cell internal state (cec) s(t) - s = node.s = theano.shared(value=t_a_h_gen_matrix.astype(T.config.floatX), name="s", borrow=False) - - # for the LSTM gates, no node/gatefunction has been calculated, so we get net sums, not post-nonlinearity values - # output gate out(t) - net_out = node.net_out = theano.shared(value=t_a_h_gou_matrix.astype(T.config.floatX), name="net_out", borrow=False) - # input gate in(t) - net_in = node.net_in = theano.shared(value=t_a_h_gin_matrix.astype(T.config.floatX), name="net_in", borrow=False) - # forget gate phi(t) - net_phi = node.net_phi = theano.shared(value=t_a_h_gfg_matrix.astype(T.config.floatX), name="net_phi", borrow=False) - - # weight sets to be updated - # cell (c) -> output (k) - w_kc = node.w_kc = theano.shared(value=w_oh_por_array.astype(T.config.floatX), name="w_kc", borrow=False) - # input (i) -> output (k) - w_ki = node.w_ki = theano.shared(value=w_oi_array.astype(T.config.floatX), name="w_ki", borrow=False) - # cell (c) -> output gate (out) - w_outc = node.w_outc = theano.shared(value=w_h_gou_h_por_array.astype(T.config.floatX), name="w_outc", borrow=False) - # input (i) -> output gate (out) - w_outi = node.w_outi = theano.shared(value=w_h_gou_i_array.astype(T.config.floatX), name="w_outi", borrow=False) - # input (i) -> cell (c) - w_ci = node.w_ci = theano.shared(value=w_h_por_i_array.astype(T.config.floatX), name="w_ci", borrow=False) - # input (i) -> cell (c) - w_cc = node.w_cc = theano.shared(value=w_h_por_h_por_array.astype(T.config.floatX), name="w_cc", borrow=False) - # input (i) -> input gate (in) - w_ini = node.w_ini = theano.shared(value=w_h_gin_i_array.astype(T.config.floatX), name="w_ini", borrow=False) - # cell (c) -> input gate (in) - w_inc = node.w_inc = theano.shared(value=w_h_gin_h_por_array.astype(T.config.floatX), name="w_inc", borrow=False) - # input (i) -> forget gate (phi) - w_phii = node.w_phii = theano.shared(value=w_h_gfg_i_array.astype(T.config.floatX), name="w_phii", borrow=False) - # cell (c) -> forget gate (phi) - w_phic = node.w_phic = theano.shared(value=w_h_gfg_h_por_array.astype(T.config.floatX), name="w_phic", borrow=False) - - # bias sets to be updated - theta_i = node.theta_i = theano.shared(value=theta_input_array.astype(T.config.floatX), name="theta_i", borrow=False) - theta_k = node.theta_k = theano.shared(value=theta_output_array.astype(T.config.floatX), name="theta_k", borrow=False) - theta_in = node.theta_in = theano.shared(value=theta_lstm_gin_array.astype(T.config.floatX), name="theta_in", borrow=False) - theta_out = node.theta_out = theano.shared(value=theta_lstm_gou_array.astype(T.config.floatX), name="theta_out", borrow=False) - theta_phi = node.theta_phi = theano.shared(value=theta_lstm_gfg_array.astype(T.config.floatX), name="theta_phi", borrow=False) - - # adadelta gradients and delta accumulation variables - node.accu_grad_w_kc = theano.shared(value=np.zeros_like(w_oh_por_array), name="accu_grad_w_kc", borrow=True) - node.accu_delta_w_kc = theano.shared(value=np.zeros_like(w_oh_por_array), name="accu_delta_w_kc", borrow=True) - node.accu_grad_w_ki = theano.shared(value=np.zeros_like(w_oi_array), name="accu_grad_w_ki", borrow=True) - node.accu_delta_w_ki = theano.shared(value=np.zeros_like(w_oi_array), name="accu_delta_w_ki", borrow=True) - node.accu_grad_w_outc = theano.shared(value=np.zeros_like(w_h_gou_h_por_array), name="accu_grad_w_outc", borrow=True) - node.accu_delta_w_outc = theano.shared(value=np.zeros_like(w_h_gou_h_por_array), name="accu_delta_w_outc", borrow=True) - node.accu_grad_w_outi = theano.shared(value=np.zeros_like(w_h_gou_i_array), name="accu_grad_w_outi", borrow=True) - node.accu_delta_w_outi = theano.shared(value=np.zeros_like(w_h_gou_i_array), name="accu_delta_w_outi", borrow=True) - node.accu_grad_w_ci = theano.shared(value=np.zeros_like(w_h_por_i_array), name="accu_grad_w_ci", borrow=True) - node.accu_delta_w_ci = theano.shared(value=np.zeros_like(w_h_por_i_array), name="accu_delta_w_ci", borrow=True) - node.accu_grad_w_cc = theano.shared(value=np.zeros_like(w_h_por_h_por_array), name="accu_grad_w_cc", borrow=True) - node.accu_delta_w_cc = theano.shared(value=np.zeros_like(w_h_por_h_por_array), name="accu_delta_w_cc", borrow=True) - node.accu_grad_w_ini = theano.shared(value=np.zeros_like(w_h_gin_i_array), name="accu_grad_w_ini", borrow=True) - node.accu_delta_w_ini = theano.shared(value=np.zeros_like(w_h_gin_i_array), name="accu_delta_w_ini", borrow=True) - node.accu_grad_w_inc = theano.shared(value=np.zeros_like(w_h_gin_h_por_array), name="accu_grad_w_inc", borrow=True) - node.accu_delta_w_inc = theano.shared(value=np.zeros_like(w_h_gin_h_por_array), name="accu_delta_w_inc", borrow=True) - node.accu_grad_w_phii = theano.shared(value=np.zeros_like(w_h_gfg_i_array), name="accu_grad_w_phii", borrow=True) - node.accu_delta_w_phii = theano.shared(value=np.zeros_like(w_h_gfg_i_array), name="accu_delta_w_phii", borrow=True) - node.accu_grad_w_phic = theano.shared(value=np.zeros_like(w_h_gfg_h_por_array), name="accu_grad_w_phic", borrow=True) - node.accu_delta_w_phic = theano.shared(value=np.zeros_like(w_h_gfg_h_por_array), name="accu_delta_w_phic", borrow=True) - node.accu_grad_theta_k = theano.shared(value=np.zeros_like(theta_output_array), name="accu_grad_theta_k", borrow=True) - node.accu_delta_theta_k = theano.shared(value=np.zeros_like(theta_output_array), name="accu_delta_theta_k", borrow=True) - node.accu_grad_theta_out = theano.shared(value=np.zeros_like(theta_lstm_gou_array), name="accu_grad_theta_out", borrow=True) - node.accu_delta_theta_out = theano.shared(value=np.zeros_like(theta_lstm_gou_array), name="accu_delta_theta_out", borrow=True) - node.accu_grad_theta_in = theano.shared(value=np.zeros_like(theta_lstm_gin_array), name="accu_grad_theta_in", borrow=True) - node.accu_delta_theta_in = theano.shared(value=np.zeros_like(theta_lstm_gin_array), name="accu_delta_theta_in", borrow=True) - node.accu_grad_theta_phi = theano.shared(value=np.zeros_like(theta_lstm_gfg_array), name="accu_grad_theta_phi", borrow=True) - node.accu_delta_theta_phi = theano.shared(value=np.zeros_like(theta_lstm_gfg_array), name="accu_delta_theta_phi", borrow=True) - - [errors, - deriv_ci_prev, - deriv_cc_prev, - deriv_ini_prev, - deriv_inc_prev, - deriv_in1_prev, - deriv_phii_prev, - deriv_phic_prev, - deriv_phi1_prev, - grad_w_ki, - grad_w_kc, - grad_w_outi, - grad_w_outc, - grad_w_ci, - grad_w_cc, - grad_w_ini, - grad_w_inc, - grad_w_phii, - grad_w_phic, - grad_theta_i, - grad_theta_k, - grad_theta_in, - grad_theta_out, - grad_theta_phi], updates = theano.scan( - fn=bpttstep, - sequences=[dict(input=s, taps=[-0]), - dict(input=tgt, taps=[-0]), - dict(input=y_k, taps=[-0]), - dict(input=y_i, taps=[-0]), - dict(input=y_c, taps=[-0]), - dict(input=net_in, taps=[-0]), - dict(input=net_out, taps=[-0]), - dict(input=net_phi, taps=[-0])], - outputs_info=[0., # error - T.zeros_like(w_ci, dtype=T.config.floatX), # deriv_ci_prev - T.zeros_like(w_cc, dtype=T.config.floatX), # deriv_cc_prev - T.zeros_like(w_ini, dtype=T.config.floatX), # deriv_ini_prev - T.zeros_like(w_inc, dtype=T.config.floatX), # deriv_inc_prev - T.zeros_like(theta_in, dtype=T.config.floatX), # deriv_in1_prev - T.zeros_like(w_phii, dtype=T.config.floatX), # deriv_phii_prev - T.zeros_like(w_phic, dtype=T.config.floatX), # deriv_phic_prev - T.zeros_like(theta_phi, dtype=T.config.floatX), # deriv_phi1_prev - T.zeros_like(w_ki, dtype=T.config.floatX), # delta_w_ki - T.zeros_like(w_kc, dtype=T.config.floatX), # delta_w_kc - T.zeros_like(w_outi, dtype=T.config.floatX), # delta_w_outi - T.zeros_like(w_outc, dtype=T.config.floatX), # delta_w_outc - T.zeros_like(w_ci, dtype=T.config.floatX), # delta_w_ci - T.zeros_like(w_cc, dtype=T.config.floatX), # delta_w_cc - T.zeros_like(w_ini, dtype=T.config.floatX), # delta_w_ini - T.zeros_like(w_inc, dtype=T.config.floatX), # delta_w_inc - T.zeros_like(w_phii, dtype=T.config.floatX), # delta_w_phii - T.zeros_like(w_phic, dtype=T.config.floatX), # delta_w_phic - T.zeros_like(theta_i, dtype=T.config.floatX), # delta_theta_i - T.zeros_like(theta_k, dtype=T.config.floatX), # delta_theta_k - T.zeros_like(theta_in, dtype=T.config.floatX), # delta_theta_in - T.zeros_like(theta_out, dtype=T.config.floatX), # delta_theta_out - T.zeros_like(theta_phi, dtype=T.config.floatX)], # delta_theta_phi - non_sequences=[w_kc, - w_ci, - w_cc, - w_outc, - w_outi, - w_ini, - w_inc, - w_phii, - w_phic], - go_backwards=True, - n_steps=steps, - strict=True) - - # adadelta momentum - accu_grad_w_kc = rho * node.accu_grad_w_kc + (1. - rho) * (grad_w_kc[SEQUENCE_LENGTH - 1]**2) - delta_w_kc = (T.sqrt(node.accu_delta_w_kc + epsilon) / T.sqrt(accu_grad_w_kc + epsilon)) * grad_w_kc[SEQUENCE_LENGTH - 1] - accu_delta_w_kc = rho * node.accu_delta_w_kc + (1. - rho) * (delta_w_kc**2) - - accu_grad_w_ki = rho * node.accu_grad_w_ki + (1. - rho) * (grad_w_ki[SEQUENCE_LENGTH - 1]**2) - delta_w_ki = (T.sqrt(node.accu_delta_w_ki + epsilon) / T.sqrt(accu_grad_w_ki + epsilon)) * grad_w_ki[SEQUENCE_LENGTH - 1] - accu_delta_w_ki = rho * node.accu_delta_w_ki + (1. - rho) * (delta_w_ki**2) - - accu_grad_w_outc = rho * node.accu_grad_w_outc + (1. - rho) * (grad_w_outc[SEQUENCE_LENGTH - 1]**2) - delta_w_outc = (T.sqrt(node.accu_delta_w_outc + epsilon) / T.sqrt(accu_grad_w_outc + epsilon)) * grad_w_outc[SEQUENCE_LENGTH - 1] - accu_delta_w_outc = rho * node.accu_delta_w_outc + (1. - rho) * (delta_w_outc**2) - - accu_grad_w_outi = rho * node.accu_grad_w_outi + (1. - rho) * (grad_w_outi[SEQUENCE_LENGTH - 1]**2) - delta_w_outi = (T.sqrt(node.accu_delta_w_outi + epsilon) / T.sqrt(accu_grad_w_outi + epsilon)) * grad_w_outi[SEQUENCE_LENGTH - 1] - accu_delta_w_outi = rho * node.accu_delta_w_outi + (1. - rho) * (delta_w_outi**2) - - accu_grad_w_ci = rho * node.accu_grad_w_ci + (1. - rho) * (grad_w_ci[SEQUENCE_LENGTH - 1]**2) - delta_w_ci = (T.sqrt(node.accu_delta_w_ci + epsilon) / T.sqrt(accu_grad_w_ci + epsilon)) * grad_w_ci[SEQUENCE_LENGTH - 1] - accu_delta_w_ci = rho * node.accu_delta_w_ci + (1. - rho) * (delta_w_ci**2) - - accu_grad_w_cc = rho * node.accu_grad_w_cc + (1. - rho) * (grad_w_cc[SEQUENCE_LENGTH - 1]**2) - delta_w_cc = (T.sqrt(node.accu_delta_w_cc + epsilon) / T.sqrt(accu_grad_w_cc + epsilon)) * grad_w_cc[SEQUENCE_LENGTH - 1] - accu_delta_w_cc = rho * node.accu_delta_w_cc + (1. - rho) * (delta_w_cc**2) - - accu_grad_w_ini = rho * node.accu_grad_w_ini + (1. - rho) * (grad_w_ini[SEQUENCE_LENGTH - 1]**2) - delta_w_ini = (T.sqrt(node.accu_delta_w_ini + epsilon) / T.sqrt(accu_grad_w_ini + epsilon)) * grad_w_ini[SEQUENCE_LENGTH - 1] - accu_delta_w_ini = rho * node.accu_delta_w_ini + (1. - rho) * (delta_w_ini**2) - - accu_grad_w_inc = rho * node.accu_grad_w_inc + (1. - rho) * (grad_w_inc[SEQUENCE_LENGTH - 1]**2) - delta_w_inc = (T.sqrt(node.accu_delta_w_inc + epsilon) / T.sqrt(accu_grad_w_inc + epsilon)) * grad_w_inc[SEQUENCE_LENGTH - 1] - accu_delta_w_inc = rho * node.accu_delta_w_inc + (1. - rho) * (delta_w_inc**2) - - accu_grad_w_phii = rho * node.accu_grad_w_phii + (1. - rho) * (grad_w_phii[SEQUENCE_LENGTH - 1]**2) - delta_w_phii = (T.sqrt(node.accu_delta_w_phii + epsilon) / T.sqrt(accu_grad_w_phii + epsilon)) * grad_w_phii[SEQUENCE_LENGTH - 1] - accu_delta_w_phii = rho * node.accu_delta_w_phii + (1. - rho) * (delta_w_phii**2) - - accu_grad_w_phic = rho * node.accu_grad_w_phic + (1. - rho) * (grad_w_phic[SEQUENCE_LENGTH - 1]**2) - delta_w_phic = (T.sqrt(node.accu_delta_w_phic + epsilon) / T.sqrt(accu_grad_w_phic + epsilon)) * grad_w_phic[SEQUENCE_LENGTH - 1] - accu_delta_w_phic = rho * node.accu_delta_w_phic + (1. - rho) * (delta_w_phic**2) - - accu_grad_theta_k = rho * node.accu_grad_theta_k + (1. - rho) * (grad_theta_k[SEQUENCE_LENGTH - 1]**2) - delta_theta_k = (T.sqrt(node.accu_delta_theta_k + epsilon) / T.sqrt(accu_grad_theta_k + epsilon)) * grad_theta_k[SEQUENCE_LENGTH - 1] - accu_delta_theta_k = rho * node.accu_delta_theta_k + (1. - rho) * (delta_theta_k**2) - - accu_grad_theta_out = rho * node.accu_grad_theta_out + (1. - rho) * (grad_theta_out[SEQUENCE_LENGTH - 1]**2) - delta_theta_out = (T.sqrt(node.accu_delta_theta_out + epsilon) / T.sqrt(accu_grad_theta_out + epsilon)) * grad_theta_out[SEQUENCE_LENGTH - 1] - accu_delta_theta_out = rho * node.accu_delta_theta_out + (1. - rho) * (delta_theta_out**2) - - accu_grad_theta_in = rho * node.accu_grad_theta_in + (1. - rho) * (grad_theta_in[SEQUENCE_LENGTH - 1]**2) - delta_theta_in = (T.sqrt(node.accu_delta_theta_in + epsilon) / T.sqrt(accu_grad_theta_in + epsilon)) * grad_theta_in[SEQUENCE_LENGTH - 1] - accu_delta_theta_in = rho * node.accu_delta_theta_in + (1. - rho) * (delta_theta_in**2) - - accu_grad_theta_phi = rho * node.accu_grad_theta_phi + (1. - rho) * (grad_theta_phi[SEQUENCE_LENGTH - 1]**2) - delta_theta_phi = (T.sqrt(node.accu_delta_theta_phi + epsilon) / T.sqrt(accu_grad_theta_phi + epsilon)) * grad_theta_phi[SEQUENCE_LENGTH - 1] - accu_delta_theta_phi = rho * node.accu_delta_theta_phi + (1. - rho) * (delta_theta_phi**2) - - # update weights - w_kc += delta_w_kc - w_ki += delta_w_ki - w_outc += delta_w_outc - w_outi += delta_w_outi - w_ci += delta_w_ci - w_cc += delta_w_cc - w_ini += delta_w_ini - w_inc += delta_w_inc - w_phii += delta_w_phii - w_phic += delta_w_phic - - # update biases - # theta_i += delta_theta_i - theta_k += delta_theta_k - theta_out += delta_theta_out - theta_in += delta_theta_in - theta_phi += delta_theta_phi - - # this will provide new w values to be written back to the node net, - # as well as deriv_lm_prev values to be used in the next step - node.get_updated_parameters = theano.function([rho, epsilon, steps], - errors, - updates=[(node.w_kc, w_kc), - (node.w_ki, w_ki), - (node.w_outc, w_outc), - (node.w_outi, w_outi), - (node.w_ci, w_ci), - (node.w_cc, w_cc), - (node.w_ini, w_ini), - (node.w_inc, w_inc), - (node.w_phii, w_phii), - (node.w_phic, w_phic), - (node.theta_i, theta_i), - (node.theta_k, theta_k), - (node.theta_in, theta_in), - (node.theta_out, theta_out), - (node.theta_phi, theta_phi), - (node.accu_grad_w_kc, accu_grad_w_kc), - (node.accu_delta_w_kc, accu_delta_w_kc), - (node.accu_grad_w_ki, accu_grad_w_ki), - (node.accu_delta_w_ki, accu_delta_w_ki), - (node.accu_grad_w_outc, accu_grad_w_outc), - (node.accu_delta_w_outc, accu_delta_w_outc), - (node.accu_grad_w_outi, accu_grad_w_outi), - (node.accu_delta_w_outi, accu_delta_w_outi), - (node.accu_grad_w_ci, accu_grad_w_ci), - (node.accu_delta_w_ci, accu_delta_w_ci), - (node.accu_grad_w_cc, accu_grad_w_cc), - (node.accu_delta_w_cc, accu_delta_w_cc), - (node.accu_grad_w_ini, accu_grad_w_ini), - (node.accu_delta_w_ini, accu_delta_w_ini), - (node.accu_grad_w_inc, accu_grad_w_inc), - (node.accu_delta_w_inc, accu_delta_w_inc), - (node.accu_grad_w_phii, accu_grad_w_phii), - (node.accu_delta_w_phii, accu_delta_w_phii), - (node.accu_grad_w_phic, accu_grad_w_phic), - (node.accu_delta_w_phic, accu_delta_w_phic), - (node.accu_grad_theta_k, accu_grad_theta_k), - (node.accu_delta_theta_k, accu_delta_theta_k), - (node.accu_grad_theta_out, accu_grad_theta_out), - (node.accu_delta_theta_out, accu_delta_theta_out), - (node.accu_grad_theta_in, accu_grad_theta_in), - (node.accu_delta_theta_in, accu_delta_theta_in), - (node.accu_grad_theta_phi, accu_grad_theta_phi), - (node.accu_delta_theta_phi, accu_delta_theta_phi) - ], - on_unused_input='warn') - - node.get_error = theano.function([], T.sum(T.square(tgt[SEQUENCE_LENGTH] - y_k[SEQUENCE_LENGTH])) / 2.) - - node.initialized = True - - # every step - - error_prev = node.get_state("current_error") - if error_prev is None: - error_prev = 0. - node.get_gate('e').gate_function(error_prev) - - if netapi.step % 3 == 0 and node.get_slot("debug").activation > 0.5: - netapi.logger.debug("%10i: lstm sample step" % netapi.step) - - if netapi.step % 3 != 1: - return - # every three steps, sample activation from LSTMs - - node.t += 1 - if node.t >= SEQUENCE_LENGTH: - node.t = 0 - - # roll time snapshots to the left - node.t_a_i_matrix = np.roll(node.t_a_i_matrix, -1, 0) - node.t_a_t_matrix = np.roll(node.t_a_t_matrix, -1, 0) - node.t_a_o_matrix = np.roll(node.t_a_o_matrix, -1, 0) - node.t_a_h_gen_matrix = np.roll(node.t_a_h_gen_matrix, -1, 0) - node.t_a_h_por_matrix = np.roll(node.t_a_h_por_matrix, -1, 0) - node.t_a_h_gin_matrix = np.roll(node.t_a_h_gin_matrix, -1, 0) - node.t_a_h_gou_matrix = np.roll(node.t_a_h_gou_matrix, -1, 0) - node.t_a_h_gfg_matrix = np.roll(node.t_a_h_gfg_matrix, -1, 0) - - # insert new snapshot at the end - node.t_a_i_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, input) - node.t_a_t_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, target) - node.t_a_o_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, output) - node.t_a_h_gen_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, lstm_gen) - node.t_a_h_por_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, lstm_por) - node.t_a_h_gin_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, lstm_gou) - node.t_a_h_gou_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, lstm_gin) - node.t_a_h_gfg_matrix[SEQUENCE_LENGTH - 1, :] = netapi.get_activations(nodespace, lstm_gfg) - node.samples += 1 - - if node.get_slot("debug").activation > 0.5: - netapi.logger.debug("%10i: bp sample #%i t, i, c, k data: t[0]=%.6f i[0]=%.6f c[0]=%.6f k[0]=%.6f" - % (netapi.step, node.t, node.t_a_t_matrix[node.t, 0], node.t_a_i_matrix[node.t, 0], - node.t_a_h_por_matrix[node.t, 0], node.t_a_o_matrix[node.t, 0])) - - if node.t != SEQUENCE_LENGTH - 1 or node.samples < 3: - return - # every sequence length samples, do backpropagation-through-time for the sampled sequence - - # netapi.logger.debug("t=%.6f o=%.6f s=%.6f c=%.6f i=%.6f" % (node.t_a_t_matrix[0, 0], node.t_a_o_matrix[0, 0], - # node.t_a_h_gen_matrix[0, 0], node.t_a_h_por_matrix[0, 0], node.t_a_i_matrix[0, 0])) - # netapi.logger.debug("t=%.6f o=%.6f s=%.6f c=%.6f i=%.6f" % (node.t_a_t_matrix[1, 0], node.t_a_o_matrix[1, 0], - # node.t_a_h_gen_matrix[1, 0], node.t_a_h_por_matrix[1, 0], node.t_a_i_matrix[1, 0])) - # netapi.logger.debug("t=%.6f o=%.6f s=%.6f c=%.6f i=%.6f" % (node.t_a_t_matrix[2, 0], node.t_a_o_matrix[2, 0], - # node.t_a_h_gen_matrix[2, 0], node.t_a_h_por_matrix[2, 0], node.t_a_i_matrix[2, 0])) - - # fill w and a variables with values from the Node Net - node.w_kc.set_value(netapi.get_link_weights(nodespace, lstm_por, nodespace, output), borrow=True) - node.w_ki.set_value(netapi.get_link_weights(nodespace, input, nodespace, output), borrow=True) - node.w_outc.set_value(netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_gou), borrow=True) - node.w_outi.set_value(netapi.get_link_weights(nodespace, input, nodespace, lstm_gou), borrow=True) - node.w_ci.set_value(netapi.get_link_weights(nodespace, input, nodespace, lstm_por), borrow=True) - node.w_cc.set_value(netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_por), borrow=True) - node.w_ini.set_value(netapi.get_link_weights(nodespace, input, nodespace, lstm_gin), borrow=True) - node.w_inc.set_value(netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_gin), borrow=True) - node.w_phii.set_value(netapi.get_link_weights(nodespace, input, nodespace, lstm_gfg), borrow=True) - node.w_phic.set_value(netapi.get_link_weights(nodespace, lstm_por, nodespace, lstm_gfg), borrow=True) - - node.theta_i.set_value(netapi.get_thetas(nodespace, input), borrow=True) - node.theta_k.set_value(netapi.get_thetas(nodespace, output), borrow=True) - node.theta_in.set_value(netapi.get_thetas(nodespace, lstm_gin), borrow=True) - node.theta_out.set_value(netapi.get_thetas(nodespace, lstm_gou), borrow=True) - node.theta_phi.set_value(netapi.get_thetas(nodespace, lstm_gfg), borrow=True) - - node.tgt.set_value(node.t_a_t_matrix, borrow=True) - node.y_k.set_value(node.t_a_o_matrix, borrow=True) - node.y_i.set_value(node.t_a_i_matrix, borrow=True) - node.y_c.set_value(node.t_a_h_por_matrix, borrow=True) - node.s.set_value(node.t_a_h_gen_matrix, borrow=True) - node.net_out.set_value(node.t_a_h_gou_matrix, borrow=True) - node.net_in.set_value(node.t_a_h_gin_matrix, borrow=True) - node.net_phi.set_value(node.t_a_h_gfg_matrix, borrow=True) - - rho = float(node.get_parameter('adadelta_rho')) - if not isinstance(rho, Number): - rho = 0.95 - node.set_parameter('adadelta_rho', rho) - - epsilon = float(node.get_parameter('adadelta_epsilon')) - if not isinstance(epsilon, Number): - epsilon = 0.000001 - node.set_parameter('adadelta_epsilon', epsilon) - - len_output = len(netapi.get_activations(nodespace, output)) - len_input = len(netapi.get_activations(nodespace, input)) - len_hidden = len(netapi.get_activations(nodespace, lstm_por)) - - # update the weights, all derivatives and weight update sums are 0 for the first step - errors = node.get_updated_parameters(rho, epsilon, node.t + 1) - - if node.get_slot("debug").activation > 0.5: - netapi.logger.debug("%10i: bp with error %.4f" % (netapi.step, errors[SEQUENCE_LENGTH - 1])) - - # write back changed weights to node net - - # netapi.set_thetas(nodespace, input, node.theta_i.get_value(borrow=True)) - if node.get_parameter("bias_gin") == "true": - netapi.set_thetas(nodespace, lstm_gin, node.theta_in.get_value(borrow=True)) - if node.get_parameter("bias_gou") == "true": - netapi.set_thetas(nodespace, lstm_gou, node.theta_out.get_value(borrow=True)) - if node.get_parameter("bias_gfg") == "true": - netapi.set_thetas(nodespace, lstm_gfg, node.theta_phi.get_value(borrow=True)) - - netapi.set_link_weights(nodespace, input, nodespace, lstm_gou, node.w_outi.get_value(borrow=True)) - netapi.set_link_weights(nodespace, input, nodespace, lstm_por, node.w_ci.get_value(borrow=True)) - netapi.set_link_weights(nodespace, input, nodespace, lstm_gin, node.w_ini.get_value(borrow=True)) - netapi.set_link_weights(nodespace, input, nodespace, lstm_gfg, node.w_phii.get_value(borrow=True)) - netapi.set_link_weights(nodespace, lstm_por, nodespace, output, node.w_kc.get_value(borrow=True)) - - if node.get_parameter("links_io") == "true": - netapi.set_link_weights(nodespace, input, nodespace, output, node.w_ki.get_value(borrow=True)) - if node.get_parameter("links_porpor") == "true": - netapi.set_link_weights(nodespace, lstm_por, nodespace, lstm_por, node.w_cc.get_value(borrow=True)) - if node.get_parameter("links_porgin") == "true": - netapi.set_link_weights(nodespace, lstm_por, nodespace, lstm_gin, node.w_inc.get_value(borrow=True)) - if node.get_parameter("links_porgou") == "true": - netapi.set_link_weights(nodespace, lstm_por, nodespace, lstm_gou, node.w_outc.get_value(borrow=True)) - if node.get_parameter("links_porgfg") == "true": - netapi.set_link_weights(nodespace, lstm_por, nodespace, lstm_gfg, node.w_phic.get_value(borrow=True)) - - node.set_state('current_error', errors[SEQUENCE_LENGTH - 1]) - node.set_state('error', node.get_state('error') + errors[SEQUENCE_LENGTH - 1]) - if node.get_state('updates') % 100 == 0: - netapi.logger.debug("Number of lstm backprop steps computed %d" % node.get_state('updates')) - netapi.logger.debug("Error %.6f (Latest from loop: 0=%.6f)" % ((node.get_state('error') / 100), errors[SEQUENCE_LENGTH - 1])) - node.set_state('error', 0.0) - - # after weight updates, reset gen loops of lstms - netapi.substitute_activations(nodespace, lstm_gen, np.zeros_like(netapi.get_activations(nodespace, lstm_gen))) - # netapi.substitute_activations(nodespace, "lstm_por", np.zeros_like(a_h_por_array)) - - node.set_state('updates', node.get_state('updates') + 1) - - -def gradient_descent(netapi, node=None, **params): - """ - Online gradient descent with backpropagation for three layers (input, hidden, - and output layer) and AdaDelta for adapting the learning rate per parameter. - - References: - [1] Werbos, PJ. "Beyond Regression: New Tools for Prediction and Analysis - in the Behavioral Sciences." (1974). - [2] Zeiler, MD. "ADADELTA: An adaptive learning rate method." (2012). - [3] Vincent, P. "Extracting and Composing Robust Features with Denoising - Autoencoders." (2008). - """ - - # To be able to switch this native module on and off, require positive - # activation on the gen slot for its code to be run. - if node.get_slot('gen').activation > 0: - - import theano - import theano.tensor as T - - # get shared name prefix of nodes in input, hidden, and output layers - input_ = node.get_parameter('input_prefix') - hidden = node.get_parameter('hidden_prefix') - output = node.get_parameter('output_prefix') - - # get the name of the nodespace where the input lives - ns_input_name = node.get_parameter('input_nodespace') - - # get nodespace uids of nodes in input, hidden, and output layers - # assumption: if the input layer consists of sensor nodes, they have their - # own nodespace, all other nodes are in this node's nodespace - ns_input_uid = None - for ns in netapi.get_nodespaces(): - if ns.name == ns_input_name: - ns_input_uid = ns.uid - break - ns_hidden_uid = node.parent_nodespace - ns_output_uid = node.parent_nodespace - - # initialization - if not hasattr(node, 'initialized'): - - node.set_state('cumulative_error', 0) - - sparse = str(node.get_parameter('ae_type')) == "sparse" - # denoising = str(node.get_parameter('ae_type')) == "denoising" - tied_weights = str(node.get_parameter('tied_weights')) == "True" - - # group nodes - netapi.group_nodes_by_names(ns_input_uid, node_name_prefix=input_) - netapi.group_nodes_by_names(ns_hidden_uid, node_name_prefix=hidden) - netapi.group_nodes_by_names(ns_output_uid, node_name_prefix=output) - - # get activation values - a_i_array = netapi.get_activations(ns_input_uid, input_) - a_h_array = netapi.get_activations(ns_hidden_uid, hidden) - a_o_array = netapi.get_activations(ns_output_uid, output) - - node.set_parameter('error', 0.0) # store error values to observe how training develops - - len_input = len(a_i_array) - len_hidden = len(a_h_array) - len_output = len(a_o_array) - - if len_input == 0: - netapi.logger.warning("Node net has no input nodes whose names start with '%s'", input_) - node.set_parameter('ctr', 0) - return - elif len_hidden == 0: - netapi.logger.warning("Node net has no hidden nodes whose names start with '%s'.", hidden) - node.set_parameter('ctr', 0) - return - elif len_output == 0: - netapi.logger.warning("Node net has no output names whose names start with '%s'.", output) - node.set_parameter('ctr', 0) - return - else: - netapi.logger.info("Initializing theano-based autoencoder backprop with layout: %i -> %i -> %i", - len_input, len_hidden, len_output) - - # get parameter values from node net - b_h_array = netapi.get_thetas(ns_hidden_uid, hidden) - b_o_array = netapi.get_thetas(ns_output_uid, output) - w_hi_array = netapi.get_link_weights(ns_input_uid, input_, ns_hidden_uid, hidden) - w_oh_array = netapi.get_link_weights(ns_hidden_uid, hidden, ns_output_uid, output) - - # declare shared variables ( shared b/w theano and node nets ) - a_i = node.a_i = theano.shared(value=a_i_array.astype(T.config.floatX), name="a_i", borrow=False) - a_h = node.a_h = theano.shared(value=a_h_array.astype(T.config.floatX), name="a_h", borrow=False) - a_o = node.a_o = theano.shared(value=a_o_array.astype(T.config.floatX), name="a_o", borrow=False) - b_h = node.b_h = theano.shared(value=b_h_array.astype(T.config.floatX), name="b_h", borrow=False) - b_o = node.b_o = theano.shared(value=b_o_array.astype(T.config.floatX), name="b_o", borrow=False) - w_hi = node.w_hi = theano.shared(value=w_hi_array.astype(T.config.floatX), name="w_hi", borrow=False) - w_oh = node.w_oh = theano.shared(value=w_oh_array.astype(T.config.floatX), name="w_oh", borrow=False) - - # write initial parameter values to shared variables - node.b_h.set_value(b_h_array, borrow=True) - node.b_o.set_value(b_o_array, borrow=True) - node.w_hi.set_value(w_hi_array, borrow=True) - node.w_oh.set_value(w_oh_array, borrow=True) - - # initialize accumulation variables for AdaDelta, ie. mean square gradients and mean square deltas - ms_grad_b_o = node.ms_grad_b_o = theano.shared(value=np.zeros_like(b_o_array), name="ms_grad_b_o", borrow=True) - ms_grad_w_oh = node.ms_grad_w_oh = theano.shared(value=np.zeros_like(w_oh_array), name="ms_grad_w_oh", borrow=True) - ms_grad_b_h = node.ms_grad_b_h = theano.shared(value=np.zeros_like(b_h_array), name="ms_grad_b_h", borrow=True) - ms_grad_w_hi = node.ms_grad_w_hi = theano.shared(value=np.zeros_like(w_hi_array), name="ms_grad_w_hi", borrow=True) - - ms_delta_b_o = node.ms_delta_b_o = theano.shared(value=np.zeros_like(b_o_array), name="ms_delta_b_o", borrow=True) - ms_delta_w_oh = node.ms_delta_w_oh = theano.shared(value=np.zeros_like(w_oh_array), name="ms_delta_w_oh", borrow=True) - ms_delta_b_h = node.ms_delta_b_h = theano.shared(value=np.zeros_like(b_h_array), name="ms_delta_b_h", borrow=True) - ms_delta_w_hi = node.ms_delta_w_hi = theano.shared(value=np.zeros_like(w_hi_array), name="ms_delta_w_hi", borrow=True) - - # make function parameters theano compatible - weight_decay = T.scalar("weight_decay", dtype=T.config.floatX) - sparsity_value = T.scalar("sparsity_value", dtype=T.config.floatX) - sparsity_penalty = T.scalar("sparsity_penalty", dtype=T.config.floatX) - ada_rho = T.scalar("ada_rho", dtype=T.config.floatX) - ada_eps = T.scalar("ada_eps", dtype=T.config.floatX) - - # declare the reconstruction error - error_term = T.sum(T.square(a_o - a_i)) / 2. # squared error - # error_term = -T.sum(a_i * T.log(a_o) + (1. - a_i) * T.log(1. - a_o)) # cross-entropy - - # use a weight constraint as a regularizer - weight_constraint = (weight_decay / 2.) * (T.sum(T.square(w_hi)) + T.sum(T.square(w_oh))) - - if sparse: # training criterion for a sparse autoencoder - - # save the average activation of hidden units; initialize to first activation received - avg_a_h = node.avg_a_h = theano.shared(value=a_h_array, name="avg_a_h", borrow=False) - new_avg_a_h = 0.95 * avg_a_h + (1 - 0.95) * a_h # for gradient checking, set new_avg_a_h = a_h - - rho = sparsity_value - information_gain = rho * T.log(rho / new_avg_a_h) + (1. - rho) * T.log((1. - rho) / (1. - new_avg_a_h)) - - sparsity_constraint = sparsity_penalty * T.sum(information_gain) - cost = error_term + weight_constraint + sparsity_constraint - - else: # training criterion for a denoising autoencoder - - cost = error_term + weight_constraint - - node.cost = theano.function([weight_decay, sparsity_value, sparsity_penalty], cost, on_unused_input='ignore') - node.error = theano.function([], error_term / len(b_h_array)) - - # compute gradients - sigmoid_deriv_a_o = a_o * (1. - a_o) - grad_o = (a_o - a_i) * sigmoid_deriv_a_o # squared error # T.grad(cost, z_o) - # grad_o = ((a_i - a_o) / (a_o - a_o**2)) * sigmoid_deriv_a_o # cross-entropy - - sigmoid_deriv_a_h = a_h * (1. - a_h) - - if sparse: - - grad_w_oh = T.dot(T.reshape(grad_o, (len_input, 1)), T.reshape(a_h, (1, len_hidden))) + weight_decay * w_oh - grad_sparsity = (- rho / new_avg_a_h + (1. - rho) / (1. - new_avg_a_h)).T - grad_h = (T.dot(w_oh.T, grad_o) + sparsity_penalty * grad_sparsity) * sigmoid_deriv_a_h - grad_w_hi = T.dot(T.reshape(grad_h, (len_hidden, 1)), T.reshape(a_i, (1, len_input))) + weight_decay * w_hi - - else: # denoising - - grad_w_oh = T.dot(T.reshape(grad_o, (len_input, 1)), T.reshape(a_h, (1, len_hidden))) + weight_decay * w_oh - grad_h = T.dot(w_oh.T, grad_o) * sigmoid_deriv_a_h - grad_w_hi = T.dot(T.reshape(grad_h, (len_hidden, 1)), T.reshape(a_i, (1, len_input))) + weight_decay * w_hi - - if tied_weights: - grad_w_oh = grad_w_oh + grad_w_hi.T - gradients = [grad_o, grad_w_oh, grad_h] - ms_grad = [ms_grad_b_o, ms_grad_w_oh, ms_grad_b_h] - ms_delta = [ms_delta_b_o, ms_delta_w_oh, ms_delta_b_h] - else: - gradients = [grad_o, grad_w_oh, grad_h, grad_w_hi] - ms_grad = [ms_grad_b_o, ms_grad_w_oh, ms_grad_b_h, ms_grad_w_hi] - ms_delta = [ms_delta_b_o, ms_delta_w_oh, ms_delta_b_h, ms_delta_w_hi] - - # update accumulation variables for AdaDelta and compute new deltas - # compute an exponentially decaying average of squared gradients - # ie. recent gradients are more important and the quantity doesn't continue to grow - # thereby allowing the learning rate to grow or shrink as time progresses ( rather than just shrink as in AdaGrad ) - new_ms_grad = [ada_rho * ms_g + (1 - ada_rho) * (g**2) for ms_g, g in zip(ms_grad, gradients)] - # Note: the square root of the mean squared gradients plus epsilon is effectively the RMS of the gradients - # epsilon is added ~"to start off the first iteration and to ensure progress when previous updates become small" - deltas = [(T.sqrt(ms_d + ada_eps) / T.sqrt(ms_g + ada_eps)) * g for ms_d, ms_g, g in zip(ms_delta, new_ms_grad, gradients)] - # compute an exponentially decaying average of squared deltas -- this is to ensure correct units - new_ms_delta = [ada_rho * ms_d + (1 - ada_rho) * (d**2) for ms_d, d in zip(ms_delta, deltas)] - - # update parameters, ie. old_value - learning_rate * delta_value - if tied_weights: - new_b_o, new_w_oh, new_b_h = (old - update for old, update in zip([b_o, w_oh, b_h], deltas)) - new_w_hi = new_w_oh.T - new_ms_grad.append(new_ms_grad[1].T) - new_ms_delta.append(new_ms_delta[1].T) - gradients.append(gradients[1].T) - else: - new_b_o, new_w_oh, new_b_h, new_w_hi = (old - update for old, update in zip([b_o, w_oh, b_h, w_hi], deltas)) - - if sparse: - - update_function = theano.function([weight_decay, sparsity_value, sparsity_penalty, ada_rho, ada_eps], - None, - updates=[(b_o, new_b_o), - (w_oh, new_w_oh), - (b_h, new_b_h), - (w_hi, new_w_hi), - (avg_a_h, new_avg_a_h), - (ms_grad_b_o, new_ms_grad[0]), - (ms_grad_w_oh, new_ms_grad[1]), - (ms_grad_b_h, new_ms_grad[2]), - (ms_grad_w_hi, new_ms_grad[3]), - (ms_delta_b_o, new_ms_delta[0]), - (ms_delta_w_oh, new_ms_delta[1]), - (ms_delta_b_h, new_ms_delta[2]), - (ms_delta_w_hi, new_ms_delta[3])], - on_unused_input='ignore') - - else: # denoising - - update_function = theano.function([weight_decay, sparsity_value, sparsity_penalty, ada_rho, ada_eps], - None, - updates=[(b_o, new_b_o), - (w_oh, new_w_oh), - (b_h, new_b_h), - (w_hi, new_w_hi), - (ms_grad_b_o, new_ms_grad[0]), - (ms_grad_w_oh, new_ms_grad[1]), - (ms_grad_b_h, new_ms_grad[2]), - (ms_grad_w_hi, new_ms_grad[3]), - (ms_delta_b_o, new_ms_delta[0]), - (ms_delta_w_oh, new_ms_delta[1]), - (ms_delta_b_h, new_ms_delta[2]), - (ms_delta_w_hi, new_ms_delta[3])], - on_unused_input='ignore') - - node.get_updated_parameters = update_function - - # for gradient checking use the following function: - node.get_gradients = theano.function([weight_decay, sparsity_value, sparsity_penalty, ada_rho, ada_eps], - [gradients[0], gradients[1], gradients[2], gradients[3]], on_unused_input='ignore') - - node.initialized = True - - # get input activations from node net - a_i_array = netapi.get_activations(ns_input_uid, input_) - - # learn only if activation on the input layer has been persistent for as many steps as your neural net has layers - # Note: since we're currently using denoising autoencoders, this means persistent up to Bernoulli noise - try: - # check if activation has changed since the last step ( by testing if there's any different activation value ) - bool_idx = node.prev_a_i != a_i_array - input_changed = np.any(bool_idx) - - # if deviating activations were 0 ( i.e most likely the effect of Bernoulli noising ), assume no change - is_zero = node.prev_a_i[bool_idx] == 0 - # if is_zero contains elements but not all input activations and their values are all zero, assume no change - if len(is_zero) and len(is_zero) < len(a_i_array) and np.all(is_zero): - input_changed = False - except: - input_changed = True - - node.prev_a_i = a_i_array - - if input_changed: - node.set_parameter('ctr', 1) - else: - node.set_parameter('ctr', int(node.get_parameter('ctr')) + 1) - - # until counter equals number of layers, ie. the same activation has reached all layers, don't compute - if node.get_parameter('ctr') < 3: - return - - # get other activations from node net - a_h_array = netapi.get_activations(ns_hidden_uid, hidden) - a_o_array = netapi.get_activations(ns_output_uid, output) - - # define learning parameters - param = node.get_parameter('weight_decay') - if param is None: - weight_decay = netapi.floatX(4e-06) # 0.0001 . 1e-07 assuming batches of size 1000 . 4e-06 assuming batches of size 256 - node.set_parameter('weight_decay', str(weight_decay)) # store as regular float to appease the serializer - else: - weight_decay = netapi.floatX(param) - - param = node.get_parameter('sparsity_value') - if param is None: - sparsity_value = netapi.floatX(0.05) - node.set_parameter('sparsity_value', str(sparsity_value)) - else: - sparsity_value = netapi.floatX(param) - - param = node.get_parameter('sparsity_penalty') - if param is None: - sparsity_penalty = netapi.floatX(0.001) # 3.0 . 0.003 assuming batches of size 1000 . 0.01 assuming batches of size 256 - node.set_parameter('sparsity_penalty', str(sparsity_penalty)) - else: - sparsity_penalty = netapi.floatX(param) - - param = node.get_parameter('adadelta_rho') - if param is None: - ada_rho = netapi.floatX(0.95) - node.set_parameter('adadelta_rho', str(ada_rho)) - else: - ada_rho = netapi.floatX(param) - - param = node.get_parameter('adadelta_eps') - if param is None: - ada_eps = netapi.floatX(1e-6) - node.set_parameter('adadelta_eps', str(ada_eps)) - else: - ada_eps = netapi.floatX(param) - - param = node.get_parameter('ae_type') - if param is None: - ae_type = 'sparse' # options: 'sparse', 'denoising' - node.set_parameter('ae_type', 'sparse') - else: - ae_type = str(param) - - param = node.get_parameter('t') - if param is None: - t = 0 - node.set_parameter('t', t) - else: - t = int(param) - - # gradient checking - # Note: use double precision when running gradient checks - if node.get_parameter('check_grad') == 'yes': - - # get values of biases and weights from node net - b_h_array = netapi.get_thetas(ns_hidden_uid, hidden) - b_o_array = netapi.get_thetas(ns_output_uid, output) - w_hi_array = netapi.get_link_weights(ns_input_uid, input_, ns_hidden_uid, hidden) - w_oh_array = netapi.get_link_weights(ns_hidden_uid, hidden, ns_output_uid, output) - - # compute the analytical gradient - anal_grad = compute_analytic_gradient( - netapi, node, a_i_array, a_h_array, a_o_array, b_h_array, b_o_array, w_hi_array, w_oh_array, - weight_decay, sparsity_value, sparsity_penalty, ada_rho, ada_eps) - - # compute the numerical gradient - num_grad = compute_numeric_gradient( - netapi, node, a_i_array, a_h_array, a_o_array, b_h_array, b_o_array, w_hi_array, w_oh_array, - weight_decay, sparsity_value, sparsity_penalty) - - # compare them - diff = np.linalg.norm(num_grad - anal_grad) / np.linalg.norm(num_grad + anal_grad) - print("Gradient difference: %e" % diff) # %.10f" % diff - print("The norm of the difference between numerical and analytical gradient should be < 1e-9\n") - - # write values to shared variables - node.a_i.set_value(a_i_array, borrow=True) - node.a_h.set_value(a_h_array, borrow=True) - node.a_o.set_value(a_o_array, borrow=True) - - # update values in shared variables ( using backpropgation of the gradients ) - node.get_updated_parameters(weight_decay, sparsity_value, sparsity_penalty, ada_rho, ada_eps) - - # write new parameter values to node net - netapi.set_thetas(ns_output_uid, output, node.b_o.get_value(borrow=True)) - netapi.set_link_weights(ns_hidden_uid, hidden, ns_output_uid, output, node.w_oh.get_value(borrow=True)) - netapi.set_thetas(ns_hidden_uid, hidden, node.b_h.get_value(borrow=True)) - netapi.set_link_weights(ns_input_uid, input_, ns_hidden_uid, hidden, node.w_hi.get_value(borrow=True)) - - error = float(node.error()) - # save current error as node parameter - node.set_parameter('error', error) - node.set_state('cumulative_error', node.get_state('cumulative_error') + error) - - t = int(node.get_parameter('t')) - if t % 1000 == 0: - netapi.logger.debug("Number of backprop steps computed %d" % t) - netapi.logger.debug("Average Error %.6f (Latest: 0=%.6f)" % ((node.get_state('cumulative_error') / 1000), error)) - node.set_state('cumulative_error', 0.0) - - # reset counter after successful backprop step; cf. must wait for new sensory activation to reach output layer - node.set_parameter('ctr', 0) - node.set_parameter('t', t + 1) - - -def sigmoid(z): - """ The sigmoid ( activation ) function. """ - return 1. / (1. + np.exp(-z)) - - -def compute_analytic_gradient(netapi, node, a_i, a_h, a_o, b_h, b_o, w_hi, w_oh, weight_decay, - sparsity_value, sparsity_penalty, ada_rho, ada_eps): - - # make sure borrow is False here because otherwise the buffers are overwritten and - # compute_numerical_gradient(..) still needs these same input values for proper comparison - node.a_i.set_value(a_i, borrow=False) - node.a_h.set_value(a_h, borrow=False) - node.a_o.set_value(a_o, borrow=False) - node.b_h.set_value(b_h, borrow=False) - node.b_o.set_value(b_o, borrow=False) - node.w_hi.set_value(w_hi, borrow=False) - node.w_oh.set_value(w_oh, borrow=False) - - delta_o, delta_w_oh, delta_h, delta_w_hi = \ - node.get_gradients(weight_decay, sparsity_value, sparsity_penalty, ada_rho, ada_eps) - - gradient = np.concatenate((delta_o, np.ravel(delta_w_oh), delta_h, np.ravel(delta_w_hi))) - - return gradient - - -def compute_numeric_gradient(netapi, node, a_i, a_h, a_o, b_h, b_o, w_hi, w_oh, weight_decay, sparsity_value, sparsity_penalty): - """ Compute numerical gradient for validating backprop implementation above. """ - - from copy import deepcopy - - # helper variables - epsilon = netapi.floatX(1e-4) - ni = len(b_o) - nh = len(b_h) - nih = ni * nh - - theta = np.concatenate((b_o, np.ravel(w_oh), b_h, np.ravel(w_hi))) - - n = theta.shape[0] - I = np.eye(n, dtype=netapi.floatX) - gradient = np.zeros(theta.shape, dtype=netapi.floatX) - - for i in range(n): - - eps_vec = np.array(I[:, i] * epsilon, dtype=netapi.floatX) - eps_plus = theta + eps_vec - eps_minus = theta - eps_vec - - # split theta into parts, recompute activations, update shared variables, compute cost - b_o_plus = eps_plus[: ni] - w_oh_plus = eps_plus[ni: ni + nih].reshape((ni, nh)) - b_h_plus = eps_plus[ni + nih: ni + nih + nh] - w_hi_plus = eps_plus[ni + nih + nh:].reshape((nh, ni)) - a_i_plus = deepcopy(a_i) - a_h_plus = np.ravel(sigmoid(w_hi_plus.dot(a_i_plus) + b_h_plus)) - a_o_plus = np.ravel(sigmoid(w_oh_plus.dot(a_h_plus) + b_o_plus)) - - node.a_i.set_value(a_i_plus, borrow=True) - node.a_h.set_value(a_h_plus, borrow=True) - node.a_o.set_value(a_o_plus, borrow=True) - node.b_h.set_value(b_h_plus, borrow=True) - node.b_o.set_value(b_o_plus, borrow=True) - node.w_hi.set_value(w_hi_plus, borrow=True) - node.w_oh.set_value(w_oh_plus, borrow=True) - - cost = node.cost(weight_decay, sparsity_value, sparsity_penalty) - - # split theta into parts, recompute activations, update shared variables, compute cost - b_o_minus = eps_minus[: ni] - w_oh_minus = eps_minus[ni: ni + nih].reshape((ni, nh)) - b_h_minus = eps_minus[ni + nih: ni + nih + nh] - w_hi_minus = eps_minus[ni + nih + nh:].reshape((nh, ni)) - a_i_minus = deepcopy(a_i) - a_h_minus = np.ravel(sigmoid(w_hi_minus.dot(a_i_minus) + b_h_minus)) - a_o_minus = np.ravel(sigmoid(w_oh_minus.dot(a_h_minus) + b_o_minus)) - - node.a_i.set_value(a_i_minus, borrow=True) - node.a_h.set_value(a_h_minus, borrow=True) - node.a_o.set_value(a_o_minus, borrow=True) - node.b_h.set_value(b_h_minus, borrow=True) - node.b_o.set_value(b_o_minus, borrow=True) - node.w_hi.set_value(w_hi_minus, borrow=True) - node.w_oh.set_value(w_oh_minus, borrow=True) - - cost_ = node.cost(weight_decay, sparsity_value, sparsity_penalty) - - # compute cost difference - gradient[i] = (cost - cost_) / (2. * epsilon) - - if i % 1000 == 0: - print("Computed numeric gradient for %d parameters" % i) - - return gradient From c9281501a9dfbe7e903302733627e4d1cc90af71 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 23 May 2017 16:56:02 +0200 Subject: [PATCH 788/945] remove traces of built-in native modules special treatment --- micropsi_core/runtime.py | 2 -- micropsi_server/tests/test_json_api.py | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index fe256f2f..b1a84bbc 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1779,8 +1779,6 @@ def reload_code(): errors = [] # load builtins: - from micropsi_core.nodenet.native_modules import nodetypes - native_modules.update(nodetypes) operationspath = os.path.dirname(os.path.realpath(__file__)) + '/nodenet/operations/' for file in os.listdir(operationspath): import micropsi_core.nodenet.operations diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index b5992ed1..34ffac3a 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -942,10 +942,7 @@ def test_get_available_node_types(app, test_nodenet): def test_get_available_native_module_types(app, test_nodenet, engine): response = app.get_json('/rpc/get_available_native_module_types(nodenet_uid="%s")' % test_nodenet) assert_success(response) - if engine == 'dict_engine': - assert response.json_body['data'] == {} - elif engine == 'theano_engine': - assert "GradientDescent" in response.json_body['data'] + assert response.json_body['data'] == {} def test_set_node_parameters(app, test_nodenet): From c0a06cbff03952027c45f74d5021ee7aba845e27 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 24 May 2017 18:11:35 +0200 Subject: [PATCH 789/945] reload and revert api method --- micropsi_core/_runtime_api_world.py | 25 ++++++++++++++++----- micropsi_core/runtime.py | 30 +++++++++++--------------- micropsi_server/micropsi_app.py | 6 ++++++ micropsi_server/tests/test_json_api.py | 26 ++++++++++++++++++++++ 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index 8cfac963..d145f67c 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -231,17 +231,32 @@ def set_world_data(world_uid, data): def revert_world(world_uid): """Reverts the world to the last saved state.""" - data = micropsi_core.runtime.world_data[world_uid] + unload_world(world_uid) + load_world(world_uid) + return True + + +def unload_world(world_uid): if world_uid in micropsi_core.runtime.worlds: micropsi_core.runtime.worlds[world_uid].__del__() del micropsi_core.runtime.worlds[world_uid] - if data.get('world_type'): - micropsi_core.runtime.worlds[world_uid] = get_world_class_from_name(data.world_type)(**data) - else: - micropsi_core.runtime.worlds[world_uid] = world.World(**data) return True +def load_world(world_uid): + if world_uid not in micropsi_core.runtime.worlds: + if world_uid in micropsi_core.runtime.world_data: + data = micropsi_core.runtime.world_data[world_uid] + if "world_type" in data: + try: + micropsi_core.runtime.worlds[world_uid] = get_world_class_from_name(data.world_type)(**data) + except Exception as e: + logging.getLogger("system").error("Could not load world %s: %s - %s" % (data.world_type, e.__class__.__name__, str(e))) + else: + micropsi_core.runtime.worlds[world_uid] = world.World(**data) + return micropsi_core.runtime.worlds.get(world_uid) + + def save_world(world_uid): """Stores the world state on the server.""" data = micropsi_core.runtime.worlds[world_uid].data diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 9148a23e..a79627c2 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -420,24 +420,6 @@ def load_nodenet(nodenet_uid): return False, "Agent %s not found in %s" % (nodenet_uid, PERSISTENCY_PATH) -def load_world(world_uid): - global worlds - if world_uid not in worlds: - if world_uid in world_data: - if "world_type" in world_data[world_uid]: - try: - worlds[world_uid] = get_world_class_from_name(world_data[world_uid].world_type)(**world_data[world_uid]) - except TypeError: - worlds[world_uid] = world.World(**world_data[world_uid]) - # except AttributeError as err: - # logging.getLogger('system').warning("Unknown world_type: %s (%s)" % (world_data[world_uid].world_type, str(err))) - # except: - # logging.getLogger('system').warning("Can not instantiate World \"%s\": %s" % (world_data[world_uid].name, str(sys.exc_info()[1]))) - else: - worlds[world_uid] = world.World(**world_data[world_uid]) - return worlds.get(world_uid) - - def get_nodenet_metadata(nodenet_uid): """ returns the given nodenet's metadata""" nodenet = get_nodenet(nodenet_uid) @@ -762,6 +744,18 @@ def revert_nodenet(nodenet_uid, also_revert_world=False): return True +def reload_and_revert(nodenet_uid, also_revert_world=False): + """Returns the nodenet to the last saved state.""" + nodenet = get_nodenet(nodenet_uid) + world_uid = nodenet.world + unload_nodenet(nodenet_uid) + if world_uid: + unload_world(world_uid) + result = reload_code() + load_nodenet(nodenet_uid) + return result + + def save_nodenet(nodenet_uid): """Stores the nodenet on the server (but keeps it open).""" nodenet = get_nodenet(nodenet_uid) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 936bb0c8..3deada05 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1005,6 +1005,12 @@ def revert_nodenet(nodenet_uid): return runtime.revert_nodenet(nodenet_uid) +@rpc("reload_and_revert", permission_required="manage nodenets") +def reload_and_revert(nodenet_uid): + """ reload code, and revert calculation""" + return runtime.reload_and_revert(nodenet_uid) + + @rpc("save_nodenet", permission_required="manage nodenets") def save_nodenet(nodenet_uid): """ Persist the current state of the nodenet""" diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index b5992ed1..4c9d805a 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -296,6 +296,32 @@ def test_revert_both(app, test_nodenet, default_world): assert res.json_body['data']['current_world_step'] == 0 +def test_revert_and_reload(app, test_nodenet, default_world, resourcepath): + import os + app.set_auth() + app.post_json('/rpc/set_nodenet_properties', params=dict(nodenet_uid=test_nodenet, worldadapter="Default", world_uid=default_world)) + for i in range(5): + app.get_json('/rpc/step_calculation(nodenet_uid="%s")' % test_nodenet) + res = app.get_json('/rpc/get_calculation_state(nodenet_uid="%s")' % test_nodenet) + nodetype_file = os.path.join(resourcepath, 'nodetypes', 'Test', 'testnode.py') + with open(nodetype_file, 'w') as fp: + fp.write("""nodetype_definition = { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "gatetypes": ["gen", "foo", "bar"], + "symbol": "t"} + +def testnodefunc(netapi, node=None, **prams):\r\n return 17 +""") + app.get_json('/rpc/reload_and_revert(nodenet_uid="%s")' % test_nodenet) + res = app.get_json('/rpc/get_calculation_state(nodenet_uid="%s")' % test_nodenet) + assert res.json_body['data']['current_nodenet_step'] == 0 + assert res.json_body['data']['current_world_step'] == 0 + response = app.get_json('/rpc/get_available_node_types(nodenet_uid="%s")' % test_nodenet) + assert "Testnode" in response.json_body['data']['native_modules'] + + def test_save_nodenet(app, test_nodenet, default_world): app.set_auth() response = app.post_json('/rpc/set_nodenet_properties', params=dict(nodenet_uid=test_nodenet, nodenet_name="new_name", worldadapter="Default", world_uid=default_world)) From c865760ae45bc769a045e21a4ea4303a7a30c151 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 24 May 2017 19:31:20 +0200 Subject: [PATCH 790/945] nodenet shall keep track of open plots and close them on unload --- micropsi_core/nodenet/netapi.py | 7 +++++++ micropsi_core/nodenet/nodenet.py | 13 +++++++++++++ micropsi_core/runtime.py | 1 + 3 files changed, 21 insertions(+) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 1e951979..270e3d8a 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -565,3 +565,10 @@ def set_nodespace_properties(self, nodespace_uid, properties): def announce_nodes(self, nodespace_uid, numer_of_nodes, average_element_per_node): pass + + def show_plot(self, figure=None): + from matplotlib import pyplot as plt + if figure is None: + figure = plt.gca().figure + plt.show() + self.__nodenet.register_figure(figure) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index e49c81d1..e258eed8 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -180,6 +180,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.deleted_items = {} self.stepping_rate = [] self.dashboard_values = {} + self.figures = set() self.native_modules = {} for type, data in native_modules.items(): @@ -742,3 +743,15 @@ def check_stop_runner_condition(self): else: del self.self._runner_condition['monitor'] return False + + def register_figure(self, figure): + self.figures.add(figure) + + def close_figures(self): + try: + import matplotlib.pyplot as plt + self.logger.debug("Closing %d open pyplot figures" % len(self.figures)) + for fig in self.figures: + plt.close(fig) + except ImportError: + pass diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index a79627c2..618512a0 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -519,6 +519,7 @@ def unload_nodenet(nodenet_uid): if nodenet_uid in netapi_consoles: del netapi_consoles[nodenet_uid] nodenet = nodenets[nodenet_uid] + nodenet.close_figures() if nodenet.world: worlds[nodenet.world].unregister_nodenet(nodenet.uid) del nodenets[nodenet_uid] From 4735f4587af2b0cf69fdd865559d2689511de238 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 24 May 2017 22:12:04 +0200 Subject: [PATCH 791/945] add tornado to dependencies.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a371adcc..b172667a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,4 @@ WebTest==2.0.21 Pillow==3.2.0 networkx==1.11 git+https://github.com/micropsi-industries/spock.git +tornado==4.5.1 From c924eb18a58ac5e6957ec2bc87793491fc27297e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 13:54:58 +0200 Subject: [PATCH 792/945] move thread-startup to initialize() also, shut the tornado logger up --- micropsi_core/runtime.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 618512a0..5f880059 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -23,19 +23,16 @@ import sys import threading +import matplotlib +matplotlib.rcParams['webagg.port'] = 6545 +matplotlib.rcParams['webagg.open_in_browser'] = False +matplotlib.use('WebAgg') + def plotter_initializer(): from matplotlib import pyplot as plt plt.show() -# bring up plotting infrastructure -import matplotlib -matplotlib.rcParams['webagg.port'] = 6545 -matplotlib.rcParams['webagg.open_in_browser'] = cfg['micropsi2'].get('plotting_open_browser', False) -matplotlib.use('WebAgg') -plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) -plt_thread.start() - from micropsi_core import tools import json from datetime import datetime, timedelta @@ -1866,6 +1863,10 @@ def initialize(persistency_path=None, resource_path=None, world_path=None): configs = config.ConfigurationManager(cfg['paths']['server_settings_path']) + # bring up plotting infrastructure + plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) + plt_thread.start() + if logger is None: logger = MicropsiLogger({ 'system': cfg['logging']['level_system'], @@ -1877,6 +1878,10 @@ def initialize(persistency_path=None, resource_path=None, world_path=None): for e in errors: logging.getLogger("system").error(e) + # shut tornado up + for key in ["tornado.application", "tornado.access", "tornado", "tornado.general"]: + logging.getLogger(key).setLevel(logging.ERROR) + # initialize runners # Initialize the threads for the continuous calculation of nodenets and worlds if 'runner_timestep' not in configs: From 099571ca23ed025c6632a7c89460e0b0d9d503f3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 14:06:18 +0200 Subject: [PATCH 793/945] ignore files starting with `_` in code discovery --- micropsi_core/runtime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index b1a84bbc..3bb78624 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1578,6 +1578,8 @@ def load_user_files(path, resourcetype, errors=[]): abspath = os.path.join(path, f) if f == "__pycache__": shutil.rmtree(abspath) + elif f.startswith("_"): + continue err = None if os.path.isdir(abspath): errors.extend(load_user_files(abspath, resourcetype, errors=[])) From 9fa3b7c5ec4862445015665e4ad39c113e403a42 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 16:08:42 +0200 Subject: [PATCH 794/945] use reload_and_revert in webfrontend --- micropsi_server/static/js/dialogs.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index dd455815..c3187792 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -289,10 +289,8 @@ $(function() { $('.navbar a.reload_code').on('click', function(event){ event.preventDefault(); if($(event.target).hasClass("reload_revert")){ - api.call('reload_code', {}, function(){ - api.call('revert_nodenet', {nodenet_uid: currentNodenet}, function(){ - window.location.reload(); - }); + api.call('reload_and_revert', {nodenet_uid: currentNodenet}, function(){ + window.location.reload(); }); return } From 01ae263659148ce0423276721d746d69660db184 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 16:27:40 +0200 Subject: [PATCH 795/945] hand a nodenet instance to the Node baseclass --- micropsi_core/nodenet/dict_engine/dict_node.py | 2 +- micropsi_core/nodenet/node.py | 3 ++- micropsi_core/nodenet/theano_engine/theano_node.py | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index ad20831b..19d80fb3 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -67,7 +67,7 @@ def __init__(self, nodenet, parent_nodespace, position, state=None, activation=0 if nodenet.is_node(uid): raise KeyError("Node with uid %s already exists" % uid) - Node.__init__(self, type, nodenet.get_nodetype(type)) + Node.__init__(self, nodenet, type, nodenet.get_nodetype(type)) NetEntity.__init__(self, nodenet, parent_nodespace, name=name, entitytype="nodes", uid=uid, index=index) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 1aec9f55..51c86a3e 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -128,10 +128,11 @@ def nodetype(self): """ return self._nodetype - def __init__(self, nodetype_name, nodetype): + def __init__(self, nodenet, nodetype_name, nodetype): """ Constructor needs the string name of this node's type, and a Nodetype instance """ + self._nodenet = nodenet self._nodetype_name = nodetype_name self._nodetype = nodetype self.logger = nodetype.logger diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 9bfe49a9..7a9e660b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -20,7 +20,6 @@ def __init__(self, nodenet, partition, parent_uid, uid, numerictype, parameters= self._id = node_from_id(uid) self._uid = uid self._parent_id = nodespace_from_id(parent_uid) - self._nodenet = nodenet self._partition = partition self._state = {} @@ -30,7 +29,7 @@ def __init__(self, nodenet, partition, parent_uid, uid, numerictype, parameters= self.parameters = None strtype = get_string_node_type(numerictype, nodenet.native_modules) - Node.__init__(self, strtype, nodenet.get_nodetype(strtype)) + Node.__init__(self, nodenet, strtype, nodenet.get_nodetype(strtype)) self.is_highdimensional = type(self._nodetype) == HighdimensionalNodetype From c506c94708f05a25e7792e2031ffd0bd5fa3ce31 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 16:28:58 +0200 Subject: [PATCH 796/945] move show_plot to node(), close plots on a per-node-basis --- .../nodenet/dict_engine/dict_nodenet.py | 1 + micropsi_core/nodenet/netapi.py | 7 ----- micropsi_core/nodenet/node.py | 10 +++++++ micropsi_core/nodenet/nodenet.py | 29 ++++++++++++++----- .../nodenet/theano_engine/theano_nodenet.py | 3 +- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 01a92e14..dcb06e36 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -369,6 +369,7 @@ def get_activation_data(self, nodespace_uids=None, rounded=1): return activations def delete_node(self, node_uid): + self.close_figures(node_uid) if node_uid in self._nodespaces: affected_entity_ids = self._nodespaces[node_uid].get_known_ids() for uid in affected_entity_ids: diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 270e3d8a..1e951979 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -565,10 +565,3 @@ def set_nodespace_properties(self, nodespace_uid, properties): def announce_nodes(self, nodespace_uid, numer_of_nodes, average_element_per_node): pass - - def show_plot(self, figure=None): - from matplotlib import pyplot as plt - if figure is None: - figure = plt.gca().figure - plt.show() - self.__nodenet.register_figure(figure) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 51c86a3e..6f2b5ef0 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -344,6 +344,16 @@ def construct_gates_dict(self): data[gate_name] = self.get_gate(gate_name).activation return data + def show_plot(self, figure=None): + try: + from matplotlib import pyplot as plt + if figure is None: + figure = plt.gca().figure + plt.show() + self._nodenet.register_figure(self.uid, figure) + except ImportError: + self.logger.error("Matplotlib is needed for plotting") + def __repr__(self): return "<%s \"%s\" (%s)>" % (self.nodetype.name, self.name, self.uid) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index e258eed8..6a7ee65b 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -180,7 +180,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.deleted_items = {} self.stepping_rate = [] self.dashboard_values = {} - self.figures = set() + self.figures = {} self.native_modules = {} for type, data in native_modules.items(): @@ -616,6 +616,7 @@ def _track_deletion(self, entity_type, uid): def clear(self): self._monitors = {} + self.close_figures() def add_gate_monitor(self, node_uid, gate, name=None, color=None): """Adds a continuous monitor to the activation of a gate. The monitor will collect the activation @@ -744,14 +745,28 @@ def check_stop_runner_condition(self): del self.self._runner_condition['monitor'] return False - def register_figure(self, figure): - self.figures.add(figure) + def register_figure(self, node_uid, figure): + """ Registers a figure for the given node_uid""" + if node_uid in self.figures: + self.figures[node_uid].append(figure) + else: + self.figures[node_uid] = [figure] - def close_figures(self): + def close_figures(self, node_uid=None): + """ Close all figures used by the given node, or alle figures if no uid given""" try: import matplotlib.pyplot as plt - self.logger.debug("Closing %d open pyplot figures" % len(self.figures)) - for fig in self.figures: - plt.close(fig) + if node_uid is not None: + plots = self.figures.get(node_uid, []) + if len(plots): + self.logger.debug("Closing %d figures belonging to node %s" % (len(plots), node_uid)) + for fig in plots: + plt.close(fig) + self.figures[node_uid] = [] + else: + self.logger.debug("Closing open figures.") + for uid in self.figures: + [plt.close(fig) for fig in self.figures[uid]] + self.figures = {} except ImportError: pass diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index dacb6f4b..f3b38767 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1314,7 +1314,7 @@ def create_node(self, nodetype, nodespace_uid, position, name=None, uid=None, pa return uid def delete_node(self, uid): - + self.close_figures(uid) partition = self.get_partition(uid) node_id = node_from_id(uid) @@ -1770,6 +1770,7 @@ def reload_native_modules(self, native_modules): position = instance.position name = instance.name partition = self.get_partition(uid) + self.close_figures(uid) if uid in self.flow_module_instances: flowdata = instance.get_flow_data(complete=True) new_instance = FlowModule( From 2d2f78d6d6e78875e8522b0ba3191284040c0ef4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 16:47:53 +0200 Subject: [PATCH 797/945] have a test --- micropsi_core/nodenet/nodenet.py | 2 +- micropsi_core/tests/test_node.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 6a7ee65b..d1507e11 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -762,7 +762,7 @@ def close_figures(self, node_uid=None): self.logger.debug("Closing %d figures belonging to node %s" % (len(plots), node_uid)) for fig in plots: plt.close(fig) - self.figures[node_uid] = [] + del self.figures[node_uid] else: self.logger.debug("Closing open figures.") for uid in self.figures: diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index 6e232ffe..ea9f62e4 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -10,6 +10,14 @@ import pytest +plotting_available = False +try: + import matplotlib + plotting_available = True +except ImportError: + pass + + @pytest.mark.engine("theano_engine") def test_nodetype_function_definition_overwrites_default_function_name_theano(runtime, test_nodenet): nodenet = runtime.get_nodenet(test_nodenet) @@ -143,3 +151,20 @@ def phatNM(netapi, node, **_): assert result['dimensionality']['slots']['B_in0'] == 62 assert result['gatetypes'] == ['gen', 'sub', 'sur', 'A_out0', 'B_out0'] assert result['is_highdimensional'] + + +@pytest.mark.skipif(not plotting_available, reason="requires matplotlib") +def test_node_show_plot_and_close_plot(runtime, test_nodenet): + from matplotlib import pyplot as plt + net = runtime.nodenets[test_nodenet] + netapi = net.netapi + node = netapi.create_node("Neuron", None, "Neuron") + fig = plt.figure(figsize=(3, 2)) + node.show_plot(fig) + assert net.figures[node.uid] == [fig] + netapi.delete_node(node) + assert node.uid not in net.figures[node.uid] + node = netapi.create_node("Neuron", None, "Neuron") + node.show_plot(fig) + runtime.unload_nodenet(test_nodenet) + assert plt.get_fignums() == [] From ae5592990be4e0b367b0cc91fddd60a28c954ec0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 16:49:58 +0200 Subject: [PATCH 798/945] no matplotlib - no thread --- micropsi_core/runtime.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 5f880059..83512ebc 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -23,15 +23,20 @@ import sys import threading -import matplotlib -matplotlib.rcParams['webagg.port'] = 6545 -matplotlib.rcParams['webagg.open_in_browser'] = False -matplotlib.use('WebAgg') +try: + import matplotlib + matplotlib.rcParams['webagg.port'] = 6545 + matplotlib.rcParams['webagg.open_in_browser'] = False + matplotlib.use('WebAgg') + def plotter_initializer(): + from matplotlib import pyplot as plt + plt.show() + +except ImportError: + matplotlib = None + pass -def plotter_initializer(): - from matplotlib import pyplot as plt - plt.show() from micropsi_core import tools import json @@ -1864,8 +1869,9 @@ def initialize(persistency_path=None, resource_path=None, world_path=None): configs = config.ConfigurationManager(cfg['paths']['server_settings_path']) # bring up plotting infrastructure - plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) - plt_thread.start() + if matplotlib is not None: + plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) + plt_thread.start() if logger is None: logger = MicropsiLogger({ From c2a9ff0e4121f9661c961e5528632335ee9246e5 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 25 May 2017 17:26:41 +0200 Subject: [PATCH 799/945] replace deprecated getargspec() with signature() --- .../nodenet/dict_engine/dict_nodenet.py | 18 ++++++--- micropsi_core/runtime.py | 37 ++++++++----------- micropsi_server/micropsi_app.py | 3 +- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 01a92e14..a8758aeb 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -805,14 +805,22 @@ def get_available_gatefunctions(self): """ Returns a dict of the available gatefunctions and their parameters and parameter-defaults """ - from inspect import getmembers, getargspec, isfunction + import inspect from micropsi_core.nodenet import gatefunctions data = {} - for name, func in getmembers(gatefunctions, isfunction): - argspec = getargspec(func) + for name, func in inspect.getmembers(gatefunctions, inspect.isfunction): + sig = inspect.signature(func) data[name] = {} - for idx, arg in enumerate(argspec.args[1:]): - data[name][arg] = argspec.defaults[idx] + skip = True + for key in sig.parameters: + if skip: + # first param is input_activation. skip + skip = False + continue + default = sig.parameters[key].default + if default == inspect.Signature.empty: + default = None + data[name][key] = default return data def has_nodespace_changes(self, nodespace_uids=[], since_step=0): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 3bb78624..463e3256 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1423,20 +1423,18 @@ def parsemembers(members): if name.startswith('_'): continue if inspect.isroutine(thing): - argspec = inspect.getargspec(thing) - arguments = argspec.args[1:] - defaults = argspec.defaults or [] + sig = inspect.signature(thing) params = [] - diff = len(arguments) - len(defaults) - for i, arg in enumerate(arguments): - if i >= diff: + for key in sig.parameters: + if key == 'self': + continue + if sig.parameters[key].default != inspect.Signature.empty: params.append({ - 'name': arg, - 'default': defaults[i - diff] + 'name': key, + 'default': sig.parameters[key].default }) else: - params.append({'name': arg}) - + params.append({'name': key}) data[name] = params else: data[name] = None @@ -1723,21 +1721,16 @@ def parse_recipe_or_operations_file(path, mode, category_overwrite=False): # import from another file of the same mode. ignore, to avoid # false duplicate-function-name alerts continue - argspec = inspect.getargspec(func) - if mode == 'recipes': - arguments = argspec.args[1:] - elif mode == 'operations': - arguments = argspec.args[2:] - defaults = argspec.defaults or [] + signature = inspect.signature(func) params = [] - diff = len(arguments) - len(defaults) - for i, arg in enumerate(arguments): - if i >= diff: - default = defaults[i - diff] - else: + for param in signature.parameters: + if param == 'netapi' or (param == 'selection' and mode == 'operations'): + continue + default = signature.parameters[param].default + if default == inspect.Signature.empty: default = None params.append({ - 'name': arg, + 'name': param, 'default': default }) if mode == 'recipes' and name in custom_recipes and id(func) != id(custom_recipes[name]['function']): diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 936bb0c8..ebf29bdb 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -117,7 +117,8 @@ def _wrapper(argument=None): else: # kwargs.update({"argument": argument, "permissions": permissions, "user_id": user_id, "token": token}) if kwargs is not None: - arguments = dict((name, kwargs[name]) for name in inspect.getargspec(func).args if name in kwargs) + signature = inspect.signature(func) + arguments = dict((name, kwargs[name]) for name in signature.parameters if name in kwargs) arguments.update(kwargs) else: arguments = {} From d8e8f7c6042fb3bf175621e2b243e27f305b37e4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 29 May 2017 16:20:49 +0200 Subject: [PATCH 800/945] use older tornado version cf: https://stackoverflow.com/a/43414507/ --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b172667a..c1744a4b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,4 +24,4 @@ WebTest==2.0.21 Pillow==3.2.0 networkx==1.11 git+https://github.com/micropsi-industries/spock.git -tornado==4.5.1 +tornado==4.4 From c4373d40848094e203c33c9bb67de4320dd40357 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 1 Jun 2017 16:08:40 +0200 Subject: [PATCH 801/945] fix test --- micropsi_core/tests/test_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index ea9f62e4..fe282837 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -163,7 +163,7 @@ def test_node_show_plot_and_close_plot(runtime, test_nodenet): node.show_plot(fig) assert net.figures[node.uid] == [fig] netapi.delete_node(node) - assert node.uid not in net.figures[node.uid] + assert node.uid not in net.figures node = netapi.create_node("Neuron", None, "Neuron") node.show_plot(fig) runtime.unload_nodenet(test_nodenet) From 5d48b9f8a4f8f5d68d7043896acd004ca5690dcd Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 4 Jun 2017 15:59:09 +0200 Subject: [PATCH 802/945] remove vizapi --- micropsi_core/nodenet/netapi.py | 10 -- micropsi_core/nodenet/vizapi.py | 200 -------------------------------- 2 files changed, 210 deletions(-) delete mode 100644 micropsi_core/nodenet/vizapi.py diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 1e951979..e4c0eca3 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -1,9 +1,4 @@ -try: - from . import vizapi -except ImportError: - vizapi = None - class NetAPI(object): # Node Net API facade class for use from within the node net (in node functions) @@ -27,11 +22,6 @@ def logger(self): """ The nodenet logger """ return self.__nodenet.logger - @property - def vizapi(self): - """ An API for visualizations """ - return vizapi - def __init__(self, nodenet): self.__nodenet = nodenet diff --git a/micropsi_core/nodenet/vizapi.py b/micropsi_core/nodenet/vizapi.py deleted file mode 100644 index 14c606c8..00000000 --- a/micropsi_core/nodenet/vizapi.py +++ /dev/null @@ -1,200 +0,0 @@ -import os -import numpy as np - -import matplotlib -import platform - -# we need special backends to work around the default behaviour -# expecting the main-thread to do gui-stuff, since we're -# (a) a multithreaded webserver, and -# (b) plot from the runner-thread as well as the frontend -# find os: http://stackoverflow.com/q/1854 -# find supported backends: http://stackoverflow.com/a/13731150 -# if tested, include here: -# if platform.system() == "Darwin": -# matplotlib.use('macosx') -# else: -matplotlib.use('agg') - -import matplotlib.pyplot as plt -import matplotlib.gridspec as gridspec - -from io import BytesIO -import base64 - - -class NodenetPlot(object): - """ A NodenetPlot object represents an image, that can hold various plots - in a grid-layout. You can specify the size of the image, and the layout in - rows and cols. - Then, you can add plots to the image, which will be filled into the gridlayout line by line. - If the image is complete, you can either retrieve a base64-encoded string-representation of the - image, that can be delivered to the client, or save the generated image to a file - e.g.: - >>> image = NodenetPlot(cols=2) - >>> image.add_activation_plot(netapi.get_activations(ns1, group1)) - >>> image.add_linkweights_plot(netapi.get_link_weights(ns1, group1, ns2, group2)) - >>> image.save_to_file('/tmp/plot.png') - - If you provide a name for the plots, you can later update them: - >>> image = NodenetPlot(cols=2) - >>> image.add_activation_plot(np.random.rand(10), name="group_activation_plot") - >>> new_data = np.random.rand(10) - >>> image.update_plot('group_activation_plot', new_data) - - """ - - def __init__(self, plotsize=(6.0, 6.0), rows=1, cols=1, wspace=0.1, hspace=0.1): - """ Creates a new empty figure. - The figure can contain a variable number of plots, that are specified via - the rows and cols parameters. - Parameters: - plotsize - A tuple indicating the (x, y) size of the Image, defaults to (6, 6) - rows - the number of rows of plots, defaults to 1 - cols - the number of cols of plots, defaults to 1 - wspace - vertical spacing between plots, defaults to 0.1 - hspace - horizontal spacing between plots, defaults to 0.1 - """ - plt.close() # attempt to close old instance - self.figure = plt.figure(figsize=plotsize) - self.plots = {} - self.plotindex = 0 - self.rows = rows - self.cols = cols - self.grid = gridspec.GridSpec(rows, cols, wspace=wspace, hspace=hspace) - - def update_plot(self, name, new_data): - """ update a named plot in this Image with the new data - returns True on success, False otherwise. - Parameters: - name - the name you gave when adding the plot - new_data - the new data for the plot - """ - if name in self.plots: - plot, shape = self.plots[name] - if new_data.shape != shape: - new_data = new_data.reshape(shape) - if type(plot) == list: - # 4d plot - row, col, inner_row, inner_col = shape - for r in range(row): - row_data = new_data[r, :] - for c in range(col): - plot[r+c].set_data(row_data[c, :]) - else: - # 2d plot - plot.set_data(new_data) - return True - return False - - def add_activation_plot(self, activations, name=None, rows=-1, cols=-1, vmin=None, vmax=None): - """ Adds a plot of node-activations to the figure. - Per default, the plot will attempt to render the activations into a square image - If you have non-quadratic data, you have to give numbers for rows and cols so that the - numbers can be reshaped accordingly - Parameters: - activations - array of activations - name - optional identification for later updates - rows - number of rows, defaults to sqrt() - cols - number of cols, defaults to sqrt() - vmin - minimal value, defaults to 0 - vmax - maximal value, defaults to 1 - """ - data = np.array(activations) - if rows > 0 or cols > 0: - matrix = data.reshape((rows, cols)) - else: - sz = int(np.ceil(np.sqrt(data.shape[0]))) - matrix = data.reshape((sz, sz)) - - self.add_2d_matrix_plot(matrix, name=name, vmin=vmin, vmax=vmax) - - def add_linkweights_plot(self, linkweights, name=None, wspace=0.1, hspace=0.1, rows_outer=0, cols_outer=0, rows_inner=0, cols_inner=0): - """ Adds a plot of linkweights to the figure. - Parameters: - linkweights - output of netapi.get_link_weights - name - optional identification for later updates - wspace - vertical spacing, defaults to 0.1 - hspace - horizontal spacing, defaults to 0.1 - rows_outer - number of rows of linkweight-plots, defaults to sqrt() - cols_outer - number of cols of linkweight-plots, defaults to sqrt() - rows_inner - number of pixel-rows per linkweight-plot, defaults to sqrt() - cols_inner - number of pixel-cols per linkweight-plot, defaults to sqrt() - """ - data = np.array(linkweights) - r, c = data.shape - outer_sqrt = int(np.ceil(np.sqrt(r))) - inner_sqrt = int(np.ceil(np.sqrt(c))) - matrix = data.reshape(( - rows_outer or outer_sqrt, - cols_outer or outer_sqrt, - rows_inner or inner_sqrt, - cols_inner or inner_sqrt - )) - result = self.add_4d_matrix_plot(matrix, name=name, wspace=wspace, hspace=hspace) - - def add_2d_matrix_plot(self, matrix, name=None, vmin=None, vmax=None): - """ General plotter function to add a two-dimensional plot. The shape - of the passed matrix determins the layout in rows and cols of the - plot - Parameters: - data - 2-dimensional numpy matrix - vmin - minimal value - vmax - maximal value - """ - ax = plt.Subplot(self.figure, self.grid[self.plotindex]) - ax.set_xticks([]) - ax.set_yticks([]) - thing = ax.imshow(matrix, cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax) - self.figure.add_subplot(ax) - self.plotindex += 1 - if name is not None: - self.plots[name] = thing, matrix.shape - - def add_4d_matrix_plot(self, data, name=None, wspace=0, hspace=0, vmin=None, vmax=None): - """ General plotter function to add a grid of several two-dimensional plots - The shape of the passed matrix determins the layout in rows and cols of the - plot - Parameters: - data - 4-dimensional numpy matrix - wspace - vertical spacing - hspace - horizontal spacing - vmin - minimal value - vmax - maximal value - """ - # compute rows & cols - row, col, inner_row, inner_col = data.shape - grid = gridspec.GridSpecFromSubplotSpec(row, col, subplot_spec=self.grid[self.plotindex], wspace=wspace, hspace=hspace) - plots = [] - for r in range(row): - row_data = data[r, :] - for c in range(col): - ax = plt.Subplot(self.figure, grid[(r * col + c)]) - ax.set_xticks([]) - ax.set_yticks([]) - plots.append(ax.imshow(row_data[c, :], cmap=matplotlib.cm.gray, vmin=vmin, vmax=vmax)) - self.figure.add_subplot(ax) - self.plotindex += 1 - if name is not None: - self.plots[name] = plots, data.shape - - def save_to_file(self, filename, format="png", **params): - """ saves the generated figure to the given file - Parameters: - filename - the target filename. expects absolute paths, or saves to toolkit-root - format - the file-format. defaults to png - takes additional keyword-arguments for savefig, see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.savefig - """ - filepath = os.path.abspath(filename) - self.figure.savefig(filepath, format=format, **params) - return filepath - - def to_base64(self, format="png", **params): - """ returns the base64 encoded bytestring of the generated figure - Parameters: - format - the file-format. defaults to png - takes additional keyword-arguments for savefig, see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.savefig - """ - bio = BytesIO() - self.figure.savefig(bio, format=format, **params) - return ''.join(base64.encodebytes(bio.getvalue()).decode("utf-8").splitlines()) From 8ff303a3a60c9093d106210ba5dea1921253fb85 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 4 Jun 2017 16:02:22 +0200 Subject: [PATCH 803/945] remove vizapi tests --- micropsi_core/tests/test_vizapi.py | 114 ----------------------------- 1 file changed, 114 deletions(-) delete mode 100644 micropsi_core/tests/test_vizapi.py diff --git a/micropsi_core/tests/test_vizapi.py b/micropsi_core/tests/test_vizapi.py deleted file mode 100644 index c94fdb03..00000000 --- a/micropsi_core/tests/test_vizapi.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Tests for vizapi -""" - -import pytest - -# skip these tests if numpy is not installed -pytest.importorskip("numpy") - - -def test_plot_activations(runtime, test_nodenet): - from random import random - nodenet = runtime.get_nodenet(test_nodenet) - vizapi = nodenet.netapi.vizapi - activations = [random() for i in range(256)] - plot = vizapi.NodenetPlot(plotsize=(2, 2)) - plot.add_activation_plot(activations) - res = plot.to_base64(format="png") - assert len(res) > 1000 - - -def test_plot_linkweights(runtime, test_nodenet): - from random import random - nodenet = runtime.get_nodenet(test_nodenet) - vizapi = nodenet.netapi.vizapi - linkweights = [] - for i in range(16): - linkweights.append([random() for i in range(16)]) - plot = vizapi.NodenetPlot(plotsize=(2, 2)) - plot.add_linkweights_plot(linkweights) - res = plot.to_base64(format="png") - assert len(res) > 1000 - - -def test_save_file(runtime, test_nodenet, resourcepath): - from random import random - import os - nodenet = runtime.get_nodenet(test_nodenet) - vizapi = nodenet.netapi.vizapi - activations = [random() for i in range(256)] - plot = vizapi.NodenetPlot(plotsize=(2, 2)) - plot.add_activation_plot(activations) - filepath = os.path.join(resourcepath, "plot.png") - returnpath = plot.save_to_file(filepath) - assert os.path.abspath(returnpath) == os.path.abspath(filepath) - assert os.path.isfile(filepath) - - -def test_plot_from_nodefunc(runtime, test_nodenet, resourcepath): - import os - from random import random - from time import sleep - nodenet = runtime.get_nodenet(test_nodenet) - vizapi = nodenet.netapi.vizapi - activations = [random() for i in range(256)] - plot = vizapi.NodenetPlot(plotsize=(2, 2)) - plot.add_activation_plot(activations) - filepath = os.path.join(resourcepath, "plot.png") - returnpath = plot.save_to_file(filepath) - assert os.path.abspath(returnpath) == os.path.abspath(filepath) - assert os.path.isfile(filepath) - os.remove(filepath) - os.mkdir(os.path.join(resourcepath, 'nodetypes', 'plotter')) - nodetype_file = os.path.join(resourcepath, "nodetypes", "plotter", "plotter.py") - with open(nodetype_file, 'w') as fp: - fp.write("""nodetype_definition = { - "name": "Plotter", - "slottypes": [], - "nodefunction_name": "plotfunc", - "gatetypes": [], - "parameters": ["plotpath"]} - -def plotfunc(netapi, node=None, **params): - import os - from random import random - filepath = os.path.join(params['plotpath'], 'plot.png') - activations = [random() for i in range(256)] - plot = netapi.vizapi.NodenetPlot(plotsize=(2, 2)) - plot.add_activation_plot(activations) - plot.save_to_file(filepath) -""") - runtime.reload_code() - node = nodenet.netapi.create_node("Plotter", None, name="Plotter") - node.set_parameter("plotpath", resourcepath) - runtime.start_nodenetrunner(test_nodenet) - sleep(2) - runtime.stop_nodenetrunner(test_nodenet) - assert runtime.MicropsiRunner.last_nodenet_exception == {} - assert os.path.isfile(os.path.join(resourcepath, "plot.png")) - - -def test_update_plot(runtime, test_nodenet): - import numpy as np - nodenet = runtime.get_nodenet(test_nodenet) - vizapi = nodenet.netapi.vizapi - image = vizapi.NodenetPlot((4, 4)) - activations_1 = np.random.rand(16) - image.add_activation_plot(activations_1, name='my_activations_plot') - result_1 = image.to_base64() - activations_2 = np.random.rand(16) - image.update_plot('my_activations_plot', activations_2) - result_2 = image.to_base64() - assert result_1 != result_2 - - image = vizapi.NodenetPlot((4, 4)) - linkweights_1 = np.random.rand(4, 4) - image.add_linkweights_plot(linkweights_1, name='my_linkweights_plot') - result_1 = image.to_base64() - linkweights_2 = np.random.rand(4, 4) - image.update_plot('my_linkweights_plot', linkweights_2) - result_2 = image.to_base64() - assert result_1 != result_2 From ab33cb42595a263f34377f273b22932881b2a8c8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 5 Jun 2017 18:00:40 +0200 Subject: [PATCH 804/945] all figures are caught by webagg, offer convenience for closing --- micropsi_core/nodenet/node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 6f2b5ef0..6bb180d0 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -354,6 +354,9 @@ def show_plot(self, figure=None): except ImportError: self.logger.error("Matplotlib is needed for plotting") + def close_figures(self): + self._nodenet.close_figures(node_uid=self.uid) + def __repr__(self): return "<%s \"%s\" (%s)>" % (self.nodetype.name, self.name, self.uid) From af46b7640f9c6fa2748a671736b23da29b60ce34 Mon Sep 17 00:00:00 2001 From: priska Date: Tue, 6 Jun 2017 13:46:42 +0200 Subject: [PATCH 805/945] Fixed flowmodules test. --- micropsi_core/tests/test_flowmodules.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index 247bdf53..e505a059 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -473,7 +473,7 @@ def test_flowmodule_persistency(runtime, test_nodenet, default_world, resourcepa result = worldadapter.get_flow_datatarget('bar') - assert np.all(result == sources * 2 * thetas.get_theta("weights").get_value() + thetas.get_theta("bias").get_value()) + assert np.allclose(result, sources * 2 * thetas.get_theta("weights").get_value() + thetas.get_theta("bias").get_value()) runtime.save_nodenet(test_nodenet) runtime.revert_nodenet(test_nodenet) @@ -484,9 +484,9 @@ def test_flowmodule_persistency(runtime, test_nodenet, default_world, resourcepa worldadapter.set_flow_datasource('foo', sources) thetas = netapi.get_node(thetas.uid) - assert np.all(thetas.get_theta("weights").get_value() == custom_theta) + assert np.allclose(thetas.get_theta("weights").get_value(), custom_theta) nodenet.step() - assert np.all(worldadapter.get_flow_datatarget('bar') == result) + assert np.allclose(worldadapter.get_flow_datatarget('bar'), result) assert netapi.get_node(double.uid).initfunction_ran # also assert, that the edge-keys are preserved: # this would raise an exception otherwise @@ -494,7 +494,7 @@ def test_flowmodule_persistency(runtime, test_nodenet, default_world, resourcepa # assert that custom thetas survive reloadCode: runtime.reload_code() - assert np.all(netapi.get_node(thetas.uid).get_theta('weights').get_value() == custom_theta) + assert np.allclose(netapi.get_node(thetas.uid).get_theta('weights').get_value(), custom_theta) @pytest.mark.engine("theano_engine") From 25c5dd357f0a46249783f5f6933c0bb22ef36d9b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 6 Jun 2017 15:09:25 +0200 Subject: [PATCH 806/945] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c77eeef0..ae96c0fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.10-alpha8 (unreleased) ========== + * use matplotlib's webagg for plotting 0.9-alpha7 (2017-02-13) From 2f211cc764f861d2d8ba875a8467be45cac8e9af Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 6 Jun 2017 15:09:33 +0200 Subject: [PATCH 807/945] release 0.10-alpha8 --- CHANGELOG.md | 2 +- configuration.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae96c0fe..fd138377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.10-alpha8 (unreleased) +0.10-alpha8 (2017-06-06) ========== * use matplotlib's webagg for plotting diff --git a/configuration.py b/configuration.py index 9f8a08b4..a659449a 100644 --- a/configuration.py +++ b/configuration.py @@ -51,7 +51,7 @@ def makedirs(path): warnings.warn('Can not read config from inifile %s' % configini) raise RuntimeError('Can not read config from inifile %s' % configini) -config['micropsi2']['version'] = "0.10-alpha8-dev" +config['micropsi2']['version'] = "0.10-alpha8" config['micropsi2']['apptitle'] = "MicroPsi" data_path = os.path.expanduser(config['micropsi2']['data_directory']) From 6954e11666799acbe54d9a6edab74c7305202fcd Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 6 Jun 2017 15:14:07 +0200 Subject: [PATCH 808/945] remove demo_data --- .../b6f40e6417ee11e4bbe920c9d087b4b7.json | 254 ------------------ .../ac7c4fb40adc11e58caa20c9d087b4b7.json | 6 - .../d4b3f5740adc11e5b9fe20c9d087b4b7.json | 137 ---------- 3 files changed, 397 deletions(-) delete mode 100644 demo_data/nodenets/b6f40e6417ee11e4bbe920c9d087b4b7.json delete mode 100644 demo_data/worlds/ac7c4fb40adc11e58caa20c9d087b4b7.json delete mode 100644 demo_data/worlds/d4b3f5740adc11e5b9fe20c9d087b4b7.json diff --git a/demo_data/nodenets/b6f40e6417ee11e4bbe920c9d087b4b7.json b/demo_data/nodenets/b6f40e6417ee11e4bbe920c9d087b4b7.json deleted file mode 100644 index a9bc046f..00000000 --- a/demo_data/nodenets/b6f40e6417ee11e4bbe920c9d087b4b7.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "current_step": 0, - "engine": "dict_engine", - "is_active": false, - "links": { - "9ce2b617-37c2-4ed5-95b1-ed2fd17601c0:gen:gen:3f1d7b8a-789b-40e9-a5f5-665c4ed7db16": { - "certainty": 1, - "source_gate_name": "gen", - "source_node_uid": "9ce2b617-37c2-4ed5-95b1-ed2fd17601c0", - "target_node_uid": "3f1d7b8a-789b-40e9-a5f5-665c4ed7db16", - "target_slot_name": "gen", - "uid": "9ce2b617-37c2-4ed5-95b1-ed2fd17601c0:gen:gen:3f1d7b8a-789b-40e9-a5f5-665c4ed7db16", - "weight": 1 - }, - "f9f75add-93da-4a59-a620-5988f34fba3c:gen:gen:029ba031-24af-4d77-bd6a-6d0210d50d0d": { - "certainty": 1, - "source_gate_name": "gen", - "source_node_uid": "f9f75add-93da-4a59-a620-5988f34fba3c", - "target_node_uid": "029ba031-24af-4d77-bd6a-6d0210d50d0d", - "target_slot_name": "gen", - "uid": "f9f75add-93da-4a59-a620-5988f34fba3c:gen:gen:029ba031-24af-4d77-bd6a-6d0210d50d0d", - "weight": 1 - } - }, - "max_coords": { - "x": 0, - "y": 0 - }, - "modulators": {}, - "monitors": { - "b2516fb6a2a211e4832820c9d087b4b7": { - "classname": "NodeMonitor", - "name": "right eye", - "node_uid": "f9f75add-93da-4a59-a620-5988f34fba3c", - "sheaf": "default", - "target": "gen", - "type": "gate", - "uid": "b2516fb6a2a211e4832820c9d087b4b7", - "values": {} - } - }, - "name": "Braitenberg", - "nodes": { - "029ba031-24af-4d77-bd6a-6d0210d50d0d": { - "activation": 0.0, - "gate_activations": { - "gen": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - } - }, - "gate_functions": { - "gen": "identity" - }, - "gate_parameters": { - "gen": { - "amplification": 1.0, - "certainty": 1.0, - "decay": 0.0, - "maximum": 1.0, - "minimum": -1.0, - "rho": 0.0, - "spreadsheaves": 0.0, - "theta": 0.0, - "threshold": 0.0 - } - }, - "index": 4, - "name": "left wheel", - "parameters": { - "datatarget": "engine_l" - }, - "parent_nodespace": "Root", - "position": [ - 250, - 340 - ], - "sheaves": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - }, - "state": {}, - "type": "Actor", - "uid": "029ba031-24af-4d77-bd6a-6d0210d50d0d" - }, - "3f1d7b8a-789b-40e9-a5f5-665c4ed7db16": { - "activation": 0.0, - "gate_activations": { - "gen": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - } - }, - "gate_functions": { - "gen": "identity" - }, - "gate_parameters": { - "gen": { - "amplification": 1.0, - "certainty": 1.0, - "decay": 0.0, - "maximum": 1.0, - "minimum": -1.0, - "rho": 0.0, - "spreadsheaves": 0.0, - "theta": 0.0, - "threshold": 0.0 - } - }, - "index": 5, - "name": "right wheel", - "parameters": { - "datatarget": "engine_r" - }, - "parent_nodespace": "Root", - "position": [ - 512.5, - 335 - ], - "sheaves": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - }, - "state": {}, - "type": "Actor", - "uid": "3f1d7b8a-789b-40e9-a5f5-665c4ed7db16" - }, - "9ce2b617-37c2-4ed5-95b1-ed2fd17601c0": { - "activation": 0.0, - "gate_activations": { - "gen": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - } - }, - "gate_functions": { - "gen": "identity" - }, - "gate_parameters": { - "gen": { - "amplification": 1.0, - "certainty": 1.0, - "decay": 0.0, - "maximum": 1.0, - "minimum": -1.0, - "rho": 0.0, - "spreadsheaves": 0.0, - "theta": 0.0, - "threshold": 0.0 - } - }, - "index": 2, - "name": "left eye", - "parameters": { - "datasource": "brightness_l" - }, - "parent_nodespace": "Root", - "position": [ - 257.5, - 118.75 - ], - "sheaves": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - }, - "state": {}, - "type": "Sensor", - "uid": "9ce2b617-37c2-4ed5-95b1-ed2fd17601c0" - }, - "f9f75add-93da-4a59-a620-5988f34fba3c": { - "activation": 0.0, - "gate_activations": { - "gen": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - } - }, - "gate_functions": { - "gen": "identity" - }, - "gate_parameters": { - "gen": { - "amplification": 1.0, - "certainty": 1.0, - "decay": 0.0, - "maximum": 1.0, - "minimum": -1.0, - "rho": 0.0, - "spreadsheaves": 0.0, - "theta": 0.0, - "threshold": 0.0 - } - }, - "index": 3, - "name": "right eye", - "parameters": { - "datasource": "brightness_r" - }, - "parent_nodespace": "Root", - "position": [ - 510, - 112.5 - ], - "sheaves": { - "default": { - "activation": 0.0, - "name": "default", - "uid": "default" - } - }, - "state": {}, - "type": "Sensor", - "uid": "f9f75add-93da-4a59-a620-5988f34fba3c" - } - }, - "nodespaces": { - "Root": { - "index": 0, - "name": "Root", - "parent_nodespace": null, - "position": [ - 0, - 0 - ], - "uid": "Root" - } - }, - "owner": "admin", - "uid": "b6f40e6417ee11e4bbe920c9d087b4b7", - "version": 1, - "world": "d4b3f5740adc11e5b9fe20c9d087b4b7", - "worldadapter": "Braitenberg" -} \ No newline at end of file diff --git a/demo_data/worlds/ac7c4fb40adc11e58caa20c9d087b4b7.json b/demo_data/worlds/ac7c4fb40adc11e58caa20c9d087b4b7.json deleted file mode 100644 index 941e7100..00000000 --- a/demo_data/worlds/ac7c4fb40adc11e58caa20c9d087b4b7.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "filename": "/Users/dwelland/workspace/tmp/micropsi_alpha2/./resources/worlds/ac7c4fb40adc11e58caa20c9d087b4b7.json", - "name": "default", - "uid": "ac7c4fb40adc11e58caa20c9d087b4b7", - "version": 1 -} \ No newline at end of file diff --git a/demo_data/worlds/d4b3f5740adc11e5b9fe20c9d087b4b7.json b/demo_data/worlds/d4b3f5740adc11e5b9fe20c9d087b4b7.json deleted file mode 100644 index f4391cc3..00000000 --- a/demo_data/worlds/d4b3f5740adc11e5b9fe20c9d087b4b7.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "agents": { - "b6f40e6417ee11e4bbe920c9d087b4b7": { - "name": "Braitenberg", - "position": [ - 700, - 400 - ], - "type": "Braitenberg", - "uid": "b6f40e6417ee11e4bbe920c9d087b4b7" - } - }, - "assets": { - "background": "island/psi_1.png", - "icons": { - "Boulder": "island/boulder.png", - "Braintree": "island/braintree.png", - "Braitenberg": "island/braitenberg.png", - "Champignon": "island/boletus-edulis.png", - "FlyAgaric": "island/fly-agaris.png", - "Juniper": "island/juniper-berries.png", - "Lightsource": "island/lamp.png", - "Maple": "island/maple.png", - "Menhir": "island/menhir.png", - "PalmTree": "island/palm-tree.png", - "Stone": "island/rock.png", - "Survivor": "island/Micropsi.png", - "Thornbush": "island/unknownbox.png", - "Waterhole": "island/well.png", - "Wirselkraut": "island/wirselkraut.png" - }, - "js": "island/island.js", - "template": "island/island.tpl", - "x": 2048, - "y": 2048 - }, - "available_worldadapters": [ - "Braitenberg", - "Survivor", - "StructuredObjects" - ], - "available_worldobjects": [ - "Wirselkraut", - "Lightsource", - "Default", - "Maple", - "Stone", - "PalmTree", - "Thornbush", - "Juniper", - "Boulder", - "Waterhole", - "Champignon", - "Braintree", - "Menhir", - "FlyAgaric" - ], - "current_step": 0, - "filename": "/Users/dwelland/workspace/tmp/micropsi_alpha2/./resources/worlds/d4b3f5740adc11e5b9fe20c9d087b4b7.json", - "name": "Doerner-Island", - "objects": { - "4be1f5260ade11e5a03620c9d087b4b7": { - "name": "", - "orientation": 0.0, - "parameters": null, - "position": [ - 505, - 547 - ], - "type": "Lightsource", - "uid": "4be1f5260ade11e5a03620c9d087b4b7" - } - }, - "owner": "admin", - "uid": "d4b3f5740adc11e5b9fe20c9d087b4b7", - "version": 1, - "world_type": "Island", - "worldadapters": { - "Braitenberg": { - "datasources": [ - "brightness_l", - "brightness_r" - ], - "datatargets": [ - "engine_l", - "engine_r" - ] - }, - "StructuredObjects": { - "datasources": [ - "fov-x", - "fov-y", - "major-newscene", - "fovea-com", - "presence-com", - "fovea-cir", - "presence-cir", - "fovea-ver", - "presence-ver", - "fovea-green", - "presence-green", - "fovea-white", - "presence-white", - "fovea-brown", - "presence-brown", - "fovea-charcoal", - "presence-charcoal", - "fovea-purple", - "presence-purple", - "fovea-navy", - "presence-navy", - "fovea-red", - "presence-red" - ], - "datatargets": [ - "fov_x", - "fov_y", - "fov_reset" - ] - }, - "Survivor": { - "datasources": [ - "body-energy", - "body-water", - "body-integrity" - ], - "datatargets": [ - "action_eat", - "action_drink", - "loco_north", - "loco_south", - "loco_east", - "loco_west" - ] - } - } -} \ No newline at end of file From bc70e21649139772a976e3b67dcc6f724dcfc0ef Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 6 Jun 2017 15:33:06 +0200 Subject: [PATCH 809/945] version bump for alpha9 --- CHANGELOG.md | 4 ++++ configuration.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd138377..af87ba08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.11-alpha9 (unreleased) +========== + + 0.10-alpha8 (2017-06-06) ========== * use matplotlib's webagg for plotting diff --git a/configuration.py b/configuration.py index a659449a..eaed5add 100644 --- a/configuration.py +++ b/configuration.py @@ -51,7 +51,7 @@ def makedirs(path): warnings.warn('Can not read config from inifile %s' % configini) raise RuntimeError('Can not read config from inifile %s' % configini) -config['micropsi2']['version'] = "0.10-alpha8" +config['micropsi2']['version'] = "0.11-alpha9-dev" config['micropsi2']['apptitle'] = "MicroPsi" data_path = os.path.expanduser(config['micropsi2']['data_directory']) From 67a11303b402cfe5c195a62240e6c82abbdf9496 Mon Sep 17 00:00:00 2001 From: priska Date: Wed, 7 Jun 2017 12:17:17 +0200 Subject: [PATCH 810/945] Corrects a typo. --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index b351f307..a473cab3 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1072,7 +1072,7 @@ def compile_flow_subgraph(self, node_uids, requested_outputs=None, use_different else: original_outex = node.build(*buildargs) except Exception as err: - self.logger.error("Error in buildfunction of Flowodule %s.\n %s: %s" % (str(node), err.__class__.__name__, str(err))) + self.logger.error("Error in buildfunction of Flowmodule %s.\n %s: %s" % (str(node), err.__class__.__name__, str(err))) post_mortem() skip = True break From e08b6325d964aa36d7df0a1f14c0349e9cb3d5ed Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 8 Jun 2017 18:09:11 +0200 Subject: [PATCH 811/945] fix reload_code for worlds with unloaded agents --- micropsi_core/runtime.py | 5 +++-- micropsi_core/tests/test_runtime_world_basics.py | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index bd05e2bf..a8c7e08c 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1832,8 +1832,9 @@ def reload_code(): worlds[world_uid] = world_classes[wtype](**world_data[world_uid]) worlds[world_uid].initialize_world(data) for uid in agents: - worlds[world_uid].register_nodenet(agents[uid]['type'], uid, agents[uid]['name'], nodenets[uid].metadata['worldadapter_config']) - nodenets[uid].worldadapter_instance = worlds[world_uid].agents[uid] + if uid in nodenets: + worlds[world_uid].register_nodenet(agents[uid]['type'], uid, agents[uid]['name'], nodenets[uid].metadata['worldadapter_config']) + nodenets[uid].worldadapter_instance = worlds[world_uid].agents[uid] else: worlds[world_uid].logger.warning("World definition for world %s gone, destroying." % str(worlds[world_uid])) diff --git a/micropsi_core/tests/test_runtime_world_basics.py b/micropsi_core/tests/test_runtime_world_basics.py index ddf44d48..2e71115a 100644 --- a/micropsi_core/tests/test_runtime_world_basics.py +++ b/micropsi_core/tests/test_runtime_world_basics.py @@ -329,6 +329,10 @@ def update_data_sources_and_targets(self): assert wa.__class__.__name__ == 'MyCustomWA' assert wa.get_available_datasources() == [] + res, nn2_uid = runtime.new_nodenet("deleteme", worldadapter="MyCustomWA", world_uid=world_uid) + runtime.save_world(world_uid) + runtime.unload_nodenet(nn2_uid) + runtime.revert_world(world_uid) with open(os.path.join(resourcepath, 'custom_world.py'), 'w') as fp: fp.write(""" @@ -363,11 +367,14 @@ def update_data_sources_and_targets(self): """) - runtime.reload_code() + res, errors = runtime.reload_code() + assert res + assert errors == [] assert 'MyCustomWA' in runtime.get_worldadapters(world_uid) assert 'SecondWA' in runtime.get_worldadapters(world_uid) + assert nn2_uid not in runtime.worlds[world_uid].agents assert default_nodenet in runtime.worlds[world_uid].data['agents'] wa = runtime.nodenets[default_nodenet].worldadapter_instance assert wa.get_available_datasources() == ['foo'] From 85f0e40a4af95cbe386773881f4ad521a3cacd1d Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 14 Jun 2017 10:11:40 +0200 Subject: [PATCH 812/945] Code for separating numpy elements out of the state dict --- .../nodenet/theano_engine/theano_node.py | 53 +++++++++++++++++++ micropsi_core/tests/test_node.py | 29 ++++++++++ 2 files changed, 82 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 7a9e660b..03824391 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -413,6 +413,59 @@ def clone_state(self): else: return None + def _pluck_apart_state(self, state, numpy_elements): + if isinstance(state, dict): + result = dict() + for key, value in state.items(): + result[key] = self._pluck_apart_state(value, numpy_elements) + elif isinstance(state, list): + result = [] + for value in state: + result.append(self._pluck_apart_state(value, numpy_elements)) + elif isinstance(state, np.ndarray): + result = "__numpyelement__" + str(id(state)) + numpy_elements[result] = state + else: + return state + + return result + + def _put_together_state(self, state, numpy_elements): + if isinstance(state, dict): + result = dict() + for key, value in state.items(): + result[key] = self._put_together_state(value, numpy_elements) + elif isinstance(state, list): + result = [] + for value in state: + result.append(self._put_together_state(value, numpy_elements)) + elif isinstance(state, str) and state.startswith("__numpyelement__"): + result = numpy_elements[state] + else: + return state + + return result + + def get_persistable_state(self): + """ + Returns a tuple of dicts, the first one containing json-serializable state information + and the second one containing numpy elements that should be persisted into an npz. + The json-seriazable dict will contain special values that act as keys for the second dict. + This allows to save nested numpy state. + set_persistable_state knows how to unserialize from the returned tuple. + """ + numpy_elements = dict() + json_state = self._pluck_apart_state(self._state, numpy_elements) + + return json_state, numpy_elements + + def set_persistable_state(self, json_state, numpy_elements): + """ + Sets this node's state from a tuple created with get_persistable_state, + essentially nesting numpy objects back into the state dict where it belongs + """ + self._state = self._put_together_state(json_state, numpy_elements) + def node_function(self): try: self.nodetype.nodefunction(netapi=self._nodenet.netapi, node=self, **self.clone_parameters()) diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index fe282837..ccc8929e 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -168,3 +168,32 @@ def test_node_show_plot_and_close_plot(runtime, test_nodenet): node.show_plot(fig) runtime.unload_nodenet(test_nodenet) assert plt.get_fignums() == [] + +@pytest.mark.engine("theano_engine") +def test_numpy_state_persistency(runtime, test_nodenet): + import numpy as np + net = runtime.nodenets[test_nodenet] + netapi = net.netapi + node = netapi.create_node("Neuron", None, "Neuron") + node._state["string"] = "hugo" + node._state["dict"] = {"eins": 1, "zwei": 2} + node._state["list"] = [{"eins": 1, "zwei": 2}, "boing"] + node._state["numpy"] = np.asarray([1,2,3,4]) + + json_state, numpy_state = node.get_persistable_state() + + assert json_state["string"] == "hugo" + assert json_state["dict"]["eins"] == 1 + assert json_state["list"][0]["eins"] == 1 + assert json_state["list"][1] == "boing" + assert json_state["numpy"].startswith("__numpyelement__") + assert numpy_state[json_state["numpy"]].sum() == 10 + + node.set_persistable_state(json_state, numpy_state) + + assert node._state["string"] == "hugo" + assert node._state["dict"]["eins"] == 1 + assert node._state["list"][0]["eins"] == 1 + assert node._state["list"][1] == "boing" + assert node._state["numpy"].sum() == 10 + From 2718b41ebea0acee2470122cf4de59d78eb6d9cc Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Wed, 14 Jun 2017 17:40:25 +0200 Subject: [PATCH 813/945] Full load/save logic and a test --- .../nodenet/theano_engine/theano_nodenet.py | 34 +++++++++- .../nodenet/theano_engine/theano_partition.py | 6 +- micropsi_core/tests/test_node.py | 38 ----------- .../tests/test_runtime_nodenet_basics.py | 66 +++++++++++++++++++ 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index a473cab3..cb3bd50a 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -289,14 +289,14 @@ def get_nodes(self, nodespace_uids=[], include_links=True, links_to_nodespaces=[ linked_nodespaces_by_partition[spid].append(nodespace_from_id(uid)) for spid in nodespaces_by_partition: - nodes, links = self.partitions[spid].get_node_data(nodespaces_by_partition=nodespaces_by_partition, include_links=include_links, linked_nodespaces_by_partition=linked_nodespaces_by_partition) + nodes, links, _ = self.partitions[spid].get_node_data(nodespaces_by_partition=nodespaces_by_partition, include_links=include_links, linked_nodespaces_by_partition=linked_nodespaces_by_partition) data['nodes'].update(nodes) data['links'] = links else: data['nodespaces'] = self.construct_nodespaces_dict(None, transitive=True) for partition in self.partitions.values(): - nodes, _ = partition.get_node_data(nodespaces_by_partition=None, include_links=include_links) + nodes, _ , _ = partition.get_node_data(nodespaces_by_partition=None, include_links=include_links) data['nodes'].update(nodes) return data @@ -506,6 +506,13 @@ def save(self): metadata['worldadapter_flow_nodes'] = self.worldadapter_flow_nodes fp.write(json.dumps(metadata, sort_keys=True, indent=4)) + # write numpy states of native modules + numpy_states = self.construct_native_modules_numpy_state_dict() + for node_uid, states in numpy_states.items(): + if len(states) > 0: + file = os.path.join(base_path, 'numpystate_'+ node_uid+'.npz') + np.savez(file, **states) + for node_uid in self.thetas: # save thetas data = {} @@ -567,6 +574,17 @@ def load(self): self.worldadapter_flow_nodes = initfrom.get('worldadapter_flow_nodes', {}) self.reload_native_modules(self.native_module_definitions) + # recover numpy states for native modules + for partition in self.partitions.values(): + nodeids = np.where((partition.allocated_nodes > MAX_STD_NODETYPE) | (partition.allocated_nodes == COMMENT))[0] + for node_id in nodeids: + node_uid = node_to_id(node_id, partition.pid) + file = os.path.join(self.get_persistency_path(), 'numpystate_'+node_uid+'.npz') + if os.path.isfile(file): + node = self.get_node(node_uid) + numpy_states = np.load(file) + node.set_persistable_state(node._state, numpy_states) + for monitorid in monitors: data = monitors[monitorid] if hasattr(monitor, data['classname']): @@ -2048,6 +2066,16 @@ def construct_native_modules_and_comments_dict(self): data[node_uid].update(self.flow_module_instances[node_uid].get_flow_data()) return data + def construct_native_modules_numpy_state_dict(self): + numpy_states = {} + i = 0 + for partition in self.partitions.values(): + nodeids = np.where((partition.allocated_nodes > MAX_STD_NODETYPE) | (partition.allocated_nodes == COMMENT))[0] + for node_id in nodeids: + node_uid = node_to_id(node_id, partition.pid) + numpy_states[node_uid] = self.get_node(node_uid).get_persistable_state()[1] + return numpy_states + def construct_nodes_dict(self, nodespace_uid=None, complete=False, include_links=True): data = {} for partition in self.partitions.values(): @@ -2384,7 +2412,7 @@ def get_nodespace_changes(self, nodespace_uids=[], since_step=0, include_links=T result['nodespaces_deleted'].extend(self.deleted_items[i].get('nodespaces_deleted', [])) result['nodes_deleted'].extend(self.deleted_items[i].get('nodes_deleted', [])) changed_nodes, changed_nodespaces = partition.get_nodespace_changes(nodespace.uid, since_step) - nodes, _ = partition.get_node_data(ids=changed_nodes, nodespaces_by_partition=nodespaces_by_partition, include_links=include_links) + nodes, _, _ = partition.get_node_data(ids=changed_nodes, nodespaces_by_partition=nodespaces_by_partition, include_links=include_links) result['nodes_dirty'].update(nodes) for uid in changed_nodespaces: uid = nodespace_to_id(uid, partition.pid) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index a525da6d..b6c1fd52 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1921,6 +1921,7 @@ def get_node_data(self, ids=None, nodespaces_by_partition=None, complete=False, linked_nodespaces_by_partition[self.spid] = self.allocated_node_parents[node_ids] nodes = {} + node_numpy_data = {} highdim_nodes = [] additional_links = [] @@ -1961,7 +1962,8 @@ def get_node_data(self, ids=None, nodespaces_by_partition=None, complete=False, state = None if uid in self.native_module_instances: - state = self.native_module_instances[uid].clone_state() + state, numpy_state = self.native_module_instances[uid].get_persistable_state() + node_numpy_data[uid] = numpy_state parameters = {} if strtype == "Sensor": @@ -2222,7 +2224,7 @@ def get_node_data(self, ids=None, nodespaces_by_partition=None, complete=False, "source_node_uid": source_uid, "source_gate_name": source_gate_type}) - return nodes, additional_links + return nodes, additional_links, node_numpy_data def integrity_check(self): diff --git a/micropsi_core/tests/test_node.py b/micropsi_core/tests/test_node.py index ccc8929e..b776e391 100644 --- a/micropsi_core/tests/test_node.py +++ b/micropsi_core/tests/test_node.py @@ -42,16 +42,6 @@ def test_nodetype_function_definition_overwrites_default_function_name(runtime, assert foo.nodefunction(nodenet, None) == 17 -def test_node_states(runtime, test_nodenet, node): - nodenet = runtime.get_nodenet(test_nodenet) - node = nodenet.get_node(node) - assert node.get_state('foobar') is None - node.set_state('foobar', 'bazbaz') - assert node.get_state('foobar') == 'bazbaz' - node.set_state('foobar', 42) - assert node.get_state('foobar') == 42 - - def test_node_positions_as_tuples(runtime, test_nodenet): nodenet = runtime.get_nodenet(test_nodenet) api = nodenet.netapi @@ -169,31 +159,3 @@ def test_node_show_plot_and_close_plot(runtime, test_nodenet): runtime.unload_nodenet(test_nodenet) assert plt.get_fignums() == [] -@pytest.mark.engine("theano_engine") -def test_numpy_state_persistency(runtime, test_nodenet): - import numpy as np - net = runtime.nodenets[test_nodenet] - netapi = net.netapi - node = netapi.create_node("Neuron", None, "Neuron") - node._state["string"] = "hugo" - node._state["dict"] = {"eins": 1, "zwei": 2} - node._state["list"] = [{"eins": 1, "zwei": 2}, "boing"] - node._state["numpy"] = np.asarray([1,2,3,4]) - - json_state, numpy_state = node.get_persistable_state() - - assert json_state["string"] == "hugo" - assert json_state["dict"]["eins"] == 1 - assert json_state["list"][0]["eins"] == 1 - assert json_state["list"][1] == "boing" - assert json_state["numpy"].startswith("__numpyelement__") - assert numpy_state[json_state["numpy"]].sum() == 10 - - node.set_persistable_state(json_state, numpy_state) - - assert node._state["string"] == "hugo" - assert node._state["dict"]["eins"] == 1 - assert node._state["list"][0]["eins"] == 1 - assert node._state["list"][1] == "boing" - assert node._state["numpy"].sum() == 10 - diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 18f63cff..950b67ca 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -6,6 +6,7 @@ """ import os import mock +import pytest __author__ = 'joscha' __date__ = '29.10.12' @@ -308,6 +309,71 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 assert node.get_parameter('protocol_mode') == 'most_active_one' +@pytest.mark.engine("dict_engine") +def test_node_states(runtime, test_nodenet, node): + nodenet = runtime.get_nodenet(test_nodenet) + node = nodenet.get_node(node) + assert node.get_state('foobar') is None + node.set_state('foobar', 'bazbaz') + assert node.get_state('foobar') == 'bazbaz' + node.set_state('foobar', 42) + assert node.get_state('foobar') == 42 + + +@pytest.mark.engine("theano_engine") +def test_node_states(runtime, test_nodenet, node, resourcepath): + import os + import numpy as np + + nodenet = runtime.get_nodenet(test_nodenet) + node = nodenet.get_node(node) + assert node.get_state('foobar') is None + node.set_state('foobar', 'bazbaz') + assert node.get_state('foobar') == 'bazbaz' + node.set_state('foobar', 42) + assert node.get_state('foobar') == 42 + + nodetype_file = os.path.join(resourcepath, 'nodetypes', 'Test', 'testnode.py') + with open(nodetype_file, 'w') as fp: + fp.write("""nodetype_definition = { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": ["linktype", "threshold", "protocol_mode"], + "parameter_values": { + "linktype": ["catexp", "subsur"], + "protocol_mode": ["all_active", "most_active_one"] + }, + "parameter_defaults": { + "linktype": "catexp", + "protocol_mode": "all_active" + } +} +def testnodefunc(netapi, node=None, **prams):\r\n return 17 +""") + + assert runtime.reload_code() + res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test", parameters={"threshold": "", "protocol_mode": "most_active_one"}) + + testnode = runtime.nodenets[test_nodenet].get_node(uid) + testnode.set_state("string", "hugo") + testnode.set_state("dict", {"eins": 1, "zwei": 2}) + testnode.set_state("list", [{"eins": 1, "zwei": 2}, "boing"]) + testnode.set_state("numpy", np.asarray([1,2,3,4])) + + runtime.save_nodenet(test_nodenet) + runtime.revert_nodenet(test_nodenet) + + testnode = runtime.nodenets[test_nodenet].get_node(uid) + + assert testnode.get_state("string") == "hugo" + assert testnode.get_state("dict")["eins"] == 1 + assert testnode.get_state("list")[0]["eins"] == 1 + assert testnode.get_state("list")[1] == "boing" + assert testnode.get_state("numpy").sum() == 10 + + def test_delete_linked_nodes(runtime, test_nodenet): nodenet = runtime.get_nodenet(test_nodenet) From e71478b78c8446ff37528a6a5224bc871865d04f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 14 Jun 2017 18:01:13 +0200 Subject: [PATCH 814/945] fix test names, remove unnecessary params also, add a comment that the assert functions as a typecheck --- .../tests/test_runtime_nodenet_basics.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 950b67ca..76598b6d 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -321,7 +321,7 @@ def test_node_states(runtime, test_nodenet, node): @pytest.mark.engine("theano_engine") -def test_node_states(runtime, test_nodenet, node, resourcepath): +def test_node_states_numpy(runtime, test_nodenet, node, resourcepath): import os import numpy as np @@ -340,15 +340,6 @@ def test_node_states(runtime, test_nodenet, node, resourcepath): "slottypes": ["gen", "foo", "bar"], "gatetypes": ["gen", "foo", "bar"], "nodefunction_name": "testnodefunc", - "parameters": ["linktype", "threshold", "protocol_mode"], - "parameter_values": { - "linktype": ["catexp", "subsur"], - "protocol_mode": ["all_active", "most_active_one"] - }, - "parameter_defaults": { - "linktype": "catexp", - "protocol_mode": "all_active" - } } def testnodefunc(netapi, node=None, **prams):\r\n return 17 """) @@ -360,7 +351,7 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 testnode.set_state("string", "hugo") testnode.set_state("dict", {"eins": 1, "zwei": 2}) testnode.set_state("list", [{"eins": 1, "zwei": 2}, "boing"]) - testnode.set_state("numpy", np.asarray([1,2,3,4])) + testnode.set_state("numpy", np.asarray([1, 2, 3, 4])) runtime.save_nodenet(test_nodenet) runtime.revert_nodenet(test_nodenet) @@ -371,7 +362,7 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 assert testnode.get_state("dict")["eins"] == 1 assert testnode.get_state("list")[0]["eins"] == 1 assert testnode.get_state("list")[1] == "boing" - assert testnode.get_state("numpy").sum() == 10 + assert testnode.get_state("numpy").sum() == 10 # only numpy arrays have ".sum()" def test_delete_linked_nodes(runtime, test_nodenet): From 8cbe1080db4190b1a4acb5cdf92c01435cf47c7a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 14 Jun 2017 18:49:23 +0200 Subject: [PATCH 815/945] first shot at auto-saving nodenets --- config.default.ini | 3 +++ micropsi_core/runtime.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/config.default.ini b/config.default.ini index 8fd79c72..62d033e2 100644 --- a/config.default.ini +++ b/config.default.ini @@ -37,6 +37,9 @@ host = localhost # python profiler for recipes and the nodenet-runner ; profile_runner = 1 +# automatically save running nodenets every X steps: +;auto_save_interval = 1000 + [minecraft] # use your minecraft.net username with password, respective diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index a8c7e08c..67b46114 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -203,6 +203,10 @@ def run(self): post_mortem() if self.profiler: self.profiler.disable() + save_interval = cfg['micropsi2'].get('auto_save_interval') + if save_interval is not None and nodenet.current_step % int(save_interval) == 0: + logging.getLogger("system").info("Auto-saving nodenet %s @ step %d" % (nodenet.name, nodenet.current_step)) + save_nodenet(uid) calc_time = datetime.now() - start left = step - calc_time From 92e1b6440b4e71ec5907a7da9d3e6bea605a8927 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 15 Jun 2017 15:09:01 +0200 Subject: [PATCH 816/945] fix parameter defaults in initfunction --- micropsi_core/nodenet/theano_engine/theano_flowmodule.py | 2 +- micropsi_core/tests/test_flowmodules.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py index 744c68b5..c52a3769 100644 --- a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py +++ b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py @@ -167,7 +167,7 @@ def node_function(self): def ensure_initialized(self): if not self.__initialized and not self.is_copy_of: - self._initfunction(self._nodenet.netapi, self, self.parameters) + self._initfunction(self._nodenet.netapi, self, self.clone_parameters()) self.__initialized = True def build(self, *inputs): diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index e505a059..6fbfeae7 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -39,11 +39,14 @@ def out12345(netapi, node, parameters): "init_function_name": "double_init", "inputs": ["inputs"], "outputs": ["outputs"], - "inputdims": [1] + "inputdims": [1], + "parameters": ["test_param"], + "parameter_defaults": {"test_param": "defaultvalue"} } def double_init(netapi, node, parameters): node.initfunction_ran = True + assert parameters['test_param'] == 'defaultvalue' def double(inputs, netapi, node, parameters): return inputs * 2 From 4dfc2c03e6296a50f3c4404671a0f1f0193abadb Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 15 Jun 2017 15:26:29 +0200 Subject: [PATCH 817/945] print a line-number if building the flowmodule failed if available --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index a473cab3..1e516a29 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1072,7 +1072,10 @@ def compile_flow_subgraph(self, node_uids, requested_outputs=None, use_different else: original_outex = node.build(*buildargs) except Exception as err: - self.logger.error("Error in buildfunction of Flowmodule %s.\n %s: %s" % (str(node), err.__class__.__name__, str(err))) + import traceback as tb + frame = [f[0] for f in tb.walk_tb(err.__traceback__) if f[0].f_code.co_filename == node.definition['path']] + lineno = "" if len(frame) == 0 else str(frame[0].f_lineno) + self.logger.error("Error in Flowmodule %s at line %s: %s: %s" % (str(node), lineno, err.__class__.__name__, str(err))) post_mortem() skip = True break From c5cecd04eb04dd88d85602e3dac25aa69b047a73 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 15 Jun 2017 19:31:04 +0200 Subject: [PATCH 818/945] simple adhoc monitor implementation --- micropsi_core/_runtime_api_monitors.py | 1 + micropsi_core/nodenet/monitor.py | 17 ++++++++++ micropsi_core/nodenet/netapi.py | 3 ++ micropsi_core/nodenet/nodenet.py | 18 +++++++++++ micropsi_core/tests/test_runtime_monitors.py | 34 ++++++++++++++++++++ 5 files changed, 73 insertions(+) diff --git a/micropsi_core/_runtime_api_monitors.py b/micropsi_core/_runtime_api_monitors.py index 40e02512..bdc1c4ac 100644 --- a/micropsi_core/_runtime_api_monitors.py +++ b/micropsi_core/_runtime_api_monitors.py @@ -91,6 +91,7 @@ def get_monitor_data(nodenet_uid, step=0, from_step=0, count=-1, with_recorders= elif from_step + count > nodenet.current_step: from_step = max(nodenet.current_step + 1 - count, 0) monitor_data = nodenet.construct_monitors_dict() + monitor_data.update(nodenet.construct_adhoc_monitors_dict()) if from_step > 0 or count > 0: for uid in monitor_data: values = {} diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index aed0ee44..654cfd39 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -192,3 +192,20 @@ def get_data(self, with_values=True): def getvalue(self): return self.compiled_function(self.nodenet.netapi) + + +class AdhocMonitor(Monitor): + + def __init__(self, nodenet, function, name=None, uid=None, color=None, values={}, **_): + super().__init__(nodenet, name, uid, color=color, values=values) + self.function = function + + def get_data(self, with_values=True): + data = super().get_data(with_values=with_values) + data.update({ + "function": "%s.%s" % (self.function.__module__, self.function.__name__) + }) + return data + + def getvalue(self): + return self.function() diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index e4c0eca3..21eb65a2 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -515,6 +515,9 @@ def add_custom_monitor(self, function, name, color=None): Returns the uid of the new monitor.""" return self.__nodenet.add_custom_monitor(function, name, color=color) + def add_adhoc_monitor(self, function, name): + return self.__nodenet.add_adhoc_monitor(function, name) + def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], gate='gen', color=None): """Adds a continuous monitor, that tracks the activations of the given group return-value for every calculation step. diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index d1507e11..34eebd2e 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -166,6 +166,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.owner = owner self._monitors = {} self._recorders = {} + self._adhoc_monitors = {} self._nodespace_ui_properties = {} self.netlock = Lock() @@ -666,6 +667,15 @@ def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], self._monitors[mon.uid] = mon return mon.uid + def add_adhoc_monitor(self, function, name): + """Adds an ephemeral adhoc monitor to quickly plot values returned by the given function. + If a monitor with the given name already exists, it's value-function is updated. """ + if name in self._adhoc_monitors: + self._adhoc_monitors[name].function = function + else: + mon = monitor.AdhocMonitor(self, function, name) + self._adhoc_monitors[name] = mon + def get_monitor(self, uid): return self._monitors.get(uid) @@ -677,6 +687,8 @@ def update_monitors_and_recorders(self): self._monitors[uid].step(self.current_step) for uid in self._recorders: self._recorders[uid].step(self.current_step) + for name in self._adhoc_monitors: + self._adhoc_monitors[name].step(self.current_step) def construct_monitors_dict(self, with_values=True): data = {} @@ -690,6 +702,12 @@ def construct_recorders_dict(self): data[uid] = self._recorders[uid].get_data() return data + def construct_adhoc_monitors_dict(self, with_values=True): + data = {} + for name in self._adhoc_monitors: + data[self._adhoc_monitors[name].uid] = self._adhoc_monitors[name].get_data(with_values=with_values) + return data + def remove_monitor(self, monitor_uid): del self._monitors[monitor_uid] diff --git a/micropsi_core/tests/test_runtime_monitors.py b/micropsi_core/tests/test_runtime_monitors.py index ddc7c92d..f7fac4ef 100644 --- a/micropsi_core/tests/test_runtime_monitors.py +++ b/micropsi_core/tests/test_runtime_monitors.py @@ -228,3 +228,37 @@ def test_add_group_monitor(runtime, test_nodenet): data3 = nodenet.get_monitor(monitor_uid).get_data() assert set(data3['values'][6][:6]) == {1.0} # first 6 active assert set(data3['values'][6][6:]) == {0.0} # rest off + + +def test_adhoc_monitor(runtime, test_nodenet): + nodenet = runtime.get_nodenet(test_nodenet) + netapi = nodenet.netapi + var = 13 + + def valuefunc(): + return var + netapi.add_adhoc_monitor(valuefunc, 'test') + runtime.step_nodenet(test_nodenet) + items = list(runtime.get_monitor_data(test_nodenet)['monitors'].items()) + assert len(items) == 1 + uid, data = items[0] + assert uid != data['name'] + assert data['name'] == 'test' + assert data['values'][1] == 13 + + def doublefunc(): + return var * 2 + netapi.add_adhoc_monitor(doublefunc, 'test') + runtime.step_nodenet(test_nodenet) + items = list(runtime.get_monitor_data(test_nodenet)['monitors'].items()) + assert len(items) == 1 + uid, data = items[0] + assert uid != data['name'] + assert data['name'] == 'test' + assert data['values'][1] == 13 + assert data['values'][2] == 26 + + runtime.save_nodenet(test_nodenet) + runtime.revert_nodenet(test_nodenet) + items = list(runtime.get_monitor_data(test_nodenet)['monitors'].items()) + assert len(items) == 0 From b58fc7d968ac68d3f9c88db7be9de45bcf49bdc3 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 16 Jun 2017 14:33:54 +0200 Subject: [PATCH 819/945] Adding parameters to ad hoc monitors --- micropsi_core/nodenet/monitor.py | 5 +++-- micropsi_core/nodenet/netapi.py | 4 ++-- micropsi_core/nodenet/nodenet.py | 5 +++-- micropsi_core/tests/test_runtime_monitors.py | 12 ++++++++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/micropsi_core/nodenet/monitor.py b/micropsi_core/nodenet/monitor.py index 654cfd39..8de9e668 100644 --- a/micropsi_core/nodenet/monitor.py +++ b/micropsi_core/nodenet/monitor.py @@ -196,9 +196,10 @@ def getvalue(self): class AdhocMonitor(Monitor): - def __init__(self, nodenet, function, name=None, uid=None, color=None, values={}, **_): + def __init__(self, nodenet, function, name=None, uid=None, color=None, values={}, parameters={}, **_): super().__init__(nodenet, name, uid, color=color, values=values) self.function = function + self.parameters = parameters def get_data(self, with_values=True): data = super().get_data(with_values=with_values) @@ -208,4 +209,4 @@ def get_data(self, with_values=True): return data def getvalue(self): - return self.function() + return self.function(**self.parameters) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 21eb65a2..7add6c22 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -515,8 +515,8 @@ def add_custom_monitor(self, function, name, color=None): Returns the uid of the new monitor.""" return self.__nodenet.add_custom_monitor(function, name, color=color) - def add_adhoc_monitor(self, function, name): - return self.__nodenet.add_adhoc_monitor(function, name) + def add_adhoc_monitor(self, function, name, parameters={}): + return self.__nodenet.add_adhoc_monitor(function, name, parameters) def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], gate='gen', color=None): """Adds a continuous monitor, that tracks the activations of the given group diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 34eebd2e..edafd414 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -667,13 +667,14 @@ def add_group_monitor(self, nodespace, name, node_name_prefix='', node_uids=[], self._monitors[mon.uid] = mon return mon.uid - def add_adhoc_monitor(self, function, name): + def add_adhoc_monitor(self, function, name, parameters={}): """Adds an ephemeral adhoc monitor to quickly plot values returned by the given function. If a monitor with the given name already exists, it's value-function is updated. """ if name in self._adhoc_monitors: self._adhoc_monitors[name].function = function + self._adhoc_monitors[name].parameters else: - mon = monitor.AdhocMonitor(self, function, name) + mon = monitor.AdhocMonitor(self, function, name, parameters=parameters) self._adhoc_monitors[name] = mon def get_monitor(self, uid): diff --git a/micropsi_core/tests/test_runtime_monitors.py b/micropsi_core/tests/test_runtime_monitors.py index f7fac4ef..4e9de67a 100644 --- a/micropsi_core/tests/test_runtime_monitors.py +++ b/micropsi_core/tests/test_runtime_monitors.py @@ -258,6 +258,18 @@ def doublefunc(): assert data['values'][1] == 13 assert data['values'][2] == 26 + def parameterfunc(foo): + return var * foo + netapi.add_adhoc_monitor(doublefunc, 'test', {'foo': 2}) + runtime.step_nodenet(test_nodenet) + items = list(runtime.get_monitor_data(test_nodenet)['monitors'].items()) + assert len(items) == 1 + uid, data = items[0] + assert uid != data['name'] + assert data['name'] == 'test' + assert data['values'][1] == 13 + assert data['values'][2] == 26 + runtime.save_nodenet(test_nodenet) runtime.revert_nodenet(test_nodenet) items = list(runtime.get_monitor_data(test_nodenet)['monitors'].items()) From 35d0c62b75f0da3fcbe1763ef0144cf0bf737187 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 16:55:18 +0200 Subject: [PATCH 820/945] step the worlds after sleeping --- micropsi_core/runtime.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index a8c7e08c..7d1cc225 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -172,6 +172,9 @@ def run(self): start = datetime.now() log = False uids = [uid for uid in nodenets if nodenets[uid].is_active] + world_uids = {} + if self.profiler: + self.profiler.enable() for uid in uids: if uid in nodenets: nodenet = nodenets[uid] @@ -181,8 +184,6 @@ def run(self): # nodenet.is_active = False continue log = True - if self.profiler: - self.profiler.enable() try: nodenet.timed_step() nodenet.update_monitors_and_recorders() @@ -193,23 +194,33 @@ def run(self): post_mortem() MicropsiRunner.last_nodenet_exception[uid] = sys.exc_info() if nodenet.world and nodenet.current_step % runner['factor'] == 0: - try: - worlds[nodenet.world].step() - except: - stop_nodenetrunner(uid) - # nodenet.is_active = False - logging.getLogger("world").error("Exception in Environment:", exc_info=1) - MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() - post_mortem() - if self.profiler: - self.profiler.disable() + if nodenet.world not in world_uids: + world_uids[nodenet.world] = [] + world_uids[nodenet.world].append(uid) + if self.profiler: + self.profiler.disable() calc_time = datetime.now() - start left = step - calc_time if left.total_seconds() > 0: time.sleep(left.total_seconds()) - step_time = datetime.now() - start + + if self.profiler: + self.profiler.enable() + for wuid, nodenet_uids in world_uids.items(): + try: + worlds[wuid].step() + except: + for uid in nodenet_uids: + stop_nodenetrunner(uid) + logging.getLogger("world").error("Exception in Environment:", exc_info=1) + MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() + post_mortem() + if self.profiler: + self.profiler.disable() + if log: + step_time = datetime.now() - start calc_ms = calc_time.seconds + ((calc_time.microseconds // 1000) / 1000) step_ms = step_time.seconds + ((step_time.microseconds // 1000) / 1000) self.sum_of_calc_durations += calc_ms From 41a5e92094903ba4429747c5d24f1acfe804c3aa Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 17:18:57 +0200 Subject: [PATCH 821/945] warn on overlong steps. --- micropsi_core/runtime.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 7d1cc225..2b56a795 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -201,9 +201,13 @@ def run(self): self.profiler.disable() calc_time = datetime.now() - start - left = step - calc_time - if left.total_seconds() > 0: - time.sleep(left.total_seconds()) + if step.total_seconds() > 0: + left = step - calc_time + if left.total_seconds() > 0: + time.sleep(left.total_seconds()) + elif left.total_seconds() < 0: + logging.getLogger("system").warning("Overlong step %d took %.4f secs, allowed are %.4f secs!" % + (self.total_steps, calc_time.total_seconds(), step.total_seconds())) if self.profiler: self.profiler.enable() From 9a36b3cd1874c49592bbd010d00b0bfb27fe8e9a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 18:45:23 +0200 Subject: [PATCH 822/945] clear worlds/nodenets created by tests --- conftest.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/conftest.py b/conftest.py index e20039f8..71d694ec 100644 --- a/conftest.py +++ b/conftest.py @@ -98,17 +98,23 @@ def pytest_runtest_setup(item): if engine_marker != item.callspec.params['engine']: pytest.skip("test requires engine %s" % engine_marker) + for uid in list(micropsi_runtime.nodenets.keys()): + micropsi_runtime.delete_nodenet(uid) + for uid in list(micropsi_runtime.worlds.keys()): + micropsi_runtime.delete_world(uid) + for item in os.listdir(testpath): - if item != 'worlds' and item != 'nodenets': - path = os.path.join(testpath, item) - if os.path.isdir(path): - shutil.rmtree(path) - else: - os.remove(path) + path = os.path.join(testpath, item) + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.remove(path) os.mkdir(os.path.join(testpath, 'nodetypes')) os.mkdir(os.path.join(testpath, 'recipes')) os.mkdir(os.path.join(testpath, 'operations')) + os.mkdir(os.path.join(testpath, 'nodenets')) + os.mkdir(os.path.join(testpath, 'worlds')) os.mkdir(os.path.join(testpath, 'nodetypes', 'Test')) open(os.path.join(testpath, 'nodetypes', 'Test', '__init__.py'), 'w').close() micropsi_runtime.reload_code() From cf3db6d9c80dd2c08924998efb64df9b92d506c5 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 18:45:45 +0200 Subject: [PATCH 823/945] be defensive against worlds/nodenets deleted in the meantime --- micropsi_core/runtime.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 2b56a795..ed0b448d 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -212,14 +212,16 @@ def run(self): if self.profiler: self.profiler.enable() for wuid, nodenet_uids in world_uids.items(): - try: - worlds[wuid].step() - except: - for uid in nodenet_uids: - stop_nodenetrunner(uid) - logging.getLogger("world").error("Exception in Environment:", exc_info=1) - MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() - post_mortem() + if wuid in worlds: + try: + worlds[wuid].step() + except: + for uid in nodenet_uids: + if uid in nodenets: + stop_nodenetrunner(uid) + logging.getLogger("world").error("Exception in Environment:", exc_info=1) + MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() + post_mortem() if self.profiler: self.profiler.disable() From 24611d03e0f4a5acb554258e580bf81d564e7117 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 18:46:03 +0200 Subject: [PATCH 824/945] stop runners before deletion --- micropsi_core/runtime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index ed0b448d..ab9526ba 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -539,6 +539,7 @@ def unload_nodenet(nodenet_uid): return False if nodenet_uid in netapi_consoles: del netapi_consoles[nodenet_uid] + stop_nodenetrunner(nodenet_uid) nodenet = nodenets[nodenet_uid] nodenet.close_figures() if nodenet.world: From 9c8d6e8dba4ba3211d698e29dc2e8f7b946681bc Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 18:46:11 +0200 Subject: [PATCH 825/945] set runner-interval in tests to fastest --- conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conftest.py b/conftest.py index 71d694ec..7bd70df2 100644 --- a/conftest.py +++ b/conftest.py @@ -119,7 +119,7 @@ def pytest_runtest_setup(item): open(os.path.join(testpath, 'nodetypes', 'Test', '__init__.py'), 'w').close() micropsi_runtime.reload_code() micropsi_runtime.logger.clear_logs() - micropsi_runtime.set_runner_properties(1, 1) + micropsi_runtime.set_runner_properties(0, 1) set_logging_levels() From c475301d82370a743caa262dbecc7f250cb74896 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 16 Jun 2017 18:56:38 +0200 Subject: [PATCH 826/945] don't log to a logfile when running the tests --- conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conftest.py b/conftest.py index 7bd70df2..7973074c 100644 --- a/conftest.py +++ b/conftest.py @@ -25,6 +25,8 @@ cfg['paths']['persistency_directory'] = testpath cfg['paths']['server_settings_path'] = os.path.join(testpath, 'server_cfg.json') cfg['paths']['usermanager_path'] = os.path.join(testpath, 'user-db.json') +if 'logfile' in cfg['logging']: + del cfg['logging']['logfile'] cfg['micropsi2']['single_agent_mode'] = '' if 'theano' in cfg: cfg['theano']['initial_number_of_nodes'] = '50' From 66404c7bc92c047d871c2965b6af8a73fa1b4f9d Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 19 Jun 2017 14:02:35 +0200 Subject: [PATCH 827/945] Following file naming scheme consistent with theta --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index cb3bd50a..68c6d193 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -510,8 +510,7 @@ def save(self): numpy_states = self.construct_native_modules_numpy_state_dict() for node_uid, states in numpy_states.items(): if len(states) > 0: - file = os.path.join(base_path, 'numpystate_'+ node_uid+'.npz') - np.savez(file, **states) + np.savez(os.path.join(base_path, '%s_numpystate.npz' % node_uid), **states) for node_uid in self.thetas: # save thetas @@ -579,7 +578,7 @@ def load(self): nodeids = np.where((partition.allocated_nodes > MAX_STD_NODETYPE) | (partition.allocated_nodes == COMMENT))[0] for node_id in nodeids: node_uid = node_to_id(node_id, partition.pid) - file = os.path.join(self.get_persistency_path(), 'numpystate_'+node_uid+'.npz') + file = os.path.join(self.get_persistency_path(), '%s_numpystate.npz' % node_uid) if os.path.isfile(file): node = self.get_node(node_uid) numpy_states = np.load(file) From 09a9f26021fec8628206665b567fff754b2e04f1 Mon Sep 17 00:00:00 2001 From: priska Date: Mon, 19 Jun 2017 16:38:59 +0200 Subject: [PATCH 828/945] Adds an API function to benchmark the speed of the underlying machine. --- micropsi_core/benchmark_system.py | 77 +++++++++++++++++++++++++++++++ micropsi_core/runtime.py | 7 +++ micropsi_server/micropsi_app.py | 6 +++ 3 files changed, 90 insertions(+) create mode 100644 micropsi_core/benchmark_system.py diff --git a/micropsi_core/benchmark_system.py b/micropsi_core/benchmark_system.py new file mode 100644 index 00000000..f2468966 --- /dev/null +++ b/micropsi_core/benchmark_system.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + + +def benchmark_system(): + + import numpy as np + import theano + import scipy + import timeit + + result = [""] + + result.append("numpy version: %s" % np.__version__) + result.append("scipy version: %s" % scipy.__version__) + result.append("theano version: %s" % theano.__version__) + result.append("") + result.append("theano device: %s" % theano.config.device) + result.append("theano blas: %s" % theano.config.blas.ldflags) + # theano CPU or GPU, which BLAS variant + result.append("") + + n = 1000 + repeat = 100 + + # numpy dot + setup = "import numpy as np; x = np.random.random(({0}, {0})).astype(np.float32)".format(n) + statement = "np.dot(x, x.T)" + timer = timeit.Timer(statement, setup=setup) + t = timer.timeit(repeat) / repeat + f = 2 * n ** 3 / t / 1e9 + result.append("numpy dot %.4f seconds; flop rate = %.2f Gflops/s" % (t, f)) + + # scipy dot + setup = "import scipy; import numpy as np; x = np.random.random(({0}, {0})).astype(np.float32)".format(n) + statement = "scipy.dot(x, x.T)" + timer = timeit.Timer(statement, setup=setup) + t = timer.timeit(repeat) / repeat + f = 2 * n ** 3 / t / 1e9 + result.append("scipy dot %.4f seconds; flop rate = %.2f Gflops/s" % (t, f)) + + # Fortran dot + setup = "from scipy import linalg; import numpy as np; x = np.random.random(({0}, {0})).astype(np.float32)".format(n) + statement = "linalg.blas.dgemm(1.0, x, x.T)" + timer = timeit.Timer(statement, setup=setup) + t = timer.timeit(repeat) / repeat + f = 2 * n ** 3 / t / 1e9 + result.append("scipy dgemm %.4f seconds; flop rate = %.2f Gflops/s" % (t, f)) + + # theano dot + setup = "import theano; import theano.tensor as T; x = T.matrix(); \ + dot = x.dot(x.T); func = theano.function([x], dot); \ + import numpy as np; x = np.random.random(({0}, {0})).astype(np.float32)".format(n) + statement = "func(x)" + timer = timeit.Timer(statement, setup=setup) + t = timer.timeit(repeat) / repeat + f = 2 * n ** 3 / t / 1e9 + result.append("theano dot %.4f seconds; flop rate = %.2f Gflops/s" % (t, f)) + + # theano using a shared variable + setup = "import theano; import numpy as np; x = np.random.random(({0}, {0})).astype(np.float32); \ + X = theano.shared(x); z = theano.tensor.dot(X, X.T); f = theano.function([], z)".format(n) + statement = "f()" + timer = timeit.Timer(statement, setup=setup) + # theano_times_list = timer.repeat(num_repeats, 1) + t = timer.timeit(repeat) / repeat + f = 2 * n ** 3 / t / 1e9 + result.append("theano shared %.4f seconds; flop rate = %.2f Gflops/s" % (t, f)) + + result.append("") + + return "\n".join(result) + + +if __name__ == "__main__": + + results = benchmark_system() + print(results) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index ab9526ba..24867b22 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -324,6 +324,13 @@ def get_logging_levels(nodenet_uid=None): return levels +def benchmark_system(): + from micropsi_core.benchmark_system import benchmark_system as benchmark + benchmarks = {} + benchmarks["benchmark"] = benchmark() + return benchmark + + # Nodenet def get_available_nodenets(owner=None): """Returns a dict of uids: Nodenet of available (running and stored) nodenets. diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 3fc4ac67..0ba32ffa 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1516,6 +1516,12 @@ def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor data = runtime.get_monitoring_info(nodenet_uid, logger, after, monitor_from, monitor_count, with_recorders=with_recorders) return True, data +# --------- benchmark system speed -------- + +@rpc("benchmark_system") +def benchmark_system(): + """ Time some math operations to determine the speed of the underlying machine. """ + return True, runtime.benchmark_system() # --- user scripts --- From 6ebd5fad55b31cd6497bd8623d5f53946b1d7f72 Mon Sep 17 00:00:00 2001 From: priska Date: Mon, 19 Jun 2017 16:51:11 +0200 Subject: [PATCH 829/945] Removes note to self. --- micropsi_core/benchmark_system.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micropsi_core/benchmark_system.py b/micropsi_core/benchmark_system.py index f2468966..04be45ea 100644 --- a/micropsi_core/benchmark_system.py +++ b/micropsi_core/benchmark_system.py @@ -16,7 +16,6 @@ def benchmark_system(): result.append("") result.append("theano device: %s" % theano.config.device) result.append("theano blas: %s" % theano.config.blas.ldflags) - # theano CPU or GPU, which BLAS variant result.append("") n = 1000 From 9d9645e1f6675713bffa95d0657d22920819fda5 Mon Sep 17 00:00:00 2001 From: priska Date: Mon, 19 Jun 2017 17:03:39 +0200 Subject: [PATCH 830/945] Renames benchmark_system to benchmark_info in compliance with other _info calls. --- micropsi_core/runtime.py | 6 +++--- micropsi_server/micropsi_app.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 24867b22..e25fb8e5 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -324,10 +324,10 @@ def get_logging_levels(nodenet_uid=None): return levels -def benchmark_system(): - from micropsi_core.benchmark_system import benchmark_system as benchmark +def benchmark_info(): + from micropsi_core.benchmark_system import benchmark_system benchmarks = {} - benchmarks["benchmark"] = benchmark() + benchmarks["benchmark"] = benchmark_system() return benchmark diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 0ba32ffa..bc58326f 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1516,10 +1516,10 @@ def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor data = runtime.get_monitoring_info(nodenet_uid, logger, after, monitor_from, monitor_count, with_recorders=with_recorders) return True, data -# --------- benchmark system speed -------- +# --------- benchmark info -------- -@rpc("benchmark_system") -def benchmark_system(): +@rpc("benchmark_info") +def benchmark_info(): """ Time some math operations to determine the speed of the underlying machine. """ return True, runtime.benchmark_system() From 185f4f13978f6b797803500bb3abd3f89c6c46d1 Mon Sep 17 00:00:00 2001 From: priska Date: Mon, 19 Jun 2017 17:26:35 +0200 Subject: [PATCH 831/945] Expose magic numbers as parameters in benchmark_system.py. --- micropsi_core/benchmark_system.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/micropsi_core/benchmark_system.py b/micropsi_core/benchmark_system.py index 04be45ea..3425bc97 100644 --- a/micropsi_core/benchmark_system.py +++ b/micropsi_core/benchmark_system.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -def benchmark_system(): +def benchmark_system(n=1000, repeat=100): import numpy as np import theano @@ -18,9 +18,6 @@ def benchmark_system(): result.append("theano blas: %s" % theano.config.blas.ldflags) result.append("") - n = 1000 - repeat = 100 - # numpy dot setup = "import numpy as np; x = np.random.random(({0}, {0})).astype(np.float32)".format(n) statement = "np.dot(x, x.T)" From e3d3532285b91d6f2038eabab51a0bf2bb2ebb4e Mon Sep 17 00:00:00 2001 From: priska Date: Mon, 19 Jun 2017 17:38:15 +0200 Subject: [PATCH 832/945] Fixes renaming bugs. --- micropsi_core/runtime.py | 2 +- micropsi_server/micropsi_app.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index e25fb8e5..aa6c9618 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -328,7 +328,7 @@ def benchmark_info(): from micropsi_core.benchmark_system import benchmark_system benchmarks = {} benchmarks["benchmark"] = benchmark_system() - return benchmark + return benchmarks # Nodenet diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index bc58326f..4d90f11d 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1521,7 +1521,7 @@ def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor @rpc("benchmark_info") def benchmark_info(): """ Time some math operations to determine the speed of the underlying machine. """ - return True, runtime.benchmark_system() + return True, runtime.benchmark_info() # --- user scripts --- From e934994d29e4c392276b42b2848db4791e60424d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 19 Jun 2017 17:45:58 +0200 Subject: [PATCH 833/945] tests, pep-8 --- micropsi_core/benchmark_system.py | 2 +- micropsi_core/tests/test_runtime.py | 13 +++++++++++++ micropsi_server/micropsi_app.py | 2 ++ micropsi_server/tests/test_json_api.py | 9 +++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/micropsi_core/benchmark_system.py b/micropsi_core/benchmark_system.py index 3425bc97..6c5349df 100644 --- a/micropsi_core/benchmark_system.py +++ b/micropsi_core/benchmark_system.py @@ -67,7 +67,7 @@ def benchmark_system(n=1000, repeat=100): return "\n".join(result) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover results = benchmark_system() print(results) diff --git a/micropsi_core/tests/test_runtime.py b/micropsi_core/tests/test_runtime.py index 4e016df7..5fead99e 100644 --- a/micropsi_core/tests/test_runtime.py +++ b/micropsi_core/tests/test_runtime.py @@ -7,6 +7,7 @@ """ import logging +import pytest def test_set_logging_level(runtime): @@ -281,3 +282,15 @@ def test_get_netapi_autocomplete(runtime, test_nodenet): def test_get_nodenet_by_name(runtime, test_nodenet): assert runtime.get_nodenet_uid_by_name("Foobar") is None assert runtime.get_nodenet_uid_by_name("Testnet") == test_nodenet + + +@pytest.mark.engine("theano_engine") +def test_system_benchmark(runtime, test_nodenet): + from micropsi_core.benchmark_system import benchmark_system + result = benchmark_system(n=10, repeat=1) + assert "numpy version" in result + assert "scipy version" in result + assert "theano version" in result + assert "numpy dot" in result + assert "scipy dot" in result + assert "theano dot" in result diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 4d90f11d..897bd12f 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1516,6 +1516,7 @@ def get_monitoring_info(nodenet_uid, logger=[], after=0, monitor_from=0, monitor data = runtime.get_monitoring_info(nodenet_uid, logger, after, monitor_from, monitor_count, with_recorders=with_recorders) return True, data + # --------- benchmark info -------- @rpc("benchmark_info") @@ -1523,6 +1524,7 @@ def benchmark_info(): """ Time some math operations to determine the speed of the underlying machine. """ return True, runtime.benchmark_info() + # --- user scripts --- @rpc("run_recipe") diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 6b0002bc..db3a143d 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1277,6 +1277,15 @@ def test_get_monitoring_info(app, test_nodenet): assert response.json_body['data']['logs']['logs'] == [] +@pytest.mark.engine("theano_engine") +def test_get_benchmark_info(app, test_nodenet): + import mock + with mock.patch("micropsi_core.benchmark_system.benchmark_system", return_value="testbench") as benchmock: + response = app.get_json('/rpc/benchmark_info()') + assert_success(response) + assert response.json_body['data']['benchmark'] == 'testbench' + + def test_400(app): app.set_auth() response = app.get_json('/rpc/save_nodenet("foobar")', expect_errors=True) From 6a9267378d9000a3bafa89bb01cb1c6775617761 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 15 May 2017 14:31:23 +0200 Subject: [PATCH 834/945] runtime decides persistency path --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 8 ++++---- micropsi_core/nodenet/nodenet.py | 11 ++++------- micropsi_core/nodenet/recorder.py | 2 +- micropsi_core/nodenet/theano_engine/theano_node.py | 2 +- .../nodenet/theano_engine/theano_nodenet.py | 12 ++++++------ .../nodenet/theano_engine/theano_partition.py | 6 +++--- micropsi_core/runtime.py | 1 + 7 files changed, 20 insertions(+), 22 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 09ded675..ccac00d6 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -110,7 +110,7 @@ def engine(self): def current_step(self): return self._step - def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None): + def __init__(self, persistency_path, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None): """Create a new MicroPsi agent. Arguments: @@ -120,7 +120,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No uid (optional): unique handle of the agent; if none is given, it will be generated """ - super().__init__(name, worldadapter, world, owner, uid, native_modules=native_modules, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance, version=version) + super().__init__(persistency_path, name, worldadapter, world, owner, uid, native_modules=native_modules, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance, version=version) self.nodetypes = {} for type, data in STANDARD_NODETYPES.items(): @@ -209,7 +209,7 @@ def get_nodes(self, nodespace_uids=[], include_links=True, links_to_nodespaces=[ return data def save(self): - base_path = self.get_persistency_path() + base_path = self.persistency_path filename = os.path.join(base_path, 'nodenet.json') # dict_engine saves everything to json, just dump the json export data = json.dumps(self.export_json(), sort_keys=True, indent=4) @@ -225,7 +225,7 @@ def load(self): if self._version != NODENET_VERSION: self.logger.error("Wrong version of nodenet data in nodenet %s, cannot load." % self.uid) return False - filename = os.path.join(self.get_persistency_path(), 'nodenet.json') + filename = os.path.join(self.persistency_path, 'nodenet.json') with self.netlock: initfrom = {} diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index edafd414..e0663969 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -144,11 +144,12 @@ def worldadapter_instance(self, _worldadapter_instance): if self._worldadapter_instance: self._worldadapter_instance.nodenet = self - def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None): + def __init__(self, persistency_path, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None): """ Constructor for the abstract base class, must be called by implementations """ self._uid = uid or micropsi_core.tools.generate_uid() + self.persistency_path = persistency_path self._name = name self._world_uid = world self._worldadapter_uid = worldadapter if world else None @@ -202,12 +203,8 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No for modulator in emo.writeable_modulators + emo.readable_modulators: self._modulators[modulator] = 1 - if not os.path.isdir(self.get_persistency_path()): - os.mkdir(self.get_persistency_path()) - - def get_persistency_path(self): - from micropsi_core.runtime import PERSISTENCY_PATH, NODENET_DIRECTORY - return os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, self.uid) + if not os.path.isdir(self.persistency_path): + os.mkdir(self.persistency_path) def get_data(self, complete=False, include_links=True): """ diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index ac1c0e9c..c0e6dbec 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -31,7 +31,7 @@ def __init__(self, nodenet, name="", uid="", interval=1, first_step=0, current_i self.name = name self.uid = uid or tools.generate_uid() self.interval = interval - self.filename = os.path.join(nodenet.get_persistency_path(), 'recorder_%s.npz' % self.uid) + self.filename = os.path.join(nodenet.persistency_path, 'recorder_%s.npz' % self.uid) self.first_step = first_step self.current_index = current_index self.shapes = {} diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 03824391..bff1d2b0 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -33,7 +33,7 @@ def __init__(self, nodenet, partition, parent_uid, uid, numerictype, parameters= self.is_highdimensional = type(self._nodetype) == HighdimensionalNodetype - self.datafile = os.path.join(nodenet.get_persistency_path(), '%s_node_%s.npz' % (self._nodenet.uid, self.uid)) + self.datafile = os.path.join(nodenet.persistency_path, '%s_node_%s.npz' % (self._nodenet.uid, self.uid)) if strtype in nodenet.native_modules or strtype == "Comment": self.slot_activation_snapshot = {} diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 3e52e0c5..11a4acae 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -127,7 +127,7 @@ def worldadapter_instance(self, _worldadapter_instance): def current_step(self): return self._step - def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None, flow_modules={}): + def __init__(self, persistency_path, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None, flow_modules={}): # map of string uids to positions. Not all nodes necessarily have an entry. self.positions = {} @@ -141,7 +141,7 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No # map of data targets to string node IDs self.actuatormap = {} - super().__init__(name, worldadapter, world, owner, uid, native_modules=native_modules, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance, version=version) + super().__init__(persistency_path, name, worldadapter, world, owner, uid, native_modules=native_modules, use_modulators=use_modulators, worldadapter_instance=worldadapter_instance, version=version) self.nodetypes = {} for type, data in STANDARD_NODETYPES.items(): @@ -489,7 +489,7 @@ def initialize_stepoperators(self): self.stepoperators.sort(key=lambda op: op.priority) def save(self): - base_path = self.get_persistency_path() + base_path = self.persistency_path # write json metadata, which will be used by runtime to manage the net with open(os.path.join(base_path, 'nodenet.json'), 'w+', encoding="utf-8") as fp: @@ -537,7 +537,7 @@ def load(self): return False # try to access file - filename = os.path.join(self.get_persistency_path(), 'nodenet.json') + filename = os.path.join(self.persistency_path, 'nodenet.json') with self.netlock: initfrom = {} if os.path.isfile(filename): @@ -596,14 +596,14 @@ def load(self): data = initfrom['recorders'][recorder_uid] self._recorders[recorder_uid] = getattr(recorder, data['classname'])(self, **data) - flowfile = os.path.join(self.get_persistency_path(), 'flowgraph.pickle') + flowfile = os.path.join(self.persistency_path, 'flowgraph.pickle') if os.path.isfile(flowfile): self.flowgraph = nx.read_gpickle(flowfile) for node_uid in self.flow_module_instances: self.flow_module_instances[node_uid].ensure_initialized() - theta_file = os.path.join(self.get_persistency_path(), "%s_thetas.npz" % node_uid) + theta_file = os.path.join(self.persistency_path, "%s_thetas.npz" % node_uid) if os.path.isfile(theta_file): data = np.load(theta_file) for key in data: diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index b6c1fd52..85a0c75b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -725,7 +725,7 @@ def grow_number_of_nodes(self, growby): self.has_new_usages = True def save(self): - base_path = self.nodenet.get_persistency_path() + base_path = self.nodenet.persistency_path allocated_nodes = self.allocated_nodes allocated_node_offsets = self.allocated_node_offsets @@ -811,7 +811,7 @@ def load_data(self, nodes_data): """Load the node net from a file""" # try to access file - base_path = self.nodenet.get_persistency_path() + base_path = self.nodenet.persistency_path filename = os.path.join(base_path, "partition-%s.npz" % self.spid) datafile = None if os.path.isfile(filename): @@ -1033,7 +1033,7 @@ def load_data(self, nodes_data): self.__calculate_g_factors() def load_inlinks(self): - base_path = self.nodenet.get_persistency_path() + base_path = self.nodenet.persistency_path for spid in self.nodenet.partitions: filename = os.path.join(base_path, "inlinks-%s-from-%s.npz" % (self.spid, spid)) if os.path.isfile(filename): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 75bf16a0..82c7d81d 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -416,6 +416,7 @@ def load_nodenet(nodenet_uid): logger.register_logger("agent.%s" % nodenet_uid, cfg['logging']['level_agent']) params = { + 'persistency_path': os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, data.uid), 'name': data.name, 'worldadapter': worldadapter, 'worldadapter_instance': worldadapter_instance, From 7f06123508dce862b7e144a5108186f0d714cb55 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 19 Jun 2017 19:02:52 +0200 Subject: [PATCH 835/945] adjust new state persistency to cherry picked commit --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 11a4acae..547279ef 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -578,7 +578,7 @@ def load(self): nodeids = np.where((partition.allocated_nodes > MAX_STD_NODETYPE) | (partition.allocated_nodes == COMMENT))[0] for node_id in nodeids: node_uid = node_to_id(node_id, partition.pid) - file = os.path.join(self.get_persistency_path(), '%s_numpystate.npz' % node_uid) + file = os.path.join(self.persistency_path, '%s_numpystate.npz' % node_uid) if os.path.isfile(file): node = self.get_node(node_uid) numpy_states = np.load(file) From 1dc0642ea3805e4d13fb6496ef02e83b0b6dd672 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 19 May 2017 13:58:41 +0200 Subject: [PATCH 836/945] allow to save nodenet to custom folders --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 5 +++-- micropsi_core/nodenet/nodenet.py | 6 ++++-- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 ++++--- micropsi_core/nodenet/theano_engine/theano_partition.py | 5 +++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index ccac00d6..70e715ca 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -208,8 +208,9 @@ def get_nodes(self, nodespace_uids=[], include_links=True, links_to_nodespaces=[ return data - def save(self): - base_path = self.persistency_path + def save(self, base_path=None): + if base_path is None: + base_path = self.persistency_path filename = os.path.join(base_path, 'nodenet.json') # dict_engine saves everything to json, just dump the json export data = json.dumps(self.export_json(), sort_keys=True, indent=4) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index e0663969..f3c5f633 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -248,9 +248,11 @@ def get_links_for_nodes(self, node_uids): pass # pragma: no cover @abstractmethod - def save(self): + def save(self, base_path=None): """ - Saves the nodenet to the given main metadata json file. + Saves the nodenet to persistency. + Arguments: + base_path (String) - Save files to a non-standard directory """ pass # pragma: no cover diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 547279ef..ba759cbf 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -488,8 +488,9 @@ def initialize_stepoperators(self): self.stepoperators.append(DoernerianEmotionalModulators()) self.stepoperators.sort(key=lambda op: op.priority) - def save(self): - base_path = self.persistency_path + def save(self, base_path=None): + if base_path is None: + base_path = self.persistency_path # write json metadata, which will be used by runtime to manage the net with open(os.path.join(base_path, 'nodenet.json'), 'w+', encoding="utf-8") as fp: @@ -527,7 +528,7 @@ def save(self): for partition in self.partitions.values(): # save partitions - partition.save() + partition.save(base_path=base_path) def load(self): """Load the node net from a file""" diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 85a0c75b..940c168b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -724,8 +724,9 @@ def grow_number_of_nodes(self, growby): self.NoN = new_NoN self.has_new_usages = True - def save(self): - base_path = self.nodenet.persistency_path + def save(self, base_path=None): + if base_path is None: + base_path = self.nodenet.persistency_path allocated_nodes = self.allocated_nodes allocated_node_offsets = self.allocated_node_offsets From 31a87aec62b4b40accd54a4e69ab35648cd04d66 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 19 Jun 2017 19:30:11 +0200 Subject: [PATCH 837/945] autosave to dedicated folder --- config.default.ini | 2 +- micropsi_core/runtime.py | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/config.default.ini b/config.default.ini index 62d033e2..53fe6e27 100644 --- a/config.default.ini +++ b/config.default.ini @@ -38,7 +38,7 @@ host = localhost ; profile_runner = 1 # automatically save running nodenets every X steps: -;auto_save_interval = 1000 +auto_save_intervals = 1000,10000,1000000 [minecraft] diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 82c7d81d..ac32da72 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -59,6 +59,7 @@ def plotter_initializer(): RESOURCE_PATH = None PERSISTENCY_PATH = None WORLD_PATH = None +AUTOSAVE_PATH = None configs = None logger = None @@ -77,6 +78,9 @@ def plotter_initializer(): initialized = False +auto_save_intervals = cfg['micropsi2'].get('auto_save_intervals') +if auto_save_intervals is not None: + auto_save_intervals = sorted([int(x) for x in cfg['micropsi2']['auto_save_intervals'].split(',')], reverse=True) from code import InteractiveConsole @@ -173,6 +177,7 @@ def run(self): log = False uids = [uid for uid in nodenets if nodenets[uid].is_active] world_uids = {} + nodenets_to_save = [] if self.profiler: self.profiler.enable() for uid in uids: @@ -198,13 +203,22 @@ def run(self): world_uids[nodenet.world] = [] world_uids[nodenet.world].append(uid) save_interval = cfg['micropsi2'].get('auto_save_interval') - if save_interval is not None and nodenet.current_step % int(save_interval) == 0: - logging.getLogger("system").info("Auto-saving nodenet %s @ step %d" % (nodenet.name, nodenet.current_step)) - save_nodenet(uid) + + if auto_save_intervals is not None: + for val in auto_save_intervals: + if nodenet.current_step % val == 0: + nodenets_to_save.append((nodenet.uid, val)) + break if self.profiler: self.profiler.disable() + for uid, interval in nodenets_to_save: + savepath = os.path.join(AUTOSAVE_PATH, "%s_%d" % (uid, interval)) + os.makedirs(savepath, exist_ok=True) + logging.getLogger("system").info("Auto-saving nodenet %s at step %d (interval %d)" % (uid, nodenets[uid].current_step, interval)) + nodenets[uid].save(base_path=savepath) + calc_time = datetime.now() - start if step.total_seconds() > 0: left = step - calc_time @@ -1889,8 +1903,8 @@ def runtime_info(): } -def initialize(persistency_path=None, resource_path=None, world_path=None): - global PERSISTENCY_PATH, RESOURCE_PATH, WORLD_PATH, configs, logger, runner, initialized +def initialize(persistency_path=None, resource_path=None, world_path=None, autosave_path=None): + global PERSISTENCY_PATH, RESOURCE_PATH, WORLD_PATH, AUTOSAVE_PATH, configs, logger, runner, initialized PERSISTENCY_PATH = persistency_path or cfg['paths']['persistency_directory'] RESOURCE_PATH = resource_path or cfg['paths']['agent_directory'] @@ -1900,6 +1914,10 @@ def initialize(persistency_path=None, resource_path=None, world_path=None): configs = config.ConfigurationManager(cfg['paths']['server_settings_path']) + # create autosave-dir if not exists: + if auto_save_intervals is not None: + AUTOSAVE_PATH = autosave_path or os.path.join(PERSISTENCY_PATH, "nodenets", "__autosave__") + # bring up plotting infrastructure if matplotlib is not None: plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) From fef3e53154df406912a96805e3609cb6ee731093 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 19 Jun 2017 19:35:59 +0200 Subject: [PATCH 838/945] ignore autosaved nets during discovery --- micropsi_core/runtime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index ac32da72..20c7c274 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1558,6 +1558,8 @@ def crawl_definition_files(path, datatype="definition"): result = {} os.makedirs(path, exist_ok=True) for user_directory_name, user_directory_names, file_names in os.walk(path): + if os.path.relpath(user_directory_name, start=os.path.join(PERSISTENCY_PATH, "nodenets")).startswith("__autosave__"): + continue for definition_file_name in file_names: if definition_file_name.endswith(".json"): try: From 0cd50f174de0913a7b793e97a9df2f7261fe5c59 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 19 Jun 2017 19:46:06 +0200 Subject: [PATCH 839/945] at least basically test the autosave feature --- conftest.py | 3 ++- micropsi_core/tests/test_runtime_nodenet_basics.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/conftest.py b/conftest.py index 7973074c..4c6d188b 100644 --- a/conftest.py +++ b/conftest.py @@ -25,6 +25,7 @@ cfg['paths']['persistency_directory'] = testpath cfg['paths']['server_settings_path'] = os.path.join(testpath, 'server_cfg.json') cfg['paths']['usermanager_path'] = os.path.join(testpath, 'user-db.json') + if 'logfile' in cfg['logging']: del cfg['logging']['logfile'] cfg['micropsi2']['single_agent_mode'] = '' @@ -32,7 +33,7 @@ cfg['theano']['initial_number_of_nodes'] = '50' if 'on_exception' in cfg['micropsi2']: cfg['micropsi2']['on_exception'] = '' - +cfg['micropsi2']['auto_save_intervals'] = '100' world_uid = 'WorldOfPain' nn_uid = 'Testnet' diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 76598b6d..98e845c7 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -531,3 +531,13 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 neuron = runtime.nodenets[test_nodenet].get_node(neuron_uid) assert neuron.get_gate('gen').get_links() == [] assert neuron.get_slot('gen').get_links() == [] + + +def test_runtime_nodenet_autosave(runtime, test_nodenet, resourcepath): + import os + from time import sleep + runtime.set_runner_condition(test_nodenet, steps=101) + runtime.start_nodenetrunner(test_nodenet) + while runtime.nodenets[test_nodenet].is_active: + sleep(.1) + assert os.path.isfile(os.path.join(resourcepath, "nodenets", "__autosave__", "%s_%d" % (test_nodenet, 100), "nodenet.json")) From 9c5cf1f490ab1608fcef06083fa0a070292fcc45 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 20 Jun 2017 15:11:38 +0200 Subject: [PATCH 840/945] fix parameter defaults in flow run_function --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 +- micropsi_core/tests/test_flowmodules.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 3e52e0c5..5f28ee3b 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1202,7 +1202,7 @@ def compiled(thetas=None, **kwargs): elif source == 'path': funcargs.append(all_outputs[pidx][item]) if thunk['implementation'] == 'python': - out = thunk['function'](*funcargs, netapi=self.netapi, node=thunk['node'], parameters=thunk['node'].parameters) + out = thunk['function'](*funcargs, netapi=self.netapi, node=thunk['node'], parameters=thunk['node'].clone_parameters()) if len(thunk['node'].outputs) <= 1: out = [out] else: diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index 6fbfeae7..dc482c53 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -22,11 +22,14 @@ def prepare(runtime, test_nodenet, default_world, resourcepath, wa_class=None): "run_function_name": "out12345", "inputs": [], "outputs": ["out"], - "inputdims": [] + "inputdims": [], + "parameters": ["default_test"], + "parameter_defaults": {"default_test": "defaultvalue"} } def out12345(netapi, node, parameters): import numpy as np + assert parameters['default_test'] == 'defaultvalue' return np.asarray([1,2,3,4,5]) """) From 655d89ab71daa6973efba34c24c32a8519a76ea0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 20 Jun 2017 18:08:50 +0200 Subject: [PATCH 841/945] fix duplicate logger outputs --- micropsi_core/micropsi_logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_core/micropsi_logger.py b/micropsi_core/micropsi_logger.py index b9143b46..59c49704 100644 --- a/micropsi_core/micropsi_logger.py +++ b/micropsi_core/micropsi_logger.py @@ -101,6 +101,7 @@ def __init__(self, default_logging_levels={}, log_to_file=False): def register_logger(self, name, level): self.loggers[name] = logging.getLogger(name) self.loggers[name].setLevel(level) + self.loggers[name].propagate = False self.record_storage[name] = [] self.handlers[name] = RecordWebStorageHandler(self.record_storage, name) formatter = logging.Formatter(self.default_format) @@ -110,7 +111,7 @@ def register_logger(self, name, level): logging.getLogger(name).addHandler(self.filehandler) self.loggers[name].debug("Logger %s ready" % name) if coloredlogs is not None: - coloredlogs.install(logger=self.loggers[name]) + coloredlogs.install(logger=self.loggers[name], level=level) def unregister_logger(self, name): logging.getLogger(name).removeHandler(self.handlers[name]) From 20ec27aedba71dcc48839da453bde1220b8f34bf Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 20 Jun 2017 20:16:38 +0200 Subject: [PATCH 842/945] move theano floatX config to runtime --- .../nodenet/theano_engine/theano_nodenet.py | 9 ++------- micropsi_core/runtime.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 5f28ee3b..7839282d 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -160,13 +160,8 @@ def __init__(self, name="", worldadapter="Default", world=None, owner="", uid=No self.scipyfloatX = scipy.float64 self.numpyfloatX = np.float64 self.byte_per_float = 8 - else: # pragma: no cover - self.logger.warning("Unsupported precision value from configuration: %s, falling back to float64", precision) - T.config.floatX = "float64" - self.theanofloatX = "float64" - self.scipyfloatX = scipy.float64 - self.numpyfloatX = np.float64 - self.byte_per_float = 8 + else: + raise RuntimeError("Unsupported float precision value") device = T.config.device self.logger.info("Theano configured to use %s", device) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index aa6c9618..bffc3b9a 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1905,6 +1905,20 @@ def initialize(persistency_path=None, resource_path=None, world_path=None): 'world': cfg['logging']['level_world'] }, cfg['logging'].get('logfile')) + try: + import theano + precision = cfg['theano']['precision'] + if precision == "32": + theano.config.floatX = "float32" + elif precision == "64": + theano.config.floatX = "float64" + else: # pragma: no cover + logging.getLogger("system").warning("Unsupported precision value from configuration: %s, falling back to float64", precision) + theano.config.floatX = "float64" + cfg['theano']['precision'] = "64" + except ImportError: + pass + result, errors = reload_code() load_definitions() for e in errors: From 56ffb76c3b8e4b35bb9e56fd90ab1299639b2915 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 21 Jun 2017 16:31:21 +0200 Subject: [PATCH 843/945] move autosve settings to initialize() --- micropsi_core/runtime.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 521aaca7..72708050 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -78,11 +78,9 @@ def plotter_initializer(): initialized = False -auto_save_intervals = cfg['micropsi2'].get('auto_save_intervals') -if auto_save_intervals is not None: - auto_save_intervals = sorted([int(x) for x in cfg['micropsi2']['auto_save_intervals'].split(',')], reverse=True) -from code import InteractiveConsole +auto_save_intervals = None +from code import InteractiveConsole class FileCacher(): @@ -202,7 +200,6 @@ def run(self): if nodenet.world not in world_uids: world_uids[nodenet.world] = [] world_uids[nodenet.world].append(uid) - save_interval = cfg['micropsi2'].get('auto_save_interval') if auto_save_intervals is not None: for val in auto_save_intervals: @@ -1906,7 +1903,7 @@ def runtime_info(): def initialize(persistency_path=None, resource_path=None, world_path=None, autosave_path=None): - global PERSISTENCY_PATH, RESOURCE_PATH, WORLD_PATH, AUTOSAVE_PATH, configs, logger, runner, initialized + global PERSISTENCY_PATH, RESOURCE_PATH, WORLD_PATH, AUTOSAVE_PATH, configs, logger, runner, initialized, auto_save_intervals PERSISTENCY_PATH = persistency_path or cfg['paths']['persistency_directory'] RESOURCE_PATH = resource_path or cfg['paths']['agent_directory'] @@ -1917,8 +1914,11 @@ def initialize(persistency_path=None, resource_path=None, world_path=None, autos configs = config.ConfigurationManager(cfg['paths']['server_settings_path']) # create autosave-dir if not exists: + auto_save_intervals = cfg['micropsi2'].get('auto_save_intervals') if auto_save_intervals is not None: + auto_save_intervals = sorted([int(x) for x in cfg['micropsi2']['auto_save_intervals'].split(',')], reverse=True) AUTOSAVE_PATH = autosave_path or os.path.join(PERSISTENCY_PATH, "nodenets", "__autosave__") + os.makedirs(AUTOSAVE_PATH, exist_ok=True) # bring up plotting infrastructure if matplotlib is not None: From 50e41869ee27d683aba5ba5d9c39317e4ad923f9 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 21 Jun 2017 16:33:40 +0200 Subject: [PATCH 844/945] autosave now saves to a zipfile --- .../nodenet/dict_engine/dict_nodenet.py | 19 ++-- micropsi_core/nodenet/nodenet.py | 3 +- .../nodenet/theano_engine/theano_nodenet.py | 60 ++++++++---- .../nodenet/theano_engine/theano_partition.py | 98 +++++++++++-------- micropsi_core/runtime.py | 12 ++- .../tests/test_runtime_nodenet_basics.py | 73 +++++++++++++- 6 files changed, 190 insertions(+), 75 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 70e715ca..3c364c94 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -208,17 +208,20 @@ def get_nodes(self, nodespace_uids=[], include_links=True, links_to_nodespaces=[ return data - def save(self, base_path=None): + def save(self, base_path=None, zipfile=None): if base_path is None: base_path = self.persistency_path - filename = os.path.join(base_path, 'nodenet.json') - # dict_engine saves everything to json, just dump the json export data = json.dumps(self.export_json(), sort_keys=True, indent=4) - with open(filename, 'w+', encoding="utf-8") as fp: - fp.write(data) - if os.path.getsize(filename) < 100: - # kind of hacky, but we don't really know what was going on - raise RuntimeError("Error writing nodenet file") + if zipfile: + zipfile.writestr('nodenet.json', data) + else: + filename = os.path.join(base_path, 'nodenet.json') + # dict_engine saves everything to json, just dump the json export + with open(filename, 'w+', encoding="utf-8") as fp: + fp.write(data) + if os.path.getsize(filename) < 100: + # kind of hacky, but we don't really know what was going on + raise RuntimeError("Error writing nodenet file") def load(self): """Load the node net from a file""" diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index f3c5f633..2c0e5216 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -248,11 +248,12 @@ def get_links_for_nodes(self, node_uids): pass # pragma: no cover @abstractmethod - def save(self, base_path=None): + def save(self, base_path=None, zipfile=None): """ Saves the nodenet to persistency. Arguments: base_path (String) - Save files to a non-standard directory + zipfile (ZipFile object) - Save the nodenet to a zipfile instead """ pass # pragma: no cover diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 8eacc3e7..7cf8eba5 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -4,6 +4,7 @@ Nodenet definition """ import json +import io import os import copy import math @@ -483,47 +484,70 @@ def initialize_stepoperators(self): self.stepoperators.append(DoernerianEmotionalModulators()) self.stepoperators.sort(key=lambda op: op.priority) - def save(self, base_path=None): + def save(self, base_path=None, zipfile=None): if base_path is None: base_path = self.persistency_path # write json metadata, which will be used by runtime to manage the net - with open(os.path.join(base_path, 'nodenet.json'), 'w+', encoding="utf-8") as fp: - metadata = self.metadata - metadata['positions'] = self.positions - metadata['names'] = self.names - metadata['actuatormap'] = self.actuatormap - metadata['sensormap'] = self.sensormap - metadata['nodes'] = self.construct_native_modules_and_comments_dict() - metadata['monitors'] = self.construct_monitors_dict() - metadata['modulators'] = self.construct_modulators_dict() - metadata['partition_parents'] = self.inverted_partitionmap - metadata['recorders'] = self.construct_recorders_dict() - metadata['worldadapter_flow_nodes'] = self.worldadapter_flow_nodes - fp.write(json.dumps(metadata, sort_keys=True, indent=4)) + metadata = self.metadata + metadata['positions'] = self.positions + metadata['names'] = self.names + metadata['actuatormap'] = self.actuatormap + metadata['sensormap'] = self.sensormap + metadata['nodes'] = self.construct_native_modules_and_comments_dict() + metadata['monitors'] = self.construct_monitors_dict() + metadata['modulators'] = self.construct_modulators_dict() + metadata['partition_parents'] = self.inverted_partitionmap + metadata['recorders'] = self.construct_recorders_dict() + metadata['worldadapter_flow_nodes'] = self.worldadapter_flow_nodes + if zipfile: + zipfile.writestr('nodenet.json', json.dumps(metadata)) + else: + with open(os.path.join(base_path, 'nodenet.json'), 'w+', encoding="utf-8") as fp: + fp.write(json.dumps(metadata, sort_keys=True, indent=4)) # write numpy states of native modules numpy_states = self.construct_native_modules_numpy_state_dict() for node_uid, states in numpy_states.items(): if len(states) > 0: - np.savez(os.path.join(base_path, '%s_numpystate.npz' % node_uid), **states) + filename = "%s_numpystate.npz" % node_uid + if zipfile: + stream = io.BytesIO() + np.savez(stream, **states) + stream.seek(0) + zipfile.writestr(filename, stream.getvalue()) + else: + np.savez(os.path.join(base_path, filename), **states) for node_uid in self.thetas: # save thetas data = {} + filename = "%s_thetas.npz" % node_uid for idx, name in enumerate(self.thetas[node_uid]['names']): data[name] = self.thetas[node_uid]['variables'][idx].get_value() - np.savez(os.path.join(base_path, "%s_thetas.npz" % node_uid), **data) + if zipfile: + stream = io.BytesIO() + np.savez(stream, **data) + stream.seek(0) + zipfile.writestr(filename, stream.getvalue()) + else: + np.savez(os.path.join(base_path, filename), **data) # write graph data - nx.write_gpickle(self.flowgraph, os.path.join(base_path, "flowgraph.pickle")) + if zipfile: + stream = io.BytesIO() + nx.write_gpickle(self.flowgraph, stream) + stream.seek(0) + zipfile.writestr("flowgraph.pickle", stream.getvalue()) + else: + nx.write_gpickle(self.flowgraph, os.path.join(base_path, "flowgraph.pickle")) for recorder_uid in self._recorders: self._recorders[recorder_uid].save() for partition in self.partitions.values(): # save partitions - partition.save(base_path=base_path) + partition.save(base_path=base_path, zipfile=zipfile) def load(self): """Load the node net from a file""" diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 940c168b..12eec16e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1,5 +1,6 @@ +import io import os import theano @@ -724,7 +725,7 @@ def grow_number_of_nodes(self, growby): self.NoN = new_NoN self.has_new_usages = True - def save(self, base_path=None): + def save(self, base_path=None, zipfile=None): if base_path is None: base_path = self.nodenet.persistency_path @@ -766,47 +767,60 @@ def save(self, base_path=None): sizeinformation = [self.NoN, self.NoE, self.NoNS] for spid, inlinks in self.inlinks.items(): - filename = os.path.join(base_path, "inlinks-%s-from-%s.npz" % (self.spid, spid)) - from_ids = inlinks[0].get_value(borrow=True) - to_ids = inlinks[1].get_value(borrow=True) - weights = inlinks[2].get_value(borrow=True) if inlinks[2] else None - np.savez(filename, - from_partition_id=spid, - from_ids=from_ids, - to_ids=to_ids, - weights=weights, - inlink_type=inlinks[4]) - - np.savez(os.path.join(base_path, "partition-%s.npz" % self.spid), - allocated_nodes=allocated_nodes, - allocated_node_offsets=allocated_node_offsets, - allocated_elements_to_nodes=allocated_elements_to_nodes, - allocated_node_parents=allocated_node_parents, - allocated_nodespaces=allocated_nodespaces, - w_data=w.data, - w_indices=w.indices, - w_indptr=w.indptr, - a=a, - g_bias=g_bias, - g_factor=g_factor, - g_threshold=g_threshold, - g_amplification=g_amplification, - g_min=g_min, - g_max=g_max, - g_function_selector=g_function_selector, - g_expect=g_expect, - g_countdown=g_countdown, - g_wait=g_wait, - n_function_selector=n_function_selector, - sizeinformation=sizeinformation, - allocated_elements_to_activators=allocated_elements_to_activators, - allocated_nodespaces_por_activators=allocated_nodespaces_por_activators, - allocated_nodespaces_ret_activators=allocated_nodespaces_ret_activators, - allocated_nodespaces_sub_activators=allocated_nodespaces_sub_activators, - allocated_nodespaces_sur_activators=allocated_nodespaces_sur_activators, - allocated_nodespaces_cat_activators=allocated_nodespaces_cat_activators, - allocated_nodespaces_exp_activators=allocated_nodespaces_exp_activators, - allocated_nodespaces_sampling_activators=allocated_nodespaces_sampling_activators) + filename = "inlinks-%s-from-%s.npz" % (self.spid, spid) + data = { + 'from_partition_id': spid, + 'from_ids': inlinks[0].get_value(borrow=True), + 'to_ids': inlinks[1].get_value(borrow=True), + 'weights': inlinks[2].get_value(borrow=True) if inlinks[2] else None, + 'inlink_type': inlinks[4] + } + if zipfile: + stream = io.BytesIO() + np.savez(stream, **data) + stream.seek(0) + zipfile.writestr(filename, stream.getvalue()) + else: + np.savez(os.path.join(base_path, filename), **data) + filename = "partition-%s.npz" % self.spid + data = { + 'allocated_nodes': allocated_nodes, + 'allocated_node_offsets': allocated_node_offsets, + 'allocated_elements_to_nodes': allocated_elements_to_nodes, + 'allocated_node_parents': allocated_node_parents, + 'allocated_nodespaces': allocated_nodespaces, + 'w_data': w.data, + 'w_indices': w.indices, + 'w_indptr': w.indptr, + 'a': a, + 'g_bias': g_bias, + 'g_factor': g_factor, + 'g_threshold': g_threshold, + 'g_amplification': g_amplification, + 'g_min': g_min, + 'g_max': g_max, + 'g_function_selector': g_function_selector, + 'g_expect': g_expect, + 'g_countdown': g_countdown, + 'g_wait': g_wait, + 'n_function_selector': n_function_selector, + 'sizeinformation': sizeinformation, + 'allocated_elements_to_activators': allocated_elements_to_activators, + 'allocated_nodespaces_por_activators': allocated_nodespaces_por_activators, + 'allocated_nodespaces_ret_activators': allocated_nodespaces_ret_activators, + 'allocated_nodespaces_sub_activators': allocated_nodespaces_sub_activators, + 'allocated_nodespaces_sur_activators': allocated_nodespaces_sur_activators, + 'allocated_nodespaces_cat_activators': allocated_nodespaces_cat_activators, + 'allocated_nodespaces_exp_activators': allocated_nodespaces_exp_activators, + 'allocated_nodespaces_sampling_activators': allocated_nodespaces_sampling_activators + } + if zipfile: + stream = io.BytesIO() + np.savez(stream, **data) + stream.seek(0) + zipfile.writestr(filename, stream.getvalue()) + else: + np.savez(os.path.join(base_path, filename), **data) def load_data(self, nodes_data): """Load the node net from a file""" diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 72708050..c8e79a41 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -22,6 +22,7 @@ import os import sys +import zipfile import threading try: @@ -211,10 +212,13 @@ def run(self): self.profiler.disable() for uid, interval in nodenets_to_save: - savepath = os.path.join(AUTOSAVE_PATH, "%s_%d" % (uid, interval)) - os.makedirs(savepath, exist_ok=True) - logging.getLogger("system").info("Auto-saving nodenet %s at step %d (interval %d)" % (uid, nodenets[uid].current_step, interval)) - nodenets[uid].save(base_path=savepath) + if uid in nodenets: + net = nodenets[uid] + savefile = os.path.join(AUTOSAVE_PATH, "%s_%d.zip" % (uid, interval)) + logging.getLogger("system").info("Auto-saving nodenet %s at step %d (interval %d)" % (uid, net.current_step, interval)) + zipobj = zipfile.ZipFile(savefile, 'w', zipfile.ZIP_STORED) + net.save(zipfile=zipobj) + zipobj.close() calc_time = datetime.now() - start if step.total_seconds() > 0: diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 98e845c7..92c962f3 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -533,11 +533,80 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 assert neuron.get_slot('gen').get_links() == [] -def test_runtime_nodenet_autosave(runtime, test_nodenet, resourcepath): +@pytest.mark.engine("dict_engine") +def test_runtime_autosave_dict(runtime, test_nodenet, resourcepath): + import os + import zipfile + from time import sleep + runtime.set_runner_condition(test_nodenet, steps=101) + runtime.start_nodenetrunner(test_nodenet) + count = 0 + while runtime.nodenets[test_nodenet].is_active: + sleep(.1) + count += 1 + assert count < 20 # quit if not done after 2 sec + filename = os.path.join(resourcepath, "nodenets", "__autosave__", "%s_%d.zip" % (test_nodenet, 100)) + assert os.path.isfile(filename) + with zipfile.ZipFile(filename, 'r') as archive: + assert set(archive.namelist()) == {"nodenet.json"} + + +@pytest.mark.engine("theano_engine") +def test_runtime_autosave_theano(runtime, test_nodenet, resourcepath): import os + import zipfile from time import sleep + with open(os.path.join(resourcepath, "nodetypes", "Source.py"), 'w') as fp: + fp.write("""nodetype_definition = { + "flow_module": True, + "implementation": "python", + "name": "Source", + "init_function_name": "source_init", + "run_function_name": "source", + "inputs": [], + "outputs": ["X"] +} + +def source_init(netapi, node, parameters): + import numpy as np + w_array = np.random.rand(8).astype(netapi.floatX) + node.set_theta("weights", w_array) + +def source(netapi, node, parameters): + return node.get_theta("weights") +""") + with open(os.path.join(resourcepath, "nodetypes", "Target.py"), 'w') as fp: + fp.write("""nodetype_definition = { + "flow_module": True, + "implementation": "python", + "name": "Target", + "run_function_name": "target", + "inputs": ["X"], + "outputs": [], + "inputdims": [1] +} + +def target(X, netapi, node, parameters): + node.set_state("incoming", X.get_value()) +""") + + runtime.reload_code() + nodenet = runtime.nodenets[test_nodenet] + netapi = nodenet.netapi + source = netapi.create_node("Source", None, "Source") + target = netapi.create_node("Target", None, "Target") + netapi.flow(source, "X", target, "X") + neuron = netapi.create_node("Neuron", None, "Neuron") + netapi.link(neuron, 'gen', target, 'sub') + neuron.activation = 1 runtime.set_runner_condition(test_nodenet, steps=101) runtime.start_nodenetrunner(test_nodenet) + count = 0 while runtime.nodenets[test_nodenet].is_active: sleep(.1) - assert os.path.isfile(os.path.join(resourcepath, "nodenets", "__autosave__", "%s_%d" % (test_nodenet, 100), "nodenet.json")) + count += 1 + assert count < 20 # quit if not done after 2 sec + filename = os.path.join(resourcepath, "nodenets", "__autosave__", "%s_%d.zip" % (test_nodenet, 100)) + assert os.path.isfile(filename) + with zipfile.ZipFile(filename, 'r') as archive: + assert set(archive.namelist()) == {"nodenet.json", "flowgraph.pickle", "partition-000.npz", "%s_numpystate.npz" % target.uid, "%s_thetas.npz" % source.uid} From 25ae27fe61ec00c6eacd05be95305fb14365a4a8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 21 Jun 2017 16:34:23 +0200 Subject: [PATCH 845/945] fix tests --- conftest.py | 5 +++-- micropsi_server/tests/test_json_api.py | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/conftest.py b/conftest.py index 4c6d188b..79431b1d 100644 --- a/conftest.py +++ b/conftest.py @@ -113,11 +113,12 @@ def pytest_runtest_setup(item): else: os.remove(path) + os.mkdir(os.path.join(testpath, 'worlds')) + os.mkdir(os.path.join(testpath, 'nodenets')) + os.mkdir(os.path.join(testpath, 'nodenets', '__autosave__')) os.mkdir(os.path.join(testpath, 'nodetypes')) os.mkdir(os.path.join(testpath, 'recipes')) os.mkdir(os.path.join(testpath, 'operations')) - os.mkdir(os.path.join(testpath, 'nodenets')) - os.mkdir(os.path.join(testpath, 'worlds')) os.mkdir(os.path.join(testpath, 'nodetypes', 'Test')) open(os.path.join(testpath, 'nodetypes', 'Test', '__init__.py'), 'w').close() micropsi_runtime.reload_code() diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index db3a143d..95989b3f 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -149,10 +149,6 @@ def test_start_calculation(app, default_nodenet): def test_start_calculation_with_condition(app, default_nodenet): import time app.set_auth() - app.post_json('/rpc/set_runner_properties', params={ - 'timestep': 10, - 'factor': 1 - }) response = app.post_json('/rpc/set_runner_condition', params={ 'nodenet_uid': default_nodenet, 'steps': '2' From 67ab4829c8e000d19715dd1acc0f3744213f935b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 21 Jun 2017 23:37:33 +0200 Subject: [PATCH 846/945] autosave tests test end-to-end --- .../tests/test_runtime_nodenet_basics.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 92c962f3..a82824d2 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -536,9 +536,11 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 @pytest.mark.engine("dict_engine") def test_runtime_autosave_dict(runtime, test_nodenet, resourcepath): import os + import json import zipfile + import tempfile from time import sleep - runtime.set_runner_condition(test_nodenet, steps=101) + runtime.set_runner_condition(test_nodenet, steps=100) runtime.start_nodenetrunner(test_nodenet) count = 0 while runtime.nodenets[test_nodenet].is_active: @@ -549,12 +551,22 @@ def test_runtime_autosave_dict(runtime, test_nodenet, resourcepath): assert os.path.isfile(filename) with zipfile.ZipFile(filename, 'r') as archive: assert set(archive.namelist()) == {"nodenet.json"} + tmp = tempfile.TemporaryDirectory() + archive.extractall(tmp.name) + with open(os.path.join(tmp.name, "nodenet.json"), 'r') as fp: + restored = json.load(fp) + original = runtime.nodenets[test_nodenet].export_json() + # step and runner_conditions might differ + for key in ['nodes', 'links', 'modulators', 'uid', 'name', 'owner', 'world', 'worldadapter', 'version', 'monitors', 'nodespaces']: + assert restored[key] == original[key] @pytest.mark.engine("theano_engine") def test_runtime_autosave_theano(runtime, test_nodenet, resourcepath): import os + import tempfile import zipfile + import numpy as np from time import sleep with open(os.path.join(resourcepath, "nodetypes", "Source.py"), 'w') as fp: fp.write("""nodetype_definition = { @@ -599,7 +611,7 @@ def target(X, netapi, node, parameters): neuron = netapi.create_node("Neuron", None, "Neuron") netapi.link(neuron, 'gen', target, 'sub') neuron.activation = 1 - runtime.set_runner_condition(test_nodenet, steps=101) + runtime.set_runner_condition(test_nodenet, steps=100) runtime.start_nodenetrunner(test_nodenet) count = 0 while runtime.nodenets[test_nodenet].is_active: @@ -610,3 +622,16 @@ def target(X, netapi, node, parameters): assert os.path.isfile(filename) with zipfile.ZipFile(filename, 'r') as archive: assert set(archive.namelist()) == {"nodenet.json", "flowgraph.pickle", "partition-000.npz", "%s_numpystate.npz" % target.uid, "%s_thetas.npz" % source.uid} + from micropsi_core.nodenet.theano_engine.theano_nodenet import TheanoNodenet + tmp = tempfile.TemporaryDirectory() + archive.extractall(tmp.name) + net = TheanoNodenet(tmp.name, "restored", uid=test_nodenet, native_modules=runtime.native_modules) + net.load() + nsource = net.netapi.get_node(source.uid) + ntarget = net.netapi.get_node(target.uid) + nneuron = net.netapi.get_node(neuron.uid) + assert nsource.name == "Source" + assert nsource.outputmap == {'X': {(ntarget.uid, 'X')}} + assert np.all(nsource.get_theta("weights").get_value() == source.get_theta("weights").get_value()) + assert np.all(ntarget.get_state("incoming") == target.get_state("incoming")) + assert nneuron.get_gate('gen').get_links()[0].target_node == ntarget From f42cd854f33dca8af3206da19eddb66203bf4e95 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 22 Jun 2017 18:12:49 +0200 Subject: [PATCH 847/945] make pytest-coverage work with nodenet-repo --- .coveragerc | 3 ++- conftest.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index fe11b7d1..50f806cf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,5 +3,6 @@ omit = */tests/* micropsi_server/bottle.py */conftest.py - micropsi_core/world/island/png.py micropsi_server/minidoc.py + */_test_* + */_demo_* diff --git a/conftest.py b/conftest.py index 7973074c..314ee7a9 100644 --- a/conftest.py +++ b/conftest.py @@ -53,6 +53,7 @@ def pytest_cmdline_main(config): implementation will invoke the configure hooks and runtest_mainloop. """ if config.getoption('agents'): config.args = [orig_agent_dir] + config._inicache['python_functions'] = [] config.addinivalue_line('python_files', '*.py') config.addinivalue_line('python_functions', '_test*') config.addinivalue_line('norecursedirs', 'experiments') From 864e9541ae3dba2fb25bdfb5d98762988bfc1829 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 22 Jun 2017 20:15:41 +0200 Subject: [PATCH 848/945] fix new_nodenet behaviour save_nodenet writes updated info to the runtime's metadata, nodenet.save() doesn't. Thus, the world and worldadapter config was getting lost here on a revert --- micropsi_core/runtime.py | 3 +-- micropsi_core/tests/test_runtime_nodenet_basics.py | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index bffc3b9a..f163b61f 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -593,8 +593,7 @@ def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template= if world_uid and worldadapter: set_nodenet_properties(uid, worldadapter=worldadapter, world_uid=world_uid, worldadapter_config=worldadapter_config) - - nodenets[uid].save() + save_nodenet(uid) return True, data['uid'] diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 76598b6d..8fd626c6 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -22,9 +22,13 @@ def prepare(runtime, test_nodenet): return net, netapi, source, register -def test_new_nodenet(runtime, test_nodenet, resourcepath, engine): - success, nodenet_uid = runtime.new_nodenet("Test_Nodenet", engine=engine, worldadapter="Default", owner="tester") +def test_new_nodenet(runtime, test_nodenet, default_world, resourcepath, engine): + success, nodenet_uid = runtime.new_nodenet("Test_Nodenet", engine=engine, world_uid=default_world, worldadapter="Default", owner="tester") assert success + runtime.revert_nodenet(nodenet_uid) + nodenet = runtime.get_nodenet(nodenet_uid) + assert nodenet.world == default_world + assert nodenet.worldadapter == "Default" assert nodenet_uid != test_nodenet assert runtime.get_available_nodenets("tester")[nodenet_uid].name == "Test_Nodenet" n_path = os.path.join(resourcepath, runtime.NODENET_DIRECTORY, nodenet_uid, "nodenet.json") From beae611cf25ce95bb8d6967a1918eeb85468325f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 23 Jun 2017 12:43:49 +0200 Subject: [PATCH 849/945] default the `owner` param to `admin` --- micropsi_core/_runtime_api_world.py | 2 +- micropsi_core/runtime.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/_runtime_api_world.py b/micropsi_core/_runtime_api_world.py index d145f67c..e0ddc5f6 100644 --- a/micropsi_core/_runtime_api_world.py +++ b/micropsi_core/_runtime_api_world.py @@ -105,7 +105,7 @@ def set_worldagent_properties(world_uid, uid, position=None, orientation=None, n return micropsi_core.runtime.worlds[world_uid].set_agent_properties(uid, position, orientation, name, parameters) -def new_world(world_name, world_type, owner="", config={}): +def new_world(world_name, world_type, owner="admin", config={}): """Creates a new world and registers it. Arguments: diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index f163b61f..f6651fe6 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -556,7 +556,7 @@ def unload_nodenet(nodenet_uid): return True -def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template=None, owner="", world_uid=None, use_modulators=True, worldadapter_config={}): +def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template=None, owner="admin", world_uid=None, use_modulators=True, worldadapter_config={}): """Creates a new node net manager and registers it. Arguments: From e3a81f2ad6d575fa3de852e2de4d24e7a2b2ec41 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 23 Jun 2017 18:43:09 +0200 Subject: [PATCH 850/945] do not default missing nodetypes to comment ignore them, and upgrade the logging level to error --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 7 ++----- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 09ded675..c83f07d5 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -431,12 +431,9 @@ def merge_data(self, nodenet_data, keep_uids=False): data['uid'] = newuid uidmap[uid] = newuid if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) - data['parameters'] = { - 'comment': 'There was a %s node here' % data['type'] - } - data['type'] = 'Comment' + self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) invalid_nodes.append(uid) + continue self._nodes[newuid] = DictNode(self, **data) # merge in links diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 7839282d..813f8e27 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -694,12 +694,9 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only parent_uid = uidmap[data['parent_nodespace']] id_to_pass = None if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) - data['parameters'] = { - 'comment': 'There was a %s node here' % data['type'] - } - data['type'] = 'Comment' + self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) invalid_nodes.append(uid) + continue if native_module_instances_only: if data.get('flow_module') and data['type'] in self.native_module_definitions: node = FlowModule( From cdf9bc7b52b4495c879f94a280048d821f70e653 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 26 Jun 2017 19:01:12 +0200 Subject: [PATCH 851/945] fix two typos logger is the micropsi-logger, and nodes is not defined (!) --- micropsi_core/runtime.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 5be4fe9f..ab0af52d 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -995,7 +995,7 @@ def clone_nodes(nodenet_uid, node_uids, clonemode, nodespace=None, offset=[50, 5 if uid: uidmap[n.uid] = uid else: - logger.warning('Could not clone node: ' + uid) + logging.getLogger("system").warning('Could not clone node: ' + uid) for uid, l in copylinks.items(): source_uid = uidmap.get(l.source_node.uid, l.source_node.uid) @@ -1013,7 +1013,7 @@ def clone_nodes(nodenet_uid, node_uids, clonemode, nodespace=None, offset=[50, 5 for uid in followupnodes: result[uid] = nodenet.get_node(uid).get_data(include_links=True) - if len(result.keys()) or len(nodes) == 0: + if len(result.keys()) or len(node_uids) == 0: return True, result else: return False, "Could not clone nodes. See log for details." From a42f964aa33d9e9746420319c5ee6649d01765a9 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 14:49:23 +0200 Subject: [PATCH 852/945] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af87ba08..79a56985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ 0.11-alpha9 (unreleased) ========== + * Add adhoc-monitors for plotting simple scalar values + * Node states now support numpy data structrues + * Add autosave-functionality for nodenets + * Change the stepping order from `nodenet, world, sleep` to `nodenet, sleep, world` 0.10-alpha8 (2017-06-06) From c2b87609e9adbc2d534cce92e40d4f8ffe2ebf61 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 14:49:30 +0200 Subject: [PATCH 853/945] old changelog formatting --- CHANGELOG.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a56985..c7e715c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,20 +64,20 @@ 0.4-alpha2 (2015-06-05) ========== -* Introduced Comment nodes -* Introduced global modulators and Doernerian emotional model -* Introduced por/ret link decay -* Introduced recipes: python scripts that can be run from the nodenet editor and have netapi access -* Copy & paste functionality in nodenet editor -* Snap to grid in nodenet editor -* Nodenet editor setting for rendering links: always, for currently selected nodes, or never -* Nodenet editor can link multiple selected nodes at once -* Improved nodenet editor user experience when zoomed out -* Additional monitor types, including link weight monitors -* Display origin-gate & target-slot in link-sidebar - -* Introduced theano_engine, an engine for running nodenets on top of numpy/theano (requires additional configuration) -* Introduced Minecraft connectivity (requires additional configuration) + * Introduced Comment nodes + * Introduced global modulators and Doernerian emotional model + * Introduced por/ret link decay + * Introduced recipes: python scripts that can be run from the nodenet editor and have netapi access + * Copy & paste functionality in nodenet editor + * Snap to grid in nodenet editor + * Nodenet editor setting for rendering links: always, for currently selected nodes, or never + * Nodenet editor can link multiple selected nodes at once + * Improved nodenet editor user experience when zoomed out + * Additional monitor types, including link weight monitors + * Display origin-gate & target-slot in link-sidebar + + * Introduced theano_engine, an engine for running nodenets on top of numpy/theano (requires additional configuration) + * Introduced Minecraft connectivity (requires additional configuration) 0.3-alpha1 (2014-06-30) From cb22158402a4debda453d7e4f0be9b8ddb2de6a3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 14:57:47 +0200 Subject: [PATCH 854/945] readme formatting --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3ff73824..aa5eaf78 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,28 @@ For more information visit [micropsi.com](http://www.micropsi.com), for instance Prerequisites ----- -* Python3 (tested with 3.4.3 and 3.5.1) -* On Windows, we recommend downloading and installing [WinPython 3.4.3.7](http://winpython.github.io/) + * Python3 (tested with 3.4.3 and 3.5.1) + * On Windows, we recommend downloading and installing [WinPython 3.4.3.7](http://winpython.github.io/) Run on OS X or Linux: ----- -* Run `./run.sh` -* View in browser at [http://localhost:6543/](http://localhost:6543/) + * Run `./run.sh` + * View in browser at [http://localhost:6543/](http://localhost:6543/) Run on Windows: ----- -* Add the winpython folders `python-3.4.3` and `python-3.4.3\Scripts` to your PATH environment variable -* On the Windows command-line, "cd" to the microps2 folder and run `python start_micropsi_server.py` -* View in browser at [http://localhost:6543/](http://localhost:6543/) + * Add the winpython folders `python-3.4.3` and `python-3.4.3\Scripts` to your PATH environment variable + * On the Windows command-line, "cd" to the microps2 folder and run `python start_micropsi_server.py` + * View in browser at [http://localhost:6543/](http://localhost:6543/) Attribution ----- [micropsi2](https://github.com/joschabach/micropsi2) uses -* [bottle](https://github.com/defnull/bottle) -* [spock](https://github.com/nickelpro/spock) -* [paperjs](http://github.com/paperjs/paper.js) -* [theano](https://github.com/Theano/Theano) + * [bottle](https://github.com/defnull/bottle) + * [spock](https://github.com/nickelpro/spock) + * [paperjs](http://github.com/paperjs/paper.js) + * [theano](https://github.com/Theano/Theano) From eb1695402a574c3906ef73137b7944de2131ec9f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 14:57:56 +0200 Subject: [PATCH 855/945] add link to BDK documentation, add section about installing worlds --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aa5eaf78..8503e198 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ About ----- A Python implementation of the cognitive architecture MicroPsi. -For more information visit [micropsi.com](http://www.micropsi.com), for instance, the [publications ](http://www.micropsi.com/publications/publications.html) page. For a one-paper introduction see [The AEP Toolkit for Agent Design and Simulation](http://www.micropsi.com/publications/assets/BachVuineMates2003.pdf) +For more information visit [micropsi.com](http://www.micropsi.com), for instance, the [publications ](http://www.micropsi.com/publications/publications.html) page. For a one-paper introduction see [The AEP Toolkit for Agent Design and Simulation](http://www.micropsi.com/publications/assets/BachVuineMates2003.pdf). +You can also take a look at the [BDK Documentation](http://www.micropsi-industries.com/documentation/introduction) Prerequisites @@ -24,6 +25,14 @@ Run on Windows: * View in browser at [http://localhost:6543/](http://localhost:6543/) +Installing environments +----- + * [Download a zip-file of the Island World](http://micropsi.industries/tech/island_world.zip) + * Unzip in your `micropsi_code` folder + * Restart micropsi, or click `Reload code` + * You can find further environments on the [micropsi industries download page](http://www.micropsi-industries.com/download) + + Attribution ----- [micropsi2](https://github.com/joschabach/micropsi2) uses From bbd1b6b03afb3d36bf9067bb6aa5a62078c06b1e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 15:00:44 +0200 Subject: [PATCH 856/945] fix the links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8503e198..0c941536 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ About ----- A Python implementation of the cognitive architecture MicroPsi. -For more information visit [micropsi.com](http://www.micropsi.com), for instance, the [publications ](http://www.micropsi.com/publications/publications.html) page. For a one-paper introduction see [The AEP Toolkit for Agent Design and Simulation](http://www.micropsi.com/publications/assets/BachVuineMates2003.pdf). +For more information visit [cognitive-ai.com](http://http://cognitive-ai.com) or [micropsi.com](http://www.micropsi.com), or check the [publications ](http://cognitive-ai.com/publications/publications.html) page. For a one-paper introduction see [The AEP Toolkit for Agent Design and Simulation](http://cognitive-ai.com/publications/assets/BachVuineMates2003.pdf). You can also take a look at the [BDK Documentation](http://www.micropsi-industries.com/documentation/introduction) From 4624201ef4829ec666f56d372a8d94a1095568bc Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 15:04:41 +0200 Subject: [PATCH 857/945] linkfix, copy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c941536..768fb833 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ About ----- A Python implementation of the cognitive architecture MicroPsi. -For more information visit [cognitive-ai.com](http://http://cognitive-ai.com) or [micropsi.com](http://www.micropsi.com), or check the [publications ](http://cognitive-ai.com/publications/publications.html) page. For a one-paper introduction see [The AEP Toolkit for Agent Design and Simulation](http://cognitive-ai.com/publications/assets/BachVuineMates2003.pdf). +For more information visit [cognitive-ai.com](http://cognitive-ai.com), for instance the [publications ](http://cognitive-ai.com/publications/publications.html) page. For a one-paper introduction see [The AEP Toolkit for Agent Design and Simulation](http://cognitive-ai.com/publications/assets/BachVuineMates2003.pdf). You can also take a look at the [BDK Documentation](http://www.micropsi-industries.com/documentation/introduction) From 99bf6c3684ff0f946a5991ec3d2249f18f3d5316 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 15:21:44 +0200 Subject: [PATCH 858/945] release 0.11-alpha9 --- CHANGELOG.md | 3 ++- configuration.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e715c6..4b14744a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -0.11-alpha9 (unreleased) + +0.11-alpha9 (2017-06-26) ========== * Add adhoc-monitors for plotting simple scalar values * Node states now support numpy data structrues diff --git a/configuration.py b/configuration.py index eaed5add..11624ad2 100644 --- a/configuration.py +++ b/configuration.py @@ -51,7 +51,7 @@ def makedirs(path): warnings.warn('Can not read config from inifile %s' % configini) raise RuntimeError('Can not read config from inifile %s' % configini) -config['micropsi2']['version'] = "0.11-alpha9-dev" +config['micropsi2']['version'] = "0.11-alpha9" config['micropsi2']['apptitle'] = "MicroPsi" data_path = os.path.expanduser(config['micropsi2']['data_directory']) From 7c8fbabad694b8f86401b2760527c50c3ad13542 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 15:22:48 +0200 Subject: [PATCH 859/945] version bump for 0.12-alpha10 --- CHANGELOG.md | 4 ++++ configuration.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b14744a..bc80edc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ +0.12-alpha10 (unreleased) +========== + + 0.11-alpha9 (2017-06-26) ========== * Add adhoc-monitors for plotting simple scalar values diff --git a/configuration.py b/configuration.py index 11624ad2..1131edf9 100644 --- a/configuration.py +++ b/configuration.py @@ -51,7 +51,7 @@ def makedirs(path): warnings.warn('Can not read config from inifile %s' % configini) raise RuntimeError('Can not read config from inifile %s' % configini) -config['micropsi2']['version'] = "0.11-alpha9" +config['micropsi2']['version'] = "0.12-alpha10-dev" config['micropsi2']['apptitle'] = "MicroPsi" data_path = os.path.expanduser(config['micropsi2']['data_directory']) From 381ff77adaffa78689f1a14ad72a8da7fb0e47d8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 17:49:53 +0200 Subject: [PATCH 860/945] flow fix: there might be non-endpoints before an endpoint --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 79545e7d..624bea2f 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -962,7 +962,7 @@ def update_flow_graphs(self, node_uids=None): path = [] for uid in reversed(fullpath): if uid in endpoints and uid != enduid: - break + continue path.insert(0, uid) if path: graphs.append(path) From c61e2fb80c27195bbc35be4e2b4183934333263c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 27 Jun 2017 17:49:53 +0200 Subject: [PATCH 861/945] flow fix: there might be non-endpoints before an endpoint --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 79545e7d..624bea2f 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -962,7 +962,7 @@ def update_flow_graphs(self, node_uids=None): path = [] for uid in reversed(fullpath): if uid in endpoints and uid != enduid: - break + continue path.insert(0, uid) if path: graphs.append(path) From 2c2563fd79b7853705c9bde37b302de4509b57ac Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 29 Jun 2017 16:33:38 +0200 Subject: [PATCH 862/945] make runtime configurable, cleanup config/imports --- config.default.ini | 1 + conftest.py | 14 ++- micropsi_core/nodenet/recorder.py | 1 - micropsi_core/runtime.py | 154 ++++++++++++++-------------- micropsi_core/tests/test_runtime.py | 14 +-- micropsi_core/tools.py | 7 +- 6 files changed, 97 insertions(+), 94 deletions(-) diff --git a/config.default.ini b/config.default.ini index 53fe6e27..f9058b37 100644 --- a/config.default.ini +++ b/config.default.ini @@ -40,6 +40,7 @@ host = localhost # automatically save running nodenets every X steps: auto_save_intervals = 1000,10000,1000000 + [minecraft] # use your minecraft.net username with password, respective diff --git a/conftest.py b/conftest.py index 0b943250..f6e6126a 100644 --- a/conftest.py +++ b/conftest.py @@ -17,11 +17,13 @@ print("test data directory:", testpath) from micropsi_core import runtime as micropsi_runtime -from micropsi_core.runtime import cfg +from configuration import config as cfg orig_agent_dir = cfg['paths']['agent_directory'] orig_world_dir = cfg['paths']['world_directory'] +cfg['paths']['agent_directory'] = testpath +cfg['paths']['world_directory'] = testpath cfg['paths']['persistency_directory'] = testpath cfg['paths']['server_settings_path'] = os.path.join(testpath, 'server_cfg.json') cfg['paths']['usermanager_path'] = os.path.join(testpath, 'user-db.json') @@ -58,14 +60,16 @@ def pytest_cmdline_main(config): config.addinivalue_line('python_files', '*.py') config.addinivalue_line('python_functions', '_test*') config.addinivalue_line('norecursedirs', 'experiments') - micropsi_runtime.initialize(persistency_path=testpath, resource_path=orig_agent_dir, world_path=orig_world_dir) + cfg['paths']['agent_directory'] = orig_agent_dir + micropsi_runtime.initialize(config=cfg) elif config.getoption('worlds'): config.args = [orig_world_dir] config.addinivalue_line('python_functions', 'test_*') - micropsi_runtime.initialize(persistency_path=testpath, world_path=orig_world_dir, resource_path=testpath) + cfg['paths']['world_directory'] = orig_world_dir + micropsi_runtime.initialize(config=cfg) else: config.addinivalue_line('python_functions', 'test_*') - micropsi_runtime.initialize(persistency_path=testpath, world_path=testpath, resource_path=testpath) + micropsi_runtime.initialize(config=cfg) from micropsi_server.micropsi_app import usermanager usermanager.create_user('Pytest User', 'test', 'Administrator', uid='Pytest User') @@ -144,7 +148,7 @@ def set_logging_levels(): """ sets the logging levels of the default loggers back to WARNING """ logging.getLogger('system').setLevel(logging.WARNING) logging.getLogger('world').setLevel(logging.WARNING) - micropsi_runtime.cfg['logging']['level_agent'] = 'WARNING' + micropsi_runtime.runtime_config['logging']['level_agent'] = 'WARNING' @pytest.fixture(scope="session") diff --git a/micropsi_core/nodenet/recorder.py b/micropsi_core/nodenet/recorder.py index c0e6dbec..f353545b 100644 --- a/micropsi_core/nodenet/recorder.py +++ b/micropsi_core/nodenet/recorder.py @@ -15,7 +15,6 @@ pass from abc import ABCMeta, abstractmethod from micropsi_core import tools -from micropsi_core.runtime import PERSISTENCY_PATH, NODENET_DIRECTORY class Recorder(metaclass=ABCMeta): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index ab0af52d..9eeb15ad 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -6,48 +6,30 @@ maintains a set of users, worlds (up to one per user), and nodenets, and provides an interface to external clients """ -from micropsi_core._runtime_api_world import * -from micropsi_core._runtime_api_monitors import * -import re - __author__ = 'joscha' __date__ = '10.05.12' -from configuration import config as cfg - -from micropsi_core.nodenet import node_alignment -from micropsi_core import config -from micropsi_core.tools import Bunch -from micropsi_core.tools import post_mortem - +import re import os import sys +import time +import json +import signal +import logging import zipfile import threading -try: - import matplotlib - matplotlib.rcParams['webagg.port'] = 6545 - matplotlib.rcParams['webagg.open_in_browser'] = False - matplotlib.use('WebAgg') - - def plotter_initializer(): - from matplotlib import pyplot as plt - plt.show() - -except ImportError: - matplotlib = None - pass +from code import InteractiveConsole +from datetime import datetime, timedelta +from micropsi_core.config import ConfigurationManager -from micropsi_core import tools -import json -from datetime import datetime, timedelta -import time -import signal -import logging +from micropsi_core._runtime_api_world import * +from micropsi_core._runtime_api_monitors import * -from .micropsi_logger import MicropsiLogger +from micropsi_core.nodenet import node_alignment +from micropsi_core.micropsi_logger import MicropsiLogger +from micropsi_core.tools import Bunch, post_mortem, generate_uid NODENET_DIRECTORY = "nodenets" WORLD_DIRECTORY = "worlds" @@ -62,7 +44,8 @@ def plotter_initializer(): WORLD_PATH = None AUTOSAVE_PATH = None -configs = None +runtime_config = None +runner_config = None logger = None worlds = {} @@ -81,8 +64,6 @@ def plotter_initializer(): auto_save_intervals = None -from code import InteractiveConsole - class FileCacher(): """Cache the stdout text so we can analyze it before returning it""" @@ -151,7 +132,7 @@ class MicropsiRunner(threading.Thread): def __init__(self): threading.Thread.__init__(self) - if cfg['micropsi2'].get('profile_runner'): + if runtime_config['micropsi2'].get('profile_runner'): import cProfile self.profiler = cProfile.Profile() else: @@ -167,10 +148,10 @@ def run(self): if self.paused: self.state.wait() - if configs['runner_timestep'] > 1000: - step = timedelta(seconds=configs['runner_timestep'] / 1000) + if runner_config['runner_timestep'] > 1000: + step = timedelta(seconds=runner_config['runner_timestep'] / 1000) else: - step = timedelta(milliseconds=configs['runner_timestep']) + step = timedelta(milliseconds=runner_config['runner_timestep']) start = datetime.now() log = False @@ -313,7 +294,7 @@ def kill_runners(signal=None, frame=None): def set_logging_levels(logging_levels): for key in logging_levels: if key == 'agent': - cfg['logging']['level_agent'] = logging_levels[key] + runtime_config['logging']['level_agent'] = logging_levels[key] else: logger.set_logging_level(key, logging_levels[key]) return True @@ -340,7 +321,7 @@ def get_logging_levels(nodenet_uid=None): if key.startswith('agent') or key in ['world', 'system']: levels[key] = logging.getLevelName(logging.getLogger(key).getEffectiveLevel()) if 'agent' not in levels: - levels['agent'] = cfg['logging']['level_agent'] + levels['agent'] = runtime_config['logging']['level_agent'] return levels @@ -400,7 +381,7 @@ def load_nodenet(nodenet_uid): with nodenet_lock: - if cfg['micropsi2'].get('single_agent_mode'): + if runtime_config['micropsi2'].get('single_agent_mode'): # unload all other nodenets if single_agent_mode is selected for uid in list(nodenets.keys()): if uid != nodenet_uid: @@ -428,7 +409,7 @@ def load_nodenet(nodenet_uid): engine = data.get('engine') or 'dict_engine' - logger.register_logger("agent.%s" % nodenet_uid, cfg['logging']['level_agent']) + logger.register_logger("agent.%s" % nodenet_uid, runtime_config['logging']['level_agent']) params = { 'persistency_path': os.path.join(PERSISTENCY_PATH, NODENET_DIRECTORY, data.uid), @@ -590,7 +571,7 @@ def new_nodenet(nodenet_name, engine="dict_engine", worldadapter=None, template= nodenet_uid if successful, None if failure """ - uid = tools.generate_uid() + uid = generate_uid() data = dict( step=0, @@ -677,9 +658,9 @@ def set_runner_properties(timestep, factor): Argument: timestep: sets the calculation speed. """ - configs['runner_timestep'] = timestep + runner_config['runner_timestep'] = timestep runner['timestep'] = timestep - configs['runner_factor'] = int(factor) + runner_config['runner_factor'] = int(factor) runner['factor'] = int(factor) return True @@ -710,8 +691,8 @@ def remove_runner_condition(nodenet_uid): def get_runner_properties(): """Returns the speed that has been configured for the nodenet runner (in ms).""" return { - 'timestep': configs['runner_timestep'], - 'factor': configs['runner_factor'] + 'timestep': runner_config['runner_timestep'], + 'factor': runner_config['runner_factor'] } @@ -742,14 +723,14 @@ def step_nodenet(nodenet_uid): """ nodenet = get_nodenet(nodenet_uid) - if cfg['micropsi2'].get('profile_runner'): + if runtime_config['micropsi2'].get('profile_runner'): import cProfile profiler = cProfile.Profile() profiler.enable() nodenet.timed_step() - if cfg['micropsi2'].get('profile_runner'): + if runtime_config['micropsi2'].get('profile_runner'): profiler.disable() import pstats import io @@ -760,7 +741,7 @@ def step_nodenet(nodenet_uid): logging.getLogger("agent.%s" % nodenet_uid).debug(s.getvalue()) nodenet.update_monitors_and_recorders() - if nodenet.world and nodenet.current_step % configs['runner_factor'] == 0: + if nodenet.world and nodenet.current_step % runner_config['runner_factor'] == 0: worlds[nodenet.world].step() return nodenet.current_step @@ -832,7 +813,7 @@ def import_nodenet(string, owner=None): global nodenet_data import_data = json.loads(string) if 'uid' not in import_data: - import_data['uid'] = tools.generate_uid() + import_data['uid'] = generate_uid() else: if import_data['uid'] in nodenets: raise RuntimeError("An agent with this ID already exists.") @@ -1409,7 +1390,7 @@ def run_recipe(nodenet_uid, name, parameters): params[key] = parameters[key] if name in custom_recipes: func = custom_recipes[name]['function'] - if cfg['micropsi2'].get('profile_runner'): + if runtime_config['micropsi2'].get('profile_runner'): import cProfile profiler = cProfile.Profile() profiler.enable() @@ -1417,7 +1398,7 @@ def run_recipe(nodenet_uid, name, parameters): ret = func(netapi, **params) if ret: result.update(ret) - if cfg['micropsi2'].get('profile_runner'): + if runtime_config['micropsi2'].get('profile_runner'): profiler.disable() import pstats import io @@ -1614,7 +1595,7 @@ def load_definitions(): world_data = crawl_definition_files(path=os.path.join(PERSISTENCY_PATH, WORLD_DIRECTORY), datatype="world") if not world_data: # create a default world for convenience. - uid = tools.generate_uid() + uid = generate_uid() filename = os.path.join(PERSISTENCY_PATH, WORLD_DIRECTORY, uid + '.json') world_data[uid] = Bunch(uid=uid, name="default", version=1, filename=filename, owner="admin", world_type="DefaultWorld") with open(filename, 'w+', encoding="utf-8") as fp: @@ -1898,45 +1879,62 @@ def reload_code(): def runtime_info(): return { - "version": cfg['micropsi2']['version'], + "version": runtime_config['micropsi2']['version'], "persistency_directory": PERSISTENCY_PATH, "agent_directory": RESOURCE_PATH, "world_directory": WORLD_PATH } -def initialize(persistency_path=None, resource_path=None, world_path=None, autosave_path=None): - global PERSISTENCY_PATH, RESOURCE_PATH, WORLD_PATH, AUTOSAVE_PATH, configs, logger, runner, initialized, auto_save_intervals +def initialize(config=None): + global PERSISTENCY_PATH, RESOURCE_PATH, WORLD_PATH, AUTOSAVE_PATH + global runtime_config, runner_config, logger, runner, initialized, auto_save_intervals + + if config is None: + from configuration import config - PERSISTENCY_PATH = persistency_path or cfg['paths']['persistency_directory'] - RESOURCE_PATH = resource_path or cfg['paths']['agent_directory'] - WORLD_PATH = world_path or cfg['paths']['world_directory'] + runtime_config = config + + PERSISTENCY_PATH = config['paths']['persistency_directory'] + RESOURCE_PATH = config['paths']['agent_directory'] + WORLD_PATH = config['paths']['world_directory'] sys.path.append(WORLD_PATH) - configs = config.ConfigurationManager(cfg['paths']['server_settings_path']) + runner_config = ConfigurationManager(config['paths']['server_settings_path']) # create autosave-dir if not exists: - auto_save_intervals = cfg['micropsi2'].get('auto_save_intervals') + auto_save_intervals = config['micropsi2'].get('auto_save_intervals') if auto_save_intervals is not None: - auto_save_intervals = sorted([int(x) for x in cfg['micropsi2']['auto_save_intervals'].split(',')], reverse=True) - AUTOSAVE_PATH = autosave_path or os.path.join(PERSISTENCY_PATH, "nodenets", "__autosave__") + auto_save_intervals = sorted([int(x) for x in config['micropsi2']['auto_save_intervals'].split(',')], reverse=True) + AUTOSAVE_PATH = os.path.join(PERSISTENCY_PATH, "nodenets", "__autosave__") os.makedirs(AUTOSAVE_PATH, exist_ok=True) # bring up plotting infrastructure - if matplotlib is not None: + try: + import matplotlib + matplotlib.rcParams['webagg.port'] = 6545 + matplotlib.rcParams['webagg.open_in_browser'] = False + matplotlib.use('WebAgg') + + def plotter_initializer(): + from matplotlib import pyplot as plt + plt.show() + plt_thread = threading.Thread(target=plotter_initializer, args=(), daemon=True) plt_thread.start() + except ImportError: + pass if logger is None: logger = MicropsiLogger({ - 'system': cfg['logging']['level_system'], - 'world': cfg['logging']['level_world'] - }, cfg['logging'].get('logfile')) + 'system': config['logging']['level_system'], + 'world': config['logging']['level_world'] + }, config['logging'].get('logfile')) try: import theano - precision = cfg['theano']['precision'] + precision = config['theano']['precision'] if precision == "32": theano.config.floatX = "float32" elif precision == "64": @@ -1944,7 +1942,7 @@ def initialize(persistency_path=None, resource_path=None, world_path=None, autos else: # pragma: no cover logging.getLogger("system").warning("Unsupported precision value from configuration: %s, falling back to float64", precision) theano.config.floatX = "float64" - cfg['theano']['precision'] = "64" + config['theano']['precision'] = "64" except ImportError: pass @@ -1959,14 +1957,14 @@ def initialize(persistency_path=None, resource_path=None, world_path=None, autos # initialize runners # Initialize the threads for the continuous calculation of nodenets and worlds - if 'runner_timestep' not in configs: - configs['runner_timestep'] = 10 - configs.save_configs() - if 'runner_factor' not in configs: - configs['runner_factor'] = 1 - configs.save_configs() - - set_runner_properties(configs['runner_timestep'], configs['runner_factor']) + if 'runner_timestep' not in runner_config: + runner_config['runner_timestep'] = 10 + runner_config.save_configs() + if 'runner_factor' not in runner_config: + runner_config['runner_factor'] = 1 + runner_config.save_configs() + + set_runner_properties(runner_config['runner_timestep'], runner_config['runner_factor']) runner['running'] = True if runner.get('runner') is None: diff --git a/micropsi_core/tests/test_runtime.py b/micropsi_core/tests/test_runtime.py index 5fead99e..bc256113 100644 --- a/micropsi_core/tests/test_runtime.py +++ b/micropsi_core/tests/test_runtime.py @@ -15,7 +15,7 @@ def test_set_logging_level(runtime): runtime.set_logging_levels({'system': 'DEBUG', 'world': 'DEBUG', 'agent': 'DEBUG'}) assert logging.getLogger('system').getEffectiveLevel() == logging.DEBUG assert logging.getLogger('world').getEffectiveLevel() == logging.DEBUG - assert runtime.cfg['logging']['level_agent'] == 'DEBUG' + assert runtime.runtime_config['logging']['level_agent'] == 'DEBUG' def test_get_logging_levels(runtime): @@ -52,12 +52,12 @@ def test_nodenet_specific_loggers(runtime): def test_single_agent_mode(runtime): - mode = runtime.cfg['micropsi2'].get('single_agent_mode') - runtime.cfg['micropsi2'].update({'single_agent_mode': '1'}) + mode = runtime.runtime_config['micropsi2'].get('single_agent_mode') + runtime.runtime_config['micropsi2'].update({'single_agent_mode': '1'}) res, uid1 = runtime.new_nodenet("test1") res, uid2 = runtime.new_nodenet("test2") assert uid1 not in runtime.nodenets - runtime.cfg['micropsi2'].update({'single_agent_mode': mode}) + runtime.runtime_config['micropsi2'].update({'single_agent_mode': mode}) def test_unregister_logger(runtime): @@ -145,8 +145,8 @@ def test_get_links_for_nodes(runtime, test_nodenet, node): def test_create_nodenet_from_template(runtime, test_nodenet, node, engine): - mode = runtime.cfg['micropsi2'].get('single_agent_mode') - runtime.cfg['micropsi2'].update({'single_agent_mode': '1'}) + mode = runtime.runtime_config['micropsi2'].get('single_agent_mode') + runtime.runtime_config['micropsi2'].update({'single_agent_mode': '1'}) api = runtime.nodenets[test_nodenet].netapi node1 = api.get_node(node) node2 = api.create_node("Neuron", None, "node2") @@ -159,7 +159,7 @@ def test_create_nodenet_from_template(runtime, test_nodenet, node, engine): assert len(n['links']['gen']) == 2 else: assert n['name'] == node2.name - runtime.cfg['micropsi2'].update({'single_agent_mode': mode}) + runtime.runtime_config['micropsi2'].update({'single_agent_mode': mode}) def test_export_json_does_not_send_duplicate_links(runtime, test_nodenet): diff --git a/micropsi_core/tools.py b/micropsi_core/tools.py index 54c45b0e..c7aea629 100644 --- a/micropsi_core/tools.py +++ b/micropsi_core/tools.py @@ -12,17 +12,18 @@ import errno import os import sys -from configuration import config as cfg try: import ipdb as pdb except ImportError: import pdb + def post_mortem(): """ if desired, point a debugger to the origin of the last exception """ - if cfg['micropsi2'].get('on_exception') == 'debug': + from micropsi_core.runtime import runtime_config + if runtime_config['micropsi2'].get('on_exception') == 'debug': exception_type, exception, tb = sys.exc_info() - print('\033[01m\033[31m%s: \033[32m%s\033[0m' % (exception_type.__name__, exception)) + print('\033[01m\033[31m%s: \033[32m%s\033[0m' % (exception_type.__name__, exception)) pdb.post_mortem(tb) From db5f6dd1774db0b653a4360c582ef42ecd0c585c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 29 Jun 2017 17:57:35 +0200 Subject: [PATCH 863/945] make the webagg port configurable --- config.default.ini | 3 +++ micropsi_core/runtime.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config.default.ini b/config.default.ini index f9058b37..13773886 100644 --- a/config.default.ini +++ b/config.default.ini @@ -40,6 +40,9 @@ host = localhost # automatically save running nodenets every X steps: auto_save_intervals = 1000,10000,1000000 +# port for the webagg backend of matplotlib. +webagg_port = 6545 + [minecraft] diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 9eeb15ad..bffc06c2 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1913,7 +1913,7 @@ def initialize(config=None): # bring up plotting infrastructure try: import matplotlib - matplotlib.rcParams['webagg.port'] = 6545 + matplotlib.rcParams['webagg.port'] = int(config['micropsi2'].get('webagg_port', 6545)) matplotlib.rcParams['webagg.open_in_browser'] = False matplotlib.use('WebAgg') From f454cdd989e54266636f9806e996505df9265a98 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 5 Jul 2017 17:09:39 +0200 Subject: [PATCH 864/945] recreate the worldadapter-instance if the config is changed --- micropsi_core/runtime.py | 2 +- micropsi_core/tests/test_worldadapters.py | 10 ++++++++++ micropsi_core/world/worldadapter.py | 8 ++++++++ micropsi_server/micropsi_app.py | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index bffc06c2..c90f3764 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -619,7 +619,7 @@ def set_nodenet_properties(nodenet_uid, nodenet_name=None, worldadapter=None, wo nodenet = get_nodenet(nodenet_uid) if world_uid == '': world_uid = None - if nodenet.world and (nodenet.world != world_uid or nodenet.worldadapter != worldadapter): + if nodenet.world and (nodenet.world != world_uid or nodenet.worldadapter != worldadapter or worldadapter_config != nodenet.worldadapter_instance.config): worlds[nodenet.world].unregister_nodenet(nodenet.uid) nodenet.world = None nodenet.worldadapter_instance = None diff --git a/micropsi_core/tests/test_worldadapters.py b/micropsi_core/tests/test_worldadapters.py index 9c541d74..cda6f3ac 100644 --- a/micropsi_core/tests/test_worldadapters.py +++ b/micropsi_core/tests/test_worldadapters.py @@ -199,3 +199,13 @@ def update_data_sources_and_targets(self): assert adapter.get_datasource_value("some_setting") == 23 assert adapter.some_setting == 23 assert adapter.other_setting == 42 + + +def test_worldadapter_update_config(default_world, default_nodenet): + runtime.set_nodenet_properties(default_nodenet, worldadapter="Default", world_uid=default_world) + runtime.save_nodenet(default_nodenet) + assert runtime.nodenets[default_nodenet].worldadapter_instance.foo == 'bar' + runtime.set_nodenet_properties(default_nodenet, worldadapter="Default", world_uid=default_world, worldadapter_config={'foo': 'changed'}) + assert runtime.nodenets[default_nodenet].worldadapter_instance.foo == 'changed' + assert runtime.nodenets[default_nodenet].worldadapter_instance.config['foo'] == 'changed' + assert runtime.worlds[default_world].agents[default_nodenet].foo == 'changed' diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index ec8115b5..df4c4bde 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -174,6 +174,14 @@ class Default(WorldAdapter): """ A default Worldadapter, that provides example-datasources and -targets """ + @classmethod + def get_config_options(cls): + return [ + {'name': 'foo', + 'description': 'does nothing', + 'default': 'bar'} + ] + def __init__(self, world, uid=None, config={}, **data): super().__init__(world, uid=uid, config=config, **data) self.datasources = dict((s, 0) for s in ['static_on', 'random', 'static_off']) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 897bd12f..98a28e83 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1605,7 +1605,7 @@ def main(host=None, port=None): server = 'wsgiref' kwargs = {} - run(micropsi_app, host=host, port=port, quiet=True, server=server, **kwargs) + run(micropsi_app, host=host, port=port, quiet=False, server=server, **kwargs) if __name__ == "__main__": From 4c11ca7d5507e6853aee1b6fbd46e55775f98b90 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 10 Jul 2017 16:16:37 +0200 Subject: [PATCH 865/945] add missing vars --- micropsi_core/runtime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index c90f3764..6a9a8e53 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -49,7 +49,9 @@ logger = None worlds = {} +world_data = {} nodenets = {} +nodenet_data = {} native_modules = {} custom_recipes = {} From 34d7af7f305de87c140f38b7c139dcbb604d2531 Mon Sep 17 00:00:00 2001 From: cknd Date: Thu, 13 Jul 2017 17:25:13 +0200 Subject: [PATCH 866/945] check for matching input and inputdim arguments --- micropsi_core/runtime.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 6a9a8e53..309f6cc6 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1726,7 +1726,7 @@ def parse_native_module_file(path): category = os.path.relpath(os.path.dirname(path), start=base_path) if category == '.': category = '' - moduledef = module.nodetype_definition + moduledef = nodedef_sanity_check(module.nodetype_definition) moduledef['path'] = path moduledef['category'] = category if moduledef['name'] in native_modules: @@ -1736,6 +1736,18 @@ def parse_native_module_file(path): post_mortem() return "%s when importing nodetype file %s: %s" % (e.__class__.__name__, relpath, str(e)) +def nodedef_sanity_check(nodetype_definition): + """ catch some common errors in nodetype definitions """ + nd = nodetype_definition + + if nd.get('flow_module', False): + # chedck for mismatch between nr of inputdims and nr of inputs + n_in = len(nd.get('inputs', [])) + n_indims = len(nd.get('inputdims', [])) + if n_in != n_indims: + raise Exception('Node takes %s inputs but %s inputdims have been given' % (n_in, n_indims)) + + return nodetype_definition def parse_recipe_or_operations_file(path, mode, category_overwrite=False): global custom_recipes From 4d7b3b625239483743a3d86912b938ffbc673556 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 24 Jul 2017 12:01:42 +0200 Subject: [PATCH 867/945] Make sure all mixins can get notified of runner states --- micropsi_core/world/worldadapter.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index df4c4bde..7feb5a0d 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -59,6 +59,15 @@ def write_to_world(self): def read_from_world(self): pass # pragma: no cover + def on_simulation_started(self): + pass # pragma: no cover + + def on_simulation_paused(self): + pass # pragma: no cover + + def shutdown(self): + pass # pragma: no cover + class WorldAdapter(WorldObject, metaclass=ABCMeta): """Transmits data between agent and environment. From 478ac813ac5c08675587e3f9693562394b2cce93 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 24 Jul 2017 20:06:35 +0200 Subject: [PATCH 868/945] =?UTF-8?q?Preparing=20for=20vision:=20assert=20fl?= =?UTF-8?q?oats,=20don=E2=80=99t=20convert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- micropsi_core/world/worldadapter.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 7feb5a0d..4f82c12e 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -357,17 +357,20 @@ def set_datatarget_feedback_value(self, key, value): def set_flow_datasource(self, name, values): """Set the values of the given flow_datasource """ - values = np.array(np.reshape(values, self.flow_datasources[name].shape), dtype=self.floatX) + assert values.dtype == self.floatX + assert self.flow_datasources[name].shape == values.shape self.flow_datasources[name] = values def add_to_flow_datatarget(self, name, values): """Add the given values to the given flow_datatarget """ - values = np.array(np.reshape(values, self.flow_datatargets[name].shape), dtype=self.floatX) + assert values.dtype == self.floatX + assert self.flow_datasources[name].shape == values.shape self.flow_datatargets[name] += values def set_flow_datatarget_feedback(self, name, values): """Set the values of the given flow_datatarget_feedback """ - values = np.array(np.reshape(values, self.flow_datatarget_feedbacks[name].shape), dtype=self.floatX) + assert values.dtype == self.floatX + assert self.flow_datasources[name].shape == values.shape self.flow_datatarget_feedbacks[name] = values def set_datasource_values(self, values): From 3c603f40cdf840405236bdf33d5e29861385528a Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Mon, 24 Jul 2017 20:28:06 +0200 Subject: [PATCH 869/945] Flow module tests used to be naughty floatX-wise --- micropsi_core/tests/test_flowmodules.py | 10 +++++----- micropsi_core/tests/test_worldadapters.py | 10 +++++----- micropsi_core/world/worldadapter.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index dc482c53..1a809871 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -30,7 +30,7 @@ def prepare(runtime, test_nodenet, default_world, resourcepath, wa_class=None): def out12345(netapi, node, parameters): import numpy as np assert parameters['default_test'] == 'defaultvalue' - return np.asarray([1,2,3,4,5]) + return np.asarray([1,2,3,4,5]).astype(netapi.floatX) """) with open(os.path.join(foodir, "Double.py"), 'w') as fp: @@ -225,9 +225,9 @@ def __init__(self, world, **kwargs): def update_data_sources_and_targets(self): for key in self.flow_datatargets: - self.flow_datatarget_feedbacks[key] = np.copy(self.flow_datatargets[key]) + self.flow_datatarget_feedbacks[key] = np.copy(self.flow_datatargets[key]).astype(self.floatX) for key in self.flow_datasources: - self.flow_datasources[key] = np.random.rand(len(self.flow_datasources[key])) + self.flow_datasources[key] = np.random.rand(len(self.flow_datasources[key])).astype(self.floatX) """) nodenet = runtime.nodenets[test_nodenet] @@ -986,7 +986,7 @@ def test_connect_flow_modules_to_structured_flow_datasource(runtime, test_nodene sources = np.zeros((6), dtype=nodenet.numpyfloatX) sources[:] = np.random.randn(*sources.shape) worldadapter.set_flow_datasource('vision', sources) - worldadapter.set_flow_datasource('start', np.asarray([0.73])) + worldadapter.set_flow_datasource('start', np.asarray([0.73]).astype(nodenet.numpyfloatX)) double = netapi.create_node("Double", None, "Double") netapi.flow('worldadapter', 'vision', double, 'inputs') @@ -1013,7 +1013,7 @@ def test_connect_flow_modules_to_structured_flow_datasource(runtime, test_nodene sources = np.zeros((6), dtype=nodenet.numpyfloatX) sources[:] = np.random.randn(*sources.shape) worldadapter.set_flow_datasource('vision', sources) - worldadapter.set_flow_datasource('start', np.asarray([0.64])) + worldadapter.set_flow_datasource('start', np.asarray([0.64]).astype(nodenet.numpyfloatX)) runtime.step_nodenet(test_nodenet) assert np.all(worldadapter.get_flow_datatarget_feedback('motor') == np.zeros(6)) assert np.allclose(worldadapter.get_flow_datatarget_feedback('stop'), [0.64]) diff --git a/micropsi_core/tests/test_worldadapters.py b/micropsi_core/tests/test_worldadapters.py index cda6f3ac..b515923b 100644 --- a/micropsi_core/tests/test_worldadapters.py +++ b/micropsi_core/tests/test_worldadapters.py @@ -127,13 +127,13 @@ def test_flow_datasources(default_world): class TestArrayWA(wa.ArrayWorldAdapter): def update_data_sources_and_targets(self): - self.datasource_values = np.random.rand(self.datasource_values.shape) - self.datatarget_feedback_values = np.copy(self.datatarget_values) + self.datasource_values = np.random.rand(self.datasource_values.shape).astype(self.floatX) + self.datatarget_feedback_values = np.copy(self.datatarget_values).astype(self.floatX) adapter = TestArrayWA(runtime.worlds[default_world]) vision_shape = (2, 5) - vision_init = np.random.rand(*vision_shape) + vision_init = np.random.rand(*vision_shape).astype(adapter.floatX) adapter.add_datasource("s_foo") adapter.add_flow_datasource("s_vision", shape=vision_shape, initial_values=vision_init) adapter.add_datasource("s_bar") @@ -148,8 +148,8 @@ def update_data_sources_and_targets(self): assert adapter.get_available_datatargets() == ['t_execute'] assert adapter.get_available_flow_datatargets() == ['t_motor'] - vision = np.random.rand(*vision_shape) - motor = np.random.rand(*motor_shape) + vision = np.random.rand(*vision_shape).astype(adapter.floatX) + motor = np.random.rand(*motor_shape).astype(adapter.floatX) adapter.set_flow_datasource("s_vision", vision) adapter.add_to_flow_datatarget("t_motor", motor) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 4f82c12e..5b837702 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -358,19 +358,19 @@ def set_datatarget_feedback_value(self, key, value): def set_flow_datasource(self, name, values): """Set the values of the given flow_datasource """ assert values.dtype == self.floatX - assert self.flow_datasources[name].shape == values.shape + if name in self.flow_datasources: assert self.flow_datasources[name].shape == values.shape self.flow_datasources[name] = values def add_to_flow_datatarget(self, name, values): """Add the given values to the given flow_datatarget """ assert values.dtype == self.floatX - assert self.flow_datasources[name].shape == values.shape + if name in self.flow_datasources: assert self.flow_datasources[name].shape == values.shape self.flow_datatargets[name] += values def set_flow_datatarget_feedback(self, name, values): """Set the values of the given flow_datatarget_feedback """ assert values.dtype == self.floatX - assert self.flow_datasources[name].shape == values.shape + if name in self.flow_datasources: assert self.flow_datasources[name].shape == values.shape self.flow_datatarget_feedbacks[name] = values def set_datasource_values(self, values): From ac340f452b06f46163375f2b4e1d9be76f2c6e90 Mon Sep 17 00:00:00 2001 From: cknd Date: Thu, 27 Jul 2017 20:04:09 +0200 Subject: [PATCH 870/945] let flow datasources reject all non-arrays --- .cache/v/cache/lastfailed | 5 + LICENSE-3RD-PARTY.txt | 551 ++++++++++++++++++++++++++++ LICENSE.txt | 21 ++ micropsi_core/world/worldadapter.py | 9 +- 4 files changed, 583 insertions(+), 3 deletions(-) create mode 100644 .cache/v/cache/lastfailed create mode 100644 LICENSE-3RD-PARTY.txt create mode 100644 LICENSE.txt diff --git a/.cache/v/cache/lastfailed b/.cache/v/cache/lastfailed new file mode 100644 index 00000000..7ca78c70 --- /dev/null +++ b/.cache/v/cache/lastfailed @@ -0,0 +1,5 @@ +{ + "micropsi_core/tests/test_node_activation.py::test_gate_arithmetics_directional_activator_amplification[theano_engine]": true, + "micropsi_core/tests/test_node_activation.py::test_gate_arithmetics_directional_activator_muting[theano_engine]": true, + "micropsi_core/tests/test_runtime.py::test_run_netapi_command[dict_engine]": true +} \ No newline at end of file diff --git a/LICENSE-3RD-PARTY.txt b/LICENSE-3RD-PARTY.txt new file mode 100644 index 00000000..3bca780d --- /dev/null +++ b/LICENSE-3RD-PARTY.txt @@ -0,0 +1,551 @@ +OpenCV library is redistributed within opencv-python package. +This license applies to OpenCV binary in the directory cv2/. + +By downloading, copying, installing or using the software you agree to this license. +If you do not agree to this license, do not download, install, +copy or use the software. + + + License Agreement + For Open Source Computer Vision Library + (3-clause BSD License) + +Copyright (C) 2000-2016, Intel Corporation, all rights reserved. +Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved. +Copyright (C) 2009-2016, NVIDIA Corporation, all rights reserved. +Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. +Copyright (C) 2015-2016, OpenCV Foundation, all rights reserved. +Copyright (C) 2015-2016, Itseez Inc., all rights reserved. +Third party copyrights are property of their respective owners. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the names of the copyright holders nor the names of the contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are disclaimed. +In no event shall copyright holders or contributors be liable for any direct, +indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused +and on any theory of liability, whether in contract, strict liability, +or tort (including negligence or otherwise) arising in any way out of +the use of this software, even if advised of the possibility of such damage. + +------------------------------------------------------------------------------ +FFmpeg is redistributed within opencv-python package. +This license applies to FFmpeg binary in the directory cv2/. + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..0a8a6ebc --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Olli-Pekka Heinisuo, Carlos Martinez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 5b837702..08358980 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -357,20 +357,23 @@ def set_datatarget_feedback_value(self, key, value): def set_flow_datasource(self, name, values): """Set the values of the given flow_datasource """ + assert isinstance(values, np.ndarray), "must provide numpy array" assert values.dtype == self.floatX - if name in self.flow_datasources: assert self.flow_datasources[name].shape == values.shape + assert self.flow_datasources[name].shape == values.shape self.flow_datasources[name] = values def add_to_flow_datatarget(self, name, values): """Add the given values to the given flow_datatarget """ + assert isinstance(values, np.ndarray), "must provide numpy array" assert values.dtype == self.floatX - if name in self.flow_datasources: assert self.flow_datasources[name].shape == values.shape + assert self.flow_datatargets[name].shape == values.shape self.flow_datatargets[name] += values def set_flow_datatarget_feedback(self, name, values): """Set the values of the given flow_datatarget_feedback """ + assert isinstance(values, np.ndarray), "must provide numpy array" assert values.dtype == self.floatX - if name in self.flow_datasources: assert self.flow_datasources[name].shape == values.shape + assert self.flow_datatarget_feedbacks[name].shape == values.shape self.flow_datatarget_feedbacks[name] = values def set_datasource_values(self, values): From dff47e86d94ff4decc52361cb2808460f5f4a70c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 28 Jul 2017 14:36:42 +0200 Subject: [PATCH 871/945] seperate detailed test and fix for reload_code --- micropsi_core/runtime.py | 8 ++ micropsi_core/tests/test_code_reload.py | 109 ++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 micropsi_core/tests/test_code_reload.py diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 309f6cc6..8ed1a701 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1736,6 +1736,7 @@ def parse_native_module_file(path): post_mortem() return "%s when importing nodetype file %s: %s" % (e.__class__.__name__, relpath, str(e)) + def nodedef_sanity_check(nodetype_definition): """ catch some common errors in nodetype definitions """ nd = nodetype_definition @@ -1749,6 +1750,7 @@ def nodedef_sanity_check(nodetype_definition): return nodetype_definition + def parse_recipe_or_operations_file(path, mode, category_overwrite=False): global custom_recipes import importlib @@ -1819,6 +1821,12 @@ def reload_code(): from micropsi_core.world.world import DefaultWorld from micropsi_core.world.worldadapter import Default from micropsi_core.world.worldobject import TestObject + import sys + for mod in list(sys.modules.keys()): + if hasattr(sys.modules[mod], '__file__'): + path = sys.modules[mod].__file__ + if path.startswith(RESOURCE_PATH) or path.startswith(WORLD_PATH): + del sys.modules[mod] world_classes['DefaultWorld'] = DefaultWorld worldadapter_classes['Default'] = Default worldobject_classes['TestObject'] = TestObject diff --git a/micropsi_core/tests/test_code_reload.py b/micropsi_core/tests/test_code_reload.py new file mode 100644 index 00000000..95413d14 --- /dev/null +++ b/micropsi_core/tests/test_code_reload.py @@ -0,0 +1,109 @@ + + +def test_code_reload(runtime, test_nodenet, resourcepath): + import os + os.makedirs(os.path.join(resourcepath, 'nodetypes', 'library'), exist_ok=True) + os.makedirs(os.path.join(resourcepath, 'dummyworld'), exist_ok=True) + os.makedirs(os.path.join(resourcepath, 'shared_utils'), exist_ok=True) + + nodetypef = os.path.join(resourcepath, 'nodetypes', 'testnode.py') + foof = os.path.join(resourcepath, 'nodetypes', 'library', 'foo.py') + barf = os.path.join(resourcepath, 'nodetypes', 'library', 'bar.py') + + worldjsonf = os.path.join(resourcepath, 'dummyworld', 'worlds.json') + worldf = os.path.join(resourcepath, 'dummyworld', 'dummyworld.py') + worldsharedf = os.path.join(resourcepath, 'shared_utils', 'stuff.py') + + def write_resources(nodevalues, datatarget_name, worldvalues): + with open(nodetypef, 'w') as fp: + fp.write(""" +nodetype_definition = { + 'doc': 'calculates stuff', + 'nodefunction_name': 'testnode', + 'name': 'testnode', + 'slottypes': ['gen'], + 'gatetypes': ['gen'], +} + +from nodetypes.library.foo import module_level + + +def testnode(netapi, node): + from nodetypes.library.foo import inline, get_bar + val = 1 + module_level + inline + get_bar() + node.get_gate('gen').gate_function(val) +""") + with open(foof, 'w') as fp: + fp.write(""" +module_level = %d +inline = %d +def get_bar(): + from nodetypes.library.bar import magicnumber + return magicnumber +""" % (nodevalues[0], nodevalues[1])) + with open(barf, 'w') as fp: + fp.write("magicnumber=%d" % nodevalues[2]) + + with open(worldjsonf, 'w') as fp: + fp.write("""{"worlds": ["dummyworld.py"],"worldadapters": ["dummyworld.py"]}""") + with open(worldf, 'w') as fp: + fp.write("""from micropsi_core.world.world import World +from micropsi_core.world.worldadapter import WorldAdapter +from shared_utils.stuff import variable + +class DummyWorld(World): + supported_worldadapters=['DummyWA'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.var = variable + self.inline = %d + +class DummyWA(WorldAdapter): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_datasource("foo") + self.add_datasource("bar") + self.add_datatarget("%s") + def update_data_sources_and_targets(self): + from shared_utils.stuff import get_values + values = get_values() + self.datasources['foo'] = values[0] + self.datasources['bar'] = values[1] +""" % (worldvalues[0], datatarget_name)) + with open(worldsharedf, 'w') as fp: + fp.write("""variable = %d +def get_values(): + return %d, %d""" % (worldvalues[1], worldvalues[2], worldvalues[3])) + + write_resources([3, 5, 7], "target", [13, 15, 17, 19]) + res, errors = runtime.reload_code() + # assert res + + res, wuid = runtime.new_world("dummyworld", "DummyWorld") + runtime.set_nodenet_properties(test_nodenet, world_uid=wuid, worldadapter="DummyWA") + + net = runtime.nodenets[test_nodenet] + netapi = net.netapi + node = netapi.create_node('testnode') + runtime.step_nodenet(test_nodenet) + assert node.get_gate('gen').activation == 1 + 3 + 5 + 7 + world = runtime.worlds[wuid] + assert world.inline == 13 + assert world.var == 15 + wa = net.worldadapter_instance + assert "target" in wa.datatargets + assert wa.get_datasource_value("foo") == 17 + assert wa.get_datasource_value("bar") == 19 + + write_resources([11, 13, 17], "foobar", [1, 3, 5, 7]) + runtime.reload_code() + node = netapi.get_node(node.uid) + runtime.step_nodenet(test_nodenet) + assert node.get_gate('gen').activation == 1 + 11 + 13 + 17 + world = runtime.worlds[wuid] + assert world.inline == 1 + assert world.var == 3 + wa = net.worldadapter_instance + assert "foobar" in wa.datatargets + assert wa.get_datasource_value("foo") == 5 + assert wa.get_datasource_value("bar") == 7 From a44d53703e9f55d849932082e47c429493167800 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 28 Jul 2017 15:24:57 +0200 Subject: [PATCH 872/945] Fixing bad commit -a --- .cache/v/cache/lastfailed | 5 - .gitignore | 1 + LICENSE-3RD-PARTY.txt | 551 -------------------------------------- LICENSE.txt | 21 -- 4 files changed, 1 insertion(+), 577 deletions(-) delete mode 100644 .cache/v/cache/lastfailed delete mode 100644 LICENSE-3RD-PARTY.txt delete mode 100644 LICENSE.txt diff --git a/.cache/v/cache/lastfailed b/.cache/v/cache/lastfailed deleted file mode 100644 index 7ca78c70..00000000 --- a/.cache/v/cache/lastfailed +++ /dev/null @@ -1,5 +0,0 @@ -{ - "micropsi_core/tests/test_node_activation.py::test_gate_arithmetics_directional_activator_amplification[theano_engine]": true, - "micropsi_core/tests/test_node_activation.py::test_gate_arithmetics_directional_activator_muting[theano_engine]": true, - "micropsi_core/tests/test_runtime.py::test_run_netapi_command[dict_engine]": true -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0e010299..88c9fd6c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ micropsi.log pip-selfcheck.json /cherrypy /.pycharmvenv/ +/.cache/ diff --git a/LICENSE-3RD-PARTY.txt b/LICENSE-3RD-PARTY.txt deleted file mode 100644 index 3bca780d..00000000 --- a/LICENSE-3RD-PARTY.txt +++ /dev/null @@ -1,551 +0,0 @@ -OpenCV library is redistributed within opencv-python package. -This license applies to OpenCV binary in the directory cv2/. - -By downloading, copying, installing or using the software you agree to this license. -If you do not agree to this license, do not download, install, -copy or use the software. - - - License Agreement - For Open Source Computer Vision Library - (3-clause BSD License) - -Copyright (C) 2000-2016, Intel Corporation, all rights reserved. -Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved. -Copyright (C) 2009-2016, NVIDIA Corporation, all rights reserved. -Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. -Copyright (C) 2015-2016, OpenCV Foundation, all rights reserved. -Copyright (C) 2015-2016, Itseez Inc., all rights reserved. -Third party copyrights are property of their respective owners. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the names of the copyright holders nor the names of the contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as is" and -any express or implied warranties, including, but not limited to, the implied -warranties of merchantability and fitness for a particular purpose are disclaimed. -In no event shall copyright holders or contributors be liable for any direct, -indirect, incidental, special, exemplary, or consequential damages -(including, but not limited to, procurement of substitute goods or services; -loss of use, data, or profits; or business interruption) however caused -and on any theory of liability, whether in contract, strict liability, -or tort (including negligence or otherwise) arising in any way out of -the use of this software, even if advised of the possibility of such damage. - ------------------------------------------------------------------------------- -FFmpeg is redistributed within opencv-python package. -This license applies to FFmpeg binary in the directory cv2/. - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 0a8a6ebc..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Olli-Pekka Heinisuo, Carlos Martinez - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file From d76d19716f87455bb84827cde4d9976263d1982f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 28 Jul 2017 15:30:11 +0200 Subject: [PATCH 873/945] fix changing/deleting nativemodule parameters --- .../nodenet/dict_engine/dict_node.py | 3 +- .../nodenet/theano_engine/theano_node.py | 3 +- micropsi_core/tests/test_runtime_nodes.py | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index 19d80fb3..dbea6b2b 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -203,7 +203,8 @@ def set_parameter(self, parameter, value): value = self.nodetype.parameter_defaults[parameter] else: value = None - self.__parameters[parameter] = value + if parameter in self.nodetype.parameters: + self.__parameters[parameter] = value def clone_parameters(self): return self.__parameters.copy() diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index bff1d2b0..f655d463 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -336,7 +336,8 @@ def set_parameter(self, parameter, value): elif self.type == "Comment" and parameter == "comment": self.parameters[parameter] = value elif self.type in self._nodenet.native_modules: - self.parameters[parameter] = value + if parameter in self.nodetype.parameters: + self.parameters[parameter] = value def clear_parameter(self, parameter): if self.type in self._nodenet.native_modules and parameter in self.parameters: diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index 797f68fd..d69af069 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -365,3 +365,39 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 runtime.revert_nodenet(test_nodenet) node = runtime.nodenets[test_nodenet].get_node(uid) assert node.get_parameter("testparam") == 42 + + +def test_change_node_parameters(runtime, test_nodenet, resourcepath): + import os + nodetype_file = os.path.join(resourcepath, 'nodetypes', 'Test', 'testnode.py') + + def write_nodetypedef(params=[]): + with open(nodetype_file, 'w') as fp: + fp.write("""nodetype_definition = { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": %s} +def testnodefunc(netapi, node=None, **prams):\r\n return 17 + """ % str(params)) + + write_nodetypedef(params=["foo", "bar"]) + runtime.reload_code() + res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10, 10], name="Test") + node = runtime.nodenets[test_nodenet].get_node(uid) + keys = node.clone_parameters().keys() + assert "foo" in keys + assert "bar" in keys + node.set_parameter("foo", 42) + + write_nodetypedef(params=["spam", "eggs"]) + runtime.reload_code() + node = runtime.nodenets[test_nodenet].get_node(uid) + keys = node.clone_parameters().keys() + assert "foo" not in keys + assert "bar" not in keys + assert "spam" in keys + assert "eggs" in keys + assert node.get_parameter('spam') is None + assert node.get_parameter('eggs') is None From 5c85d379dc446cc4d3a5afc0bcd615e9e9b45fa5 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 28 Jul 2017 15:48:37 +0200 Subject: [PATCH 874/945] Revert "fix changing/deleting nativemodule parameters" This reverts commit d76d19716f87455bb84827cde4d9976263d1982f since it breaks user_prompts --- .../nodenet/dict_engine/dict_node.py | 3 +- .../nodenet/theano_engine/theano_node.py | 3 +- micropsi_core/tests/test_runtime_nodes.py | 36 ------------------- 3 files changed, 2 insertions(+), 40 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index dbea6b2b..19d80fb3 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -203,8 +203,7 @@ def set_parameter(self, parameter, value): value = self.nodetype.parameter_defaults[parameter] else: value = None - if parameter in self.nodetype.parameters: - self.__parameters[parameter] = value + self.__parameters[parameter] = value def clone_parameters(self): return self.__parameters.copy() diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index f655d463..bff1d2b0 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -336,8 +336,7 @@ def set_parameter(self, parameter, value): elif self.type == "Comment" and parameter == "comment": self.parameters[parameter] = value elif self.type in self._nodenet.native_modules: - if parameter in self.nodetype.parameters: - self.parameters[parameter] = value + self.parameters[parameter] = value def clear_parameter(self, parameter): if self.type in self._nodenet.native_modules and parameter in self.parameters: diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index d69af069..797f68fd 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -365,39 +365,3 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 runtime.revert_nodenet(test_nodenet) node = runtime.nodenets[test_nodenet].get_node(uid) assert node.get_parameter("testparam") == 42 - - -def test_change_node_parameters(runtime, test_nodenet, resourcepath): - import os - nodetype_file = os.path.join(resourcepath, 'nodetypes', 'Test', 'testnode.py') - - def write_nodetypedef(params=[]): - with open(nodetype_file, 'w') as fp: - fp.write("""nodetype_definition = { - "name": "Testnode", - "slottypes": ["gen", "foo", "bar"], - "gatetypes": ["gen", "foo", "bar"], - "nodefunction_name": "testnodefunc", - "parameters": %s} -def testnodefunc(netapi, node=None, **prams):\r\n return 17 - """ % str(params)) - - write_nodetypedef(params=["foo", "bar"]) - runtime.reload_code() - res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10, 10], name="Test") - node = runtime.nodenets[test_nodenet].get_node(uid) - keys = node.clone_parameters().keys() - assert "foo" in keys - assert "bar" in keys - node.set_parameter("foo", 42) - - write_nodetypedef(params=["spam", "eggs"]) - runtime.reload_code() - node = runtime.nodenets[test_nodenet].get_node(uid) - keys = node.clone_parameters().keys() - assert "foo" not in keys - assert "bar" not in keys - assert "spam" in keys - assert "eggs" in keys - assert node.get_parameter('spam') is None - assert node.get_parameter('eggs') is None From f4516e8e87128253d1cd92a47c4a5bce709e5326 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 28 Jul 2017 15:30:11 +0200 Subject: [PATCH 875/945] fix changing/deleting nativemodule parameters --- .../nodenet/dict_engine/dict_node.py | 3 +- .../nodenet/theano_engine/theano_node.py | 3 +- micropsi_core/tests/test_runtime_nodes.py | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index 19d80fb3..dbea6b2b 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -203,7 +203,8 @@ def set_parameter(self, parameter, value): value = self.nodetype.parameter_defaults[parameter] else: value = None - self.__parameters[parameter] = value + if parameter in self.nodetype.parameters: + self.__parameters[parameter] = value def clone_parameters(self): return self.__parameters.copy() diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index bff1d2b0..f655d463 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -336,7 +336,8 @@ def set_parameter(self, parameter, value): elif self.type == "Comment" and parameter == "comment": self.parameters[parameter] = value elif self.type in self._nodenet.native_modules: - self.parameters[parameter] = value + if parameter in self.nodetype.parameters: + self.parameters[parameter] = value def clear_parameter(self, parameter): if self.type in self._nodenet.native_modules and parameter in self.parameters: diff --git a/micropsi_core/tests/test_runtime_nodes.py b/micropsi_core/tests/test_runtime_nodes.py index 797f68fd..d69af069 100644 --- a/micropsi_core/tests/test_runtime_nodes.py +++ b/micropsi_core/tests/test_runtime_nodes.py @@ -365,3 +365,39 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 runtime.revert_nodenet(test_nodenet) node = runtime.nodenets[test_nodenet].get_node(uid) assert node.get_parameter("testparam") == 42 + + +def test_change_node_parameters(runtime, test_nodenet, resourcepath): + import os + nodetype_file = os.path.join(resourcepath, 'nodetypes', 'Test', 'testnode.py') + + def write_nodetypedef(params=[]): + with open(nodetype_file, 'w') as fp: + fp.write("""nodetype_definition = { + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "parameters": %s} +def testnodefunc(netapi, node=None, **prams):\r\n return 17 + """ % str(params)) + + write_nodetypedef(params=["foo", "bar"]) + runtime.reload_code() + res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10, 10], name="Test") + node = runtime.nodenets[test_nodenet].get_node(uid) + keys = node.clone_parameters().keys() + assert "foo" in keys + assert "bar" in keys + node.set_parameter("foo", 42) + + write_nodetypedef(params=["spam", "eggs"]) + runtime.reload_code() + node = runtime.nodenets[test_nodenet].get_node(uid) + keys = node.clone_parameters().keys() + assert "foo" not in keys + assert "bar" not in keys + assert "spam" in keys + assert "eggs" in keys + assert node.get_parameter('spam') is None + assert node.get_parameter('eggs') is None From f3a01591d5f8dad263f6c78e3757b7dce2b58520 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 28 Jul 2017 17:12:10 +0200 Subject: [PATCH 876/945] keep user_prompts seperate, don't abuse parameters --- micropsi_core/nodenet/dict_engine/dict_node.py | 5 ++++- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 1 + micropsi_core/nodenet/nodenet.py | 1 + micropsi_core/nodenet/theano_engine/theano_node.py | 6 ++++-- .../nodenet/theano_engine/theano_nodenet.py | 6 +++++- micropsi_core/runtime.py | 3 +-- micropsi_core/tests/test_runtime_nodenet_basics.py | 7 ++----- micropsi_server/tests/test_json_api.py | 14 +++++++------- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index dbea6b2b..be694909 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -118,7 +118,10 @@ def node_function(self): #call node function try: - self.nodetype.nodefunction(netapi=self.nodenet.netapi, node=self, **self.__parameters) + params = self.clone_parameters() + if self.uid in self.nodenet.user_prompt_response: + params.update(self.nodenet.user_prompt_response[self.uid]) + self.nodetype.nodefunction(netapi=self.nodenet.netapi, node=self, **params) except Exception: self.nodenet.is_active = False self.activation = -1 diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 4a5f9e00..ecaebd83 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -488,6 +488,7 @@ def step(self): break else: del self.deleted_items[i] + self.user_prompt_response = {} def create_node(self, nodetype, nodespace_uid, position, name="", uid=None, parameters=None, gate_configuration=None): nodespace_uid = self.get_nodespace(nodespace_uid).uid diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 2c0e5216..dfc88235 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -176,6 +176,7 @@ def __init__(self, persistency_path, name="", worldadapter="Default", world=None self.logger.info("Setting up nodenet %s with engine %s", self.name, self.engine) self.user_prompt = None + self.user_prompt_response = {} self.netapi = NetAPI(self) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index f655d463..2d1ab6b8 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -397,7 +397,6 @@ def clone_parameters(self): for parameter in self.parameters: if parameter not in parameters: parameters[parameter] = self.parameters[parameter] - return parameters def get_state(self, state): @@ -469,7 +468,10 @@ def set_persistable_state(self, json_state, numpy_elements): def node_function(self): try: - self.nodetype.nodefunction(netapi=self._nodenet.netapi, node=self, **self.clone_parameters()) + params = self.clone_parameters() + if self.uid in self._nodenet.user_prompt_response: + params.update(self._nodenet.user_prompt_response[self.uid]) + self.nodetype.nodefunction(netapi=self._nodenet.netapi, node=self, **params) except Exception: self._nodenet.is_active = False if self.nodetype is not None and self.nodetype.nodefunction is None: diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 624bea2f..1c544afe 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -836,6 +836,7 @@ def step(self): break else: del self.deleted_items[i] + self.user_prompt_response = {} def get_partition(self, uid): if uid is None: @@ -1219,7 +1220,10 @@ def compiled(thetas=None, **kwargs): elif source == 'path': funcargs.append(all_outputs[pidx][item]) if thunk['implementation'] == 'python': - out = thunk['function'](*funcargs, netapi=self.netapi, node=thunk['node'], parameters=thunk['node'].clone_parameters()) + params = thunk['node'].clone_parameters() + if self.uid in self.user_prompt_response: + params.update(self.user_prompt_response[self.uid]) + out = thunk['function'](*funcargs, netapi=self.netapi, node=thunk['node'], parameters=params) if len(thunk['node'].outputs) <= 1: out = [out] else: diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 309f6cc6..7cdbe087 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1346,8 +1346,7 @@ def align_nodes(nodenet_uid, nodespace): def user_prompt_response(nodenet_uid, node_uid, values, resume_nodenet): nodenet = get_nodenet(nodenet_uid) - for key, value in values.items(): - nodenet.get_node(node_uid).set_parameter(key, value) + nodenet.user_prompt_response[node_uid] = values if resume_nodenet: start_nodenetrunner(nodenet_uid) # nodenet.is_active = resume_nodenet diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 18b227b8..90540b24 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -82,9 +82,7 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 assert data['user_prompt']['node']['uid'] == node_uid assert data['user_prompt']['options'] == options # response - runtime.user_prompt_response(test_nodenet, node_uid, {'foo_parameter': 42}, True) - assert nodenet.get_node(node_uid).get_parameter('foo_parameter') == 42 - assert nodenet.is_active + runtime.user_prompt_response(test_nodenet, node_uid, {'foo_parameter': 42}, False) from micropsi_core.nodenet import nodefunctions tmp = nodefunctions.concept nodefunc = mock.Mock() @@ -93,8 +91,7 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 foo = nodenet.get_node(node_uid).clone_parameters() foo.update({'foo_parameter': 42}) assert nodefunc.called_with(nodenet.netapi, nodenet.get_node(node_uid), foo) - nodenet.get_node(node_uid).clear_parameter('foo_parameter') - assert nodenet.get_node(node_uid).get_parameter('foo_parameter') is None + assert 'foo_parameter' not in nodenet.get_node(node_uid).clone_parameters() nodefunctions.concept = tmp diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 95989b3f..65c7ba16 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1198,7 +1198,7 @@ def test_user_prompt_response(app, test_nodenet, resourcepath): "gatetypes": ["gen", "foo", "bar"], "symbol": "t"} -def testnodefunc(netapi, node=None, **prams):\r\n return 17 +def testnodefunc(netapi, node=None, **params):\r\n node.get_gate('foo').gate_function(params.get('foo', 23)) """) response = app.get_json('/rpc/reload_code()') assert_success(response) @@ -1217,14 +1217,14 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 response = app.post_json('/rpc/user_prompt_response', { 'nodenet_uid': test_nodenet, 'node_uid': uid, - 'values': {'foo': 'bar'}, - 'resume_nodenet': True + 'values': {'foo': 42}, + 'resume_nodenet': False }) assert_success(response) - response = app.get_json('/rpc/export_nodenet(nodenet_uid="%s")' % test_nodenet) - data = json.loads(response.json_body['data']) - assert data['nodes'][uid]['parameters']['foo'] == 'bar' - assert data['is_active'] + response = app.get_json('/rpc/step_calculation(nodenet_uid="%s")' % test_nodenet) + response = app.get_json('/rpc/get_nodes(nodenet_uid="%s")' % test_nodenet) + data = response.json_body['data'] + assert data['nodes'][uid]['gate_activations']['foo'] == 42 def test_set_logging_levels(app): From 22fbf021880e15b7c464caa5c2d4b9a63bddfe69 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 28 Jul 2017 17:34:16 +0200 Subject: [PATCH 877/945] autogenerated (e.g. datasources) flow nodes don't have a path --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 624bea2f..462a6013 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1107,7 +1107,7 @@ def compile_flow_subgraph(self, node_uids, requested_outputs=None, use_different original_outex = node.build(*buildargs) except Exception as err: import traceback as tb - frame = [f[0] for f in tb.walk_tb(err.__traceback__) if f[0].f_code.co_filename == node.definition['path']] + frame = [f[0] for f in tb.walk_tb(err.__traceback__) if f[0].f_code.co_filename == node.definition.get('path', '')] lineno = "" if len(frame) == 0 else str(frame[0].f_lineno) self.logger.error("Error in Flowmodule %s at line %s: %s: %s" % (str(node), lineno, err.__class__.__name__, str(err))) post_mortem() From d1b0e3b6c8fe1dcdd4072e4cb4477995a188e26b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sat, 29 Jul 2017 14:24:26 +0200 Subject: [PATCH 878/945] use API for default worladapter setup --- micropsi_core/world/worldadapter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/world/worldadapter.py b/micropsi_core/world/worldadapter.py index 08358980..ecb7a9e5 100644 --- a/micropsi_core/world/worldadapter.py +++ b/micropsi_core/world/worldadapter.py @@ -193,9 +193,9 @@ def get_config_options(cls): def __init__(self, world, uid=None, config={}, **data): super().__init__(world, uid=uid, config=config, **data) - self.datasources = dict((s, 0) for s in ['static_on', 'random', 'static_off']) - self.datatargets = {'echo': 0} - self.datatarget_feedback = {'echo': 0} + for s in ['static_on', 'random', 'static_off']: + self.add_datasource(s, 0) + self.add_datatarget('echo', 0) self.update_data_sources_and_targets() def update_data_sources_and_targets(self): From 9a023528a0572eb141019ac605396dd4492fdab4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 30 Jul 2017 12:05:23 +0200 Subject: [PATCH 879/945] deliver errors from runners to frontend --- micropsi_core/runtime.py | 12 ++++++++++++ micropsi_server/static/js/dialogs.js | 2 ++ 2 files changed, 14 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 309f6cc6..3c2c2549 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -494,6 +494,18 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No data = {} nodenet_obj = get_nodenet(nodenet_uid) if nodenet_obj is not None: + if nodenet_uid in MicropsiRunner.last_nodenet_exception: + import traceback + t, err, tb = MicropsiRunner.last_nodenet_exception[nodenet_uid] + del MicropsiRunner.last_nodenet_exception[nodenet_uid] + raise err + # return False, "%s: %s \r\n%s" % (type(err).__name__, str(err), '\n'.join(traceback.format_tb(tb))) + if nodenet_obj.world is not None and nodenet_obj.world in MicropsiRunner.last_world_exception: + import traceback + t, err, tb = MicropsiRunner.last_world_exception[nodenet_obj.world] + del MicropsiRunner.last_world_exception[nodenet_obj.world] + raise err + # return False, "%s: %s \r\n%s" % (type(err).__name__, str(err), '\n'.join(traceback.format_tb(tb))) condition = nodenet_obj.get_runner_condition() if condition: data['calculation_condition'] = condition diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index c3187792..13773521 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -733,6 +733,8 @@ fetch_stepping_info = function(){ if(data.data == 'No such nodenet'){ currentNodenet = null; $.cookie('selected_nodenet', '', { expires: -1, path: '/' }); + } else { + api.defaultErrorCallback(data, outcome, type); } }); From 509b2d23b94523caa9bee5d805fd1ce24e55636b Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Sun, 30 Jul 2017 12:06:15 +0200 Subject: [PATCH 880/945] don't crash the runner if autosave fails --- micropsi_core/runtime.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 3c2c2549..bbc42aa0 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -196,12 +196,15 @@ def run(self): for uid, interval in nodenets_to_save: if uid in nodenets: - net = nodenets[uid] - savefile = os.path.join(AUTOSAVE_PATH, "%s_%d.zip" % (uid, interval)) - logging.getLogger("system").info("Auto-saving nodenet %s at step %d (interval %d)" % (uid, net.current_step, interval)) - zipobj = zipfile.ZipFile(savefile, 'w', zipfile.ZIP_STORED) - net.save(zipfile=zipobj) - zipobj.close() + try: + net = nodenets[uid] + savefile = os.path.join(AUTOSAVE_PATH, "%s_%d.zip" % (uid, interval)) + logging.getLogger("system").info("Auto-saving nodenet %s at step %d (interval %d)" % (uid, net.current_step, interval)) + zipobj = zipfile.ZipFile(savefile, 'w', zipfile.ZIP_STORED) + net.save(zipfile=zipobj) + zipobj.close() + except Exception as err: + logging.getLogger("system").error("Auto-save failure for nodenet %s: %s: %s" % (uid, type(err).__name__, str(err))) calc_time = datetime.now() - start if step.total_seconds() > 0: From 99fa7a2fcbfa4e115f591aef26f3dcead4d9d85e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 2 Aug 2017 16:06:24 +0200 Subject: [PATCH 881/945] complain on output mismatches in flowmodules --- micropsi_core/nodenet/theano_engine/theano_flowmodule.py | 1 + micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py index c52a3769..b9629dcb 100644 --- a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py +++ b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py @@ -230,4 +230,5 @@ def worldadapter_flowfunction(self, *args, **kwargs): returnvalue = [] for key in self.outputs: returnvalue.append(self._nodenet.worldadapter_instance.get_flow_datasource(key)) + returnvalue = tuple(returnvalue) return returnvalue diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 462a6013..65828696 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1222,6 +1222,13 @@ def compiled(thetas=None, **kwargs): out = thunk['function'](*funcargs, netapi=self.netapi, node=thunk['node'], parameters=thunk['node'].clone_parameters()) if len(thunk['node'].outputs) <= 1: out = [out] + else: + if type(out) != tuple: + raise RuntimeError("""Output mismatch! + Node %s returned only one output instead of %d.""" % (str(thunk['node']), len(thunk['node'].outputs))) + elif len(out) != len(thunk['node'].outputs): + raise RuntimeError("""Output mismatch! + Node %s returned %d outputs instead of %d.""" % (str(thunk['node']), len(out), len(thunk['node'].outputs))) else: if thetas: funcargs += thetas From 1c8af23e59316d45ca903e673b00e8a4b457f522 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 3 Aug 2017 15:02:27 +0200 Subject: [PATCH 882/945] fix changing worldadapter config unregistering means datasource/target nodes get deleted, thus datasources & -target nodes lost their flows --- micropsi_core/runtime.py | 2 +- micropsi_core/world/world.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index c4cb2035..eba908c6 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -636,7 +636,7 @@ def set_nodenet_properties(nodenet_uid, nodenet_name=None, worldadapter=None, wo nodenet = get_nodenet(nodenet_uid) if world_uid == '': world_uid = None - if nodenet.world and (nodenet.world != world_uid or nodenet.worldadapter != worldadapter or worldadapter_config != nodenet.worldadapter_instance.config): + if nodenet.world and (nodenet.world != world_uid or nodenet.worldadapter != worldadapter): worlds[nodenet.world].unregister_nodenet(nodenet.uid) nodenet.world = None nodenet.worldadapter_instance = None diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index f39332f8..9fd5fe40 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -248,10 +248,10 @@ def register_nodenet(self, worldadapter, nodenet_uid, nodenet_name=None, config= world definition itself. """ if nodenet_uid in self.agents: - if self.agents[nodenet_uid].__class__.__name__ == worldadapter: - return True, self.agents[nodenet_uid] - else: + if self.agents[nodenet_uid].__class__.__name__ != worldadapter: return False, "Nodenet agent already exists in this world, but has the wrong type" + elif config == self.agents[nodenet_uid].config: + return True, self.agents[nodenet_uid] return self.spawn_agent(worldadapter, nodenet_uid, nodenet_name=nodenet_name, config=config) def unregister_nodenet(self, nodenet_uid): From 3bbb618faac7e7dcb40f21bb5f0f05e105c95b74 Mon Sep 17 00:00:00 2001 From: nemanja Date: Thu, 3 Aug 2017 16:46:44 +0200 Subject: [PATCH 883/945] updated numpy and matplotlib versions for ubuntu --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c1744a4b..edebaada 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,10 @@ CherryPy==5.4.0 coloredlogs==6.0 coverage==4.0.3 cycler==0.10.0 -matplotlib==1.5.1 +matplotlib==2.0.2 mock==2.0.0 nose-parameterized==0.5.0 -numpy==1.11.0 +numpy==1.13.1 pbr==1.9.1 py==1.4.31 pycrypto==2.6.1 From 83eb21c82ce518a5964d5d09bf3a70e248e09b28 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 19 May 2017 20:00:21 +0200 Subject: [PATCH 884/945] use correct and proper modulenames --- micropsi_core/nodenet/node.py | 8 +++++++- micropsi_core/nodenet/theano_engine/theano_flowmodule.py | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 6bb180d0..2dfdc3b3 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -526,7 +526,13 @@ def nodefunction_name(self, nodefunction_name): self._nodefunction_name = nodefunction_name try: if self.path: - module = SourceFileLoader("nodefunctions", self.path).load_module() + if "micropsi_core" in self.path: + # builtin native module + modulename = "micropsi_core.nodenet.native_modules" + else: + # native module from code dir + modulename = "nodetypes." + self.category.replace('/', '.') + os.path.basename(self.path)[:-3] + module = SourceFileLoader(modulename, self.path).load_module() self.nodefunction = getattr(module, nodefunction_name) self.line_number = inspect.getsourcelines(self.nodefunction)[1] else: diff --git a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py index b9629dcb..fce8249c 100644 --- a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py +++ b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py @@ -199,15 +199,17 @@ def build(self, *inputs): def _load_functions(self): """ Loads the run-/build-/init-functions """ - from importlib.machinery import SourceFileLoader + import os import inspect + from importlib.machinery import SourceFileLoader if self.definition.get('is_autogenerated'): self.__initialized = True self._initfunction = lambda x, y, z: None self._flowfunction = self.worldadapter_flowfunction else: sourcefile = self.definition['path'] - module = SourceFileLoader("nodefunctions", sourcefile).load_module() + modulename = 'nodetypes.' + self.definition['category'].replace('/', '.') + os.path.basename(sourcefile)[:-3] + module = SourceFileLoader(modulename, sourcefile).load_module() if self.definition.get('init_function_name'): self._initfunction = getattr(module, self.definition['init_function_name']) From 68f77a5e79b0b9d1f32be71476aa948bb2fe0d83 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 3 Aug 2017 18:21:49 +0200 Subject: [PATCH 885/945] assert in the test that the definition matches this basically tests the 'correct and proper modulenames' from `83eb21c8` --- micropsi_core/tests/test_flowmodules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index 1a809871..7a8191d9 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -48,6 +48,7 @@ def out12345(netapi, node, parameters): } def double_init(netapi, node, parameters): + assert nodetype_definition['name'] == 'Double' node.initfunction_ran = True assert parameters['test_param'] == 'defaultvalue' From 16f898eba8b7ec69d21e9edb0ff064888b3092af Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 4 Aug 2017 14:50:22 +0200 Subject: [PATCH 886/945] raise NameError if the parameter isn't defined --- micropsi_core/nodenet/dict_engine/dict_node.py | 2 ++ micropsi_core/nodenet/theano_engine/theano_node.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index be694909..0e8e4f61 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -208,6 +208,8 @@ def set_parameter(self, parameter, value): value = None if parameter in self.nodetype.parameters: self.__parameters[parameter] = value + else: + raise NameError("Parameter %s not defined for node %s" % (parameter, str(self))) def clone_parameters(self): return self.__parameters.copy() diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 2d1ab6b8..4fa2a43e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -338,6 +338,8 @@ def set_parameter(self, parameter, value): elif self.type in self._nodenet.native_modules: if parameter in self.nodetype.parameters: self.parameters[parameter] = value + else: + raise NameError("Parameter %s not defined for node %s" % (parameter, str(self))) def clear_parameter(self, parameter): if self.type in self._nodenet.native_modules and parameter in self.parameters: From 71d4dc2f039aa3a8680f0cff21c0e371cfa1f91a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 4 Aug 2017 15:06:12 +0200 Subject: [PATCH 887/945] fix tests --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 5 ++++- micropsi_core/tests/test_runtime_nodenet_basics.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 1c544afe..1bfc720e 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1839,7 +1839,10 @@ def reload_native_modules(self, native_modules): new_instance.position = position new_instance.name = name for key, value in parameters.items(): - new_instance.set_parameter(key, value) + try: + new_instance.set_parameter(key, value) + except NameError: + pass # parameter not defined anymore for key, value in state.items(): new_instance.set_state(key, value) diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 90540b24..1bca3b7d 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -346,7 +346,7 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 """) assert runtime.reload_code() - res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test", parameters={"threshold": "", "protocol_mode": "most_active_one"}) + res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test") testnode = runtime.nodenets[test_nodenet].get_node(uid) testnode.set_state("string", "hugo") @@ -513,7 +513,7 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 """) assert runtime.reload_code() - res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test", parameters={"threshold": "", "protocol_mode": "most_active_one"}) + res, uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test") res, neuron_uid = runtime.add_node(test_nodenet, 'Neuron', [10, 10]) runtime.add_link(test_nodenet, neuron_uid, 'gen', uid, 'gen') runtime.add_link(test_nodenet, uid, 'gen', neuron_uid, 'gen') From 5ff4e1a496e29247293f9740c96608ea6af21f62 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 4 Aug 2017 15:07:33 +0200 Subject: [PATCH 888/945] fix test and bug in theano_engine --- micropsi_core/nodenet/theano_engine/theano_partition.py | 2 +- micropsi_core/tests/test_node_netapi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index 12eec16e..ab2dc7df 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1327,7 +1327,7 @@ def create_node(self, nodetype, nodespace_id, id=None, parameters=None, gate_con self.allocated_node_offsets[self.allocated_nodespaces_exp_activators[nodespace_id]] if nto.parameter_defaults.get('expectation'): - value = int(parameters.get('expectation', nto.parameter_defaults['expectation'])) + value = float(parameters.get('expectation', nto.parameter_defaults['expectation'])) g_expect_array = self.g_expect.get_value(borrow=True) g_expect_array[offset + GEN] = float(value) g_expect_array[offset + SUR] = float(value) diff --git a/micropsi_core/tests/test_node_netapi.py b/micropsi_core/tests/test_node_netapi.py index 17258852..f39138c8 100644 --- a/micropsi_core/tests/test_node_netapi.py +++ b/micropsi_core/tests/test_node_netapi.py @@ -722,7 +722,7 @@ def test_copy_nodes(runtime, test_nodenet): a3 = netapi.create_node('Pipe', None, "a3") netapi.link(a3, 'gen', a1, 'gen') netapi.link(a1, 'por', a2, 'por') - a1.set_parameter('expecation', 0.6) + a1.set_parameter('expectation', 0.6) a1.set_gate_configuration('gen', 'sigmoid', {'bias': 1.3}) mapping = netapi.copy_nodes([a1, a2], nodespace.uid) assert a1 in mapping From 18087692b37ca34f62bc0b35c709c1499234af82 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 4 Aug 2017 15:08:07 +0200 Subject: [PATCH 889/945] ignore undefined param in dict-node init --- micropsi_core/nodenet/dict_engine/dict_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index 0e8e4f61..ce167002 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -84,7 +84,7 @@ def __init__(self, nodenet, parent_nodespace, position, state=None, activation=0 self.__parameters = dict((key, self.nodetype.parameter_defaults.get(key)) for key in self.nodetype.parameters) if parameters is not None: for key in parameters: - if parameters[key] is not None: + if parameters[key] is not None and key in self.nodetype.parameters: self.set_parameter(key, parameters[key]) for gate in self.nodetype.gatetypes: From b40d3304b3fd8559b3270974e70b871ea9a7eedb Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 8 Aug 2017 17:18:21 +0200 Subject: [PATCH 890/945] remove runner_factor setting --- conftest.py | 4 ++-- micropsi_core/runtime.py | 18 ++++++------------ micropsi_server/micropsi_app.py | 11 +++++------ micropsi_server/static/js/dialogs.js | 6 +++--- micropsi_server/tests/test_json_api.py | 4 +--- micropsi_server/view/runner_form.tpl | 13 ------------- 6 files changed, 17 insertions(+), 39 deletions(-) diff --git a/conftest.py b/conftest.py index f6e6126a..8f9346b0 100644 --- a/conftest.py +++ b/conftest.py @@ -75,7 +75,7 @@ def pytest_cmdline_main(config): usermanager.create_user('Pytest User', 'test', 'Administrator', uid='Pytest User') usermanager.start_session('Pytest User', 'test', True) set_logging_levels() - micropsi_runtime.set_runner_properties(1, 1) + micropsi_runtime.set_runner_properties(1) def pytest_configure(config): @@ -128,7 +128,7 @@ def pytest_runtest_setup(item): open(os.path.join(testpath, 'nodetypes', 'Test', '__init__.py'), 'w').close() micropsi_runtime.reload_code() micropsi_runtime.logger.clear_logs() - micropsi_runtime.set_runner_properties(0, 1) + micropsi_runtime.set_runner_properties(0) set_logging_levels() diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index c4cb2035..40947845 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -34,7 +34,7 @@ NODENET_DIRECTORY = "nodenets" WORLD_DIRECTORY = "worlds" -runner = {'timestep': 1000, 'runner': None, 'factor': 1} +runner = {'timestep': 1000, 'runner': None} nodenet_lock = threading.Lock() @@ -180,7 +180,7 @@ def run(self): logging.getLogger("agent.%s" % uid).error("Exception in Agent:", exc_info=1) post_mortem() MicropsiRunner.last_nodenet_exception[uid] = sys.exc_info() - if nodenet.world and nodenet.current_step % runner['factor'] == 0: + if nodenet.world: if nodenet.world not in world_uids: world_uids[nodenet.world] = [] world_uids[nodenet.world].append(uid) @@ -669,7 +669,7 @@ def start_nodenetrunner(nodenet_uid): return True -def set_runner_properties(timestep, factor): +def set_runner_properties(timestep): """Sets the speed of the nodenet calculation in ms. Argument: @@ -677,8 +677,6 @@ def set_runner_properties(timestep, factor): """ runner_config['runner_timestep'] = timestep runner['timestep'] = timestep - runner_config['runner_factor'] = int(factor) - runner['factor'] = int(factor) return True @@ -708,8 +706,7 @@ def remove_runner_condition(nodenet_uid): def get_runner_properties(): """Returns the speed that has been configured for the nodenet runner (in ms).""" return { - 'timestep': runner_config['runner_timestep'], - 'factor': runner_config['runner_factor'] + 'timestep': runner_config['runner_timestep'] } @@ -758,7 +755,7 @@ def step_nodenet(nodenet_uid): logging.getLogger("agent.%s" % nodenet_uid).debug(s.getvalue()) nodenet.update_monitors_and_recorders() - if nodenet.world and nodenet.current_step % runner_config['runner_factor'] == 0: + if nodenet.world: worlds[nodenet.world].step() return nodenet.current_step @@ -1997,11 +1994,8 @@ def plotter_initializer(): if 'runner_timestep' not in runner_config: runner_config['runner_timestep'] = 10 runner_config.save_configs() - if 'runner_factor' not in runner_config: - runner_config['runner_factor'] = 1 - runner_config.save_configs() - set_runner_properties(runner_config['runner_timestep'], runner_config['runner_factor']) + set_runner_properties(runner_config['runner_timestep']) runner['running'] = True if runner.get('runner') is None: diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 98a28e83..507877ff 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -787,7 +787,7 @@ def world_list(current_world=None): def edit_runner_properties(): user_id, permissions, token = get_request_data() if len(request.params) > 0: - runtime.set_runner_properties(int(request.params['timestep']), int(request.params['factor'])) + runtime.set_runner_properties(int(request.params['timestep'])) return dict(status="success", msg="Settings saved") else: return template("runner_form", action="/config/runner", value=runtime.get_runner_properties()) @@ -963,16 +963,15 @@ def remove_runner_condition(nodenet_uid): @rpc("set_runner_properties", permission_required="manage server") -def set_runner_properties(timestep, factor): +def set_runner_properties(timestep): """ Configure the server-settings: - timestep: miliseconds per nodenet-step - factor: nodenet-steps per world-step""" - return runtime.set_runner_properties(timestep, factor) + timestep: miliseconds per nodenet-step""" + return runtime.set_runner_properties(timestep) @rpc("get_runner_properties") def get_runner_properties(): - """ Return the server-settings, returning timestep and factor in a dict""" + """ Return the server-settings, returning timestep in a dict""" return True, runtime.get_runner_properties() diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index 13773521..09d90408 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -773,9 +773,9 @@ $(document).on('nodenet_changed', function(event, new_uid){ $(document).on('form_submit', function(event, data){ if(data.url == '/config/runner'){ for(var i=0; i < data.values.length; i++){ - switch(data.values[i].name){ - case 'timestep': runner_properties.timestep = parseInt(data.values[i].value); break; - case 'factor': runner_properties.timestep = parseInt(data.values[i].value); break; + if (data.values[i].name == 'timestep'){ + runner_properties.timestep = parseInt(data.values[i].value); + break; } } } diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 95989b3f..42e9e4b8 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -170,17 +170,15 @@ def test_get_runner_properties(app): response = app.get_json('/rpc/get_runner_properties()') assert_success(response) assert 'timestep' in response.json_body['data'] - assert 'factor' in response.json_body['data'] def test_set_runner_properties(app): app.set_auth() - response = app.post_json('/rpc/set_runner_properties', params=dict(timestep=123, factor=1)) + response = app.post_json('/rpc/set_runner_properties', params=dict(timestep=123)) assert_success(response) response = app.get_json('/rpc/get_runner_properties()') assert_success(response) assert response.json_body['data']['timestep'] == 123 - assert response.json_body['data']['factor'] == 1 def test_get_is_calculation_running(app, default_nodenet): diff --git a/micropsi_server/view/runner_form.tpl b/micropsi_server/view/runner_form.tpl index ec4d3731..43b26cba 100644 --- a/micropsi_server/view/runner_form.tpl +++ b/micropsi_server/view/runner_form.tpl @@ -31,19 +31,6 @@ %end - %if not defined("name_error"): -
    - %else: -
    - %end - -
    - - %if defined("name_error"): - {{name_error}} - %end -
    -
    From d931867282d7b6f63f72d6447a0f9c7a8bd74f52 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 8 Aug 2017 18:14:34 +0200 Subject: [PATCH 891/945] step_nodenet now starts the world-runner --- conftest.py | 3 ++- micropsi_core/runtime.py | 50 ++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/conftest.py b/conftest.py index 8f9346b0..15867747 100644 --- a/conftest.py +++ b/conftest.py @@ -105,7 +105,8 @@ def pytest_runtest_setup(item): engine_marker = engine_marker.args[0] if engine_marker != item.callspec.params['engine']: pytest.skip("test requires engine %s" % engine_marker) - + for uid in list(micropsi_runtime.nodenets.keys()): + micropsi_runtime.stop_nodenetrunner(uid) for uid in list(micropsi_runtime.nodenets.keys()): micropsi_runtime.delete_nodenet(uid) for uid in list(micropsi_runtime.worlds.keys()): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 40947845..1d96d2c4 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -158,7 +158,6 @@ def run(self): start = datetime.now() log = False uids = [uid for uid in nodenets if nodenets[uid].is_active] - world_uids = {} nodenets_to_save = [] if self.profiler: self.profiler.enable() @@ -180,10 +179,6 @@ def run(self): logging.getLogger("agent.%s" % uid).error("Exception in Agent:", exc_info=1) post_mortem() MicropsiRunner.last_nodenet_exception[uid] = sys.exc_info() - if nodenet.world: - if nodenet.world not in world_uids: - world_uids[nodenet.world] = [] - world_uids[nodenet.world].append(uid) if auto_save_intervals is not None: for val in auto_save_intervals: @@ -217,13 +212,14 @@ def run(self): if self.profiler: self.profiler.enable() - for wuid, nodenet_uids in world_uids.items(): - if wuid in worlds: + for wuid, world in worlds.items(): + if world.is_active: + uids.append(wuid) try: - worlds[wuid].step() + world.step() except: - for uid in nodenet_uids: - if uid in nodenets: + for uid in nodenets: + if nodenets[uid].world == wuid and nodenets[uid].is_active: stop_nodenetrunner(uid) logging.getLogger("world").error("Exception in Environment:", exc_info=1) MicropsiRunner.last_world_exception[nodenets[uid].world] = sys.exc_info() @@ -737,6 +733,34 @@ def step_nodenet(nodenet_uid): """ nodenet = get_nodenet(nodenet_uid) + if runtime_config['micropsi2'].get('profile_runner'): + import cProfile + profiler = cProfile.Profile() + profiler.enable() + + if nodenet.world and not worlds[nodenet.world].is_active: + worlds[nodenet.world].simulation_started() + if runner['runner'].paused: + runner['runner'].resume() + + nodenet.timed_step() + + if runtime_config['micropsi2'].get('profile_runner'): + profiler.disable() + import pstats + import io + s = io.StringIO() + sortby = 'cumtime' + ps = pstats.Stats(profiler, stream=s).sort_stats(sortby) + ps.print_stats('micropsi_') + logging.getLogger("agent.%s" % nodenet_uid).debug(s.getvalue()) + + nodenet.update_monitors_and_recorders() + return nodenet.current_step + + +def single_step_nodenet_only(nodenet_uid): + nodenet = get_nodenet(nodenet_uid) if runtime_config['micropsi2'].get('profile_runner'): import cProfile profiler = cProfile.Profile() @@ -755,8 +779,6 @@ def step_nodenet(nodenet_uid): logging.getLogger("agent.%s" % nodenet_uid).debug(s.getvalue()) nodenet.update_monitors_and_recorders() - if nodenet.world: - worlds[nodenet.world].step() return nodenet.current_step @@ -764,6 +786,10 @@ def step_nodenets_in_world(world_uid, nodenet_uid=None, steps=1): """ Advances all nodenets registered in the given world (or, only the given nodenet) by the given number of steps""" nodenet = None + if world_uid in worlds and not worlds[world_uid].is_active: + worlds[world_uid].simulation_started() + if runner['runner'].paused: + runner['runner'].resume() if nodenet_uid is not None: nodenet = get_nodenet(nodenet_uid) if nodenet and nodenet.world == world_uid: From 04c2ef0cc5cad75b490dd25134ca8ae585389072 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 8 Aug 2017 18:14:51 +0200 Subject: [PATCH 892/945] report running if nodenet or world is running --- micropsi_core/runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 1d96d2c4..990d2e82 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -514,7 +514,7 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No data['calculation_condition']['monitor']['color'] = monitor.color else: del data['calculation_condition']['monitor'] - data['calculation_running'] = nodenet_obj.is_active + data['calculation_running'] = nodenet_obj.is_active or (nodenet_obj.world and worlds[nodenet_obj.world].is_active) data['current_nodenet_step'] = nodenet_obj.current_step data['current_world_step'] = worlds[nodenet_obj.world].current_step if nodenet_obj.world else 0 data['control_frequency'] = nodenet_obj.frequency From 61fc1720e32bb652867d2e53dbda5e0a004139d0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 8 Aug 2017 19:31:56 +0200 Subject: [PATCH 893/945] raise exception if someone tries to set tuples in node states --- micropsi_core/nodenet/theano_engine/theano_node.py | 2 ++ micropsi_core/tests/test_runtime_nodenet_basics.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 4fa2a43e..2aa9d757 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -424,6 +424,8 @@ def _pluck_apart_state(self, state, numpy_elements): result = [] for value in state: result.append(self._pluck_apart_state(value, numpy_elements)) + elif isinstance(state, tuple): + raise ValueError("Tuples in node states are not supported") elif isinstance(state, np.ndarray): result = "__numpyelement__" + str(id(state)) numpy_elements[result] = state diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 1bca3b7d..1c2ef341 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -365,6 +365,11 @@ def testnodefunc(netapi, node=None, **prams):\r\n return 17 assert testnode.get_state("list")[1] == "boing" assert testnode.get_state("numpy").sum() == 10 # only numpy arrays have ".sum()" + testnode.set_state("wrong", (np.asarray([1, 2, 3]), 'tuple')) + + with pytest.raises(ValueError): + runtime.save_nodenet(test_nodenet) + def test_delete_linked_nodes(runtime, test_nodenet): From 7a45d810e923dae97287e27fa85ece9020d85fcc Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 9 Aug 2017 12:01:41 +0200 Subject: [PATCH 894/945] `share` contains some networkx stuff --- micropsi_server/minidoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_server/minidoc.py b/micropsi_server/minidoc.py index d9356f5c..d6ed3e15 100644 --- a/micropsi_server/minidoc.py +++ b/micropsi_server/minidoc.py @@ -19,7 +19,7 @@ PROJECT_ROOT = os.path.join(os.path.dirname(__file__), "..") PREFIX = "minidoc/" FILETYPES = [".py"] -EXCLUDED_DIRS = ["..", "test", "tests", "__pycache__", "bin", "lib", "include", "htmlcov", "cherrypy", "src"] +EXCLUDED_DIRS = ["..", "test", "tests", "__pycache__", "bin", "lib", "include", "htmlcov", "cherrypy", "src", "share"] EXCLUDED_FILES = ["__init__.py"] EXCLUDE_HIDDEN = True API_SORT = ["json_rpc_api", "netapi", "theano_netapi", "node_api"] From 8ea23151e0907c0ae19bb273c251340d98727626 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 9 Aug 2017 18:40:58 +0200 Subject: [PATCH 895/945] introduce a is_realtime flag for worlds. if a world sets this flag to True, step_nodenet will start the world-runner if the world isn't running --- micropsi_core/runtime.py | 8 +++- .../tests/test_runtime_world_basics.py | 40 +++++++++++++++++++ micropsi_core/world/world.py | 3 +- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 990d2e82..f6658b6d 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -738,8 +738,10 @@ def step_nodenet(nodenet_uid): profiler = cProfile.Profile() profiler.enable() - if nodenet.world and not worlds[nodenet.world].is_active: - worlds[nodenet.world].simulation_started() + if nodenet.world: + if type(worlds[nodenet.world]).is_realtime and not worlds[nodenet.world].is_active: + worlds[nodenet.world].simulation_started() + if runner['runner'].paused: runner['runner'].resume() @@ -755,6 +757,8 @@ def step_nodenet(nodenet_uid): ps.print_stats('micropsi_') logging.getLogger("agent.%s" % nodenet_uid).debug(s.getvalue()) + if nodenet.world and not type(worlds[nodenet.world]).is_realtime: + worlds[nodenet.world].step() nodenet.update_monitors_and_recorders() return nodenet.current_step diff --git a/micropsi_core/tests/test_runtime_world_basics.py b/micropsi_core/tests/test_runtime_world_basics.py index 2e71115a..7c700d74 100644 --- a/micropsi_core/tests/test_runtime_world_basics.py +++ b/micropsi_core/tests/test_runtime_world_basics.py @@ -286,6 +286,46 @@ def update_data_sources_and_targets(self): assert runtime.nodenets[default_nodenet].worldadapter_instance.__class__.__name__ == 'MyCustomWA' +def test_realtime_world_stepping(runtime, default_nodenet, resourcepath): + import os + import time + with open(os.path.join(resourcepath, 'worlds.json'), 'w') as fp: + fp.write(""" + {"worlds": ["custom_world.py"], + "worldadapters": ["custom_world.py"]}""") + with open(os.path.join(resourcepath, 'custom_world.py'), 'w') as fp: + fp.write(""" + +from micropsi_core.world.world import World +from micropsi_core.world.worldadapter import WorldAdapter + +class MyWorld(World): + is_realtime = True + supported_worldadapters = ['MyCustomWA'] + + def __init__(self, filename, **kwargs): + super().__init__(filename, **kwargs) + +class MyCustomWA(WorldAdapter): + def __init__(self, world, uid=None, config={}, **data): + super().__init__(world, uid=uid, config=config, **data) + + def update_data_sources_and_targets(self): + pass +""") + runtime.reload_code() + result, world_uid = runtime.new_world("test world", "MyWorld") + assert runtime.set_nodenet_properties(default_nodenet, world_uid=world_uid, worldadapter="MyCustomWA") + runtime.step_nodenet(default_nodenet) + time.sleep(.2) + assert not runtime.nodenets[default_nodenet].is_active + assert runtime.nodenets[default_nodenet].current_step == 1 + assert runtime.worlds[world_uid].is_active + assert runtime.worlds[world_uid].current_step > 2 + runtime.stop_nodenetrunner(default_nodenet) + assert not runtime.worlds[world_uid].is_active + + def test_reload_world_code(runtime, default_nodenet, resourcepath): import os with open(os.path.join(resourcepath, 'worlds.json'), 'w') as fp: diff --git a/micropsi_core/world/world.py b/micropsi_core/world/world.py index f39332f8..1b7812e8 100644 --- a/micropsi_core/world/world.py +++ b/micropsi_core/world/world.py @@ -84,6 +84,7 @@ def get_supported_worldadapters(cls): supported_worldadapters = [] supported_worldobjects = [] + is_realtime = False def __init__(self, filename, world_type="", name="", owner="", uid=None, engine=None, version=WORLD_VERSION, config={}): """Create a new MicroPsi world environment. @@ -338,4 +339,4 @@ def __del__(self): class DefaultWorld(World): supported_worldadapters = ['Default', 'DefaultArray'] - supported_worldobjects = ['TestObject'] \ No newline at end of file + supported_worldobjects = ['TestObject'] From 3b27062701af308e9711b6d370e45f9497917d36 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 9 Aug 2017 18:48:39 +0200 Subject: [PATCH 896/945] remove cruft --- micropsi_core/runtime.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index f6658b6d..2c79c427 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -494,17 +494,13 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No nodenet_obj = get_nodenet(nodenet_uid) if nodenet_obj is not None: if nodenet_uid in MicropsiRunner.last_nodenet_exception: - import traceback t, err, tb = MicropsiRunner.last_nodenet_exception[nodenet_uid] del MicropsiRunner.last_nodenet_exception[nodenet_uid] raise err - # return False, "%s: %s \r\n%s" % (type(err).__name__, str(err), '\n'.join(traceback.format_tb(tb))) if nodenet_obj.world is not None and nodenet_obj.world in MicropsiRunner.last_world_exception: - import traceback t, err, tb = MicropsiRunner.last_world_exception[nodenet_obj.world] del MicropsiRunner.last_world_exception[nodenet_obj.world] raise err - # return False, "%s: %s \r\n%s" % (type(err).__name__, str(err), '\n'.join(traceback.format_tb(tb))) condition = nodenet_obj.get_runner_condition() if condition: data['calculation_condition'] = condition From d1f2b8befcfc9d268ad029a90dbaa5d72a80c014 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 9 Aug 2017 19:41:09 +0200 Subject: [PATCH 897/945] drive-by cleanup this test is redundant. functionality is meticulously tested in the dedicated reload_code tests --- .../tests/test_runtime_world_basics.py | 98 ------------------- 1 file changed, 98 deletions(-) diff --git a/micropsi_core/tests/test_runtime_world_basics.py b/micropsi_core/tests/test_runtime_world_basics.py index 7c700d74..1b26c74a 100644 --- a/micropsi_core/tests/test_runtime_world_basics.py +++ b/micropsi_core/tests/test_runtime_world_basics.py @@ -324,101 +324,3 @@ def update_data_sources_and_targets(self): assert runtime.worlds[world_uid].current_step > 2 runtime.stop_nodenetrunner(default_nodenet) assert not runtime.worlds[world_uid].is_active - - -def test_reload_world_code(runtime, default_nodenet, resourcepath): - import os - with open(os.path.join(resourcepath, 'worlds.json'), 'w') as fp: - fp.write(""" - {"worlds": ["custom_world.py"], - "worldadapters": ["someadapter.py"]}""") - with open(os.path.join(resourcepath, 'custom_world.py'), 'w') as fp: - fp.write(""" - -from micropsi_core.world.world import World - -class MyWorld(World): - supported_worldadapters = ['MyCustomWA'] - - def __init__(self, filename, **kwargs): - super().__init__(filename, **kwargs) - -""") - - with open(os.path.join(resourcepath, 'someadapter.py'), 'w') as fp: - fp.write(""" - -from micropsi_core.world.worldadapter import WorldAdapter - -class MyCustomWA(WorldAdapter): - def __init__(self, world, uid=None, config={}, **data): - super().__init__(world, uid=uid, config=config, **data) - - def update_data_sources_and_targets(self): - pass - -""") - - runtime.reload_code() - assert "MyWorld" in runtime.get_available_world_types() - - result, world_uid = runtime.new_world("test world", "MyWorld") - - assert runtime.set_nodenet_properties(default_nodenet, world_uid=world_uid, worldadapter="MyCustomWA") - wa = runtime.nodenets[default_nodenet].worldadapter_instance - assert wa.__class__.__name__ == 'MyCustomWA' - assert wa.get_available_datasources() == [] - - res, nn2_uid = runtime.new_nodenet("deleteme", worldadapter="MyCustomWA", world_uid=world_uid) - runtime.save_world(world_uid) - runtime.unload_nodenet(nn2_uid) - runtime.revert_world(world_uid) - with open(os.path.join(resourcepath, 'custom_world.py'), 'w') as fp: - fp.write(""" - -from micropsi_core.world.world import World - -class MyWorld(World): - supported_worldadapters = ['MyCustomWA', 'SecondWA'] - - def __init__(self, filename, **kwargs): - super().__init__(filename, **kwargs) - -""") - - with open(os.path.join(resourcepath, 'someadapter.py'), 'w') as fp: - fp.write(""" - -from micropsi_core.world.worldadapter import WorldAdapter - -class MyCustomWA(WorldAdapter): - def __init__(self, world, uid=None, config={}, **data): - super().__init__(world, uid=uid, config=config, **data) - self.datasources = {'foo': 1} - self.datatargets = {'bar': 0} - self.datatarget_feedback = {'bar': 0} - - def update_data_sources_and_targets(self): - self.datasources['foo'] = self.datatargets['bar'] * 2 - -class SecondWA(WorldAdapter): - def update_data_sources_and_targets(self): - pass - -""") - - res, errors = runtime.reload_code() - assert res - assert errors == [] - - assert 'MyCustomWA' in runtime.get_worldadapters(world_uid) - assert 'SecondWA' in runtime.get_worldadapters(world_uid) - - assert nn2_uid not in runtime.worlds[world_uid].agents - assert default_nodenet in runtime.worlds[world_uid].data['agents'] - wa = runtime.nodenets[default_nodenet].worldadapter_instance - assert wa.get_available_datasources() == ['foo'] - - runtime.set_nodenet_properties(default_nodenet, world_uid=world_uid, worldadapter='SecondWA') - wa = runtime.nodenets[default_nodenet].worldadapter_instance - assert wa.get_available_datasources() == [] From 1ee96a5742855401c9c34759990e35f8d015c76a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 10 Aug 2017 00:41:38 +0200 Subject: [PATCH 898/945] also test single_step and simulation_start/stop calls --- .../tests/test_runtime_world_basics.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/micropsi_core/tests/test_runtime_world_basics.py b/micropsi_core/tests/test_runtime_world_basics.py index 1b26c74a..d1b0dced 100644 --- a/micropsi_core/tests/test_runtime_world_basics.py +++ b/micropsi_core/tests/test_runtime_world_basics.py @@ -305,6 +305,15 @@ class MyWorld(World): def __init__(self, filename, **kwargs): super().__init__(filename, **kwargs) + self.custom_state = None + + def simulation_started(self): + super().simulation_started() + self.custom_state = 'runner started' + + def simulation_stopped(self): + super().simulation_stopped() + self.custom_state = 'runner stopped' class MyCustomWA(WorldAdapter): def __init__(self, world, uid=None, config={}, **data): @@ -316,11 +325,18 @@ def update_data_sources_and_targets(self): runtime.reload_code() result, world_uid = runtime.new_world("test world", "MyWorld") assert runtime.set_nodenet_properties(default_nodenet, world_uid=world_uid, worldadapter="MyCustomWA") + runtime.single_step_nodenet_only(default_nodenet) + assert not runtime.worlds[world_uid].is_active + assert runtime.nodenets[default_nodenet].current_step == 1 + assert runtime.worlds[world_uid].current_step == 0 + assert runtime.worlds[world_uid].custom_state is None runtime.step_nodenet(default_nodenet) time.sleep(.2) assert not runtime.nodenets[default_nodenet].is_active - assert runtime.nodenets[default_nodenet].current_step == 1 + assert runtime.nodenets[default_nodenet].current_step == 2 assert runtime.worlds[world_uid].is_active - assert runtime.worlds[world_uid].current_step > 2 + assert runtime.worlds[world_uid].current_step > 3 + assert runtime.worlds[world_uid].custom_state == 'runner started' runtime.stop_nodenetrunner(default_nodenet) assert not runtime.worlds[world_uid].is_active + assert runtime.worlds[world_uid].custom_state == 'runner stopped' From b66c6cf24dc03b15c1f561c5645f2cd95d2af28a Mon Sep 17 00:00:00 2001 From: foo <3dnwmzeb35@bdec2.anonbox.net> Date: Thu, 17 Aug 2017 16:09:25 +0200 Subject: [PATCH 899/945] better error reporting --- micropsi_server/micropsi_app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 98a28e83..6b984ca4 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -109,7 +109,11 @@ def _wrapper(argument=None): kwargs = request.json except ValueError: if len(request.params) > 0: - kwargs = dict((key.strip('[]'), json.loads(val)) for key, val in request.params.iteritems()) + try: + kwargs = dict((key.strip('[]'), json.loads(val)) for key, val in request.params.iteritems()) + except json.JSONDecodeError: + raise ValueError("Invalid JSON in request: " + str(request.params.__dict__)) + user_id, permissions, token = get_request_data() if permission_required and permission_required not in permissions: response.status = 401 From e223ca3899caa93da22427edb9ee21c3856b98c8 Mon Sep 17 00:00:00 2001 From: foo <3dnwmzeb35@bdec2.anonbox.net> Date: Thu, 17 Aug 2017 17:55:05 +0200 Subject: [PATCH 900/945] make exception readable in mesh --- micropsi_server/micropsi_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 6b984ca4..a9cfcda6 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -112,7 +112,8 @@ def _wrapper(argument=None): try: kwargs = dict((key.strip('[]'), json.loads(val)) for key, val in request.params.iteritems()) except json.JSONDecodeError: - raise ValueError("Invalid JSON in request: " + str(request.params.__dict__)) + response.status = 400 + return {'status': 'error', 'data': "Malformed arguments for remote procedure call: %s" % str(request.params.__dict__)} user_id, permissions, token = get_request_data() if permission_required and permission_required not in permissions: From a48cb70bc699a96836e4e6638980d9d715aab837 Mon Sep 17 00:00:00 2001 From: foo <3dnwmzeb35@bdec2.anonbox.net> Date: Thu, 17 Aug 2017 18:39:57 +0200 Subject: [PATCH 901/945] fix netapi-command error-handling for ipython --- micropsi_core/runtime.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index e6264385..e6ab8c37 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -111,7 +111,13 @@ def push(self, line): err = self.errcache.flush() if err and err.startswith('Traceback'): parts = err.strip().split('\n') - return False, "%s: %s" % (parts[-3], parts[-1]) + if len(parts) > 10: + if ":" in parts[10]: + return False, parts[10] + else: + return False, "%s: %s" % (parts[10], parts[12]) + else: + return False, err out = self.outcache.flush() return True, out.strip() From dc534c0eb1ccac1e277ccd11198372a2e5e59641 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 17 Aug 2017 15:27:29 +0200 Subject: [PATCH 902/945] make sure calling the init-functions follows the flow's topological sorting --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 7aa0d019..b421f65a 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -621,7 +621,7 @@ def load(self): if os.path.isfile(flowfile): self.flowgraph = nx.read_gpickle(flowfile) - for node_uid in self.flow_module_instances: + for node_uid in nx.topological_sort(self.flowgraph): self.flow_module_instances[node_uid].ensure_initialized() theta_file = os.path.join(self.persistency_path, "%s_thetas.npz" % node_uid) if os.path.isfile(theta_file): @@ -1866,8 +1866,8 @@ def reload_native_modules(self, native_modules): name=data['name'], uid=uid, parameters=data['parameters']) - if data.get('flow_module'): - self.get_node(new_uid).ensure_initialized() + for new_uid in nx.topological_sort(self.flowgraph): + self.get_node(new_uid).ensure_initialized() # recompile flow_graphs: self.update_flow_graphs() From 195b9b4f7bd8aa05233d87a41dd66d718749d9fe Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 17 Aug 2017 16:25:36 +0200 Subject: [PATCH 903/945] only initialize recreated instances --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index b421f65a..7af0cee9 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1866,8 +1866,10 @@ def reload_native_modules(self, native_modules): name=data['name'], uid=uid, parameters=data['parameters']) + for new_uid in nx.topological_sort(self.flowgraph): - self.get_node(new_uid).ensure_initialized() + if new_uid in instances_to_recreate: + self.get_node(new_uid).ensure_initialized() # recompile flow_graphs: self.update_flow_graphs() From 0a1024f1212e233f730bd443432646fded67579c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 19:39:12 +0200 Subject: [PATCH 904/945] offer to guard flowmodules against non-finite values --- config.default.ini | 3 +++ conftest.py | 1 + .../nodenet/theano_engine/theano_nodenet.py | 2 ++ .../theano_engine/theano_stepoperators.py | 26 +++++++++++++++++-- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/config.default.ini b/config.default.ini index 13773886..606df784 100644 --- a/config.default.ini +++ b/config.default.ini @@ -87,3 +87,6 @@ initial_number_of_nodes = 2000 # should use a (7 + 1) / 2 = 4 elements assumption # pure register partitions can use a 1 element assumption elements_per_node_assumption = 4 + +# raise an exception when nans or infs appear in any flowmodule +flow_finite_guard = 1 diff --git a/conftest.py b/conftest.py index f6e6126a..c0a83243 100644 --- a/conftest.py +++ b/conftest.py @@ -33,6 +33,7 @@ cfg['micropsi2']['single_agent_mode'] = '' if 'theano' in cfg: cfg['theano']['initial_number_of_nodes'] = '50' + cfg['theano']['flow_finite_guard'] = '1' if 'on_exception' in cfg['micropsi2']: cfg['micropsi2']['on_exception'] = '' cfg['micropsi2']['auto_save_intervals'] = '100' diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 7af0cee9..ee81af6f 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -164,6 +164,8 @@ def __init__(self, persistency_path, name="", worldadapter="Default", world=None else: raise RuntimeError("Unsupported float precision value") + self.flow_finite_guard = settings['theano'].get('flow_finite_guard') + device = T.config.device self.logger.info("Theano configured to use %s", device) if device.startswith("gpu"): diff --git a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py index 39a175b7..43c54eb8 100644 --- a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py +++ b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py @@ -75,6 +75,27 @@ def priority(self): def __init__(self, nodenet): self.nodenet = nodenet + self.flow_finite_guard = nodenet.flow_finite_guard + + def value_guard(self, value, source, name): + if value is None: + return None + if self.flow_finite_guard: + if np.isnan(np.sum(value)): + raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) + elif np.isinf(np.sum(value)): + raise ValueError("INF value in flow datected: %s" % self.format_error(source, name)) + return value + + def format_error(self, source, name): + if type(source) == dict: + if len(source['members']) == 1: + msg = "output %s of %s" % (name, source['members'][0]) + else: + msg = "output %s of graph %s" % (name, str(source['members'])) + else: + msg = "output %s of %s" % (name, str(source)) + return msg def execute(self, nodenet, nodes, netapi): if not nodenet.flow_module_instances: @@ -88,7 +109,8 @@ def execute(self, nodenet, nodes, netapi): sourcenode = nodenet.get_node(nodenet.worldadapter_flow_nodes['datasources']) flowio[sourcenode.uid] = {} for key in sourcenode.outputs: - flowio[sourcenode.uid][key] = nodenet.worldadapter_instance.get_flow_datasource(key) + flowio[sourcenode.uid][key] = self.value_guard(nodenet.worldadapter_instance.get_flow_datasource(key), nodenet.worldadapter, key) + for target_uid, target_name in sourcenode.outputmap[key]: if target_uid == nodenet.worldadapter_flow_nodes.get('datatargets', False): nodenet.worldadapter_instance.add_to_flow_datatarget(target_name, flowio[sourcenode.uid][key]) @@ -124,4 +146,4 @@ def execute(self, nodenet, nodes, netapi): for uid, name in nodenet.get_node(node_uid).outputmap[out_name]: if uid == targetnode.uid and node_uid != nodenet.worldadapter_flow_nodes.get('datasources', False): nodenet.worldadapter_instance.add_to_flow_datatarget(name, out[index]) - flowio[node_uid][out_name] = out[index] if out is not None else None + flowio[node_uid][out_name] = self.value_guard(out[index], func, out_name) if out is not None else None From 9ac1042e42c403dad93910b08a1cac9b7718b779 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 19:39:41 +0200 Subject: [PATCH 905/945] add a test for the infguard --- micropsi_core/tests/test_flowmodules.py | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index 7a8191d9..8348d816 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -198,6 +198,33 @@ def trpoinpython(X, Y, netapi, node, parameters): return Y """) + with open(os.path.join(resourcepath, "nodetypes", "infmaker.py"), 'w') as fp: + fp.write("""nodetype_definition = { + "flow_module": True, + "implementation": "python", + "name": "infmaker", + "run_function_name": "infmaker", + "inputs": [], + "outputs": ["A"], + "inputdims": [], + "parameters": ["what"], + "parameter_values": {"what": ["nan", "inf", "neginf"]}, + "parameter_defaults": {"what": "nan"} +} + +import numpy as np + +def infmaker(netapi, node, parameters): + data = np.ones(12) + what = np.nan + if parameters['what'] == 'inf': + what = np.inf + elif parameters['what'] == 'neginf': + what = -np.inf + data[np.random.randint(0, 11)] = what + return data +""") + with open(os.path.join(resourcepath, 'worlds.json'), 'w') as fp: fp.write("""{"worlds":["flowworld.py"],"worldadapters":["flowworld.py"]}""") @@ -1100,3 +1127,34 @@ def test_flownode_generate_netapi_fragment(runtime, test_nodenet, default_world, x = np.array([1, 2, 3], dtype=netapi.floatX) result = np.array([5, 8, 11], dtype=netapi.floatX) assert np.all(function(X=x) == result) + + +@pytest.mark.engine("theano_engine") +def test_flow_inf_guard(runtime, test_nodenet, default_world, resourcepath): + nodenet, netapi, worldadapter = prepare(runtime, test_nodenet, default_world, resourcepath) + + infmaker = netapi.create_node("infmaker") + add = netapi.create_node("Add") + netapi.flow(infmaker, "A", add, "input1") + netapi.flow('worldadapter', 'foo', add, "input2") + netapi.flow(add, 'outputs', 'worldadapter', 'bar') + source = netapi.create_node("Neuron") + source.activation = 1 + netapi.link(source, 'gen', source, 'gen') + netapi.link(source, 'gen', add, 'sub') + with pytest.raises(ValueError) as excinfo: + nodenet.step() + assert "output A" in str(excinfo.value) + assert "infmaker" in str(excinfo.value) + assert "NAN value" in str(excinfo.value) + + infmaker.set_parameter('what', 'inf') + with pytest.raises(ValueError) as excinfo: + nodenet.step() + assert "INF value" in str(excinfo.value) + + worldadapter.flow_datasources['foo'][3] = np.nan + with pytest.raises(ValueError) as excinfo: + nodenet.step() + assert type(worldadapter).__name__ in str(excinfo.value) + assert "foo" in str(excinfo.value) From fe8e503023638d78d6b0929b9184b254badc68d8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 20:11:17 +0200 Subject: [PATCH 906/945] driveby fix: raise typos in implementation-param --- micropsi_core/nodenet/theano_engine/theano_flowmodule.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py index fce8249c..f01445cc 100644 --- a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py +++ b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py @@ -193,6 +193,9 @@ def build(self, *inputs): elif self.implementation == 'python': outexpression = self._flowfunction + else: + raise ValueError("Unknown flow-implementation: %s" % self.implementation) + self.outexpression = outexpression return outexpression From 75da9ab793ad2f1d977bcae02ec130e6e3375c3f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 20:11:56 +0200 Subject: [PATCH 907/945] make the infguard deal with magic-list-outputs --- .../theano_engine/theano_stepoperators.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py index 43c54eb8..22e72c04 100644 --- a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py +++ b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py @@ -80,21 +80,28 @@ def __init__(self, nodenet): def value_guard(self, value, source, name): if value is None: return None + listval = type(value) == list + if not listval: + value = [value] if self.flow_finite_guard: - if np.isnan(np.sum(value)): - raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) - elif np.isinf(np.sum(value)): - raise ValueError("INF value in flow datected: %s" % self.format_error(source, name)) - return value - - def format_error(self, source, name): + for idx, val in enumerate(value): + if np.isnan(np.sum(value)): + raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name, idx, listval)) + elif np.isinf(np.sum(value)): + raise ValueError("INF value in flow datected: %s" % self.format_error(source, name, idx, listval)) + return value[0] if not listval else value + + def format_error(self, source, name, idx, listval): + msg = "" + if listval: + msg = "at index %d of " % idx if type(source) == dict: if len(source['members']) == 1: - msg = "output %s of %s" % (name, source['members'][0]) + msg += "output %s of %s" % (name, source['members'][0]) else: - msg = "output %s of graph %s" % (name, str(source['members'])) + msg += "output %s of graph %s" % (name, str(source['members'])) else: - msg = "output %s of %s" % (name, str(source)) + msg += "output %s of %s" % (name, str(source)) return msg def execute(self, nodenet, nodes, netapi): From 05e04c404d02ea5ef8f37715ad8d65bbd937ec36 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 20:12:19 +0200 Subject: [PATCH 908/945] this test propagated a shared var over flows... --- micropsi_core/tests/test_runtime_nodenet_basics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 1c2ef341..d010d62c 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -591,7 +591,7 @@ def source_init(netapi, node, parameters): node.set_theta("weights", w_array) def source(netapi, node, parameters): - return node.get_theta("weights") + return node.get_theta("weights").get_value() """) with open(os.path.join(resourcepath, "nodetypes", "Target.py"), 'w') as fp: fp.write("""nodetype_definition = { @@ -605,7 +605,7 @@ def source(netapi, node, parameters): } def target(X, netapi, node, parameters): - node.set_state("incoming", X.get_value()) + node.set_state("incoming", X) """) runtime.reload_code() From 7076d340c57ec3f53d9909d6654982080f75cf0a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 20:41:28 +0200 Subject: [PATCH 909/945] fix/test flowguard with symbolic graphs --- .../theano_engine/theano_stepoperators.py | 27 ++++++---------- micropsi_core/tests/test_flowmodules.py | 31 +++++++++++++++++-- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py index 22e72c04..43c54eb8 100644 --- a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py +++ b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py @@ -80,28 +80,21 @@ def __init__(self, nodenet): def value_guard(self, value, source, name): if value is None: return None - listval = type(value) == list - if not listval: - value = [value] if self.flow_finite_guard: - for idx, val in enumerate(value): - if np.isnan(np.sum(value)): - raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name, idx, listval)) - elif np.isinf(np.sum(value)): - raise ValueError("INF value in flow datected: %s" % self.format_error(source, name, idx, listval)) - return value[0] if not listval else value - - def format_error(self, source, name, idx, listval): - msg = "" - if listval: - msg = "at index %d of " % idx + if np.isnan(np.sum(value)): + raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) + elif np.isinf(np.sum(value)): + raise ValueError("INF value in flow datected: %s" % self.format_error(source, name)) + return value + + def format_error(self, source, name): if type(source) == dict: if len(source['members']) == 1: - msg += "output %s of %s" % (name, source['members'][0]) + msg = "output %s of %s" % (name, source['members'][0]) else: - msg += "output %s of graph %s" % (name, str(source['members'])) + msg = "output %s of graph %s" % (name, str(source['members'])) else: - msg += "output %s of %s" % (name, str(source)) + msg = "output %s of %s" % (name, str(source)) return msg def execute(self, nodenet, nodes, netapi): diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index 8348d816..531321d8 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -158,12 +158,17 @@ def two_outputs(X, netapi, node, parameters): "build_function_name": "trpoout", "inputs": ["X"], "outputs": ["Y", "Z"], - "inputdims": [1] + "inputdims": [1], + "parameters": ["makeinf"], + "parameter_defaults": {"makeinf": "False"} } def trpoout(X, netapi, node, parameters): from theano import tensor as T - return [X, X+1, X*2], T.exp(X) + if parameters["makeinf"] == "False": + return [X, X+1, X*2], T.exp(X) + else: + return [X, X/0, X*2], T.exp(X) """) with open(os.path.join(resourcepath, "nodetypes", "TRPOIn.py"), 'w') as fp: fp.write("""nodetype_definition = { @@ -1158,3 +1163,25 @@ def test_flow_inf_guard(runtime, test_nodenet, default_world, resourcepath): nodenet.step() assert type(worldadapter).__name__ in str(excinfo.value) assert "foo" in str(excinfo.value) + + +@pytest.mark.engine("theano_engine") +def test_flow_inf_guard_on_list_outputs(runtime, test_nodenet, default_world, resourcepath): + nodenet, netapi, worldadapter = prepare(runtime, test_nodenet, default_world, resourcepath) + + trpoout = netapi.create_node("TRPOOut", None, "TRPOOut") + trpoout.set_parameter("makeinf", "True") + trpoin = netapi.create_node("TRPOIn", None, "TRPOIn") + + netapi.flow(trpoout, "Y", trpoin, "Y") + netapi.flow(trpoout, "Z", trpoin, "Z") + netapi.flow('worldadapter', 'foo', trpoout, "X") + netapi.flow(trpoin, 'A', 'worldadapter', 'bar') + source = netapi.create_node("Neuron") + source.activation = 1 + netapi.link(source, 'gen', source, 'gen') + netapi.link(source, 'gen', trpoin, 'sub') + with pytest.raises(ValueError) as excinfo: + nodenet.step() + assert "INF value in" in str(excinfo.value) + assert "output A of graph" in str(excinfo.value) From 3e50a87094da16821155f9e49f3a0cbd6e370686 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 22:10:21 +0200 Subject: [PATCH 910/945] first PoC of improved user_prompts --- micropsi_core/nodenet/netapi.py | 40 ++++---------------------------- micropsi_core/nodenet/node.py | 27 ++++++++++++++++++++- micropsi_core/nodenet/nodenet.py | 22 ++++++++++++++++++ micropsi_core/runtime.py | 11 ++++----- micropsi_server/micropsi_app.py | 4 ++-- 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index 7add6c22..dbcc153f 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -317,41 +317,11 @@ def notify_user(self, node, msg): node: the node object that emits this message msg: a string to display to the user """ - self.__nodenet.user_prompt = { - 'node': node.get_data(include_links=False), - 'msg': msg, - 'options': None - } - self.__nodenet.is_active = False - - def ask_user_for_parameter(self, node, msg, options): - """ - Stops the nodenetrunner for this nodenet, and asks the user for values to the given parameters. - These parameters will be passed into the nodefunction in the next step of the nodenet. - The user can choose to either continue or suspend running the nodenet - Parameters: - node: the node object that emits this message - msg: a string to display to the user - options: an array of objects representing the variables to set by the user. Needs key, label. Optional: type (textarea or text), values: an array or object of possible values - - example usage: - options = [{ - 'key': 'where', - 'label': 'Where should I go next?', - 'values': {'north': 'North', 'east': 'East', 'south': 'South', 'west': 'west'} - }, { - 'key': 'wait', - 'label': 'How long should I wait until I go there?', - 'type': 'textarea' - }] - netapi.ask_user_for_parameter(node, "Please decide what to do next", options) - """ - self.__nodenet.user_prompt = { - 'node': node.get_data(), - 'msg': msg, - 'options': options - } - self.__nodenet.is_active = False + self.__nodenet.set_user_prompt(node, node.uid, msg) + + def show_user_prompt(self, node, key): + promptinfo = node.get_user_prompt(key) + self.__nodenet.set_user_prompt(node, key, promptinfo['callback'].__doc__, promptinfo['parameters']) def autoalign_nodespace(self, nodespace): """ Calls the autoalignment on the given nodespace """ diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index 2dfdc3b3..bbc903f8 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -170,6 +170,11 @@ def construct_links_dict(self): links[key] = [l.get_data() for l in gatelinks] return links + def get_user_prompt(self, key): + if key not in self._nodetype.user_prompts: + raise KeyError("Nodetype %s does not define a user_prompt named %s" % (self._nodetype.name, key)) + return self._nodetype.user_prompts[key] + @abstractmethod def get_gate(self, type): """ @@ -548,7 +553,7 @@ def nodefunction_name(self, nodefunction_name): def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, - symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', **_): + symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', user_prompts=None, **_): """Initializes or creates a nodetype. Arguments: @@ -579,6 +584,10 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.parameter_values = parameter_values or {} self.parameter_defaults = parameter_defaults or {} + self.user_prompts = user_prompts or {} + if user_prompts: + self.load_user_prompt_functions() + if nodefunction_definition: self.nodefunction_definition = nodefunction_definition elif nodefunction_name: @@ -586,6 +595,22 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non else: self.nodefunction = None + def load_user_prompt_functions(self): + import os + from importlib.machinery import SourceFileLoader + import inspect + try: + modulename = "nodetypes." + self.category.replace('/', '.') + os.path.basename(self.path)[:-3] + module = SourceFileLoader(modulename, self.path).load_module() + for key, data in self.user_prompts.items(): + if hasattr(module, data['callback']): + self.user_prompts[key]['callback'] = getattr(module, data['callback']) + else: + self.logger.warning("Callback '%s' for user_prompt %s of nodetype %s not defined" % (data['callback'], key, self.name)) + except (ImportError, AttributeError) as err: + self.logger.warning("Import error while importing node function: nodefunctions.%s %s" % (nodefunction_name, err)) + raise err + def get_gate_dimensionality(self, gate): return 1 diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index dfc88235..74445b0e 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -233,6 +233,28 @@ def simulation_started(self): def simulation_stopped(self): self.is_active = False + def set_user_prompt(self, node, key, message, parameters={}): + if self.user_prompt is not None: + raise RuntimeError("Currently only one user prompt per nodenet step supported. node %s already registered one" % str(self.user_prompt['node'])) + else: + self.user_prompt = { + 'node': node, + 'key': key, + 'msg': message, + 'parameters': parameters + } + self.is_active = False + + def get_user_prompt(self): + data = self.user_prompt + data['node'] = data['node'].get_data() + self.user_prompt = None + return data + + def set_user_prompt_response(self, node_uid, key, parameters): + node = self.get_node(node_uid) + node.get_user_prompt(key)['callback'](self.netapi, node, parameters) + @abstractmethod def get_nodes(self, nodespaces=[], node_uids=[], include_links=True, links_to_nodespaces=[]): """ diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index e6264385..7b8e6dda 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -534,9 +534,9 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No } if activations['has_changes']: data['nodenet_diff']['changes'] = nodenet_obj.get_nodespace_changes(nodenet_diff.get('nodespaces', []), nodenet_diff['step'], include_links=nodenet_diff.get('include_links', True)) - if nodenet_obj.user_prompt: - data['user_prompt'] = nodenet_obj.user_prompt - nodenet_obj.user_prompt = None + prompt = nodenet_obj.get_user_prompt() + if prompt: + data['user_prompt'] = prompt if world is not None and nodenet_obj.world: if not type(world) == dict: world = {} @@ -1359,13 +1359,12 @@ def align_nodes(nodenet_uid, nodespace): return result -def user_prompt_response(nodenet_uid, node_uid, values, resume_nodenet): +def user_prompt_response(nodenet_uid, node_uid, key, parameters, resume_nodenet): nodenet = get_nodenet(nodenet_uid) - nodenet.user_prompt_response[node_uid] = values + nodenet.set_user_prompt_response(node_uid, key, parameters) if resume_nodenet: start_nodenetrunner(nodenet_uid) # nodenet.is_active = resume_nodenet - nodenet.user_prompt = None def get_available_recipes(): diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 6b984ca4..183b6bcc 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -1440,9 +1440,9 @@ def reload_code(): @rpc("user_prompt_response") -def user_prompt_response(nodenet_uid, node_uid, values, resume_nodenet): +def user_prompt_response(nodenet_uid, node_uid, key, parameters, resume_nodenet): """ Respond to a user-prompt issued by a node. """ - runtime.user_prompt_response(nodenet_uid, node_uid, values, resume_nodenet) + runtime.user_prompt_response(nodenet_uid, node_uid, key, parameters, resume_nodenet) return True From de30ae70061052122fccb4c7ba1cb5b496d19e92 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 25 Aug 2017 22:10:53 +0200 Subject: [PATCH 911/945] have a first test for improved user prompts --- .../tests/test_runtime_nodenet_basics.py | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index 1c2ef341..dcf57a28 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -61,38 +61,51 @@ def test_user_prompt(runtime, test_nodenet, resourcepath): "parameters": ["testparam"], "parameter_defaults": { "testparam": 13 - } + }, + "user_prompts": { + "promptident": { + "callback": "user_prompt_callback", + "parameters": [ + {"name": "foo", "description": "value for foo", "default": 23}, + {"name": "bar", "description": "value for bar", "default": 42} + ] + } } -def testnodefunc(netapi, node=None, **prams):\r\n return 17 +} + +def testnodefunc(netapi, node=None, **prams): + if not hasattr(node, 'foo'): + node.foo = 0 + node.bar = 1 + netapi.show_user_prompt(node, "promptident") + node.get_gate("foo").gate_function(node.foo) + node.get_gate("bar").gate_function(node.bar) + +def user_prompt_callback(netapi, node, user_prompt_params): + \"\"\"Elaborate explanation as to what this user prompt is for\"\"\" + node.foo = int(user_prompt_params['foo']) + node.bar = int(user_prompt_params['bar']) """) runtime.reload_code() res, node_uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test") nativemodule = nodenet.get_node(node_uid) - options = [{'key': 'foo_parameter', 'label': 'Please give value for "foo"', 'values': [23, 42]}] - nodenet.netapi.ask_user_for_parameter( - nativemodule, - "foobar", - options - ) + runtime.step_nodenet(test_nodenet) result, data = runtime.get_calculation_state(test_nodenet, nodenet={}) assert 'user_prompt' in data - assert data['user_prompt']['msg'] == 'foobar' + assert data['user_prompt']['key'] == "promptident" + assert data['user_prompt']['msg'] == 'Elaborate explanation as to what this user prompt is for' assert data['user_prompt']['node']['uid'] == node_uid - assert data['user_prompt']['options'] == options + assert len(data['user_prompt']['parameters']) == 2 + assert nativemodule.get_gate('foo').activation == 0 + assert nativemodule.get_gate('bar').activation == 1 + # response - runtime.user_prompt_response(test_nodenet, node_uid, {'foo_parameter': 42}, False) - from micropsi_core.nodenet import nodefunctions - tmp = nodefunctions.concept - nodefunc = mock.Mock() - nodefunctions.concept = nodefunc - nodenet.step() - foo = nodenet.get_node(node_uid).clone_parameters() - foo.update({'foo_parameter': 42}) - assert nodefunc.called_with(nodenet.netapi, nodenet.get_node(node_uid), foo) - assert 'foo_parameter' not in nodenet.get_node(node_uid).clone_parameters() - nodefunctions.concept = tmp + runtime.user_prompt_response(test_nodenet, node_uid, "promptident", {'foo': '111', 'bar': '222'}, False) + runtime.step_nodenet(test_nodenet) + assert nativemodule.get_gate('foo').activation == 111 + assert nativemodule.get_gate('bar').activation == 222 def test_user_notification(runtime, test_nodenet, node): From 107dce6c4975a8c5383d0e7d3604aa8c9215e1f6 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 28 Aug 2017 11:51:47 +0200 Subject: [PATCH 912/945] calling step_nodenet should switch to world_running state --- micropsi_core/runtime.py | 7 ++++--- micropsi_core/tests/test_runtime_world_basics.py | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 2c79c427..10f2586b 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -728,6 +728,8 @@ def step_nodenet(nodenet_uid): nodenet_uid: The uid of the nodenet """ nodenet = get_nodenet(nodenet_uid) + if nodenet.is_active: + nodenet.is_active = False if runtime_config['micropsi2'].get('profile_runner'): import cProfile @@ -736,11 +738,10 @@ def step_nodenet(nodenet_uid): if nodenet.world: if type(worlds[nodenet.world]).is_realtime and not worlds[nodenet.world].is_active: + if runner['runner'].paused: + runner['runner'].resume() worlds[nodenet.world].simulation_started() - if runner['runner'].paused: - runner['runner'].resume() - nodenet.timed_step() if runtime_config['micropsi2'].get('profile_runner'): diff --git a/micropsi_core/tests/test_runtime_world_basics.py b/micropsi_core/tests/test_runtime_world_basics.py index d1b0dced..a2accab1 100644 --- a/micropsi_core/tests/test_runtime_world_basics.py +++ b/micropsi_core/tests/test_runtime_world_basics.py @@ -340,3 +340,11 @@ def update_data_sources_and_targets(self): runtime.stop_nodenetrunner(default_nodenet) assert not runtime.worlds[world_uid].is_active assert runtime.worlds[world_uid].custom_state == 'runner stopped' + runtime.start_nodenetrunner(default_nodenet) + time.sleep(.2) + runtime.step_nodenet(default_nodenet) + laststep = runtime.nodenets[default_nodenet].current_step + time.sleep(.2) + assert laststep == runtime.nodenets[default_nodenet].current_step + assert not runtime.nodenets[default_nodenet].is_active + assert runtime.worlds[world_uid].is_active From 76f0e0393f3b2aee63ec71530078572d0213a9ab Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 28 Aug 2017 17:56:53 +0200 Subject: [PATCH 913/945] do not overwrite the incoming definition --- micropsi_core/nodenet/node.py | 8 +++++--- micropsi_core/tests/test_runtime_nodenet_basics.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index bbc903f8..eb65b6f3 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -553,7 +553,7 @@ def nodefunction_name(self, nodefunction_name): def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, - symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', user_prompts=None, **_): + symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', user_prompts={}, **_): """Initializes or creates a nodetype. Arguments: @@ -584,8 +584,10 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.parameter_values = parameter_values or {} self.parameter_defaults = parameter_defaults or {} - self.user_prompts = user_prompts or {} - if user_prompts: + self.user_prompts = {} + for key, val in user_prompts.items(): + self.user_prompts[key] = val.copy() + if self.user_prompts.keys(): self.load_user_prompt_functions() if nodefunction_definition: diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index dcf57a28..c6a76b7d 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -90,7 +90,7 @@ def user_prompt_callback(netapi, node, user_prompt_params): runtime.reload_code() res, node_uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test") nativemodule = nodenet.get_node(node_uid) - + runtime.reload_code() # this breaks, if the nodetype overwrites the definition runtime.step_nodenet(test_nodenet) result, data = runtime.get_calculation_state(test_nodenet, nodenet={}) assert 'user_prompt' in data From 7061f0b54a8cbf775aefb4fdf0020cbf9bccf4d1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 28 Aug 2017 17:57:40 +0200 Subject: [PATCH 914/945] rename, to reflect that the method deletes the entry --- micropsi_core/nodenet/nodenet.py | 5 +++-- micropsi_core/runtime.py | 2 +- micropsi_core/tests/test_flowmodules.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 74445b0e..b498a7f9 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -245,9 +245,10 @@ def set_user_prompt(self, node, key, message, parameters={}): } self.is_active = False - def get_user_prompt(self): + def consume_user_prompt(self): data = self.user_prompt - data['node'] = data['node'].get_data() + if data: + data['node'] = data['node'].get_data() self.user_prompt = None return data diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 7c8186ae..3137e600 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -540,7 +540,7 @@ def get_calculation_state(nodenet_uid, nodenet=None, nodenet_diff=None, world=No } if activations['has_changes']: data['nodenet_diff']['changes'] = nodenet_obj.get_nodespace_changes(nodenet_diff.get('nodespaces', []), nodenet_diff['step'], include_links=nodenet_diff.get('include_links', True)) - prompt = nodenet_obj.get_user_prompt() + prompt = nodenet_obj.consume_user_prompt() if prompt: data['user_prompt'] = prompt if world is not None and nodenet_obj.world: diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index 7a8191d9..918f65f6 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -845,7 +845,7 @@ def test_none_output_skips_following_graphs(runtime, test_nodenet, default_world # assert that the bisect function did not run assert np.all(worldadapter.get_flow_datatarget('bar') == np.zeros(5)) # but python did - assert nodenet.user_prompt['msg'] == 'numpyfunc ran' + assert nodenet.consume_user_prompt()['msg'] == 'numpyfunc ran' # and assert that you can get that info from the sur-gates: assert bisect.get_gate('sur').activation == 0 assert py.get_gate('sur').activation == 1 From 0f5d71778923f1ac7c7460e7a9595b1fd8cf0a7a Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 28 Aug 2017 17:58:09 +0200 Subject: [PATCH 915/945] adjust frontend code to new user prompts --- micropsi_server/static/js/dialogs.js | 40 +++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index 13773521..53ce4eeb 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -741,20 +741,21 @@ fetch_stepping_info = function(){ $('#nodenet_user_prompt .btn-primary').on('click', function(event){ event.preventDefault(); var form = $('#nodenet_user_prompt form'); - values = {}; + parameters = {}; var startnet = false; var fields = form.serializeArray(); for(var idx in fields){ if(fields[idx].name == 'run_nodenet'){ startnet = true; } else { - values[fields[idx].name] = fields[idx].value; + parameters[fields[idx].name] = fields[idx].value; } } api.call('user_prompt_response', { nodenet_uid: currentNodenet, node_uid: $('#user_prompt_node_uid').val(), - values: values, + key: $('#user_prompt_key').val(), + parameters: parameters, resume_nodenet: startnet }, function(data){ $(document).trigger("runner_started"); @@ -999,30 +1000,33 @@ function promptUser(data){ html += '

    Agent interrupted by Node ' + (data.node.name || data.node.uid) +' with message:

    '; html += "

    " + data.msg +"

    "; html += '
    '; - if (data.options){ - for(var idx in data.options){ - var item = data.options[idx]; - html += '
    '; - if(item.values && typeof item.values == 'object'){ - html += '
    '; + for(var val in item.options){ + html += ''; } - html += '
    '; - } else if(item.type && item.type == "textarea"){ - html += '
    '; + html += ''; } else { - html += '
    '; + html += '
    '; + } + if (item.description){ + html += '
    '+item.description+'
    ' } + html += ''; } } html += '
    '; html += '
    '; html += ''; + html += ''; html += '
    '; $('#nodenet_user_prompt .modal-body').html(html); $('#nodenet_user_prompt').modal("show"); From b07f868e0257fb32b7a28f70d04e9ac764a93de9 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 28 Aug 2017 17:58:27 +0200 Subject: [PATCH 916/945] adjust integration test --- micropsi_server/tests/test_json_api.py | 56 +++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 65c7ba16..9d76efe8 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -1192,13 +1192,33 @@ def test_user_prompt_response(app, test_nodenet, resourcepath): nodetype_file = os.path.join(resourcepath, 'nodetypes', 'Test', 'testnode.py') with open(nodetype_file, 'w') as fp: fp.write("""nodetype_definition = { - "name": "Testnode", - "slottypes": ["gen", "foo", "bar"], - "nodefunction_name": "testnodefunc", - "gatetypes": ["gen", "foo", "bar"], - "symbol": "t"} + "name": "Testnode", + "slottypes": ["gen", "foo", "bar"], + "gatetypes": ["gen", "foo", "bar"], + "nodefunction_name": "testnodefunc", + "user_prompts": { + "promptident": { + "callback": "user_prompt_callback", + "parameters": [ + {"name": "foo", "description": "value for foo", "default": 23}, + {"name": "bar", "description": "value for bar", "default": 42} + ] + } + } +} -def testnodefunc(netapi, node=None, **params):\r\n node.get_gate('foo').gate_function(params.get('foo', 23)) +def testnodefunc(netapi, node=None, **prams): + if not hasattr(node, 'foo'): + node.foo = 0 + node.bar = 1 + netapi.show_user_prompt(node, "promptident") + node.get_gate("foo").gate_function(node.foo) + node.get_gate("bar").gate_function(node.bar) + +def user_prompt_callback(netapi, node, user_prompt_params): + \"\"\"Elaborate explanation as to what this user prompt is for\"\"\" + node.foo = int(user_prompt_params['foo']) + node.bar = int(user_prompt_params['bar']) """) response = app.get_json('/rpc/reload_code()') assert_success(response) @@ -1211,20 +1231,38 @@ def testnodefunc(netapi, node=None, **params):\r\n node.get_gate('foo').gate_ 'name': 'Testnode' }) assert_success(response) - uid = response.json_body['data'] + response = app.get_json('/rpc/step_calculation(nodenet_uid="%s")' % test_nodenet) + assert_success(response) + + response = app.post_json('/rpc/get_calculation_state', {'nodenet_uid': test_nodenet}) + assert_success(response) + + prompt_data = response.json_body['data']['user_prompt'] + assert prompt_data['key'] == 'promptident' + assert prompt_data['node']['uid'] == uid + assert len(prompt_data['parameters']) == 2 + response = app.post_json('/rpc/user_prompt_response', { 'nodenet_uid': test_nodenet, 'node_uid': uid, - 'values': {'foo': 42}, + 'key': prompt_data['key'], + 'parameters': { + 'foo': '77', + 'bar': '99' + }, 'resume_nodenet': False }) assert_success(response) + response = app.get_json('/rpc/step_calculation(nodenet_uid="%s")' % test_nodenet) + assert_success(response) + response = app.get_json('/rpc/get_nodes(nodenet_uid="%s")' % test_nodenet) data = response.json_body['data'] - assert data['nodes'][uid]['gate_activations']['foo'] == 42 + assert data['nodes'][uid]['gate_activations']['foo'] == 77 + assert data['nodes'][uid]['gate_activations']['bar'] == 99 def test_set_logging_levels(app): From 5dc003038399c7a52b768d357ee0a005d439e603 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 29 Aug 2017 15:13:19 +0200 Subject: [PATCH 917/945] fix test --- micropsi_core/tests/test_runtime_nodenet_basics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropsi_core/tests/test_runtime_nodenet_basics.py b/micropsi_core/tests/test_runtime_nodenet_basics.py index c6a76b7d..25b8c8f0 100644 --- a/micropsi_core/tests/test_runtime_nodenet_basics.py +++ b/micropsi_core/tests/test_runtime_nodenet_basics.py @@ -89,8 +89,8 @@ def user_prompt_callback(netapi, node, user_prompt_params): runtime.reload_code() res, node_uid = runtime.add_node(test_nodenet, "Testnode", [10, 10], name="Test") - nativemodule = nodenet.get_node(node_uid) runtime.reload_code() # this breaks, if the nodetype overwrites the definition + nativemodule = nodenet.get_node(node_uid) runtime.step_nodenet(test_nodenet) result, data = runtime.get_calculation_state(test_nodenet, nodenet={}) assert 'user_prompt' in data From 12c96221507a50c6918aea3cdbaf056631563386 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 29 Aug 2017 17:40:38 +0200 Subject: [PATCH 918/945] stop smuggling user_prompts into params --- micropsi_core/nodenet/dict_engine/dict_node.py | 2 -- micropsi_core/nodenet/theano_engine/theano_node.py | 2 -- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_node.py b/micropsi_core/nodenet/dict_engine/dict_node.py index ce167002..fb7ac59a 100644 --- a/micropsi_core/nodenet/dict_engine/dict_node.py +++ b/micropsi_core/nodenet/dict_engine/dict_node.py @@ -119,8 +119,6 @@ def node_function(self): #call node function try: params = self.clone_parameters() - if self.uid in self.nodenet.user_prompt_response: - params.update(self.nodenet.user_prompt_response[self.uid]) self.nodetype.nodefunction(netapi=self.nodenet.netapi, node=self, **params) except Exception: self.nodenet.is_active = False diff --git a/micropsi_core/nodenet/theano_engine/theano_node.py b/micropsi_core/nodenet/theano_engine/theano_node.py index 2aa9d757..f1050ca9 100644 --- a/micropsi_core/nodenet/theano_engine/theano_node.py +++ b/micropsi_core/nodenet/theano_engine/theano_node.py @@ -473,8 +473,6 @@ def set_persistable_state(self, json_state, numpy_elements): def node_function(self): try: params = self.clone_parameters() - if self.uid in self._nodenet.user_prompt_response: - params.update(self._nodenet.user_prompt_response[self.uid]) self.nodetype.nodefunction(netapi=self._nodenet.netapi, node=self, **params) except Exception: self._nodenet.is_active = False diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 7af0cee9..bef68527 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -1221,8 +1221,6 @@ def compiled(thetas=None, **kwargs): funcargs.append(all_outputs[pidx][item]) if thunk['implementation'] == 'python': params = thunk['node'].clone_parameters() - if self.uid in self.user_prompt_response: - params.update(self.user_prompt_response[self.uid]) out = thunk['function'](*funcargs, netapi=self.netapi, node=thunk['node'], parameters=params) if len(thunk['node'].outputs) <= 1: out = [out] From 8d0969d2e8a5560ce7ba0421d430cf756d2fa5d0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 29 Aug 2017 19:02:35 +0200 Subject: [PATCH 919/945] load nodefunction and user_prompt callback in one go --- micropsi_core/nodenet/node.py | 60 ++++++++++++----------------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/micropsi_core/nodenet/node.py b/micropsi_core/nodenet/node.py index eb65b6f3..322bcbc3 100644 --- a/micropsi_core/nodenet/node.py +++ b/micropsi_core/nodenet/node.py @@ -523,34 +523,6 @@ def nodefunction_definition(self, nodefunction_definition): def nodefunction_name(self): return self._nodefunction_name - @nodefunction_name.setter - def nodefunction_name(self, nodefunction_name): - import os - from importlib.machinery import SourceFileLoader - import inspect - self._nodefunction_name = nodefunction_name - try: - if self.path: - if "micropsi_core" in self.path: - # builtin native module - modulename = "micropsi_core.nodenet.native_modules" - else: - # native module from code dir - modulename = "nodetypes." + self.category.replace('/', '.') + os.path.basename(self.path)[:-3] - module = SourceFileLoader(modulename, self.path).load_module() - self.nodefunction = getattr(module, nodefunction_name) - self.line_number = inspect.getsourcelines(self.nodefunction)[1] - else: - from micropsi_core.nodenet import nodefunctions - if hasattr(nodefunctions, nodefunction_name): - self.nodefunction = getattr(nodefunctions, nodefunction_name) - else: - self.logger.warning("Can not find definition of nodefunction %s" % nodefunction_name) - - except (ImportError, AttributeError) as err: - self.logger.warning("Import error while importing node function: nodefunctions.%s %s" % (nodefunction_name, err)) - raise err - def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=None, nodefunction_definition=None, nodefunction_name=None, parameter_values=None, symbol=None, shape=None, engine=None, parameter_defaults=None, path='', category='', user_prompts={}, **_): @@ -587,30 +559,40 @@ def __init__(self, name, nodenet, slottypes=None, gatetypes=None, parameters=Non self.user_prompts = {} for key, val in user_prompts.items(): self.user_prompts[key] = val.copy() - if self.user_prompts.keys(): - self.load_user_prompt_functions() if nodefunction_definition: self.nodefunction_definition = nodefunction_definition elif nodefunction_name: - self.nodefunction_name = nodefunction_name + self._nodefunction_name = nodefunction_name else: self.nodefunction = None + self.load_functions() - def load_user_prompt_functions(self): + def load_functions(self): + """ Loads nodefunctions and user_prompt callbacks""" import os from importlib.machinery import SourceFileLoader import inspect try: - modulename = "nodetypes." + self.category.replace('/', '.') + os.path.basename(self.path)[:-3] - module = SourceFileLoader(modulename, self.path).load_module() - for key, data in self.user_prompts.items(): - if hasattr(module, data['callback']): - self.user_prompts[key]['callback'] = getattr(module, data['callback']) + if self.path and self._nodefunction_name or self.user_prompts.keys(): + modulename = "nodetypes." + self.category.replace('/', '.') + os.path.basename(self.path)[:-3] + module = SourceFileLoader(modulename, self.path).load_module() + if self._nodefunction_name: + self.nodefunction = getattr(module, self._nodefunction_name) + self.line_number = inspect.getsourcelines(self.nodefunction)[1] + for key, data in self.user_prompts.items(): + if hasattr(module, data['callback']): + self.user_prompts[key]['callback'] = getattr(module, data['callback']) + else: + self.logger.warning("Callback '%s' for user_prompt %s of nodetype %s not defined" % (data['callback'], key, self.name)) + elif self._nodefunction_name: + from micropsi_core.nodenet import nodefunctions + if hasattr(nodefunctions, self._nodefunction_name): + self.nodefunction = getattr(nodefunctions, self._nodefunction_name) else: - self.logger.warning("Callback '%s' for user_prompt %s of nodetype %s not defined" % (data['callback'], key, self.name)) + self.logger.warning("Can not find definition of nodefunction %s" % self._nodefunction_name) except (ImportError, AttributeError) as err: - self.logger.warning("Import error while importing node function: nodefunctions.%s %s" % (nodefunction_name, err)) + self.logger.warning("Import error while importing node definition file of nodetype %s: %s" % (self.name, err)) raise err def get_gate_dimensionality(self, gate): From c6434ac0ea41f1aa1434b50a6422ba9d81a0fd5f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 29 Aug 2017 19:13:12 +0200 Subject: [PATCH 920/945] make responses to simple notifications work. --- micropsi_core/nodenet/netapi.py | 2 +- micropsi_core/runtime.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/micropsi_core/nodenet/netapi.py b/micropsi_core/nodenet/netapi.py index dbcc153f..b78cac8f 100644 --- a/micropsi_core/nodenet/netapi.py +++ b/micropsi_core/nodenet/netapi.py @@ -317,7 +317,7 @@ def notify_user(self, node, msg): node: the node object that emits this message msg: a string to display to the user """ - self.__nodenet.set_user_prompt(node, node.uid, msg) + self.__nodenet.set_user_prompt(node, None, msg, []) def show_user_prompt(self, node, key): promptinfo = node.get_user_prompt(key) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 3137e600..dd76e30f 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1367,7 +1367,8 @@ def align_nodes(nodenet_uid, nodespace): def user_prompt_response(nodenet_uid, node_uid, key, parameters, resume_nodenet): nodenet = get_nodenet(nodenet_uid) - nodenet.set_user_prompt_response(node_uid, key, parameters) + if key and parameters: + nodenet.set_user_prompt_response(node_uid, key, parameters) if resume_nodenet: start_nodenetrunner(nodenet_uid) # nodenet.is_active = resume_nodenet From 7afc700a44a54b9921f3935061543831c624541c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Tue, 29 Aug 2017 19:13:43 +0200 Subject: [PATCH 921/945] only ask if agent was running --- micropsi_server/static/js/dialogs.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/micropsi_server/static/js/dialogs.js b/micropsi_server/static/js/dialogs.js index 53ce4eeb..fca14510 100644 --- a/micropsi_server/static/js/dialogs.js +++ b/micropsi_server/static/js/dialogs.js @@ -1023,8 +1023,10 @@ function promptUser(data){ html += ''; } } - html += '
    '; - html += '
    '; + if (nodenetRunning){ + html += '
    '; + html += '
    '; + } html += ''; html += ''; html += ''; From 3fdb18daf6b90e8d089c83a96054d829a33c2ac0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 11:58:31 +0200 Subject: [PATCH 922/945] Revert "do not default missing nodetypes to comment" This reverts commit e3a81f2ad6d575fa3de852e2de4d24e7a2b2ec41. --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 7 +++++-- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index ecaebd83..06e9c9fa 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -435,9 +435,12 @@ def merge_data(self, nodenet_data, keep_uids=False): data['uid'] = newuid uidmap[uid] = newuid if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) + self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) + data['parameters'] = { + 'comment': 'There was a %s node here' % data['type'] + } + data['type'] = 'Comment' invalid_nodes.append(uid) - continue self._nodes[newuid] = DictNode(self, **data) # merge in links diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 7af0cee9..93c90348 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -719,9 +719,12 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only parent_uid = uidmap[data['parent_nodespace']] id_to_pass = None if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) + self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) + data['parameters'] = { + 'comment': 'There was a %s node here' % data['type'] + } + data['type'] = 'Comment' invalid_nodes.append(uid) - continue if native_module_instances_only: if data.get('flow_module') and data['type'] in self.native_module_definitions: node = FlowModule( From 357bde2e005aa480412ea9b12f663b62a5aff9e5 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 11:59:11 +0200 Subject: [PATCH 923/945] delete_flow_module can now delete non-existant nodes --- .../theano_engine/theano_flowmodule.py | 2 +- .../nodenet/theano_engine/theano_nodenet.py | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py index fce8249c..1a734721 100644 --- a/micropsi_core/nodenet/theano_engine/theano_flowmodule.py +++ b/micropsi_core/nodenet/theano_engine/theano_flowmodule.py @@ -146,7 +146,7 @@ def set_input(self, input_name, source_uid, source_output): raise RuntimeError("This input is already connected") self.inputmap[input_name] = (source_uid, source_output) - def unset_input(self, input_name, source_uid, source_output): + def unset_input(self, input_name): """ Disconnect a Flowmodule or the worldadapter from the given input of this Flowmodule """ if input_name not in self.inputs: raise NameError("Unknown input %s" % input_name) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 93c90348..d61861e7 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -917,24 +917,24 @@ def unflow(self, source_uid, source_output, target_uid, target_input): if target_uid == "worldadapter": target_uid = self.worldadapter_flow_nodes['datatargets'] self.flowgraph.remove_edge(source_uid, target_uid, key="%s_%s" % (source_output, target_input)) - self.flow_module_instances[target_uid].unset_input(target_input, source_uid, source_output) + self.flow_module_instances[target_uid].unset_input(target_input) self.flow_module_instances[source_uid].unset_output(source_output, target_uid, target_input) self.update_flow_graphs() def _delete_flow_module(self, delete_uid): self.flowgraph.remove_node(delete_uid) - module = self.flow_module_instances[delete_uid] - for name in module.inputmap: - if module.inputmap[name]: - source_uid, source_name = module.inputmap[name] - if source_uid in self.flow_module_instances: - self.flow_module_instances[source_uid].unset_output(source_name, delete_uid, name) - for name in module.outputmap: - for target_uid, target_name in module.outputmap[name]: - if target_uid in self.flow_module_instances: - self.flow_module_instances[target_uid].unset_input(target_name, delete_uid, name) - - del self.flow_module_instances[delete_uid] + for uid, module in self.flow_module_instances.items(): + for name in module.inputmap: + if module.inputmap[name]: + source_uid, source_name = module.inputmap[name] + if source_uid == delete_uid: + module.unset_input(name) + for name in module.outputmap: + for target_uid, target_name in module.outputmap[name].copy(): + if target_uid == delete_uid: + module.unset_output(name, delete_uid, target_name) + if delete_uid in self.flow_module_instances: + del self.flow_module_instances[delete_uid] self.update_flow_graphs() def update_flow_graphs(self, node_uids=None): From c92a04baa72d4f446e1b588e350e972b6b64c33f Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 11:59:50 +0200 Subject: [PATCH 924/945] delete a node from the flowgraph if its definition vanished --- .../nodenet/theano_engine/theano_nodenet.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index d61861e7..d586dc49 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -622,12 +622,15 @@ def load(self): self.flowgraph = nx.read_gpickle(flowfile) for node_uid in nx.topological_sort(self.flowgraph): - self.flow_module_instances[node_uid].ensure_initialized() - theta_file = os.path.join(self.persistency_path, "%s_thetas.npz" % node_uid) - if os.path.isfile(theta_file): - data = np.load(theta_file) - for key in data: - self.set_theta(node_uid, key, data[key]) + if node_uid in self.flow_module_instances: + self.flow_module_instances[node_uid].ensure_initialized() + theta_file = os.path.join(self.persistency_path, "%s_thetas.npz" % node_uid) + if os.path.isfile(theta_file): + data = np.load(theta_file) + for key in data: + self.set_theta(node_uid, key, data[key]) + else: + self._delete_flow_module(node_uid) self.update_flow_graphs() From 724006dc28d64dad5900909b8b77b561250d7bb3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 12:02:51 +0200 Subject: [PATCH 925/945] flow_module_definitions is not kept up to date, remove --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index d586dc49..fb6f8863 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -128,7 +128,7 @@ def worldadapter_instance(self, _worldadapter_instance): def current_step(self): return self._step - def __init__(self, persistency_path, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None, flow_modules={}): + def __init__(self, persistency_path, name="", worldadapter="Default", world=None, owner="", uid=None, native_modules={}, use_modulators=True, worldadapter_instance=None, version=None): # map of string uids to positions. Not all nodes necessarily have an entry. self.positions = {} @@ -223,7 +223,6 @@ def __init__(self, persistency_path, name="", worldadapter="Default", world=None if native_modules[key].get('engine', self.engine) == self.engine: self.native_module_definitions[key] = native_modules[key] - self.flow_module_definitions = flow_modules self.flow_module_instances = {} self.flow_graphs = [] self.thetas = {} @@ -729,7 +728,7 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only data['type'] = 'Comment' invalid_nodes.append(uid) if native_module_instances_only: - if data.get('flow_module') and data['type'] in self.native_module_definitions: + if data.get('flow_module') and data['type'] in self.get_flow_module_definitions(): node = FlowModule( self, self.get_partition(uid), From a75dff79ab74e6a6955fef3bdf0a98fdecd3e5c4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 12:12:33 +0200 Subject: [PATCH 926/945] deal with nodes becoming flownodes --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index fb6f8863..79d558d7 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -728,7 +728,7 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only data['type'] = 'Comment' invalid_nodes.append(uid) if native_module_instances_only: - if data.get('flow_module') and data['type'] in self.get_flow_module_definitions(): + if data['type'] in self.get_flow_module_definitions(): node = FlowModule( self, self.get_partition(uid), @@ -736,8 +736,8 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only data['uid'], get_numerical_node_type(data['type'], nativemodules=self.native_modules), parameters=data.get('parameters', {}), - inputmap=data['inputmap'], - outputmap=data['outputmap'], + inputmap=data.get('inputmap', {}), + outputmap=data.get('outputmap', {}), is_copy_of=data.get('is_copy_of')) self.flow_module_instances[node.uid] = node From be027d7a904da52aaf3522179ece660394b6d01c Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 17:37:31 +0200 Subject: [PATCH 927/945] explicitly set the is_active flag --- micropsi_core/runtime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 92ab17cf..3c5670a2 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -661,6 +661,7 @@ def start_nodenetrunner(nodenet_uid): nodenet.simulation_started() # nodenets[nodenet_uid].is_active = True if nodenet.world: + worlds[world_uid].is_active = True worlds[nodenet.world].simulation_started() if runner['runner'].paused: runner['runner'].resume() @@ -721,6 +722,7 @@ def stop_nodenetrunner(nodenet_uid): if nodenet.world: test_world = {nodenets[uid].is_active and nodenets[uid].world == nodenet.world for uid in nodenets} if True not in test_world: + worlds[world_uid].is_active = False worlds[nodenet.world].simulation_stopped() if True not in test: runner['runner'].pause() From bc3b36d0c022d4a7fe2cc1f1f62a2a11d20c50ae Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 17:38:31 +0200 Subject: [PATCH 928/945] d'oh --- micropsi_core/runtime.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 3c5670a2..2ddc3740 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -661,7 +661,7 @@ def start_nodenetrunner(nodenet_uid): nodenet.simulation_started() # nodenets[nodenet_uid].is_active = True if nodenet.world: - worlds[world_uid].is_active = True + worlds[nodenet.world].is_active = True worlds[nodenet.world].simulation_started() if runner['runner'].paused: runner['runner'].resume() @@ -722,7 +722,7 @@ def stop_nodenetrunner(nodenet_uid): if nodenet.world: test_world = {nodenets[uid].is_active and nodenets[uid].world == nodenet.world for uid in nodenets} if True not in test_world: - worlds[world_uid].is_active = False + worlds[nodenet.world].is_active = False worlds[nodenet.world].simulation_stopped() if True not in test: runner['runner'].pause() From 07b3c35be384aa3b587340e1f26f4158e71362d1 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Wed, 30 Aug 2017 18:43:57 +0200 Subject: [PATCH 929/945] fix the relpath for builtin operations --- micropsi_core/runtime.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 2ddc3740..68de25a4 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -1806,7 +1806,11 @@ def parse_recipe_or_operations_file(path, mode, category_overwrite=False): category = category_overwrite or os.path.relpath(os.path.dirname(path), start=base_path) if category == '.': category = '' # relapth in rootfolder - relpath = os.path.relpath(path, start=base_path) + if path.startswith(base_path): + relpath = os.path.relpath(path, start=base_path) + else: + # builtin operations get their filename as relpath + relpath, _ = os.path.splitext(os.path.basename(path)) name = os.path.basename(path)[:-3] try: From 776efb7d54458124b3dfd667719cbe745037f5d0 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 31 Aug 2017 19:15:23 +0200 Subject: [PATCH 930/945] Revert "Revert "do not default missing nodetypes to comment"" This reverts commit 3fdb18daf6b90e8d089c83a96054d829a33c2ac0. --- micropsi_core/nodenet/dict_engine/dict_nodenet.py | 7 ++----- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/micropsi_core/nodenet/dict_engine/dict_nodenet.py b/micropsi_core/nodenet/dict_engine/dict_nodenet.py index 06e9c9fa..ecaebd83 100644 --- a/micropsi_core/nodenet/dict_engine/dict_nodenet.py +++ b/micropsi_core/nodenet/dict_engine/dict_nodenet.py @@ -435,12 +435,9 @@ def merge_data(self, nodenet_data, keep_uids=False): data['uid'] = newuid uidmap[uid] = newuid if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) - data['parameters'] = { - 'comment': 'There was a %s node here' % data['type'] - } - data['type'] = 'Comment' + self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) invalid_nodes.append(uid) + continue self._nodes[newuid] = DictNode(self, **data) # merge in links diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 79d558d7..9a5892ae 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -721,12 +721,9 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only parent_uid = uidmap[data['parent_nodespace']] id_to_pass = None if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: - self.logger.warning("Invalid nodetype %s for node %s" % (data['type'], uid)) - data['parameters'] = { - 'comment': 'There was a %s node here' % data['type'] - } - data['type'] = 'Comment' + self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) invalid_nodes.append(uid) + continue if native_module_instances_only: if data['type'] in self.get_flow_module_definitions(): node = FlowModule( From bbbbcb8c6054cf0a019a30f5e11e02ec1b69d568 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 31 Aug 2017 19:55:03 +0200 Subject: [PATCH 931/945] delete flowmodules, if without definition, ignore other nodes --- .../nodenet/theano_engine/theano_nodenet.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 9a5892ae..1cacc730 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -291,7 +291,7 @@ def get_nodes(self, nodespace_uids=[], include_links=True, links_to_nodespaces=[ else: data['nodespaces'] = self.construct_nodespaces_dict(None, transitive=True) for partition in self.partitions.values(): - nodes, _ , _ = partition.get_node_data(nodespaces_by_partition=None, include_links=include_links) + nodes, _, _ = partition.get_node_data(nodespaces_by_partition=None, include_links=include_links) data['nodes'].update(nodes) return data @@ -666,7 +666,7 @@ def initialize_nodenet(self, initfrom): def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only=False): """merges the nodenet state with the current node net, might have to give new UIDs to some entities""" uidmap = {} - invalid_nodes = [] + invalid_nodes = {} # for dict_engine compatibility uidmap["Root"] = self.rootpartition.rootnodespace_uid @@ -722,10 +722,10 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only id_to_pass = None if data['type'] not in self.nodetypes and data['type'] not in self.native_modules: self.logger.error("Invalid nodetype %s for node %s" % (data['type'], uid)) - invalid_nodes.append(uid) + invalid_nodes[uid] = data continue if native_module_instances_only: - if data['type'] in self.get_flow_module_definitions(): + if data.get('flow_module'): node = FlowModule( self, self.get_partition(uid), @@ -736,7 +736,6 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only inputmap=data.get('inputmap', {}), outputmap=data.get('outputmap', {}), is_copy_of=data.get('is_copy_of')) - self.flow_module_instances[node.uid] = node else: node = TheanoNode(self, self.get_partition(uid), parent_uid, uid, get_numerical_node_type(data['type'], nativemodules=self.native_modules), parameters=data.get('parameters')) @@ -791,6 +790,10 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only mon = monitor.NodeMonitor(self, name=data['node_name'], **data) self._monitors[mon.uid] = mon + for uid in invalid_nodes: + if invalid_nodes[uid].get('flow_module'): + self._delete_flow_module(uid) + def merge_nodespace_data(self, nodespace_uid, data, uidmap, keep_uids=False): """ merges the given nodespace with the given nodespace data dict @@ -921,7 +924,8 @@ def unflow(self, source_uid, source_output, target_uid, target_input): self.update_flow_graphs() def _delete_flow_module(self, delete_uid): - self.flowgraph.remove_node(delete_uid) + if delete_uid in self.flowgraph.nodes(): + self.flowgraph.remove_node(delete_uid) for uid, module in self.flow_module_instances.items(): for name in module.inputmap: if module.inputmap[name]: From a83131bf775031bde3e18623a1f0138ea42e919e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 31 Aug 2017 19:55:36 +0200 Subject: [PATCH 932/945] unset entries if node definition missing --- micropsi_core/nodenet/theano_engine/theano_partition.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index ab2dc7df..f88d9634 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -1021,7 +1021,11 @@ def load_data(self, nodes_data): if self.allocated_nodes[id] > MAX_STD_NODETYPE: uid = node_to_id(id, self.pid) if uid in nodes_data: - self.allocated_nodes[id] = get_numerical_node_type(nodes_data[uid]['type'], self.nodenet.native_modules) + try: + self.allocated_nodes[id] = get_numerical_node_type(nodes_data[uid]['type'], self.nodenet.native_modules) + except ValueError: + self.allocated_nodes[id] = 0 + self.allocated_elements_to_nodes[np.where(self.allocated_elements_to_nodes == id)] = 0 if self.allocated_nodes[id] > MAX_STD_NODETYPE: self.native_module_instances[uid] = self.nodenet.get_node(uid) elif self.allocated_nodes[id] == COMMENT: From 75c36058225d4f069fd7f25a7e665b94d4edd7a3 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Thu, 31 Aug 2017 19:56:19 +0200 Subject: [PATCH 933/945] have a test --- .../tests/test_changing_definitions.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 micropsi_core/tests/test_changing_definitions.py diff --git a/micropsi_core/tests/test_changing_definitions.py b/micropsi_core/tests/test_changing_definitions.py new file mode 100644 index 00000000..5af91050 --- /dev/null +++ b/micropsi_core/tests/test_changing_definitions.py @@ -0,0 +1,85 @@ + +import pytest + + +@pytest.mark.engine("theano_engine") +def test_chaning_defs_theano(runtime, test_nodenet, default_world, resourcepath): + import os + os.makedirs(os.path.join(resourcepath, 'nodetypes'), exist_ok=True) + + def write_flowmodule(filename, name): + with open(os.path.join(resourcepath, 'nodetypes', filename), 'w') as fp: + fp.write(""" +nodetype_definition = { + 'run_function_name': 'flowfunc', + 'name': '%s', + 'flow_module': True, + 'implementation': 'python', + 'inputs': ['Y'], + 'outputs': ['X'], + 'inputdims': ['2'] +} + +def flowfunc(Y, netapi, node, params): + import numpy as np + return np.ones((2,3)) +""" % name) + + def write_nativemodule(filename, name): + with open(os.path.join(resourcepath, 'nodetypes', filename), 'w') as fp: + fp.write(""" +nodetype_definition = { + 'nodefunction_name': 'nodefunc', + 'name': '%s', + 'slottypes': ['gen', 'foo', 'bar'], + 'gatetypes': ['gen', 'foo', 'bar'] +} + +def nodefunc(netapi, node, params): + pass +""" % name) + + def removedefs(*filenames): + for f in filenames: + os.remove(os.path.join(resourcepath, 'nodetypes', f)) + + write_flowmodule('foonode.py', 'foonode') + write_flowmodule('foo2node.py', 'foo2node') + write_nativemodule('barnode.py', 'barnode') + runtime.reload_code() + + netapi = runtime.nodenets[test_nodenet].netapi + foonode = netapi.create_node('foonode') + barnode = netapi.create_node('barnode') + neuron = netapi.create_node('Neuron') + foo2node = netapi.create_node('foo2node') + netapi.link(neuron, 'gen', foonode, 'gen') + netapi.link(neuron, 'gen', foo2node, 'gen') + netapi.link(neuron, 'gen', barnode, 'gen') + runtime.set_nodenet_properties(nodenet_uid=test_nodenet, world_uid=default_world, worldadapter="DefaultArray") + netapi.flow('worldadapter', 'vision', foonode, 'Y') + netapi.flow(foonode, 'X', foo2node, 'Y') + netapi.flow(foo2node, 'X', 'worldadapter', 'action') + runtime.save_nodenet(test_nodenet) + + # remove nativemodule + runtime.unload_nodenet(test_nodenet) + removedefs('barnode.py') + runtime.reload_code() + net = runtime.get_nodenet(test_nodenet) + assert type(net.netapi.get_node(foonode.uid)).__name__ == "FlowModule" + with pytest.raises(KeyError): + net.netapi.get_node(barnode.uid) + write_nativemodule('barnode.py', 'barnode') + + # remove a flowmodule + runtime.unload_nodenet(test_nodenet) + removedefs('foonode.py') + runtime.reload_code() + net = runtime.get_nodenet(test_nodenet) + foo2node = net.netapi.get_node(foo2node.uid) + assert type(foo2node).__name__ == "FlowModule" + assert type(net.netapi.get_node(barnode.uid)).__name__ == "TheanoNode" + assert foonode.uid not in foo2node.inputmap['Y'] + with pytest.raises(KeyError): + net.netapi.get_node(foonode.uid) From d511e1282fdb5a299f5129ce16ba8d9774249f7d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 1 Sep 2017 12:40:36 +0200 Subject: [PATCH 934/945] deal with nodes changing their flow nature --- .../nodenet/theano_engine/theano_nodenet.py | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 1cacc730..969fd6bd 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -578,11 +578,19 @@ def load(self): monitors = initfrom.pop('monitors', {}) # initialize - self.initialize_nodenet(initfrom) + invalid_uids = self.initialize_nodenet(initfrom) for partition in self.partitions.values(): partition.load_data(nodes_data) + if invalid_uids: + for uid in invalid_uids: + partition = self.get_partition(uid) + id = node_from_id(uid) + partition.allocated_nodes[id] = 0 + partition.allocated_node_parents[id] = 0 + partition.allocated_elements_to_nodes[np.where(partition.allocated_elements_to_nodes == id)] = 0 + for partition in self.partitions.values(): partition.load_inlinks() @@ -649,9 +657,10 @@ def initialize_nodenet(self, initfrom): self._nodespace_ui_properties = initfrom.get('nodespace_ui_properties', {}) + invalid_uids = [] if len(initfrom) != 0: # now merge in all init data (from the persisted file typically) - self.merge_data(initfrom, keep_uids=True, native_module_instances_only=True) + invalid_uids = self.merge_data(initfrom, keep_uids=True, native_module_instances_only=True) if 'names' in initfrom: self.names = initfrom['names'] if 'positions' in initfrom: @@ -662,6 +671,7 @@ def initialize_nodenet(self, initfrom): self.sensormap = initfrom['sensormap'] if 'current_step' in initfrom: self._step = initfrom['current_step'] + return invalid_uids def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only=False): """merges the nodenet state with the current node net, might have to give new UIDs to some entities""" @@ -726,19 +736,27 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only continue if native_module_instances_only: if data.get('flow_module'): - node = FlowModule( - self, - self.get_partition(uid), - data['parent_nodespace'], - data['uid'], - get_numerical_node_type(data['type'], nativemodules=self.native_modules), - parameters=data.get('parameters', {}), - inputmap=data.get('inputmap', {}), - outputmap=data.get('outputmap', {}), - is_copy_of=data.get('is_copy_of')) - self.flow_module_instances[node.uid] = node + if self.native_module_definitions[data['type']].get('flow_module'): + node = FlowModule( + self, + self.get_partition(uid), + data['parent_nodespace'], + data['uid'], + get_numerical_node_type(data['type'], nativemodules=self.native_modules), + parameters=data.get('parameters', {}), + inputmap=data.get('inputmap', {}), + outputmap=data.get('outputmap', {}), + is_copy_of=data.get('is_copy_of')) + self.flow_module_instances[node.uid] = node + else: + invalid_nodes[uid] = data + continue else: - node = TheanoNode(self, self.get_partition(uid), parent_uid, uid, get_numerical_node_type(data['type'], nativemodules=self.native_modules), parameters=data.get('parameters')) + if not self.native_module_definitions[data['type']].get('flow_module'): + node = TheanoNode(self, self.get_partition(uid), parent_uid, uid, get_numerical_node_type(data['type'], nativemodules=self.native_modules), parameters=data.get('parameters')) + else: + invalid_nodes[uid] = data + continue self.proxycache[node.uid] = node new_uid = node.uid else: @@ -793,6 +811,7 @@ def merge_data(self, nodenet_data, keep_uids=False, native_module_instances_only for uid in invalid_nodes: if invalid_nodes[uid].get('flow_module'): self._delete_flow_module(uid) + return invalid_nodes.keys() def merge_nodespace_data(self, nodespace_uid, data, uidmap, keep_uids=False): """ From 32f4d176af2c46c3f4ea5986ad380a95cd488814 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 1 Sep 2017 13:02:35 +0200 Subject: [PATCH 935/945] also remove entries from linkweight matrix --- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 969fd6bd..c5770a55 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -584,12 +584,16 @@ def load(self): partition.load_data(nodes_data) if invalid_uids: + w_matrix = partition.w.get_value() for uid in invalid_uids: partition = self.get_partition(uid) id = node_from_id(uid) partition.allocated_nodes[id] = 0 partition.allocated_node_parents[id] = 0 + els = partition.allocated_elements_to_nodes[np.where(partition.allocated_elements_to_nodes == id)] + w_matrix[els] = 0 partition.allocated_elements_to_nodes[np.where(partition.allocated_elements_to_nodes == id)] = 0 + partition.w.set_value(w_matrix) for partition in self.partitions.values(): partition.load_inlinks() From b8fb397d40c129bebbadfc7c6946a61d0d9490a3 Mon Sep 17 00:00:00 2001 From: Ronnie Vuine Date: Fri, 1 Sep 2017 17:17:11 +0200 Subject: [PATCH 936/945] Faster updates for the Hz number, see MESH-73 --- micropsi_core/runtime.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 68de25a4..4139d6bb 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -241,13 +241,15 @@ def run(self): self.sum_of_step_durations += step_ms self.number_of_samples += 1 self.total_steps += 1 - if self.total_steps % self.granularity == 0: - average_calc_duration = self.sum_of_calc_durations / self.number_of_samples + if self.total_steps % (self.granularity/10) == 0: average_step_duration = self.sum_of_step_durations / self.number_of_samples if average_step_duration > 0: nodenet.frequency = round((1 / average_step_duration) * 1000) else: nodenet.frequency = 0 + + if self.total_steps % self.granularity == 0: + average_calc_duration = self.sum_of_calc_durations / self.number_of_samples if self.profiler: import pstats import io From 249ad337e576a69a4d1c213f37adf7440077cfcc Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Fri, 1 Sep 2017 18:51:30 +0200 Subject: [PATCH 937/945] purge arrays asap we need to purge invalid entries from the arrays directly after loading, otherwise entries there will lead to node instances, so move this code to the partition --- .../nodenet/theano_engine/theano_nodenet.py | 17 ++++--------- .../nodenet/theano_engine/theano_partition.py | 13 +++++++++- .../tests/test_changing_definitions.py | 24 +++++++++++++++++-- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index c5770a55..67e67fbc 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -580,20 +580,11 @@ def load(self): # initialize invalid_uids = self.initialize_nodenet(initfrom) + for uid in invalid_uids: + del nodes_data[uid] + for partition in self.partitions.values(): - partition.load_data(nodes_data) - - if invalid_uids: - w_matrix = partition.w.get_value() - for uid in invalid_uids: - partition = self.get_partition(uid) - id = node_from_id(uid) - partition.allocated_nodes[id] = 0 - partition.allocated_node_parents[id] = 0 - els = partition.allocated_elements_to_nodes[np.where(partition.allocated_elements_to_nodes == id)] - w_matrix[els] = 0 - partition.allocated_elements_to_nodes[np.where(partition.allocated_elements_to_nodes == id)] = 0 - partition.w.set_value(w_matrix) + partition.load_data(nodes_data, invalid_uids=invalid_uids) for partition in self.partitions.values(): partition.load_inlinks() diff --git a/micropsi_core/nodenet/theano_engine/theano_partition.py b/micropsi_core/nodenet/theano_engine/theano_partition.py index f88d9634..a39e0500 100644 --- a/micropsi_core/nodenet/theano_engine/theano_partition.py +++ b/micropsi_core/nodenet/theano_engine/theano_partition.py @@ -822,7 +822,7 @@ def save(self, base_path=None, zipfile=None): else: np.savez(os.path.join(base_path, filename), **data) - def load_data(self, nodes_data): + def load_data(self, nodes_data, invalid_uids=[]): """Load the node net from a file""" # try to access file @@ -1017,6 +1017,17 @@ def load_data(self, nodes_data): else: self.logger.warning("no g_function_selector in file, falling back to defaults") + for uid in invalid_uids: + if self.nodenet.get_partition(uid) == self: + w_matrix = self.w.get_value() + id = node_from_id(uid) + self.allocated_nodes[id] = 0 + self.allocated_node_parents[id] = 0 + els = self.allocated_elements_to_nodes[np.where(self.allocated_elements_to_nodes == id)] + w_matrix[els] = 0 + self.allocated_elements_to_nodes[np.where(self.allocated_elements_to_nodes == id)] = 0 + self.w.set_value(w_matrix) + for id in np.nonzero(self.allocated_nodes)[0]: if self.allocated_nodes[id] > MAX_STD_NODETYPE: uid = node_to_id(id, self.pid) diff --git a/micropsi_core/tests/test_changing_definitions.py b/micropsi_core/tests/test_changing_definitions.py index 5af91050..975bc36d 100644 --- a/micropsi_core/tests/test_changing_definitions.py +++ b/micropsi_core/tests/test_changing_definitions.py @@ -61,9 +61,9 @@ def removedefs(*filenames): netapi.flow(foonode, 'X', foo2node, 'Y') netapi.flow(foo2node, 'X', 'worldadapter', 'action') runtime.save_nodenet(test_nodenet) + runtime.unload_nodenet(test_nodenet) # remove nativemodule - runtime.unload_nodenet(test_nodenet) removedefs('barnode.py') runtime.reload_code() net = runtime.get_nodenet(test_nodenet) @@ -71,9 +71,9 @@ def removedefs(*filenames): with pytest.raises(KeyError): net.netapi.get_node(barnode.uid) write_nativemodule('barnode.py', 'barnode') + runtime.unload_nodenet(test_nodenet) # remove a flowmodule - runtime.unload_nodenet(test_nodenet) removedefs('foonode.py') runtime.reload_code() net = runtime.get_nodenet(test_nodenet) @@ -83,3 +83,23 @@ def removedefs(*filenames): assert foonode.uid not in foo2node.inputmap['Y'] with pytest.raises(KeyError): net.netapi.get_node(foonode.uid) + runtime.unload_nodenet(test_nodenet) + + # change native module to flowmodule and vice versa + write_flowmodule('barnode.py', 'barnode') + write_nativemodule('foo2node.py', 'foo2node') + runtime.reload_code() + net = runtime.get_nodenet(test_nodenet) + with pytest.raises(KeyError): + foo2node = net.netapi.get_node(foo2node.uid) + with pytest.raises(KeyError): + barnode = net.netapi.get_node(barnode.uid) + with pytest.raises(KeyError): + net.netapi.get_node(foonode.uid) + newbarnode = net.netapi.create_node("barnode") + newfoo2node = net.netapi.create_node("foo2node") + neuron = net.netapi.get_node(neuron.uid) + net.netapi.link(neuron, 'gen', newbarnode, 'sub') + net.netapi.link(neuron, 'gen', newfoo2node, 'foo') + net.netapi.flow('worldadapter', 'vision', newbarnode, 'Y') + net.netapi.flow(newbarnode, 'X', 'worldadapter', 'action') From 1987460d294160662957eef5d8dc01a6421ab036 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 12:18:01 +0200 Subject: [PATCH 938/945] set infguard via runner settings --- micropsi_core/runtime.py | 15 ++++++++++----- micropsi_server/micropsi_app.py | 6 +++--- micropsi_server/tests/test_json_api.py | 4 +++- micropsi_server/view/runner_form.tpl | 11 +++++++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index 3c5f42cf..c6d8ab0a 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -34,7 +34,7 @@ NODENET_DIRECTORY = "nodenets" WORLD_DIRECTORY = "worlds" -runner = {'timestep': 1000, 'runner': None} +runner = {'timestep': 1000, 'runner': None, 'infguard': True} nodenet_lock = threading.Lock() @@ -670,14 +670,16 @@ def start_nodenetrunner(nodenet_uid): return True -def set_runner_properties(timestep): +def set_runner_properties(timestep, infguard=False): """Sets the speed of the nodenet calculation in ms. Argument: timestep: sets the calculation speed. """ runner_config['runner_timestep'] = timestep + runner_config['runner_infguard'] = bool(infguard) runner['timestep'] = timestep + runner['infguard'] = bool(infguard) return True @@ -707,7 +709,8 @@ def remove_runner_condition(nodenet_uid): def get_runner_properties(): """Returns the speed that has been configured for the nodenet runner (in ms).""" return { - 'timestep': runner_config['runner_timestep'] + 'timestep': runner_config['runner_timestep'], + 'infguard': runner_config['runner_infguard'] } @@ -2033,9 +2036,11 @@ def plotter_initializer(): # Initialize the threads for the continuous calculation of nodenets and worlds if 'runner_timestep' not in runner_config: runner_config['runner_timestep'] = 10 - runner_config.save_configs() + if 'infguard' not in runner_config: + runner_config['runner_infguard'] = True + runner_config.save_configs() - set_runner_properties(runner_config['runner_timestep']) + set_runner_properties(runner_config['runner_timestep'], runner_config['runner_infguard']) runner['running'] = True if runner.get('runner') is None: diff --git a/micropsi_server/micropsi_app.py b/micropsi_server/micropsi_app.py index 0d6606c3..3b458a96 100755 --- a/micropsi_server/micropsi_app.py +++ b/micropsi_server/micropsi_app.py @@ -792,7 +792,7 @@ def world_list(current_world=None): def edit_runner_properties(): user_id, permissions, token = get_request_data() if len(request.params) > 0: - runtime.set_runner_properties(int(request.params['timestep'])) + runtime.set_runner_properties(int(request.params['timestep']), bool(request.params.get('infguard'))) return dict(status="success", msg="Settings saved") else: return template("runner_form", action="/config/runner", value=runtime.get_runner_properties()) @@ -968,10 +968,10 @@ def remove_runner_condition(nodenet_uid): @rpc("set_runner_properties", permission_required="manage server") -def set_runner_properties(timestep): +def set_runner_properties(timestep, infguard): """ Configure the server-settings: timestep: miliseconds per nodenet-step""" - return runtime.set_runner_properties(timestep) + return runtime.set_runner_properties(timestep, infguard) @rpc("get_runner_properties") diff --git a/micropsi_server/tests/test_json_api.py b/micropsi_server/tests/test_json_api.py index 46de75e1..1cf0667e 100644 --- a/micropsi_server/tests/test_json_api.py +++ b/micropsi_server/tests/test_json_api.py @@ -170,15 +170,17 @@ def test_get_runner_properties(app): response = app.get_json('/rpc/get_runner_properties()') assert_success(response) assert 'timestep' in response.json_body['data'] + assert 'infguard' in response.json_body['data'] def test_set_runner_properties(app): app.set_auth() - response = app.post_json('/rpc/set_runner_properties', params=dict(timestep=123)) + response = app.post_json('/rpc/set_runner_properties', params=dict(timestep=123, infguard=False)) assert_success(response) response = app.get_json('/rpc/get_runner_properties()') assert_success(response) assert response.json_body['data']['timestep'] == 123 + assert not response.json_body['data']['infguard'] def test_get_is_calculation_running(app, default_nodenet): diff --git a/micropsi_server/view/runner_form.tpl b/micropsi_server/view/runner_form.tpl index 43b26cba..5066707b 100644 --- a/micropsi_server/view/runner_form.tpl +++ b/micropsi_server/view/runner_form.tpl @@ -31,6 +31,17 @@ %end +
    + +
    + +
    +
    + From de4ad86a54a9df705824fb412941947afc7c6b8d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 14:00:46 +0200 Subject: [PATCH 939/945] remove the config flag, use the runner_config --- config.default.ini | 3 --- conftest.py | 1 - micropsi_core/nodenet/theano_engine/theano_nodenet.py | 7 +++++-- .../nodenet/theano_engine/theano_stepoperators.py | 3 +-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/config.default.ini b/config.default.ini index 606df784..13773886 100644 --- a/config.default.ini +++ b/config.default.ini @@ -87,6 +87,3 @@ initial_number_of_nodes = 2000 # should use a (7 + 1) / 2 = 4 elements assumption # pure register partitions can use a 1 element assumption elements_per_node_assumption = 4 - -# raise an exception when nans or infs appear in any flowmodule -flow_finite_guard = 1 diff --git a/conftest.py b/conftest.py index a4886442..15867747 100644 --- a/conftest.py +++ b/conftest.py @@ -33,7 +33,6 @@ cfg['micropsi2']['single_agent_mode'] = '' if 'theano' in cfg: cfg['theano']['initial_number_of_nodes'] = '50' - cfg['theano']['flow_finite_guard'] = '1' if 'on_exception' in cfg['micropsi2']: cfg['micropsi2']['on_exception'] = '' cfg['micropsi2']['auto_save_intervals'] = '100' diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index 18f61fe6..c273cc97 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -102,6 +102,11 @@ class TheanoNodenet(Nodenet): def engine(self): return "theano_engine" + @property + def flow_infguard(self): + from micropsi_core.runtime import runner_config + return runner_config["runner_infguard"] + @property def worldadapter_instance(self): return self._worldadapter_instance @@ -164,8 +169,6 @@ def __init__(self, persistency_path, name="", worldadapter="Default", world=None else: raise RuntimeError("Unsupported float precision value") - self.flow_finite_guard = settings['theano'].get('flow_finite_guard') - device = T.config.device self.logger.info("Theano configured to use %s", device) if device.startswith("gpu"): diff --git a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py index 43c54eb8..7a9a67f8 100644 --- a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py +++ b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py @@ -75,12 +75,11 @@ def priority(self): def __init__(self, nodenet): self.nodenet = nodenet - self.flow_finite_guard = nodenet.flow_finite_guard def value_guard(self, value, source, name): if value is None: return None - if self.flow_finite_guard: + if self.nodenet.flow_infguard: if np.isnan(np.sum(value)): raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) elif np.isinf(np.sum(value)): From 6a64203a62c008213a34a0a1b8f9b50ff12024d4 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 14:20:48 +0200 Subject: [PATCH 940/945] pass runner_config into step method --- micropsi_core/nodenet/nodenet.py | 3 ++- micropsi_core/nodenet/theano_engine/theano_nodenet.py | 5 ----- .../nodenet/theano_engine/theano_stepoperators.py | 2 +- micropsi_core/runtime.py | 10 +++++----- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index b498a7f9..19d0d345 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -288,8 +288,9 @@ def load(self): """ pass # pragma: no cover - def timed_step(self): + def timed_step(self, runner_config={}): start = datetime.now() + self.runner_config = runner_config self.step() elapsed = datetime.now() - start self.stepping_rate.append(elapsed.seconds + ((elapsed.microseconds // 1000) / 1000)) diff --git a/micropsi_core/nodenet/theano_engine/theano_nodenet.py b/micropsi_core/nodenet/theano_engine/theano_nodenet.py index c273cc97..bef68527 100644 --- a/micropsi_core/nodenet/theano_engine/theano_nodenet.py +++ b/micropsi_core/nodenet/theano_engine/theano_nodenet.py @@ -102,11 +102,6 @@ class TheanoNodenet(Nodenet): def engine(self): return "theano_engine" - @property - def flow_infguard(self): - from micropsi_core.runtime import runner_config - return runner_config["runner_infguard"] - @property def worldadapter_instance(self): return self._worldadapter_instance diff --git a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py index 7a9a67f8..c1a1740a 100644 --- a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py +++ b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py @@ -79,7 +79,7 @@ def __init__(self, nodenet): def value_guard(self, value, source, name): if value is None: return None - if self.nodenet.flow_infguard: + if self.nodenet.runner_config.get('runner_infguard'): if np.isnan(np.sum(value)): raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) elif np.isinf(np.sum(value)): diff --git a/micropsi_core/runtime.py b/micropsi_core/runtime.py index c6d8ab0a..ed4a42e2 100755 --- a/micropsi_core/runtime.py +++ b/micropsi_core/runtime.py @@ -177,7 +177,7 @@ def run(self): continue log = True try: - nodenet.timed_step() + nodenet.timed_step(runner_config.data) nodenet.update_monitors_and_recorders() except: stop_nodenetrunner(uid) @@ -755,7 +755,7 @@ def step_nodenet(nodenet_uid): runner['runner'].resume() worlds[nodenet.world].simulation_started() - nodenet.timed_step() + nodenet.timed_step(runner_config.data) if runtime_config['micropsi2'].get('profile_runner'): profiler.disable() @@ -780,7 +780,7 @@ def single_step_nodenet_only(nodenet_uid): profiler = cProfile.Profile() profiler.enable() - nodenet.timed_step() + nodenet.timed_step(runner_config.data) if runtime_config['micropsi2'].get('profile_runner'): profiler.disable() @@ -808,13 +808,13 @@ def step_nodenets_in_world(world_uid, nodenet_uid=None, steps=1): nodenet = get_nodenet(nodenet_uid) if nodenet and nodenet.world == world_uid: for i in range(steps): - nodenet.timed_step() + nodenet.timed_step(runner_config.data) nodenet.update_monitors_and_recorders() else: for i in range(steps): for uid in worlds[world_uid].agents: nodenet = get_nodenet(uid) - nodenet.timed_step() + nodenet.timed_step(runner_config.data) nodenet.update_monitors_and_recorders() return True From c4581153211dcad626aa91387ed12d582fa4922d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 14:21:06 +0200 Subject: [PATCH 941/945] use infguard in tests --- conftest.py | 4 ++-- micropsi_core/tests/test_flowmodules.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/conftest.py b/conftest.py index 15867747..9d6e7f3a 100644 --- a/conftest.py +++ b/conftest.py @@ -75,7 +75,7 @@ def pytest_cmdline_main(config): usermanager.create_user('Pytest User', 'test', 'Administrator', uid='Pytest User') usermanager.start_session('Pytest User', 'test', True) set_logging_levels() - micropsi_runtime.set_runner_properties(1) + micropsi_runtime.set_runner_properties(1, True) def pytest_configure(config): @@ -129,7 +129,7 @@ def pytest_runtest_setup(item): open(os.path.join(testpath, 'nodetypes', 'Test', '__init__.py'), 'w').close() micropsi_runtime.reload_code() micropsi_runtime.logger.clear_logs() - micropsi_runtime.set_runner_properties(0) + micropsi_runtime.set_runner_properties(0, True) set_logging_levels() diff --git a/micropsi_core/tests/test_flowmodules.py b/micropsi_core/tests/test_flowmodules.py index caa3875f..784860a8 100644 --- a/micropsi_core/tests/test_flowmodules.py +++ b/micropsi_core/tests/test_flowmodules.py @@ -220,7 +220,7 @@ def trpoinpython(X, Y, netapi, node, parameters): import numpy as np def infmaker(netapi, node, parameters): - data = np.ones(12) + data = np.ones(12).astype(netapi.floatX) what = np.nan if parameters['what'] == 'inf': what = np.inf @@ -1148,19 +1148,19 @@ def test_flow_inf_guard(runtime, test_nodenet, default_world, resourcepath): netapi.link(source, 'gen', source, 'gen') netapi.link(source, 'gen', add, 'sub') with pytest.raises(ValueError) as excinfo: - nodenet.step() + runtime.step_nodenet(test_nodenet) assert "output A" in str(excinfo.value) assert "infmaker" in str(excinfo.value) assert "NAN value" in str(excinfo.value) infmaker.set_parameter('what', 'inf') with pytest.raises(ValueError) as excinfo: - nodenet.step() + runtime.step_nodenet(test_nodenet) assert "INF value" in str(excinfo.value) worldadapter.flow_datasources['foo'][3] = np.nan with pytest.raises(ValueError) as excinfo: - nodenet.step() + runtime.step_nodenet(test_nodenet) assert type(worldadapter).__name__ in str(excinfo.value) assert "foo" in str(excinfo.value) @@ -1182,6 +1182,6 @@ def test_flow_inf_guard_on_list_outputs(runtime, test_nodenet, default_world, re netapi.link(source, 'gen', source, 'gen') netapi.link(source, 'gen', trpoin, 'sub') with pytest.raises(ValueError) as excinfo: - nodenet.step() + runtime.step_nodenet(test_nodenet) assert "INF value in" in str(excinfo.value) assert "output A of graph" in str(excinfo.value) From 62e97c399eef02e4afc7de2a7701a6bdae06b0c8 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 14:36:30 +0200 Subject: [PATCH 942/945] need to set runner_config initially --- micropsi_core/nodenet/nodenet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropsi_core/nodenet/nodenet.py b/micropsi_core/nodenet/nodenet.py index 19d0d345..a2501b9f 100644 --- a/micropsi_core/nodenet/nodenet.py +++ b/micropsi_core/nodenet/nodenet.py @@ -164,6 +164,7 @@ def __init__(self, persistency_path, name="", worldadapter="Default", world=None self._uid = uid self._runner_condition = None + self.runner_config = {} self.owner = owner self._monitors = {} self._recorders = {} From c233a9bd01ff033cd35c65ead24b64489ba4b834 Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 16:38:04 +0200 Subject: [PATCH 943/945] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc80edc7..8fc041d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ 0.12-alpha10 (unreleased) ========== + * Improved user prompts + * Inf/NaN guard for flowmodules + * Realtime world support + * Reliable code reloading 0.11-alpha9 (2017-06-26) From aa8988933c1fa98818324a1e8264f622f0c7c22e Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 16:38:15 +0200 Subject: [PATCH 944/945] release 0.12-alpha10 --- CHANGELOG.md | 2 +- configuration.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc041d9..150e6a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -0.12-alpha10 (unreleased) +0.12-alpha10 (2017-09-04) ========== * Improved user prompts * Inf/NaN guard for flowmodules diff --git a/configuration.py b/configuration.py index 1131edf9..738d98f1 100644 --- a/configuration.py +++ b/configuration.py @@ -51,7 +51,7 @@ def makedirs(path): warnings.warn('Can not read config from inifile %s' % configini) raise RuntimeError('Can not read config from inifile %s' % configini) -config['micropsi2']['version'] = "0.12-alpha10-dev" +config['micropsi2']['version'] = "0.12-alpha10" config['micropsi2']['apptitle'] = "MicroPsi" data_path = os.path.expanduser(config['micropsi2']['data_directory']) From 06387b738b7ead63d359b078a8b562b0c32b2e4d Mon Sep 17 00:00:00 2001 From: Dominik Welland Date: Mon, 4 Sep 2017 18:58:30 +0200 Subject: [PATCH 945/945] fix for the nanguard --- .../nodenet/theano_engine/theano_stepoperators.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py index c1a1740a..a6362e83 100644 --- a/micropsi_core/nodenet/theano_engine/theano_stepoperators.py +++ b/micropsi_core/nodenet/theano_engine/theano_stepoperators.py @@ -80,12 +80,19 @@ def value_guard(self, value, source, name): if value is None: return None if self.nodenet.runner_config.get('runner_infguard'): - if np.isnan(np.sum(value)): - raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) - elif np.isinf(np.sum(value)): - raise ValueError("INF value in flow datected: %s" % self.format_error(source, name)) + if type(value) == list: + for val in value: + self._guard(val, source, name) + else: + self._guard(value, source, name) return value + def _guard(self, value, source, name): + if np.isnan(np.sum(value)): + raise ValueError("NAN value in flow datected: %s" % self.format_error(source, name)) + elif np.isinf(np.sum(value)): + raise ValueError("INF value in flow datected: %s" % self.format_error(source, name)) + def format_error(self, source, name): if type(source) == dict: if len(source['members']) == 1: