Re: Lahman, how ya doing?
- From: glhansen@xxxxxxxxxxxxxxxxxxxxx (Gregory L. Hansen)
- Date: Thu, 5 May 2005 01:51:49 +0000 (UTC)
In article <fMaee.15793$Ab.6587@trndny04>,
H. S. Lahman <hsl@xxxxxxxxxxxxxxxxx> wrote:
>Responding to Hansen...
>
>>>>>>Before, I told Timer to include a control block, and what the scheduling
>>>>>>should be, e.g.
>>>>>>
>>>>>> timer.add_task(&c1, 5); // five second intervals
>>>>>
>>>>>I liked this (or at least a refinement where one registered the event
>>>>>associated the interval along with the recipient object who cares about
>>>>>it). 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. (If you have gotten rid of Tasks, then
>>>>>you could just rename it to something like Timer::add_event(...), since
>>>>>that is what you are actually doing.)
>>>>
>>>>
>>>>The problem I had with it is that the number, types, and scheduling of
>>>>events has to be known and declared separately from the object definition.
>>>>E.g.
>>>>
>>>> ThermalBlock target(10, 2); // heat capacity 10 J/K, initially 2K
>>>> timer.add_task(&target, 1/60, 1);
>>>> // You just have to know there's only one type of event to
>>>> // generate, it is of type 1, and time information is declared
>>>> // seperately from the initialization of target.
>>>
>>>But Timer doesn't know anything about what responses go with those events.
>>>
>>>In your problem Timer has to churn out events on a schedule that is
>>>based on counting time units (clock ticks or simulation ticks). To do
>>>that all it needs to know is what the event ID is, how often (the tick
>>>count interval) it should be generated, and who the event goes to.
>>>That's what registration provides. The registration completely
>>>decouples Timer from the responses and event the ordering decisions.
>>>More important, the responses are completely decoupled from the logic
>>>that determines when they should be executed. Best of all, everything
>>>about the sequencing of operations can be defined in a set of
>>>Timer.add_event calls.
>>
>>
>> But the user, meaning the person that is just using some classes and not
>> programming their behavior, shouldn't have to know event IDs, how many
>> different types of events an object has, or which order they should be in.
>> If the thermometer in the real system doesn't have an event ID, the user
>> shouldn't have to include an event ID when he instantiates a thermometer.
>
>I am thoroughly disconnected here. This sounds like the "user" is the
>user of the simulation application. If so, I can't see why the user
>would be instantiating objects, much less knowing about events. Those
>are strictly software artifacts in the solution. The user might
>parameterize the processing (e.g., defining the number of samples to use
>for computing basic Thermometer statistics), but that mapping to
>simulation ticks is a pure software design issue.
I mean the programmer who only looks at the header files, who doesn't know
anything about the internal workings of the classes.
>
>If the Thermometer in the real system had been implemented as a state
>machine (as I argue it should have been), then there would have been an
>event id for each event that triggers a transition. The event identity
>is the only way to know what events go with what transitions. (In fact,
>it is that abstracted event identity that enables the decoupling -- it
>is the only thing shared by the event generator and the responding
>behavior.)
In the real system the thermometer is a germanium resistor in an AC bridge
with a voltage read by a lock-in amplifier. But the Thermometer class
will surely know which event IDs it will respond to.
>
>>
>>
>>>> ChartRecorder chart(&target);
>>>> timer.add_task(&chart, 1, 1);
>>>> timer.add_task(&chart, 60, 2);
>>>> // You have to know that there are two types of events to
>>>> // generate, they are of types 1 and 2, 1 should come before
>>>> // 2, and the timing information of once per second and
>>>> // once per 60 seconds is declared separately from the
>>>> // initialization of chart.
>>>
>>>IOW, somewhere in the design one needs to ensure correct sequencing when
>>>operations are invoked. You always have the problem; the issue is how
>>>to address it with minimal dependencies.
>>>
>>>
>>>>I wanted those classes, when programmed, to know for themselves how many,
>>>>what types, and what order of events to generate, own their own timing
>>>>information, and simply be told to register with timer in the way they see
>>>>fit. E.g.
>>>
>>>I assert that this is what you do not want. The individual objects do
>>>their individual responsibilities when it is time. They should not be
>>>concerned with sequencing their activities within the overall solution
>>>context (i.e., determining when it is time to do so). That is a context
>>>decision for collaboration.
>>>
>>>Using Timer.add_event to define that sequencing is equivalent to drawing
>>>message lines in swimlanes in a UML Sequence Diagram. It defines the
>>>collaboration sequencing at a different level of abstraction than
>>>individual object responsibilities and implementations.
>>
>>
>> I thought it was more important that the user doesn't have to pop the hood
>> and count the number of spark plugs in the engine before he turns the key.
>
>I clearly have no idea what you mean by "user" because this makes no
>sense to me.
The person who #includes "control.h", but isn't the one who programmed
the methods.
>
>> Another way to do it, rather than having chart internally calling
>> Timer.add_event, is for chart to prepare a list of events in their proper
>> order. Then when
>
>Again, ChartRecorder should not know how to do that. That is part of
>the overall simulation solution, not an individual responsibility of the
>object whose collaboration with other objects is being defined. The
>collaborations are about connecting the flow of control dots. The
>individual objects should only know about their own dots
>(responsibilities), not the overall flow of control of the solution.
Let's consolidate this below.
>
>>>> ChartRecorder chart(&target, 1, 60);
>>>> chart.declare(&timer);
>>>> // The user who just wants to define and use a ChartRecorder
>>>> // doesn't need to know the registration details. He needs
>>>> // to know what is being recorded, the two time intervals,
>>>> // and what the timer is, and nothing else.
>>>
>>>It is Timer that is generating the events so the relationship only needs
>>>to be navigated from Timer to ChartRecorder. So ChartRecorder has no
>>>need to know that Timer even exists; it just processes events it is
>>>given. 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.
>>
>>
>> ChartRecorder accepts two different kinds of timing events, one to compute
>> sums and one to compute an average and reset the sums. So ChartRecorder
>> had better know which order they should arrive in. It doesn't need to
>> know anything about events sent to other objects.
>
>Why?!? All ChartRecorder needs to know is (a) how to compute sums and
>(b) how to compute an average. Providing the data and determining when
>those things should be done is not ChartRecorder's responsibility; that
>is a matter of collaboration with other objects.
>
>Nor would it know anything about events sent to other objects; it only
>responds to events for transitions in its own state machine (i.e., those
>defined in its STT). (If it gets an event it doesn't understand, it
>should signal an exception because the event was addressed to the wrong
>object, which should never happen in correct software.)
>
>>>ChartRecorder may reasonably know individual parameters (e.g., how many
>>>time ticks) that determine that sequencing, but it shouldn't know how
>>>events are defined or generated.
>>>
>>>Also, who is the 'user' here? The simulation software user? If so, why
>>>would that user know anything about the sequencing mechanisms in either
>>>of our designs?
>>>
>>>
>>>>>I am also concerned about how complicated that "scheduling information"
>>>>>is and the sorts of decision Timer will make. I think Timer should be
>>>>>rather simple-minded here. It just count ticks and generates events in
>>>>>an order defined by Timer::add_event. There shouldn't be any, "If I
>>>>>already generated the E47 event, then I should...," sort of decision
>>>>>making. Just have it enforce the simple rules that multiple events on
>>>>>the same tick are issued in the order that they were defined (or use a
>>>>>relative priority number that is defined when Timer::add_event is invoked).
>>>>
>>>>
>>>>A page of code is worth a hundred words, so I'll just show you how it
>>>>looks right now.
>>>>
>>>>struct EvolutionList
>>>>{
>>>> Block * bptr;
>>>> int event_type;
>>>> float wait_time;
>>>> float next_time;
>>>> float last_time;
>>>> bool just_triggered;
>>>>};
>>>>
>>>>bptr points to the object that is to receive the event. bptr, event_type,
>>>>and wait_time are parameters passed to Timer, and once defined they don't
>>>>change. next_time, last_time, and just_triggered are variables that
>>>>Timer manages, and they're initialized to next_time=wait_time,
>>>>last_time=0, just_triggered=false.
>>>
>>>That's what I was afraid of. B-)) I see no reason for Timer to have
>>>attributes like next_time, last_time, or just_triggered. All it needs
>>>to do is count ticks and generate the relevant event(s) when the count
>>>is right. So I see the entire <C++> implementation of Timer as:
>>>
>>>class EventElement
>>>{
>>>private:
>>> Object* recipient;
>>> int event_id;
>>> int tick_count;
>>>public:
>>> EventElement (Object* r, int e, int t)
>>> {recipient = r; event_id = e; tick_count = t;};
>>> int getTickCount() {return tick_count;};
>>>}
>>>
>>>class Timer
>>>{
>>>private:
>>> EventQueue* queueManager;
>>> EventElement eventList[MAX_EVENT_COUNT];
>>> int tick_count;
>>> int next_event;
>>>public:
>>> Timer (EvetnQueue* e)
>>> {queueManager = e; next_event = 0; tick_count = 0;};
>>> void add_event (Object* o, int e, int t);
>>> void tick()
>>> void reset() {tick_count = 0;};
>>>}
>>>
>>>void Timer::add_event (Object* o, int e, int t)
>>>{
>>> EventElement event = new [] (o, e, t);
>>> eventList[next_event] = event;
>>> next_event++;
>>> if (next_event == MAX_EVENT_COUNT)
>>> // signal exception.
>>>};
>>>
>>>void Timer::tick()
>>>{
>>> tick_count++;
>>> for (int i = 0; i < next_event; i++)
>>> {
>>> int eventTickCount = eventList[i].getTickCount();
>>> if ((tick_count MOD eventTickCount) == 0)
>>> queueManager->push(&eventList[i]);
>>> }
>>>}
>>
>>
>> MODing it is an interesting approach that I hadn't thought of. And that
>> makes more sense of some of the other things you'd said-- I imagined
>> filling the event queue with 2 million events before the simulation even
>> began!
>
>Presumably Timer.tick() is invoked when all the processing for the
>current simulation tick is completed. (The easy way to do that is for
>the event queue manager to invoke tick() when it is empty since tick()
>will fill it up with the events for the current tick and those all have
>to be processed.)
In my non-multitasking system, where would control pass to a queue manager
that would pop events and distribute them?
>
>>
>> It's not important in a simulation, but the approach you gave isn't robust
>> against delays. If the interval is 10 ticks and 11 ticks pass, an event
>> is skipped. And when it is processed at 20 ticks it will think that 10
>> ticks had passed. My implentation tests for current_time>=next_time so
>> that the skipped event would be generated one tick late instead of ten,
>> and it tracks last_time so that the actual interval
>> current_time-last_time can be passed rather than an assumed nominal interval.
>
>But you weren't planning to use real-time ticks. So there should never
>be a need to skip any processing when using simulation ticks because
>everything for each tick will be processed (i.e., time will be either
>scaled so that is true or Timer.tick() will not be invoked until the
>processing for the current tick is completed.
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.
>
>We talked about delays inherent in the hardware before, but you didn't
>seem to feel they were relevant (as I recall). If such delays are
>possible, then one does, indeed, need to address that explicitly in the
>design.
>
>However, that should be done outside both Timer and the objects it
>triggers. The events are still generated by Timer just as they would be
>in the real-time system (i.e., the clock doesn't stop). You have to
>emulate hardware delays between Timer and the objects that will skip
>processing because of them.
>
>Note that the objects responding to the events don't know they skipped a
>beat if there is a delay. They still do their job when told to do so;
They should be told they skipped a beat if their job depends on the time
elapsed. Suppose the controller has some term like
v_deriv = D*(v_in - v_last)/dt;
where dt is the time between v_in and v_last. That is a slope, the finite
version of a derivative. If the controller execution is delayed then dt
had better hold the actual elapsed time or it will compute the wrong
number. A delay would lower your time resolution, but at least you want a
slope to be calculated from two points that actually exist on the signal.
>it just happens to be in a different time slice in the overall
>simulation than normal. That's because those responsibilities are
>deliberately not dependent in any way on the relative time they do
>things compared to other objects or the whole sequencing scheme. That
>separation of concerns will make it easier to simulate delays because it
>can be done externally to both Timer and the triggered objects. So I
>would argue the need for delays would be a justification for not having
>the triggered object understand anything about the overall sequencing
>problem.
>
>>
>> If you make provisions for that, I think you just gotta have the extra
>> information and the extra processing.
>
>True. The tricky part is deciding where it goes.
Philosophically, Timer does the timing. Timer knows what time it is, it
knows when the events should be generated, and it should know when an
event it was supposed to have generated was skipped.
>
>>>>>[This assumes every relevant class has a static "dispatch" function. If
>>>>>it doesn't recognize the <event id> (i.e., you've got the wrong class),
>>>>>it will generate and error. Unfortunately statically typed languages
>>>>>may get upset trying to invoke a static method from an instance, so one
>>>>>may need to add a table lookup on a <recipient class id> argument passed
>>>>>with the event.]
>>>>>
>>>>>Class::dispatch accesses the current_state attribute of the recipient
>>>>>object and does a table lookup for the state action in a static STT for
>>>>>the class. The STT is indexed by {current_state, event_id}. It then
>>>>>synchronously invokes the object action from the table lookup, passing
>>>>>it the data packet handle. Each action signature looks the same:
>>>>>
>>>>>actionN (<recipient handle>, <data packet handle>)
>>>>>
>>>>>[Note that the actions are implicitly static class actions so one can be
>>>>>independent of instance typing in the table lookup, hence the explicit
>>>>>"this" pointer so that the action can access instance attributes.]
>>>>>
>>>>>It seems to me that your Block::trigger is doing this sort of dispatch,
>>>>>right? If so, then the thing that seems to be missing is the decoupling
>>>>>provided by the event queue manager. I think that decoupling is
>>>>>important despite the indirections of the table lookups. Also, the
>>>>>event infrastructure is highly reusable or at least amenable to template
>>>>>specification.
>>>>>
>>>>>
>>>>>
>>>>>>But I decided it doesn't make sense to separate timing information from
>>>>>>the rest of the control block definition. The shutter cycle time is owned
>>>>>>by the shutter, the controller sampling time is owned by the controller,
>>>>>>etc. So, e.g. a beam shutter object is defined as
>>>>>
>>>>>I am disconnected again. What's a "control block"?
>>>>
>>>>
>>>>A generic term for one of the shapes you'd find in a diagram of a control
>>>>system. An amplifier, a thermal mass, a digital controller, etc.
>>>
>>>OK, but they seem like different entities that would be uniquely
>>>abstracted as Amplifier, ThermalMass, DigitalController, etc., each with
>>>their own unique instances. I was hung up on the use of "control block"
>>>as a generic, implying something in common among all of them (e.g., a
>>>superclass).
>>
>>
>> Well, they are different entities that would be represented as different
>> classes derived from a base class so that, among other reasons, a single
>> Timer::add_event (Object* o, int e, int t) can be defined rather than
>> having to overload it. C++ isn't very friendly to recasting pointer
>> types.
>
>I was more concerned about the commonality. Using Object* is OK during
>OOP for generic, reusable infrastructure like event dispatching because
>that sort of stuff tends to be aspect-like (i.e., it cross-cuts
>objects). But it is fragile so one wants to do it very carefully and
>only when there is a big reuse payoff and one has economies of scale for
>debugging and testing.
>
>I would not be enchanted with something like:
>
> [ControlBlock]
> A
> |
> +------------+------------+----...
> | | |
>[Amplifier] [ThermalMass] [DigitalController]
>
>as a problem space abstraction for the resolution of functional
>requirements because there is no basis for generalization.
>
>>>>>I have no problem with the objects owning their own timing intervals.
>>>>>But that is just parametric data for the scheduling (i.e., an argument
>>>>>to Timer::add_event). My concern is with the coupling...
>>>>>
>>>>>
>>>>>
>>>>>> HeatInput beam(0, 10, 3);
>>>>>> // low power 0 watts, high power 10 watts, cycle time 3 seconds
>>>>>>
>>>>>>And now the control block informs timer of its scheduling needs,
>>>>>>
>>>>>> beam.declare(&timer);
>>>>>
>>>>>Why would a beam need to know anything about where the event came from?
>>>>>The beam just has an operation to perform when some condition arises.
>>>>>The event announces that condition. But that (that the condition
>>>>>prevails) is all Beam needs to know.
>>>>>
>>>>>By employing true event-based processing you completely separate the
>>>>>concerns of determining when the Beam should do its thing (Timer's job
>>>>>to present events in a timely fashion) from the concerns of what it
>>>>>needs to do.
>>>>>
>>>>>
>>>>>
>>>>>>And beam.declare() internally uses a sequence of timer.add_tasks() to
>>>>>>add as many schedulers as it needs. In this case one, but I've tried two.
>>>>>
>>>>>I understand what you are trying to do and it is certainly plausible.
>>>>>The problem is that to do that Beam must understand the context of how
>>>>>its operations fit into the overall schedule of the problem solution. I
>>>>>applauded Timer::add_task above because it moved the rules and policies
>>>>>of sequencing out of the Timer's implementation. I am against putting
>>>>>those rules and policies in Beam's implementation for basically the same
>>>>>reasons.
>>>>
>>>>
>>>>I'm not sure I understand the objection. declare() tells beam to register
>>>>itself with timer, it is called once during the initialization. beam
>>>>doesn't actually keep the pointer, it's just passed so that beam can call
>>>>as many timer->add_task()s as it needs, with the appropriate event types
>>>>in the appropriate sequence.
>>>
>>>As I indicated above, I think that the problem is that Beam should not
>>>know Timer exists. That is essentially navigating the relationship in
>>>the wrong direction. Nor should it know how to register events because
>>>that is a particular mechanism chosen for the sequence ordering of the
>>>overall solution. So if you change mechanism for defining sequencing,
>>>you have to tinker with the implementation of Beam and every other
>>>object triggered by that mechanism.
>>>
>>>If you encapsulate the sequencing rules and the mechanism in a dedicated
>>>object, you only have to change that object. In this case the calls to
>>>Timer.add_event could be encapsulated in that single object. The rules
>>>and policies of sequencing are orthogonal to the rules and policies of
>>>collaboration. Complexity is better managed by separating those
>>>concerns. That's why we have lots of GoF patterns for instantiating
>>>objects and relationships; it separates and encapsulates instantiation
>>>away from collaboration.
>>
>>
>> What of my suggestion at the top,
>>
>> timer.add_event(&chart, chart.event_list());?
>
>I don't care for it because Chart owns the event_list, which implies it
>knows how to construct it. That is a matter for the overall simulation
>solution flow of control. Keep Chart simple by limiting its
>responsibilities to comptuing sums and averages on demand. So I would
>much rather see the events, the sequencing of events, and the addressing
>of events defined outside both Timer and Chart. So I much prefer
>
>timer.add_event(&chart, COMPUTE_SUM, chart.sum_frequency);
>timer.add_event(&chart, COMPUTE_AVE, chart.ave_frequency);
>
>done outside both Timer and Chart. Note that Chart still owns the
>parametric data, but it is the developer that is providing the link
>between a state machine transition and that data. It may seem pretty
>much the same thing, but it is subtlely different enough to be much more
>robust.
But the two lines you've written are the only lines that can ever be used
to register chart and produce sensible results. You need both lines, not
just one. You need them in that order, they can't be reversed. You need
those event IDs, inserting different IDs is not an option. You need those
times, they cannot be altered or chart will produce non-sensible results.
There's one and only one right way to register it. And I think you're
spilling information into main() that shouldn't be there (the event IDs
and the frequencies which chart already knows), and presenting the
programmer with options (changing the sequence, changing the numbers,
omissions) that should never be used.
>
>For example, suppose the semantics of sum_frequency was based on the
>idea of computing sums every tick so the value of sum_frequency is 1.
>The ticks were 100 ms apart originally but now you need to change the
>unit tick to 10 ms apart for some reason unrelated to Chart. But you
>still want the sum calculated every 100 ms. Where do you make that
>change in each case?
>
>In your version you have to go into the implementation of Chart and
>tinker with the construction of event_list so the the value changes from
>1 to 10. Any time you tinker with Chart's implementation, there is a
>risk of breaking some other aspect of its functionality. In addition,
>you have to go into every object that constructed an equivalent
>event_list and make the same change.
>
>In my version I change:
>
>timer.add_event(&chart, COMPUTE_SUM, chart.sum_frequency*10);
>timer.add_event(&chart, COMPUTE_AVE, chart.ave_frequency*10);
>
>All the relevant events will be changed in the same way as the change in
>the construction of event_list. But it will be done _without touching
>any of the triggered objects' implementations_. In addition, it will be
>done in exactly the same way for each event in the one place where all
>of the events are defined. That just happens to be the one place where
>the overall flow of control is defined. IOW, only the implementation of
>the flow of control definition is changed -- which is exactly the way it
>should be for such a change.
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.
>
>[Why didn't I suggest going into Timer and multiplying the argument by
>10? That would literally be one place. The problem with that is that
>there was a reason for needing 10 ms ticks. Whoever needs to do
>something every 10 ms will be passing a 1 to add_event just like
>everyone else, but we don't want to change that particular case. We
>could make that particular argument 0.1 so that the multiplication would
>work right, but that would change the semantics of the argument for that
>call, which is not a good idea. One wants the data to be fully and
>consistently scalable.]
>
>>>I also argue that defining the events is a mechanism for defining the
>>>overall flow of control of the application execution. As such, that is
>>>at a higher level of abstraction that individual objects. That's why I
>>>like separately defining the Timer.add_event calls -- it can be done at
>>>the level of main() when initializing the application.
>>
>>
>> The sequence that the blocks are registered in, yes. But not the details
>> of the events for a single instantiated object. That should be hidden.
>
>Why? In an event-based simulation that mapping of event frequency is
>one of the most fundamental design decisions made. It should be up
>front and highly visible to anyone looking at the application. That's
>why in an OOA/D model in UML the Sequence Diagram exists. (The
>information is actually redundant if one uses object state machines and
>an abstract action language for method descriptions.) It allows one to
>see the overall flow of control of the application in one place at a
>high level of abstraction undistracted by low-level implementation
>details. As I already argued, placing the event registrations in one
>place is equivalent of providing that same high level of abstraction --
>the entire flow of control of simulation will be represented in that
>handful of adjacent statements.
>
>If you hide that flow of control in the implementations of multiple
>triggered objects there will be no way to view the overall sequencing
>except piecemeal by rooting through those implementations. Been there;
>done that and it isn't fun. I want to see the important stuff all in
>one place without distractions and with high visibility.
There's obviously more to it than I had in mind. But when an object can
only be registered in one correct way, I didn't see much point in allowing
incorrect ways. I thought that might introduce bugs.
>
>[Anecdotal data point. When I first converted to OO development and
>used a translation approach where object state machines are always used,
>I was astounded at how easy it was to debug and determine where
>requirements changes had to be made. (Our maintenance time dropped by
>nearly an order of magnitude.) IMO, a major part of that was being able
>to look at the model flow of control at a high level of abstraction. We
>found we could find bugs by inspection within a few minutes in 70-80% of
>the cases. Resorting to a debugger became a rarity.]
An order of magnitude is nothing to sneeze at.
>
>>>>>>So now, with each tick, Timer updates the time and then goes through its
>>>>>>list of schedules. For every one that's due it generates the indicated
>>>>>>type of event, passes it to the target, and updates last_time and
>>>>>>next_time. Then it goes through them again, and for every one that had
>>>>>>just been triggered it sends an UpdateEvent to update the state of every
>>>>>>triggered peice at the end of an iteration.
>>>>>
>>>>>The first two sentences I'm fine with; that's just routine event
>>>>>generation. I am not sure that the last sentence represents, though.
>>>>
>>>>
>>>>That last sentence is a logistics issue, I want to make sure everyone is
>>>>on the same tick. In a given iteration, or a tick, say the step from 10.0
>>>>seconds to 10.1 seconds, I don't want the controller to be updated to its
>>>>output at 10.1 seconds, then the target to receive the heat from the
>>>>controller at 10.1 seconds and the heat from the beam at 10.0 seconds.
>>>
>>>But that should come for free from the event ordering. The granularity
>>>is 0.1 <simulation> seconds for a tick. All the events for the 10.0
>>>tick are put on the event queue before the events for the 10.1 tick.
>>>Since there is a single event queue and it pops in FIFO order and it
>>>only processes one event at a time to completion, all of the actions for
>>>the 10.0 events will have executed before any of the actions for the
>>>10.1 events.
>>
>>
>> But as each 10.0 object executes, it's left in the 10.1 state. Any 10.0
>> object getting data from it after that point will be getting data of the
>> future, not the present.
>
>That's what I addressed in the section immediately below. What did I miss?
>
>>
>> That might be overcome by sequencing if the data only goes in one
>> direction, and if there are no loops. But the data can go in two
>> directions, e.g. from thermal mass 1 to mass 2 and from mass 2 to mass 1.
>> And there are loops.
>
>It's still just an array and each object just needs to know which index
>to use. A loop may make the array size > 2 so you may need a relative
>index for the loop, but its is still just about knowing which [t] to access.
>
>You could even make it independent of the event ordering by having each
>object have an attribute for where the current t for the tick is in the
>array. That would allow "blocks" to process in arbitrary order using
>relative indexing from that index. But I would expect the computation
>ordering to be driven by the order of migration of heat flow (source ->
>sink). That, in turn, would drive the definition of event sequencing
>anyway. IOW, the event sequencing and order of heat flow computations
>are going to be deliberately tied together anyway so I don't see a need
>for an infrastructure to make them independent.
An array of size 2? That means you still have to manage the array. I'm
not sure how that makes anything simpler.
Speaking of dependencies, if the array size would have to be increased
depending on the loops and interconnections, doesn't that introduce
unwanted dependencies? I had imagined defining any number of objects and
connecting them in any way that pleases you, changing parts at a whim, and
you just wouldn't have to worry about things like registering the
thermometer before the controller, or adjusting which index to use.
>
>>>Even the ordering for multiple events being generated on a single tick
>>>can be handled. (Note that eventList in my code example above is also
>>>essentially a FIFO queue, so one can ensure the right relative order for
>>>actions on the same tick through the order of the Timer.add_event calls.)
>>>
>>>
>>>>So I hold the new controller output in another variable, the target
>>>>will receive heat from the controller and beam both at 10.0 seconds and
>>>>the target will record T(10.1) seconds in a second variable, and the
>>>>thermometer will receive T(10.0) from the target, etc. After every block
>>>>has been managed, the new output values are transferred so they will be
>>>>seen in the next iteration.
>>>
>>>This seems like a different problem:
>>>
>>> +-----------+ +------------+
>>> 10.0 heat | T1 | T2 10.0 heat | T3 | T4
>>>---------->| Block1 |------------------->| Block2 |
>>> | | | |
>>> +-----------+ +------------+
>>>
>>>where you need to calculate new temperatures for each block on tick 10.0
>>>in separate operations. But if you do Block1 first, the T2 computed for
>>>10.0 will not the the right one to use to compute a new T3 for Block2.
>>>IOW, when computing 10.0 for Block2 one needs to use 9.9 value for
>>>Block1 because that is the value that is there at the computation of
>>>10.0 for Block2. Is this correct?
>>>
>>>If so, this sort of lead/lag thing is really a matter of concern for the
>>>individual blocks. One needs a data-based lag mechanism so that the
>>>computation for Block2 accesses the output of t-1 rather than the output
>>>of t. As you suggest, dual variables is probably easiest to do that.
>>>The trick is to synchronize.
>>>
>>>If you "walk" the heat flows linearly through the blocks using event
>>>sequencing, then you can probably just do the migration of values
>>>(current [t] -> [t-1]) before the computation of the new [t] value.
>
>
>*************
>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
>
>
>
--
"When the fool walks through the street, in his lack of understanding he
calls everything foolish." -- Ecclesiastes 10:3, New American Bible
.
- Follow-Ups:
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- 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?
- Prev by Date: ANN: Boost review of Singleton library (C++)
- Next by Date: Re: When and where to use Visitor Pattern?
- Previous by thread: Re: Lahman, how ya doing?
- Next by thread: Re: Lahman, how ya doing?
- Index(es):
Relevant Pages
|