Re: Lahman, how ya doing?



In article <p6vce.441$yd1.128@trndny01>,
H. S. Lahman <hsl@xxxxxxxxxxxxxxxxx> wrote:
>Responding to Hansen...

>> There's also the matter of what resolution is needed. In the real system,
>> sometimes an event like adjusting the control is more than 1/60 second
>> late. But not 7/60 seconds late. And the thermal time constant of the
>> target is around 14 seconds, much longer than any delays in the control
>> loop. The data I've taken fits well to a model second-order control
>> system, and in general I've seen no reason to believe that processing lags
>> have any interesting effect, so I think the ideal computer approximation
>> will do.
>
>The actual system is a real-time hardware control system. As such, its
>software will be event-based and the software processing will mostly be
>done via interacting state machines. There will be <at least one> event
>queue manager program unit and a Timer program unit that gets ticks from
>the system clock and generates events to other program units. That's
>just basic R-T/E design.
>
>My point in this context is that the closer your simulation models that
>actual software, the better and more robust it will be. In fact, if
>that controller is well-formed OO you should be able to reuse much of
>the software in the feedback loop with, at most, only cosmetic changes
>(e.g., the Thermometer object reads an attribute in the simulation
>rather than an instrument hardware register).

The actual software has a main loop that continually calls GetTicks(), a
system-level call to the computer's internal timer, and then runs through
a list of scheduling variables to see if it's time to call a function to
read voltages, calculate a control action, move the shutter, etc. If the
tick count doesn't change, it will just loop and loop, retesting the same
number.

>
>There are only two things that make your simulation fundamentally
>different than the real system: (A) you must calculate the value of
>temperature that the thermometer "reads" and that computation may trash
>real-time absolute simulation because of its complexity; and (B) instead
>of writing to the heater, beam, shutter, and other hardware on the other
>end of the feedback loop you need to provide inputs for the calculation
>in (A) for the next cycle, which also may take time that would trash a
>real-time simulation. IOW, everything between reading the hardware and
>writing the hardware should be exactly the same; what you are actually
>simulating is the hardware itself.
>
>The finite execution time for the simulation infrastructure computations
>probably means you can't do an absolute real-time simulation. Somehow
>you have to spread out the events to accommodate those computations.
>One way is to simply scale the time in the Timer (e.g., 50 clock ticks =
>1 simulation (event) clock ticks). Another way is to not use the system
>clock at all and just trigger the next simulation clock tick with an
>event to the Timer that is generated when all the processing for a
>single <simulation> clock tick is done.

I plan to not use the system clock at all. The point of the simulation is
that it isn't real-time. But the computational needs of the analog parts
are pretty light.

>
>Either way, that is just a matter of how you implement the Timer for the
>simulation. That should be completely transparent to all the rest of
>the software. Thus you have to ensure is that the events will be
>generated by the Timer in _exactly the same order_ that they would be in
>the real-time controller timer. All of your calculations on temperature
>samples, statistics, filtering, smoothing, etc. should be completely
>indifferent to the way the events are generated. As far as they are
>concerned they are being done in real-time to the system clock. IOW,
>all of you simulation software -- except the hardware emulation itself
>-- /is/ a real-time simulation and it should work exactly like it does
>in the real controller. That's what I meant about the problem being a
>real-time simulation; it should work exactly the same way, down to being
>event-based.
>
>>>>>>I also need jobs that do task 1 at time interval 1, and task 2 at time
>>>>>>interval 2. But it won't be hard to add a
>>>>>>
>>>>>> time.add_task(&c3, 5, 20);
>>>>>
>>>>>At the risk of being picky, doesn't this do the /same/ task twice?
>>>>
>>>>
>>>>No. In the system I want to simulate, there's a sampling time of, say,
>>>>one second, and an averaging time of, say, 60 seconds. Once per second it
>>>>takes a data point and calculates a running total and total^2, and once
>>>>per 60 seconds it calculates and reports an average and standard
>>>>deviation, and zeroes the totals.
>>>
>>>OK, I thought the tasks would be in different objects. So the c3 object
>>>has a state machine like(?):
>>>
>>> E2:sixty ticks
>>>+--------> [Sampled] --------------> [Statistics Computed]
>>>| | ^ |
>>>| | | |
>>>+------------+ +--------------------------+
>>>E1:one tick E1:one tick
>>>
>>>and all you are doing is registering it to receive both the E1 and E2
>>>events from the Time object. If so, then it might be simpler to just
>>>register via
>>>
>>>time.add_task(&c3, 1);
>>>time.add_task(&c3, 60);
>>>
>>>since the Time object shouldn't care about duplicate handles; it just
>>>needs to know where to send the indicated event. Then you don't a
>>>special implementation in Time for this case.
>>
>>
>> Yes. Except it has to know that the first line refers to a sample and the
>> second line refers to an averaging. I can think of not-so-clean ways
>> around that, like a third parameter that specifies which task, or a
>> counter internal to the object which then wouldn't need a trigger for the
>> second event. Or maybe make every event a double even with a third
>> parameter 0=off by default. I can't think of a satisfactorily clean way.
>
>It doesn't matter. What you are telling the Timer is that it should
>generate the E1 event every 1 second and send it to &c3. It should also
>generate the E60 event every 60 seconds and send it <serendipitously> to
>&c3. The Timer does not and should not know anything about the
>semantics of how the events will be interpreted by the receivers, who
>the receivers are, or why the receivers care about the events.

