@hugov/correl-range2
Version:
monte carlo simulation for correlated variables expressed as ranges
96 lines (85 loc) • 2.97 kB
JavaScript
/**
* @typedef {Array|Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Uint8ClampedArray|Float32Array|Float64Array} ArrayLike
*/
import Stats from './_stats.js'
import icdf from 'norm-dist/icdf-voutier.js'
function random(dim) {
const zs = dim.length ? dim : new Float64Array(dim)
return function() {
for (let i=0; i<zs.length; ++i) zs[i] = icdf(Math.random())
return zs
}
}
export default class Sim {
constructor(rndNs, risks, model, resolution) {
const point = model(),
names = Object.keys( point )
this.names = names
this.risks = risks
this.rndNs = rndNs
this.model = model
this.stats = new Stats( names, resolution )
/**
* single run with given Z inputs
* @param {ArrayLike<number>} zs
* @return {Object}
*/
this.one = Function('zs',
`for (const rn of this.rndNs) rn.update(zs);const o=this.model();${
names.filter( n => typeof point[n] !== 'number')
.map( n => `o['${n}']=o['${n}'].value`)
.join(';')
};return o`
)
/**
* N runs compressed into empirical sample distributions
* TODO allow context parameter as input to the loop function for chaining/spreadsheets
* @param {number} N number of runs
* @param {()=>ArrayLike<number>)} [sampler] source of Z inputs
* @param {number} [dim] empirical distribution points
* @return {Object}
*/
this.run = Function(
/* binded */'random', 'moments',
/* arguments */'N=25000', 'sampler=random(this.risks.length)','dim',
/* javascrip */`const stats = this.stats;
for (let i=0; i<N; ++i) {
const zs = sampler();
for (const rn of this.rndNs) rn.update(zs);
const o=this.model();
moments.push(${
this.names.map(
(n,i) => typeof point[n] === 'number' ? `o['${n}']` : `o['${n}'].value`
).join(',')
});${
this.names.map(
n => typeof point[n] === 'number' ? `stats['${n}'].push(o['${n}'])` : `stats['${n}'].push(o['${n}'].value)`
).join(';')
}
}
return this`
).bind(this, random, Stats.momentsOf(this.stats))
}
all(iterations, sampler=random(this.risks.length)) {
const TypedArray = Float32Array, //all Float32Array for now
BYTES_PER_SET = TypedArray.BYTES_PER_ELEMENT * this.names.length,
buffer = typeof iterations === 'number' ? new ArrayBuffer(BYTES_PER_SET * iterations) : iterations.buffer || iterations
let offset = iterations.byteOffset || 0
const size = Math.floor( ( buffer.byteLength - offset ) / BYTES_PER_SET ),
results = {}
for (const name of this.names) {
results[name] = new TypedArray( buffer, offset, size )
offset += size * TypedArray.BYTES_PER_ELEMENT
}
for (let i=0; i<size; ++i) {
const zs = sampler()
for (const rnd of this.rndNs) rnd.update(zs)
const sample = this.model()
for (const name of this.names) results[name][i] = +sample[name] // +important to trigger RandomNumber.valueOf()
}
return results
}
get buffer() {
return Stats.bufferOf(this.stats)
}
}