Re: Lahman, how ya doing?



In article <vTPee.117$Ws6.102@trndny07>,
H. S. Lahman <hsl@xxxxxxxxxxxxxxxxx> wrote:
>Responding to Hansen...

>
>> You know, I've actually picked up a nice book on UML and OOA from the lab
>> library and started reading it. I should be learning this there instead
>> of bugging you about it. I've picked up other homework that has priority
>> because it's more directly related to making a living, but I really
>> should find time to just read about this stuff.
>
>Hopefully it is a good book. B-) You might check out the Books section
>of my blog for some suggestions. Bruce Douglass has several books on
>the OOA/D-based use of event-based processing in R-T/E. One also has to
>be careful about books with 'UML' or a specific OOPL in the title; they
>are good at describing how to express your design in the UML or OOPL
>syntax but they tend to be short on advice about coming up with a good
>design in the first place.

Applying UML and Patterns, 2nd ed, by Craig Larman. The author has made
clear in an early chapter that the book is about OOA, and what UML is
introduced will serve that purpose.

>>
>> I appreciate the help, though.
>
>That's what these forums are for. Being retired after 40+ years in the
>business I have an oversupply of opinions and I need to get rid of them
>someplace.

And you're still paying attention to me. I get the feeling that the other
readers of the forum see the thread now and then and think "Are they still
going at it?"

>>>>I want to understand it. It looks like you're generating dumb events--
>>>>the event generator can't adapt to changes and it doesn't pass
>>>>context-dependent information in them.
>>>
>>>That's correct because I abstracted Timer to a very restricted task:
>>>generating scheduled events. The "smarts" of the simulation is in the
>>>intervals assigned, the ordering rules, the destinations, and the state
>>>machines. That deals with the requirements as I understood them: your
>>>processing was defined in terms of relative position in a "hard-wired"
>>>time stream. All of the computations and sequencing you have described
>>>so far can be accommodated by the way the events are registered with Timer.
>>>
>>>So far I don't see anything that is context-dependent or needs
>>>adaptation. When we talked about things like hardware propagation
>>>delays before you didn't seem to think they were relevant (i.e., all the
>>>controller processing could be completed in a base clock tick interval
>>>(100 ms) in the real controller). You can simulate that easily by
>>>scaling real time (to accommodate your simulation computations) or
>>>simply invoking Timer.tick() when all the rest of the processing for a
>>>tick is done. So...
>>
>>
>> But speaking of maintainability, if it were to be used in a real-time
>> system with Timer reading the system clock rather than incrementing a
>> counter, could it easily accomodate that?
>>
>> You'd get rid of simulated hardware, then. But some things like the
>> controller logic and the chart recorders should be reuseable.
>>
>> I think Timer would need to be revised, but as long as it spits out events
>> with EventElement.tick_count set appropriately, maybe that's it.
>
>Let's say the real controller has a problem on "witching" ticks where
>there is so much processing that it will overflow the tick interval and
>screw up the synchronization. The controller is going to have to deal
>with that explicitly somehow. That means the design will have certain
>priorities for what gets done in the tick interval and it will have to
>work around them.
>
>In your case the hardware sampling is probably more critical than some
>of the data processing steps. Thus whether the computation of the
>average in Thermometer includes 60 samples, 59, or 61 may not matter as
>long as one has a sample count for the standard deviation. One possible
>solution to that problem is to assign a different event queue to the
>statistic computation and put it in a lower priority thread than the
>temperature sampling itself. Then it doesn't matter if it bleeds over
>the tick interval because it won't affect other processing that must get
>done within the tick interval. (There is no problem with
>synchronization to the next statistic computation because that is 50+
>ticks in the future.)
>
>However, that may introduce another problem. The filtering and other
>computations in the feedback loop that are done after the statistics may
>need be deferred as well. So you may not want to trigger them from
>Timer directly. Instead the computeStatistics action of Thermometer can
>generate that event when it is done. That will ensure the subsequent
>processing in the loop is synchronized to the actual samples rather than
>the Timer's ticks.
>
>This is just a simple example and I have no idea whether it would work
>in your controller. But hopefully the basic idea is clear. Not all the
>events need to be generated by Timer; other synchronizations in a
>daisy-chain may be done simply on the basis of relative processing step.
> To figure out the best way of solving the problem one would have to
>identify all the dependencies through the feedback loop and separate
>those that were directly dependent on clock ticks from those that were
>only indirectly dependent on them.

A priority queue is an interesting idea. I suppose when that empties it
goes to the normal queue. Some systems let your prioritize tasks on a
scale of 0 to 7 or so, I wonder if they have eight queues.

