1 /*
  2     Copyright 2008-2024
  3         Matthias Ehmann,
  4         Carsten Miller,
  5         Alfred Wassermann
  6 
  7     This file is part of JSXGraph.
  8 
  9     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 10 
 11     You can redistribute it and/or modify it under the terms of the
 12 
 13       * GNU Lesser General Public License as published by
 14         the Free Software Foundation, either version 3 of the License, or
 15         (at your option) any later version
 16       OR
 17       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 18 
 19     JSXGraph is distributed in the hope that it will be useful,
 20     but WITHOUT ANY WARRANTY; without even the implied warranty of
 21     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 22     GNU Lesser General Public License for more details.
 23 
 24     You should have received a copy of the GNU Lesser General Public License and
 25     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 26     and <https://opensource.org/licenses/MIT/>.
 27  */
 28 
 29 "use strict";
 30 
 31 import Type from "../utils/type.js";
 32 import Mat from "./math.js";
 33 import Geometry from "./geometry.js";
 34 import Numerics from "./numerics.js";
 35 import Quadtree from "./bqdt.js";
 36 
 37 /**
 38  * Plotting of curves which are given implicitly as the set of points solving an equation
 39  * <i>f(x,y) = 0</i>.
 40  * <p>
 41  * The main class initializes a new implicit plot instance.
 42  * <p>
 43  * The algorithm should be able to plot most implicit curves as long as the equations
 44  * are not too complex. We are aware of the paper by Oliver Labs,
 45  * <a href="https://link.springer.com/chapter/10.1007/978-1-4419-0999-2_6">A List of Challenges for Real Algebraic Plane Curve Visualization Software</a>
 46  * which contains many equations where this algorithm may fail.
 47  * For example,  at the time being there is no attempt to detect <i>solitary points</i>.
 48  * Also, it is always a trade off to find all components of the curve and
 49  * keep the construction responsive.
 50  *
 51  * @name JXG.Math.ImplicitPlot
 52  * @exports Mat.ImplicitPlot as JXG.Math.ImplicitPlot
 53  * @param {Array} bbox Bounding box of the area in which solutions of the equation
 54  * are determined.
 55  * @param {Object} config Configuration object. Default:
 56  * <pre>
 57  *  {
 58  *      resolution_out: 5,    // Horizontal resolution: distance between vertical lines to search for components
 59  *      resolution_in: 5,     // Vertical resolution to search for components
 60  *      max_steps: 1024,      // Max number of points in one call of tracing
 61  *      alpha_0: 0.05,        // Angle between two successive tangents: smoothness of curve
 62  *
 63  *      tol_u0: Mat.eps,      // Tolerance to find starting points for tracing.
 64  *      tol_newton: 1.0e-7,   // Tolerance for Newton steps.
 65  *      tol_cusp: 0.05,       // Tolerance for cusp / bifurcation detection
 66  *      tol_progress: 0.0001, // If two points are closer than this value, we bail out
 67  *      qdt_box: 0.2,         // half of box size to search in qdt
 68  *      kappa_0: 0.2,         // Inverse of planned number of Newton steps
 69  *      delta_0: 0.05,        // Distance of predictor point to curve
 70  *
 71  *      h_initial: 0.1,       // Initial stepwidth
 72  *      h_critical: 0.001,    // If h is below this threshold we bail out
 73  *      h_max: 1,             // Maximal value of h (user units)
 74  *      loop_dist: 0.09,      // Allowed distance (multiplied by actual stepwidth) to detect loop
 75  *      loop_dir: 0.99,       // Should be > 0.95
 76  *      loop_detection: true, // Use Gosper's loop detector
 77  *      unitX: 10,            // unitX of board
 78  *      unitY: 10             // unitX of board
 79  *   };
 80  * </pre>
 81  * @param {function} f function from <b>R</b><sup>2</sup> to <b>R</b>
 82  * @param {function} [dfx] Optional partial derivative of <i>f</i> with regard to <i>x</i>
 83  * @param {function} [dfy] Optional partial derivative of <i>f</i> with regard to <i>y</i>
 84  *
 85  * @constructor
 86  * @example
 87  *     var f = (x, y) => x**3 - 2 * x * y + y**3;
 88  *     var c = board.create('curve', [[], []], {
 89  *             strokeWidth: 3,
 90  *             strokeColor: JXG.palette.red
 91  *         });
 92  *
 93  *     c.updateDataArray = function () {
 94  *         var bbox = this.board.getBoundingBox(),
 95  *             ip, cfg,
 96  *             ret = [],
 97  *             mgn = 1;
 98  *
 99  *         bbox[0] -= mgn;
100  *         bbox[1] += mgn;
101  *         bbox[2] += mgn;
102  *         bbox[3] -= mgn;
103  *
104  *         cfg = {
105  *             resolution_out: 5,
106  *             resolution_in: 5,
107  *             unitX: this.board.unitX,
108  *             unitY: this.board.unitX
109  *         };
110  *
111  *         this.dataX = [];
112  *         this.dataY = [];
113  *         ip = new JXG.Math.ImplicitPlot(bbox, cfg, f, null, null);
114  *         ret = ip.plot();
115  *         this.dataX = ret[0];
116  *         this.dataY = ret[1];
117  *     };
118  *     board.update();
119  * </pre><div id="JXGf3e8cd82-2b67-4efb-900a-471eb92b3b96" class="jxgbox" style="width: 300px; height: 300px;"></div>
120  * <script type="text/javascript">
121  *     (function() {
122  *         var board = JXG.JSXGraph.initBoard('JXGf3e8cd82-2b67-4efb-900a-471eb92b3b96',
123  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
124  *             var f = (x, y) => x**3 - 2 * x * y + y**3;
125  *             var c = board.create('curve', [[], []], {
126  *                     strokeWidth: 3,
127  *                     strokeColor: JXG.palette.red
128  *                 });
129  *
130  *             c.updateDataArray = function () {
131  *                 var bbox = this.board.getBoundingBox(),
132  *                     ip, cfg,
133  *                     ret = [],
134  *                     mgn = 1;
135  *
136  *                 bbox[0] -= mgn;
137  *                 bbox[1] += mgn;
138  *                 bbox[2] += mgn;
139  *                 bbox[3] -= mgn;
140  *
141  *                 cfg = {
142  *                     resolution_out: 5,
143  *                     resolution_in: 5,
144  *                     unitX: this.board.unitX,
145  *                     unitY: this.board.unitX
146  *                 };
147  *
148  *                 this.dataX = [];
149  *                 this.dataY = [];
150  *
151  *                 ip = new JXG.Math.ImplicitPlot(bbox, cfg, f, null, null);
152  *                 ret = ip.plot();
153  *
154  *                 this.dataX = ret[0];
155  *                 this.dataY = ret[1];
156  *             };
157  *             board.update();
158  *
159  *     })();
160  *
161  * </script><pre>
162  *
163  */
164 Mat.ImplicitPlot = function (bbox, config, f, dfx, dfy) {
165 
166     // Default values
167     var cfg_default = {
168         resolution_out: 5,    // Distance between vertical lines to search for components
169         resolution_in: 5,     // Distance between vertical lines to search for components
170         max_steps: 1024,      // Max number of points in one call of tracing
171         alpha_0: 0.05,        // Angle between two successive tangents: smoothness of curve
172 
173         tol_u0: Mat.eps,      // Tolerance to find starting points for tracing.
174         tol_newton: 1.0e-7,   // Tolerance for Newton steps.
175         tol_cusp: 0.05,       // Tolerance for cusp / bifurcation detection
176         tol_progress: 0.0001, // If two points are closer than this value, we bail out
177         qdt_box: 0.2,         // half of box size to search in qdt
178         kappa_0: 0.2,         // Inverse of planned number of Newton steps
179         delta_0: 0.05,        // Distance of predictor point to curve
180 
181         h_initial: 0.1,       // Initial step width
182         h_critical: 0.001,    // If h is below this threshold we bail out
183         h_max: 1,             // Maximum value of h (user units)
184         loop_dist: 0.09,      // Allowed distance (multiplied by actual step width) to detect loop
185         loop_dir: 0.99,       // Should be > 0.95
186         loop_detection: true, // Use Gosper's loop detector
187         unitX: 10,            // unitX of board
188         unitY: 10             // unitX of board
189     };
190 
191     this.config = Type.merge(cfg_default, config);
192 
193     this.f = f;
194 
195     this.dfx = null;
196     this.dfy = null;
197 
198     if (Type.isFunction(dfx)) {
199         this.dfx = dfx;
200     } else {
201         this.dfx = function (x, y) {
202             var h = Mat.eps * Mat.eps;
203             return (this.f(x + h, y) - this.f(x - h, y)) * 0.5 / h;
204         };
205     }
206 
207     if (Type.isFunction(dfy)) {
208         this.dfy = dfy;
209     } else {
210         this.dfy = function (x, y) {
211             var h = Mat.eps * Mat.eps;
212             return (this.f(x, y + h) - this.f(x, y - h)) * 0.5 / h;
213         };
214     }
215 
216     this.bbox = bbox;
217     this.qdt = new Quadtree(20, 5, bbox);
218 
219     this.components = [];
220 };
221 
222 Type.extend(
223     Mat.ImplicitPlot.prototype,
224     /** @lends JXG.Math.ImplicitPlot.prototype */ {
225 
226         /**
227          * Implicit plotting method.
228          *
229          * @returns {Array} consisting of [dataX, dataY, number_of_components]
230          */
231         plot: function () {
232             var // components = [],
233                 doVerticalSearch = true,
234                 doHorizontalSearch = true,
235                 x, y,
236                 mi_x, ma_x, mi_y, ma_y,
237                 dataX = [],
238                 dataY = [],
239                 ret = [],
240                 num_components = 0,
241 
242                 delta,
243                 that = this,
244 
245                 fmi_x = function (t) {
246                     return that.f(x, t);
247                 },
248                 fma_x = function (t) {
249                     return -that.f(x, t);
250                 },
251                 fmi_y = function (t) {
252                     return that.f(t, y);
253                 },
254                 fma_y = function (t) {
255                     return -that.f(t, y);
256                 };
257 
258             // Vertical lines or circular search:
259             mi_x = Math.min(this.bbox[0], this.bbox[2]) - Mat.eps;
260             ma_x = Math.max(this.bbox[0], this.bbox[2]);
261             mi_y = Math.min(this.bbox[1], this.bbox[3]) + Mat.eps;
262             ma_y = Math.max(this.bbox[1], this.bbox[3]);
263 
264             if (doVerticalSearch) {
265                 delta = this.config.resolution_out / this.config.unitX;
266                 delta *= (1 + Mat.eps);
267                 // console.log("Outer delta x", delta)
268 
269                 for (x = mi_x; x < ma_x; x += delta) {
270                     ret = this.searchLine(
271                         fmi_x, fma_x, x,
272                         [mi_y, ma_y], 'vertical',
273                         num_components, dataX, dataY);
274 
275                     if (ret !== false) {
276                         dataX = ret[0];
277                         dataY = ret[1];
278                         num_components = ret[2];
279                     }
280 
281                 }
282             }
283             if (doHorizontalSearch) {
284                 delta = this.config.resolution_out / this.config.unitY;
285                 delta *= (1 + Mat.eps);
286                 // console.log("Outer delta y", delta)
287 
288                 for (y = mi_y; y < ma_y; y += delta) {
289                     ret = this.searchLine(
290                         fmi_y, fma_y, y,
291                         [mi_x, ma_x], 'horizontal',
292                         num_components, dataX, dataY);
293 
294                     if (ret !== false) {
295                         dataX = ret[0];
296                         dataY = ret[1];
297                         num_components = ret[2];
298                     }
299                 }
300             }
301 
302             return [dataX, dataY, num_components];
303         },
304 
305         /**
306          * Recursively search a horizontal or vertical line for points on the
307          * fulfilling the given equation.
308          *
309          * @param {Function} fmi Minimization function
310          * @param {Function} fma Maximization function
311          * @param {Number} fix Value of the fixed variable
312          * @param {Array} interval Search interval of the free variable
313          * @param {String} dir 'vertical' or 'horizontal'
314          * @param {Number} num_components Number of components before search
315          * @param {Array} dataX x-coordinates of points so far
316          * @param {Array} dataY y-coordinates of points so far
317          * @returns {Array} consisting of [dataX, dataY, number_of_components]-
318          * @private
319          */
320         searchLine: function (fmi, fma, fix, interval, dir,
321             num_components, dataX, dataY) {
322             var t_mi, t_ma, t,
323                 ft,
324                 mi, ma, tmp, m,
325                 is_in,
326                 u0, i, le,
327                 ret,
328                 offset,
329                 delta,
330                 eps = this.config.tol_u0,
331                 DEBUG = false,
332                 b = interval[0],
333                 e = interval[1];
334 
335             t_mi = Numerics.fminbr(fmi, [b, e]);
336             mi = fmi(t_mi);
337             t_ma = Numerics.fminbr(fma, [b, e]);
338             ma = fmi(t_ma);
339 
340             if (mi < eps && ma > -eps) {
341                 tmp = t_mi;
342                 t_mi = Math.min(tmp, t_ma);
343                 t_ma = Math.max(tmp, t_ma);
344 
345                 t = Numerics.fzero(fmi, [t_mi, t_ma]);
346                 // t = Numerics.chandrupatla(fmi, [t_mi, t_ma]);
347 
348                 ft = fmi(t);
349                 if (Math.abs(ft) > Math.max((ma - mi) * Mat.eps, 0.001)) {
350                     //console.log("searchLine:",  dir, fix, t, "no root " + ft);
351                     return false;
352                     // throw new Error("searchLine: no root " + ft);
353                 }
354                 if (dir === 'vertical') {
355                     u0 = [1, fix, t];
356                     delta = this.config.resolution_in / this.config.unitY;
357                     // console.log("Inner delta x", delta)
358                 } else {
359                     u0 = [1, t, fix];
360                     delta = this.config.resolution_in / this.config.unitX;
361                     // console.log("Inner delta y", delta)
362                 }
363                 delta *= (1 + Mat.eps);
364 
365                 is_in = this.curveContainsPoint(u0, dataX, dataY,
366                     delta * 2,           // Allowed dist from segment
367                     this.config.qdt_box  // 0.5 of box size to search in qdt
368                 );
369 
370                 if (is_in) {
371                     if (DEBUG) {
372                         console.log("Found in quadtree", u0);
373                     }
374                 } else {
375                     if (DEBUG) {
376                         console.log("Not in quadtree", u0, dataX.length);
377                     }
378                     ret = this.traceComponent(u0, 1);
379                     if (ret.length > 0) {
380                         // Add jump in curve
381                         if (num_components > 0) {
382                             dataX.push(NaN);
383                             dataY.push(NaN);
384                         }
385 
386                         offset = dataX.length;
387                         le = ret[0].length;
388                         for (i = 1; i < le; i++) {
389                             this.qdt.insertItem({
390                                 xlb: Math.min(ret[0][i - 1], ret[0][i]),
391                                 xub: Math.max(ret[0][i - 1], ret[0][i]),
392                                 ylb: Math.min(ret[1][i - 1], ret[1][i]),
393                                 yub: Math.max(ret[1][i - 1], ret[1][i]),
394                                 idx1: offset + i - 1,
395                                 idx2: offset + i,
396                                 comp: num_components
397                             });
398                         }
399 
400                         num_components++;
401                         Type.concat(dataX, ret[0]);
402                         Type.concat(dataY, ret[1]);
403                     }
404                 }
405 
406                 m = t - delta * 0.01;
407                 if (m - b > delta) {
408                     ret = this.searchLine(
409                         fmi, fma, fix, [b, m], dir,
410                         num_components, dataX, dataY);
411                     if (ret !== false) {
412                         dataX = ret[0];
413                         dataY = ret[1];
414                         num_components = ret[2];
415                     }
416                 }
417                 m = t + delta * 0.01;
418                 if (e - m > delta) {
419                     ret = this.searchLine(
420                         fmi, fma, fix, [m, e], dir,
421                         num_components, dataX, dataY);
422                     if (ret !== false) {
423                         dataX = ret[0];
424                         dataY = ret[1];
425                         num_components = ret[2];
426                     }
427                 }
428 
429                 return [dataX, dataY, num_components];
430             }
431 
432             return false;
433         },
434 
435         /**
436          * Test if the data points contain a given coordinate, i.e. if the
437          * given coordinate is close enough to the polygonal chain
438          * through the data points.
439          *
440          * @param {Array} p Homogenous coordinates [1, x, y] of the coordinate point
441          * @param {Array} dataX x-coordinates of points so far
442          * @param {Array} dataY y-coordinates of points so far
443          * @param {Number} tol Maximal distance of p from the polygonal chain through the data points
444          * @param {Number} eps Helper tolerance used for the quadtree
445          * @returns Boolean
446          */
447         curveContainsPoint: function (p, dataX, dataY, tol, eps) {
448             var i, le, hits, d,
449                 x = p[1],
450                 y = p[2];
451 
452             hits = this.qdt.find([x - eps, y + eps, x + eps, y - eps]);
453 
454             le = hits.length;
455             for (i = 0; i < le; i++) {
456                 d = Geometry.distPointSegment(
457                     p,
458                     [1, dataX[hits[i].idx1], dataY[hits[i].idx1]],
459                     [1, dataX[hits[i].idx2], dataY[hits[i].idx2]]
460                 );
461                 if (d < tol) {
462                     return true;
463                 }
464             }
465             return false;
466         },
467 
468         /**
469          * Starting at an initial point the curve is traced with a Euler-Newton method.
470          * After tracing in one direction the algorithm stops if the component is a closed loop.
471          * Otherwise, the curved is traced in the opposite direction, starting from
472          * the same initial point. Finally, the two components are glued together.
473          *
474          * @param {Array} u0 Initial point in homogenous coordinates [1, x, y].
475          * @returns Array [dataX, dataY] containing a new component.
476          * @private
477          */
478         traceComponent: function (u0) {
479             var dataX = [],
480                 dataY = [],
481                 arr = [];
482 
483             // Trace in first direction
484             // console.log("---- Start tracing forward ---------")
485             arr = this.tracing(u0, 1);
486 
487             if (arr.length === 0) {
488                 // console.log("Could not start tracing due to singularity")
489             } else {
490                 // console.log("Trace from", [arr[0][0], arr[1][0]], "to", [arr[0][arr[0].length - 1], arr[1][arr[1].length - 1]],
491                 //     "num points:", arr[0].length);
492                 dataX = arr[0];
493                 dataY = arr[1];
494             }
495 
496             // Trace in the other direction
497             if (!arr[2]) {
498                 // No loop in the first tracing step,
499                 // now explore the other direction.
500 
501                 // console.log("---- Start tracing backward ---------")
502                 arr = this.tracing(u0, -1);
503 
504                 if (arr.length === 0) {
505                     // console.log("Could not start backward tracing due to singularity")
506                 } else {
507                     // console.log("Trace backwards from", [arr[0][0], arr[1][0]], "to",
508                     //     [arr[0][arr[0].length - 1], arr[1][arr[1].length - 1]], "num points:", arr[0].length);
509                     dataX = arr[0].reverse().concat(dataX.slice(1));
510                     dataY = arr[1].reverse().concat(dataY.slice(1));
511                 }
512             }
513 
514             if (dataX.length < 6) {
515                 // Solitary point
516                 dataX.push(dataX[dataX.length - 1]);
517                 dataY.push(dataY[dataY.length - 1]);
518             }
519 
520             return [dataX, dataY];
521         },
522 
523         /**
524          * Starting at a point <i>u0</i>, this routine traces the curve <i>f(u)=0</i> until
525          * a loop is detected, a critical point is reached, the curve leaves the bounding box,
526          * or the maximum number of points is reached.
527          * <p>
528          * The method is a predictor / corrector method consisting of Euler and Newton steps
529          * together with step width adaption.
530          * <p>
531          * The algorithm is an adaption of the algorithm in
532          * Eugene L. Allgower, Kurt Georg: <i>Introduction to Numerical Continuation methods.</i>
533          *
534          * @param {Array} u0 Starting point in homogenous coordinates  [1, x, y].
535          * @param {Number} direction 1 or -1
536          * @returns Array [pathX, pathY, loop_closed] or []
537          * @private
538          */
539         tracing: function (u0, direction) {
540             var u = [],
541                 v = [],
542                 v_start = [],
543                 w = [],
544                 t_u, t_v, t_u_0,
545                 A,
546                 grad,
547                 nrm,
548                 dir,
549                 steps = 0,
550                 k = 0,
551                 loop_closed = false,
552                 k0, k1, denom, dist, progress,
553                 kappa, delta, alpha,
554                 factor,
555                 point_added = false,
556 
557                 quasi = false,
558                 cusp_or_bifurc = false,
559                 kappa_0 = this.config.kappa_0,  // Inverse of planned number of Newton steps
560                 delta_0 = this.config.delta_0,  // Distance of predictor point to curve
561                 alpha_0 = this.config.alpha_0,  // Angle between two successive tangents
562                 h = this.config.h_initial,
563                 max_steps = this.config.max_steps,
564 
565                 omega = direction,
566                 pathX = [],
567                 pathY = [],
568 
569                 T = [],            // Gosper's loop detector table
570                 n, m, i, e;
571 
572             u = u0.slice(1);
573             pathX.push(u[0]);
574             pathY.push(u[1]);
575 
576             t_u = this.tangent(u);
577             if (t_u === false) {
578                 // We don't want to start at a singularity.
579                 // Get out of here and search for another starting point.
580                 return [];
581             }
582             A = [this.dfx(u[0], u[1]), this.dfy(u[0], u[1])];
583 
584             do {
585 
586                 if (quasi) {
587                     t_u = this.tangent_A(A);
588                 } else {
589                     t_u = this.tangent(u);
590                 }
591                 if (t_u === false) {
592                     u = v.slice();
593                     pathX.push(u[0]);
594                     pathY.push(u[1]);
595                     // console.log("-> Bail out: t_u undefined.");
596                     break;
597                 }
598 
599                 if (pathX.length === 1) {
600                     // Store first point
601                     t_u_0 = t_u.slice();
602                 } else if (pathX.length === 2) {
603                     T.push(pathX.length - 1);       // Put first point into Gosper table T
604 
605                 } else if (point_added && pathX.length > 2 && !cusp_or_bifurc) {
606 
607                     // Detect if loop has been closed
608                     dist = Geometry.distPointSegment(
609                         [1, u[0], u[1]],
610                         [1, pathX[0], pathY[0]],
611                         [1, pathX[1], pathY[1]]
612                     );
613 
614                     if (dist < this.config.loop_dist * h &&
615                         Mat.innerProduct(t_u, t_u_0, 2) > this.config.loop_dir
616                     ) {
617 
618                         // console.log("Loop detected after", steps, "steps");
619                         // console.log("\t", "v", v, "u0:", u0)
620                         // console.log("\t", "Dist(v, path0)", dist, config.loop_dist * h)
621                         // console.log("\t", "t_u", t_u);
622                         // console.log("\t", "inner:", Mat.innerProduct(t_u, t_u_0, 2));
623                         // console.log("\t", "h", h);
624 
625                         u = u0.slice(1);
626                         pathX.push(u[0]);
627                         pathY.push(u[1]);
628 
629                         loop_closed = true;
630                         break;
631                     }
632 
633                     // Gosper's loop detector
634                     if (this.config.loop_detection) {
635                         n = pathX.length - 1;
636                         // console.log("Check Gosper", n);
637                         m = Math.floor(Mat.log2(n));
638 
639                         for (i = 0; i <= m; i++) {
640                             dist = Geometry.distPointSegment(
641                                 [1, u[0], u[1]],
642                                 [1, pathX[T[i] - 1], pathY[T[i] - 1]],
643                                 [1, pathX[T[i]], pathY[T[i]]]
644                             );
645 
646                             if (dist < this.config.loop_dist * h) {
647                                 // console.log("!!!!!!!!!!!!!!! GOSPER LOOP CLOSED !!!!", i, n + 1,
648                                 //     this.config.loop_dist * h
649                                 // );
650 
651                                 t_v = this.tangent([pathX[T[i]], pathY[T[i]]]);
652                                 if (Mat.innerProduct(t_u, t_v, 2) > this.config.loop_dir) {
653                                     // console.log("!!!!!!!!!!!!!!! angle is good enough");
654                                     break;
655                                 }
656                             }
657                         }
658                         if (i <= m) {
659                             loop_closed = true;
660                             break;
661                         }
662 
663                         m = 1;
664                         e = 0;
665                         for (i = 0; i < 100; i++) {
666                             if ((n + 1) % m !== 0) {
667                                 break;
668                             }
669                             m *= 2;
670                             e++;
671                         }
672                         // console.log("Add at e", e);
673                         T[e] = n;
674                     }
675 
676                 }
677 
678                 // Predictor step
679                 // if (true /*h < 2 * this.config.h_initial*/) {
680                 // Euler
681                 // console.log("euler")
682                 v[0] = u[0] + h * omega * t_u[0];
683                 v[1] = u[1] + h * omega * t_u[1];
684                 // } else {
685                 //     // Heun
686                 //     // console.log("heun")
687                 //     v[0] = u[0] + h * omega * t_u[0];
688                 //     v[1] = u[1] + h * omega * t_u[1];
689 
690                 //     t_v = this.tangent(v);
691                 //     v[0] = 0.5 * u[0] + 0.5 * (v[0] + h * omega * t_v[0]);
692                 //     v[1] = 0.5 * u[1] + 0.5 * (v[1] + h * omega * t_v[1]);
693                 // }
694                 if (quasi) {
695                     A = this.updateA(A, u, v);
696                     v_start = v.slice();
697                 }
698 
699                 // Corrector step: Newton
700                 k = 0;
701                 do {
702                     if (quasi) {
703                         grad = A;
704                     } else {
705                         grad = [this.dfx(v[0], v[1]), this.dfy(v[0], v[1])];
706                     }
707 
708                     // Compute w = v - A(v) * f(v),
709                     // grad: row vector and A(v) is the Moore-Penrose inverse:
710                     // grad^T * (grad * grad^T)^(-1)
711                     denom = grad[0] * grad[0] + grad[1] * grad[1];
712                     nrm = this.f(v[0], v[1]) / denom;
713 
714                     w[0] = v[0] - grad[0] * nrm;
715                     w[1] = v[1] - grad[1] * nrm;
716                     if (k === 0) {
717                         k0 = Math.abs(nrm) * Math.sqrt(denom);
718                     } else if (k === 1) {
719                         k1 = Math.abs(nrm) * Math.sqrt(denom);
720                     }
721 
722                     v[0] = w[0];
723                     v[1] = w[1];
724                     k++;
725                 } while (k < 20 &&
726                     Math.abs(this.f(v[0], v[1])) > this.config.tol_newton
727                 );
728 
729                 delta = k0;
730                 if (k > 1) {
731                     kappa = k1 / k0;
732                 } else {
733                     kappa = 0.0;
734                 }
735 
736                 if (quasi) {
737                     A = this.updateA(A, v_start, v);
738                     t_v = this.tangent_A(A);
739                 } else {
740                     t_v = this.tangent(v);
741                 }
742 
743                 dir = Mat.innerProduct(t_u, t_v, 2);
744                 dir = Math.max(-1, Math.min(1, dir));
745                 alpha = Math.acos(dir);
746 
747                 // Look for simple bifurcation points and cusps
748                 cusp_or_bifurc = false;
749                 progress = Geometry.distance(u, v, 2);
750                 if (progress < this.config.tol_progress) {
751                     u = v.slice();
752                     pathX.push(u[0]);
753                     pathY.push(u[1]);
754                     // console.log("-> Bail out, no progress", progress, steps);
755                     break;
756 
757                 } else if (dir < 0.0) {
758                     if (h > this.config.h_critical) {
759                         // console.log("Critical point at [", u[0].toFixed(4), u[1].toFixed(4), "], v: [", v[0].toFixed(4), v[1].toFixed(4), "], but large  h:", h);
760 
761                     } else {
762 
763                         cusp_or_bifurc = true;
764                         if (this.isBifurcation(u, this.config.tol_cusp)) {
765                             // console.log(steps, "bifurcation point between", u, "and", v, ":", dir, "h", h, "alpha", alpha);
766                             // A = [dfx(v[0], v[1]), dfy(v[0], v[1])];
767                             omega *= (-1);
768                             // If there is a bifurcation point, we
769                             // ignore the angle alpha for subsequent step length
770                             // adaption. Because then we might be able to
771                             // "jump over the critical point"
772                             alpha = 0;
773                         } else {
774                             // Cusp or something more weird
775                             u = v.slice();
776                             pathX.push(u[0]);
777                             pathY.push(u[1]);
778                             // console.log("-> Bail out, cusp")
779                             break;
780                         }
781                     }
782                 }
783 
784                 // Adapt stepwidth
785                 if (!cusp_or_bifurc) {
786                     factor = Math.max(
787                         Math.sqrt(kappa / kappa_0),
788                         Math.sqrt(delta / delta_0),
789                         alpha / alpha_0
790                     );
791                     if (isNaN(factor)) {
792                         factor = 1;
793                     }
794                     factor = Math.max(Math.min(factor, 2), 0.5);
795                     h /= factor;
796                     h = Math.min(this.config.h_max, h);
797 
798                     if (factor >= 2) {
799                         steps++;
800                         if (steps >= 3 * max_steps) {
801                             break;
802                         }
803 
804                         point_added = false;
805                         continue;
806                     }
807                 }
808 
809                 u = v.slice();
810                 pathX.push(u[0]);
811                 pathY.push(u[1]);
812                 point_added = true;
813 
814                 steps++;
815             } while (
816                 steps < max_steps &&
817                 u[0] >= this.bbox[0] &&
818                 u[1] <= this.bbox[1] &&
819                 u[0] <= this.bbox[2] &&
820                 u[1] >= this.bbox[3]
821             );
822 
823             // if (!loop_closed) {
824             //     console.log("No loop", steps);
825             // } else {
826             //     console.log("Loop", steps);
827             // }
828 
829             return [pathX, pathY, loop_closed];
830         },
831 
832         /**
833          * If both eigenvalues of the Hessian are different from zero, the critical point at u
834          * is a simple bifurcation point.
835          *
836          * @param {Array} u Critical point [x, y]
837          * @param {Number} tol Tolerance of the eigenvalues to be zero.
838          * @returns Boolean True if the point is a simple bifurcation point.
839          * @private
840          */
841         isBifurcation: function (u, tol) {
842             // Former experiments:
843             // If the Hessian has exactly one zero eigenvalue,
844             // we claim that there is a cusp.
845             // Otherwise, we decide that there is a bifurcation point.
846             // In the latter case, if both eigenvalues are zero
847             // this is a somewhat crude decision.
848             //
849             var h = Mat.eps * Mat.eps * 100,
850                 x, y, a, b, c, d, ad,
851                 lbda1, lbda2,
852                 dis;
853 
854             x = u[0];
855             y = u[1];
856             a = 0.5 * (this.dfx(x + h, y) - this.dfx(x - h, y)) / h;
857             b = 0.5 * (this.dfx(x, y + h) - this.dfx(x, y - h)) / h;
858             c = 0.5 * (this.dfy(x + h, y) - this.dfy(x - h, y)) / h;
859             d = 0.5 * (this.dfy(x, y + h) - this.dfy(x, y - h)) / h;
860 
861             // c = b
862             ad = a + d;
863             dis = ad * ad - 4 * (a * d - b * c);
864             lbda1 = 0.5 * (ad + Math.sqrt(dis));
865             lbda2 = 0.5 * (ad - Math.sqrt(dis));
866 
867             // console.log(a, b, c, d)
868             // console.log("Eigenvals u:", lbda1, lbda2, tol);
869 
870             if (Math.abs(lbda1) > tol && Math.abs(lbda2) > tol) {
871                 // if (lbda1 * lbda2 > 0) {
872                 //     console.log("Seems to be isolated singularity at", u);
873                 // }
874                 return true;
875             }
876 
877             return false;
878         },
879 
880         /**
881          * Search in an arc around a critical point for a further point on the curve.
882          * Unused for the moment.
883          *
884          * @param {Array} u Critical point [x, y]
885          * @param {Array} t_u Tangent at u
886          * @param {Number} r Radius
887          * @param {Number} omega angle
888          * @returns {Array} Coordinates [x, y] of a new point.
889          * @private
890          */
891         handleCriticalPoint: function (u, t_u, r, omega) {
892             var a = Math.atan2(omega * t_u[1], omega * t_u[0]),
893                 // s = a - 0.75 * Math.PI,
894                 // e = a + 0.75 * Math.PI,
895                 f_circ = function (t) {
896                     var x = u[0] + r * Math.cos(t),
897                         y = u[1] + r * Math.sin(t);
898                     return this.f(x, y);
899                 },
900                 x, y, t0;
901 
902             // t0 = Numerics.fzero(f_circ, [s, e]);
903             t0 = Numerics.root(f_circ, a);
904 
905             x = u[0] + r * Math.cos(t0);
906             y = u[1] + r * Math.sin(t0);
907             // console.log("\t", "result", x, y, "f", f(x, y));
908 
909             return [x, y];
910         },
911 
912         /**
913          * Quasi-Newton update of the Moore-Penrose inverse.
914          * See (7.2.3) in Allgower, Georg.
915          *
916          * @param {Array} A
917          * @param {Array} u0
918          * @param {Array} u1
919          * @returns Array
920          * @private
921          */
922         updateA: function (A, u0, u1) {
923             var s = [u1[0] - u0[0], u1[1] - u0[1]],
924                 y = this.f(u1[0], u1[1]) - this.f(u0[0], u0[1]),
925                 nom, denom;
926 
927             denom = s[0] * s[0] + s[1] * s[1];
928             nom = y - (A[0] * s[0] + A[1] * s[1]);
929             nom /= denom;
930             A[0] += nom * s[0];
931             A[1] += nom * s[1];
932 
933             return A;
934         },
935 
936         /**
937          * Approximate tangent (of norm 1) with Quasi-Newton method
938          * @param {Array} A
939          * @returns Array
940          * @private
941          */
942         tangent_A: function (A) {
943             var t = [-A[1], A[0]],
944                 nrm = Mat.norm(t, 2);
945 
946             if (nrm < Mat.eps) {
947                 // console.log("Approx. Singularity", t, "is zero", nrm);
948             }
949             return [t[0] / nrm, t[1] / nrm];
950         },
951 
952         /**
953          * Tangent of norm 1 at point u.
954          * @param {Array} u Point [x, y]
955          * @returns Array
956          * @private
957          */
958         tangent: function (u) {
959             var t = [-this.dfy(u[0], u[1]), this.dfx(u[0], u[1])],
960                 nrm = Mat.norm(t, 2);
961 
962             if (nrm < Mat.eps * Mat.eps) {
963                 // console.log("Singularity", t, "is zero", "at", u, ":", nrm);
964                 return false;
965             }
966             return [t[0] / nrm, t[1] / nrm];
967         }
968     }
969 
970 );
971 
972 export default Mat.ImplicitPlot;
973 
974