Skip to content

Instantly share code, notes, and snippets.

@rctay
Last active December 21, 2015 05:49
Show Gist options
  • Save rctay/6260157 to your computer and use it in GitHub Desktop.
Save rctay/6260157 to your computer and use it in GitHub Desktop.
math for physics/physical quantities (viz. values with units), in "worksheets" http://plnkr.co/edit/CHccBbCGltDI2e3U1kVY?p=preview
// Code goes here
angular.module('aModule', [])
.service('quant_alg', function() {
return quant_alg;
})
.controller('ControllerA', function($scope, quant_alg) {
var qa = quant_alg;
angular.extend($scope, qa);
})
.directive('value', function(quant_alg) {
return {
restrict: 'E',
replace: true,
scope: {
readOnly: '@',
property: '=',
value: '&',
unitsExpr: '&'
},
link: function(scope, iElement, iAttrs, controller) {
if (iAttrs.readOnly === "")
return;
var units;
if (iAttrs.unitsExpr)
units = scope.unitsExpr();
else {
units = iAttrs.units;
}
scope.property = quant_alg.value(iAttrs.display || iAttrs.property, scope.value(), units);
},
template: '<div class="value">'
+ '<div class="vvalue" ng-switch on="readOnly">'
+ '<span ng-switch-when="" ng-bind="property.value"></span>'
+ '<input ng-switch-default type="text" ng-model="property.value">'
+ '</div>'
+ '<span class="units">{{ property.units.toString() }}</span>'
+ '</div>'
};
})
.directive('comp', function() {
return {
restrict: 'E',
replace: true,
scope: {
expr: '&',
property: '=set'
},
link: function(scope, iElement, iAttrs, controller) {
var shallSet = !!iAttrs.set;
scope.$watch("expr()", function(ret, old) {
if (!ret.length)
return;
scope.lines = ret[0];
if (shallSet) {
var v = ret[1];
v.name = iAttrs.display || iAttrs.set;
scope.property = v;
}
}, true);
},
template: '<div">'
+ '<span ng-repeat="line in lines">'
+ '{{line}}'
+ '<br ng-if="!last"/>'
+ '</span>'
+ '</div>'
};
});
<!DOCTYPE html>
<html ng-app="aModule">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"
data-require="angular.js@*"
data-semver="1.1.5"></script>
<script src="//cdn.jsdelivr.net/underscorejs/1.5.1/underscore-min.js"
data-require="underscore.js@*"
data-semver="1.5.1"></script>
<link rel="stylesheet" href="style.css" />
<script src="quant_alg.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="ControllerA">
<p>rain falls at a constant rate of <value
property="rain_rate" display="rain rate"
value="5"
units-expr="units('drops').over('m', 2).over('s')"></value>
</p>
<p>windscreen has width <value
property="width"
value="1"
units="m">
</value> by breadth <value
property="breadth"
value="1"
units="m">
</value>, ie. an area of <comp
set="area"
expr="mul(width, breadth)"></comp>
</p>
<p>debug: area is now <value read-only property="area"></value></p>
<p>consider it over a duration of <value
property="duration"
value="100"
units="s"></value>
</p>
<p>when stationary, it collects <comp
expr="mul(rain_rate, area, duration)">
</comp>
</p>
<p>now, consider when the object moves at <value
property="speed"
value="0.1"
units-expr="units('m').over('s')">
</value> - that is, in 1 s, it covers a distance of <comp
set="distance"
expr="mul(speed, value(1, 's'))"></comp></p>
<p>let us assume that the object transits <em>instantaneously</em> between discrete positions/steps between the start and end point (spending an equal amount of time at each step), instead of a smooth, continuous motion.<br/>
(base case steps=1: object spends duration at start point, then transits to end point)
</p>
<p>let there be <value
property="steps"
value="1"
units="steps">
</value>. at each step, the object occupies the position for <comp
set="duration_per_step"
display="duration per step"
expr="div(duration, steps)">
</comp>, and receives <comp
set="drops_per_step"
display="drops per step"
expr="mul(rain_rate, area, duration_per_step)">
</comp></p>
<p>(try increasing the number of steps to a large number <button ng-click="steps.value = 1e6">eg. 1e6 (1 million)</button>, this approaches a smooth motion)</p>
<p>therefore, in total, a moving object receives <comp
expr="mul(drops_per_step, steps)">
</comp></p>
</div>
</body>
</html>
/**
* Provides an algebra for handling physical quantities.
**/
quant_alg = (function() {
function Value(name, val, dim) {
if (arguments.length === 2) {
dim = val;
val = name;
name = undefined;
}
if (typeof dim === "string")
dim = units(dim);
this.name = name;
this.value = val;
this.units = dim;
}
function value(name, val, dim) {
if (arguments.length === 2)
return new Value(name, val);
return new Value(name, val, dim);
}
Value.prototype.toString = function() {
return this.value.toString() + ' ' + this.units.toString();
};
function Units(init) {
this.val = init || {};
}
Units.prototype.toString = function(sep) {
return join(_.pairs(this.val), sep, function(pair) {
var desc = pair[0], pow = pair[1];
if (pow === 0)
return '';
var str = desc.toString();
if (pow !== 1)
str += '^' + pow.toString();
return str;
});
};
Units.prototype.merge = function(other) {
var a = this.val, b = other.val;
var ret = _.extend({}, a, b);
_.each(
// intersection
_.reduce(a, function(m, v, k) {
if (b.hasOwnProperty(k))
m.push(k);
return m;
}, []),
function(desc) {
ret[desc] += a[desc];
});
return new Units(ret);
};
function addUnit(top, bottom, desc, pow) {
if (desc) {
if (pow === undefined)
pow = 1;
if (pow === 0)
return;
var receiver;
if (pow > 0)
receiver = top.val;
else {
receiver = bottom.val;
pow *= -1;
}
if (receiver.hasOwnProperty(desc))
receiver[desc] += pow;
else
receiver[desc] = pow;
}
}
function RationalUnits(desc, pow) {
this.top = new Units();
this.bottom = new Units();
addUnit(this.top, this.bottom, desc, pow);
}
function units(desc, pow) {
return new RationalUnits(desc, pow);
}
RationalUnits.prototype.over = function(desc, pow) {
addUnit(this.bottom, this.top, desc, pow);
return this;
};
RationalUnits.prototype.toString = function() {
var str1 = this.top.toString(' . ');
var str2 = this.bottom.toString(' / ');
if (str2.length)
str1 = str1 + ' / ' + str2;
return str1;
};
function join(values, sep, fn) {
if (values.length < 1)
return '';
return _.reduce(values.slice(1), function(m, v) {
return m + sep + fn(v);
}, fn(values[0]));
}
RationalUnits.prototype.cancel = function() {
var top = this.top.val,
bottom = this.bottom.val;
_.each(top, function(pow, desc) {
if (!bottom.hasOwnProperty(desc))
return;
var pow2 = bottom[desc];
if (pow2 === pow) {
delete top[desc];
delete bottom[desc];
} else if (pow2 < pow) {
top[desc] -= pow2;
delete bottom[desc];
} else {
bottom[desc] -= pow;
delete top[desc];
}
});
};
function mul(_) {
return reduce(
Array.prototype.slice.call(arguments),
' * ', // disp
function(a, b) { return a * b }, // valueOp
function(a, b) {
var u = units();
u.top = a.top.merge(b.top);
u.bottom = a.bottom.merge(b.bottom);
u.cancel();
return u;
}
);
}
function div(_) {
return reduce(
Array.prototype.slice.call(arguments),
' / ', // disp
function(a, b) { return a / b }, // valueOp
function(a, b) {
var u = units();
u.top = a.top.merge(b.bottom);
u.bottom = a.bottom.merge(b.top);
u.cancel();
return u;
}
);
}
function reduce(values, disp, valueOp, unitsOp) {
var lines = [];
if (values.length < 1 || !_.every(values))
return lines;
lines.push(join(values, disp, function(v) {
return v.name || v.toString();
}));
function addLine(line) {
lines.push('= ' + line);
return line;
}
var a = values[0], v;
for (var i = 1, b; i < values.length; i++) {
addLine(join(values.slice(i-1), disp, function(v) {
return v.toString();
}));
b = values[i];
v = value(valueOp(a.value, b.value), unitsOp(a.units, b.units));
values[i] = v;
a = v;
}
addLine(v.toString());
return [lines, v];
}
return {
value: value,
units: units,
reduce: reduce,
mul: mul,
div: div
};
})();
/* Styles go here */
body {
font: 14px helvetica, arial, clean, sans-serif;
}
.value, .vvalue {
display: inline;
}
.value > .vvalue > input {
width: 2em;
}
.value > .vvalue {
margin: 0 0.5em;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment