Number of necessary time steps to handle a Zero-Crossing?

7 views (last 30 days)
Over the years I have observed that Simulink usually uses 3 time steps to handle a zero-crossing, but in some cases it uses only 2 or even 1. I'm wondering whether this is a bug or intended behavior.
I'll demonstrate all three cases with minimal examples (see below), so everyone can reproduce this. Some thoughts and questions:
  • Some people seem to rely on having 3 time steps there (very close to the zero-crossing) – e.g. when they use memory blocks –, so I need to know whether Simulink guarantees this behavior (i.e. 3 steps) under some circumstances but I cannot find anything in the documentation.
  • The only difference between the 2-steps-case and the 3-steps-case is whether the output port of a Hit Crossing block is shown or not. Why should that influence the solver at all?
  • My minimal example for the 1-step-case uses a Pulse Generator. I'm aware that this block uses Variable Sample Time but I don't see why that should influence the number of time steps at the zero-crossing.
  • I'm not sure whether it is (technically/numerically) necessary to have more than one (major) time step at the zero-crossing at all? If yes, why/when?
  • Depending on which criteria does Simulink decide how many time steps it needs to handle a zero-crossing (I'm not counting minor time steps!)?
Maybe only Mathworks can answer some of these questions (?).
Detailed steps to reproduce all three cases
Create a new blank model. (This one will cover both the 3-steps-case and the 2-steps-case.)
Place these blocks, set parameters, and connect them in this order:
  • Clock
  • Gain (parameter "Gain" = 0.9)
  • Hit Crossing (offset = 1, direction = rising).
Open Configuration Parameters / Solver tab. Set "Max step size" = 0.5 and "Stop time" = 2.
Click the Run button.
Type "tout" in the command window. This prints:
Note: There are 3 time steps close to the zero-crossing (at t=1.1111111...).
Now uncheck the Hit Crossing block's "Show output port" checkbox and apply the change.
Run the simulation again.
Type "tout" in the command window. This prints:
Note: This is different from the first simulation. Now there are only 2 time steps close to the zero crossing (t=1.1111111...).
Now create a 2nd new blank model. (This will cover the 1-step-case.)
Place these blocks, set parameters, and connect them in this order:
  • Pulse Generator (period = 1)
  • Hit Crossing (offset = 0.5, direction = rising).
Add a Scope block and connect it to the output of the Pulse Generator (to prevent a warning about all blocks being either virtual or having been removed by block reduction optimization).
Open Configuration Parameters / Solver tab. Set "Max step size" = 0.5 and "Stop time" = 2.
Click the Run button.
Type "tout" in the command window. This prints:
Note: There is only one time step at the zero crossing (t=1).
Tested on R2018b and R2020a. I had to use "out.tout" instead of "tout" on R2020a. Don't know why.

Sign in to comment.

Answers (2)

Paul on 25 Jun 2020
I will speculate on what is happening here.
One comment applicable to all three cases is that none of the models involve states or memory. So the only thing the solver needs to worry about is when certain events will occur and what action to take at those times. Also, using the default Nonadaptive zero crossing algorithm.
I'm going to consider the three cases in reverse order.
1-Step: The Pulse Generator has Variable Sample Time, which means "the block tells Simulink when to run it."
From t = 0, the solver has to figure out how far to take the next step. The candidates are t = 0.05 (trailing edge of the first pulse, which the block tells the solver) or t = 0.5 (max step). So it initially takes the smaller of the two t = 0.05. The solver can then do a final check to make sure that it didn't miss anything, like a zero crossing event. There are none, so it stays at 0.05.
From t = 0.05, going through the same process, it takes the max step to t = 0.55.
From t = 0.55, the choices are either step to t = 1, or t = 1.05. So it picks t = 1 as the candiate. At that time, it detects the hit crossing. So the question for the solver is whether or not it needs to go backwards to figure out when that crossing really occurred. I'm going to speculate it doesn't, beacause the Variable Sample Time of the root source block is telling the solver that nothing would have changed prior to t = 1. So it keeps the step at t = 1 and sets the Hit Crossing output high.
From t = 1, the solver determines that the next event is not earlier than t = 1.05 (trailing edge of the pulse). So it jumps to t = 1.05, at which point the hit crossing output goes low.
2-Step: The Clock has Continuous Sample Time.
From t = 0, solver takes the max step t = 0.5, no problem.
From t = 0.5, solver takes the max step to t = 1, no problem.
From t = 1, solver tries to take the max step to t = 1.5. Detects the hit crossing occured somewhere between t = 1 and t = 1.5. Now it has to go backwards. I don't have a clue how that NonAdaptive algorithm works, so don't know why it couldn't figure out that the threhold was reached at t = 1/0.9 (in general, finding the exact time of the crossing won't be so easy). In any case, it backs the solver up to t = 1/0.9 - eps (out.tout(4)), which is as close as it can get to the crossing. Maybe it purposefully tries to bracket the crossing point?
From t = 1/0.9 - eps, the solver knows that the next step should be small to get on the other side of the hit crossing. For whatever reason, it took a step of 64*eps
>> isequal(out.tout(5),out.tout(4)+64*eps)
ans =
At this point, the input to the Hit Crossing at out.tout(5) exceeds the threshold, and the input at out.tout(4) is below, so it sets the output high. As required, the block found the hit crossing, to within some tolerance (over which the user has not control as far as I can tell for the NonAdaptive algorithm.
From t = 1/0.9 + 63*eps, the solver is free take the max step, because there is no reason not to.
BTW, I changed the min step size of the solver to 100*eps, and got the exact same result with no warnings or errors. Not sure what that's all about. Maybe the min step size doesn't apply for zero crossing algorithm.
3-Step Case: This case is the same as the 2-step case up to the point of out.tout(5) = 1/0.9 + 63*eps.
From t = out.tout(5) = out.tout(4) + 64*eps = 1/0.9 + 63*eps. Now the solver has to decide what its next step should be. My first thought was that it should try the max step as a candidate and see if any events occurred prior. But it doesn't do that. Instead it takes a step of 64*eps again (u is the input to the hit crossing and y is the output)
>> [out.tout out.u>=1 out.y]
ans =
0 0 0
5.000000000000000e-01 0 0
1.000000000000000e+00 0 0
1.111111111111111e+00 0 0
1.111111111111125e+00 1.000000000000000e+00 1.000000000000000e+00
1.111111111111139e+00 1.000000000000000e+00 0
1.611111111111139e+00 1.000000000000000e+00 0
2.000000000000000e+00 1.000000000000000e+00 0
>> isequal(out.tout(5)+64*eps,out.tout(6))
ans =
My speculation is that the solver takes this small step because it can't predict when the next event will occur because of the Continuous Sample Time of the clock AND because there might be a customer of the output of the Hit Crossing. So it takes a small step and gives us the spike output. Assuming that's what's really happening here, I don't know if that's an intentional feature or not. But if it is, it is useful.
I did some additional experimentation that suggests the solver is smart enough to know that the function driving the hit crossing is Variable Time, even if the actual solver step size is being determined by other factors, like a small time constant filter. The solver even knows this if there is gain block between the Pulse Generator and the Hit Crossing, where the sample time of the output of the gain block is FiM.
Summary: If the ultimate signal driving the hit crossing is Continuous Sample Time and the Hit Crossing has an output, you get the 3-step case with one very small step prior to the Hit and one very small step after the Hit, with the output being 0-1-0 at those times. That seemed to be true with other inputs, as long as the hit crossing was detected (see Note below).
Disclaimer. This is all just guesswork.
Note: It's also easy to miss the hit crossing altogether. Go back to the 1-Step case and replace the Clock with a Sine Wave block with Amplitude 2 and Frequency 2*pi/0.5.

creepydog on 1 Jul 2020
First of all: Thank you for your efforts! Your guesses are similar to my guesses. ;-)
I've been aware of the fact that my minimal examples do not have any states. Now I have altered my Pulse Generator example model to include a continuous state and also make the input of the Hit Crossing a continuous signal (not FiM!).
When I run this, I get 2 time steps at the zero-crossing. When I "unshow" the Hit Crossing's output port and run it again, I get only 1 time step at the zero-crossing. That's in contradiction to your summary, isn't it? (Note, though, that the Hit Crossing block itself is FiM!)
Some more remarks:
Yes, min step size seems to be irrelevant for zero-crossing. From my point of view, there are good reasons for that.
Yes, Simulink can miss zero-crossings (but that's another topic. In my real world model I can prevent that robustly by using benign signals to drive the Hit Crossing blocks and choosing an appropriate max step size.)
How I think the zero-crossing mechanism finds the crossing point: By iterative interpolation, at least in principle. In the 2-step example, Simulink evaluates the Hit Crossing input signal at t=1 and t=1.5 (as you stated) and got these values: 0.9 and 1.35. As the threshold is 1, Simulink knows there's a crossing in that interval. Now it tries to find the crossing point by interpolating the input signal between these two points. So it does something like this: interp1([1 1.5]*0.9,[1 1.5],1). In our case the input signal is a linear function of time (*g*), so one iteration is sufficient for convergence and Simulink immediately "guesses" the correct t=1.11111111...
Just in case you've never done that: Try the Simulink Debugger, step through the simulation and observe the Hit Crossing block I/O and the (minor) time steps that Simulink chooses.
The official Simulink documentation states: "If any zero crossings are detected, the Simulink software interpolates between the previous and current values of each variable that changed sign to estimate the times of the zero crossings, that is, the discontinuities."
Now I found what seems to be some ancient version of the Simulink documentation, which adds: "Simulink then steps up to and over each zero crossing in turn. In this way, Simulink avoids simulating exactly at the discontinuity, where the value of the state variable might be undefined."
I do not know whether this can still be regarded as canonical Simulink wisdom (or why it has been censored/lost) but it seems to be an explanation why there should be at least 2 time steps at a zero-crossing. However, today's Simulink works differently.
I'd be glad to get an answer from some Mathworks expert ...


Find more on General Applications in Help Center and File Exchange




Community Treasure Hunt

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

Start Hunting!

Translated by