I'm not sure I'm following you here. At one second, something would tell
&c3 something like

c3->evolve(elapsed_time, E1);

and at 60 seconds it would tell &c3 something like

c3->evolve(elapsed_time, E60);

? That is, pass both an elapsed time and an event designator to the block
that is to record a data point, simulate the moving of the shutter, etc.?
Or pass a current time to it, or whatever.

And then &c3 would have to be told, when it is initialized, that E1 means
to perform action 1 and E60 means to perform action 2? Perhaps by a call
like

ChartRecorder c3(&c1,1,60); // input from c1, nominal times 1 and 60
timer.add_task(&c3,1); // repeat timing info for timer's benefit
timer.add_task(&c3,60);

>
>As a practical matter you probably want better naming conventions for
>the events and you would parameterize that as well. B-) IOW, you
>register the receiver, the event to generate, AND the tick count interval.

I'm trying to solidify in some way what you're discussing.

ArbitraryControlBlock c1(parameter, another_parameter, etc);
timer.add_task(&c1, time_to_wait, id_of_an_event);
timer.add_task(&c1, second_time, id_of_another_event);

>
>>>>>You kind of get that for free if you use a single event queue and
>>>>>popping an event doesn't return in the event queue manager until the
>>>>>task is completed. If, though, the timer event is only one of several
>>>>>events that may be issued to complete tasks, one needs a different
>>>>>mechanism.
>>>>
>>>>
>>>>What I've done so far, I don't think you could really call an event queue.
>>>>Each task keeps a wait_time and a next_event, and executes and updates
>>>>when polled with a time >= next_event. Since it's a simulation, I don't
>>>>think it's really sensible to have an event queue. It's not going to be
>>>>waiting for a user to decide to click the mouse or anything, except
>>>>externally to the simulation.
>>>
>>>If you have a timer at all it is easier to make it event-based. Then
>>>you don't have to write any conditional code or have history attributes.
>>> The serialization is essentially free, as in the 1, 60, and 7/60 event
>>>case above. At most you may have to have a prioritization scheme. The
>>>Timer will be a simpler and more generic as well. And the event queue
>>>manager will be entirely reusable (the target &cN is passed in the event).
>>
>>
>> I guess I'm not sure whether I'm event based. I gave a little more detail
>> elsewhere, but as I have it set up now, when timer.add_task(&c1,5) is
>> called, a Task internal to Timer (the user doesn't directly interact with
>> it) is created, and is given the Block pointer and the timing information.
>> Every time timer ticks, each Task is told the current time. The Task
>> decides whether to trigger. When it triggers, it sends the Block the
>> elapsed time since the last trigger, e.g. ptr->evolve(dt).
>
>This is exactly what I am arguing against. The controller is
>/inherently/ event-based. Think of it as a major R-T/E design pattern.

I don't think I know what any design patterns look like.

> Decades of pain and suffering have determined that the best way to
>implement this sort of hardware controller is making it event-based. In
>that sort of design the sequencing of operations is determined by the
>order in which events are generated and who consumes them. Your
>simulation software should emulate that.
>
>The recipient of an event does its thing immediately with no debate;
>consuming the event is the trigger. Doing that thing was properly
>placed in the overall processing sequence by the ordering of the events
>so there is no decision for the receiver to make.

