Published
Edited
May 8, 2019
Insert cell
md`# Node math`
Insert cell
import {math} from '@ajbouh/math-js-with-unitsum'
Insert cell
import {toposort} from '@ajbouh/toposort'
Insert cell
S = require('s-js')
Insert cell
class SObject {
static fromDataEntries(dataEntries, writeable) {
let signals = {}
dataEntries.forEach(
([k, v]) => signals[k] = S.data(v)
)
return new SObject(signals, writeable)
}

static fromData(data, writeable) {
return SObject.fromDataEntries(Object.entries(data), writeable)
}
constructor(signals, writeable) {
const proxy = new Proxy(signals, {
set: (target, prop, value, receiver) => {
let signal = target[prop]
if (signal !== undefined) {
if (writeable) {
signal(value)
return true
}

console.log("tried to update", prop, "to", value, "from", typeof signal === 'function' ? signal() : signal)
return false
}
return false
},
get: (target, prop, receiver) => {
if (prop === 'constructor') { return Object; }
if (prop === Symbol.toStringTag) { return undefined; }
let signal = target[prop]
if (signal !== undefined) {
return signal()
}
},
})

this.proxy = proxy
this.signals = signals
}
merge(...others) {
return new SObject(Object.assign({}, this.signals, ...others.map(o => o.signals)))
}

map(fn) {
let dsts = {}
Object.keys(this.signals).forEach(
key => {
let src = this.signals[key]
let data = fn(src(), key)
let dst = S.data(data)
dsts[key] = dst
S.on(src, _ => dst(fn(src(), key)), undefined, true)
}
)

return new SObject(dsts, false)
}
}

Insert cell
function NewArrayProxy(arr) {
// console.log("NewArrayProxy", arr)
return new Proxy(arr, {
get: (target, prop) => {
if (prop === 'constructor') { return Object }
if (prop === 'length') { return target.length }
if (prop === Symbol.toStringTag) { return undefined }
if (prop === 'toString') { return target.toString }
console.log("array", typeof prop, prop)
if (typeof prop === "string") {
let c = prop.charCodeAt(0)
if (c >= 48 && c <= 57) {
return target[prop]
}
}
let anyUndefined = false
let r = target.map(e => {
let v = e[prop]
if (v === undefined) {
anyUndefined = true
}
return v
})
console.log(target, `.${prop}`, r)
// r[NewArrayProxySymbol] = true
return anyUndefined ? undefined : r
}
})
}
Insert cell
// NewArrayProxySymbol = Symbol()
Insert cell
class MathExpr {
static compileBlock(code) {
let node = math.parse(code)
console.log("fromCode", code, node)
let nodes;
switch (node.type) {
case "BlockNode":
nodes = node.blocks.map(n => n.node)
break;
default:
nodes = [node]
}

return nodes.filter(n => n.type === "AssignmentNode")
.map(an => new MathExpr(an.object.name, an.value))
}
static compile(name, source) {
return new MathExpr(name, math.parse(source))
}

constructor(name, exprRoot) {
let depSet = new Set(
exprRoot.filter((n, path, z) => {
// console.log(n, path, z)
if (!n.isSymbolNode) {
return false
}

if (math[n.name] !== undefined) {
return false
}

return !math.type.Unit.isValuelessUnit(n.name)
}).map(n => n.name)
)

this.name = name
this.deps = Array.from(depSet)
this.root = exprRoot
}
toString() {
return this.root.toString()
}
eval(scope) {
return this.root.eval(scope)
}
}

Insert cell
class Frame {
constructor(name, exprs, initialScopeValues) {
let exprIndex = {}
exprs.forEach(expr => exprIndex[expr.name] = expr)

this.name = name
this.exprs = exprs
this.exprOrder = toposort(exprs, expr => expr.deps.map(depName => exprIndex[depName]).filter(_ => _))
let scope = Object.assign({}, initialScopeValues)
this.exprOrder.forEach(expr => {
S(() => {
let r = undefined
let shouldEval = expr.deps.every(depName => scope[depName] !== undefined)
if (shouldEval) {
// console.log("eval expr", key, expr, scope)
console.log(`[${name}.${expr.name}] eval`, expr, "in", scope)
r = expr.eval(scope)
// console.log("eval res", r)
}
scope[expr.name] = r
})
})
this.scope = SObject.fromData(scope).proxy
}
cloneAndOverride(name, withExprs, withScope) {
let exprs = new Map(withExprs.map(expr => [expr.name, expr]))
this.exprs.forEach(expr => exprs.has(expr.name) || exprs.set(expr.name, expr))

let scope = Object.assign({}, withScope)
Object.keys(this.scope).forEach(
k => {
if (scope[k] !== undefined) { return }
// This is subtly wrong. Also need to avoid copying values are invalidated by an override...
scope[k] = this.scope[k]
}
)

return new Frame(name, Array.from(exprs.values()), scope)
}
}
Insert cell
{
let o = {a: ['b', 'c'], e: ['d'], b: ['c'], c: ['d']}
return toposort(Object.keys(o), n => o[n])
}
Insert cell

