From cb07f5f68ab37e825d6e0ecc7566837b31e26d1c Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Mon, 11 Apr 2022 16:16:29 +1000 Subject: [PATCH] A playground based on reducer --- .../src/components/DistPlusChart.tsx | 124 ---------------- .../src/components/DistributionChart.tsx | 38 +++++ .../src/components/FunctionChart.tsx | 134 ++++-------------- .../src/components/SquiggleChart.tsx | 62 ++++---- packages/squiggle-lang/src/js/index.ts | 71 ++++++++-- .../GenericDist/GenericDist.resi | 1 + .../GenericDist/GenericDist_Types.res | 12 ++ .../Distributions/PointSetDist/Discrete.res | 2 +- .../PointSetDist/PointSetTypes.res | 3 + .../rescript/Reducer/Reducer_ErrorValue.res | 1 + .../src/rescript/TypescriptInterface.res | 18 +++ 11 files changed, 181 insertions(+), 285 deletions(-) delete mode 100644 packages/components/src/components/DistPlusChart.tsx create mode 100644 packages/components/src/components/DistributionChart.tsx diff --git a/packages/components/src/components/DistPlusChart.tsx b/packages/components/src/components/DistPlusChart.tsx deleted file mode 100644 index 02f2fabd..00000000 --- a/packages/components/src/components/DistPlusChart.tsx +++ /dev/null @@ -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 ( - - ); - } 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 ; - } 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 ( - - ); - } -}; \ No newline at end of file diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx new file mode 100644 index 00000000..502adc8c --- /dev/null +++ b/packages/components/src/components/DistributionChart.tsx @@ -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 = ({ distribution, width, height }: DistributionChartProps) => { + console.log("Making shape") + let shape = distribution.shape(); + console.log(shape) + if (shape.tag === "Ok") { + return ( + + ); + } + else{ + return <> {distributionErrorToString(shape.value)} ; + } +}; diff --git a/packages/components/src/components/FunctionChart.tsx b/packages/components/src/components/FunctionChart.tsx index 7625b2da..e93e8bd9 100644 --- a/packages/components/src/components/FunctionChart.tsx +++ b/packages/components/src/components/FunctionChart.tsx @@ -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 -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( x: result): 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" ? ( - + ) : ( <> ); @@ -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; - } -} diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index f5dc5667..ded0a96a 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -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 = ({ squiggleString = "", sampleCount = 1000, outputXYPoints = 1000, - kernelWidth, - pointDistLength = 1000, diagramStart = 0, diagramStop = 10, diagramCount = 20, @@ -49,43 +45,35 @@ export const SquiggleChart: React.FC = ({ 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 ; - } else if (chartResult["NAME"] === "DistPlus") { - return ( - - ); - } else if (chartResult.NAME === "Function") { - return ( - - ); - } - }); - return <>{chartResults}; + let chartResult = result.value; + if (chartResult.tag === "number") { + return ; + } else if (chartResult.tag === "distribution") { + console.log("Is a distribution") + return ( + + ); + console.log("NOT THIS LINE") + } + else { + console.log("Is a distribution") + return {"We don't currently have a viewer for this type: " + chartResult.tag}; + } } else if (result.tag === "Error") { // At this point, we came across an error. What was our error? - return {result.value}; + return {errorValueToString(result.value)}; } }; diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index 923303ef..35fa0537 100644 --- a/packages/squiggle-lang/src/js/index.ts +++ b/packages/squiggle-lang/src/js/index.ts @@ -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 = +export type result = | { tag: "Ok"; value: a; @@ -65,6 +60,10 @@ export function resultMap( } } +function Ok(x: a): result { + return {"tag": "Ok", value: x} +} + type tagged = {tag: a, value: b} function tag(x: a, y: b) : tagged{ @@ -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(r: result): 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 { + 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 { 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 { - 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 { diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi index 4565ec14..bd6755fd 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi @@ -21,6 +21,7 @@ let toFloatOperation: ( ~distToFloatOperation: Operation.distToFloatOperation, ) => result +@genType let toPointSet: ( t, ~xyPointLength: int, diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res index 96e7d3f8..4d2ba9b6 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res @@ -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 = diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res index 3b689453..3bd22018 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res @@ -214,4 +214,4 @@ module T = Dist({ let getMeanOfSquares = t => t |> shapeMap(XYShape.T.square) |> mean XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares) } -}) \ No newline at end of file +}) diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetTypes.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetTypes.res index 2d7947c0..4de2e880 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetTypes.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetTypes.res @@ -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, } +@genType type discreteShape = { xyShape: xyShape, integralSumCache: option, integralCache: option, } +@genType type mixedShape = { continuous: continuousShape, discrete: discreteShape, diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index 666f5476..d0c85d59 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -8,6 +8,7 @@ type errorValue = type t = errorValue +@genType let errorToString = err => switch err { | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` diff --git a/packages/squiggle-lang/src/rescript/TypescriptInterface.res b/packages/squiggle-lang/src/rescript/TypescriptInterface.res index c52dd8a7..a706bdd2 100644 --- a/packages/squiggle-lang/src/rescript/TypescriptInterface.res +++ b/packages/squiggle-lang/src/rescript/TypescriptInterface.res @@ -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