Seeking JavaScript Help
Published 15 years, 11 months pastEven though it turned out that there is no simple solution for the math problem I posted, I learned a fair amount from the fantastic responses—thanks, everyone!—and eventually came up with a solution that worked for me. (I’d like to say it was one of the iterative approaches posted, but none of them worked for me. In the end, I brute-forced it.) I’m hoping for a different outcome with my next question, which is about JavaScript.
Consider the following structure, which is a much-edited-down version of part of the HYDEsim code:
function Detonation(name,lat,lon,yield) { var scale = Math.pow(yield,1/3); var gz = new GLatLng(lat,lon); this.name = name; this.lon = lon; this.lat = lat; this.gz = gz; this.yield = yield; this.overpressure = { p30 : 0.108 * scale, p15 : 0.153 * scale, p10 : 0.185 * scale, p5 : 0.281 * scale, p1 : 0.725 * scale }; this.psi30 = { radius: 0.108 * scale, overlay : { points: makePoints(this.gz,0.108 * scale) } }; this.psi15 = { radius: 0.153 * scale, overlay : { points: makePoints(this.gz, 0.153 * scale) } }; this.therm20 = { radius: thermDist(20,this.yield,0.33,conditions.visibility), overlay : { points: makePoints( this.gz, thermDist(20,this.yield,0.33,conditions.visibility) ) }; // ...and so on... }
There are two things I’ve tried and failed to do. And tried, and tried, and tried; and failed, and failed, and failed.
- Eliminate the redundant calculations of radii. Note how I define a radius property in each case? And then have to not use it when I create the overlay? It seems like there must be a way to just define the value once for each subsection and then use it as many times as needed within that context. How?
- How do I make it so that all those properties and overlays and such automatically recalculate any time one of the “upper-level” terms changes? As in, once I’ve created a new Detonation object
det
, how can I set things up so declaringdet.yield = 250;
will trigger recalculation of all the other pieces of the object? At present, I’m just blowing away the existingdet
and creating a whole new one, which just seems clumsy and silly. I have to believe there’s a better way. I just can’t find it.
Please note: tossing off comments like “oh, just instantiate a mixin constructor with an extra closure” will be of no help at all, because I don’t understand what those terms mean. Hell, I’m not even sure I used the words “object” and “property” correctly in my explanation above. Similarly, pointing me to tutorials that use those terms liberally is unlikely to be of help, since the text will just confuse me. Sample code (whether posted or in a tutorial) will help a great deal, because it will give me something to poke and prod and dissect. That’s how I’ve always learned to program. Actually, it’s how I’ve always learned anything.
As well, I’m absolutely willing to believe that there are much, much better ways to structure the object, but right now I really need to learn how those two things are accomplished in the context of what I already have. Once I get familiar with those and finish up some other work, I can start thinking about more fundamental overhauls of the code (which needs it, but not now).
I really appreciate any concrete help you can give me.
Addendum: if you leave code in a comment, please just wrap it in a code
element and use whatever indentation you like. The indentation won’t show up when the post goes up, but I’ll go in after and wrap the code
in a pre
and then everything will be fine. Sorry to those who’ve already gone to the effort of posting with indents or nbsp
entities to try to preserve indentation! As soon as I can dig up the right preference panel or plugin to allow pre
in comments, I’ll do that, but for now I’ll manually edit in the needed pre
s as comments are added. THanks, and again, apologies to those who posted before I made this clear!
Comments (21)
Looks like you might need to make use of the prototype object. Crude example:
As Pete B recommends, define a setter instead of a way to directly access the variable. setYeld should set the value for yeld and should also recompute things that depend on it (and if necessary assign those values using their own setters, but be careful not to fall into the trap of infinite recursive function calls) and announce the change to it’s children so they may recalculate whatever they need.
Q/3.07FY=[(1/D^2)]+[1.4(D/V)e^-2(D/V)]/D^2
I think you can solve this equation by integration..substitution method (been long time since calculus). Maybe even a double integration but don’t think its that complicated of an equation. I would say integration by substitution method. Sorry I forgot all that good calculus stuff. By the form of the equation looks like some sort of financial equation to calculate rate over time. Good Luck.
I would think what your doing for this.overpressure would work for radius, example:
if that code populates the same results as the old way, then I would create something like:
Hi Eric
From Pete B’s post (originally in a <blockquote>, but that played havoc with the formatting):
// Prototype methods and properties inherited by all instances,
Detonation.prototype = {
defaultValue: 3.142,
setYield: function () {
this.yield = yield;
}
}
var det = new Detonation(1, 2, 3, 4);
det.setYield(21);
…I believe (but might be wrong; my JS is rusty) that the above won’t quite work, as you still need to have a parameter, like so:
Detonation.prototype = {
setYield: function(yield_) {
this.yield_ = yield_
}
}
Also, note that
yield
is a reserved word in JavaScript 1.7, which probably won’t bite you now, but might later (it has to be programmatically enabled in Firefox 2.0+, but it’s worth bearing in mind). I suggest that you name it something likeyield_
ordet_yield
instead to avoid any problems.I’m afraid that I don’t understand where/how you’re using the radius values, as your posted code shows them being defined, but — so far as I can see — never used. I’d need to know more about how they’re used to be able to suggest ways to refactor them (=reorganise/restructure, in coding jargon).
Good luck, and I’ll check back to see how you go.
you can also specify a general setter for all “this.” variables like so:
this.setVals = function(params){
for(var p in params){
this[p] = params[p]
}
}
obviously then you can set any number of vars that you’d like, and add conditional code to be executed if a specific param is set (like yield).
– Andrew
This may not work… so you’ve got a template there for creating a Detonation object. But it’s important in what context you create that object, so this may not be right.
1) If you’re essentially calculating the radius measurements when you’re creating the this.overpressure object, you should be able to refer specifically to the “this.overpressure.p30”. So in this.psi30, you can simply write “radius: this.overpressure.p30”. You can do the same 2 lines down, specifying “points: makePoints(this.gz, this.overpressure.p30)”. As you say, if you’ve referred to it once, you don’t need to recalculate.
2) For this, you can’t really restructure your existing code to do it, you’ll need to change the structure of the Detonation obj. Here’s why…. As of the moment, when you create your object, you’re doing all of these calculations and wiring the results of the calculations into your Detonation object. To dynamically recalculate (like if you were to set a new yield), you’d have to create a new function that does all of the (needed) recalculations again. At this point, you’d then want to rewrite your code so when you create a new object, you also call this new function (so you don’t have 2 pieces of code that do the same thing, in multiple places.. it’s easier to maintain, etc.).
However, a better solution (though you may not be able to… I’m only looking at an abbreviated version), would be to simply store the 4 or so parameters you pass to the Detonation object (name, lat, lon, yield). You then write functions that automatically create these values for you only when you ask for them. That way, you can set a new yield, and not waste any time recalculating then… you could wait to recalculate the values only when you need to.
The ideal solution is probably somewhere inbetween…. where you store the 4 parameters, as well creating a function to re-calculate the radius measurements each time… since you use those so often, it’d be more efficient (as you said in #1) to calculate them just once.
I know you said you’d like to look at code, and this is just a high-level overview, but let us know what kinda changes you can make, and we can try to get to code.
If this problem doesn”t get solved here, you could try posting it on Stack Overflow.
Well, I’d instantiate a binding function, thereby avoiding the double recursion closure bug that you’ve got because you failed to globally initialise the object prototype.
Or I’d hit it with a hammer and curse.
Well, I”d instantiate a binding function, thereby avoiding the double recursion closure bug that you”ve got because you failed to globally initialise the object prototype.
I assume this is exactly the kind of comment Eric loves to hear (no offense Bruce i appreciate your comment nonetheless). I am in the same shoes as Eric and appreciate Eric’s calls for help very much. So much to learn here if you are able to understand the comments. Great!
Along the lines of what Eddie Welker suggested, instead of setting all the values when you instantiate the Detonation object, you calculate individual values as needed. This basically amounts to wrapping functions around everything. This code is untested, but should get you close:
This also takes care of issue #1 in that you set your radii calculations once and refer to them as needed.
Right there with you, Eric. Any theoretical discussion of programming quickly turns into the last line of Dr. Seuss’s Fox In Socks for me. (From faulty memory: when the tweedle beetles battle in a bottle with their paddles, and the bottle’s on a poodle and the poodle’s eating noodles, THIS is what we call a muddle puddle tweedle beetle poodle noodle bottle paddle battle. Or something like that.)
Not really knowing the context of this code makes things a little tougher, but I would probably do something along these lines:
Sorry if the formatting didn’t come through, I tried a few tags and combinations and couldn’t get it to look right in the preview.
Sorry for the underlines but it just does not format correctly otherwise. Argh!
Anyway, the idea here is you use inner functions to calculate the main bits. When you want to access stuff, it’s like:
This way you only define your PSI values and other factors once.
Anyway, the code is:
Here’s a version of the code with underlines for indents:
function Detonation(name, lat, lon, yield) {
____this.name = name;
____this.lat = lat;
____this.lon = lon;
____this.gz = new GLatLng(lat, lon);
____this.yield = yield;
____recalculate(); // cheaper than doing setThis(); setThat(); all of which will call recalculate()
}
Detonation.prototype.setYield = function (yield) {
____this.yield = yield;
____recalculate();
};
Detonation.prototype.recalculate = function () {
____function makeDestruction() {
________var result = {};
________for (var i = 0; i < arguments.length; i++) {
____________var radius = arguments[i].factor * scale;
____________result[arguments[i].psi] = {
________________radius: radius,
________________overlay: { points: makePoints(this.gz, radius) }
____________};
________}
________return result;
____}
____function makeTemperature() {
________var result = {};
________for (var i = 0; i < arguments.length; i++) {
____________var radius = thermDist(arguments[i].celsius, this.yield, arguments[i].factor, conditions.visibility);
____________result[arguments[i].celsius] = {
________________radius: radius,
________________overlay: { points: makePoints(this.gz, radius) }
____________}
________}
________return result;
____}
____var scale = Math.pow(yield, 1/3);
____this.destructionAtPsi = makeDestruction.call(this, // need ".call(this," to preserve "this" in inner function
________{psi: 30, factor: 0.108},
________{psi: 15, factor: 0.153},
________{psi: 10, factor: 0.185},
________{psi: 5, factor: 0.281},
________{psi: 1, factor: 0.725}
____);
____this.temperature = makeTemperature.call(this,
________{celsius: 20, factor: 0.33},
________...
____);
};
Can we please get a markup engine that DOESN’T SCREW UP CODE IN CODE TAGS AAAAAAAAAAAARGH.
Sorry, just had to vent.
“Even though it turned out that there is no simple solution for the math problem I posted….”
But there is a simple solution: the value of D that satisfies the equation. What can be simpler than that?
Maybe a few comments at this point will make such problems easier in the future.
There is a simple numerical solution but no ALGEBRAIC solution. And your brute-force method is exactly how such equations are solved, that is to say, by numerical approximation. Now there are some mathematical tricks to improve the efficiency of arriving at a solution. Two simple methods come to mind.
1. g(D) = Q-f(D). At what value of D does g(D) = 0? Trial and error. (Extra points if you can efficiently estimate the next guess. The pros use the slope g'(D) to improve their aim. And if you can’t calculate the slope algebraically, you can estimate it numerically.)
2. D approximately = f(D). At what point does D=f(D)? This is a helpful trick that you can use sometimes. If you know the approximate range of values of the various parameters, you may be able to rewrite the equation in a way such that f(D) does not depend much on D. Then you can iterate and get the value in a hurry.
In your previous example, if D/V << 1 and both are positive, then as a first approximation maybe you can ignore the exponential. Then D^2=3.07FY(1+1.4ED/V)/Q, where E is that nasty exponential. You can solve that for D algebraically to get your approximation. Make a first guess, find the value of E, plug all the numbers into the equation to get your next guess. Plug that value back into E to get the next guess. Repeat until satisfied. If D/V is not sufficiently small, it might not work, as you will find out very quickly.
In both cases, you have to watch out for the special cases Q=0 and V=0. (The function is undefined at those values!) If you are iterating and encounter those values (or cross those values), then obviously there is trouble.
Oops, I mean
this.recalculate()
, of course. Not justrecalculate()
.Here’s my kick at the can. I’ve only rewritten what it given in the example, I assume that more can be added later.:
Gaak! nbsp to get the indenting.
First of all, Detination uses a variation on Crockfords power constructor so don’t call it with new (but it should work regardless). Second the object it creates has two members, psi and therm. These each have a number of numerically identified members each of which contain the radius and points. You would do the following:
I’ll leave it up to the reader to add the other useful bits in (like the name, yield, etc).
This has a number of advantages. First of all the global scope is kept pristine. Second, those things that are fixed (the different PSI levels, for example) and the functions that use them are only created once. Third its easy to add levels, simply add members to the OP_MULT and TD_MULT constants.
Wow! You all are 125% awesome, with a side order of fantastic. When I finally decide to give HYDEsim a complete overhaul, I know who to e-mail. Thank you!
(Also, I think
pre
is allowed in comments even if it isn’t listed as accepted. Someone try it and we’ll see. If not, sorry! I’ll get it on the list.)Piggy-backing on comments and code from Micheal Bester and Eddie Welker:
Detonation1 makes small changes internal to Detonation, but the code which uses it (det.whatever) should remain unchanged.
However, it only addresses your first of your requests:
Detonation2 causes somes changes externally (ie,
det.yield = 20
becomesdet.yield(20)
and similar)However, you do get 1) automatic recalculation and 2) only calculated when needed.
You said you wanted something to poke at, so I went a bit heavy on the comments.
Just reading the example from Adam van den Hoven and it’s along the lines of what I was going to suggest. Even after my refactoring, it’s clear there are still patterns in the properties and function names (and their calculations) that could surely be reduced to a more abstract or basic formula.
Anyway, thanks for the mental exercise and, of course, your CSS and standards work.
P.S. “pre” tag doesn’t work. Sorry, but I just can’t go back and replace spaces w/ nbsb;
Here’s an alternative using the getters and setters syntax supported by Firefox, Opera and Safari. If support for IE is needed, than this won’t be suitable. (IE8 has added support for getters and setters recently, but unfortunately, it seems the syntax defined by ECMAScript 3.1 and implemented by IE is not entirely compatible with that used by the others.)
There are various improvements that could be made to optimise this, but it’s difficult to know what can be changed without known what it’s for or how it’s meant to be used. I’m not even sure some of the changes I made to this version are suitable for what you need. But I’m sure you can use this to get you started anyway.