Re: Lahman, how ya doing?
- From: glhansen@xxxxxxxxxxxxxxxxxxxxx (Gregory L. Hansen)
- Date: Fri, 6 May 2005 15:15:17 +0000 (UTC)
In article <tDCee.18502$yd1.17138@trndny01>,
H. S. Lahman <hsl@xxxxxxxxxxxxxxxxx> wrote:
>Responding to Hansen...
>
>>>>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.
>
>OK. But that programmer needs to know what the object interface is. If
>one is using an object state machine, then the events /are/ the object
>interface. So one has the synchronous interface:
>
>Thermometer.takeSample()
>Thermometer.computeStatistics()
>
>vs. the asynchronous interface:
>
>E1:takeSample
>E2:computeStatistics
>
>Similarly, the programmer is going to have to know when to invoke those
>behaviors _in the course of the problem solution the programmer is
>providing_. One of my points here is that it is a lot easier and more
>robust to define when to invoke those behaviors via
>
>Timer.add_event (&thermometer, E1, 1);
>Timer.add_event (&thermometer, E2, 60);
>
>than it is to provide an infrastructure for calling
>Thermometer.computeStatistics() after exactly 60 calls to
>Thermometer.takeSample(), especially if the context where one decides it
>is time to take a sample is different than the context that needs the
>statistics.
You do understand that I was proposing for Thermometer to register itself
with Timer just once, during the initialization? That would give Timer
the scheduling information it needs to generate the right events at the
right time and in the right sequence. It's just an initialization, not an
on-going process.
>
>[Note that using add_event to define timing is just one aspect of the
>solution design. Part of that design lies in Timer's responsibilities
>and part in Thermometer's state machine. So the combination of elements
>provides the overall solution. The benefit of this particular
>combination lies in simplification of Timer and Thermometer while
>reducing coupling of their implementations. But it is the way
>encapsulation, implementation hiding, peer-to-peer collaboration, and
>event-based processing play together that achieve that benefit.]
>
>>>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.
>
>That's the hardware that provides the surrogate value for the
>temperature. There will be software that polls the hardware, converts
>the voltage to a temperature value, stores it as a sample, and,
>subsequently, operates on multiple samples to compute an average. If
>one chooses an event-based solution, then the interface to those two
>responsibilities will be events.
>
>But it is not the Thermometer's job to decide when those
>responsibilities should be invoked in a particular problem solution.
>That's the programmer's job in connecting the dots among
>responsibilities of collaborating objects.
>
>>>>>>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?
>
>Once the application is initialized (presumably in main() or equivalent
>for the language) a "seed" event is put on the queue and the Queue is
>started. It pops events until the queue is empty, in which case the
>application is done. Note that the state machine actions put events on
>the queue and the pop is not complete until the action completes, so the
>queue will not empty out until all the processing is completed.
>
>Things are more complicated in a truly asynchronous system where the
>queue may have to wait for an external event. (Or for events generated
>by a real- or scaled-time timer.) But then the push just needs to
>restart the queue operation if the event count is exactly 1 after the
>push. Since you are using simulation ticks rather than scaling, this
>doesn't matter because you just just need an event to Timer to trigger
>tick() when all the tick's processing is completed. In your case there
>are a couple of easy options: (1) have Timer put a self-directed event
>on the queue as the last event or (2) have the event queue manager call
>tick() when it is empty. (Tick() can generate an event for a graceful
>exit if, say, the maximum simulation ticks have executed to provide an
>exit.)
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.
I appreciate the help, though.
>
>>>>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.
>
>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.
>
>>>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.
>
>But why would there be any difference in timing from one tick to the
>next? That is, why wouldn't dt be exactly the same on each iteration?
>I would expect the real-time controller to be designed to ensure the
>hardware was always sampled at the same dt. That's kind of why R-T/E is
>challenging. B-)
>
>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.
>
>>>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.
>
>Assuming there is a need to push scheduled events to the next tick on
>certain "witching" ticks, then I agree Timer is the place to do that.
>[One could argue it goes in the event queue manager. That is, the Timer
>deals with /scheduled/ events while the queue manager deals with
>adjustments. I don't like that for a number of reasons. Among other
>things, unless the rule is very simple the queue manager would have to
>make sure the deferred event was in the right order on the next tick,
>which would require a knowledge of the schedule.]
>
>But that addresses a requirement we haven't dealt with before.
>
>>>>
>>>>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.
>
>Exactly my point! Those two lines provide an elegant solution to the
>entire simulation sequencing problem all in one place. Sure they have
>to be carefully crafted, but the solution flow of control /always/ has
>to be carefully crafted. You can do that crafting in one place with two
>lines or you can sprinkle it through several lines in multiple object
>implementations.
Okay, so there's no misunderstanding there.
I was coming from the perspective of hiding unnecessary information, the
same sort of idea as the private: region of a class, or using local
variables in a function. But you're saying it's important to make the
lines of communication clear and visible, even if technically they don't
have to be. The two add_event() lines should more or less just be
considered as part of the parameter list.
>
>> 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.
>
>I don't think it is necessary because all the registration statements
>are doing is parameterizing Timer as part of the overall application
>initialization and overall initialization is exactly what one does in
>main(). That is, they only get executed once during startup, just like
>assorted constructor invocations.
>
>Note that the event-based solution is very heavy on statically defined
>rules rather than executable statements. That is all the registration
>is doing is defining that static structure at startup. The actual
>behavior is still in Timer and the other objects and they still
>collaborate with each other on a peer-to-peer basis to actually execute
>the simulation.
>
>The event IDs are the public interface to the objects. They have to be
>visible to access the object responsibilities.
>
>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.
>
>>>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.
>
>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?
>
>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.
I started out putting things in ticks, but then I thought the tick size
shouldn't matter. The second is the natural unit for all the peices-- I
move the shutter every 30 minutes, not (let me get a calculator) 108000
ticks; the integral time constant is 100 seconds, not 6000 ticks. And I
wanted to be able to change the time resolution to see how that changes
the performance, without having to change the timing for all the peices of
the system.
>
>[Anecdotal data point. In nearly two decades of programming complex
>hardware drivers I saw less than a dozen floating point calculations and
>those were far removed from the hardware.]
>
>>>>>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.
>
>In an event-based application one does not need to register objects.
>The events are just messages passed on a peer-to-peer basis. What is
>being registered here are events and it is being done is a special
>manner that is fundamental to the specific solution design. There are
>many ways to solve the sequencing problem that will yield correct
>solution, including yours. The choice is about maintainability rather
>than correctness (if one is doing OO development).
>
>>>[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.
>
>True. It is anecdotal because variables were things like skill levels,
>methodology, shop culture, etc. were uncontrolled. OTOH, it was
>persuasive because we were a process oriented shop and we ran controlled
>experiments. (How many shops rewrite a 1 MLOC application not once but
>twice to evaluate a new methodology?)
I can respect controlled experiments in a shop.
>
>>>>>>>>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.
>
>If all one has to worry about is using the [t-1] values from the last
>tick versus the <recently computed> [t] values from the current tick,
>all one needs are old/new values. Loops potentially add an iteration
>count to the number of values to be tracked.
>
>> 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.
>
>The array is probably hidden entirely in the implementation of the
>object. The interface is something like getCurrentTemperature() and
>getPreviousTemperature(). Alternatively on could use
>getTemperature(UNCOMPUTED) vs. getTemperature(COMPUTED), depending on
>whether the object accessed is before or after the object being computed
>in the computation order. So in...
>
>>>>> +-----------+ +------------+
>>>>>10.0 heat | T1 | T2 10.0 heat | T3 | T4
>>>>>---------->| Block1 |------------------->| Block2 |
>>>>> | | | |
>>>>> +-----------+ +------------+
>
>if one is computing Block2, one asks asks for getPreviousTemperature
>from Block1 to get T2 but if one is computing Block1, one asks for
>getCurrentTemperature to get T3 from BLock2.
I don't like having to decide which temperature to get based on the order
of processing. E.g. if I had a single loop with the target connected to a
constant temperature sink, then decided to simulate the system more
closely by connecting the target through a thermal link to another
controlled block. Or just decide to throw in a low-pass filter, or a
second power supply to solve a problem with dynamic range while not
sacrificing resolution near an error of zero. I wouldn't want to have to
reevaluate which temperatures the peices should get. The code might be
easy maintain, but the implementation of the problem to be simulated
isn't.
--
"Let us learn to dream, gentlemen, then perhaps we shall find the
truth... But let us beware of publishing our dreams before they have been
put to the proof by the waking understanding." -- Friedrich August Kekulé
.
- 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: Re: can classes communicate via message passing?
- 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
|