math.unit('second').units[0].unit

Insert cell
objs = {
try {
class Node {
constructor(name, def, nodeDeps, frame) {
this.name = name
this.def = def
this.nodeDeps = nodeDeps
this.frame = frame
this.attrs = frame.scope
this.attrStrings = {}
Object.entries(this.attrs).forEach(([k, v]) => {
if (v !== undefined) {
this.attrStrings[k] = v.toString ? v.toString() : v
}
})
}
}

let base = new Frame(
"base",
MathExpr.compileBlock(`
varunitcost = 0
varcostrate = (sum(deps.varcost) + varunitcost) / varunit
usage = 1 varunit
varcost = dot(usage, varcostrate)
fixedcost = 0
fixedunit = 1
fixedcostself = 0
fixedcost = (sum(deps.fixedcost) + fixedcostself) / fixedunit`
),
{
deps: NewArrayProxy([]),
})

console.log("base", base)
let exprs = {
// power: { attrs: {varunitcost: '0.1 usd / hour', varunit: 'kW'} },
// computer: { deps: {power: {usage: '1 kW'}}, attrs: {fixedcostself: '50 usd / measure'}},
// // sysadmin: { attrs: {fixedcostself: '200000 usd / year'} },
// webserver: {
// deps: {
// // computer: { usage: '1 second' },
// // sysadmin: {},
// },
// },
// visit: { deps: {webserver: {usage: '1 second'}} }
tea: { deps: {hotwater: {usage: '100 mL'}} },
hotwater: { deps: {kettle: {usage: '2 kettle minute'}, water: {}}, attrs:{varunit: 'L'}},
water: { attrs: {varunitcost: '0.1 usd', varunit: 'L'} },
kettle: { deps: {power: {usage: '1 kW'} }},
power: { attrs: {varunitcost: '0.1 usd / hour', varunit: 'kW'} },
}
// Evaluate expressions in attrs and dep overrides
let nodeDepIndex = {}
Object.entries(exprs).forEach(([nodeName, nodeDef]) =>
nodeDepIndex[nodeName] = Object.keys(nodeDef.deps || {})
)
let nodeOrder = toposort(Object.keys(nodeDepIndex), node => nodeDepIndex[node])

let nodes = {}
nodeOrder.forEach(nodeName => {
let nodeDef = exprs[nodeName]
let attrExprs = Object.entries(nodeDef.attrs || {}).map(([name, src]) => MathExpr.compile(name, src))
let nodeDeps = {}
Object.entries(nodeDef.deps || {}).forEach(([depName, depOverrideDef]) => {
depOverrideDef = depOverrideDef || {}
let depOverrideExprs = Object.entries(depOverrideDef).map(([name, src]) => MathExpr.compile(name, src))
let depNode = nodes[depName]
nodeDeps[depName] = depNode.frame.cloneAndOverride(
`${nodeName}.dep < ${depName}`, depOverrideExprs, {})
})
let depsProxy = NewArrayProxy(
Object.values(nodeDeps).map(dep => dep.scope)
)
console.log(nodeName, "depsProxy", depsProxy, nodeDeps)
let scope = {deps: depsProxy}
if (!attrExprs.some(expr => expr.name === "varunit")) {
if (!math.type.Unit.isValuelessUnit(nodeName)) {
math.createUnit(nodeName)
}
scope.varunit = math.unit(nodeName)
// scope.usage = math.multiply(1, scope.varunit)
}
let frame = base.cloneAndOverride(nodeName, attrExprs, scope)

nodes[nodeName] = new Node(nodeName, nodeDef, nodeDeps, frame)
})

console.log("nodeOrder", nodeOrder)
// S.root(d => {
// invalidation.then(d)
// })

return nodes
} catch (e) {
console.log(e)
throw e
}
}
Insert cell
objs.power.attrs.varcost.toString()
Insert cell
tex`${math.parse(objs.power.attrs.varcostrate.toString()).toTex()}`
Insert cell
objs.kettle.nodeDeps.power.scope.usage.toString()
Insert cell
tex`${math.parse(objs.kettle.attrs.varcostrate.toString()).toTex()}`
Insert cell
tex`${math.parse(math.divide(1, objs.water.attrs.varcostrate).toString()).toTex()}`
Insert cell
tex`${math.parse(objs.hotwater.attrs.varcostrate.toString()).toTex()}`
Insert cell
objs.hotwater.attrs.varcostrate.toString()
Insert cell
math.eval('dot(3 usd / L + 4 usd / kWh, 1 L + 1 kWh)').toString()
Insert cell
tex`${math.parse(objs.tea.attrs.varcost.toString()).toTex()}`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more