Hard to understand error message: Lengths of U2 (1:27) and Y1
tallakt opened this issue · comments
So I was working on my first larger block diagram, and trying to get everything in place. First I was meeting error messages giving me the name of an input/output that I was able to figure out. But after sorting out all that, I was presented with the following error message, after which I am quite stuck.
I am asking in this issue if maybe the error message should be improved on?
julia> bd = mk2_block_diagram_()
┌ Warning: Connecting single output to multiple inputs Y1=Union{Nothing, Int64}[1, 9, 8, 5, 20, 21, 22, 23, 24, 25, 11, 13, 12, 14, 3, 4, 6, 7, 16, 18, 17, 19, 15, 4, 11, 12, 21, 2, 10, 1]
└ @ ControlSystemsBase ~/.julia/packages/ControlSystemsBase/eWEYr/src/connections.jl:323
ERROR: Lengths of U2 (1:27) and Y1 (Union{Nothing, Int64}[1, 9, 8, 5, 20, 21, 22, 23, 24, 25, 11, 13, 12, 14, 3, 4, 6, 7, 16, 18, 17, 19, 15, 4, 11, 12, 21, 2, 10, 1]) must be equal
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:35
[2] feedback(sys1::StateSpace{Continuous, Float64}, sys2::StateSpace{Continuous, Bool}; U1::Vector{Union{Nothing, Int64}}, Y1::Vector{Union{Nothing, Int64}}, U2::UnitRange{Int64}, Y2::UnitRange{Int64}, W1::Vector{Union{Nothing, Int64}}, Z1::Vector{Union{Nothing, Int64}}, W2::Vector{Union{Nothing, Int64}}, Z2::Vector{Union{Nothing, Int64}}, Wperm::Colon, Zperm::Colon, pos_feedback::Bool)
@ ControlSystemsBase ~/.julia/packages/ControlSystemsBase/eWEYr/src/connections.jl:332
[3] feedback(s1::NamedStateSpace{Continuous, StateSpace{Continuous, Float64}}, s2::NamedStateSpace{Continuous, StateSpace{Continuous, Bool}}; u1::Vector{Symbol}, w1::Vector{Symbol}, z1::Vector{Symbol}, y1::Vector{Symbol}, u2::Function, y2::Function, w2::Vector{Any}, z2::Vector{Any}, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:pos_feedback,), Tuple{Bool}}})
@ RobustAndOptimalControl ~/.julia/packages/RobustAndOptimalControl/nd5G8/src/named_systems2.jl:367
[4] connect(systems::Vector{NamedStateSpace{Continuous, StateSpace{Continuous, Float64}}}; u1::Vector{Symbol}, y1::Vector{Symbol}, w1::Vector{Symbol}, z1::Vector{Symbol}, verbose::Bool, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ RobustAndOptimalControl ~/.julia/packages/RobustAndOptimalControl/nd5G8/src/named_systems2.jl:463
[5] connect(systems::Vector{NamedStateSpace{Continuous, StateSpace{Continuous, Float64}}}, pairs::Vector{Pair{Symbol, Symbol}}; kwargs::Base.Pairs{Symbol, Vector{Symbol}, Tuple{Symbol, Symbol}, NamedTuple{(:w1, :z1), Tuple{Vector{Symbol}, Vector{Symbol}}}})
@ RobustAndOptimalControl ~/.julia/packages/RobustAndOptimalControl/nd5G8/src/named_systems2.jl:467
[6] mk2_block_diagram_(; inner_feedback::Bool, outer_feedback::Bool)
@ Main ~/Documents/forsøk/spark_10_feedback/spark_10_feedback.jl:386
[7] mk2_block_diagram_()
@ Main ~/Documents/forsøk/spark_10_feedback/spark_10_feedback.jl:327
[8] top-level scope
@ REPL[256]:1
The code for the failing function:
function mk2_block_diagram(airspeed; params = init_pitch_params(), inner_feedback = true, outer_feedback = true)
integrator = tf([1], [1, 0])
dynamic_pressure = 0.5 * params.rho * airspeed^2
k0 = params.aoa_to_moment_gain_normalized
k1 = params.aoa_rate_to_moment_gain_normalized
back = dynamic_pressure * parallel(k0 * integrator, tf(k1 / max(airspeed, eps())))
# we assume the airflow is coming from pitch angle zero
feedback(inertia_tf(params = params), back)
my_named_tf = (tf, name) -> named_ss(ss(tf), u = Symbol("$(name)_u"), x = Symbol("$(name)_x"), y = Symbol("$(name)_y"))
only_first = arr -> [x[1] => y[1] for (x, y) = arr]
inertia = my_named_tf(inertia_tf(), :inertia) # output is pitch rate
pitch_int = my_named_tf(integrator, :pitch_int) # output is pitch angle
aoa_aero = my_named_tf(tf(dynamic_pressure * k0), :aoa_aero)
aoa_rate_aero = my_named_tf(tf(dynamic_pressure * k1 / max(eps(), airspeed)), :aoa_rate_aero)
aoa_int = my_named_tf(integrator, :aoa_int) # output is aoa angle, signal is aoa_rate otherwise system is not proper
hacker = my_named_tf(hacker_tf(), :hacker)
elev_aero = my_named_tf(elevator_to_aero_moments_tf(airspeed, params = params), :elev_aero)
props_ctrl = my_named_tf(mk2_inner_props_controller(), :props_ctrl)
elev_ctrl = my_named_tf(mk2_inner_elev_controller(), :elev_ctrl)
outer_ctrl = my_named_tf(mk2_outer_controller_tf(), :outer_ctrl)
inputs = [:pitch_ref
, :aoa_rate
, :disturbance
]
blocks = [
inertia
, pitch_int
, aoa_aero
, aoa_rate_aero
, aoa_int
, hacker
, elev_aero
, props_ctrl
, elev_ctrl
, outer_ctrl
]
splitters = [
splitter(:aoa_rate, 2)
, splitter(:pitch_int_y, 3)
, splitter(:outer_ctrl_y, 2)
, splitter(:inertia_y, 2)
]
sum_blocks = [
sumblock("aoa_int_u = aoa_rate1 - pitch_int_y1")
, sumblock("aoa_rate_aero_u = aoa_rate2 - pitch_int_y2")
, sumblock("inertia_u = aoa_aero_y + aoa_rate_aero_y + hacker_y + elev_aero_y + disturbance")
, sumblock(inner_feedback ? "props_ctrl_u = outer_ctrl_y1 - inertia_y1" : "props_ctrl_u = outer_ctrl_y1")
, sumblock(inner_feedback ? "elev_ctrl_u = outer_ctrl_y2 - inertia_y2" : "elev_ctrl_u = outer_ctrl_y2" )
, sumblock(outer_feedback ? "outer_ctrl_u = pitch_ref - pitch_int_y3" : "outer_ctrl_u = pitch_ref")
]
connections = vcat(
only_first([elev_ctrl.y => elev_aero.u
, props_ctrl.y => hacker.u
, aoa_int.y => aoa_aero.u
])
, [sys.y[1] => sys.y[1] for sys = sum_blocks] # need to connect sum blocks to real blocks
, vcat([[n => n for n = setdiff(sys.u, inputs)] for sys = sum_blocks]...)
, vcat([sys.u[1] => sys.u[1] for sys = splitters])
, :inertia_y3 => :pitch_int_u
)
# dump([
# :blocks => blocks
# , :splitters => splitters
# , :sum_blocks => sum_blocks
# , :connections => connections
# ])
connect(vcat(blocks, splitters, sum_blocks), connections, w1 = inputs, z1 = [sys.y[1] for sys = blocks])
end
I could also mention a suggestion for improving the user experience by leaving less of the plumbing to the end user. The code above could instead look like:
function mk2_block_diagram_(; inner_feedback = true, outer_feedback = true)
inertia = tag_sys(dummy_tf)
pitch_int = tag_sys(dummy_tf)
aoa_aero = tag_sys(dummy_tf)
aoa_rate_aero = tag_sys(dummy_tf)
aoa_int = tag_sys(dummy_tf)
hacker = tag_sys(dummy_tf)
elev_aero = tag_sys(dummy_tf)
props_ctrl = tag_sys(dummy_tf)
elev_ctrl = tag_sys(dummy_tf)
outer_ctrl = tag_sys(dummy_tf)
error_aoa_rate = summation(:aoa_rate, (:-, inertia))
error_pitch_rate = summation(outer_ctrl, (:-, inertia))
bd = initblockdiagram()
bd = connect(bd, :moment, inertia, :pitch_rate, pitch_int, :pitch)
bd = connect(bd, summation(
(:-, aoa_aero)
, (:-, aoa_rate_aero)
, hacker
, elev_aero
, :disturbance
)
, inertia)
bd = connect(bd, error_aoa_rate, aoa_rate_aero)
bd = connect(bd, aoa_int, aoa_aero)
bd = connect(bd, error_aoa_rate, aoa_int)
bd = connect(bd, :elev_sp, elev_ctrl, elev_aero)
bd = connect(bd, :prop_moment_sp, props_ctrl, hacker)
bd = inner_feedback ? connect(bd, error_pitch_rate, elev_ctrl ) : bd
bd = inner_feedback ? connect(bd, error_pitch_rate, props_ctrl) : bd
bd = outer_feedback ? connect(bd, summation(:pitch_ref, (:-, :pitch)), outer_ctrl) : bd
# now use the block diagram graph to create a system
sys0 = blockdiagramtosys(bd, :pitch_ref, pitch_int)
# or alternatively
sys1 = blockdiagramtosys(bd, :pitch_ref, :pitch)
sys2 = blockdiagramtosys(bd, :pitch_ref, :pitch)
# then use it...
bodeplot(sys0)
end
Edit: after giving this a bit more though, I further simplified the API. Now just get rid of all the named and then drop in tags at any appropriate place in the connect
calls. A tag can later become an input or an output, depending on how it's used in the blockdiagramtosys
function
That could be a bug in connect
, are you sure you have connections to all the blocks that are listed in
vcat(blocks, splitters, sum_blocks)
?
That could be a bug in
connect
, are you sure you have connections to all the blocks that are listed invcat(blocks, splitters, sum_blocks)
I went through all inputs and outputs in more detail and found I believe two bugs. Though fixing those did not seem to change the outcome. I have edited the original code.