def test_events(): # use bouncing ball to test events work # simulate in block diagram int_opts = block_diagram.DEFAULT_INTEGRATOR_OPTIONS.copy() int_opts['rtol'] = 1E-12 int_opts['atol'] = 1E-15 int_opts['nsteps'] = 1000 int_opts['max_step'] = 2**-3 x = x1, x2 = Array(dynamicsymbols('x_1:3')) mu, g = sp.symbols('mu g') constants = {mu: 0.8, g: 9.81} ic = np.r_[10, 15] sys = SwitchedSystem( x1, Array([0]), state_equations=r_[x2, -g], state_update_equation=r_[sp.Abs(x1), -mu*x2], state=x, constants_values=constants, initial_condition=ic ) bd = BlockDiagram(sys) res = bd.simulate(5, integrator_options=int_opts) # compute actual impact time tvar = dynamicsymbols._t impact_eq = (x2*tvar - g*tvar**2/2 + x1).subs( {x1: ic[0], x2: ic[1], g: 9.81} ) t_impact = sp.solve(impact_eq, tvar)[-1] # make sure simulation actually changes velocity sign around impact abs_diff_impact = np.abs(res.t - t_impact) impact_idx = np.where(abs_diff_impact == np.min(abs_diff_impact))[0] assert np.sign(res.x[impact_idx-1, 1]) != np.sign(res.x[impact_idx+1, 1])
def input(self, input_): if input_ is None: # or other checks? input_ = empty_array() if isinstance(input_, sp.Expr): # check it's a single dynamicsymbol? input_ = Array([input_]) self.dim_input = len(input_) self._inputs = input_
def state(self, state): if state is None: # or other checks? state = empty_array() if isinstance(state, sp.Expr): state = Array([state]) self.dim_state = len(state) self._state = state
def augment_input(system, input_=[], update_outputs=True): """ Augment input, useful to construct control-affine systems. Parameters ---------- system : DynamicalSystem The sytsem to augment the input of input_ : array_like of symbols, optional The input to augment. Use to augment only a subset of input components. update_outputs : boolean If true and the system provides full state output, will also add the augmented inputs to the output. """ # accept list, etc of symbols to augment augmented_system = system.copy() if input_ == []: # augment all input_ = system.input augmented_system.state = r_[system.state, input_] augmented_system.input = Array([ dynamicsymbols(str(input_var.func) + 'prime') for input_var in input_ ]) augmented_system.state_equation = r_[system.state_equation, augmented_system.input] if update_outputs and system.output_equation == system.state: augmented_system.output_equation = augmented_system.state return augmented_system
def output_equation(self, output_equation): if isinstance(output_equation, sp.Expr): output_equation = Array([output_equation]) if output_equation is None and self.dim_state == 0: output_equation = empty_array() else: if output_equation is None: output_equation = self.state assert output_equation.atoms( sp.Symbol) <= (set(self.constants_values.keys()) | set([dynamicsymbols._t])) if self.dim_state: assert find_dynamicsymbols(output_equation) <= set(self.state) else: assert find_dynamicsymbols(output_equation) <= set(self.input) self.dim_output = len(output_equation) self._output_equation = output_equation self.update_output_equation_function()
BlockDiagram = block_diagram.BlockDiagram int_opts = block_diagram.DEFAULT_INTEGRATOR_OPTIONS.copy() find_opts = block_diagram.DEFAULT_EVENT_FIND_OPTIONS.copy() int_opts['rtol'] = 1E-12 int_opts['atol'] = 1E-15 int_opts['nsteps'] = 1000 find_opts['xtol'] = 1E-12 find_opts['maxiter'] = int(1E3) # This example shows how to implement a simple saturation block # create an oscillator to generate the sinusoid x = Array([dynamicsymbols('x')]) # placeholder output symbol tvar = dynamicsymbols._t # use this symbol for time sin = DynamicalSystem(Array([sp.cos(tvar)]), x) # define the oscillator, llim = -0.75 ulim = 0.75 saturation_limit = r_[llim, ulim] saturation_output = r_['0,2', llim, x[0], ulim] sat = SwitchedOutput(x[0], saturation_limit, output_equations=saturation_output, input_=x) sat_bd = BlockDiagram(sin, sat) sat_bd.connect(sin, sat)
This example shows how to use a SwitchedSystem to model a bouncing ball. The event detection accurately finds the point of impact, and the simulation is generally accurate when the ball has sufficient energy. However, due to numerical error, the simulation does show the ball chattering after all the energy should have been dissipated. """ int_opts['rtol'] = 1E-12 int_opts['atol'] = 1E-15 int_opts['nsteps'] = 1000 int_opts['max_step'] = 2**-3 find_opts['xtol'] = 1E-12 find_opts['maxiter'] = int(1E3) x = x1, x2 = Array(dynamicsymbols('x_1:3')) mu, g = sp.symbols('mu g') constants = {mu: 0.8, g: 9.81} ic = np.r_[10, 15] sys = SwitchedSystem(x1, Array([0]), state_equations=r_[x2, -g], state_update_equation=r_[sp.Abs(x1), -mu * x2], state=x, constants_values=constants, initial_condition=ic) bd = BlockDiagram(sys) res = bd.simulate(25, integrator_options=int_opts, event_find_options=find_opts)
import numpy as np import sympy as sp import matplotlib.pyplot as plt from simupy.systems.symbolic import DynamicalSystem, dynamicsymbols from simupy.block_diagram import BlockDiagram from simupy.array import Array, r_ plt.ion() # This example simulates the Van der Pol oscillator. x = x1, x2 = Array(dynamicsymbols('x1:3')) mu = sp.symbols('mu') state_equation = r_[x2, -x1 + mu * (1 - x1**2) * x2] output_equation = r_[x1**2 + x2**2, sp.atan2(x2, x1)] sys = DynamicalSystem(state_equation, x, output_equation=output_equation, constants_values={mu: 5}) sys.initial_condition = np.array([1, 1]).T BD = BlockDiagram(sys) res = BD.simulate(30) plt.figure() plt.plot(res.t, res.x) plt.legend([sp.latex(s, mode='inline') for s in sys.state])
import numpy as np import sympy as sp import matplotlib.pyplot as plt from simupy.systems.symbolic import DynamicalSystem, dynamicsymbols from simupy.block_diagram import BlockDiagram from simupy.array import Array, r_ from simupy.discontinuities import SwitchedOutput plt.ion() # This example shows how to implement a simple saturation block llim = -0.75 ulim = 0.75 x = Array([dynamicsymbols('x')]) tvar = dynamicsymbols._t sin = DynamicalSystem(Array([sp.cos(tvar)]), x) sin_bd = BlockDiagram(sin) sin_res = sin_bd.simulate(2 * np.pi) plt.figure() plt.plot(sin_res.t, sin_res.x) limit = r_[llim, ulim] saturation_output = r_['0,2', llim, x[0], ulim] sat = SwitchedOutput(x[0], limit, output_equations=saturation_output, input_=x) sat_bd = BlockDiagram(sin, sat) sat_bd.connect(sin, sat)