| 131 | } |
| 132 | |
| 133 | type C = { re: number; im: number }; |
| 134 | |
| 135 | function complexAt( |
| 136 | expr: BoxedExpression, |
| 137 | x: string, |
| 138 | value: number | C |
| 139 | ): C | null { |
| 140 | const xv = |
| 141 | typeof value === 'number' ? value : expr.engine.number(value); |
| 142 | // Each point runs inside a `withTimeLimit` span and throws |
| 143 | // CancellationError when a single evaluation runs too long (a slow |
| 144 | // ₂F₁/Appell point); treat that as an unusable point (null), not a harness |
| 145 | // error. The span is required: the engine has no ambient time limit (the |
| 146 | // `ce.timeLimit` property was retired), so an unwrapped N() is unbounded. |
| 147 | let v: BoxedExpression; |
| 148 | try { |
| 149 | v = expr.engine.withTimeLimit( |
| 150 | { ms: POINT_TIME_LIMIT_MS, label: 'rubi:benchmark:point' }, |
| 151 | () => expr.subs({ [x]: xv as any }).N() |
| 152 | ); |
| 153 | } catch (e) { |
| 154 | if (e instanceof Error && e.constructor.name === 'CancellationError') |
| 155 | return null; |
| 156 | throw e; |
| 157 | } |
| 158 | if (!isNumber(v)) return null; |
| 159 | const re = v.re; |
| 160 | const im = v.im; |