def test_gain_class(): system = System() gain_block = Gain(system, k=[[1, 2], [3, 4]]) gain_in = constant(value=[3, 4]) gain_block.input.connect(gain_in) npt.assert_almost_equal(gain_block.output(None), [11, 25])
def test_rigidbody_defaults(): mass = 1.5 moment_of_inertia = np.diag([13.2e-3, 12.8e-3, 24.6e-3]) system = System() rb_6dof = RigidBody6DOFFlatEarth(owner=system, mass=mass, moment_of_inertia=moment_of_inertia) dcm_to_euler = DirectCosineToEuler(system) thrust = constant(value=np.r_[0, 0, 0]) moment = constant(value=moment_of_inertia @ np.r_[0, 0, math.pi / 30**2]) thrust.connect(rb_6dof.forces_body) moment.connect(rb_6dof.moments_body) rb_6dof.dcm.connect(dcm_to_euler.dcm) rtol = 1e-12 atol = 1e-12 sim = Simulator(system, start_time=0, rtol=rtol, atol=atol) *_, last_item = sim.run_until(time_boundary=30.0, include_last=True) npt.assert_allclose(dcm_to_euler.yaw(last_item), math.pi / 2, rtol=rtol, atol=atol) npt.assert_allclose(dcm_to_euler.pitch(last_item), 0, rtol=rtol, atol=atol) npt.assert_allclose(dcm_to_euler.roll(last_item), 0, rtol=rtol, atol=atol) npt.assert_allclose(rb_6dof.position_earth(last_item), [0, 0, 0], rtol=rtol, atol=atol)
def damped_oscillator_with_events( mass=100.0, damping_coefficient=50.0, spring_coefficient=1.0, initial_value=10.0, ): system = System() lti = LTISystem( parent=system, system_matrix=[ [0, 1], [-spring_coefficient / mass, -damping_coefficient / mass], ], input_matrix=[[-1 / mass], [0]], output_matrix=[[1, 0]], feed_through_matrix=np.zeros((1, 1)), initial_condition=[initial_value, 0], ) ZeroCrossEventSource(owner=system, event_function=(lambda data: lti.state(data)[1])) time_constant = 2 * mass / damping_coefficient src = InputSignal(system, shape=1) lti.input.connect(src) return system, lti, 3 * time_constant, [lti.output]
def test_multiple_event_sources_error(): """ Test the detection of multiple conflicting event sources on connection. """ system = System() clock_a = Clock(system, period=1.0) clock_b = Clock(system, period=1.0) port_a = EventPort(system) port_b = EventPort(system) port_c = EventPort(system) port_d = EventPort(system) port_a.connect(clock_a) port_d.connect(clock_b) port_b.connect(port_a) port_c.connect(port_d) assert port_b.source is clock_a assert port_c.source is clock_b with pytest.raises(MultipleEventSourcesError): port_c.connect(port_b)
def pendulum(length=1): """Create a steady-state model for a pendulum""" system = System() g = 9.81 # Gravity def omega_dt(data): """Calculate the acceleration of the pendulum""" return np.sin(phi(data)) * g / length def phi_dt(data): """Calculate the velocity of the pendulum""" return omega(data) omega = State(system, derivative_function=omega_dt) phi = State(system, derivative_function=phi_dt) # Set up a steady-state configuration config = SteadyStateConfiguration(system) # Minimize the total energy config.objective = lambda data: (g * length * (1 - np.cos(phi(data))) + (length * omega(data))**2) # Constrain the angular velocity (no steady state) config.states[omega].steady_state = False # Constrain the angle config.states[phi].steady_state = False config.states[phi].lower_bounds = -np.pi config.states[phi].upper_bounds = np.pi return config
def test_event_connection(): """ Test connecting event ports to events. """ system = System() clock_a = Clock(system, period=1.0) port_a = EventPort(system) port_b = EventPort(system) port_c = EventPort(system) port_d = EventPort(system) port_b.connect(clock_a) port_d.connect(clock_a) port_a.connect(port_b) port_c.connect(port_d) port_c.connect(port_b) assert port_a.source == clock_a assert port_b.source == clock_a assert port_c.source == clock_a assert port_d.source == clock_a
def test_deprecated_options(): """Test if deprecated options lead to warnings""" system = System() with pytest.warns(DeprecationWarning): Simulator(system, start_time=0, event_xtol=1e-12) with pytest.warns(DeprecationWarning): Simulator(system, start_time=0, event_maxiter=1000)
def test_port_not_connected_error(): """Test the detection of unconnected ports""" system = System() port = Port() system_state = SystemState(time=0, system=system) with pytest.raises(PortNotConnectedError): port(system_state)
def test_clock_handling(): """Test the handling of clocks in the simulator.""" time_constant = 1.0 initial_value = 2.0 system = System() input_signal = constant(value=0.0) lag = LTISystem( parent=system, system_matrix=-1 / time_constant, input_matrix=1, output_matrix=1, feed_through_matrix=0, initial_condition=[initial_value], ) lag.input.connect(input_signal) clock1 = Clock(system, period=0.2) hold1 = zero_order_hold( system, input_port=lag.output, event_port=clock1, initial_condition=initial_value, ) clock2 = Clock(system, period=0.25, end_time=2.0) hold2 = zero_order_hold( system, input_port=lag.output, event_port=clock2, initial_condition=initial_value, ) # Clock 3 will not fire within the simulation time frame clock3 = Clock(system, period=0.25, start_time=-6.0, end_time=-1.0) hold3 = zero_order_hold( system, input_port=lag.output, event_port=clock3, initial_condition=initial_value, ) simulator = Simulator(system, start_time=0.0) result = SimulationResult(system, simulator.run_until(time_boundary=5.0)) time_floor1 = np.floor(result.time / clock1.period) * clock1.period time_floor2 = np.minimum( clock2.end_time, (np.floor(result.time / clock2.period) * clock2.period)) reference1 = initial_value * np.exp(-time_floor1 / time_constant) reference2 = initial_value * np.exp(-time_floor2 / time_constant) npt.assert_almost_equal(hold1(result), reference1) npt.assert_almost_equal(hold2(result), reference2) npt.assert_almost_equal(hold3(result), initial_value)
def test_without_goal(): """Test the steady-state finding algorithm without objective function and with no steady states""" system = System() config = SteadyStateConfiguration(system) with pytest.raises(ValueError): find_steady_state(config)
def test_invalid_objective(): """Test the steady-state finding algorithm with an invalid objective""" system = System() config = SteadyStateConfiguration(system) config.objective = True with pytest.raises(ValueError): find_steady_state(config)
def test_sum_signal(channel_weights, output_size, inputs, expected_output): system = System() input_signals = [ InputSignal(system, shape=output_size, value=input_value) for input_value in inputs ] sum_result = sum_signal(input_signals, gains=channel_weights) system_state = SystemState(time=0, system=system) actual_output = sum_result(system_state) npt.assert_almost_equal(actual_output, expected_output)
def test_integrator(): """Test an integrator""" system = System() int_input = constant(1.0) int_output = integrator(system, int_input) simulator = Simulator(system, start_time=0.0) result = SimulationResult(system, simulator.run_until(time_boundary=10.0)) npt.assert_almost_equal(int_output(result).ravel(), result.time)
def test_tick_generator_stop_iteration(): """Test whether the clock tick generator throws a ``StopIteration`` exception when the current time is after the end time.""" system = System() clock = Clock(system, period=1.0, start_time=0.0, end_time=2.0) tick_generator = clock.tick_generator(not_before=3.0) with pytest.raises(StopIteration): next(tick_generator)
def first_order_lag_no_input(time_constant=1, initial_value=10): system = System() lti = LTISystem( parent=system, system_matrix=-1 / time_constant, input_matrix=np.empty(shape=(1, 0)), output_matrix=1, feed_through_matrix=np.empty(shape=(0, )), initial_condition=[initial_value], ) return system, lti, 3 * time_constant, [lti.output]
def test_aerodyn_blocks(thrust_coefficient, power_coefficient): system = System() propeller = Propeller( system, thrust_coefficient=thrust_coefficient, power_coefficient=power_coefficient, diameter=8 * 25.4e-3, ) dcmotor = DCMotor( system, motor_constant=789.0e-6, resistance=43.3e-3, inductance=1.9e-3, moment_of_inertia=5.284e-6, initial_omega=1, ) thruster = Thruster(system, vector=np.c_[0, 0, -1], arm=np.c_[1, 1, 0], direction=1) density = constant(value=1.29) gravity = constant(value=[0, 0, 1.5 / 4 * 9.81]) voltage = InputSignal(system) voltage.connect(dcmotor.voltage) density.connect(propeller.density) dcmotor.speed_rps.connect(propeller.speed_rps) propeller.torque.connect(dcmotor.external_torque) propeller.thrust.connect(thruster.scalar_thrust) dcmotor.torque.connect(thruster.scalar_torque) force_sum = sum_signal((thruster.thrust_vector, gravity)) # Determine the steady state steady_state_config = SteadyStateConfiguration(system) # Enforce the sum of forces to be zero along the z-axis steady_state_config.ports[force_sum].lower_bounds[2] = 0 steady_state_config.ports[force_sum].upper_bounds[2] = 0 # Ensure that the input voltage is non-negative steady_state_config.inputs[voltage].lower_bounds = 0 sol = find_steady_state(steady_state_config) assert sol.success npt.assert_almost_equal(sol.state, [856.5771575, 67.0169392]) npt.assert_almost_equal(sol.inputs, [3.5776728]) npt.assert_almost_equal(propeller.power(sol.system_state), 45.2926865) npt.assert_almost_equal( np.ravel(thruster.torque_vector(sol.system_state)), [-3.67875, 3.67875, -0.0528764], )
def test_empty_system(): """Test whether the linearization algorithm properly flags a system without states and inputs""" # Create a system and add a signal to it, so we can check whether the # algorithm is actually checking states and inputs instead of signals or # outputs system = System() output_signal = Signal() config = LinearizationConfiguration(system) OutputDescriptor(config, output_signal) with pytest.raises(ValueError): system_jacobian(config, single_matrix=False)
def test_sum_block(channel_weights, output_size, inputs, expected_output): system = System() sum_block = Sum(system, channel_weights=channel_weights, output_size=output_size) for idx in range(len(inputs)): input_signal = InputSignal(system, shape=output_size, value=inputs[idx]) sum_block.inputs[idx].connect(input_signal) system_state = SystemState(time=0, system=system) actual_output = sum_block.output(system_state) npt.assert_almost_equal(actual_output, expected_output)
def lti_gain(gain): system = System() lti = LTISystem( parent=system, system_matrix=np.empty((0, 0)), input_matrix=np.empty((0, 1)), output_matrix=np.empty((1, 0)), feed_through_matrix=gain, ) src = InputSignal(system) lti.input.connect(src) return system, lti, 10.0, [lti.output]
def first_order_lag(time_constant=1, initial_value=10): system = System() lti = LTISystem( parent=system, system_matrix=-1 / time_constant, input_matrix=1, output_matrix=[1], feed_through_matrix=0, initial_condition=[initial_value], ) src = InputSignal(system) lti.input.connect(src) return system, lti, 3 * time_constant, [lti.output]
def test_saturation(): system = System() Clock(system, period=0.01) @signal_function def _sine_source(system_state): return np.sin(2 * np.pi * system_state.time) saturated_out = saturation(_sine_source, lower_limit=-0.5, upper_limit=0.6) simulator = Simulator(system, start_time=0.0) result = SimulationResult(system, simulator.run_until(time_boundary=1.0)) sine_data = _sine_source(result) saturated_data = saturated_out(result) saturated_exp = np.minimum(np.maximum(sine_data, -0.5), 0.6) npt.assert_almost_equal(saturated_data, saturated_exp)
def test_tick_generator(start_time, end_time, run_before_start, expected): """Test the tick generator of a clock event""" system = System() clock = Clock(system, period=1.0, start_time=start_time, end_time=end_time, run_before_start=run_before_start) tick_generator = clock.tick_generator(not_before=0.0) # Fetch at most 4 ticks ticks = [tick for _idx, tick in zip(range(4), tick_generator)] npt.assert_almost_equal(ticks, expected)
def test_state_access(): """Test whether calling a ``State`` object calls the ``get_state_value`` and ``set_state_value`` methods of the provider object""" system = System() state = State(system) provider = Mock() # Check read access state(provider) provider.get_state_value.assert_called_with(state) # Check write access provider.reset_mock() state.set_value(provider, 10) provider.set_state_value.assert_called_with(state, 10)
def test_rigidbody_movement(): mass = 1.5 omega = 2 * math.pi / 120 r = 10.0 vx = r * omega moment_of_inertia = np.diag([13.2e-3, 12.8e-3, 24.6e-3]) system = System() rb_6dof = RigidBody6DOFFlatEarth( owner=system, mass=mass, initial_velocity_earth=[vx, 0, 0], initial_angular_rates_earth=[0, 0, omega], initial_position_earth=[0, 0, 0], initial_transformation=np.eye(3, 3), moment_of_inertia=moment_of_inertia, ) dcm_to_euler = DirectCosineToEuler(system) thrust = constant(value=np.r_[0, mass * omega * vx, 0]) moment = constant(value=np.r_[0, 0, 0]) thrust.connect(rb_6dof.forces_body) moment.connect(rb_6dof.moments_body) rb_6dof.dcm.connect(dcm_to_euler.dcm) rtol = 1e-12 atol = 1e-12 sim = Simulator(system, start_time=0, rtol=rtol, atol=atol) *_, last_item = sim.run_until(time_boundary=30.0, include_last=True) npt.assert_allclose(dcm_to_euler.yaw(last_item), math.pi / 2, rtol=rtol, atol=atol) npt.assert_allclose(dcm_to_euler.pitch(last_item), 0, rtol=rtol, atol=atol) npt.assert_allclose(dcm_to_euler.roll(last_item), 0, rtol=rtol, atol=atol) npt.assert_allclose(rb_6dof.position_earth(last_item), [r, r, 0], rtol=rtol, atol=atol) npt.assert_allclose(rb_6dof.velocity_body(last_item), [vx, 0, 0], rtol=rtol, atol=atol) npt.assert_allclose(rb_6dof.omega_body(last_item), [0, 0, omega], rtol=rtol, atol=atol)
def test_invalid_lti_configurations(input_shape, system_shape, output_shape, feed_through_shape): """Test the detection of invalid LTI matrix configurations""" input_matrix = _make_array_or_scalar(shape=input_shape) system_matrix = _make_array_or_scalar(shape=system_shape) output_matrix = _make_array_or_scalar(shape=output_shape) feed_through_matrix = _make_array_or_scalar(shape=feed_through_shape) system = System() with pytest.raises(InvalidLTIException): LTISystem( parent=system, input_matrix=input_matrix, system_matrix=system_matrix, output_matrix=output_matrix, feed_through_matrix=feed_through_matrix, )
def test_event_port_call(): """Test the call method on EventPort objects""" mock_event_source = Mock() mock_event_source.configure_mock(source=mock_event_source, reference=mock_event_source) mock_event_source.signal.return_value = mock_event_source mock_data = Mock() system = System() port = EventPort(system) port.connect(mock_event_source) port(mock_data) mock_event_source.assert_called_with(mock_data)
def test_input_constraint_access(): """Test access to input constraint properties""" system = System() input_1 = InputSignal(system) input_2 = InputSignal(system) config = SteadyStateConfiguration(system) config.inputs[input_1].lower_bounds = 10 config.inputs[input_2].lower_bounds = 20 config.inputs[input_1].upper_bounds = 15 config.inputs[input_2].upper_bounds = 25 config.inputs[input_1].initial_guess = 100 npt.assert_equal(config.inputs[input_1].lower_bounds, 10) npt.assert_equal(config.inputs[input_2].lower_bounds, 20) npt.assert_equal(config.inputs[input_1].upper_bounds, 15) npt.assert_equal(config.inputs[input_2].upper_bounds, 25) npt.assert_equal(config.inputs[input_1].initial_guess, 100)
def water_tank_model( inflow_area=0.01, outflow_area=0.02, tank_area=0.2, target_height=5, initial_condition=None, initial_guess=None, max_inflow_velocity=None, ): """Create a steady-state configuration for a water tank""" g = 9.81 # Gravity # Create a new system system = System() # Model the inflow inflow_velocity = InputSignal(system) # Model the height state def height_derivative(data): """Calculate the time derivative of the height""" return (inflow_area * inflow_velocity(data) - outflow_area * np.sqrt(2 * g * height_state(data))) / tank_area height_state = State(system, derivative_function=height_derivative) # Set up the steady-state configuration config = SteadyStateConfiguration(system) # Enforce the inflow to be non-negative config.inputs[inflow_velocity].lower_bounds = 0 if max_inflow_velocity is not None: config.inputs[inflow_velocity].upper_bounds = max_inflow_velocity # Define the target height config.states[height_state].lower_bounds = target_height config.states[height_state].upper_bounds = target_height if initial_condition is not None: config.states[height_state].initial_condition = initial_condition if initial_guess is not None: config.inputs[inflow_velocity].initial_guess = initial_guess return config
def test_zero_crossing_event_detection(): """Test the detection of zero-crossing events.""" x0 = 0 y0 = 10 vx0 = 1 vy0 = 0 g = 9.81 gamma = 0.7 system = System() bouncing_ball = BouncingBall(parent=system, gravity=-g, gamma=gamma) initial_condition = np.empty(system.num_states) initial_condition[bouncing_ball.velocity.state_slice] = [vx0, vy0] initial_condition[bouncing_ball.position.state_slice] = [x0, y0] simulator = Simulator( system, start_time=0, initial_condition=initial_condition, max_step=0.05, event_detector_options={"max_subdiv": 10}, ) result = SimulationResult(system, simulator.run_until(time_boundary=8.0)) # Determine the time of the first impact t_impact = (2 * vy0 + math.sqrt(4 * vy0**2 + 8 * g * y0)) / (2 * g) # Check that the y-component of velocity changes sign around time of impact t_tolerance = 0.1 idx_start = bisect.bisect_left(result.time, t_impact - t_tolerance) idx_end = bisect.bisect_left(result.time, t_impact + t_tolerance) assert result.time[idx_start] < t_impact assert result.time[idx_end] >= t_impact vy = bouncing_ball.velocity(result)[1][idx_start:idx_end] sign_change = np.sign(vy[:-1]) != np.sign(vy[1:]) assert any(sign_change) # Check detection of excessive event error with pytest.raises(ExcessiveEventError): for _ in simulator.run_until(time_boundary=10.0): pass
def test_simulation_result_dictionary_access(): """Test the deprecated dictionary access for simulation results""" system = System() state_a = State(system, initial_condition=10) state_b = State(system, shape=2, initial_condition=[11, 12]) state_c = State(system, shape=(2, 2), initial_condition=[[13, 14], [15, 16]]) system_state = SystemState(time=0, system=system) result = SimulationResult(system) result.append(system_state) npt.assert_equal(result[state_a], state_a(result)) npt.assert_equal(result[state_b], state_b(result)) npt.assert_equal(result[state_b, 0], state_b(result)[0]) npt.assert_equal(result[state_b, 1], state_b(result)[1]) npt.assert_equal(result[state_c, 0, 0], state_c(result)[0, 0])