But really, a lot of this just isn't much of a problem if timing
information is passed to an object. E.g. in our real equipment we have
some CAMAC counters that accumulate NIM pulses until they're stopped,
read, and reset. And one channel is connected to a precision time
standard. We're making a precision measurement, if the timing is off by
1/60 second that would be horrible. But for as long as the other channels
count events from the charged particle detectors, the timing channel will
be counting ticks from the time standard, and we can simply divide to find
the counting rate to a precision that's limited by counting statistics but
not by time resolution.

Unless there are gross timing errors that e.g. would cause the control
system to go unstable or get too choppy to collect good data, I think just
passing an elapsed time to the parts that need it is a simple and
satisfactory solution.

>
>[Alas, that might get more complicated if dependencies were nastier.
>That might mean tracking value histories to ensure getting the right
>samples or something. However, I suspect your filters, etc. probably
>only care about the most recent N samples so if they are delayed in real
>time so that they never "see" some older samples it won't matter.]
>
>>>The only situation that seems like a problem is if the total computation
>>>time on some "witching" ticks where the maximum number of events occur
>>>(e.g., a 1 tick sample, a 60 tick statistic, and a 7/60 adjustment all
>>>occur on the 420th tick) causes the total computation time to extend
>>>beyond the tick interval. That would screw up everything if one did all
>>>the processing. Is that what you had in mind?
>>
>>
>> Yep. In the real system, running on a ten year old computer, sometimes
>> a scheduled time is missed. Whether it's because of witching ticks or
>> delays when reading the GPIB or other, I'm not sure. But that happens on
>> a small fraction of the total ticks, and the timing information is passed
>> to the functions that need it.
>
>OK. I described one general approach above, but in the end it depends
>on how the skip occurs and what significance it has.
>
>Unrealistic as it may be, let's assume just for the sake of example that
>the statistics tick comes along but there are only 59 samples because
>one single tick event never got processed and we need exactly 60 samples
>in the statistic for consistency. Let's also assume that everything
>will be fine if you do two things: (1) don't invoke computeStatistics on
>the current tick and (2) do invoke computeStatistics on the next tick.
>
>If that is all that needs to be done to keep synchronization, then your
>solution of having Timer defer the event generation to the next tick
>would work fine. There are lots of ways to do that, such has having
>Timer maintain an internal queue of deferred events (which could be a
>simple bitmap for events). You could also make Thermometer responsible
>for keeping track of how may samples it had since the last statistics

Thermometer might be a bad example in a real-time system because it would
be replaced by physical components. The end result would be a voltage
that is read periodically. But Controller, as a typical PID controller,
has an integral and derivative term, and a low-pass filter, that all
require a time interval. (And a proportional term that doesn't.)

>computation. Then my code in Timer.tick for the event generation loop
>might look something like:
>
> int eventTickCount = eventList[i].getTickCount();
> bool isReady = eventList[i].getRecipient()->isReady();
> if ((tick_count MOD eventTickCount) == 0)
> {
> if (isReady())
> queueManager->push(&eventList[i]);
> else
> deferredBitmap |= (1 << i); // set deferral of event

Woah, what are you doing to deferredBitmap there? Looks kinky.

> }
> else
> {
> if ((deferredBitmap &= (1 << i))) && isReady)
> {
> queueManager->push(&eventList[i]);
> deferredBitmap ^= (1 << i); // clear deferred event
> }
> }
>
>[There is a drawback here because every receiver needs an isReady()
>method even if this notion of readiness is not relevant. (Worse, in C++
>isReady needs to be a behavior of Object.) But I have a different point
>to make in the example, so ignore the details.]
>
>I chose this implementation because it is logically almost identical to
>your solution where Thermometer decides when to do the computation.
>Indirectly Thermometer /is/ driving the decision here. There are,
>though, subtle differences.

Well, I didn't have Thermometer deciding when to do a calculation. Except
in a manner of speaking when it was initialized with Timer. After that,
Timer had a list of scheduling information which it updated and used to
calculate elapsed times, which were included when events were sent.
Thermometer would receive an event and read an elapsed time. There was no
communication back to Timer, although I did have Timer itself track
whether it had just sent an event there.

It's not really important for anything I'm doing that a certain number of
samples are collected, just so you know how many there are. And it's not
so important that something is processed exactly at a particular time,
just so long as you know what time it is processed.

>
>What Thermometer knows is that it has enough samples, which is quite
>consistent with a responsibility for computing sample statistics. That
>semantic is quite different than knowing whether a sample has been
>skipped on some tick during the processing. So in Timer we combine
>reasonable knowledge from Thermometer for its semantics with a rule and
>infrastructure in Timer related exclusively to scheduling events. IOW,
>the developer is providing the mapping between the two independent
>semantic views through interpreting isReady() in the local context.
>Thus the separation of concerns has been preserved and the
>implementations of Timer and Thermometer remain independent of one another.
>
>A second difference is that Timer is accessing a simple knowledge
>responsibility in Thermometer rather than a behavior. This is good
>because data is usually easier to manage and manipulate than sequencing
>of behaviors. Capturing the solution parametrically in data also
>reduces executable code, which improves reliability. However, the main
>reason this is good is because in OOA/D one thinks of knowledge and
>behavior responsibilities differently, so it is a form of separation of
>concerns and complexity management.
>
>In OOA/D behavior communication is via an asynchronous communication
>model, as I think I have already mentioned. That's why events and state
>machines are ideally suited to OO development. OTOH, knowledge is
>accessed synchronously on an as-needed basis. This dual view tends to
>make things much simpler to manage in complex OOP implementations where
>stuff like concurrency may come into play. The asynchronous model
>effectively forces us to define behavior scope narrowly, essentially to
>the method level. That means that the scope of data integrity for
>knowledge access within those methods is very narrowly defined. One has
>to screw around with concurrent processing to have the importance of
>that burned into one's lobes, but it can avoid problems even in
>synchronous implementations.
>
>One corollary of this separation of knowledge and behavior is manifested
>in two facts that are rarely mentioned in the OO literature but
>logically follows from encapsulation, separation of message and method,
>peer-to-peer collaboration, all the rest of that OO stuff. Behavior
>messages very rarely carry data in OO applications and behavior methods
>never return values in well-formed OO applications. The first is
>because knowledge access is synchronous so one accesses it directly from
>the source when it is needed. The second is because of the separation
>of message an method and encapsulation; returning a value that the
>caller uses would create an instant dependence on what the callee did.

I want to discuss that last paragraph a little more. The second point,
that behavior messages never return values, is something I hadn't really
thought of, but have been finding no reason to try to do.

The first, though, that behavior messages very rarely carry data, that one
accesses it directly from the source when it is needed, I need more on
that. For starters, I thought behavior messages *do* carry data, or that
they can. E.g. when a mouse down event is generated, you're going to want
to know where it went down. But in the case we're discussing, I get the
idea that if Controller needs to know an elapsed time it should ask Timer
for the time, keep its own record of the previous time, and do its own
calculation of elapsed time. Which is easily done. But then that
requires that Controller knows that Timer exists, which I thought was
something we're trying to avoid.

>>>The issue is not about providing the programmer options. There is only
>>>one way to correctly define the sequencing so the programmer doesn't
>>>have any options in that sense. The issue, as in all OO software, is
>>>how hard will it be to change the sequencing when the requirements
>>>change. Envision the various sorts of changes that might be made in the
>>>sequencing of Chart's responsibilities, especially relative to other
>>>object's responsibilities. I assert that for many such changes it will
>>>be a whole lot easier to do that by manipulating my statements than the
>>>implementations of Chart, Timer, and other objects. (In fact, I gave an
>>>example, as immediately below...)
>>
>>
>> In my implementation it would only be manipulating Chart, since by design
>> Chart just internally takes on the task of doing what you did explicitly
>> in main(). And if Chart's responsibilities change, you'd be mucking about
>> with it anyway.
>
>The problem is that proper sequencing is a broader issue than only one
>object. Consider:
>
>Timer.add_event(&chart, E1, 1);
>Timer.add_event(&thermometer, E2, 10);
>Timer.add_event(&chart, E3, 20);
>
>vs.
>
>Timer.add_event(&chart, E1, 1);
>Timer.add_event(&chart, E3, 20);
>Timer.add_event(&thermometer, E2, 10);
>
>In my solution this changes the ordering in which the E1, E2, and E3
>responses are executed on the 20th tick, which could be significant to
>the results. How would you capture that change? I suspect you will
>have to change the implementations of at least two objects.

I can see that being used to give some basic prioritization, for an
example.

I know you don't like my thought to issue update events to all the blocks
at the end of an iteration, to move new output values into current values.
But what you wrote above does suggest an easy way to provide for that.

Timer.add_event(&target, eEvolve1, 1);
Timer.add_event(&controller, eEvolve1, 7);
Timer.add_event(&target, eUpdate, 1);
Timer.add_event(&controller, eUpdate, 7);

Instead of fiddling around with sorting the time evolution needs from the
updating needs, just save all the update events for the end.

>
>>>>Actually, in my version I would use
>>>>
>>>> timer.set_ticks_per_second(100);
>>>>
>>>>I think it wasn't clear, but all the times I'm using in my implementation
>>>>are in seconds. Ticks are internal to Timer, and changing the number of
>>>>ticks per second should have no effect other than to change the time
>>>>resolution. ChartRecorder and all the others only know seconds.
>>>>
>>>>But that does bring up another point related to how timing events are
>>>>generated. It would be perfectly possible, say, to define something like
>>>>
>>>> Timer timer(10); // 10 ticks per second
>>>> Object ob(0.15); // trigger every 150 ms
>>>>
>>>>That won't trigger at regular intervals. But it should trigger at 0.20
>>>>seconds, 0.30, 0.50, 0.60..., and it should run perfectly fine.
>>>
>>>My background is showing because an R-T/E person would never even
>>>consider this. B-)
>>
>>
>> I'm not sure I ever figured out what R-T/E means. Real-Time/Event?
>
>Real-Time/Embedded.
>
>>>The hardware only understands integers (unless it is an FPP in an ALU).
>>> Floating point is usually very expensive compared to integer
>>>arithmetic and performance is usually critical in device drivers and
>>>controllers. (I'm sure the data processing in your real controller
>>>needs floating point, but an R-T/E guy will be looking to minimize it.)
>>> And one has potential roundoff problems for things like clock ticks
>>>that could raise havoc with synchronization. So the interval would
>>>always be designed as an integer multiple of a base tick value.
>>
>>
>> Is floating point arithmetic still that much slower than integer in modern
>> CPUs? I thought the floating point performance was getting quite fast.
>
>It certainly is a lot faster than the Old Days where an FP addition took
>300+ clock cycles compared to one cycle for an integer addition. It can
>also be very fast on RISC machines where more gate real estate is
>available for basic computation because the instruction set is a lot
>simpler. But on something like a Pentium the floating point isn't even
>done in the main ALU; it is farmed off to a special floating point
>processor (FPP). [That's why the RISC people only want to benchmark in
>FLOPs while the CISC people only want to benchmark "real" applications.
> In reality none of the benchmarks mean much because a reasonable
>comparison needs to consider stuff like bus architecture, register
>count, compiler optimization, pipelining, memory cache, etc., etc.]
>
>Nonetheless, it can still take multiple clock cycles compared to integer
>arithmetic for multiplication and division.

