def get_equation_io(self, eqn_sys: EqnSys): # determine all signals present in the set of equations all_signal_names = set(signal_names(eqn_sys.get_all_signals())) # determine inputs input_names = (signal_names(self.get_analog_inputs()) | self.assignments.keys()) & all_signal_names inputs = self.get_signals(input_names) # determine states state_names = set(signal_names(eqn_sys.get_states())) deriv_names = set(signal_names(eqn_sys.get_derivs())) states = self.get_signals(state_names) # determine outputs output_names = (all_signal_names - input_names - state_names - deriv_names) & self.signals.keys() outputs = self.get_signals(output_names) # determine sel_bits sel_bit_names = set(signal_names(eqn_sys.get_sel_bits())) sel_bits = self.get_signals(sel_bit_names) # return result return inputs, states, outputs, sel_bits
def main(): # equation display print(EqnList([AnalogSignal('a') == AnalogSignal('b')])) print() # signal extraction print(signal_names(EqnList([AnalogSignal('a') == AnalogSignal('b')]).get_all_signals())) print(signal_names(EqnList([AnalogSignal('a') == Deriv(AnalogSignal('b'))]).get_derivs())) print(signal_names(EqnList([AnalogSignal('a') == Deriv(AnalogSignal('b'))]).get_states())) print(signal_names(EqnList([AnalogSignal('a') == EqnCase([1, 2], [DigitalSignal('s')])]).get_sel_bits()))
def make_array(self, expr: Array): # make the output signal that will hold the results output = Signal(name=next(self.namer), format_=expr.format_) self.make_signal(output) # compile the array elements and address to signals elements = [self.expr_to_signal(element) for element in expr.elements] address = self.expr_to_signal(expr.address) # extra step for analog signals -- they must be aligned to the output format if isinstance(expr.format_, RealFormat): # rename the elements list to indicate these values are not aligned to the output format non_aligned_elements = elements # created an "aligned" version of each signal elements = [Signal(name=next(self.namer), format_=expr.format_) for _ in range(len(expr))] for element, non_aligned_element in zip(elements, non_aligned_elements): self.make_signal(element) self.make_assign(input_=non_aligned_element, output=element) # now create the array case_statment(gen=self, sel=address.name, var=output.name, values=signal_names(elements), default=0) # and last return the output signal return output
def make_concatenation(self, expr: Concatenate): output = Signal(name=next(self.namer), format_=expr.format_) self.make_signal(output) inputs_ = [self.expr_to_signal(operand) for operand in expr.operands] value = '{' + ', '.join(signal_names(inputs_)) + '}' self.digital_assignment(signal=output, value=value) return output
def make_bitwise_operator(self, expr: BitwiseOperator): output = Signal(name=next(self.namer), format_=expr.format_) self.make_signal(output) inputs = [self.expr_to_signal(operand) for operand in expr.operands] value = BITWISE_OP[type(expr)].join(signal_names(inputs)) self.digital_assignment(signal=output, value=value) return output
def main(): from msdsl.eqn.deriv import Deriv from msdsl.eqn.cases import eqn_case from msdsl.expr.signals import DigitalSignal model = MixedSignalModel('test', AnalogInput('x'), dt=1) y = AnalogSignal('y') z = model.add_signal(AnalogSignal('z', 10)) s = model.add_signal(DigitalSignal('s')) eqn_sys = EqnSys( [y == model.x + 1, Deriv(z) == (y - z) * eqn_case([2, 3], [s])]) inputs, states, outputs, sel_bits = model.get_equation_io(eqn_sys) print('inputs:', signal_names(inputs)) print('states:', signal_names(states)) print('outputs:', signal_names(outputs)) print('sel_bits:', signal_names(sel_bits))
def to_lds(self, inputs: List[Signal] = None, states: List[Signal] = None, outputs: List[Signal] = None): # set defaults inputs = inputs if inputs is not None else [] states = states if states is not None else [] outputs = outputs if outputs is not None else [] # create list of derivatives of state variables deriv_dict = {deriv.name: deriv for deriv in self.get_derivs()} derivs = list(deriv_dict.values()) # sanity check: no repeated entries in inputs, states, derivatives, or outputs assert len(set(signal_names(inputs))) == len( inputs), 'Repeated entries in inputs.' assert len(set(signal_names(outputs))) == len( outputs), 'Repeated entries in outputs.' assert len(set(signal_names(states))) == len( states), 'Repeated entries in states.' assert len(set(signal_names(derivs))) == len( derivs), 'Repeated entries in derivatives.' # sanity check: inputs, states, derivatives, and outputs should be disjoint assert set(signal_names(inputs)).isdisjoint( signal_names(outputs)), 'Inputs and outputs are not disjoint.' assert set(signal_names(inputs)).isdisjoint( signal_names(states)), 'Inputs and states are not disjoint.' assert set(signal_names(inputs)).isdisjoint(signal_names( derivs)), 'Inputs and state derivatives are not disjoint.' assert set(signal_names(outputs)).isdisjoint( signal_names(states)), 'Outputs and states are not disjoint.' assert set(signal_names(outputs)).isdisjoint(signal_names( derivs)), 'Outputs and state derivatives are not disjoint.' assert set(signal_names(states)).isdisjoint(signal_names( derivs)), 'States and state derivatives are not disjoint.' # sanity check: signal names of derivatives should be the states assert set(signal_names(states)) == set( signal_names([deriv.signal for deriv in derivs])) # create list of all internal signals, then use it to figure out what signals are completely internal external_names = set(signal_names(inputs + outputs + states + derivs)) internal_name_set = set(signal_names( self.get_all_signals())) - external_names # indices of known and unknown variables unknowns = list2dict( list(internal_name_set) + signal_names(outputs) + signal_names(derivs)) knowns = list2dict(signal_names(inputs) + signal_names(states)) # sanity checks assert not ( len(self) > len(unknowns) ), f'System of equations is over-constrained with {len(self)} equations and {len(unknowns)} unknowns.' assert not ( len(self) < len(unknowns) ), f'System of equations is under-constrained with {len(self)} equations and {len(unknowns)} unknowns.' # build up matrices U = np.zeros((len(self), len(unknowns)), dtype=float) V = np.zeros((len(self), len(knowns)), dtype=float) for row, eqn in enumerate(self): # prepare equation for analysis eqn = eqn.lhs - eqn.rhs eqn = distribute_mult(eqn) # extract coefficients of signals (note that some signals may be repeated - we deal with this in the next step coeffs, others = extract_coeffs(eqn) assert len(others) == 0, \ 'The following terms are not yet handled: ['+ ', '.join(str(other) for other in others)+']' # sum up all of the coefficients for each signal for coeff, signal in coeffs: if signal.name in unknowns: U[row, unknowns[signal.name]] += +coeff elif signal.name in knowns: V[row, knowns[signal.name]] += -coeff else: raise Exception( 'Variable is not marked as known vs. unknown: ' + signal.name) # solve for unknowns in terms of knowns M = np.linalg.solve(U, V) # separate into A, B, C, D matrices if len(states) > 0: A = np.zeros((len(states), len(states)), dtype=float) for row, out_state in enumerate(signal_names(states)): for col, in_state in enumerate(signal_names(states)): A[row, col] = M[unknowns[deriv_str(out_state)], knowns[in_state]] else: A = None if len(states) > 0 and len(inputs) > 0: B = np.zeros((len(states), len(inputs)), dtype=float) for row, out_state in enumerate(signal_names(states)): for col, in_input in enumerate(signal_names(inputs)): B[row, col] = M[unknowns[deriv_str(out_state)], knowns[in_input]] else: B = None if len(outputs) > 0 and len(states) > 0: C = np.zeros((len(outputs), len(states)), dtype=float) for row, out_output in enumerate(signal_names(outputs)): for col, in_state in enumerate(signal_names(states)): C[row, col] = M[unknowns[out_output], knowns[in_state]] else: C = None if len(outputs) > 0 and len(inputs) > 0: D = np.zeros((len(outputs), len(inputs)), dtype=float) for row, out_output in enumerate(signal_names(outputs)): for col, in_input in enumerate(signal_names(inputs)): D[row, col] = M[unknowns[out_output], knowns[in_input]] else: D = None return LDS(A=A, B=B, C=C, D=D)
def add_eqn_sys(self, eqns: List[ModelExpr], extra_outputs=None): """ Accepts a list of equations that can contain derivatives of analog state variables. The approach used is to convert the system of differential equations into a standard-form linear dynamical system (reference: http://ee263.stanford.edu/lectures/linsys.pdf). If there are several eqn_cases to be considered, we construct a different LDS for each one. These LDS's are discretized using the given timestep by using the exponential matrix function assuming piecewise-constant input (sometimes known as the zero-order hold approach). :param eqns: List of equations. :param extra_outputs: List of internal variables in the system of equations that should be bound to analog signals. """ # set defaults extra_outputs = extra_outputs if extra_outputs is not None else [] # create object to hold system of equations eqn_sys = EqnSys(eqns) # analyze equation to find out knowns and unknowns inputs, states, outputs, sel_bits = self.get_equation_io(eqn_sys) # add the extra outputs as needed for extra_output in extra_outputs: if not isinstance(extra_output, Signal): print('Skipping extra output ' + str(extra_output) + ' since it is not a Signal.') elif extra_output.name in signal_names(outputs): print('Skipping extra output ' + extra_output.name + \ ' since it is already included by default in the outputs of the system of equations.') else: outputs.append(extra_output) # initialize lists of matrices collection = LdsCollection() # iterate over all of the bit combinations for k in range(2**len(sel_bits)): # substitute values for this particular setting sel_bit_settings = address_to_settings(k, sel_bits) eqn_sys_k = eqn_sys.subst_case(sel_bit_settings) # convert system of equations to a linear dynamical system lds = eqn_sys_k.to_lds(inputs=inputs, states=states, outputs=outputs) # discretize linear dynamical system lds = lds.discretize(dt=self.dt) # add to collection of LDS systems collection.append(lds) # construct address for selection if len(sel_bits) > 0: sel = concatenate(sel_bits) else: sel = None # add the discrete-time equation self.add_discrete_time_lds(collection=collection, inputs=inputs, states=states, outputs=outputs, sel=sel)