Ordering the events? How do I order the events? I don't see any inherent
order of events in the system I'm trying to simulate. Every block either
executes at a particular time, or it does nothing. If it executes, it's
assumed that everything executes at the same time. But if there were any
sequence to it, the sequence just doesn't matter. No block depends on
another block having completed an action before performing an action of
its own.

>
>In this particular case making it event-based actually makes the
>simulation a lot easier. You describe a significant infrastructure in
>each Task just to decide if it should do something. That goes away
>completely if Timer generates events and the Tasks just respond. All of
>the decision making is done in registering the events with the timer
>because that is where the ordering can be defined as well (i.e., Timer
>generates events for the same time tick in the order that they were
>registered). That also makes it trivially easy to change the order of
>processing -- you just reorganize the registration statements. So here
>the event-based view is much cleaner, simpler, and easier to maintain.

Each Task, recall, is generated by Timer in the first place when a
control block is registered. The user defines a control block and adds
it to Timer, then Timer defines a Task and attaches the control
block to it, and manages a list of tasks. I did it that way because I
wanted to bundle the scheduling data and logic for each block together.
When I've finished testing it, I intend to make Task private to Timer
because there's no reason for the user to make one externally.

Off-hand, I can't think of another way to do it, except to unroll Task's
functionality and parameter lists into Timer. E.g.

class Timer {
private:
vector<Block *> block_list;
vector<float> wait_time;
vector<float> next_time;
vector<float> last_time;
...
};

wait_time[0], next_time[0], last_time[0] all belong to block 0,
wait_time[1], next_time[1], last_time[1] all belong to block 1, etc.

I didn't want it spewed all over the place like that. So I have

class Timer {
private:
vector<Task *> task_list;
...
};

>
>>
>> That sort of seems event based since each Task is really just sitting
>> there waiting for a kick in the pants, and each Block is sitting there
>> waiting for a kick. But I guess I don't know the language very well. The
>> timer still calls each task, one by one. I suppose it's not really an
>> event queue, anyway.
>
>Event-based processing is a methodological mindset; its about the way
>one thinks of collaborations. One defines the overall solution sequence
>by determining where events should be generated and where they go. The
>receivers have no explicit decision making; they just execute their
>responsibility when triggered by an event.
>
>The corollary is that events are announcements (I'm Done) and they are
>sent to whoever cares about that (i.e., has a response for the
>announcement). That decouples what the sender is doing from what the
>receiver does. That happens to be one of the key things that
>distinguishes OO methodology from procedural or functional methodologies
>where sequences are chained together by imperatives (Do This). It just
>so happens that the rules of finite state machines coincide very nicely
>with fundamental OO practice.

I can't say I have a queue for my events, but my end receivers, the
control blocks, are given an elapsed time. That's all they're told, they
don't make any timing decisions on their own. They just run when they're
told to run, with the appropriate dt to stick into a delta_T=dQ/C/dt or
something.

>
>Consider the other side of the coin. Suppose one has designed with
>object state machines and an event queue manager. If there is nothing
>inherently asynchronous in the problem (which there doesn't appear to be
>in your case), one could do a pure synchronous implementation at the 3GL
>level.

What's a 3GL level?

>To do that all one needs to do is replace push calls to the
>event queue manager with direct method calls to the state action that
>would respond to the event. That eliminates the event queue manager and
>if one looks at the resulting 3GL code it might be difficult to tell the
>OOA/D had been done with state machines at all, much less that
>event-based processing had been used.

I think that's what I'm doing. In principle, a Task could be made to
generate an event and stick it in a queue. But it just calls
bptr->evolve(dt) directly.

>>>The simple way to march those calculations is to just daisy-chain them.
>>
>>
>> Daisy-chaining makes me nervous. I'm not really sure what you mean by it.
>
>Remember I am talking about the calculations you add that emulate the
>hardware, particularly the computation of the temperature value. Since
>they are mathematically defined by thermodynamics much more general than
>the context of this problem, I would be inclined to put them in realized
>code that I did procedurally or functionally outside of the OO
>development (e.g., in a function library or a reusable package).
>Procedural/functional techniques are more intuitive and tend to be more
>terse than OO solutions, especially for algorithmic processing. Since
>the algorithms don't change there is no maintainability issue once one
>gets them working. In that world literal, hierarchical daisy-chaining
>of imperatives is the normal operating mode. B-)
>
> From a more philosophical perspective, all software solutions are
>daisy-chained. The tricky part is to do it without creating
>implementation level dependencies, which is the OO goal. The tools for
>that are things like encapsulation, implementation hiding, peer-to-peer
>collaboration, and separation of message and method in OOA/D. So in
>OOA/D we daisy chain by sending messages from one object ot another on a
>peer-to-peer basis. If one looks at a UML Sequence Diagram, that is
>exactly what it describes -- connecting the behavior dots in time via
>passing messages. (Note that events are pure messages.) However, in
>OOA/D defining collaboration messages to connect the dots is usually the
>last thing we do (after we define objects, responsibilities,
>relationships, state machines, behaviors, etc.) while in
>procedural/functional development message and response are not separated
>at all.
>
>If you feel the computations are complicated enough to warrant
>distributing over multiple objects and you want to do an OO solution for
>those computations, then I would suggest tucking it in a subsystem where
>you can address that problem alone. In that subsystem you may or may
>not choose to use events for communication. But that subsystem would
>still just return a temperature value when it was done and that would be
>encapsulated in a single method in your software the simulates the
>control system.
>
>> I imagine Blocks representing elements of the system, and they can be
>> connected together in any sort of pattern, with loops and interconnections
>> all over the place. Change any little thing and it's hard to maintain one
>> iteration. Or maybe you mean the blocks are just arranged in a queue,
>> and my code
>>
>> for (ptr = list.begin(); ptr != list.end(); ptr++)
>> (*ptr)->timeis(t);
>>
>> would be replaced by
>>
>> ptr = list.begin();
>> (*ptr)->timeis(t); // rely on first object to tickle second, etc.
>
>This is close to the event-based approach. The most rigorous approach
>to state machine design is a variation on DbC.