For this immediate purpose, I think I'd be willing to sacrifice some speed
needs in favor of keeping the timing of events in terms of "immutable"
seconds, and keep ticks per second a parameter to pass to Timer. But I'm
not sure what I would do that I could be happy with if I needed to go to
ticks.

[Trimming the hedge a little. The neighbors are complaining about it
growing into their yards.]
--
"Not that there's anything wrong with just lying around on your back. In
its way, rotting is interesing too... It's just that there are other ways
to spend your time as a cadaver." -- Mary Roach, "Stiff", 2003.
.



Relevant Pages

  • Re: Lahman, how ya doing?
    ... Straightforward diagram right out of a control theory text. ... Normally Controller would be sent a control event, and would then get a temperature and sample time from Thermometer and calculate a control action. ... But if it had previously received a delay event, it would send a control event to Controller on the next tick. ... When I saw the diagram I thought you were going to suggest that the event that triggers Controller is generated by Thermometer rather than Timer. ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... >>>Thermometer and before Controller. ... It's a substitute as far as Timer sending control events is ... So on a witching tick ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... That allowed one to define the scheduling in the way Timer::add_task() was invoked rather than embedding the scheduling rules in the implementation of Timer. ... They should not be concerned with sequencing their activities within the overall solution context. ... More important, given your explanation below, is that ChartRecorder should not know about the rules and policies that determine the correct sequencing in the overall flow of control. ... Just have it enforce the simple rules that multiple events on the same tick are issued in the order that they were defined. ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... Object* recipient; int event_id; ... Timer ... Not relevant to my simulation, but generalizing to a case where any amount of time can pass between when an event is pushed on to the queue and when it is popped off and processed, it seems you can have two identical events going to the same object. ... given tick. ...
    (comp.object)
  • Re: Lahman, how ya doing?
    ... That allowed one to define the scheduling in the way Timer::add_task() was invoked rather than embedding the scheduling rules in the implementation of Timer. ... In your problem Timer has to churn out events on a schedule that is based on counting time units (clock ticks or simulation ticks). ... Just have it enforce the simple rules that multiple events on the same tick are issued in the order that they were defined. ... By employing true event-based processing you completely separate the concerns of determining when the Beam should do its thing from the concerns of what it needs to do. ...
    (comp.object)