Enabled Subsystem does not reliably disable

The Simulink model shown in the screenshot does not behave as I expect, and I would like to understand the reason.
All blocks are on default settings, except:
  • Gain: 1/pi
  • Compare To Constant: operator >=, constant value 1.0
  • Logical Operator: XOR
  • (Scope: two inputs, line markers)
Note that the Compare To Constant block has Zero-crossing (ZC) detection enabled by default. The Enabled Subsystem consists of one input port directly connected to the output port.
All solver settings are at default values. The solver is auto(VariableStepDiscrete). I only change the StopTime of the simulation (see below).
Expected behavior
The enabled subsystem should be enabled for exactly one time point (in major time step) and "sample and hold" the value of its input (the scaled clock signal).
This should occur when the scaled clock signal reaches the threshold value of 1.0 (but not later than that, thanks to zero-crossing detection).
The sampled and held value (see Display block) should be 1.0 (sampled at time t=pi).
Observed behavior
Sometimes the result (= sampled and held value at the end of the simulation) is larger than expected. The scope screenshot (lower half) shows that the enabled subsystem seems to be enabled for two time points (instead of one). In the first time point (at t=pi) it copies a value of 1.0 to its output, in the subsequent second time point a value of approx. 1.025, which is then held until the end of the simulation.
Whether the problem occurs, weirdly depends on the StopTime. Some examples:
  • For these values of StopTime the problem occurs: 4.00001, 8, 9, 10.1, 11, 13.
  • For these values everything works fine: 4, 6, 7, 10, 12.
