Skip to content

Output calcs produce 0's at every other call #1009

Description

@corbinklett

I've noticed that subsystems can produce outputs = 0 on every other call to a system's output function. The final result of the simulation is correct; however if you are doing something like processing quaternions in the output function, a quaternion of all zeros is invalid and an error results (without some hacky handling of that situation).

In the code below, line 1 printouts alternate between 0 and 1 rather than printing 1 at each call.

import numpy as np
import control as ct

def output_sys_outputs(t, x, u, params):
    print(u) # alternates between zero and 1
    return u

output_sys = ct.nlsys(updfcn=None, outfcn=output_sys_outputs, inputs=1, outputs=1, name="output_sys")

def input_sys_outputs(t, x, u, params):
    return 1

input_sys = ct.nlsys(updfcn=None, outfcn=input_sys_outputs, outputs=1, inputs=0, name="input_sys")

# outputs just 1's
input_sim = ct.input_output_response(input_sys, T=np.linspace(0, 1, 100))

system = ct.interconnect(
    (input_sys, output_sys),
    connections=[
        ['output_sys.u', 'input_sys.y']
    ],
    inplist=[],
    outlist=['output_sys.y']
)

timeseries = ct.input_output_response(system, T=np.linspace(0, 1, 100))
y = timeseries.outputs
print(y) # prints all 1's

Activity

  1. murrayrm commented on Jun 8, 2024

    @murrayrm
    Member

    The reason you are seeing the alternating pattern is that when you have an interconnected system, you have to propagate the external inputs through the block structure before computing the derivative (or the final output). This is implemented in the InterconnectedSystem._compute_static_io method.

    Generally, the idea is that you take the external inputs and compute the outputs for each of the subsystems. Any subsystem that was connected to the external input and had a feedthrough term (eg, nonzero D matrix) will now have a different output vector (which you couldn't know until you propagated the input through). If any of the outputs changed, you need to compute the outputs again, until you either stop getting changes or you determine there is an algebraic loop (which you can do by limiting the number of interactions to the number of subsystems).

    For your example above, the first thing that happens is _compute_static_io computes the outputs for all of the subsystems, which is based on the current state (empty in your case). At this point in time, we don't (yet) know what the internal inputs are, so anything that is not an external input is just set to zero. In your example above, this means that input_sys gets called with no inputs and then output_sys gets called with one input (set to zero => you see a zero printed). Once we know what all of the outputs are for the subsystems, we can then call the output function again, this time with the proper input (set to 1 => now you see a one). Since the system inputs haven't changed on this second call (input_sys sent out a 1 again), we know we are done and so we can proceed to compute the derivative and/or output.

    There might be a better way to implement this, but I think that in any case you have to call the output functions multiple in order to propagate external inputs through the interconnected system.

  2. corbinklett commented on Jun 8, 2024

    @corbinklett
    Author

    Gotcha, thanks for the thorough explanation. I was recently working on something similar to python-control (then switched to python-control as soon as I found it!) where I re-arranged the list of subsystems to compute things in a 'causal' order (line 204 here: https://github.com/corbinklett/modsim/blob/main/modsim/main.py). Not sure how easy it is to scale or generalize this. Definitely interested to see how others solve it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions