Cached FunctionChart percentiles calculation

This commit is contained in:
Ozzie Gooen 2022-05-15 14:39:50 -04:00
parent 2da5d5394e
commit b015c20fa4
4 changed files with 93 additions and 70 deletions

View File

@ -7,6 +7,8 @@ import {
lambdaValue,
environment,
runForeign,
squiggleExpression,
errorValue,
errorValueToString,
} from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega";
@ -45,6 +47,23 @@ interface FunctionChartProps {
environment: environment;
}
type percentiles = {
x: number;
p1: number;
p5: number;
p10: number;
p20: number;
p30: number;
p40: number;
p50: number;
p60: number;
p70: number;
p80: number;
p90: number;
p95: number;
p99: number;
}[];
export const FunctionChart: React.FC<FunctionChartProps> = ({
fn,
chartSettings,
@ -58,7 +77,9 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
setMouseOverlay(NaN);
}
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
let mouseItem = runForeign(fn, [mouseOverlay], environment);
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
? runForeign(fn, [mouseOverlay], { sampleCount: 10000, xyPointLength: 1000 })
: { tag: "Error", value: { tag: "REExpectedType", value: "Expected float, got NaN" } };
let showChart =
mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
<DistributionChart
@ -76,86 +97,75 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
chartSettings.count
);
type point = { x: number; value: result<Distribution, string> };
let valueData: point[] = React.useMemo(
() =>
data1.map((x) => {
let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") {
if (result.value.tag == "distribution") {
return { x, value: { tag: "Ok", value: result.value.value } };
} else {
return {
x,
value: {
tag: "Error",
value:
"Cannot currently render functions that don't return distributions",
},
};
}
let getPercentiles: () => percentiles = () => {
let valueData:any = data1.map((x) => {
let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") {
if (result.value.tag == "distribution") {
return { x, value: { tag: "Ok", value: result.value.value } };
} else {
return {
x,
value: { tag: "Error", value: errorValueToString(result.value) },
value: {
tag: "Error",
value:
"Cannot currently render functions that don't return distributions",
},
};
}
}),
[environment, fn]
);
} else {
return {
x,
value: { tag: "Error", value: errorValueToString(result.value) },
};
}
});
let initialPartition: [
{ x: number; value: Distribution }[],
{ x: number; value: string }[]
] = [[], []];
let [functionImage, errors] = valueData.reduce((acc, current) => {
if (current.value.tag === "Ok") {
acc[0].push({ x: current.x, value: current.value.value });
} else {
acc[1].push({ x: current.x, value: current.value.value });
}
return acc;
}, initialPartition);
let initialPartition: [
{ x: number; value: Distribution }[],
{ x: number; value: string }[]
] = [[], []];
let [functionImage, errors] = valueData.reduce((acc, current) => {
if (current.value.tag === "Ok") {
acc[0].push({ x: current.x, value: current.value.value });
} else {
acc[1].push({ x: current.x, value: current.value.value });
}
return acc;
}, initialPartition);
let percentiles:percentiles = functionImage.map(({ x, value }) => {
let toPointSet: Distribution = unwrap(value.toPointSet());
return {
x: x,
p1: unwrap(toPointSet.inv(0.01)),
p5: unwrap(toPointSet.inv(0.05)),
p10: unwrap(toPointSet.inv(0.12)),
p20: unwrap(toPointSet.inv(0.2)),
p30: unwrap(toPointSet.inv(0.3)),
p40: unwrap(toPointSet.inv(0.4)),
p50: unwrap(toPointSet.inv(0.5)),
p60: unwrap(toPointSet.inv(0.6)),
p70: unwrap(toPointSet.inv(0.7)),
p80: unwrap(toPointSet.inv(0.8)),
p90: unwrap(toPointSet.inv(0.9)),
p95: unwrap(toPointSet.inv(0.95)),
p99: unwrap(toPointSet.inv(0.99)),
};
});
return percentiles;
};
let percentiles = functionImage.map(({ x, value }) => {
return {
x: x,
p1: unwrap(value.inv(0.01)),
p5: unwrap(value.inv(0.05)),
p10: unwrap(value.inv(0.12)),
p20: unwrap(value.inv(0.2)),
p30: unwrap(value.inv(0.3)),
p40: unwrap(value.inv(0.4)),
p50: unwrap(value.inv(0.5)),
p60: unwrap(value.inv(0.6)),
p70: unwrap(value.inv(0.7)),
p80: unwrap(value.inv(0.8)),
p90: unwrap(value.inv(0.9)),
p95: unwrap(value.inv(0.95)),
p99: unwrap(value.inv(0.99)),
};
});
let _getPercentiles = React.useMemo(getPercentiles, [environment, fn])
let groupedErrors = _.groupBy(errors, (x) => x.value);
return (
<>
<SquigglePercentilesChart
data={{ facet: percentiles }}
data={{ facet: _getPercentiles }}
actions={false}
signalListeners={signalListeners}
/>
{showChart}
{_.entries(groupedErrors).map(([errorName, errorPoints]) => (
<ErrorBox key={errorName} heading={errorName}>
Values:{" "}
{errorPoints
.map((r, i) => <NumberShower key={i} number={r.x} />)
.reduce((a, b) => (
<>
{a}, {b}
</>
))}
</ErrorBox>
))}
</>
);
};

View File

@ -194,7 +194,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
<FunctionChart
fn={expression.value}
chartSettings={chartSettings}
environment={environment}
environment={{ sampleCount: environment.sampleCount / 10, xyPointLength: environment.sampleCount / 10 }}
/>
);
}
@ -232,7 +232,7 @@ const ChartWrapper = styled.div`
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
`;
let defaultChartSettings = { start: 0, stop: 10, count: 100 };
let defaultChartSettings = { start: 0, stop: 10, count: 20 };
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
environment,
@ -247,7 +247,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
chartSettings = defaultChartSettings,
}: SquiggleChartProps) => {
let expressionResult = run(squiggleString, bindings, environment, jsImports);
let e = environment ? environment : defaultEnvironment;
let e = environment ? environment : { sampleCount: 100000, xyPointLength: 1000 };
let internal: JSX.Element;
if (expressionResult.tag === "Ok") {
let expression = expressionResult.value;

View File

@ -56,7 +56,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
environment,
diagramStart = 0,
diagramStop = 10,
diagramCount = 100,
diagramCount = 20,
onChange,
bindings = defaultBindings,
jsImports = defaultImports,

View File

@ -153,6 +153,19 @@ to allow large and small numbers being printed cleanly.
</Story>
</Canvas>
## Functions
<Canvas>
<Story
name="Function"
args={{
squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Records
<Canvas>