A playground based on reducer

This commit is contained in:
Sam Nolan 2022-04-11 16:16:29 +10:00
parent 61b589d0bd
commit cb07f5f68a
11 changed files with 181 additions and 285 deletions

View File

@ -1,124 +0,0 @@
import * as React from "react";
import _ from "lodash";
import type { Spec } from "vega";
import type {
DistPlus,
} from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega";
import * as chartSpecification from "../vega-specs/spec-distributions.json";
let SquiggleVegaChart = createClassFromSpec({
spec: chartSpecification as Spec,
});
export const DistPlusChart: React.FC<{
distPlus: DistPlus;
width: number;
height: number;
}> = ({ distPlus, width, height }) => {
let shape = distPlus.pointSetDist;
if (shape.tag === "Continuous") {
let xyShape = shape.value.xyShape;
let totalY = xyShape.ys.reduce((a, b) => a + b);
let total = 0;
let cdf = xyShape.ys.map((y) => {
total += y;
return total / totalY;
});
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
cdf: (c * 100).toFixed(2) + "%",
x: x,
y: y,
}));
return (
<SquiggleVegaChart
width={width}
height={height}
data={{ con: values }}
actions={false}
/>
);
} else if (shape.tag === "Discrete") {
let xyShape = shape.value.xyShape;
let totalY = xyShape.ys.reduce((a, b) => a + b);
let total = 0;
let cdf = xyShape.ys.map((y) => {
total += y;
return total / totalY;
});
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
cdf: (c * 100).toFixed(2) + "%",
x: x,
y: y,
}));
return <SquiggleVegaChart data={{ dis: values }} actions={false} />;
} else if (shape.tag === "Mixed") {
let discreteShape = shape.value.discrete.xyShape;
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
let continuousShape = shape.value.continuous.xyShape;
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
interface labeledPoint {
x: number;
y: number;
type: "discrete" | "continuous";
}
let markedDisPoints: labeledPoint[] = discretePoints.map(([x, y]) => ({
x: x,
y: y,
type: "discrete",
}));
let markedConPoints: labeledPoint[] = continuousPoints.map(([x, y]) => ({
x: x,
y: y,
type: "continuous",
}));
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), "x");
let totalContinuous = 1 - totalDiscrete;
let totalY = continuousShape.ys.reduce((a: number, b: number) => a + b);
let total = 0;
let cdf = sortedPoints.map((point: labeledPoint) => {
if (point.type === "discrete") {
total += point.y;
return total;
} else if (point.type === "continuous") {
total += (point.y / totalY) * totalContinuous;
return total;
}
});
interface cdfLabeledPoint {
cdf: string;
x: number;
y: number;
type: "discrete" | "continuous";
}
let cdfLabeledPoint: cdfLabeledPoint[] = _.zipWith(
cdf,
sortedPoints,
(c: number, point: labeledPoint) => ({
...point,
cdf: (c * 100).toFixed(2) + "%",
})
);
let continuousValues = cdfLabeledPoint.filter(
(x) => x.type === "continuous"
);
let discreteValues = cdfLabeledPoint.filter((x) => x.type === "discrete");
return (
<SquiggleVegaChart
data={{ con: continuousValues, dis: discreteValues }}
actions={false}
/>
);
}
};

View File

@ -0,0 +1,38 @@
import * as React from "react";
import _ from "lodash";
import type { Spec } from "vega";
import type {
Distribution,
} from "@quri/squiggle-lang";
import { distributionErrorToString } from '@quri/squiggle-lang';
import { createClassFromSpec } from "react-vega";
import * as chartSpecification from "../vega-specs/spec-distributions.json";
let SquiggleVegaChart = createClassFromSpec({
spec: chartSpecification as Spec,
});
type DistributionChartProps = {
distribution: Distribution;
width: number;
height: number;
}
export const DistributionChart: React.FC<DistributionChartProps> = ({ distribution, width, height }: DistributionChartProps) => {
console.log("Making shape")
let shape = distribution.shape();
console.log(shape)
if (shape.tag === "Ok") {
return (
<SquiggleVegaChart
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
width={width}
height={height}
actions={false}
/>
);
}
else{
return <> {distributionErrorToString(shape.value)} </>;
}
};

View File