My analysis so far
When the problem occurs, Simulink has placed two time points at the zero-crossing, one just before and one just after the zero-crossing. When the problem doesn't occur, there are three time points close to the zero-crossing (one just before, two just after the ZC).
Although the XOR block output is correctly true only for one time point (right hand side of the zero-crossing, see upper half of the scope screenshot), the final output of the enabled subsystem seems to always be equal to the value of its input sampled one time point later (that is: when the XOR has dropped to false again). That is not a problem if that time point is the third of 3 close to the ZC, but it produces a wrong result if that time point is much later than the ZC.
So I wonder why Simulink sometimes uses 3 time points at a ZC (good), but sometimes only 2 (bad). Any hints or explanations welcome why Simulink behaves like this and/or why it should(n't).
Some more notes
  1. I know the expected behavior could be implemented differently. That's not the point. This is a minimal example. In my opinion it should not behave the way it does.
  2. I'm unsure about the correct/official wording: What I mean by "time point" are the elements of "tout". Usually I refer to them as major time steps, but maybe that's wrong as they're points in time, not steps. (?)
  3. My maybe related (and unfortunately still unanswered) question about zero-crossings and the number of time steps/points taken by Simulink: https://de.mathworks.com/matlabcentral/answers/553960-number-of-necessary-time-steps-to-handle-a-zero-crossing

3 Kommentare

Paul
Paul am 23 Sep. 2024
I replicated your results and it does seem strange that in the "bad" case it sure looks the Enabled Subsystem is executed twice even though the enabling signal is true at only one value of tout (as it is also for the "good" case).
creepydog
creepydog am 24 Sep. 2024
Hi Paul. Thanks for your investigations. It looks as if the Enabled Subsystem were executed twice but I think it isn't. I just made an additional experiment: I added a counter to the subsystem and observed that it was incremented only once. Really strange.
Using the Good Ol' Simulink Debugger (sldebug), I decided to further examine the side quest(ion) why the number of major time steps at the Zero Crossing is sometimes 2 and sometimes 3.
In the "bad" case (StopTime 4.00001) Simulink finds one major time point ("TzL") before the crossing (zero crossing variable < 0) and one ("TzR") after the crossing (> 0). Those two differ by 128*eps seconds. My interpretation of the Debugger's output is that "[-+]" means a direct transition from "< 0" to "> 0". The following time step is large; thus, there are only two major time points close to the ZC.
[Tm = 3.2000080000000022 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.200008000000002 ] Detected 1 Zero Crossing Event 0[-+]
Begin iteration to bracket zero crossing event
[Tz = 3.200008000000002 ] [Hz = 0 ] [Iz = 0.08000020000000019 ] 0[-+]
[Tm = 3.1415926535897927 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.200008000000002 ] [Hz = 0.02158485358979068 ] [Iz = 0.05841534641020951 ] 0[-+]
[Tm = 3.1415926535898211 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
End iteration to bracket zero crossing event
[TzL= 3.1415926535897927 ] Start of Major Time Step just before Zero Crossings
[TzR= 3.1415926535898211 ] min_ex_mod.Outputs.Major
Two major time points close to the ZC: 3.1415926535897927 and 3.1415926535898211.
In the "good" case (StopTime 4.0), Simulink is lucky and happens to find the exact (!?) time (= pi) where the crossing occurs. It subtracts 128*eps, which it uses as TzL (before the ZC). Note that this is a transition from "< 0" to "== 0", marked by "[-0]" in the Debugger's output.
Afterwards, it needs a second ZC for the transition from "== 0" to "> 0", marked by "[0+]". This adds only one major time step, as the left-hand side of this second transition is equal to the right-hand side of the first transition (t=pi). Overall, there are now three major time points in close proximity.
[Tm = 3.2000000000000015 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.200000000000002 ] Detected 1 Zero Crossing Event 0[-+]
Begin iteration to bracket zero crossing event
[Tz = 3.200000000000002 ] [Hz = 0 ] [Iz = 0.08000000000000007 ] 0[-+]
[Tm = 3.1415926535897931 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.141592653589793 ] [Hz = 0 ] [Iz = 0.02159265358979168 ] 0[-0]
[Tm = 3.1307963267948971 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.141592653589793 ] [Hz = 0.01079632679489562 ] [Iz = 0.01079632679489606 ] 0[-0]
End iteration to bracket zero crossing event
[TzL= 3.1415926535897647 ] Start of Major Time Step just before Zero Crossings
[TzR= 3.1415926535897931 ] min_ex_mod.Outputs.Major
Second transition:
[Tm = 3.2215926535897932 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.221592653589793 ] Detected 1 Zero Crossing Event 0[0+]
Begin iteration to bracket zero crossing event
[Tz = 3.221592653589793 ] [Hz = 0 ] [Iz = 0.08000000000000007 ] 0[0+]
[Tm = 3.1815926535897932 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
[Tz = 3.181592653589793 ] [Hz = 0 ] [Iz = 0.04000000000000004 ] 0[0+]
[Tm = 3.1415926535898215 ] min_ex_mod.Outputs.Minor
[Tm = 3.1415926535897931 ] 0:2 RelationalOperator.ZeroCrossings 'min_ex_mod/Compare To Constant/Compare'
End iteration to bracket zero crossing event
[TzR= 3.1415926535898215 ] Start of Major Time Step at or after Zero Crossings
Three major time points close to the ZC: 3.1415926535897647, 3.1415926535897931, 3.1415926535898215.
Conclusion: Whether you get two or three major time points seems to be a matter of luck. (?)

Melden Sie sich an, um zu kommentieren.

 Akzeptierte Antwort

David Balbuena
David Balbuena am 8 Aug. 2025

0 Stimmen

Not sure if you still need an answer to this, but the sample time colors give you a hint at what's causing the weird behavior:
The input to the enable subsystem is black, meaning it has continuous sample time. The enable port is grey, meaning it has fixed in minor step (FIM) sample time. That means that the input to the enabled subsystem can change during minor time steps, while that enable port cannot. With that said, consider this scenario:
At t = 3.14+ (on the right side of the zero crossing), you'd have:
  • enableSig is true
  • inpVal is 1
Since enableSig is true, the block executes the equation
outVal = inpVal
and so outVal would be 1.
And then lets say the solver takes a minor step at t = 3.30. Here's what the block would see:
  • enableSig is true (since FIM signals don't change during minor steps)
  • inpVal is 1.05
Since enableSig is true, the block is enabled, and so it will execute the equation
outVal = inpVal
which means that outVal would be 1.05. Then on the next major step, the enableSig would get set to false, an so for the rest of the simulation the output value stays at 1.05.
You can get the right results by fixing those sample times. One way would be to add a Rate Transition before the Enabled Subsystem:

9 Kommentare

Paul
Paul am 9 Aug. 2025
When I create the original model, I see exactly what you show wrt the FiM sample time. But I also see that the solver is 'auto(VariableStepDiscrete)' in the lower right hand corner, which is to be expected for a model without any continuous states (and the default solver settings). But the FiM Sample Time is a type of continuous sample time. So how can FiM, a continuous sample time, be a thing for a discrete solver, which shouldn't have any minor time steps?
creepydog
creepydog am 15 Aug. 2025
@David Balbuena Yes! I'm definitely still interested in understanding this. Thank you for your valuable hint.
I can confirm that forcing the subsystem input to FiM (as you proposed) solves the problem. Now I wonder why the other signal (the Enable port, or rather the output of the Compare block already) is FiM in the first place. It seems I know too little about these details (Cont vs. FiM).
Side note: I found something in my notes from last year that I hadn't included in my post: When I add a Hit Crossing block in parallel to the Compare block (settings: offset=1.0, rising, show output port!! without even using it, enable ZC detection), it then seems to force three major time steps at the ZC, which in turn solves the problem (well, rather masks it).
Finally: As I fear I will fall into the same well-hidden trap again next time, I think I should avoid using an Enabled Subsystem in such a case where a Triggered Subsystem would be far more appropriate.
David Balbuena
David Balbuena am 18 Aug. 2025
@Paul I was also surprised to see the minor steps. My suspicion is that the VariableStepDiscrete solver takes the minor steps in order to detect zero crossings.
David Balbuena
David Balbuena am 18 Aug. 2025
The memory block in this case is the one introducing the FiM sample rate. The sample time legend has a way to show which blocks are introducing which rate. I'm not sure if that's available in all releases though.
I agree with you about using Triggered Subsystem here. I also like using the sample time colors to avoid traps like this. Once you start enabling them on more models, you start to get a feel for what looks "normal" and what looks "weird".
Paul
Paul am 18 Aug. 2025
Hi David,
I find this all to be very mysterious.
As best I can tell, minor time steps are only used for derivative calculations of continuous solvers (Compare Solvers). Would be interested to see any doc page describing their usage for zero crossing detection.
From Using Enabled Subsystems we see in the very first sentence that "An enabled subsystem is a conditionally executed subsystem that runs once at each major time step while the control signal has a positive value." (emphasis added). That statement suggests that in @creepydog's example (for the bad case) there were two major time steps where the control signal had a positive value, which seems like it shouldn't be possible.
Regarding sample times .... it seems like there may need to be an additional type of sample time to be defined that is applicable only for discrete solvers, which would basically be "this block executes at the discrete solver step times." Maybe that's what FiM is supposed to mean for discrete solvers, but that that's not how it's documented.
Paul
Paul am 19 Aug. 2025
Thinking on this issue some more, I don't believe that quoted passage from the doc is correct, or I am misinterpreting it, when using a continuous solver (which is not the case here).
I'm quite sure that an enabled subsytem can contain blocks with continuous states, which means that the enabled subsystem must execute on minor time steps. I suppose that quoted passage can be read as being a statement as to only what happens at a major step, and one shouldn't infer what might or might not happen on minor steps, in which case the doc might need further refinement to explain how enabled subsystems are treated on minor steps.
David Balbuena
David Balbuena am 19 Aug. 2025
Hi Paul,
The best doc I can find justifying the minor time steps for zero-crossing detection is this one: Simulink Engine Interaction with C S-Functions - MATLAB & Simulink
Specifically the image that shows the methods called during the simulation loop.
I think you're right that the Enabled Subsystem doc page is only commenting on what happens during major time steps. It's probably meant to contrast the behavior with function-call subsystems, which can execute multiple times per major time step. Although I agree the wording should be clearer about what happens during minor time steps.
As for the sample time issue, a couple points on the VariableStepDiscrete solver:
  1. It's discrete, in the sense that it cannot handle continuous states (reference: Compare solvers). Being discrete doesn't constrain what sample times can be used.
  2. It's variable-step, meaning it can adjust the time step (reference: Variable Step Solvers in Simulink).
  3. It takes minor time steps so it can detect zero crossings (no reference, this is my hypothesis).
As long as #3 is true, then I think the behavior of the solver makes sense w.r.t sample times, and you would use FiM to avoid blocks executing at minor time steps.
David
creepydog
creepydog am 19 Aug. 2025
The Simulink Debugger (sldebug) supports your claim #3: There are minor time steps "all the time": Before every major time step there is a minor time step (with equal time) which computes the continuous (non-FiM) blocks outputs and checks the ZC condition. There are additional minor time steps to hunt down the ZCs.
Paul
Paul am 20 Aug. 2025
Thanks for posting that link. Seems to have a lot more detail as compared to and seems incongruous with Simulation Phases in Dynamic Systems (which seems to be missing some crucial information) and Zero Crossing Detection in Blocks.
The Solver Profiler might also provide some insight (I've never tried to use it).

Melden Sie sich an, um zu kommentieren.

Weitere Antworten (1)

MULI
MULI am 23 Sep. 2024

0 Stimmen

Hi,
I understand that the Enabled Subsystem sometimes stays on for two time points instead of one because of how Simulink handles zero-crossings.
It might happen due to following reasons
  • Simulink uses zero-crossing detection to pinpoint when signals change direction, which can trigger subsystems.
  • Occasionally, the solver places extra time points around these crossings, causing the subsystem to remain enabled longer than expected.
  • The VariableStepDiscrete solver adjusts its step size for accuracy and performance, which can lead to different time point placements, especially around zero-crossings.
  • Changes in StopTime can affect how time points are distributed, impacting zero-crossing behaviour.
This behaviour can be fixed by following the below suggestions:
  • By Switching to a fixed-step solver can help ensure consistent time point placement.
  • Adjust the solver's tolerances to enhance zero-crossing accuracy.
  • You can specify extra time points around expected zero-crossings to ensure they are evaluated properly.
  • Implement a Stateflow chart to control when the subsystem is enabled or disabled, gives you more precise control. For an example of this, please refer to the example linked below:

1 Kommentar

creepydog
creepydog am 23 Sep. 2024
No, switching to a fixed-step solver sabotages the zero-crossing mechanism as the time steps cannot be changed to exactly hit arbitrary zero-crossings any more.
And no, adjusting the solver's tolerances does not change the behavior of my example (I tried up to 1e-12 for absTol and relTol).
Also: No, specifying extra time points manually (??) doesn't work for previously unknown zero-crossing events.
And finally: No, using Stateflow is a totally different implementation (see original question, section "some more notes", point 1).

Melden Sie sich an, um zu kommentieren.

Kategorien

Mehr zu General Applications finden Sie in Hilfe-Center und File Exchange

Produkte

Version

R2023b

Gefragt:

am 10 Apr. 2024

Kommentiert:

am 20 Aug. 2025

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by