@@ -61,13 +61,16 @@ function shouldExpand(currentPath: number[], allErrorPaths: number[][]): boolean
6161 ) ;
6262}
6363
64+ type LocalErrorTreeWithExplanation = LocalErrorTree & { explanation ?: string , path ?: number [ ] } ;
65+
66+
6467function TreeRow ( {
6568 node,
6669 depth,
6770 currentPath,
6871 errorPaths,
6972} : {
70- node : LocalErrorTree ;
73+ node : LocalErrorTreeWithExplanation ;
7174 depth : number ;
7275 currentPath : number [ ] ;
7376 errorPaths : number [ ] [ ] ;
@@ -131,6 +134,31 @@ function TreeRow({
131134 < td className = "accuracy-col" >
132135 { parseFloat ( node [ "percent-accuracy" ] ) . toFixed ( 1 ) } %
133136 </ td >
137+ < td className = "explanation" >
138+ { node . explanation ?
139+ node . explanation === "cancellation" ? (
140+ < a href = "https://en.wikipedia.org/wiki/Catastrophic_cancellation" target = "_blank" rel = "noopener noreferrer" >
141+ < span className = "explanation-link" title = "Click for more information about this error type" >
142+ { node . explanation }
143+ </ span >
144+ </ a >
145+ ) :
146+ node . explanation === "oflow-rescue" ? (
147+ < a href = "https://en.wikipedia.org/wiki/Floating-point_arithmetic#:~:text=including%20the%20zeros.-,overflow,-%2C%20set%20if%20the" target = "_blank" rel = "noopener noreferrer" >
148+ < span className = "explanation-link" title = "Click for more information about this error type" >
149+ { node . explanation }
150+ </ span >
151+ </ a >
152+ ) :
153+ // TODO handle other explanations
154+ (
155+ < span className = "explanation" >
156+ { node . explanation }
157+ </ span >
158+ ) : (
159+ < span className = "no-explanation" > </ span >
160+ ) }
161+ </ td >
134162 { /* <td>
135163 <span >
136164 {parseFloat(node["ulps-error"]).toFixed(1)}
@@ -152,11 +180,35 @@ function TreeRow({
152180 ) ;
153181}
154182
183+ // Applies a function on a tree structure, providing a current path
184+ // in the tree as [] for the root, [1] for the first child, [[1, 2]] for the second child of the first child, etc.
185+ function treeMapWithPath < T , U > (
186+ tree : T ,
187+ fn : ( node : T , path : number [ ] ) => U ,
188+ getChildren : ( node : T ) => T [ ] | undefined ,
189+ path : number [ ] = [ ]
190+ ) : U {
191+ const result = fn ( tree , path ) ;
192+ const children = getChildren ( tree ) ;
193+
194+ if ( children ) {
195+ children . forEach ( ( child , index ) => {
196+ treeMapWithPath ( child , fn , getChildren , [ ...path , index + 1 ] ) ;
197+ } ) ;
198+ }
199+
200+ return result ;
201+ }
202+
155203function NewLocalError ( { expressionId } : { expressionId : number } ) {
156204 const [ selectedPointsLocalError ] = HerbieContext . useGlobal ( HerbieContext . SelectedPointsLocalErrorContext ) ;
157205 const [ localError , setLocalError ] = useState < LocalErrorTree | null > ( null ) ;
158206 const [ errorPaths , setErrorPaths ] = useState < number [ ] [ ] > ( [ ] ) ;
207+ const [ selectedPointsErrorExp , ] = HerbieContext . useGlobal ( HerbieContext . SelectedPointsErrorExpContext ) ;
208+
209+ const pointErrorExp = selectedPointsErrorExp . find ( a => a . expressionId === expressionId ) ?. error ;
159210
211+ // TODO we probably don't need these useState hooks, we can just use the context directly
160212 useEffect ( ( ) => {
161213 const pointLocalError = selectedPointsLocalError . find (
162214 ( a ) => a . expressionId === expressionId
@@ -170,16 +222,34 @@ function NewLocalError({ expressionId }: { expressionId: number }) {
170222 }
171223 } , [ selectedPointsLocalError , expressionId ] ) ;
172224
173- if ( ! localError ) {
225+ if ( ! localError || ! pointErrorExp
226+ ) {
174227 return (
175228 < div className = "not-computed" >
176229 < div > No local error computed for this expression. Select a point to compute.</ div >
177230 </ div >
178231 ) ;
179232 }
233+ console . log ( "pointErrorExp" , pointErrorExp )
234+ const explanations = pointErrorExp . explanation . map ( e => e [ 2 ] ) ;
235+ const explanationPaths = pointErrorExp . explanation . map ( e => e [ 6 ] [ 0 ] ) ;
236+ console . log ( explanations , explanationPaths ) ;
237+
238+ // For each node in the local error tree, attach its explanation if it exists
239+ const localErrorWithExplanations = treeMapWithPath ( localError as LocalErrorTreeWithExplanation , ( node , path ) => {
240+ // Find the explanation for this node, if it exists
241+ const explanationIndex = explanationPaths . findIndex ( ep =>
242+ ep . length === path . length &&
243+ ep . join ( ',' ) === path . join ( ',' )
244+ ) ;
245+ node . explanation = explanationIndex !== - 1 ? explanations [ explanationIndex ] : undefined ;
246+ return node ;
247+ } , ( node ) => node . children ) ;
180248
249+ const hasNoExplanations = explanationPaths . length === 0
250+
181251 return (
182- < div className = " local-error" >
252+ < div className = { ` local-error ${ hasNoExplanations ? ' no-explanations' : '' } ` } >
183253 < table >
184254 < thead >
185255 < tr >
@@ -190,10 +260,11 @@ function NewLocalError({ expressionId }: { expressionId: number }) {
190260 Difference
191261 </ th >
192262 < th > Accuracy</ th >
263+ < th className = "explanation" > Explanation</ th >
193264 </ tr >
194265 </ thead >
195266 < tbody >
196- < TreeRow node = { localError } depth = { 0 } currentPath = { [ ] } errorPaths = { errorPaths } />
267+ < TreeRow node = { localErrorWithExplanations } depth = { 0 } currentPath = { [ ] } errorPaths = { errorPaths } />
197268 </ tbody >
198269 </ table >
199270 </ div >
0 commit comments