What's DbC?

>To execute a state
>action some precondition must be satisfied. That precondition will
>prevail as a result of executing some other state action. IOW, the
>precondition will match the postcondition for executing the other
>action. So once the state machines are designed, one could look at each
>action's precondition and find the action whose postcondition matches.
>One then generates the event in the action with the matching
>postcondition. Thus we "daisy-chain" the overall solution flow of
>control by "walking" the chain of precondition/postcondition matches to
>define the events.

Oh, I think you're talking about a higher level of daisy-chaining than I
was thinking of.

>
>[Things are complicated in OO development because there is another form
>of "state": state variables or attributes. So the precondition for
>executing and action is compound. There is usually some other action
>that must execute -- the traditional algorithmic sequence. But any
>attributes that the action reads must also be current. So the
>precondition also includes conditions on data integrity. That is
>usually the tricky part in connecting the event dots and sometimes we
>have to deviate from the "use case" view of successive steps to make
>sure the data has been modified properly as well.]

I know what a "use case" is.

>
>But back to this particular context, I was envisioning a traditional
>procedural functional decomposition Do This hierarchy that was
>explicitly synchronous.
>
>
>*************
>There is nothing wrong with me that could
>not be cured by a capful of Drano.
>
>H. S. Lahman
>hsl@xxxxxxxxxxxxxxxxx
>Pathfinder Solutions -- Put MDA to Work
>http://www.pathfindermda.com
>blog: http://pathfinderpeople.blogs.com/hslahman
>(888)OOA-PATH
>
>
>


--
"The result of this experiment was inconclusive, so we had to use
statistics." (Overheard at international physics conference)
.



Relevant Pages

  • Re: Lahman, how ya doing?
    ... >things like Thermometer, Computer, Filter, Beam, etc. for objects. ... >> control blocks from. ... >Is Timer a true event generator? ... >queue structure. ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... >real-time when dealing with synchronization, delays, etc. ... >the simulation structure reflects the real-time structure, ... >triggered by timer events. ... Heat goes from the heater to thermal block 1, ...
    (comp.object)
  • Re: Havent done anything real with OOP yet.
    ... > The heatsink has its own control loop, thermometer, and heater. ... by the analog timer, one might just need: ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... There will be event queue manager program unit and a Timer program unit that gets ticks from the system clock and generates events to other program units. ... My point in this context is that the closer your simulation models that actual software, the better and more robust it will be. ... Another way is to not use the system clock at all and just trigger the next simulation clock tick with an event to the Timer that is generated when all the processing for a single clock tick is done. ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... The closer the simulation structure reflects the real-time structure, ... So don't have the tasks poll a timer and then make a decision about what to do. ... Then you isolate the problem of generating the "real-time" events as a simulation implementation problem for the Timer object rather than as part of emulation abstractions. ... It just so happens that the rules of finite state machines coincide very nicely with fundamental OO practice. ...
    (comp.object)