Re: Lahman, how ya doing?
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Sun, 15 May 2005 16:28:35 GMT
Responding to Hansen...
An average and a standard deviation can easily be calculated with 59 samples rather than 60. But some of what I want to do involves time-dependent evolution, like to calculate a change in temperature, or to calculate a control action. You can't just skip a tick and do an average. If you're reading an input f(t) and the number you want is
(f(7) - f(0)) / 7
and you calculate
(f(8) - f(0)) / 7
That is Just Plain Wrong. If a tick is skipped there you must calculate
(f(8) - f(0)) / 8
It's not quite on schedule, but at least it's a slope. The former isn't a slope, it isn't anything. It's just wrong.
OK. But you said one of the statistics you were computing was a standard deviation. This is something different. I was looking at the
I was actually thinking in the more general sense, but using this particular problem for concrete examples, since it's the one I have experience in and we've been discussing. As you've seen, sometimes I have trouble translating the abstract into the concrete, so I needed a particular ferinstance to center the discussion.
OK, but one solves the problem in hand. As a result the solution for one requirement may not be suitable for another.
A basic premise of OO is that requirements are volatile over time and changes are unpredictable. So one solves the current problem in hand the simplest way possible and let's good OOA/D/P practice provide software that can be modified fairly easily in the future.
Note that my solution for the slope problem in the message was a variation on the basic solution I already had for the standard deviation problem. IOW, it was a classic maintenance solution. The Thermometer state machine was essentially the same as the one I originally proposed a few messages ago; it just no longer did the actual hardware sample and now counted ticks. Basically I just delegated the actual sampling to Sampler and connected the message dots slightly differently. None of the other processing around Thermometer, including Timer and the way the basic simulation was scheduled, was affected.
My assertion is that was no accident. It followed directly from separating concerns, abstracting intrinsic responsibilities, separating message and method, hiding implementations, yadda, yadda, yadda.
standard deviation problem. (I can see computing a mean from endpoints on a slope if one assumes linearity, but in that case standard deviation probably isn't very useful.)
If you do have to compute a slope then the problem semantics will require an absolute time to be recorded somehow. Then it is fair for the notion of a hardware Sample to include both the value sampled and the timestamp (whatever form it is). Then you will still have the correct semantics _in the object itself_ to do the calculation without worry about skips per se.
Not thinking of skips in particular, just in getting certain necessary information into an object when an event triggers it. Hardware sample
includes both the value sampled and the timestamp. Let's see what I can make of that with abbreviated fragments of code and big names.
Timer { ... public: float get_time(); };
Thermometer { ... Timer* timer; float temperature; float sample_time; public: float get_temperature() {return temperature;} float get_sample_time(); };
SlopeCalculator { ... float previous_time; float previous_temperature; float slope; Thermometer* therm; public: void calculate_slope() { float temp = therm->get_temperature(); float t = therm->get_sample_time(); slope = (temp - previous_temperture) / (t - previous_time); previous_temperature = temp; previous_time = t; } float get_slope() {return slope;} };
Our messages are multiplexing. B-) In responding to the other message yesterday I indicated this is a reasonable way to go. (You have a problem with initializing previous_time for the first slope calculation, but that's easy to fix.)
Note that, other than being synchronous, this solution is not very different than the one I proposed for the slope problem. SlopeCalculator is explicitly saving the current time of the beginning sample it needs, as I did in Sample. The main difference is that in my solution the delta in ticks was relative rather than absolute. But it was still based on what Timer thought the ideal schedule was.
Whether Timer counts the ticks absolutely or Thermometer does relatively really doesn't make much difference. Whoever is counting has a reasonable knowledge responsibility for knowing the current count and that is synchronously accessed via a getter.
A second point to note is that in both these solutions there is no hint of skips (e.g., some comparison of My Time vs. Your Time). That sort of thing was implied both in your original proposal of deferred ticks or passing the current "real" tick count from Timer so that it could be compared to the apparent tick count from Thermometer. In both solutions there is now a clear partitioning of responsibilities; Timer tracks the actual schedule time while Thermometer (or whatever) makes use of that purely in the context of its own semantics (e.g., storing the right sample tick count somehow for later use).
That segues to another interesting point about this solution related to your opening comment, "...just in getting certain necessary information into an object..." In this solution nobody is putting anything into SlopeCalculator. SlopeCalculator is getting the knowledge it needs when it needs it. The quote is actually a tell on a procedural mindset in that Timer somehow needs to provide SlopeCalculator with what it needs in push mode. But the solution is actually a very OO view where SlopeCalculator alone knows what it needs and it goes and gets it in pull mode.
So in other words, the time is not sent to SlopeCalculator (or the controller, or etc.) when an event is sent to it. The thermometer records both the temperature and the time when a Get Sample event is sent to it. When a Calculate Slope event is sent to SlopeCalculator, it receives both the temperature and the time that the temperature was recorded from Thermometer, not from Timer, and not passed in the event. I want to assign the primary acquisition and storage of the time of a sample with the thing that takes the sample, they're a matched pair. When something uses the sample to calculate a slope, control action, filtering, etc., it gets the time of sample from the same place that it got the value of the sample.
Is there an echo in here? Storing the tick count with the sample is also what I did for essentially the same reason.
The interesting design question is: which tick count (absolute vs. relative)? If the samples are processed directly by other objects that also depend on time, the relative tick count in Thermometer may not cut it because the period is unique to Thermometer. This gets back to the opening point in this message: choosing which way to represent time depends on the requirements holistically rather than one at a time.
And the event sent to SlopeCalculator is to Calculate Slope, not Calculate Slope With This Time.
Still seems to be echoing in here.
And when a tick is missed and control eventually gets to Thermometer to sample the temperature, it will simply record the time it samples when it samples.
Still echoing.
Okay, let's see what you say.
The way I would probably handle that (given the single tick sample is skipped) is to delegate the sampling in Thermometer to a Sampler object:
1 uses 1 [Thermometer] ------------- [Sampler] | 1 | used by | | deletes | * [Sample] + hardwareValue + tickCount
The delegation is to allow Thermometer and Sampler to run in different threads where Sampler's is slower. When Thermometer gets a 1-tick event it increments an internal tick count and sends an event to Sampler that will poll the hardware, create a Sample instance, and send back an event to Thermometer with the instance handle.
Uh-oh. Just when I conclude that I don't have to stuff any context-specific information into an event, you stuff an instance handle into it.
Alternatively Sampler could act like a factory pattern and add the new Sample directly to the collection. However, Thermometer would have to know which one was added. One way to do that is by adding a relationship to the model:
1 uses 1 1 last provided to
[Sampler] ------------------ [Thermometer] ----------+
R1 1 | |
used by | |
| R2 R3 |
deletes | |
* | 1 |
[Sample] --------------+If Sampler is acting like a Sample factory, then it needs to instantiate all of the relevant relationships for the new Sample. So it would add it to the collection (R2) and also update the R3 relationship. That allows Thermometer to update the relative tick count correctly for the right instance without being passed the reference in the event. (As a practical matter, at OOP time one might implement R3 as a simple getLast() method of the R2 collection class.)
The point I am getting at here is the this is really a matter of instantiating a relationship, which necessarily means manipulating object references. But relationships are orthogonal to class semantics so one does worry about that sort of reference passing very much.
You are right, though, that it generally isn't a good practice to pass object references around. In event-based processing that entails risk for referential integrity because of the delay (i.e., the reference may no longer be valid by the time the event is consumed). That would no be a serious problem here because of the nature of the collaboration.
A more solid reason for not passing object references is coupling. Passing an object reference is the worst form of coupling because the receiver can do anything it wants with the object. That opens the potential for trashing the sender in unexpected ways if it also accesses the object subsequently. Again, not a terrible thing here because [Sampler] lives to create [Sample] instances and doesn't use them subsequently.
Also, passing a reference essentially instantiates a temporary relationship (i.e., the same as R3 but limited to the scope of the responding method). The problem with that is that the sender must know which object the receiver needs to collaborate with. Usually that is none of the sender's business; any collaborations the receiver might have should be carnally private to the receiver. The one exception is factory responsibilities, as in this case, where the instance is being created and referential integrity demands that the relationships be instantiated at the same time.
Finally, you are right that I was sloppy in putting it in the event data packet, but for different reasons. An OO reviewer would jump all over the fact that the tick count was not defined until Thermometer sets it but that is after the Sample leaves the scope of instantiation. That is potentially a nasty data integrity problem. The proper way to avoid that is for Sampler to ask Thermometer what the relative tick count was and initialize it as part of the instance creation.
A more serious problem is the delay before consuming the event. That makes it possible for the Thermometer to "see" another Timer single tick event and count it between the time the Sample is actually created and when Thermometer responds to Sampler's event. That is actually a killer and would preclude my solution in an event-based context. But I was trying to keep the example simple and not get side tracked over other issues.
So you are right, Sampler should ask either Thermometer for the relative tick count (or Timer for the absolute tick count) and put that in the Sample attribute before it gets out of scope. Then Sampler can also add it to the R2 collection directly and doesn't need to pass it to Thermometer on the event or even instantiate R3 because Thermometer no longer needs to do anything with it right then.
Thermometer responds by adding the Sample instance to its collection and assigning the current value of its internal tick count. Meanwhile, if Thermometer receives another 1-tick event while Sampler is doing its thing, the internal tick count will be properly incremented for the skip. So the state machine for [Thermometer] might look like:
[Sample Created] --------+ increment sample count | add to sample collection | assign tick count to | sample | ^ | +-------------------------+ | | | E1:single tick | | E3:sampled | | V | | +--------------- [Single Tick Done] <---------------+ increment tick count E1:single tick send E4:get sample to Sampler | ^ | | E2:60th tick | | E1:single tick | | V | [Statistics Computed] compute slope compute std dev clear tick count clear sample count
This allows Thermometer, in the high priority thread, to keep track of the actual tick counts between samples. Since those represent the true schedule (i.e., emulated real time) everything Just Works (as long as each action in the state machine can complete within one tick).
Is this a reasonable alternative? Thinking specifically of a simulation, the Sampler, representing the hardware interface to the lock-in, gets its ticks reliably from a high priority queue. It always has a timely temperature sample and time of sample, ready for whoever wants to read it. Thermometer, representing the part of the digital world that records the temperature for system use, the code that can be delayed, is on the low priority queue. Its processing can be delayed, and Sampler can keep right on sampling without it. But when Thermometer does receive a Get Sample event, it gets whatever sample and time of sample that Sampler has at the time. No instance handles in the events.
Remember my context. My assumption was that in the real controller the thing that got skipped was the actual sample on a given time tick because that sampling was in a lower priority thread. To emulate that Sampler needs to run in a lower priority thread in the simulation. Since the goal is to emulate sample skips, it doesn't need to trigger off the real schedule events. So it can be daisy-chained to Thermometer's processing of the real schedule events. Intuitively that makes at least some sense because I delegated Sampler from Thermometer in the first place.
************* 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
.
- Follow-Ups:
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- References:
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- Prev by Date: Code generation API's anywhere ?
- Next by Date: Re: Lahman, how ya doing?
- Previous by thread: Re: Lahman, how ya doing?
- Next by thread: Re: Lahman, how ya doing?
- Index(es):
Relevant Pages
|