Skip to content

Commit 4c57041

Browse files
authored
Merge pull request #121 from herbie-fp/ParthErrorExplanation
Error Explanation Now has an error tree and shows where does error Occur
2 parents 50633f6 + 15c83a1 commit 4c57041

File tree

7 files changed

+227
-114
lines changed

7 files changed

+227
-114
lines changed

src/herbie/ErrorExplanation.tsx

Lines changed: 115 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,125 @@
1-
import React, { useEffect} from 'react';
2-
import * as Contexts from './HerbieContext';
1+
import React, { useEffect } from 'react';
2+
import * as HerbieContext from './HerbieContext';
33
import * as fpcorejs from './lib/fpcore';
44
import { Sample } from './HerbieTypes';
5-
import { analyzeErrorExpression, ErrorExpressionResponse } from './lib/herbiejs';
5+
import * as types from './HerbieTypes';
6+
import { analyzeErrorExpression} from './lib/herbiejs';
7+
import{ErrorExpressionResponse, } from './HerbieTypes'
8+
import Mermaid from './LocalError/Mermaid';
9+
10+
function localErrorTreeAsMermaidGraph(
11+
tree: types.LocalErrorTree,
12+
bits: number,
13+
currentLocation: Array<number>,
14+
targetLocation: Array<number>
15+
) {
16+
let edges = [] as string[];
17+
let colors = {} as Record<string, string>;
18+
let counter = 0;
19+
20+
const isLeaf = (n: types.LocalErrorTree) => n['children'].length === 0;
21+
const formatName = (id: string, name: string, err: string) =>
22+
id + '[<span class=nodeLocalError title=' + err + '>' + name + '</span>]';
23+
24+
const locationsMatch = (loc1: Array<number>, loc2: Array<number>) =>
25+
JSON.stringify(loc1) === JSON.stringify(loc2);
26+
27+
function loop(n: types.LocalErrorTree, currentLoc: Array<number>) {
28+
const name = n['e'];
29+
const children = n['children'];
30+
const avg_error = n['avg-error'];
31+
32+
const id = 'N' + counter++;
33+
const nodeName = formatName(id, name, avg_error);
34+
35+
for (const [index, child] of children.entries()) {
36+
const childLocation = [...currentLoc, index + 1];
37+
38+
if (locationsMatch(childLocation, targetLocation)) {
39+
console.log(`Setting color to red for node at location: ${childLocation}`);
40+
colors[id] = 'ff0000';
41+
}
42+
43+
const cName = loop(child, childLocation);
44+
edges.push(cName + ' --> ' + nodeName);
45+
}
46+
47+
return nodeName;
48+
}
49+
50+
loop(tree, currentLocation);
51+
52+
if (isLeaf(tree)) {
53+
const name = tree['e'];
54+
const avg_error = tree['avg-error'];
55+
edges.push(formatName('N0', name, avg_error));
56+
}
57+
58+
for (const id in colors) {
59+
edges.push('style ' + id + ' fill:#' + colors[id]);
60+
}
61+
62+
return 'flowchart RL\n\n' + edges.join('\n');
63+
}
664

765
interface ErrorExplanationProps {
8-
expressionId: number;
66+
expressionId: number;
967
}
1068

