Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hard to understand error message: Lengths of U2 (1:27) and Y1 #90

Open
tallakt opened this issue Apr 21, 2023 · 3 comments
Open

Hard to understand error message: Lengths of U2 (1:27) and Y1 #90

tallakt opened this issue Apr 21, 2023 · 3 comments

Comments

@tallakt
Copy link

tallakt commented Apr 21, 2023

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
@tallakt
Copy link
Author

tallakt commented Apr 21, 2023

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

@baggepinnen
Copy link
Member

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)

?

@tallakt
Copy link
Author

tallakt commented Apr 21, 2023

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)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants