def test_multi_time_system_state(): """Test the system state with multiple time instances""" system = System() state_1 = State(owner=system) state_2 = State(owner=system, shape=2) input_1 = InputSignal(owner=system) input_2 = InputSignal(owner=system, shape=2) times = np.r_[0.0, 1.0, 2.0] states = np.empty(shape=(system.num_states, times.shape[0])) inputs = np.empty(shape=(system.num_inputs, times.shape[0])) states[state_1.state_slice] = (1.0, 2.0, 3.0) states[state_2.state_slice] = ((1.0, 2.0, 3.0), (4.0, 5.0, 6.0)) inputs[input_1.input_slice] = (1.0, 2.0, 3.0) inputs[input_2.input_slice] = ((1.0, 2.0, 3.0), (4.0, 5.0, 6.0)) system_state = SystemState(system=system, time=times, state=states, inputs=inputs) npt.assert_equal(system_state[state_1], (1.0, 2.0, 3.0)) npt.assert_equal(system_state[state_2], ((1.0, 2.0, 3.0), (4.0, 5.0, 6.0))) npt.assert_equal(system_state[input_1], (1.0, 2.0, 3.0)) npt.assert_equal(system_state[input_2], ((1.0, 2.0, 3.0), (4.0, 5.0, 6.0)))
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_system(): """Test the ``System`` class""" system = System() state_a = State( system, shape=(3, 3), initial_condition=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], derivative_function=None, ) state_b = State( system, shape=3, initial_condition=[10, 11, 12], derivative_function=None, ) input_c = InputSignal(system, value=1) input_d = InputSignal(system, shape=2, value=[2, 3]) event_a = ZeroCrossEventSource(system, event_function=None) event_b = ZeroCrossEventSource(system, event_function=None) # Check the counts assert system.num_inputs == input_c.size + input_d.size assert system.num_states == state_a.size + state_b.size assert system.num_events == 2 # Check the initial condition # This also checks that state indices are disjoint. initial_condition = system.initial_condition npt.assert_almost_equal( initial_condition[state_a.state_slice], state_a.initial_condition.ravel(), ) npt.assert_almost_equal( initial_condition[state_b.state_slice], state_b.initial_condition.ravel(), ) # Check the initial inputs # This also checks that input indices are disjoint. initial_input = system.initial_input npt.assert_almost_equal(initial_input[input_c.input_slice], np.atleast_1d(input_c.value).ravel()) npt.assert_almost_equal(initial_input[input_d.input_slice], np.atleast_1d(input_d.value).ravel()) # Check that event indices are disjoint assert event_a.event_index != event_b.event_index
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_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 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_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 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 damped_oscillator( 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], ) time_constant = 2 * mass / damping_coefficient src = InputSignal(system, shape=1) lti.input.connect(src) return system, lti, 3 * time_constant, [lti.output]
OutputDescriptor, ) from modypy.steady_state import SteadyStateConfiguration, find_steady_state # Constants G = 9.81 # Gravity A1 = 0.01 # Inflow cross section A2 = 0.02 # Outflow cross section At = 0.2 # Tank cross section TARGET_HEIGHT = 5 # 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 (A1 * inflow_velocity(data) - A2 * np.sqrt(2 * G * height_state(data))) / At height_state = State(system, derivative_function=height_derivative) # Configure for steady-state determination steady_state_config = SteadyStateConfiguration(system) # Enforce the height to equal the target height
def propeller_model( thrust_coefficient=0.09, power_coefficient=0.04, density=1.29, diameter=8 * 25.4e-3, moment_of_inertia=5.284e-6, target_thrust=1.5 * 9.81 / 4, maximum_torque=None, ): """Create a steady-state configuration for two propellers""" system = System() torque_1 = InputSignal(system) torque_2 = InputSignal(system) density_signal = constant(density) propeller_1 = Propeller( system, thrust_coefficient=thrust_coefficient, power_coefficient=power_coefficient, diameter=diameter, ) propeller_2 = Propeller( system, thrust_coefficient=thrust_coefficient, power_coefficient=power_coefficient, diameter=diameter, ) def speed_1_dt(data): """Derivative of the speed of the first propeller""" tot_torque = torque_1(data) - propeller_1.torque(data) return tot_torque / (2 * np.pi * moment_of_inertia) def speed_2_dt(data): """Derivative of the speed of the second propeller""" tot_torque = torque_2(data) - propeller_2.torque(data) return tot_torque / (2 * np.pi * moment_of_inertia) speed_1 = State(system, derivative_function=speed_1_dt) speed_2 = State(system, derivative_function=speed_2_dt) propeller_1.density.connect(density_signal) propeller_2.density.connect(density_signal) propeller_1.speed_rps.connect(speed_1) propeller_2.speed_rps.connect(speed_2) total_thrust = sum_signal((propeller_1.thrust, propeller_2.thrust)) total_power = sum_signal((propeller_1.power, propeller_2.power)) # Set up steady-state configuration config = SteadyStateConfiguration(system) # Minimize the total power config.objective = total_power # Constrain the speed to be positive config.states[speed_1].lower_bounds = 0 config.states[speed_2].lower_bounds = 0 # Constrain the torques to be positive config.inputs[torque_1].lower_bounds = 0 config.inputs[torque_2].lower_bounds = 0 npt.assert_equal(config.inputs[torque_1].lower_bounds, 0) npt.assert_equal(config.inputs[torque_2].lower_bounds, 0) if maximum_torque is not None: config.inputs[torque_1].upper_bounds = maximum_torque config.inputs[torque_2].upper_bounds = maximum_torque # Constrain the total thrust config.ports[total_thrust].lower_bounds = target_thrust config.ports[total_thrust].upper_bounds = target_thrust return config
direction=1, **parameters), Engine(system, vector=np.c_[0, 0, -1], arm=np.c_[+POSITION_X, -POSITION_Y, 0], direction=-1, **parameters), ] # Create input and output signals. # The steady-state determination will try to vary the input signals and states # such that the state derivatives and the output signals are zero. # Create individual input signals for the voltages of the four engines voltages = [ InputSignal(system, value=0), InputSignal(system, value=0), InputSignal(system, value=0), InputSignal(system, value=0), ] # Provide the air density as input density = constant(value=1.29) # Connect the engines for idx, engine in zip(itertools.count(), engines): # Provide voltage and density to the engine voltages[idx].connect(engine.voltage) density.connect(engine.density) # We consider gravity and a possible counter torque that need to be compensated
def test_system_state(): """Test the ``SystemState`` class""" system = System() input_a = InputSignal(system, shape=(3, 3), value=np.eye(3)) input_c = InputSignal(system, value=1) input_d = InputSignal(system, shape=2, value=[2, 3]) state_a = State( system, shape=(3, 3), initial_condition=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], derivative_function=input_a, ) state_a_dep = State( system, shape=(3, 3), initial_condition=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], derivative_function=input_a, ) state_b = State( system, shape=3, initial_condition=[10, 11, 12], derivative_function=(lambda data: np.r_[13, 14, 15]), ) state_b1 = State(system, shape=3, derivative_function=state_b) state_b2 = State(system, shape=3, derivative_function=None) signal_c = constant(value=16) event_a = ZeroCrossEventSource(system, event_function=(lambda data: 23)) system_state = SystemState(time=0, system=system) # Check the initial state npt.assert_almost_equal( system_state.state[state_a.state_slice], state_a.initial_condition.ravel(), ) npt.assert_almost_equal( system_state.state[state_a_dep.state_slice], state_a_dep.initial_condition.ravel(), ) npt.assert_almost_equal( system_state.state[state_b.state_slice], state_b.initial_condition.ravel(), ) npt.assert_almost_equal( system_state.state[state_b1.state_slice], state_b1.initial_condition.ravel(), ) npt.assert_almost_equal(system_state.state[state_b2.state_slice], np.zeros(state_b2.size)) # Check the inputs property npt.assert_almost_equal(system_state.inputs[input_a.input_slice], np.ravel(input_a.value)) npt.assert_almost_equal(system_state.inputs[input_c.input_slice], np.ravel(input_c.value)) npt.assert_almost_equal(system_state.inputs[input_d.input_slice], np.ravel(input_d.value)) # Check the get_state_value function npt.assert_almost_equal(system_state.get_state_value(state_a), state_a.initial_condition) npt.assert_almost_equal(system_state.get_state_value(state_a_dep), state_a_dep.initial_condition) npt.assert_almost_equal(system_state.get_state_value(state_b), state_b.initial_condition) # Check the function access npt.assert_equal(event_a(system_state), event_a.event_function(system_state)) npt.assert_equal(state_a(system_state), state_a.initial_condition) npt.assert_equal(input_a(system_state), input_a.value) npt.assert_equal(signal_c(system_state), signal_c.value)