@ -1,10 +1,10 @@
import * as React from "react";
import _ from "lodash";
import type { Spec } from "vega";
import type { DistPlus } from "@quri/squiggle-lang";
import type { Distribution, errorValue, result } from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega";
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
import { DistPlusChart } from "./DistPlusChart";
import { DistributionChart } from "./DistributionChart";
import { Error } from "./Error";
let SquigglePercentilesChart = createClassFromSpec({
@ -13,36 +13,38 @@ let SquigglePercentilesChart = createClassFromSpec({
type distPlusFn = (
a: number
) => { tag: "Ok"; value: DistPlus } | { tag: "Error"; value: string };
) => result<Distribution, errorValue>
const _rangeByCount = (start, stop, count) => {
const _rangeByCount = (start: number, stop: number, count: number) => {
const step = (stop - start) / (count - 1);
const items = _.range(start, stop, step);
const result = items.concat([stop]);
return result;
};
function unwrap<a, b>( x: result<a, b>): a {
if(x.tag === "Ok"){
return x.value
}
}
export const FunctionChart: React.FC<{
distPlusFn: distPlusFn;
diagramStart: number;
diagramStop: number;
diagramCount: number;
}> = ({ distPlusFn, diagramStart, diagramStop, diagramCount }) => {
let [mouseOverlay, setMouseOverlay] = React.useState(NaN);
let [mouseOverlay, setMouseOverlay] = React.useState(0);
function handleHover(...args) {
setMouseOverlay(args[1]);
}
function handleOut(...args) {
function handleOut() {
setMouseOverlay(NaN);
}
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
let percentileArray = [
0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99,
];
let mouseItem = distPlusFn(mouseOverlay);
let showChart =
mouseItem.tag === "Ok" ? (
<DistPlusChart distPlus={mouseItem.value} width={400} height={140} />
<DistributionChart distribution={mouseItem.value} width={400} height={140} />
) : (
<></>
);
@ -56,22 +58,21 @@ export const FunctionChart: React.FC<{
})
.filter((x) => x !== null)
.map(({ x, value }) => {
let percentiles = getPercentiles(percentileArray, value);
return {
x: x,
p1: percentiles[0],
p5: percentiles[1],
p10: percentiles[2],
p20: percentiles[3],
p30: percentiles[4],
p40: percentiles[5],
p50: percentiles[6],
p60: percentiles[7],
p70: percentiles[8],
p80: percentiles[9],
p90: percentiles[10],
p95: percentiles[11],
p99: percentiles[12],
p1: unwrap(value.inv(0.01)),
p5: unwrap(value.inv(0.05)),
p10: unwrap(value.inv(0.12)),
p20: unwrap(value.inv(0.20)),
p30: unwrap(value.inv(0.30)),
p40: unwrap(value.inv(0.40)),
p50: unwrap(value.inv(0.50)),
p60: unwrap(value.inv(0.60)),
p70: unwrap(value.inv(0.70)),
p80: unwrap(value.inv(0.80)),
p90: unwrap(value.inv(0.90)),
p95: unwrap(value.inv(0.95)),
p99: unwrap(value.inv(0.99)),
};
});
@ -101,88 +102,3 @@ export const FunctionChart: React.FC<{
);
};
function getPercentiles(percentiles: number[], t: DistPlus) {
if (t.pointSetDist.tag === "Discrete") {
let total = 0;
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
let bounds = percentiles.map((_) => maxX);
_.zipWith(
t.pointSetDist.value.xyShape.xs,
t.pointSetDist.value.xyShape.ys,
(x, y) => {
total += y;
percentiles.forEach((v, i) => {
if (total > v && bounds[i] === maxX) {
bounds[i] = x;
}
});
}
);
return bounds;
} else if (t.pointSetDist.tag === "Continuous") {
let total = 0;
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
let totalY = _.sum(t.pointSetDist.value.xyShape.ys);
let bounds = percentiles.map((_) => maxX);
_.zipWith(
t.pointSetDist.value.xyShape.xs,
t.pointSetDist.value.xyShape.ys,
(x, y) => {
total += y / totalY;
percentiles.forEach((v, i) => {
if (total > v && bounds[i] === maxX) {
bounds[i] = x;
}
});
}
);
return bounds;
} else if (t.pointSetDist.tag === "Mixed") {
let discreteShape = t.pointSetDist.value.discrete.xyShape;
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
let continuousShape = t.pointSetDist.value.continuous.xyShape;
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
interface labeledPoint {
x: number;
y: number;
type: "discrete" | "continuous";
}
let markedDisPoints: labeledPoint[] = discretePoints.map(([x, y]) => ({
x: x,
y: y,
type: "discrete",
}));
let markedConPoints: labeledPoint[] = continuousPoints.map(([x, y]) => ({
x: x,
y: y,
type: "continuous",
}));
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), "x");
let totalContinuous = 1 - totalDiscrete;
let totalY = continuousShape.ys.reduce((a: number, b: number) => a + b);
let total = 0;
let maxX = _.max(sortedPoints.map((x) => x.x));
let bounds = percentiles.map((_) => maxX);
sortedPoints.map((point: labeledPoint) => {
if (point.type === "discrete") {
total += point.y;
} else if (point.type === "continuous") {
total += (point.y / totalY) * totalContinuous;
}
percentiles.forEach((v, i) => {
if (total > v && bounds[i] === maxX) {
bounds[i] = total;
}
});
return total;
});
return bounds;
}
}

View File

@ -1,14 +1,12 @@
import * as React from "react";
import _ from "lodash";
import { run } from "@quri/squiggle-lang";
import { run, errorValueToString } from "@quri/squiggle-lang";
import type {
SamplingInputs,
samplingParams,
exportEnv,
exportDistribution,
} from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistPlusChart } from "./DistPlusChart";
import { FunctionChart } from "./FunctionChart";
import { DistributionChart } from "./DistributionChart";
import { Error } from "./Error";
export interface SquiggleChartProps {
@ -39,8 +37,6 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
sampleCount = 1000,
outputXYPoints = 1000,
kernelWidth,
pointDistLength = 1000,
diagramStart = 0,
diagramStop = 10,
diagramCount = 20,
@ -49,43 +45,35 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
width = 500,
height = 60,
}: SquiggleChartProps) => {
let samplingInputs: SamplingInputs = {
let samplingInputs: samplingParams = {
sampleCount: sampleCount,
outputXYPoints: outputXYPoints,
kernelWidth: kernelWidth,
pointDistLength: pointDistLength,
xyPointLength: outputXYPoints
};
let result = run(squiggleString, samplingInputs, environment);
console.log(result)
if (result.tag === "Ok") {
let environment = result.value.environment;
let exports = result.value.exports;
onEnvChange(environment);
let chartResults = exports.map((chartResult: exportDistribution) => {
if (chartResult["NAME"] === "Float") {
return <NumberShower precision={3} number={chartResult["VAL"]} />;
} else if (chartResult["NAME"] === "DistPlus") {
return (
<DistPlusChart
distPlus={chartResult.VAL}
height={height}
width={width}
/>
);
} else if (chartResult.NAME === "Function") {
return (
<FunctionChart
distPlusFn={chartResult.VAL}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
/>
);
}
});
return <>{chartResults}</>;
let chartResult = result.value;
if (chartResult.tag === "number") {
return <NumberShower precision={3} number={chartResult.value} />;
} else if (chartResult.tag === "distribution") {
console.log("Is a distribution")
return (
<DistributionChart
distribution={chartResult.value}
height={height}
width={width}
/>
);
console.log("NOT THIS LINE")
}
else {
console.log("Is a distribution")
return <Error heading="No Viewer">{"We don't currently have a viewer for this type: " + chartResult.tag}</Error>;
}
} else if (result.tag === "Error") {
// At this point, we came across an error. What was our error?
return <Error heading={"Parse Error"}>{result.value}</Error>;
return <Error heading={"Parse Error"}>{errorValueToString(result.value)}</Error>;
}
};

View File

@ -4,8 +4,8 @@ import type {
exportDistribution,
} from "../rescript/ProgramEvaluator.gen";
export type { exportEnv, exportDistribution };
import { genericDist, samplingParams, evaluate, expressionValue, errorValue, distributionError } from "../rescript/TypescriptInterface.gen";
export { makeSampleSetDist } from "../rescript/TypescriptInterface.gen";
import { genericDist, samplingParams, evaluate, expressionValue, errorValue, distributionError, toPointSet, continuousShape, discreteShape, distributionErrorToString } from "../rescript/TypescriptInterface.gen";
export { makeSampleSetDist, errorValueToString, distributionErrorToString } from "../rescript/TypescriptInterface.gen";
import {
Constructors_mean,
Constructors_sample,
@ -32,19 +32,14 @@ import {
Constructors_pointwiseLogarithm,
Constructors_pointwisePower,
} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
export type {samplingParams, errorValue}
export type SamplingInputs = {
readonly sampleCount?: number;
readonly outputXYPoints?: number;
readonly kernelWidth?: number;
readonly pointDistLength?: number
};
export let defaultSamplingInputs: samplingParams = {
sampleCount: 10000,
xyPointLength: 10000
};
type result<a, b> =
export type result<a, b> =
| {
tag: "Ok";
value: a;
@ -65,6 +60,10 @@ export function resultMap<a, b, c>(
}
}
function Ok<a,b>(x: a): result<a,b> {
return {"tag": "Ok", value: x}
}
type tagged<a, b> = {tag: a, value: b}
function tag<a,b>(x: a, y: b) : tagged<a, b>{
@ -97,9 +96,6 @@ function createTsExport(x: expressionValue, sampEnv: samplingParams): squiggleEx
return tag("number", x.value);
case "EvRecord":
return tag("record", _.mapValues(x.value, x => createTsExport(x, sampEnv)))
}
}
@ -108,6 +104,19 @@ export function resultExn<a, c>(r: result<a, c>): a | c {
return r.value;
}
export type point = { x: number, y: number}
export type shape = {
continuous: point[]
discrete: point[]
}
function shapePoints(x : continuousShape | discreteShape): point[]{
let xs = x.xyShape.xs;
let ys = x.xyShape.ys;
return _.zipWith(xs, ys, (x, y) => ({x, y}))
}
export class Distribution {
t: genericDist;
env: samplingParams;
@ -148,6 +157,34 @@ export class Distribution {
);
}
shape() : result<shape, distributionError> {
let pointSet = toPointSet(this.t, {xyPointLength: this.env.xyPointLength, sampleCount: this.env.sampleCount}, null);
if(pointSet.tag === "Ok"){
let distribution = pointSet.value;
if(distribution.tag === "Continuous"){
return Ok({
continuous: shapePoints(distribution.value),
discrete: []
})
}
else if(distribution.tag === "Discrete"){
return Ok({
discrete: shapePoints(distribution.value),
continuous: []
})
}
else if(distribution.tag === "Mixed"){
return Ok({
discrete: shapePoints(distribution.value.discrete),
continuous: shapePoints(distribution.value.continuous)
})
}
}
else {
return pointSet
}
}
toPointSet(): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_toPointSet({ env: this.env }, this.t)
@ -170,8 +207,14 @@ export class Distribution {
return this.mapResultDist(Constructors_inspect({ env: this.env }, this.t));
}
toString(): result<string, distributionError> {
return Constructors_toString({ env: this.env }, this.t);
toString(): string {
let result = Constructors_toString({ env: this.env }, this.t);
if(result.tag === "Ok"){
result.value
}
else {
return distributionErrorToString(result.value)
}
}
toSparkline(n: number): result<string, distributionError> {

View File

@ -21,6 +21,7 @@ let toFloatOperation: (
~distToFloatOperation: Operation.distToFloatOperation,
) => result<float, error>
@genType
let toPointSet: (
t,
~xyPointLength: int,

View File

@ -10,10 +10,21 @@ type error =
| DistributionVerticalShiftIsInvalid
| Other(string)
@genType
module Error = {
type t = error
let fromString = (s: string): t => Other(s)
@genType
let toString = (x: t) => {
switch x {
| NotYetImplemented => "Not Yet Implemented"
| Unreachable => "Unreachable"
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift Is Invalid"
| Other(s) => s
}
}
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
n->E.R2.errMap(r => r->fromString->Error)
@ -51,6 +62,7 @@ module Operation = {
| #Sample
]
@genType
type pointsetXSelection = [#Linear | #ByWeight]
type toDist =

View File

@ -214,4 +214,4 @@ module T = Dist({
let getMeanOfSquares = t => t |> shapeMap(XYShape.T.square) |> mean
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
}
})
})

View File

@ -19,6 +19,7 @@ type interpolationStrategy = XYShape.interpolationStrategy;
type extrapolationStrategy = XYShape.extrapolationStrategy;
type interpolator = XYShape.extrapolationStrategy;
@genType
type rec continuousShape = {
xyShape: xyShape,
interpolation: interpolationStrategy,
@ -26,12 +27,14 @@ type rec continuousShape = {
integralCache: option<continuousShape>,
}
@genType
type discreteShape = {
xyShape: xyShape,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
@genType
type mixedShape = {
continuous: continuousShape,
discrete: discreteShape,

View File

@ -8,6 +8,7 @@ type errorValue =
type t = errorValue
@genType
let errorToString = err =>
switch err {
| REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}`

View File

@ -36,3 +36,21 @@ type expressionValue = Reducer_Expression.expressionValue
@genType
type errorValue = Reducer_ErrorValue.errorValue
@genType
let toPointSet = GenericDist.toPointSet
@genType
type mixedShape = PointSetTypes.mixedShape
@genType
type discreteShape = PointSetTypes.discreteShape
@genType
type continuousShape = PointSetTypes.continuousShape
@genType
let errorValueToString = Reducer_ErrorValue.errorToString
@genType
let distributionErrorToString = GenericDist_Types.Error.toString