def test_if_then_else(self): b = Variable("b", Variable.Bool) e = if_then_else(b, 5.0, 3.0) self.assertEqual(str(e), "(if b then 5 else 3)") with self.assertRaises(Exception) as context: if_then_else(x, 5.0, 3.0) print(context.exception) self.assertTrue("not a Boolean variable but used as a conditional" in str(context.exception))
def test_if_then_else(self): c = x > y f1 = x == 1.0 f2 = y == 2.0 f = if_then_else(c, f1, f2) env1 = {x: 2, y: 1} env2 = {x: 1, y: 2} env3 = {x: 1, y: 0} env4 = {x: 0, y: 1} self.assertEqual(f.Evaluate(env1), False) self.assertEqual(f.Evaluate(env2), True) self.assertEqual(f.Evaluate(env3), True) self.assertEqual(f.Evaluate(env4), False)
def test_functions_with_expression(self): self.assertEqual(str(abs(e_x)), "abs(x)") self.assertEqual(str(exp(e_x)), "exp(x)") self.assertEqual(str(sqrt(e_x)), "sqrt(x)") self.assertEqual(str(pow(e_x, e_y)), "pow(x, y)") self.assertEqual(str(sin(e_x)), "sin(x)") self.assertEqual(str(cos(e_x)), "cos(x)") self.assertEqual(str(tan(e_x)), "tan(x)") self.assertEqual(str(asin(e_x)), "asin(x)") self.assertEqual(str(acos(e_x)), "acos(x)") self.assertEqual(str(atan(e_x)), "atan(x)") self.assertEqual(str(atan2(e_x, e_y)), "atan2(x, y)") self.assertEqual(str(sinh(e_x)), "sinh(x)") self.assertEqual(str(cosh(e_x)), "cosh(x)") self.assertEqual(str(tanh(e_x)), "tanh(x)") self.assertEqual(str(min(e_x, e_y)), "min(x, y)") self.assertEqual(str(max(e_x, e_y)), "max(x, y)") self.assertEqual(str(if_then_else(e_x > e_y, e_x, e_y)), "(if (x > y) then x else y)")
def test_functions_with_variable(self): self.assertEqual(str(abs(x)), "abs(x)") self.assertEqual(str(exp(x)), "exp(x)") self.assertEqual(str(sqrt(x)), "sqrt(x)") self.assertEqual(str(pow(x, y)), "pow(x, y)") self.assertEqual(str(sin(x)), "sin(x)") self.assertEqual(str(cos(x)), "cos(x)") self.assertEqual(str(tan(x)), "tan(x)") self.assertEqual(str(asin(x)), "asin(x)") self.assertEqual(str(acos(x)), "acos(x)") self.assertEqual(str(atan(x)), "atan(x)") self.assertEqual(str(atan2(x, y)), "atan2(x, y)") self.assertEqual(str(sinh(x)), "sinh(x)") self.assertEqual(str(cosh(x)), "cosh(x)") self.assertEqual(str(tanh(x)), "tanh(x)") self.assertEqual(str(min(x, y)), "min(x, y)") self.assertEqual(str(max(x, y)), "max(x, y)") self.assertEqual(str(if_then_else(x > y, x, y)), "(if (x > y) then x else y)")
def test_functions_with_float(self): v_x = 1.0 v_y = 1.0 self.assertEqual(abs(v_x), math.fabs(v_x)) self.assertEqual(exp(v_x), math.exp(v_x)) self.assertEqual(sqrt(v_x), math.sqrt(v_x)) self.assertEqual(pow(v_x, v_y), v_x**v_y) self.assertEqual(sin(v_x), math.sin(v_x)) self.assertEqual(cos(v_x), math.cos(v_x)) self.assertEqual(tan(v_x), math.tan(v_x)) self.assertEqual(asin(v_x), math.asin(v_x)) self.assertEqual(acos(v_x), math.acos(v_x)) self.assertEqual(atan(v_x), math.atan(v_x)) self.assertEqual(atan2(v_x, v_y), math.atan2(v_x, v_y)) self.assertEqual(sinh(v_x), math.sinh(v_x)) self.assertEqual(cosh(v_x), math.cosh(v_x)) self.assertEqual(tanh(v_x), math.tanh(v_x)) self.assertEqual(min(v_x, v_y), min(v_x, v_y)) self.assertEqual(max(v_x, v_y), max(v_x, v_y)) self.assertEqual( if_then_else(Expression(v_x) > Expression(v_y), v_x, v_y), v_x if v_x > v_y else v_y)
def _sympy_converter(var_map, exp, target, expand_pow=False): rv = None assert isinstance(exp, sp.Expr) and target is not None if isinstance(exp, sp.Symbol): rv = var_map.get(exp.name, None) elif isinstance(exp, sp.Number): try: rv = RealVal(exp) if isinstance(target, Z3Verifier) else sp.RealNumber(exp) except: # Z3 parser error rep = sp.Float(exp, len(str(exp))) rv = RealVal(rep) elif isinstance(exp, sp.Add): # Add(exp_0, ...) rv = _sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow) # eval this expression for e in exp.args[1:]: # add it to all other remaining expressions rv += _sympy_converter(var_map, e, target, expand_pow=expand_pow) elif isinstance(exp, sp.Mul): rv = _sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow) for e in exp.args[1:]: rv *= _sympy_converter(var_map, e, target, expand_pow=expand_pow) elif isinstance(exp, sp.Pow): x = _sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow) e = _sympy_converter(var_map, exp.args[1], target, expand_pow=expand_pow) if expand_pow: try: i = float(e.sexpr()) assert i.is_integer() i = int(i) - 1 rv = x for _ in range(i): rv *= x except: # fallback rv = _sympy_converter(var_map, exp, target, expand_pow=False) else: rv = x ** e elif isinstance(exp, sp.Max): x = _sympy_converter(var_map, exp.args[1], target, expand_pow=expand_pow) zero = exp.args[0] if target == Z3Verifier: rv = z3.If(x >= 0.0, x, 0.0) else: rv = dr.max(x, 0.0) elif isinstance(exp, sp.Heaviside): x = _sympy_converter(var_map, exp.args[0], target, expand_pow=False) if target == Z3Verifier: rv = z3.If(x > 0.0, 1.0, 0.0) else: rv = dr.if_then_else(x>0.0, 1.0, 0.0) elif isinstance(exp, sp.Function): # check various activation types ONLY FOR DREAL if isinstance(exp, sp.tanh): rv = dr.tanh(_sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow)) elif isinstance(exp, sp.sin): rv = dr.sin(_sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow)) elif isinstance(exp, sp.cos): rv = dr.cos(_sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow)) elif isinstance(exp, sp.exp): rv = dr.exp(_sympy_converter(var_map, exp.args[0], target, expand_pow=expand_pow)) else: ValueError('Term ' + str(exp) + ' not recognised') assert rv is not None return rv
def translate_ep_cross(dg, name, fluid_name='default'): """Create SMT expressions for an electrophoretic cross :param str name: the name of the junction node in the electrophoretic cross :returns: None -- no issues with translating channel parameters to SMT :raises: ValueError if the analyte_properties are not defined properly TypeError if the analyte_properties are not floats or ints """ # work in progress exprs = [] # Validate input if dg.size(name) != 4: raise ValueError("Electrophoretic Cross %s must have 4 connections" % name) # Electrophoretic Cross is a type of node, so call translate node [exprs.append(val) for val in translate_node(dg, name)] # Because it's done in translate_tjunc ep_cross_node_name = name # figure out which nodes are for sample injection and which are for separation channel # assume single input node, 3 output nodes, one junction node # assume separation and tail channels are specified by user phases = nx.get_edge_attributes(dg, 'phase') for edge, phase in phases.items(): # assuming only one separation channel, and only 1 tail channel if phase == 'separation': separation_channel_name = edge anode_node_name = edge[1] elif phase == 'tail': tail_channel_name = edge cathode_node_name = edge[ edge[0] == ep_cross_node_name] # returns whichever tuple element is NOT the ep_cross node # is there a better way to do this? node_kinds = nx.get_node_attributes(dg, 'kind') for node, kind in node_kinds.items(): if node not in separation_channel_name and node not in tail_channel_name: if kind == 'input': injection_channel_name = (node, ep_cross_node_name) injection_node_name = node # necessary? elif kind == 'output': waste_channel_name = (ep_cross_node_name, node) waste_node_name = node # necessary? # assert dimensions: # assert width and height of tail channel to be equal to separation channel exprs.append( algorithms.retrieve(dg, tail_channel_name, 'width') == algorithms.retrieve(dg, separation_channel_name, 'width')) exprs.append( algorithms.retrieve(dg, tail_channel_name, 'height') == algorithms.retrieve(dg, separation_channel_name, 'height')) # assert width and height of injection channel to be equal to waste channel exprs.append( algorithms.retrieve(dg, injection_channel_name, 'width') == algorithms.retrieve(dg, waste_channel_name, 'width')) exprs.append( algorithms.retrieve(dg, injection_channel_name, 'height') == algorithms.retrieve(dg, waste_channel_name, 'height')) # assert height of separation channel and injection channel are same exprs.append( algorithms.retrieve(dg, injection_channel_name, 'height') == algorithms.retrieve(dg, separation_channel_name, 'height')) # electric field E = Variable('E') exprs.append(E < 1000000) exprs.append(E > 0) exprs.append(E == algorithms.calculate_electric_field( dg, anode_node_name, cathode_node_name)) # only works if cathode is an input? only works for paths that are true in directed graph # assume that the analyte parameters were included in the injection port # need to validate that the data exists? D = algorithms.retrieve(dg, injection_node_name, 'analyte_diffusivities') C0 = algorithms.retrieve(dg, injection_node_name, 'analyte_initial_concentrations') q = algorithms.retrieve(dg, injection_node_name, 'analyte_charges') r = algorithms.retrieve(dg, injection_node_name, 'analyte_radii') analyte_properties = { 'analyte_diffusivities': D, 'analyte_initial_concentrations': C0, 'analyte_charges': q, 'analyte_radii': r } for property_name, values in analyte_properties.items(): # check if something is defined, otherwise should be set to false if not values: raise ValueError( 'No values defined for %s in electrophoretic cross node %s' % (property_name, ep_cross_node_name)) # make sure all the values are either ints or floats if not all(isinstance(x, (int, float)) for x in values): raise TypeError( "%s values in electrophoretic cross node '%s' must be numbers" % (property_name, ep_cross_node_name)) # n = number of analytes n = len(D) for property_name, values in analyte_properties.items(): # check that they all have the same number of values n_to_check = len(values) if not (n_to_check == n): raise ValueError( "Expecting %s values, and found %s for %s in node: '%s'" % (n, n_to_check, property_name, ep_cross_node_name)) delta = algorithms.retrieve(dg, separation_channel_name, 'min_sampling_rate') x_detector = algorithms.retrieve(dg, separation_channel_name, 'x_detector') # These are currently set as parameters to the node function # all are constants, numbers between 0 and 1 # brief descriptions: # lower c, more discernable concentration peaks # higher p, any given conc. peak must be higher (closer to max conc.) # qf is arbitrary, rule of thumb qf = 0.9 (called q in Stephen Chou's paper) c = algorithms.retrieve(dg, ep_cross_node_name, 'c') p = algorithms.retrieve(dg, ep_cross_node_name, 'p') qf = algorithms.retrieve(dg, ep_cross_node_name, 'qf') mu = [] v = [] t_peak = [] t_min = [] W = algorithms.retrieve(dg, injection_channel_name, 'width') # for each analyte for i in range(0, n): # calculate mobility mu.append(Variable('mu_' + str(i))) exprs.append(mu[i] < 10000000000) exprs.append(mu[i] > 0) exprs.append(mu[i] == algorithms.calculate_mobility( dg, separation_channel_name, q[i], r[i])) # calculate velocity v.append(Variable('v_' + str(i))) # exprs.append(v[i] < 1) exprs.append(v[i] > 0) exprs.append(v[i] == algorithms.calculate_charged_particle_velocity( dg, mu[i], E)) # calculate t_peak, initialize variables for t_min t_peak.append(Variable('t_peak_' + str(i))) t_min.append(Variable('t_min_' + str(i))) exprs.append(t_peak[i] < 1000000) exprs.append(t_peak[i] > 0) exprs.append(t_min[i] < 1000000) exprs.append(t_min[i] > 0) exprs.append(t_peak[i] == x_detector / v[i]) # detector position is somewhere along the separation channel # assume x_detector ranges from 0 to length of channel # to get absolute position of detector, add x_detector to ep_cross_node position exprs.append(x_detector <= algorithms.retrieve(dg, separation_channel_name, 'length')) # C_negligible is the minimum concentration level # i.e. smallest concentration peak should be > C_negligible C_negligible = Variable('C_negligible') C_floor = Variable('C_floor') sigma0 = Variable('sigma0') # TODO: This equation for sigma0 is for round, should add rectangular as well # definition of sigma0 for round channels (sigma0 ~ r_channel/2.355) exprs.append(sigma0 == W / (2 * 2.355)) exprs.append(C_floor == (min(C0) / (sigma0 + (2 * max(D) * x_detector / v[n - 1])**0.5))) exprs.append(C_negligible == p * C_floor) diff = [] for i in range(0, n - 1): # constrain that time difference between peaks is large enough to be detected exprs.append(t_peak[i] + delta < t_min[i]) exprs.append(t_peak[i] + delta < t_min[i + 1]) # constrain t_min to be where derivative of concentration is 0 # if two adjacent peaks are close enough in height, then instead of using # the differential eqn, can approximate Fi(tmin) = Fi+1(tmin) # where i is the current analyte, and i+1 is the next analyte # and F = C(x_detector), C is concentration # quantify closeness of heights of peaks using the variable diff diff.append(Variable('diff_' + str(i))) exprs.append(diff[i] == C0[i] / C0[i + 1] * (D[i + 1] * mu[i] / (D[i] * mu[i + 1]))**0.5) # if 0.1 < diff < 10, then use expression Fi(tmin) = Fi+1(tmin) # otherwise use expression dFi/dt (tmin) + dFi+1/dt (tmin) = 0 t_min_constraint_expression = if_then_else( logical_and(0.1 < diff[i], diff[i] < 10), algorithms.calculate_concentration(dg, C0[i], D[i], W, v[i], x_detector, t_min[i]) - algorithms.calculate_concentration(dg, C0[i + 1], D[i + 1], W, v[i + 1], x_detector, t_min[i]), (algorithms.calculate_concentration( dg, C0[i + 1], D[i + 1], W, v[i + 1], x_detector, t_min[i])).Differentiate(t_min[i]) + (algorithms.calculate_concentration( dg, C0[i + 1], D[i + 1], W, v[i + 1], x_detector, t_min[i])).Differentiate(t_min[i])) exprs.append(t_min_constraint_expression == 0) # an alternate way to define C_negligible is: # C_negligible < p * min(Fi(t_peaki)) # this requires computing the concentration again, which is inefficient # Wrote this expression just in case; this is the more exact expression # for C_negligible, in case the simpler one does not work for square # channels # I don't know how to use the min function in dreal, so I figured an # equivalent but less efficient way to do it is just to ensure it is # less than Fi(t_peaki), for every i # exprs.append(C_negligible < p * algorithms.calculate_concentration(dg, C0[i], D[i], W, v[i], x_detector, t_peak[i])) # F(tmin, i)/(F(tmax, i)) <= c # F(tmin, i)/F(tpeak, j) ~ ( Fi(tmin,i) + Fi+1(tmin, i) + (n-2)(1-q)/(n-3) ) / Fj(tpeak,j) exprs.append( (algorithms.calculate_concentration(dg, C0[i], D[i], W, v[i], x_detector, t_min[i]) + algorithms.calculate_concentration(dg, C0[i + 1], D[i + 1], W, v[ i + 1], x_detector, t_min[i]) + (n - 2) * (1 - qf) / (n - 3) * C_negligible) / (algorithms.calculate_concentration( dg, C0[i], D[i], W, v[i], x_detector, t_peak[i])) <= c) # F(tmin, i)/(F(tmax, i+1)) <= c exprs.append( (algorithms.calculate_concentration(dg, C0[i], D[i], W, v[i], x_detector, t_min[i]) + algorithms.calculate_concentration(dg, C0[i + 1], D[i + 1], W, v[ i + 1], x_detector, t_min[i]) + (n - 2) * (1 - qf) / (n - 3) * C_negligible) / (algorithms.calculate_concentration(dg, C0[i + 1], D[i + 1], W, v[ i + 1], x_detector, t_peak[i + 1])) <= c) # Call translate on output - waste node [ exprs.append(val) for val in translation_strats[algorithms.retrieve( dg, waste_node_name, 'kind')](dg, waste_node_name) ] # Call translate on output - anode [ exprs.append(val) for val in translation_strats[algorithms.retrieve( dg, anode_node_name, 'kind')](dg, anode_node_name) ] return exprs