import tensorflow as tf
import numpy as np
import time
import os
from enum import Enum
class MappingType(Enum):
Identity = 0
Linear = 1
Affine = 2
class ODESolver(Enum):
SemiImplicit = 0
Explicit = 1
RungeKutta = 2
class LTCCell(tf.nn.rnn_cell.RNNCell):
def __init__(self, num_units):
self._input_size = -1
self._num_units = num_units
self._is_built = False
# Number of ODE solver steps in one RNN step
self._ode_solver_unfolds = 6
self._solver = ODESolver.SemiImplicit
self._input_mapping = MappingType.Affine
self._erev_init_factor = 1
self._w_init_max = 1.0
self._w_init_min = 0.01
self._cm_init_min = 0.5
self._cm_init_max = 0.5
self._gleak_init_min = 1
self._gleak_init_max = 1
self._w_min_value = 0.00001
self._w_max_value = 1000
self._gleak_min_value = 0.00001
self._gleak_max_value = 1000
self._cm_t_min_value = 0.000001
self._cm_t_max_value = 1000
self._fix_cm = None
self._fix_gleak = None
self._fix_vleak = None
@property
def state_size(self):
return self._num_units
@property
def output_size(self):
return self._num_units
def _map_inputs(self,inputs,resuse_scope=False):
varscope = "sensory_mapping"
reuse = tf.AUTO_REUSE
if(resuse_scope):
varscope = self._sensory_varscope
reuse = True
with tf.variable_scope(varscope,reuse=reuse) as scope:
self._sensory_varscope = scope
if(self._input_mapping == MappingType.Affine or self._input_mapping == MappingType.Linear):
w = tf.get_variable(name='input_w',shape=[self._input_size],trainable=True,initializer=tf.initializers.constant(1))
inputs = inputs * w
if(self._input_mapping == MappingType.Affine):
b = tf.get_variable(name='input_b',shape=[self._input_size],trainable=True,initializer=tf.initializers.constant(0))
inputs = inputs + b
return inputs
# TODO: Implement RNNLayer properly,i.e, allocate variables here
def build(self,input_shape):
pass
def __call__(self, inputs, state, scope=None):
with tf.variable_scope("ltc"):
if(not self._is_built):
# TODO: Move this part into the build method inherited form tf.Layers
self._is_built = True
self._input_size = int(inputs.shape[-1])
self._get_variables()
elif(self._input_size != int(inputs.shape[-1])):
raise ValueError("You first feed an input with {} features and now one with {} features, that is not possible".format(
self._input_size,
int(inputs[-1])
))
inputs = self._map_inputs(inputs)
if(self._solver == ODESolver.Explicit):
next_state = self._ode_step_explicit(inputs,state,_ode_solver_unfolds=self._ode_solver_unfolds)
elif(self._solver == ODESolver.SemiImplicit):
next_state = self._ode_step(inputs,state)
elif(self._solver == ODESolver.RungeKutta):
next_state = self._ode_step_runge_kutta(inputs,state)
else:
raise ValueError("Unknown ODE solver '{}'".format(str(self._solver)))
outputs = next_state
return outputs, next_state
# Create tf variables
def _get_variables(self):
self.sensory_mu = tf.get_variable(name='sensory_mu',shape=[self._input_size,self._num_units],trainable=True,initializer=tf.initializers.random_uniform(minval=0.3,maxval=0.8))
self.sensory_sigma = tf.get_variable(name='sensory_sigma',shape=[self._input_size,self._num_units],trainable=True,initializer=tf.initializers.random_uniform(minval=3.0,maxval=8.0))
self.sensory_W = tf.get_variable(name='sensory_W',shape=[self._input_size,self._num_units],trainable=True,initializer=tf.initializers.constant(np.random.uniform(low=self._w_init_min,high=self._w_init_max,size=[self._input_size,self._num_units])))
sensory_erev_init = 2*np.random.randint(low=0,high=2,size=[self._input_size,self._num_units])-1
self.sensory_erev = tf.get_variable(name='sensory_erev',shape=[self._input_size,self._num_units],trainable=True,initializer=tf.initializers.constant(sensory_erev_init*self._erev_init_factor))
self.mu = tf.get_variable(name='mu',shape=[self._num_units,self._num_units],trainable=True,initializer=tf.initializers.random_uniform(minval=0.3,maxval=0.8))
self.sigma = tf.get_variable(name='sigma',shape=[self._num_units,self._num_units],trainable=True,initializer=tf.initializers.random_uniform(minval=3.0,maxval=8.0))
self.W = tf.get_variable(name='W',shape=[self._num_units,self._num_units],trainable=True,initializer=tf.initializers.constant(np.random.uniform(low=self._w_init_min,high=self._w_init_max,size=[self._num_units,self._num_units])))
erev_init = 2*np.random.randint(low=0,high=2,size=[self._num_units,self._num_units])-1
self.erev = tf.get_variable(name='erev',shape=[self._num_units,self._num_units],trainable=True,initializer=tf.initializers.constant(erev_init*self._erev_init_factor))
if(self._fix_vleak is None):
self.vleak = tf.get_variable(name='vleak',shape=[self._num_units],trainable=True,initializer=tf.initializers.random_uniform(minval=-0.2,maxval=0.2))
else:
self.vleak = tf.get_variable(name='vleak',shape=[self._num_units],trainable=False,initializer=tf.initializers.constant(self._fix_vleak))
if(self._fix_gleak is None):
initializer=tf.initializers.constant(self._gleak_init_min)
if(self._gleak_init_max > self._gleak_init_min):
initializer = tf.initializers.random_uniform(minval= self._gleak_init_min,maxval = self._gleak_init_max)
self.gleak = tf.get_variable(name='gleak',shape=[self._num_units],trainable=True,initializer=initializer)
else:
self.gleak = tf.get_variable(name='gleak',shape=[self._num_units],trainable=False,initializer=tf.initializers.constant(self._fix_gleak))
if(self._fix_cm is None):
initializer=tf.initializers.constant(self._cm_init_min)
if(self._cm_init_max > self._cm_init_min):
initializer = tf.initializers.random_uniform(minval= self._cm_init_min,maxval = self._cm_init_max)
self.cm_t = tf.get_variable(name='cm_t',shape=[self._num_units],trainable=True,initializer=initializer)
else:
self.cm_t = tf.get_variable(name='cm_t',shape=[self._num_units],trainable=False,initializer=tf.initializers.constant(self._fix_cm))
# Hybrid euler method
def _ode_step(self,inputs,state):
v_pre = state
sensory_w_activation = self.sensory_W*self._sigmoid(inputs,self.sensory_mu,self.sensory_sigma)
sensory_rev_activation = sensory_w_activation*self.sensory_erev
w_numerator_sensory = tf.reduce_sum(sensory_rev_activation,axis=1)
w_denominator_sensory = tf.reduce_sum(sensory_w_activation,axis=1)
for t in range(self._ode_solver_unfolds):
w_activation = self.W*self._sigmoid(v_pre,self.mu,self.sigma)
rev_activation = w_activation*self.erev
w_numerator = tf.reduce_sum(rev_activation,axis=1) + w_numerator_sensory
w_denominator = tf.reduce_sum(w_activation,axis=1) + w_denominator_sensory
numerator = self.cm_t * v_pre + self.gleak*self.vleak + w_numerator
denominator = self.cm_t + self.gleak + w_denominator
v_pre = numerator/denominator
return v_pre
def _f_prime(self,inputs,state):
v_pre = state
# We can pre-compute the effects of the sensory neurons here
sensory_w_activation = self.sensory_W*self._sigmoid(inputs,self.sensory_mu,self.sensory_sigma)
w_reduced_sensory = tf.reduce_sum(sensory_w_activation,axis=1)
# Unfold the mutliply ODE multiple times into one RNN step
w_activation = self.W*self._sigmoid(v_pre,self.mu,self.sigma)
w_reduced_synapse = tf.reduce_sum(w_activation,axis=1)
sensory_in = self.sensory_erev * sensory_w_activation
synapse_in = self.erev * w_activation
sum_in = tf.reduce_sum(sensory_in,axis=1) - v_pre*w_reduced_synapse + tf.reduce_sum(synapse_in,axis=1) - v_pre * w_reduced_sensory
f_prime = 1/self.cm_t * (self.gleak * (self.vleak-v_pre) + sum_in)
return f_prime
def _ode_step_runge_kutta(self,inputs,state):
h = 0.1
for i in range(self._ode_solver_unfolds):
k1 = h*self._f_prime(inputs,state)
k2 = h*self._f_prime(inputs,state+k1*0.5)
k3 = h*self._f_prime(inputs,state+k2*0.5)
k4 = h*self._f_prime(inputs,state+k3)
state = state + 1.0/6*(k1+2*k2+2*k3+k4)
return state
def _ode_step_explicit(self,inputs,state,_ode_solver_unfolds):
v_pre = state
# We can pre-compute the effects of the sensory neurons here
sensory_w_activation = self.sensory_W*self._sigmoid(inputs,self.sensory_mu,self.sensory_sigma)
w_reduced_sensory = tf.reduce_sum(sensory_w_activation,axis=1)
# Unfold the mutliply ODE multiple times into one RNN step
for t in range(_ode_solver_unfolds):
w_activation = self.W*self._sigmoid(v_pre,self.mu,self.sigma)
w_reduced_synapse = tf.reduce_sum(w_activation,axis=1)
sensory_in = self.sensory_erev * sensory_w_activation
synapse_in = self.erev * w_activation
sum_in = tf.reduce_sum(sensory_in,axis=1) - v_pre*w_reduced_synapse + tf.reduce_sum(synapse_in,axis=1) - v_pre * w_reduced_sensory
f_prime = 1/self.cm_t * (self.gleak * (self.vleak-v_pre) + sum_in)
v_pre = v_pre + 0.1 * f_prime
return v_pre
def _sigmoid(self,v_pre,mu,sigma):
v_pre = tf.reshape(v_pre,[-1,v_pre.shape[-1],1])
mues = v_pre - mu
x = sigma*mues
return tf.nn.sigmoid(x)
def get_param_constrain_op(self):
cm_clipping_op = tf.assign(self.cm_t,tf.clip_by_value(self.cm_t, self._cm_t_min_value, self._cm_t_max_value))
gleak_clipping_op = tf.assign(self.gleak,tf.clip_by_value(self.gleak, self._gleak_min_value, self._gleak_max_value))
w_clipping_op = tf.assign(self.W,tf.clip_by_value(self.W, self._w_min_value, self._w_max_value))
sensory_w_clipping_op = tf.assign(self.sensory_W ,tf.clip_by_value(self.sensory_W, self._w_min_value, self._w_max_value))
return [cm_clipping_op,gleak_clipping_op,w_clipping_op,sensory_w_clipping_op]
def export_weights(self,dirname,sess,output_weights=None):
os.makedirs(dirname,exist_ok=True)
w,erev,mu,sigma = sess.run([self.W,self.erev,self.mu,self.sigma])
sensory_w,sensory_erev,sensory_mu,sensory_sigma = sess.run([self.sensory_W,self.sensory_erev,self.sensory_mu,self.sensory_sigma])
vleak,gleak,cm = sess.run([self.vleak,self.gleak,self.cm_t])
if(not output_weights is None):
output_w,output_b = sess.run(output_weights)
np.savetxt(os.path.join(dirname,"output_w.csv"),output_w)
np.savetxt(os.path.join(dirname,"output_b.csv"),output_b)
np.savetxt(os.path.join(dirname,"w.csv"),w)
np.savetxt(os.path.join(dirname,"erev.csv"),erev)
np.savetxt(os.path.join(dirname,"mu.csv"),mu)
np.savetxt(os.path.join(dirname,"sigma.csv"),sigma)
np.savetxt(os.path.join(dirname,"sensory_w.csv"),sensory_w)
np.savetxt(os.path.join(dirname,"sensory_erev.csv"),sensory_erev)
np.savetxt(os.path.join(dirname,"sensory_mu.csv"),sensory_mu)
np.savetxt(os.path.join(dirname,"sensory_sigma.csv"),sensory_sigma)
np.savetxt(os.path.join(dirname,"vleak.csv"),vleak)
np.savetxt(os.path.join(dirname,"gleak.csv"),gleak)
np.savetxt(os.path.join(dirname,"cm.csv"),cm)
aW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0aW1lCmltcG9ydCBvcwpmcm9tIGVudW0gaW1wb3J0IEVudW0KCmNsYXNzIE1hcHBpbmdUeXBlKEVudW0pOgogICAgSWRlbnRpdHkgPSAwCiAgICBMaW5lYXIgPSAxCiAgICBBZmZpbmUgPSAyCgpjbGFzcyBPREVTb2x2ZXIoRW51bSk6CiAgICBTZW1pSW1wbGljaXQgPSAwCiAgICBFeHBsaWNpdCA9IDEKICAgIFJ1bmdlS3V0dGEgPSAyCgpjbGFzcyBMVENDZWxsKHRmLm5uLnJubl9jZWxsLlJOTkNlbGwpOgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBudW1fdW5pdHMpOgoKICAgICAgICBzZWxmLl9pbnB1dF9zaXplID0gLTEKICAgICAgICBzZWxmLl9udW1fdW5pdHMgPSBudW1fdW5pdHMKICAgICAgICBzZWxmLl9pc19idWlsdCA9IEZhbHNlCgogICAgICAgICMgTnVtYmVyIG9mIE9ERSBzb2x2ZXIgc3RlcHMgaW4gb25lIFJOTiBzdGVwCiAgICAgICAgc2VsZi5fb2RlX3NvbHZlcl91bmZvbGRzID0gNgogICAgICAgIHNlbGYuX3NvbHZlciA9IE9ERVNvbHZlci5TZW1pSW1wbGljaXQKCiAgICAgICAgc2VsZi5faW5wdXRfbWFwcGluZyA9IE1hcHBpbmdUeXBlLkFmZmluZQoKICAgICAgICBzZWxmLl9lcmV2X2luaXRfZmFjdG9yID0gMQoKICAgICAgICBzZWxmLl93X2luaXRfbWF4ID0gMS4wCiAgICAgICAgc2VsZi5fd19pbml0X21pbiA9IDAuMDEKICAgICAgICBzZWxmLl9jbV9pbml0X21pbiA9IDAuNQogICAgICAgIHNlbGYuX2NtX2luaXRfbWF4ID0gMC41CiAgICAgICAgc2VsZi5fZ2xlYWtfaW5pdF9taW4gPSAxCiAgICAgICAgc2VsZi5fZ2xlYWtfaW5pdF9tYXggPSAxCiAgICAgICAgCiAgICAgICAgc2VsZi5fd19taW5fdmFsdWUgPSAwLjAwMDAxCiAgICAgICAgc2VsZi5fd19tYXhfdmFsdWUgPSAxMDAwCiAgICAgICAgc2VsZi5fZ2xlYWtfbWluX3ZhbHVlID0gMC4wMDAwMQogICAgICAgIHNlbGYuX2dsZWFrX21heF92YWx1ZSA9IDEwMDAKICAgICAgICBzZWxmLl9jbV90X21pbl92YWx1ZSA9IDAuMDAwMDAxCiAgICAgICAgc2VsZi5fY21fdF9tYXhfdmFsdWUgPSAxMDAwCgogICAgICAgIHNlbGYuX2ZpeF9jbSA9IE5vbmUKICAgICAgICBzZWxmLl9maXhfZ2xlYWsgPSBOb25lCiAgICAgICAgc2VsZi5fZml4X3ZsZWFrID0gTm9uZQogICAgICAgIAogICAgQHByb3BlcnR5CiAgICBkZWYgc3RhdGVfc2l6ZShzZWxmKToKICAgICAgICByZXR1cm4gc2VsZi5fbnVtX3VuaXRzCgogICAgQHByb3BlcnR5CiAgICBkZWYgb3V0cHV0X3NpemUoc2VsZik6CiAgICAgICAgcmV0dXJuIHNlbGYuX251bV91bml0cwoKICAgIGRlZiBfbWFwX2lucHV0cyhzZWxmLGlucHV0cyxyZXN1c2Vfc2NvcGU9RmFsc2UpOgogICAgICAgIHZhcnNjb3BlID0gInNlbnNvcnlfbWFwcGluZyIKICAgICAgICByZXVzZSA9IHRmLkFVVE9fUkVVU0UKICAgICAgICBpZihyZXN1c2Vfc2NvcGUpOgogICAgICAgICAgICB2YXJzY29wZSA9IHNlbGYuX3NlbnNvcnlfdmFyc2NvcGUKICAgICAgICAgICAgcmV1c2UgPSBUcnVlCgogICAgICAgIHdpdGggdGYudmFyaWFibGVfc2NvcGUodmFyc2NvcGUscmV1c2U9cmV1c2UpIGFzIHNjb3BlOgogICAgICAgICAgICBzZWxmLl9zZW5zb3J5X3ZhcnNjb3BlID0gc2NvcGUKICAgICAgICAgICAgaWYoc2VsZi5faW5wdXRfbWFwcGluZyA9PSBNYXBwaW5nVHlwZS5BZmZpbmUgb3Igc2VsZi5faW5wdXRfbWFwcGluZyA9PSBNYXBwaW5nVHlwZS5MaW5lYXIpOgogICAgICAgICAgICAgICAgdyA9ICB0Zi5nZXRfdmFyaWFibGUobmFtZT0naW5wdXRfdycsc2hhcGU9W3NlbGYuX2lucHV0X3NpemVdLHRyYWluYWJsZT1UcnVlLGluaXRpYWxpemVyPXRmLmluaXRpYWxpemVycy5jb25zdGFudCgxKSkKICAgICAgICAgICAgICAgIGlucHV0cyA9IGlucHV0cyAqIHcKICAgICAgICAgICAgaWYoc2VsZi5faW5wdXRfbWFwcGluZyA9PSBNYXBwaW5nVHlwZS5BZmZpbmUpOgogICAgICAgICAgICAgICAgYiA9ICB0Zi5nZXRfdmFyaWFibGUobmFtZT0naW5wdXRfYicsc2hhcGU9W3NlbGYuX2lucHV0X3NpemVdLHRyYWluYWJsZT1UcnVlLGluaXRpYWxpemVyPXRmLmluaXRpYWxpemVycy5jb25zdGFudCgwKSkKICAgICAgICAgICAgICAgIGlucHV0cyA9IGlucHV0cyArIGIKICAgICAgICByZXR1cm4gaW5wdXRzCgogICAgIyBUT0RPOiBJbXBsZW1lbnQgUk5OTGF5ZXIgcHJvcGVybHksaS5lLCBhbGxvY2F0ZSB2YXJpYWJsZXMgaGVyZQogICAgZGVmIGJ1aWxkKHNlbGYsaW5wdXRfc2hhcGUpOgogICAgICAgIHBhc3MKCiAgICBkZWYgX19jYWxsX18oc2VsZiwgaW5wdXRzLCBzdGF0ZSwgc2NvcGU9Tm9uZSk6CiAgICAgICAgd2l0aCB0Zi52YXJpYWJsZV9zY29wZSgibHRjIik6CiAgICAgICAgICAgIGlmKG5vdCBzZWxmLl9pc19idWlsdCk6CiAgICAgICAgICAgICAgICAjIFRPRE86IE1vdmUgdGhpcyBwYXJ0IGludG8gdGhlIGJ1aWxkIG1ldGhvZCBpbmhlcml0ZWQgZm9ybSB0Zi5MYXllcnMKICAgICAgICAgICAgICAgIHNlbGYuX2lzX2J1aWx0ID0gVHJ1ZQogICAgICAgICAgICAgICAgc2VsZi5faW5wdXRfc2l6ZSA9IGludChpbnB1dHMuc2hhcGVbLTFdKQoKICAgICAgICAgICAgICAgIHNlbGYuX2dldF92YXJpYWJsZXMoKQoKICAgICAgICAgICAgZWxpZihzZWxmLl9pbnB1dF9zaXplICE9IGludChpbnB1dHMuc2hhcGVbLTFdKSk6CiAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJZb3UgZmlyc3QgZmVlZCBhbiBpbnB1dCB3aXRoIHt9IGZlYXR1cmVzIGFuZCBub3cgb25lIHdpdGgge30gZmVhdHVyZXMsIHRoYXQgaXMgbm90IHBvc3NpYmxlIi5mb3JtYXQoCiAgICAgICAgICAgICAgICAgICAgc2VsZi5faW5wdXRfc2l6ZSwKICAgICAgICAgICAgICAgICAgICBpbnQoaW5wdXRzWy0xXSkKICAgICAgICAgICAgICAgICkpCiAgICAgICAgICAgIAogICAgICAgICAgICBpbnB1dHMgPSBzZWxmLl9tYXBfaW5wdXRzKGlucHV0cykKCiAgICAgICAgICAgIGlmKHNlbGYuX3NvbHZlciA9PSBPREVTb2x2ZXIuRXhwbGljaXQpOgogICAgICAgICAgICAgICAgbmV4dF9zdGF0ZSA9IHNlbGYuX29kZV9zdGVwX2V4cGxpY2l0KGlucHV0cyxzdGF0ZSxfb2RlX3NvbHZlcl91bmZvbGRzPXNlbGYuX29kZV9zb2x2ZXJfdW5mb2xkcykKICAgICAgICAgICAgZWxpZihzZWxmLl9zb2x2ZXIgPT0gT0RFU29sdmVyLlNlbWlJbXBsaWNpdCk6CiAgICAgICAgICAgICAgICBuZXh0X3N0YXRlID0gc2VsZi5fb2RlX3N0ZXAoaW5wdXRzLHN0YXRlKQogICAgICAgICAgICBlbGlmKHNlbGYuX3NvbHZlciA9PSBPREVTb2x2ZXIuUnVuZ2VLdXR0YSk6CiAgICAgICAgICAgICAgICBuZXh0X3N0YXRlID0gc2VsZi5fb2RlX3N0ZXBfcnVuZ2Vfa3V0dGEoaW5wdXRzLHN0YXRlKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiVW5rbm93biBPREUgc29sdmVyICd7fSciLmZvcm1hdChzdHIoc2VsZi5fc29sdmVyKSkpCgogICAgICAgICAgICBvdXRwdXRzID0gbmV4dF9zdGF0ZQogICAgICAgICAgICAKICAgICAgICByZXR1cm4gb3V0cHV0cywgbmV4dF9zdGF0ZSAKCiAgICAjIENyZWF0ZSB0ZiB2YXJpYWJsZXMKICAgIGRlZiBfZ2V0X3ZhcmlhYmxlcyhzZWxmKToKICAgICAgICBzZWxmLnNlbnNvcnlfbXUgPSB0Zi5nZXRfdmFyaWFibGUobmFtZT0nc2Vuc29yeV9tdScsc2hhcGU9W3NlbGYuX2lucHV0X3NpemUsc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9VHJ1ZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMucmFuZG9tX3VuaWZvcm0obWludmFsPTAuMyxtYXh2YWw9MC44KSkKICAgICAgICBzZWxmLnNlbnNvcnlfc2lnbWEgPSB0Zi5nZXRfdmFyaWFibGUobmFtZT0nc2Vuc29yeV9zaWdtYScsc2hhcGU9W3NlbGYuX2lucHV0X3NpemUsc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9VHJ1ZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMucmFuZG9tX3VuaWZvcm0obWludmFsPTMuMCxtYXh2YWw9OC4wKSkKICAgICAgICBzZWxmLnNlbnNvcnlfVyA9IHRmLmdldF92YXJpYWJsZShuYW1lPSdzZW5zb3J5X1cnLHNoYXBlPVtzZWxmLl9pbnB1dF9zaXplLHNlbGYuX251bV91bml0c10sdHJhaW5hYmxlPVRydWUsaW5pdGlhbGl6ZXI9dGYuaW5pdGlhbGl6ZXJzLmNvbnN0YW50KG5wLnJhbmRvbS51bmlmb3JtKGxvdz1zZWxmLl93X2luaXRfbWluLGhpZ2g9c2VsZi5fd19pbml0X21heCxzaXplPVtzZWxmLl9pbnB1dF9zaXplLHNlbGYuX251bV91bml0c10pKSkKICAgICAgICBzZW5zb3J5X2VyZXZfaW5pdCA9IDIqbnAucmFuZG9tLnJhbmRpbnQobG93PTAsaGlnaD0yLHNpemU9W3NlbGYuX2lucHV0X3NpemUsc2VsZi5fbnVtX3VuaXRzXSktMQogICAgICAgIHNlbGYuc2Vuc29yeV9lcmV2ID0gdGYuZ2V0X3ZhcmlhYmxlKG5hbWU9J3NlbnNvcnlfZXJldicsc2hhcGU9W3NlbGYuX2lucHV0X3NpemUsc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9VHJ1ZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMuY29uc3RhbnQoc2Vuc29yeV9lcmV2X2luaXQqc2VsZi5fZXJldl9pbml0X2ZhY3RvcikpCgogICAgICAgIHNlbGYubXUgPSB0Zi5nZXRfdmFyaWFibGUobmFtZT0nbXUnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHMsc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9VHJ1ZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMucmFuZG9tX3VuaWZvcm0obWludmFsPTAuMyxtYXh2YWw9MC44KSkKICAgICAgICBzZWxmLnNpZ21hID0gdGYuZ2V0X3ZhcmlhYmxlKG5hbWU9J3NpZ21hJyxzaGFwZT1bc2VsZi5fbnVtX3VuaXRzLHNlbGYuX251bV91bml0c10sdHJhaW5hYmxlPVRydWUsaW5pdGlhbGl6ZXI9dGYuaW5pdGlhbGl6ZXJzLnJhbmRvbV91bmlmb3JtKG1pbnZhbD0zLjAsbWF4dmFsPTguMCkpCiAgICAgICAgc2VsZi5XID0gdGYuZ2V0X3ZhcmlhYmxlKG5hbWU9J1cnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHMsc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9VHJ1ZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMuY29uc3RhbnQobnAucmFuZG9tLnVuaWZvcm0obG93PXNlbGYuX3dfaW5pdF9taW4saGlnaD1zZWxmLl93X2luaXRfbWF4LHNpemU9W3NlbGYuX251bV91bml0cyxzZWxmLl9udW1fdW5pdHNdKSkpCgogICAgICAgIGVyZXZfaW5pdCA9IDIqbnAucmFuZG9tLnJhbmRpbnQobG93PTAsaGlnaD0yLHNpemU9W3NlbGYuX251bV91bml0cyxzZWxmLl9udW1fdW5pdHNdKS0xCiAgICAgICAgc2VsZi5lcmV2ID0gdGYuZ2V0X3ZhcmlhYmxlKG5hbWU9J2VyZXYnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHMsc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9VHJ1ZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMuY29uc3RhbnQoZXJldl9pbml0KnNlbGYuX2VyZXZfaW5pdF9mYWN0b3IpKQoKICAgICAgICBpZihzZWxmLl9maXhfdmxlYWsgaXMgTm9uZSk6CiAgICAgICAgICAgIHNlbGYudmxlYWsgPSB0Zi5nZXRfdmFyaWFibGUobmFtZT0ndmxlYWsnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHNdLHRyYWluYWJsZT1UcnVlLGluaXRpYWxpemVyPXRmLmluaXRpYWxpemVycy5yYW5kb21fdW5pZm9ybShtaW52YWw9LTAuMixtYXh2YWw9MC4yKSkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLnZsZWFrID0gdGYuZ2V0X3ZhcmlhYmxlKG5hbWU9J3ZsZWFrJyxzaGFwZT1bc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9RmFsc2UsaW5pdGlhbGl6ZXI9dGYuaW5pdGlhbGl6ZXJzLmNvbnN0YW50KHNlbGYuX2ZpeF92bGVhaykpCgogICAgICAgIGlmKHNlbGYuX2ZpeF9nbGVhayBpcyBOb25lKToKICAgICAgICAgICAgaW5pdGlhbGl6ZXI9dGYuaW5pdGlhbGl6ZXJzLmNvbnN0YW50KHNlbGYuX2dsZWFrX2luaXRfbWluKQogICAgICAgICAgICBpZihzZWxmLl9nbGVha19pbml0X21heCA+IHNlbGYuX2dsZWFrX2luaXRfbWluKToKICAgICAgICAgICAgICAgIGluaXRpYWxpemVyID0gdGYuaW5pdGlhbGl6ZXJzLnJhbmRvbV91bmlmb3JtKG1pbnZhbD0gc2VsZi5fZ2xlYWtfaW5pdF9taW4sbWF4dmFsID0gc2VsZi5fZ2xlYWtfaW5pdF9tYXgpCiAgICAgICAgICAgIHNlbGYuZ2xlYWsgPSB0Zi5nZXRfdmFyaWFibGUobmFtZT0nZ2xlYWsnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHNdLHRyYWluYWJsZT1UcnVlLGluaXRpYWxpemVyPWluaXRpYWxpemVyKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYuZ2xlYWsgPSB0Zi5nZXRfdmFyaWFibGUobmFtZT0nZ2xlYWsnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHNdLHRyYWluYWJsZT1GYWxzZSxpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMuY29uc3RhbnQoc2VsZi5fZml4X2dsZWFrKSkKCiAgICAgICAgaWYoc2VsZi5fZml4X2NtIGlzIE5vbmUpOgogICAgICAgICAgICBpbml0aWFsaXplcj10Zi5pbml0aWFsaXplcnMuY29uc3RhbnQoc2VsZi5fY21faW5pdF9taW4pCiAgICAgICAgICAgIGlmKHNlbGYuX2NtX2luaXRfbWF4ID4gc2VsZi5fY21faW5pdF9taW4pOgogICAgICAgICAgICAgICAgaW5pdGlhbGl6ZXIgPSB0Zi5pbml0aWFsaXplcnMucmFuZG9tX3VuaWZvcm0obWludmFsPSBzZWxmLl9jbV9pbml0X21pbixtYXh2YWwgPSBzZWxmLl9jbV9pbml0X21heCkKICAgICAgICAgICAgc2VsZi5jbV90ID0gdGYuZ2V0X3ZhcmlhYmxlKG5hbWU9J2NtX3QnLHNoYXBlPVtzZWxmLl9udW1fdW5pdHNdLHRyYWluYWJsZT1UcnVlLGluaXRpYWxpemVyPWluaXRpYWxpemVyKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYuY21fdCA9IHRmLmdldF92YXJpYWJsZShuYW1lPSdjbV90JyxzaGFwZT1bc2VsZi5fbnVtX3VuaXRzXSx0cmFpbmFibGU9RmFsc2UsaW5pdGlhbGl6ZXI9dGYuaW5pdGlhbGl6ZXJzLmNvbnN0YW50KHNlbGYuX2ZpeF9jbSkpCgogICAgIyBIeWJyaWQgZXVsZXIgbWV0aG9kCiAgICBkZWYgX29kZV9zdGVwKHNlbGYsaW5wdXRzLHN0YXRlKToKICAgICAgICB2X3ByZSA9IHN0YXRlCgogICAgICAgIHNlbnNvcnlfd19hY3RpdmF0aW9uID0gc2VsZi5zZW5zb3J5X1cqc2VsZi5fc2lnbW9pZChpbnB1dHMsc2VsZi5zZW5zb3J5X211LHNlbGYuc2Vuc29yeV9zaWdtYSkKICAgICAgICBzZW5zb3J5X3Jldl9hY3RpdmF0aW9uID0gc2Vuc29yeV93X2FjdGl2YXRpb24qc2VsZi5zZW5zb3J5X2VyZXYKCiAgICAgICAgd19udW1lcmF0b3Jfc2Vuc29yeSA9IHRmLnJlZHVjZV9zdW0oc2Vuc29yeV9yZXZfYWN0aXZhdGlvbixheGlzPTEpCiAgICAgICAgd19kZW5vbWluYXRvcl9zZW5zb3J5ID0gdGYucmVkdWNlX3N1bShzZW5zb3J5X3dfYWN0aXZhdGlvbixheGlzPTEpCgogICAgICAgIGZvciB0IGluIHJhbmdlKHNlbGYuX29kZV9zb2x2ZXJfdW5mb2xkcyk6CiAgICAgICAgICAgIHdfYWN0aXZhdGlvbiA9IHNlbGYuVypzZWxmLl9zaWdtb2lkKHZfcHJlLHNlbGYubXUsc2VsZi5zaWdtYSkKCiAgICAgICAgICAgIHJldl9hY3RpdmF0aW9uID0gd19hY3RpdmF0aW9uKnNlbGYuZXJldgoKICAgICAgICAgICAgd19udW1lcmF0b3IgPSB0Zi5yZWR1Y2Vfc3VtKHJldl9hY3RpdmF0aW9uLGF4aXM9MSkgKyB3X251bWVyYXRvcl9zZW5zb3J5CiAgICAgICAgICAgIHdfZGVub21pbmF0b3IgPSB0Zi5yZWR1Y2Vfc3VtKHdfYWN0aXZhdGlvbixheGlzPTEpICsgd19kZW5vbWluYXRvcl9zZW5zb3J5CiAgICAgICAgICAgIAogICAgICAgICAgICBudW1lcmF0b3IgPSBzZWxmLmNtX3QgKiB2X3ByZSArIHNlbGYuZ2xlYWsqc2VsZi52bGVhayArIHdfbnVtZXJhdG9yCiAgICAgICAgICAgIGRlbm9taW5hdG9yID0gc2VsZi5jbV90ICsgc2VsZi5nbGVhayArIHdfZGVub21pbmF0b3IKCiAgICAgICAgICAgIHZfcHJlID0gbnVtZXJhdG9yL2Rlbm9taW5hdG9yCgogICAgICAgIHJldHVybiB2X3ByZQoKICAgIGRlZiBfZl9wcmltZShzZWxmLGlucHV0cyxzdGF0ZSk6CiAgICAgICAgdl9wcmUgPSBzdGF0ZQoKICAgICAgICAjIFdlIGNhbiBwcmUtY29tcHV0ZSB0aGUgZWZmZWN0cyBvZiB0aGUgc2Vuc29yeSBuZXVyb25zIGhlcmUKICAgICAgICBzZW5zb3J5X3dfYWN0aXZhdGlvbiA9IHNlbGYuc2Vuc29yeV9XKnNlbGYuX3NpZ21vaWQoaW5wdXRzLHNlbGYuc2Vuc29yeV9tdSxzZWxmLnNlbnNvcnlfc2lnbWEpCiAgICAgICAgd19yZWR1Y2VkX3NlbnNvcnkgPSB0Zi5yZWR1Y2Vfc3VtKHNlbnNvcnlfd19hY3RpdmF0aW9uLGF4aXM9MSkKCiAgICAgICAgIyBVbmZvbGQgdGhlIG11dGxpcGx5IE9ERSBtdWx0aXBsZSB0aW1lcyBpbnRvIG9uZSBSTk4gc3RlcAogICAgICAgIHdfYWN0aXZhdGlvbiA9IHNlbGYuVypzZWxmLl9zaWdtb2lkKHZfcHJlLHNlbGYubXUsc2VsZi5zaWdtYSkKCiAgICAgICAgd19yZWR1Y2VkX3N5bmFwc2UgPSB0Zi5yZWR1Y2Vfc3VtKHdfYWN0aXZhdGlvbixheGlzPTEpCgogICAgICAgIHNlbnNvcnlfaW4gPSBzZWxmLnNlbnNvcnlfZXJldiAqIHNlbnNvcnlfd19hY3RpdmF0aW9uCiAgICAgICAgc3luYXBzZV9pbiA9IHNlbGYuZXJldiAqIHdfYWN0aXZhdGlvbgoKICAgICAgICBzdW1faW4gPSB0Zi5yZWR1Y2Vfc3VtKHNlbnNvcnlfaW4sYXhpcz0xKSAtIHZfcHJlKndfcmVkdWNlZF9zeW5hcHNlICsgdGYucmVkdWNlX3N1bShzeW5hcHNlX2luLGF4aXM9MSkgLSB2X3ByZSAqIHdfcmVkdWNlZF9zZW5zb3J5CiAgICAgICAgCiAgICAgICAgZl9wcmltZSA9IDEvc2VsZi5jbV90ICogKHNlbGYuZ2xlYWsgKiAoc2VsZi52bGVhay12X3ByZSkgKyBzdW1faW4pCgogICAgICAgIHJldHVybiBmX3ByaW1lCgogICAgZGVmIF9vZGVfc3RlcF9ydW5nZV9rdXR0YShzZWxmLGlucHV0cyxzdGF0ZSk6CgogICAgICAgIGggPSAwLjEKICAgICAgICBmb3IgaSBpbiByYW5nZShzZWxmLl9vZGVfc29sdmVyX3VuZm9sZHMpOgogICAgICAgICAgICBrMSA9IGgqc2VsZi5fZl9wcmltZShpbnB1dHMsc3RhdGUpCiAgICAgICAgICAgIGsyID0gaCpzZWxmLl9mX3ByaW1lKGlucHV0cyxzdGF0ZStrMSowLjUpCiAgICAgICAgICAgIGszID0gaCpzZWxmLl9mX3ByaW1lKGlucHV0cyxzdGF0ZStrMiowLjUpCiAgICAgICAgICAgIGs0ID0gaCpzZWxmLl9mX3ByaW1lKGlucHV0cyxzdGF0ZStrMykKCiAgICAgICAgICAgIHN0YXRlID0gc3RhdGUgKyAxLjAvNiooazErMiprMisyKmszK2s0KQoKICAgICAgICByZXR1cm4gc3RhdGUKCiAgICBkZWYgX29kZV9zdGVwX2V4cGxpY2l0KHNlbGYsaW5wdXRzLHN0YXRlLF9vZGVfc29sdmVyX3VuZm9sZHMpOgogICAgICAgIHZfcHJlID0gc3RhdGUKCiAgICAgICAgIyBXZSBjYW4gcHJlLWNvbXB1dGUgdGhlIGVmZmVjdHMgb2YgdGhlIHNlbnNvcnkgbmV1cm9ucyBoZXJlCiAgICAgICAgc2Vuc29yeV93X2FjdGl2YXRpb24gPSBzZWxmLnNlbnNvcnlfVypzZWxmLl9zaWdtb2lkKGlucHV0cyxzZWxmLnNlbnNvcnlfbXUsc2VsZi5zZW5zb3J5X3NpZ21hKQogICAgICAgIHdfcmVkdWNlZF9zZW5zb3J5ID0gdGYucmVkdWNlX3N1bShzZW5zb3J5X3dfYWN0aXZhdGlvbixheGlzPTEpCgoKICAgICAgICAjIFVuZm9sZCB0aGUgbXV0bGlwbHkgT0RFIG11bHRpcGxlIHRpbWVzIGludG8gb25lIFJOTiBzdGVwCiAgICAgICAgZm9yIHQgaW4gcmFuZ2UoX29kZV9zb2x2ZXJfdW5mb2xkcyk6CiAgICAgICAgICAgIHdfYWN0aXZhdGlvbiA9IHNlbGYuVypzZWxmLl9zaWdtb2lkKHZfcHJlLHNlbGYubXUsc2VsZi5zaWdtYSkKCiAgICAgICAgICAgIHdfcmVkdWNlZF9zeW5hcHNlID0gdGYucmVkdWNlX3N1bSh3X2FjdGl2YXRpb24sYXhpcz0xKQoKICAgICAgICAgICAgc2Vuc29yeV9pbiA9IHNlbGYuc2Vuc29yeV9lcmV2ICogc2Vuc29yeV93X2FjdGl2YXRpb24KICAgICAgICAgICAgc3luYXBzZV9pbiA9IHNlbGYuZXJldiAqIHdfYWN0aXZhdGlvbgoKICAgICAgICAgICAgc3VtX2luID0gdGYucmVkdWNlX3N1bShzZW5zb3J5X2luLGF4aXM9MSkgLSB2X3ByZSp3X3JlZHVjZWRfc3luYXBzZSArIHRmLnJlZHVjZV9zdW0oc3luYXBzZV9pbixheGlzPTEpIC0gdl9wcmUgKiB3X3JlZHVjZWRfc2Vuc29yeQogICAgICAgICAgICAKICAgICAgICAgICAgZl9wcmltZSA9IDEvc2VsZi5jbV90ICogKHNlbGYuZ2xlYWsgKiAoc2VsZi52bGVhay12X3ByZSkgKyBzdW1faW4pCgogICAgICAgICAgICB2X3ByZSA9IHZfcHJlICsgMC4xICogZl9wcmltZQoKICAgICAgICByZXR1cm4gdl9wcmUKICAgIAogICAgZGVmIF9zaWdtb2lkKHNlbGYsdl9wcmUsbXUsc2lnbWEpOgogICAgICAgIHZfcHJlID0gdGYucmVzaGFwZSh2X3ByZSxbLTEsdl9wcmUuc2hhcGVbLTFdLDFdKQogICAgICAgIG11ZXMgPSB2X3ByZSAtIG11CiAgICAgICAgeCA9IHNpZ21hKm11ZXMKICAgICAgICByZXR1cm4gdGYubm4uc2lnbW9pZCh4KQoKICAgIGRlZiBnZXRfcGFyYW1fY29uc3RyYWluX29wKHNlbGYpOgogICAgICAgIAogICAgICAgIGNtX2NsaXBwaW5nX29wID0gdGYuYXNzaWduKHNlbGYuY21fdCx0Zi5jbGlwX2J5X3ZhbHVlKHNlbGYuY21fdCwgc2VsZi5fY21fdF9taW5fdmFsdWUsIHNlbGYuX2NtX3RfbWF4X3ZhbHVlKSkKICAgICAgICBnbGVha19jbGlwcGluZ19vcCA9IHRmLmFzc2lnbihzZWxmLmdsZWFrLHRmLmNsaXBfYnlfdmFsdWUoc2VsZi5nbGVhaywgc2VsZi5fZ2xlYWtfbWluX3ZhbHVlLCBzZWxmLl9nbGVha19tYXhfdmFsdWUpKQogICAgICAgIHdfY2xpcHBpbmdfb3AgPSB0Zi5hc3NpZ24oc2VsZi5XLHRmLmNsaXBfYnlfdmFsdWUoc2VsZi5XLCBzZWxmLl93X21pbl92YWx1ZSwgc2VsZi5fd19tYXhfdmFsdWUpKQogICAgICAgIHNlbnNvcnlfd19jbGlwcGluZ19vcCA9IHRmLmFzc2lnbihzZWxmLnNlbnNvcnlfVyAsdGYuY2xpcF9ieV92YWx1ZShzZWxmLnNlbnNvcnlfVywgc2VsZi5fd19taW5fdmFsdWUsIHNlbGYuX3dfbWF4X3ZhbHVlKSkKCiAgICAgICAgcmV0dXJuIFtjbV9jbGlwcGluZ19vcCxnbGVha19jbGlwcGluZ19vcCx3X2NsaXBwaW5nX29wLHNlbnNvcnlfd19jbGlwcGluZ19vcF0KCiAgICBkZWYgZXhwb3J0X3dlaWdodHMoc2VsZixkaXJuYW1lLHNlc3Msb3V0cHV0X3dlaWdodHM9Tm9uZSk6CiAgICAgICAgb3MubWFrZWRpcnMoZGlybmFtZSxleGlzdF9vaz1UcnVlKQogICAgICAgIHcsZXJldixtdSxzaWdtYSA9IHNlc3MucnVuKFtzZWxmLlcsc2VsZi5lcmV2LHNlbGYubXUsc2VsZi5zaWdtYV0pCiAgICAgICAgc2Vuc29yeV93LHNlbnNvcnlfZXJldixzZW5zb3J5X211LHNlbnNvcnlfc2lnbWEgPSBzZXNzLnJ1bihbc2VsZi5zZW5zb3J5X1csc2VsZi5zZW5zb3J5X2VyZXYsc2VsZi5zZW5zb3J5X211LHNlbGYuc2Vuc29yeV9zaWdtYV0pCiAgICAgICAgdmxlYWssZ2xlYWssY20gPSBzZXNzLnJ1bihbc2VsZi52bGVhayxzZWxmLmdsZWFrLHNlbGYuY21fdF0pCgogICAgICAgIGlmKG5vdCBvdXRwdXRfd2VpZ2h0cyBpcyBOb25lKToKICAgICAgICAgICAgb3V0cHV0X3csb3V0cHV0X2IgPSBzZXNzLnJ1bihvdXRwdXRfd2VpZ2h0cykKICAgICAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwib3V0cHV0X3cuY3N2Iiksb3V0cHV0X3cpCiAgICAgICAgICAgIG5wLnNhdmV0eHQob3MucGF0aC5qb2luKGRpcm5hbWUsIm91dHB1dF9iLmNzdiIpLG91dHB1dF9iKQogICAgICAgIG5wLnNhdmV0eHQob3MucGF0aC5qb2luKGRpcm5hbWUsIncuY3N2IiksdykKICAgICAgICBucC5zYXZldHh0KG9zLnBhdGguam9pbihkaXJuYW1lLCJlcmV2LmNzdiIpLGVyZXYpCiAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwibXUuY3N2IiksbXUpCiAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwic2lnbWEuY3N2Iiksc2lnbWEpCiAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwic2Vuc29yeV93LmNzdiIpLHNlbnNvcnlfdykKICAgICAgICBucC5zYXZldHh0KG9zLnBhdGguam9pbihkaXJuYW1lLCJzZW5zb3J5X2VyZXYuY3N2Iiksc2Vuc29yeV9lcmV2KQogICAgICAgIG5wLnNhdmV0eHQob3MucGF0aC5qb2luKGRpcm5hbWUsInNlbnNvcnlfbXUuY3N2Iiksc2Vuc29yeV9tdSkKICAgICAgICBucC5zYXZldHh0KG9zLnBhdGguam9pbihkaXJuYW1lLCJzZW5zb3J5X3NpZ21hLmNzdiIpLHNlbnNvcnlfc2lnbWEpCiAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwidmxlYWsuY3N2IiksdmxlYWspCiAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwiZ2xlYWsuY3N2IiksZ2xlYWspCiAgICAgICAgbnAuc2F2ZXR4dChvcy5wYXRoLmpvaW4oZGlybmFtZSwiY20uY3N2IiksY20p