11-
const ErrorExplanation: React.FC<ErrorExplanationProps> = (props) => {
12-
// Export the expression to a language of the user's choice
13-
const [expressions] = Contexts.useGlobal(Contexts.ExpressionsContext);
14-
const [selectedPoint] = Contexts.useGlobal(Contexts.SelectedPointContext);
15-
const [serverUrl] = Contexts.useGlobal(Contexts.ServerContext);
16-
const [spec] = Contexts.useGlobal(Contexts.SpecContext);
17-
18-
// Get the expression text
19-
const expressionText = expressions[props.expressionId].text;
20-
21-
const [errorResponse, setErrorResponse] = React.useState<ErrorExpressionResponse | null>(null);
22-
23-
const translateExpression = async () => {
24-
25-
if (selectedPoint) {
26-
27-
const vars = fpcorejs.getVarnamesMathJS(expressionText);
28-
const specVars = fpcorejs.getVarnamesMathJS(spec.expression);
29-
const modSelectedPoint = selectedPoint.filter((xi, i) => vars.includes(specVars[i]));
30-
31-
// Make server call to get translation when user submits
32-
try {
33-
const host = serverUrl;
34-
const response = await analyzeErrorExpression(
35-
fpcorejs.mathjsToFPCore(expressionText),
36-
{ points: [[modSelectedPoint, 1e308]] } as Sample,
37-
host
38-
);
39-
setErrorResponse(response);
40-
41-
} catch (error) {
42-
console.error('Error:', error);
43-
}
44-
}
45-
};
46-
47-
useEffect(() => {
48-
translateExpression();
49-
}, [expressionText, selectedPoint]);
50-
51-
return (<div>
52-
{/* Display the export code */}
53-
{errorResponse && errorResponse.explanation.length >0 ? (
54-
<div>
55-
<p>Operator: {errorResponse.explanation[0][0]}</p>
56-
<p>Expression: {errorResponse.explanation[0][1]}</p>
57-
<p>Type: {errorResponse.explanation[0][2]}</p>
58-
<p>Occurrences: {errorResponse.explanation[0][3]}</p>
59-
<p>Errors: {errorResponse.explanation[0][4]}</p>
60-
<pre>Details: {JSON.stringify(errorResponse.explanation[0][5], null, 2)}</pre>
61-
</div>
62-
) : (
63-
<p>No explanation available.</p>
64-
)}
69+
function ErrorExplanation({ expressionId }: { expressionId: number }){
70+
const [expressions] = HerbieContext.useGlobal(HerbieContext.ExpressionsContext);
71+
const [selectedPoint] = HerbieContext.useGlobal(HerbieContext.SelectedPointContext);
72+
const [errorResponse, setErrorResponse] = React.useState<ErrorExpressionResponse | null>(null);
73+
const [selectedPointsLocalError] = HerbieContext.useGlobal(HerbieContext.SelectedPointsLocalErrorContext);
74+
const [selectedPointsErrorExp, ] = HerbieContext.useGlobal(HerbieContext.SelectedPointsErrorExpContext);
75+
const [averageLocalErrors] = HerbieContext.useGlobal(HerbieContext.AverageLocalErrorsContext);
76+
const [selectedSampleId] = HerbieContext.useGlobal(HerbieContext.SelectedSampleIdContext);
6577

66-
</div>
78+
const pointLocalError = selectedPointsLocalError.find(a => a.expressionId === expressionId)?.error;
79+
80+
console.log(selectedPointsErrorExp)
81+
const localError = selectedPoint && pointLocalError
82+
? pointLocalError
83+
: averageLocalErrors.find((localError) => localError.sampleId === selectedSampleId && localError.expressionId === expressionId)?.errorTree;
84+
85+
86+
// Use useEffect to update the errorResponse state
87+
useEffect(() => {
88+
console.log(expressionId)
89+
console.log(expressions[expressionId].text)
90+
const pointErrorExp = selectedPointsErrorExp.find(a => a.expressionId === expressionId)?.error;
91+
setErrorResponse(pointErrorExp || null); // If pointErrorExp is undefined, set null
92+
}, [selectedPointsErrorExp]); // Run this effect whenever pointErrorExp changes
93+
94+
95+
96+
if (!localError) {
97+
return (
98+
<div className="local-error not-computed">
99+
<div>Please select a point on the error plot to compute local error.</div>
100+
</div>
67101
);
102+
}
103+
104+
return (
105+
<div>
106+
{errorResponse && errorResponse.explanation.length > 0 ? (
107+
<div>
108+
<p>Operator: {errorResponse.explanation[0][0]}</p>
109+
<p>Expression: {errorResponse.explanation[0][1]}</p>
110+
<p>Type: {errorResponse.explanation[0][2]}</p>
111+
<p>Occurrences: {errorResponse.explanation[0][3]}</p>
112+
<p>Errors: {errorResponse.explanation[0][4]}</p>
113+
<pre>Details: {JSON.stringify(errorResponse.explanation[0][5], null, 2)}</pre>
114+
</div>
115+
) : (
116+
<p>No explanation available.</p>
117+
)}
118+
<div className="local-error-graph">
119+
<Mermaid chart={localErrorTreeAsMermaidGraph(localError, 64, [], [1])} />
120+
</div>
121+
</div>
122+
);
68123
};
69124

70-
export default ErrorExplanation;
125+
export default ErrorExplanation;

src/herbie/HerbieContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const SelectedSampleIdContext = makeGlobal(undefined as number | undefine
7979
export const SamplesContext = makeGlobal([] as types.Sample[])
8080
export const SelectedPointContext = makeGlobal(undefined as types.ordinalPoint | undefined)
8181
export const SelectedPointsLocalErrorContext = makeGlobal([] as types.PointLocalErrorAnalysis[])
82+
export const SelectedPointsErrorExpContext = makeGlobal([] as types.PointErrorExpAnalysis[])
8283
export const AverageLocalErrorsContext = makeGlobal([] as types.AverageLocalErrorAnalysis[])
8384
export const FPTaylorAnalysisContext = makeGlobal([] as types.FPTaylorAnalysis[])
8485
export const FPTaylorRangeContext = makeGlobal([] as types.FPTaylorRange[])

src/herbie/HerbieTypes.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,27 @@ export class PointLocalErrorAnalysis {
121121
}
122122
}
123123

124+
type Explanation = [
125+
string, // operator
126+
string, // expression
127+
string, // type
128+
number, // occurrences
129+
number, // errors
130+
any[] // details
131+
];
132+
133+
export interface ErrorExpressionResponse {
134+
explanation: Explanation[];
135+
}
136+
137+
export class PointErrorExpAnalysis {
138+
constructor(public readonly expressionId: number, public readonly point: ordinalPoint, public readonly error:ErrorExpressionResponse) {
139+
this.expressionId = expressionId;
140+
this.point = point;
141+
this.error = error;
142+
}
143+
}
144+
124145
export class FPTaylorAnalysis {
125146
constructor(public readonly expressionId: number, public readonly analysis: any) {
126147
this.expressionId = expressionId;

src/herbie/HerbieUI.tsx

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { ErrorPlot } from './ErrorPlot';
1515
import { DerivationComponent } from './DerivationComponent';
1616
import { FPTaylorComponent } from './FPTaylorComponent';
1717
import SpeedVersusAccuracyPareto from './SpeedVersusAccuracyPareto';
18-
18+
import { getApi } from './lib/servercalls';
1919
import * as fpcorejs from './lib/fpcore';
2020
import * as herbiejsImport from './lib/herbiejs';
2121
import GitHubIssueButton from './GitHubIssueButton';
@@ -123,6 +123,7 @@ function HerbieUIInner() {
123123
const [averageLocalErrors, setAverageLocalErrors] = Contexts.useGlobal(Contexts.AverageLocalErrorsContext)
124124
const [selectedPoint, setSelectedPoint] = Contexts.useGlobal(Contexts.SelectedPointContext)
125125
const [selectedPointsLocalError, setSelectedPointsLocalError] = Contexts.useGlobal(Contexts.SelectedPointsLocalErrorContext);
126+
const [selectedPointsErrorExp, setSelectedPointsErrorExp] = Contexts.useGlobal(Contexts.SelectedPointsErrorExpContext);
126127
const [FPTaylorAnalysis, setFPTaylorAnalysis] = Contexts.useGlobal(Contexts.FPTaylorAnalysisContext);
127128
const [FPTaylorRanges, setFPTaylorRanges] = Contexts.useGlobal(Contexts.FPTaylorRangeContext);
128129
const [inputRangesTable,] = Contexts.useGlobal(Contexts.InputRangesTableContext)
@@ -408,6 +409,38 @@ function HerbieUIInner() {
408409
setTimeout(getPointLocalError)
409410
}
410411

412+
413+
// when the selected point changes, update the selected point local error
414+
useEffect(updateSelectedPointErrorExp, [selectedPoint, serverUrl, expressions, archivedExpressions])
415+
function updateSelectedPointErrorExp() {
416+
async function getPointErrorExp() {
417+
const errorExp = []
418+
const activeExpressions = expressions.filter(e => !archivedExpressions.includes(e.id))
419+
for (const expression of activeExpressions) {
420+
if (selectedPoint && expression) {
421+
// HACK to make sampling work on Herbie side
422+
const vars = fpcorejs.getVarnamesMathJS(expression.text)
423+
const specVars = fpcorejs.getVarnamesMathJS(spec.expression)
424+
const modSelectedPoint = selectedPoint.filter((xi, i) => vars.includes(specVars[i]))
425+
errorExp.push(
426+
new Types.PointErrorExpAnalysis(
427+
expression.id,
428+
selectedPoint,
429+
await herbiejs.analyzeErrorExpression(
430+
fpcorejs.mathjsToFPCore(expression.text),
431+
{ points: [[modSelectedPoint, 1e308]] } as Sample,
432+
serverUrl
433+
)
434+
)
435+
)
436+
}
437+
}
438+
setSelectedPointsErrorExp(errorExp)
439+
}
440+
441+
setTimeout(getPointErrorExp)
442+
}
443+
411444
useEffect(updateFPTaylorAnalysis, [FPTaylorRanges, serverUrl, expressions, archivedExpressions])
412445
function updateFPTaylorAnalysis() {
413446
async function getFPTaylorAnalysis() {
@@ -433,21 +466,12 @@ function HerbieUIInner() {
433466
body: fpcorejs.FPCoreBody(expression.text)
434467
})
435468

436-
const fptaylorInputResponse = await (await fetch(
437-
fpbenchServerUrl + "/exec",
438-
{
439-
method: 'POST', // *GET, POST, PUT, DELETE, etc.
440-
mode: 'cors', // no-cors, *cors, same-origin
441-
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
442-
credentials: 'same-origin', // include, *same-origin, omit
443-
headers: {
444-
'Content-Type': 'application/json'
445-
},
446-
redirect: 'follow', // manual, *follow, error
447-
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
448-
body: JSON.stringify({ 'formulas': [formula] }) // body data type must match "Content-Type" header
449-
}
450-
)).json();
469+
const fptaylorInputResponse = await (getApi(
470+
fpbenchServerUrl + "/exec",
471+
{'formulas': [formula] },
472+
true
473+
));
474+
console.log(fptaylorInputResponse);
451475

452476
const fptaylorInput = fptaylorInputResponse.stdout;
453477

@@ -470,25 +494,14 @@ function HerbieUIInner() {
470494
return response;
471495
};
472496

473-
const fptaylorResult = await parseFPTaylorOutput((
474-
await (
475-
await fetch(
476-
fptaylorServerUrl + "/exec",
477-
{
478-
method: 'POST', // *GET, POST, PUT, DELETE, etc.
479-
mode: 'cors', // no-cors, *cors, same-origin
480-
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
481-
credentials: 'same-origin', // include, *same-origin, omit
482-
headers: {
483-
'Content-Type': 'application/json'
484-
},
485-
redirect: 'follow', // manual, *follow, error
486-
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
487-
body: JSON.stringify({ 'fptaylorInput': fptaylorInput }) // body data type must match "Content-Type" header
488-
}
489-
)
490-
).json()
491-
).stdout)
497+
const fptaylorResult = await parseFPTaylorOutput((
498+
await (
499+
getApi(
500+
fptaylorServerUrl + "/exec",
501+
{ 'fptaylorInput': fptaylorInput },
502+
true
503+
))
504+
).stdout)
492505

493506
FPTaylorAnalyses.splice(index, 0,
494507
new Types.FPTaylorAnalysis(

src/herbie/LocalError/LocalError.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function localErrorTreeAsMermaidGraph(tree: types.LocalErrorTree, bits: number)
1212
let colors = {} as Record<string, string>
1313
let counter = 0
1414

15+
1516
const isLeaf = (n: types.LocalErrorTree ) => n['children'].length === 0
1617

1718
function formatName(id: string, name: string, avg_err: string, exact_err: string) {
@@ -27,6 +28,7 @@ function localErrorTreeAsMermaidGraph(tree: types.LocalErrorTree, bits: number)
2728
const name = n['e']
2829
const children = n['children']
2930
const avg_error = n['avg-error']
31+
3032
const exact_error = n['exact-error']
3133

3234
// node name

0 commit comments

Comments
 (0)