def inverterScMosfet(inputVoltage, numSolutions="all"): epsilon = 1e-14 start = time.time() Vdd = 1.0 sn = 3 sp = 2 * sn outputVolt = Variable("outputVolt") iP = Variable("iP") iN = Variable("iN") allConstraints = [] allConstraints.append(outputVolt >= 0.0) allConstraints.append(outputVolt <= Vdd) allConstraints.append(-iP - iN == 0) allConstraints += mvs_id("n", 0.0, inputVoltage, outputVolt, iN, sn) allConstraints += mvs_id("p", Vdd, inputVoltage, outputVolt, iP, sp) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] singleExcludingConstraints.append(outputVolt < solution[0][0]) singleExcludingConstraints.append(outputVolt > solution[0][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((1, 2)) hyper[0, :] = [ result[outputVolt].lb() - 2 * epsilon, result[outputVolt].ub() + 2 * epsilon ] #print ("hyper", hyper) allSolutions.append(hyper) print("num solutions found", len(allSolutions)) end = time.time() print("time taken", end - start) return allSolutions
def test_type(self): real_var = Variable("x", Variable.Real) self.assertEqual(real_var.get_type(), Variable.Real) int_var = Variable("x", Variable.Int) self.assertEqual(int_var.get_type(), Variable.Int) bool_var = Variable("x", Variable.Bool) self.assertEqual(bool_var.get_type(), Variable.Bool)
def inverterLoopTanh(numInverters, numSolutions="all", a=-5.0): epsilon = 1e-14 start = time.time() vs = [] for i in range(numInverters): vs.append(Variable("v" + str(i))) allConstraints = [] # Store rambus oscillator constraints for i in range(numInverters): allConstraints.append(vs[i] >= -1) allConstraints.append(vs[i] <= 1) inputInd = i outputInd = (i + 1) % numInverters allConstraints.append(tanh(a * vs[inputInd]) - vs[outputInd] == 0.0) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] for i in range(numInverters): singleExcludingConstraints.append(vs[i] < solution[i][0]) singleExcludingConstraints.append(vs[i] > solution[i][1]) excludingConstraints.append(singleExcludingConstraints) # Add all the rambus oscillator constraints f_sat = logical_and(*allConstraints) # Add constraints so that old hyperrectangles are not considered if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((numInverters, 2)) for i in range(numInverters): hyper[i, :] = [ result[vs[i]].lb() - 2 * epsilon, result[vs[i]].ub() + 2 * epsilon ] #print ("hyper", hyper) allSolutions.append(hyper) print("num solutions found", len(allSolutions)) end = time.time() print("time taken", end - start) return allSolutions
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 exampleFun(numSolutions="all"): start = time.time() epsilon = 1e-14 lenV = 1 x = Variable('x') allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break allConstraints = [] allConstraints.append(2 * x * asin(cos(0.797) * sin(math.pi / x)) - 0.0331 * x >= 2 * math.pi - 2.097) allConstraints.append(x >= 3) allConstraints.append(x <= 64) excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] singleExcludingConstraints.append(x <= solution[0][0]) singleExcludingConstraints.append(x >= solution[0][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) #print ("numConstraints", len(allConstraints)) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((1, 2)) hyper[0, :] = [ result[x].lb() - 2 * epsilon, result[x].ub() + 2 * epsilon ] print("hyper", hyper) allSolutions.append(hyper) print("num solutions found", len(allSolutions)) '''constraints = [] x = Variable('x') #constraints.append(x >= 0.0) #constraints.append(x <= 64.0) constraints.append(2*math.pi - 2*x*asin(cos(0.797)*sin(math.pi/x)) == 2.097 - 0.0331*x) f_sat = logical_and(*constraints) result = CheckSatisfiability(f_sat, epsilon) print (result)''' end = time.time() print("time taken", end - start)
def inverterTanh(inputVoltage, a=-5.0, numSolutions="all"): epsilon = 1e-14 start = time.time() outputVolt = Variable("outputVolt") allConstraints = [] allConstraints.append(outputVolt >= -1.0) allConstraints.append(outputVolt <= 1.0) allConstraints.append(tanh(a * inputVoltage) - outputVolt == 0) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] singleExcludingConstraints.append(outputVolt < solution[0][0]) singleExcludingConstraints.append(outputVolt > solution[0][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((1, 2)) hyper[0, :] = [ result[outputVolt].lb() - 2 * epsilon, result[outputVolt].ub() + 2 * epsilon ] #print ("hyper", hyper) allSolutions.append(hyper) print("num solutions found", len(allSolutions)) end = time.time() print("time taken", end - start) return allSolutions
from dreal.util import Box from dreal.util import Interval from dreal.util import (exp, log, sqr, sqrt, pow, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, root, abs, max, min, sign, integer) from dreal.symbolic import Variable import unittest import math x = Variable("x") y = Variable("y") z = Variable("z") inf = float("inf") class IntervalTest(unittest.TestCase): def test_constructor(self): i1 = Interval() self.assertEqual(i1.lb(), -inf) self.assertEqual(i1.ub(), +inf) i2 = Interval(3, 4) self.assertEqual(i2.lb(), 3) self.assertEqual(i2.ub(), 4) i3 = Interval(5) self.assertEqual(i3.lb(), 5) self.assertEqual(i3.ub(), 5) def test_addition(self): i1 = Interval(3, 4) i2 = Interval(4, 5)
def schmittTriggerScMosfet(inputVoltage, numSolutions="all"): epsilon = 1e-14 start = time.time() lenV = 3 Vdd = 1.0 sn = 3 sp = 2 * sn vs = [] tIs = [] nIs = [] for i in range(lenV): vs.append(Variable("v" + str(i))) nIs.append(Variable("nI" + str(i))) for i in range(lenV * 2): tIs.append(Variable("tI" + str(i))) allConstraints = [] for i in range(lenV): allConstraints.append(vs[i] >= 0.0) allConstraints.append(vs[i] <= Vdd) allConstraints += mvs_id('n', 0.0, inputVoltage, vs[1], tIs[0], sn) allConstraints += mvs_id('n', vs[1], inputVoltage, vs[0], tIs[1], sn) allConstraints += mvs_id('n', vs[1], vs[0], Vdd, tIs[2], sn) allConstraints += mvs_id('p', Vdd, inputVoltage, vs[2], tIs[3], sp) allConstraints += mvs_id('p', vs[2], inputVoltage, vs[0], tIs[4], sp) allConstraints += mvs_id('p', vs[2], vs[0], 0.0, tIs[5], sp) allConstraints.append(nIs[0] == -tIs[4] - tIs[1]) allConstraints.append(nIs[1] == -tIs[0] + tIs[1] + tIs[2]) allConstraints.append(nIs[2] == -tIs[3] + tIs[5] + tIs[4]) for i in range(lenV): allConstraints.append(nIs[i] == 0.0) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] for i in range(lenV): singleExcludingConstraints.append(vs[i] < solution[i][0]) singleExcludingConstraints.append(vs[i] > solution[i][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((lenV, 2)) for i in range(lenV): hyper[i, :] = [ result[vs[i]].lb() - 2 * epsilon, result[vs[i]].ub() + 2 * epsilon ] #print ("hyper", hyper) allSolutions.append(hyper) print("num solutions found", len(allSolutions)) end = time.time() print("time taken", end - start) return allSolutions
def mvs_id(fetType, Vs, Vg, Vd, I, shape): params = model_params(fetType) version = params['version'] mType = params['mType'] W = params['W'] Lgdr = params['Lgdr'] dLg = params['dLg'] Cg = params['Cg'] etov = params['etov'] delta = params['delta'] n0 = params['n0'] Rs0 = params['Rs0'] Rd0 = params['Rd0'] Cif = params['Cif'] Cof = params['Cof'] vxo = params['vxo'] * 1e7 mu = params['mu'] beta = params['beta'] Tjun = params['Tjun'] phib = params['phib'] gamma = params['gamma'] Vt0 = params['Vt0'] alpha = params['alpha'] mc = params['mc'] CTM_select = params['CTM_select'] CC = params['CC'] nd = params['nd'] zeta = params['zeta'] # SMALL_VALUE SMALL_VALUE = 1e-10 # LARGE_VALUE LARGE_VALUE = 40 if mType == 1.0: Vb = 0.0 Vdsi = Variable("Vdsin") Vgsi = Variable("Vgsin") Vbsi = Variable("Vbsin") n = Variable("nn") nphit = Variable("nphitn") phibVbs = Variable("phibVbsn") Vtpcorr = Variable("Vtpcorrn") eVgpre = Variable("eVgpren") FFpre = Variable("FFpren") ab = Variable("abn") Vcorr = Variable("Vcorrn") Vgscorr = Variable("Vgscorrn") Vbscorr = Variable("Vbscorrn") phibVbscorr = Variable("phibVbscorrn") Vt0bs = Variable("Vt0bsn") phibVbsi = Variable("phibVbsin") Vt0bs0 = Variable("Vt0bs0n") Vtp = Variable("Vtpn") Vtp0 = Variable("Vtp0n") eVg = Variable("eVgn") FF = Variable("FFn") eVg0 = Variable("eVg0n") FF0 = Variable("FF0n") Qref = Variable("Qrefn") eta = Variable("etan") Qinv_corr = Variable("Qinv_corrn") Vdsat = Variable("Vdsatn") VdsiVdsat = Variable("VdsiVdsatn") powVal = Variable("powValn") Fsat = Variable("Fsatn") else: Vb = 1.8 Vdsi = Variable("Vdsip") Vgsi = Variable("Vgsip") Vbsi = Variable("Vbsip") n = Variable("np") nphit = Variable("nphitp") phibVbs = Variable("phibVbsp") Vtpcorr = Variable("Vtpcorrp") eVgpre = Variable("eVgprep") FFpre = Variable("FFprep") ab = Variable("abp") Vcorr = Variable("Vcorrp") Vgscorr = Variable("Vgscorrp") Vbscorr = Variable("Vbscorrp") phibVbscorr = Variable("phibVbscorrp") Vt0bs = Variable("Vt0bsp") phibVbsi = Variable("phibVbsip") Vt0bs0 = Variable("Vt0bs0p") Vtp = Variable("Vtpp") Vtp0 = Variable("Vtp0p") eVg = Variable("eVgp") FF = Variable("FFp") eVg0 = Variable("eVg0p") FF0 = Variable("FF0p") Qref = Variable("Qrefp") eta = Variable("etap") Qinv_corr = Variable("Qinv_corrp") Vdsat = Variable("Vdsatp") VdsiVdsat = Variable("VdsiVdsatp") powVal = Variable("powValp") Fsat = Variable("Fsatp") constraints = [] if mType == 1: constraints.append( logical_or(logical_and(Vs <= Vd, Vdsi == mType * (Vd - Vs)), logical_and(Vs > Vd, Vdsi == mType * (Vs - Vd)))) constraints.append( logical_or(logical_and(Vs <= Vd, Vgsi == mType * (Vg - Vs)), logical_and(Vs > Vd, Vgsi == mType * (Vg - Vd)))) constraints.append( logical_or(logical_and(Vs <= Vd, Vbsi == mType * (Vb - Vs)), logical_and(Vs > Vd, Vbsi == mType * (Vb - Vd)))) else: constraints.append( logical_or(logical_and(Vd <= Vs, Vdsi == mType * (Vd - Vs)), logical_and(Vd > Vs, Vdsi == mType * (Vs - Vd)))) constraints.append( logical_or(logical_and(Vd <= Vs, Vgsi == mType * (Vg - Vs)), logical_and(Vd > Vs, Vgsi == mType * (Vg - Vd)))) constraints.append( logical_or(logical_and(Vd <= Vs, Vbsi == mType * (Vb - Vs)), logical_and(Vd > Vs, Vbsi == mType * (Vb - Vd)))) Cofs = 0 * (0.345e-12 / etov) * dLg / 2.0 + Cof # s-terminal outer fringing cap [F/cm] Cofd = 0 * (0.345e-12 / etov) * dLg / 2.0 + Cof # d-terminal outer fringing cap [F/cm] Leff = Lgdr - dLg # Effective channel length [cm]. After subtracting overlap lengths on s and d side kB = 8.617e-5 # Boltzmann constant [eV/K] phit = kB * Tjun # Thermal voltage, kT/q [V] me = (9.1e-31) * mc # Carrier mass [Kg] constraints.append(n == n0 + nd * Vdsi) constraints.append(nphit == n * phit) aphit = alpha * phit constraints.append(phibVbs == dabs(phib - Vbsi)) constraints.append(Vtpcorr == Vt0 + gamma * (sqrt(phibVbs) - sqrt(phib)) - Vdsi * delta) constraints.append(eVgpre == exp((Vgsi - Vtpcorr) / (aphit * 1.5))) constraints.append(FFpre == 1.0 / (1.0 + eVgpre)) constraints.append(ab == 2 * (1 - 0.99 * FFpre) * phit) constraints.append(Vcorr == (1.0 + 2.0 * delta) * (ab / 2.0) * (exp(-Vdsi / ab))) constraints.append(Vgscorr == Vgsi + Vcorr) constraints.append(Vbscorr == Vbsi + Vcorr) constraints.append(phibVbscorr == dabs(phib - Vbscorr)) constraints.append(Vt0bs == Vt0 + gamma * (sqrt(phibVbscorr) - sqrt(phib))) constraints.append(phibVbsi == dabs(phib - Vbsi)) constraints.append(Vt0bs0 == Vt0 + gamma * (sqrt(phibVbsi) - sqrt(phib))) constraints.append(Vtp == Vt0bs - Vdsi * delta - 0.5 * aphit) constraints.append(Vtp0 == Vt0bs0 - Vdsi * delta - 0.5 * aphit) constraints.append(eVg == exp((Vgscorr - Vtp) / (aphit))) constraints.append(FF == 1.0 / (1.0 + eVg)) constraints.append(eVg0 == exp((Vgsi - Vtp0) / (aphit))) constraints.append(Qref == Cg * nphit) constraints.append(eta == (Vgscorr - (Vt0bs - Vdsi * delta - FF * aphit)) / (nphit)) constraints.append(Qinv_corr == Qref * log(1.0 + exp(eta))) vx0 = vxo Vdsats = vx0 * Leff / mu constraints.append(Vdsat == Vdsats * (1.0 - FF) + phit * FF) constraints.append(VdsiVdsat == Vdsi / Vdsat) constraints.append(powVal == pow(VdsiVdsat, beta)) constraints.append(Fsat == VdsiVdsat / (pow((1 + powVal), (1.0 / beta)))) if mType == 1: constraints.append( logical_or( logical_and(Vs <= Vd, I == Qinv_corr * vx0 * Fsat * W * mType * shape), logical_and( Vs > Vd, I == Qinv_corr * vx0 * Fsat * W * mType * -1.0 * shape))) else: constraints.append( logical_or( logical_and(Vd <= Vs, I == Qinv_corr * vx0 * Fsat * W * mType * shape), logical_and( Vd > Vs, I == Qinv_corr * vx0 * Fsat * W * mType * -1.0 * shape))) return constraints
def elec_port(self, name, kind, min_pressure=False, min_flow_rate=False, x=False, y=False, voltage=False, current=False, fluid_name='default'): """Create new electrical port where fluids and voltages can enter or exit the circuit, any optional tag left empty will be converted to a variable for the SMT solver to solve for a given value, units in brackets :param str name: The name of the port to use when defining channels :param str kind: Define if this is an 'input' or 'output' port :param float density: Density of fluid (kg/m^3) :param float min_viscosity: Viscosity of the fluid (Pa*s) :param float min_pressure: Pressure of the input fluid, (Pa) :param float min_flow_rate - flow rate of input fluid, (m^3/s) :param float X: x-position of port on chip schematic (m) :param float Y: y-position of port on chip schematic (m) :param float voltage: Voltage value passing through the port (V) :param float current: Current value passing through the port (A) :returns: None -- no issues with creating this port :raises: TypeError if an input parameter is wrong type ValueError if an input parameter has an invalid value """ user_provided_params = { name: 'string', min_pressure: 'positive number', min_flow_rate: 'positive number', x: 'positive number', y: 'positive number', voltage: 'number', current: 'positive number', kind: 'string', fluid_name: 'string' } # Checking that arguments are valid self.validate_params(user_provided_params, 'electrical port', name) if name in self.dg.nodes: raise ValueError("Must provide a unique name") if "translate_" + kind.lower() not in self.translation_strats: raise ValueError( "kind %s must be either %s" % ("translate_" + kind.lower(), self.translation_strats)) # Initialize fluid properties fluid_properties = Fluid(fluid_name) # Ports are stored with nodes because ports are just a specific type of # node that has a constant flow rate # only accept ports of the right kind (input or output) attributes = { 'kind': kind.lower(), 'viscosity': Variable(name + '_viscosity'), 'min_viscosity': fluid_properties.min_viscosity, 'pressure': Variable(name + '_pressure'), 'min_pressure': min_pressure, 'flow_rate': Variable(name + '_flow_rate'), 'min_flow_rate': min_flow_rate, 'density': Variable(name + '_density'), 'min_density': fluid_properties.min_density, 'x': Variable(name + '_X'), 'y': Variable(name + '_Y'), 'min_x': x, 'min_y': y, 'voltage': voltage, 'current': current, } # Create this node in the graph self.dg.add_node(name) # Add argument to attributes within NetworkX for key, attr in attributes.items(): self.dg.nodes[name][key] = attr return
from dreal.symbolic import Variable, Variables, Expression, Formula from dreal.symbolic import logical_not, logical_and, logical_or from dreal.symbolic import logical_imply, logical_iff, forall from dreal.symbolic import abs, sin, cos, tan, exp, log, sqrt from dreal.symbolic import asin, acos, atan, sinh, cosh, tanh, atan2 from dreal.symbolic import min, max, if_then_else from dreal.symbolic import intersect import unittest import math x = Variable("x") y = Variable("y") z = Variable("z") w = Variable("w") a = Variable("a") b = Variable("b") c = Variable("c") e_x = Expression(x) e_y = Expression(y) class SymbolicVariableTest(unittest.TestCase): def test_type(self): real_var = Variable("x", Variable.Real) self.assertEqual(real_var.get_type(), Variable.Real) int_var = Variable("x", Variable.Int) self.assertEqual(int_var.get_type(), Variable.Int) bool_var = Variable("x", Variable.Bool) self.assertEqual(bool_var.get_type(), Variable.Bool) def test_addition(self):
def channel(self, port_from, port_to, min_length=False, min_width=False, min_height=False, kind='rectangle', phase='None', min_sampling_rate=1): """Create new connection between two nodes/ports with attributes consisting of the dimensions of the channel to be used to create the SMT equations to calculate solvability of the circuit Units are in brackets :param str port_from: Port where fluid comes into the channel from :param str port_to: Port at the end of the channel where fluid exits :param float min_length: Constrain channel to be this long (m) :param float width: Constrain channel to be this wide (m) :param float height: Constrain channel to be this wide (m) :param str kind: Kind of cross section of the channel (rectangle) :param str phase: For channels connecting to a T-junction this must be either continuous, dispersed or output :returns: None -- no issues with creating this channel :raises: TypeError if an input parameter is wrong type ValueError if an input parameter has an invalid value """ # Collection of the kinds for which there are methods to calculate their # channel resistance valid_kinds = ("rectangle") name = (port_from, port_to) user_provided_params = { port_from: 'string', port_to: 'string', min_length: 'positive number', min_width: 'positive number', min_height: 'positive number', kind: 'string', phase: 'string', min_sampling_rate: 'positive number' } # Checking that arguments are valid # TODO: Modify this to make it work for other channel shapes if kind not in valid_kinds: raise ValueError("Valid channel kinds are: %s" % valid_kinds) if kind == "rectangle": kind = "channel" self.validate_params(user_provided_params, 'Channel', name) if (port_from, port_to) in self.dg.edges: raise ValueError("Channel already exists between these nodes %s" % (port_from, port_to)) if 'translate_' + kind.lower() not in self.translation_strats: raise ValueError( "kind %s must be either %s" % ("translate_" + kind.lower(), self.translation_strats)) # Add the information about that connection to another dict # There's extra parameters in here than in the arguments because they # are values calculated by later methods when creating the SMT eqns # Channels do not have pressure though, since it decreases linearly # across the channel attributes = { 'kind': kind, 'length': Variable('_'.join([*name, 'length'])), 'min_length': min_length, 'width': Variable('_'.join([*name, 'width'])), 'min_width': min_width, 'height': Variable('_'.join([*name, 'height'])), 'min_height': min_height, 'flow_rate': Variable('_'.join([*name, 'flow_rate'])), 'droplet_volume': Variable('_'.join([*name, 'droplet_volume'])), 'viscosity': Variable('_'.join([*name, 'viscosity'])), 'resistance': Variable('_'.join([*name, 'resistance'])), 'phase': phase.lower(), 'port_from': port_from, 'port_to': port_to, 'x_detector': Variable('_'.join([*name, 'x_detector'])), 'min_sampling_rate': min_sampling_rate } # If user provides values, put them into the attributes dictionary if not min_width: attributes['min_width'] = min_width if not min_length: attributes['min_length'] = min_length if not min_height: attributes['min_height'] = min_height # Create this edge in the graph self.dg.add_edge(*name) # Add argument to attributes within NetworkX for key, attr in attributes.items(): self.dg.edges[port_from, port_to][key] = attr return
def node(self, name, x=False, y=False, kind='node', c=0.4, p=0.5, qf=0.9): # TODO: Add ability to add features when this is tjunc, same for channel """Create new node where fluids merge or split, kind of node (T-junction, Y-junction, cross, etc.) can be specified if not then a basic node connecting multiple channels will be created, units in brackets :param str name: Name of the node to use when connecting to a channel :param float x: Set the X position of this node (m) :param float y: Set the Y position of this node (m) :param str kind: The type of node this is, default is node, other option is t-junction :returns: None -- no issues with creating this node :raises: TypeError if an input parameter is wrong type ValueError if an input parameter has an invalid value """ user_provided_params = { name: 'string', x: 'positive number', y: 'positive number', kind: 'string', c: 'positive number', p: 'positive number', qf: 'positive number' } # Checking that arguments are valid self.validate_params(user_provided_params, 'node', name) if name in self.dg.nodes: raise ValueError("Must provide a unique name") if 'translate_' + kind.lower() not in self.translation_strats: raise ValueError( "kind %s must be either %s" % ("translate_" + kind.lower(), self.translation_strats)) # Ports are stored with nodes because ports are just a specific type of # node that has a constant flow rate only accept ports of the right # kind (input or output) # While the user can't define most parameters for a node because it # doesnt take an input from outside the chip, they're still added # and set to zero so checks to each node to see if there is a min # value for each node doesn't raise a KeyError attributes = { 'kind': kind.lower(), 'pressure': Variable(name + '_pressure'), 'min_pressure': None, 'flow_rate': Variable(name + '_flow_rate'), 'min_flow_rate': None, 'viscosity': Variable(name + '_viscosity'), 'min_viscosity': None, 'density': Variable(name + '_density'), 'min_density': None, 'x': Variable(name + '_x'), 'min_x': None, 'y': Variable(name + '_y'), 'min_y': None, 'c': c, 'p': p, 'qf': qf } # If user provides values, put them into the attributes dictionary if x: attributes['min_x'] = x if y: attributes['min_y'] = y # Create this node in the graph self.dg.add_node(name) # Add argument to attributes within NetworkX for key, attr in attributes.items(): self.dg.nodes[name][key] = attr return
# -*- coding: utf-8 -*- from dreal.symbolic import Variable, logical_and, sin, cos from dreal.symbolic import logical_imply, forall from dreal.api import CheckSatisfiability, Minimize from dreal.util import Box from dreal.solver import Config import math import unittest x = Variable("x") y = Variable("y") z = Variable("z") p = Variable("p") x0 = Variable("x0") x1 = Variable("x1") x2 = Variable("x2") f_sat = logical_and(0 <= x, x <= 10, 0 <= y, y <= 10, 0 <= z, z <= 10, sin(x) + cos(y) == z) f_unsat = logical_and(3 <= x, x <= 4, 4 <= y, y <= 5, 5 <= z, z <= 6, sin(x) + cos(y) == z) objective = 2 * x * x + 6 * x + 5 constraint = logical_and(-10 <= x, x <= 10) class ApiTest(unittest.TestCase): def test_delta_sat(self): result = CheckSatisfiability(f_sat, 0.001) self.assertEqual(type(result), Box)
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
def rambusOscillatorTanh(numStages, g_cc=0.5, numSolutions="all", a=-5.0): epsilon = 1e-14 start = time.time() g_fwd = 1.0 lenV = numStages * 2 vs = [] vfwds = [] vccs = [] for i in range(lenV): vs.append(Variable("v" + str(i))) vfwds.append(Variable("vfwd" + str(i))) vccs.append(Variable("vcc" + str(i))) allConstraints = [] # Store rambus oscillator constraints for i in range(lenV): allConstraints.append(vs[i] >= -1) allConstraints.append(vs[i] <= 1) fwdInd = (i - 1) % lenV ccInd = (i + lenV // 2) % lenV allConstraints.append(vfwds[i] == tanh(a * vs[fwdInd])) allConstraints.append(vccs[i] == tanh(a * vs[ccInd])) allConstraints.append(g_fwd * vfwds[i] + (-g_fwd - g_cc) * vs[i] + g_cc * vccs[i] == 0) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] for i in range(lenV): singleExcludingConstraints.append(vs[i] < solution[i][0]) singleExcludingConstraints.append(vs[i] > solution[i][1]) excludingConstraints.append(singleExcludingConstraints) # Add all the rambus oscillator constraints f_sat = logical_and(*allConstraints) # Add constraints so that old hyperrectangles are not considered if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((lenV, 2)) for i in range(lenV): hyper[i, :] = [ result[vs[i]].lb() - 2 * epsilon, result[vs[i]].ub() + 2 * epsilon ] #print ("hyper", hyper) allSolutions.append(hyper) print("num solutions found", len(allSolutions)) end = time.time() print("time taken", end - start) return allSolutions
def rambusOscillatorLcMosfet(numStages, numSolutions = "all", g_cc = 0.5, Vtp = -0.4, Vtn = 0.4, Vdd = 1.8, Kn = 270*1e-6, Kp = -90*1e-6, Sn = 3.0): epsilon = 1e-14 start = time.time() #print ("Vtp", Vtp, "Vtn", Vtn, "Vdd", Vdd, "Kn", Kn, "Kp", Kp, "Sn", Sn) g_fwd = 1.0 lenV = numStages*2 Sp = 2*Sn vs = [] ifwdNs = [] ifwdPs = [] iccNs = [] iccPs = [] for i in range(lenV): vs.append(Variable("v" + str(i))) ifwdNs.append(Variable("ifwdN" + str(i))) ifwdPs.append(Variable("ifwdP" + str(i))) iccNs.append(Variable("iccN" + str(i))) iccPs.append(Variable("iccP" + str(i))) allConstraints = [] for i in range(lenV): allConstraints.append(vs[i] >= 0.0) allConstraints.append(vs[i] <= Vdd) allConstraints.append(g_fwd*(-ifwdNs[i]-ifwdPs[i]) + g_cc*(-iccNs[i]-iccPs[i]) == 0) fwdInd = (i-1)%lenV ccInd = (i+lenV//2)%lenV fwdConstraints = nFet(Vtn, Vdd, Kn, Sn, 0.0, vs[fwdInd], vs[i], ifwdNs[i]) fwdConstraints += pFet(Vtp, Vdd, Kp, Sp, Vdd, vs[fwdInd], vs[i], ifwdPs[i]) ccConstraints = nFet(Vtn, Vdd, Kn, Sn, 0.0, vs[ccInd], vs[i], iccNs[i]) ccConstraints += pFet(Vtp, Vdd, Kp, Sp, Vdd, vs[ccInd], vs[i], iccPs[i]) allConstraints += fwdConstraints + ccConstraints allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] for i in range(lenV): singleExcludingConstraints.append(vs[i] < solution[i][0]) singleExcludingConstraints.append(vs[i] > solution[i][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((lenV,2)) for i in range(lenV): hyper[i,:] = [result[vs[i]].lb() - 1000*epsilon, result[vs[i]].ub() + 1000*epsilon] #print ("hyper", hyper) allSolutions.append(hyper) print ("num solutions found", len(allSolutions)) end = time.time() print ("time taken", end - start) return allSolutions
def port(self, name, kind, min_pressure=False, min_flow_rate=False, x=False, y=False, fluid_name='default'): """Create new port where fluids can enter or exit the circuit, any optional tag left empty will be converted to a variable for the SMT solver to solve for a give a value, units in brackets :param str name: The name of the port to use when defining channels :param str kind: Define if this is an 'input' or 'output' port :param float density: Density of fluid (kg/m^3) :param float min_viscosity: Viscosity of the fluid (Pa*s) :param float min_pressure: Pressure of the input fluid, (Pa) :param float min_flow_rate - flow rate of input fluid, (m^3/s) :param float X: x-position of port on chip schematic (m) :param float Y: y-position of port on chip schematic (m) :returns: None -- no issues with creating this port :raises: TypeError if an input parameter is wrong type ValueError if an input parameter has an invalid value """ user_provided_params = { name: 'string', min_pressure: 'positive number', min_flow_rate: 'positive number', x: 'positive number', y: 'positive number', kind: 'string', fluid_name: 'string' } # Checking that arguments are valid self.validate_params(user_provided_params, 'port', name) if name in self.dg.nodes: raise ValueError("Must provide a unique name") if 'translate_' + kind.lower() not in self.translation_strats: raise ValueError("kind must be either %s" % self.translation_strats) # Initialize fluid properties fluid_properties = Fluid(fluid_name) # Ports are stored with nodes because ports are just a specific type of # node that has a constant flow rate # only accept ports of the right kind (input or output) attributes = { 'kind': kind.lower(), 'viscosity': Variable(name + '_viscosity'), 'min_viscosity': fluid_properties.min_viscosity, 'pressure': Variable(name + '_pressure'), 'min_pressure': min_pressure, 'flow_rate': Variable(name + '_flow_rate'), 'min_flow_rate': min_flow_rate, 'density': Variable(name + '_density'), 'min_density': fluid_properties.min_density, 'x': Variable(name + '_x'), 'y': Variable(name + '_y'), 'min_x': x, 'min_y': y, 'analyte_diffusivities': fluid_properties.analyte_diffusivities, 'analyte_initial_concentrations': fluid_properties.analyte_initial_concentrations, 'analyte_radii': fluid_properties.analyte_radii, 'analyte_charges': fluid_properties.analyte_charges } # If user provides values, put them into the attributes dictionary if not x: attributes['min_x'] = x if not y: attributes['min_y'] = y if not min_flow_rate: attributes['min_flow_rate'] = min_flow_rate if not min_pressure: attributes['min_pressure'] = min_pressure # Create this node in the graph self.dg.add_node(name) # Add argument to attributes within NetworkX for key, attr in attributes.items(): self.dg.nodes[name][key] = attr return
def inverterLoopLcMosfet(numInverters, numSolutions = "all", Vtp = -0.4, Vtn = 0.4, Vdd = 1.8, Kn = 270*1e-6, Kp = -90*1e-6, Sn = 3.0): epsilon = 1e-14 start = time.time() #print ("Vtp", Vtp, "Vtn", Vtn, "Vdd", Vdd, "Kn", Kn, "Kp", Kp, "Sn", Sn) Sp = 2*Sn vs = [] iNs = [] iPs = [] for i in range(numInverters): vs.append(Variable("v" + str(i))) iNs.append(Variable("iN" + str(i))) iPs.append(Variable("iP" + str(i))) allConstraints = [] for i in range(numInverters): allConstraints.append(vs[i] >= 0.0) allConstraints.append(vs[i] <= Vdd) allConstraints.append(-iNs[i]-iPs[i] == 0) inputInd = i outputInd = (i+1)%numInverters allConstraints += nFet(Vtn, Vdd, Kn, Sn, 0.0, vs[inputInd], vs[outputInd], iNs[i]) allConstraints += pFet(Vtp, Vdd, Kp, Sp, Vdd, vs[inputInd], vs[outputInd], iPs[i]) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] for i in range(numInverters): singleExcludingConstraints.append(vs[i] < solution[i][0]) singleExcludingConstraints.append(vs[i] > solution[i][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((numInverters,2)) for i in range(numInverters): hyper[i,:] = [result[vs[i]].lb() - 1000*epsilon, result[vs[i]].ub() + 1000*epsilon] #print ("hyper", hyper) allSolutions.append(hyper) print ("num solutions found", len(allSolutions)) end = time.time() print ("time taken", end - start) return allSolutions
def schmittTriggerLcMosfet(inputVoltage, Vtp = -0.4, Vtn = 0.4, Vdd = 1.8, Kn = 270*1e-6, Kp = -90*1e-6, Sn = 3.0, numSolutions = "all"): epsilon = 1e-14 start = time.time() #print ("Vtp", Vtp, "Vtn", Vtn, "Vdd", Vdd, "Kn", Kn, "Kp", Kp, "Sn", Sn) Sp = Sn *2.0 lenV = 3 vs = [] tIs = [] nIs = [] for i in range(lenV): vs.append(Variable("v" + str(i))) nIs.append(Variable("nI" + str(i))) for i in range(lenV*2): tIs.append(Variable("tI" + str(i))) allConstraints = [] for i in range(lenV): allConstraints.append(vs[i] >= 0.0) allConstraints.append(vs[i] <= 1.8) allConstraints += nFetLeak(Vtn, Vdd, Kn, Sn, 0.0, inputVoltage, vs[1], tIs[0]) allConstraints += nFetLeak(Vtn, Vdd, Kn, Sn, vs[1], inputVoltage, vs[0], tIs[1]) allConstraints += nFetLeak(Vtn, Vdd, Kn, Sn, vs[1], vs[0], Vdd, tIs[2]) allConstraints += pFetLeak(Vtp, Vdd, Kp, Sp, Vdd, inputVoltage, vs[2], tIs[3]) allConstraints += pFetLeak(Vtp, Vdd, Kp, Sp, vs[2], inputVoltage, vs[0], tIs[4]) allConstraints += pFetLeak(Vtp, Vdd, Kp, Sp, vs[2], vs[0], 0.0, tIs[5]) allConstraints.append(nIs[0] == -tIs[4] - tIs[1]) allConstraints.append(nIs[1] == -tIs[0] + tIs[1] + tIs[2]) allConstraints.append(nIs[2] == -tIs[3] + tIs[5] + tIs[4]) for i in range(lenV): allConstraints.append(nIs[i] == 0.0) allSolutions = [] while True: if numSolutions != "all" and len(allSolutions) == numSolutions: break # Store constraints pruning search space so that # old hyperrectangles are not considered excludingConstraints = [] for solution in allSolutions: singleExcludingConstraints = [] for i in range(lenV): singleExcludingConstraints.append(vs[i] < solution[i][0]) singleExcludingConstraints.append(vs[i] > solution[i][1]) excludingConstraints.append(singleExcludingConstraints) #print ("allConstraints") #print (allConstraints) #print ("numConstraints", len(allConstraints)) f_sat = logical_and(*allConstraints) if len(excludingConstraints) > 0: for constraints in excludingConstraints: f_sat = logical_and(f_sat, logical_or(*constraints)) #print ("f_sat") #print (f_sat) result = CheckSatisfiability(f_sat, epsilon) #print (result) if result is None: break hyper = np.zeros((lenV,2)) for i in range(lenV): hyper[i,:] = [result[vs[i]].lb() - 1000*epsilon, result[vs[i]].ub() + 1000*epsilon] #print ("hyper", hyper) allSolutions.append(hyper) print ("num solutions found", len(allSolutions)) end = time.time() print ("time taken", end - start) return allSolutions
def translate_tjunc(dg, name, crit_crossing_angle=0.5): """Create SMT expressions for a t-junction node that generates droplets Must have 2 input channels (continuous and dispersed phases) and one output channel where the droplets leave the node. Continuous is usually oil and dispersed is usually water :param str name: The name of the channel to generate SMT equations for :param crit_crossing_angle: The angle of the dispersed channel to the continuous must be great than this to have droplet generation :returns: None -- no issues with translating channel parameters to SMT :raises: KeyError, if channel is not found in the list of defined edges """ exprs = [] # Validate input if dg.size(name) != 3: raise ValueError("T-junction %s must have 3 connections" % name) # Since T-junction is just a specialized node, call translate node [exprs.append(val) for val in translate_node(dg, name)] # Renaming for consistency with the other nodes junction_node_name = name # Since there should only be one output node, this can be found first # from the dict of successors try: output_node_name = list(dict(dg.succ[name]).keys())[0] output_channel_name = (junction_node_name, output_node_name) except KeyError as e: raise KeyError("T-junction must have only one output") # these will be found later from iterating through the dict of # predecessor nodes to the junction node continuous_node_name = '' continuous_channel_name = '' dispersed_node_name = '' dispersed_channel_name = '' # NetworkX allows for the creation of dicts that contain all of # the edges containing a certain attribute, in this case phase is # of interest phases = nx.get_edge_attributes(dg, 'phase') for pred_node, phase in phases.items(): if phase == 'continuous': continuous_node_name = pred_node[0] continuous_channel_name = (continuous_node_name, junction_node_name) # assert width and height to be equal to output exprs.append( algorithms.retrieve(dg, continuous_channel_name, 'width') == algorithms.retrieve(dg, output_channel_name, 'width')) exprs.append( algorithms.retrieve(dg, continuous_channel_name, 'height') == algorithms.retrieve(dg, output_channel_name, 'height')) elif phase == 'dispersed': dispersed_node_name = pred_node[0] dispersed_channel_name = (dispersed_node_name, junction_node_name) # Assert that only the height of channel be equal exprs.append( algorithms.retrieve(dg, dispersed_channel_name, 'height') == algorithms.retrieve(dg, output_channel_name, 'height')) elif phase == 'output': continue else: raise ValueError("Invalid phase for T-junction: %s" % name) # Epsilon, sharpness of T-junc, must be greater than 0 # epsilon = 0.01*w for liquid droplets from Steijn et al. epsilon = Variable('epsilon') exprs.append( epsilon == algorithms.retrieve(dg, continuous_channel_name, 'width') * 0.01) # TODO: Figure out why original had this cause it doesn't seem true # # Pressure at each of the 4 nodes must be equal # exprs.append(Equals(junction_node['pressure'], # continuous_node['pressure'] # )) # exprs.append(Equals(junction_node['pressure'], # dispersed_node['pressure'] # )) # exprs.append(Equals(junction_node['pressure'], # output_node['pressure'] # )) # Viscosity in continous phase equals viscosity at output exprs.append( algorithms.retrieve(dg, continuous_node_name, 'viscosity') == algorithms.retrieve(dg, output_node_name, 'viscosity')) # Flow rate into the t-junction equals the flow rate out exprs.append( algorithms.retrieve(dg, continuous_channel_name, 'flow_rate') + algorithms.retrieve(dg, dispersed_channel_name, 'flow_rate') == algorithms.retrieve(dg, output_channel_name, 'flow_rate')) # Assert that continuous and output channels are in a straight line exprs.append( algorithms.channels_in_straight_line(dg, continuous_node_name, junction_node_name, output_node_name)) # Droplet volume in channel equals calculated droplet volume # TODO: Manifold also has a table of constraints in the Schematic and # sets ChannelDropletVolume equal to dropletVolumeConstraint, however # the constraint is void (new instance of RealTypeValue) and I think # could conflict with calculated value, so ignoring it for now but # may be necessary to add at a later point if I'm misunderstand why # its needed exprs.append( algorithms.retrieve(dg, output_channel_name, 'droplet_volume') == algorithms.calculate_droplet_volume( dg, algorithms.retrieve(dg, output_channel_name, 'height'), algorithms.retrieve(dg, output_channel_name, 'width'), algorithms.retrieve(dg, dispersed_channel_name, 'width'), epsilon, algorithms.retrieve(dg, dispersed_node_name, 'flow_rate'), algorithms.retrieve(dg, continuous_node_name, 'flow_rate'))) # Assert critical angle is <= calculated angle cosine_squared_theta_crit = math.cos(math.radians(crit_crossing_angle))**2 # Continuous to dispersed exprs.append(cosine_squared_theta_crit <= algorithms.cosine_law_crit_angle( dg, continuous_node_name, junction_node_name, dispersed_node_name)) # Continuous to output exprs.append(cosine_squared_theta_crit <= algorithms.cosine_law_crit_angle( dg, continuous_node_name, junction_node_name, output_node_name)) # Output to dispersed exprs.append(cosine_squared_theta_crit <= algorithms.cosine_law_crit_angle( dg, output_node_name, junction_node_name, dispersed_node_name)) # Call translate on output [ exprs.append(val) for val in translation_strats[algorithms.retrieve( dg, output_node_name, 'kind')](dg, output_node_name) ] return exprs
def make_bound(name_of_var, lb, ub): v = Variable(name_of_var) return (v, logical_and(lb <= v, v <= ub))