curve_sampling/
lib.rs

1//! This module provide a collection of routines to perform adaptive
2//! sampling of curves as well as manipulating those samplings.
3//!
4//! As of now, the following is available:
5//! - uniform sampling of the graph of functions ℝ → ℝ
6//!   (see [`Sampling::uniform`]);
7//! - adaptive sampling of the graph functions ℝ → ℝ
8//!   (see [`Sampling::fun`]);
9//! - adaptive sampling of the image functions ℝ → ℝ²
10//!   (see [`Sampling::param`]).
11//!
12//! Samplings can be saved as a list of coordinates x-y, one point per
13//! line with blank lines to indicate that the path is interrupted,
14//! with [`Sampling::write`].  They can also be saved in a format
15//! usable from the [LaTeX][] package [TikZ][] using
16//! [`Sampling::latex`].  This format allows to add arrows to the
17//! curves to indicate their orientation.
18//!
19//! [LaTeX]: https://www.latex-project.org/
20//! [TikZ]: https://tikz.dev/
21
22use std::{fmt::{self, Display, Formatter},
23          cell::Cell,
24          io::{self, Write},
25          iter::Iterator,
26          ops::ControlFlow};
27use rgb::*;
28
29// mod fibonacci_heap;
30// use fibonacci_heap as pq;
31// mod priority_queue;
32// use priority_queue as pq;
33mod approx_priority_queue;
34use approx_priority_queue as pq;
35
36mod list;
37use list::List;
38
39////////////////////////////////////////////////////////////////////////
40//
41// Bounding box
42
43/// A two dimensional rectangle \[`xmin`, `xmax`\] × \[`ymin`, `ymax`\].
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub struct BoundingBox {
46    pub xmin: f64,
47    pub xmax: f64,
48    pub ymin: f64,
49    pub ymax: f64,
50}
51
52impl BoundingBox {
53    #[inline]
54    pub(crate) fn empty() -> Self {
55        Self { xmin: f64::INFINITY,  xmax: f64::NEG_INFINITY,
56               ymin: f64::INFINITY,  ymax: f64::NEG_INFINITY }
57    }
58
59    /// Return `true` if the bounding box has a non-empty interior.
60    #[inline]
61    pub fn is_empty(&self) -> bool {
62        !(self.xmin < self.xmax && self.ymin < self.ymax) // NAN ⟹ empty
63    }
64
65    /// Return `true` if the point `p` belongs to `bb` (possibly on
66    /// the boundary).
67    #[inline]
68    fn contains(&self, p: &Point) -> bool {
69        let [x, y] = p.xy;
70        self.xmin <= x && x <= self.xmax && self.ymin <= y && y <= self.ymax
71    }
72
73    /// Return the smaller bounding-box containing both `self` and
74    /// `other`.
75    #[inline]
76    pub fn hull(&self, other: &Self) -> Self {
77        BoundingBox { xmin: self.xmin.min(other.xmin),
78                      xmax: self.xmax.max(other.xmax),
79                      ymin: self.ymin.min(other.ymin),
80                      ymax: self.ymax.max(other.ymax) }
81    }
82}
83
84
85////////////////////////////////////////////////////////////////////////
86//
87// Sampling datastructure
88
89/// A 2D point with coordinates (`x`, `y`) supposed to be given by a
90/// function evaluated at "time" `t`.  A path is a sequence of points.
91/// Points that are invalid (see [`Point::is_valid`]) are considered
92/// cuts in the path (the line is interrupted).  As most 2 cuts may
93/// follow each other (to track ranges of t outside the domain of the
94/// function) except at the boundary of the path where at most 1 cut
95/// is allowed.
96#[derive(Debug)]
97struct Point {
98    t: f64, // "time" parameter, ALWAYS finite.
99    xy: [f64; 2],
100    cost: f64, // Cache the cost of the point (not the segment).  If
101               // the point is not valid, the cost has no meaning.
102
103    // Pointer to the priority queue node if the point is the initial
104    // point of a segment.  `None` otherwise.
105    witness: Option<pq::Witness<list::Witness<Point>>>,
106}
107
108impl Clone for Point {
109    // As a cloned point may likely go to another path, reset the
110    // priority queue pointer.
111    fn clone(&self) -> Self {
112        Self { witness: None, .. *self }
113    }
114}
115
116impl Point {
117    /// Return a new point.  `t` is assumed to be finite.
118    #[inline]
119    fn new_unchecked(t: f64, xy: [f64; 2]) -> Self {
120        Point { t, xy,  cost: 0.,
121                witness: None }
122    }
123
124    #[inline]
125    fn cut(t: f64) -> Self {
126        Point { t, xy: [f64::NAN; 2],
127                cost: 0.,
128                witness: None }
129    }
130
131    /// Return `true` if the point is valid — otherwise it is a cut.
132    #[inline]
133    fn is_valid(&self) -> bool {
134        self.xy.iter().all(|z| z.is_finite())
135    }
136
137}
138
139/// A 2D sampling.  This can be thought as a path, with possible
140/// "cuts" because of discontinuities or leaving the domain of the
141/// (parametric) function describing the path.
142pub struct Sampling {
143    // Priority queue of segments.  The value points to the first
144    // `Point` of the segment (thus its .next pointer must not be
145    // null).  At least one of the endpoint of all segments must be
146    // valid (see `Point::is_valid`) If `pq` is empty but `begin` is
147    // not null, it means that the costs need to to be updated.
148    pq: PQ,
149    path: List<Point>,
150    // Guess on the path length (to allocate vectors) without making
151    // `self` mutable (which the caller would not understand).
152    guess_len: Cell<usize>,
153    vp: Option<BoundingBox>, // viewport (zone of interest)
154}
155
156type PQ = pq::PQ<list::Witness<Point>>;
157
158impl Clone for Sampling {
159    fn clone(&self) -> Self {
160        // It is guessed that one clones the sampling to transform it.
161        // Thus start with an empty queue.
162        Self { pq: PQ::new(),
163               path: self.path.clone(),
164               guess_len: self.guess_len.get().into(),
165               vp: self.vp }
166    }
167}
168
169/// `t` is the length of total range of time, `x` and `y` are the
170/// dimensions of the bounding box.
171#[derive(Debug, Clone, Copy)]
172struct Lengths {
173    t: f64,
174    x: f64,
175    y: f64,
176}
177
178impl Sampling {
179    /// Return `true` if the sampling contains no point.
180    #[inline]
181    pub fn is_empty(&self) -> bool { self.path.is_empty() }
182
183    /// Create an empty sampling.
184    #[inline]
185    pub(crate) fn empty() -> Self {
186        Self { pq: PQ::new(),
187               path: List::new(),
188               guess_len: 0.into(),
189               vp: None }
190    }
191
192    #[inline]
193    pub(crate) fn singleton(p: Point) -> Self {
194        debug_assert!(p.t.is_finite() && p.xy.iter().all(|z| z.is_finite()));
195        let mut path = List::new();
196        path.push_back(p);
197        Self { pq: PQ::new(), path, guess_len: 1.into(), vp: None }
198    }
199
200    #[inline]
201    pub(crate) fn set_vp(&mut self, bb: BoundingBox) {
202        self.vp = Some(bb);
203    }
204
205    /// Return the length of the "time interval" as well as the
206    /// lengths of the viewport.
207    pub(crate) fn len_txy(&self) -> Option<Lengths> {
208        if self.is_empty() { return None }
209        let p0 = self.path.first().unwrap();
210        let p1 = self.path.last().unwrap();
211        let len_x;
212        let len_y;
213        match self.vp {
214            Some(vp) => {
215                len_x = vp.xmax - vp.xmin;
216                len_y = vp.ymax - vp.ymin;
217            }
218            None => {
219                len_x = 1.;
220                len_y = 1.;
221            }
222        }
223        Some(Lengths { t: (p1.t - p0.t).abs(), x: len_x, y: len_y })
224    }
225
226    /// Returns an iterator on the points (and cuts) of the path.
227    /// More precisely, a path is made of continuous segments whose
228    /// points are given by contiguous values `[x,y]` with both `x`
229    /// and `y` not NaN, interspaced by "cuts" `[f64::NAN; 2]`.  Two
230    /// cuts never follow each other.  Isolated points `p` are given
231    /// by ... `[f64::NAN; 2]`, `p`, `None`,...
232    pub fn iter(&self) -> SamplingIter<'_> {
233        SamplingIter {
234            path: self.path.iter(),
235            prev_is_cut: true,
236            guess_len: self.guess_len.get(),
237        }
238    }
239
240    /// Returns an iterator that allows to modify the points and cuts
241    /// of the path.  Unlike [`iter`], this iterates on all the nodes
242    /// even if several cuts (i.e., node with a non finite coordinate)
243    /// follow each other.
244    pub fn iter_mut(&mut self) -> SamplingIterMut<'_> {
245        SamplingIterMut {
246            path: self.path.iter_mut(),
247            guess_len: self.guess_len.get(),
248        }
249    }
250
251    /// Iterator on the x-coordinates of the sampling.
252    /// See [`Self::iter`] for more information.
253    #[inline]
254    pub fn x(&self) -> Vec<f64> {
255        let mut v = Vec::with_capacity(self.guess_len.get());
256        for [x, _] in self.iter() { v.push(x) }
257        v
258    }
259
260    /// Iterator on the y-coordinates of the sampling.
261    /// See [`Self::iter`] for more information.
262    #[inline]
263    pub fn y(&self) -> Vec<f64> {
264        let mut v = Vec::with_capacity(self.guess_len.get());
265        for [_, y] in self.iter() { v.push(y) }
266        v
267    }
268}
269
270/// Iterator on the curve points (and cuts).
271///
272/// Created by [`Sampling::iter`].
273pub struct SamplingIter<'a> {
274    path: list::Iter<'a, Point>,
275    prev_is_cut: bool,
276    guess_len: usize,
277}
278
279impl<'a> Iterator for SamplingIter<'a> {
280    type Item = [f64; 2];
281
282    fn next(&mut self) -> Option<Self::Item> {
283        match self.path.next() {
284            None => None,
285            Some(p) => {
286                self.guess_len -= 1;
287                if p.is_valid() {
288                    self.prev_is_cut = false;
289                    Some(p.xy)
290                } else if self.prev_is_cut {
291                    // Find the next valid point.
292                    let r = self.path.try_fold(0, |n, p| {
293                        if p.is_valid() {
294                            ControlFlow::Break((n, p))
295                        } else {
296                            ControlFlow::Continue(n+1)
297                        }
298                    });
299                    match r {
300                        ControlFlow::Continue(_) => {
301                            // Iterator exhausted
302                            self.guess_len = 0;
303                            None
304                        }
305                        ControlFlow::Break((n, p)) => {
306                            self.guess_len -= n;
307                            self.prev_is_cut = false;
308                            Some(p.xy)
309                        }
310                    }
311                } else {
312                    self.prev_is_cut = true;
313                    Some([f64::NAN; 2])
314                }
315            }
316        }
317    }
318
319    fn size_hint(&self) -> (usize, Option<usize>) {
320        (0, Some(self.guess_len))
321    }
322}
323
324/// Mutable iterator on the curve points (and cuts).
325///
326/// Created by [`Sampling::iter_mut`].
327pub struct SamplingIterMut<'a> {
328    path: list::IterMut<'a, Point>,
329    guess_len: usize,
330}
331
332impl<'a> Iterator for SamplingIterMut<'a> {
333    type Item = &'a mut [f64; 2];
334
335    fn next(&mut self) -> Option<Self::Item> {
336        match self.path.next() {
337            None => None,
338            Some(p) => {
339                self.guess_len -= 1;
340                Some(&mut p.xy)
341            }
342        }
343    }
344}
345
346/// Intersection of a segment with the bounding box.
347#[derive(Debug)]
348enum Intersection {
349    Empty,
350    Pt(Point),
351    Seg(Point, Point),
352}
353
354impl Sampling {
355    /// Return the smallest rectangle enclosing all the points of the
356    /// sampling `self`.  If the path is empty, the "min" fields of
357    /// the bounding box are set to +∞ and "max" fields to -∞.
358    pub fn bounding_box(&self) -> BoundingBox {
359        let mut points = self.iter().skip_while(|[x,_]| x.is_nan());
360        let mut bb = match &points.next() {
361            Some([x, y]) => BoundingBox { xmin: *x,  xmax: *x,
362                                         ymin: *y,  ymax: *y },
363            None => return BoundingBox::empty()
364        };
365        for [x, y] in points {
366            if x < bb.xmin { bb.xmin = x }
367            else if bb.xmax < x { bb.xmax = x };
368            if y < bb.ymin { bb.ymin = y }
369            else if bb.ymax < y { bb.ymax = y };
370        }
371        bb
372    }
373
374    /// Transpose in place the x and y coordinates of the sampling.
375    pub fn transpose(&mut self) -> &mut Self {
376        for p in self.path.iter_mut() {
377            let [x, y] = p.xy;
378            p.xy = [y, x];
379        }
380        self
381    }
382
383    /// Assume `p0` ∈ `bb` and `p1` ∉ `bb`.  Return the point
384    /// intersecting the boundary of `bb`.  If the intersection point
385    /// is the same as `p0`, return `None`
386    #[inline]
387    #[must_use]
388    fn intersect(p0: &Point, p1: &Point, bb: BoundingBox) -> Option<Point> {
389        let mut t = 1.; // t ∈ [0, 1]
390        let [x0, y0] = p0.xy;
391        let [x1, y1] = p1.xy;
392        let dx = x1 - x0; // May be 0.
393        let r = (if dx >= 0. {bb.xmax} else {bb.xmin} - x0) / dx;
394        if r < t { t = r } // ⟹ r finite (as r ≥ 0 or NaN)
395        let dy = y1 - y0; // May be 0.
396        let r = (if dy >= 0. {bb.ymax} else {bb.ymin} - y0) / dy;
397        if r < t { t = r };
398        if t <= 1e-14 {
399            None
400        } else {
401            Some(Point::new_unchecked(p0.t + t * (p1.t - p0.t),
402                [x0 + t * dx, y0 + t * dy]))
403        }
404    }
405
406    /// Assume `p0` ∉ `bb` and `p1` ∉ `bb` (thus, not even on the
407    /// boundary of `bb`) and `p0` ≠ `p1`.  Return the endpoints of
408    /// the segment intersecting the boundary of `bb` if any.  The
409    /// "parameter direction" of `p0`, `p1` is preserved.
410    #[inline]
411    #[must_use]
412    fn intersect_seg(p0: &Point, p1: &Point, bb: BoundingBox) -> Intersection {
413        let mut t0 = 0.; // t0 ∈ [0, 1]
414        let mut t1 = 1.; // t1 ∈ [0, 1]
415        let [x0, y0] = p0.xy;
416        let [x1, y1] = p1.xy;
417        let dx = x1 - x0; // may be 0.
418        let r0;
419        let r1; // r0 ≤ r1 or NAN if on x-boundary lines.
420        if dx >= 0. {
421            r0 = (bb.xmin - x0) / dx;
422            r1 = (bb.xmax - x0) / dx;
423        } else {
424            r0 = (bb.xmax - x0) / dx;
425            r1 = (bb.xmin - x0) / dx;
426        }
427        if r0 > 1. || r1 < 0. { return Intersection::Empty }
428        if r0 > 0. { t0 = r0 } // if r0 is NAN, keep the whole segment
429        if r1 < 1. { t1 = r1 }
430        let dy = y1 - y0; // may be 0.
431        let r0;
432        let r1;
433        if dy >= 0. {
434            r0 = (bb.ymin - y0) / dy;
435            r1 = (bb.ymax - y0) / dy;
436        } else {
437            r0 = (bb.ymax - y0) / dy;
438            r1 = (bb.ymin - y0) / dy;
439        }
440        if r0 > t1 || r1 < t0 { return Intersection::Empty }
441        if r0 > t0 { t0 = r0 }
442        if r1 < t1 { t1 = r1 }
443        if t0 < t1 { // segment not reduced to a point
444            let dt = p1.t - p0.t;
445            let q0 = Point::new_unchecked(p0.t + t0 * dt,
446                [x0 + t0 * dx, y0 + t0 * dy]);
447            let q1 = Point::new_unchecked(p0.t + t1 * dt,
448                [x0 + t1 * dx, y0 + t1 * dy]);
449            Intersection::Seg(q0, q1)
450        } else if t0 == t1 {
451            let q0 = Point::new_unchecked(
452                p0.t + t0 * (p1.t - p0.t),
453                [x0 + t0 * dx, y0 + t0 * dy]);
454            Intersection::Pt(q0)
455        } else {
456            Intersection::Empty
457        }
458    }
459
460    /// Returns the sampling `self` but clipped to the 2D box `bb`.  A
461    /// path that crosses the boundary will get additional nodes at
462    /// the points of crossing and the part outside the bounding box
463    /// will be dropped.  (Thus a path entirely out of the bounding
464    /// box will be removed.)
465    #[must_use]
466    pub fn clip(&self, bb: BoundingBox) -> Self {
467        let mut s = Sampling::empty();
468        let mut new_len: usize = 0;
469        // First point of the current segment, if any.
470        let mut p0_opt: Option<&Point> = None;
471        let mut p0_inside = false;
472        let mut prev_cut = true; // New path ("virtual" cut)
473        for p1 in self.path.iter() {
474            if prev_cut {
475                // A cut was pushed at an earlier step (and the path
476                // has not subsequently intersected `bb`).  This may
477                // be because the original path was just cut (`p0_opt
478                // = None`) or because the previous segment was cut
479                // (`p0_opt = Some(p0)` with `p0` ∉ `bb`) or because
480                // there was an earlier cut and the next point `p0` is
481                // the possible new start.
482                match (p0_opt, p1.is_valid()) {
483                    (Some(p0), true) => {
484                        let p1_inside = bb.contains(p1);
485                        if p0_inside { // p0 ∈ bb with cut before
486                            s.path.push_back(p0.clone());
487                            if p1_inside {
488                                s.path.push_back(p1.clone());
489                                new_len += 2;
490                                prev_cut = false;
491                            } else if
492                                let Some(p) = Self::intersect(p0, p1, bb) {
493                                    let t = p.t;
494                                    s.path.push_back(p);
495                                    s.path.push_back(Point::cut(t));
496                                    new_len += 3;
497                                } else {
498                                    s.path.push_back(Point::cut(p0.t));
499                                    new_len += 2;
500                                }
501                        } else if p1_inside { // p0 ∉ bb, p1 ∈ bb
502                            if let Some(p) = Self::intersect(p1, p0, bb) {
503                                s.path.push_back(p); // p ≠ p1
504                                new_len += 1;
505                            }
506                            s.path.push_back(p1.clone());
507                            new_len += 1;
508                            prev_cut = false;
509                        } else { // p0, p1 ∉ bb but maybe intersection
510                            match Self::intersect_seg(p0, p1, bb) {
511                                Intersection::Seg(q0, q1) => {
512                                    let t1 = q1.t;
513                                    s.path.push_back(q0);
514                                    s.path.push_back(q1);
515                                    s.path.push_back(Point::cut(t1));
516                                    new_len += 3;
517                                }
518                                Intersection::Pt(p) => {
519                                    let t = p.t;
520                                    s.path.push_back(p);
521                                    s.path.push_back(Point::cut(t));
522                                    new_len += 2;
523                                }
524                                Intersection::Empty => (),
525                            }
526                        }
527                        p0_opt = Some(p1);
528                        p0_inside = p1_inside;
529                    }
530                    (None, true) => {
531                        p0_opt = Some(p1);
532                        p0_inside = bb.contains(p1);
533                    }
534                    (Some(p0), false) => {
535                        if p0_inside {
536                            s.path.push_back(p0.clone());
537                            s.path.push_back(Point::cut(p0.t));
538                            new_len += 2;
539                        }
540                        p0_opt = None;
541                    }
542                    (None, false) => (), // p0_opt == None
543                }
544            } else {
545                // Previous step was not a cut which also implies that
546                // `p0_opt = Some(p0)` with `p0` ∈ `bb`, and `p0` is
547                // already present in the final sampling.
548                let p0 = p0_opt.expect("No cut ⟹ previous point");
549                debug_assert!(p0_inside);
550                if p1.is_valid() {
551                    p0_opt = Some(p1);
552                    p0_inside = bb.contains(p1); // update for next step
553                    if p0_inside { // p0, p1 ∈ bb
554                        s.path.push_back(p1.clone());
555                        new_len += 1;
556                    } else { // p0 ∈ bb, p1 ∉ bb
557                        if let Some(p) = Self::intersect(p0, p1, bb) {
558                            let t = p.t;
559                            s.path.push_back(p);
560                            s.path.push_back(Point::cut(t));
561                            new_len += 2;
562                        } else {
563                            s.path.push_back(Point::cut(p0.t));
564                            new_len += 1;
565                        }
566                        prev_cut = true;
567                    }
568                } else { // p1 is invalid (i.e., represent a cut)
569                    p0_opt = None;
570                    s.path.push_back(p1.clone());
571                    new_len += 1;
572                    prev_cut = true
573                }
574            }
575        }
576        if prev_cut { s.path.pop_back(); }
577        s.set_vp(bb);
578        s.guess_len.set(new_len);
579        s
580    }
581
582    /// Create a sampling from an iterator of points.  Beware that the
583    /// invariant "`p.y` is finite ⇒ `p.x` is finite" is not checked.
584    fn from_point_iterator<P>(points: P) -> Self
585    where P: IntoIterator<Item = Point> {
586        let mut s = Sampling::empty();
587        let mut points = points.into_iter();
588        let mut len: usize = 0;
589        macro_rules! skip_until_last_cut { () => {
590            let mut cut = None;
591            let mut first_pt = None;
592            for p in &mut points {
593                if p.is_valid() { first_pt = Some(p); break; }
594                cut = Some(p);
595            }
596            match (cut, first_pt) {
597                (_, None) => {
598                    s.guess_len.set(len);
599                    return s
600                }
601                (None, Some(p)) => {
602                    s.path.push_back(p);
603                    len += 1;
604                }
605                (Some(c), Some(p)) => {
606                    s.path.push_back(Point::cut(c.t));
607                    s.path.push_back(p);
608                    len += 2;
609                }
610            }
611        }}
612        skip_until_last_cut!();
613        while let Some(p) = points.next() {
614            if p.is_valid() {
615                s.path.push_back(p);
616                len += 1;
617            } else {
618                s.path.push_back(Point::cut(p.t));
619                len += 1;
620                skip_until_last_cut!();
621            }
622        }
623        s.guess_len.set(len);
624        s
625    }
626
627    /// Create a sampling from `points` after sorting them by
628    /// increasing (if `incr`) or decreasing (if `! incr`) values of
629    /// the field `t`.  Beware that the invariant "`p.y` is finite ⇒
630    /// `p.x` is finite" is not checked.
631    fn from_vec(mut points: Vec<Point>, incr: bool) -> Self {
632        if incr {
633            points.sort_unstable_by(|p1, p2| {
634                // We know that `t1` and `t2` are finite.
635                p1.t.partial_cmp(&p2.t).unwrap() });
636        } else {
637            points.sort_unstable_by(|p1, p2| {
638                p2.t.partial_cmp(&p1.t).unwrap() });
639        }
640        Self::from_point_iterator(points)
641    }
642}
643
644
645impl FromIterator<[f64; 2]> for Sampling {
646    /// Return an sampling from the points.  Points with non-finite
647    /// coordinates are interpreted as cuts.
648    fn from_iter<T>(points: T) -> Self
649    where T: IntoIterator<Item = [f64; 2]> {
650        Sampling::from_point_iterator(
651            points.into_iter().enumerate().map(|(i, xy @ [x, y])| {
652                let t = i as f64;
653                if x.is_finite() && y.is_finite() {
654                    Point::new_unchecked(t, xy)
655                } else {
656                    Point::cut(t)
657                } }))
658    }
659}
660
661////////////////////////////////////////////////////////////////////////
662//
663// Acceptable types & functions that provide "points".
664// These "conversions" must enforce the specification: `p.x` finite ⟹
665// `p.y` finite.
666// This is internal to this library.
667
668impl From<(f64, f64)> for Point {
669    #[inline]
670    fn from((x, y): (f64, f64)) -> Self {
671        // `x` ∈ [a, b] by `init_pt` checks.
672        Point::new_unchecked(x, [x, y])
673    }
674}
675
676impl From<(f64, [f64; 2])> for Point {
677    /// Assume `t` is finite.
678    #[inline]
679    fn from((t, xy): (f64, [f64;2])) -> Self {
680        // Enforce the invariant: y finite ⟹ x finite
681        if xy[0].is_finite() { Point::new_unchecked(t, xy) }
682        else { Point::cut(t) }
683    }
684}
685
686/// Values that can be treated as Fn(f64) -> Point.
687trait IntoFnPoint {
688    fn eval(&mut self, t: f64) -> Point;
689}
690
691// This trait cannot implemented for both `FnMut(f64) -> f64` and
692// `FnMut(f64) -> [f64; 2]` (that conflicts), so we wrap the types of
693// interest.
694
695struct FnPoint<T>(T);
696
697impl<T> IntoFnPoint for FnPoint<T> where T: FnMut(f64) -> f64 {
698    #[inline]
699    fn eval(&mut self, t: f64) -> Point {
700        Point::new_unchecked(t, [t, self.0(t)])
701    }
702}
703
704struct ParamPoint<T>(T);
705
706impl<T> IntoFnPoint for ParamPoint<T> where T: FnMut(f64) -> [f64; 2] {
707    #[inline]
708    fn eval(&mut self, t: f64) -> Point {
709        let xy @ [x, y] = self.0(t);
710        if x.is_finite() && y.is_finite() {
711            Point::new_unchecked(t, xy)
712        } else {
713            Point::new_unchecked(t, [xy[0], f64::NAN])
714        }
715    }
716}
717
718////////////////////////////////////////////////////////////////////////
719//
720// Defining a sampling with standard options & checks
721
722/// Define a structure with standard fields, standard options, and a
723/// function to generate it.
724macro_rules! new_sampling_fn {
725    // Function to init the struct.
726    ($(#[$docfn: meta])*, $(#[$docfn_extra: meta])* $fun: ident -> $ft: ty,
727     // The structure to hold the options (and other fields).
728     $(#[$doc: meta])* $struct: ident,
729     $wrap_f: ident
730    ) => {
731        impl Sampling {
732            $(#[$docfn])*
733            ///
734            /// Panics if `a` or `b` is not finite.
735            ///
736            $(#[$docfn_extra])*
737            #[must_use]
738            pub fn $fun<F>(f: F, a: f64, b: f64) -> $struct<F>
739            where F: FnMut(f64) -> $ft {
740                if !a.is_finite() {
741                    panic!("curve_sampling::{}: a = {} must be finite",
742                           stringify!($fun), a);
743                }
744                if !b.is_finite() {
745                    panic!("curve_sampling::{}: b = {} must be finite",
746                           stringify!($fun), b);
747                }
748                $struct { f: $wrap_f(f),
749                          a, b,  // Order of `a`, `b` reflect orientation
750                          n: 100,
751                          viewport: None,
752                          init: vec![],
753                          init_pt: vec![],
754                }
755            }
756        }
757
758        $(#[$doc])*
759        pub struct $struct<F> {
760            f: $wrap_f<F>,  a: f64,  b: f64,
761            n: usize,
762            viewport: Option<BoundingBox>,
763            init: Vec<f64>,
764            init_pt: Vec<Point>,
765        }
766
767        impl<F> $struct<F>
768        where F: FnMut(f64) -> $ft {
769            /// Set the maximum number of evaluations of the function
770            /// to build the sampling.  Panic if `n < 2`.
771            pub fn n(mut self, n: usize) -> Self {
772                if n < 2 {
773                    panic!("curve_sampling: n = {} must at least be 2", n)
774                }
775                self.n = n;
776                self
777            }
778
779            /// Set the zone of interest for the sampling.  Segments
780            /// that end up outside this box will not be refined.
781            pub fn viewport(mut self, vp: BoundingBox) -> Self {
782                self.viewport = Some(vp);
783                self
784            }
785
786            /// Add initial values of `t` such that `f(t)` (see [`
787            #[doc = stringify!(Sampling::$fun)]
788            /// `]) must be included into the sampling in addition to
789            /// the `n` evaluations.  Only the values between `a` and
790            /// `b` are taken into account (other values are ignored).
791            pub fn init<'a, I>(mut self, ts: I) -> Self
792            where I: IntoIterator<Item = &'a f64> {
793                for &t in ts {
794                    if self.a <= t && t <= self.b { // ⟹ t is finite
795                        self.init.push(t);
796                    }
797                }
798                self
799            }
800
801            /// Add initial points `(t, f(t))` to include into the
802            /// sampling in addition to the `n` evaluations.  This
803            /// allows you to use previous evaluations of `f`.  Only
804            /// the couples with first coordinate `t` between `a` and
805            /// `b` (see [`
806            #[doc = stringify!(Sampling::$fun)]
807            /// `]) are considered (other values are ignored).
808            pub fn init_pt<'a, I>(mut self, pts: I) -> Self
809            where I: IntoIterator<Item = &'a (f64, $ft)> {
810                for &p in pts {
811                    if self.a <= p.0 && p.0 <= self.b { // ⟹ p.0 = t is finite
812                        self.init_pt.push(Point::from(p));
813                    }
814                }
815                self
816            }
817
818            /// Evaluate the function at all initial values that where
819            /// provided through [`Self::init`].
820            fn eval_init(&mut self) {
821                // `t` ∈ \[`a`, `b`\] already checked by [`init`] and
822                // [`init_pt`].
823                for &t in &self.init {
824                    self.init_pt.push(self.f.eval(t))
825                }
826                self.init.clear()
827            }
828        }
829    }
830}
831
832////////////////////////////////////////////////////////////////////////
833//
834// Uniform sampling
835
836new_sampling_fn!(
837    /// Create a sampling for the graph of `f` on the interval
838    /// \[`a`, `b`\] with evenly spaced values of the argument.
839    ,
840    /// # Example
841    ///
842    /// ```
843    /// use std::{fs::File, io::BufWriter};
844    /// use curve_sampling::Sampling;
845    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
846    /// let s = Sampling::uniform(|x| x.sin(), 0., 4.).build();
847    /// s.write(&mut BufWriter::new(File::create("target/uniform.dat")?))?;
848    /// # Ok(()) }
849    /// ```
850    uniform -> f64,
851    /// Options for uniform sampling graphs of function ℝ → ℝ.
852    /// See [`Sampling::uniform`].
853    Uniform,
854    FnPoint);
855
856impl<F> Uniform<F>
857where F: FnMut(f64) -> f64 {
858    /// Return a uniform sampling of the function.
859    pub fn build(mut self) -> Sampling {
860        if self.a == self.b {
861            let p = self.f.eval(self.a); // `a` is finite by previous tests
862            if p.is_valid() {
863                return Sampling::singleton(p);
864            } else {
865                return Sampling::empty()
866            }
867        }
868        self.eval_init();
869        // FIXME: why don't we push the points directly to the
870        // sampling?  This requires to sort self.init_pt and to insert
871        // them at the right locations while performing the uniform sampling.
872        let mut points = self.init_pt;
873        let dt = (self.b - self.a) / (self.n - 1) as f64;
874        for i in 0 .. self.n {
875            let t = self.a + i as f64 * dt;
876            points.push(self.f.eval(t));
877        }
878        Sampling::from_vec(points, self.a < self.b)
879    }
880}
881
882////////////////////////////////////////////////////////////////////////
883//
884// Cost
885
886mod cost {
887    use super::{Point, Lengths};
888
889    // The cost of a point is a measure of the curvature at this
890    // point.  This requires segments before and after the point.  In
891    // case the point is a cut, or first, or last, it has a cost of 0.
892    // If it is an endpoint of a segment with the other point a cut,
893    // the cost is set to [`HANGING_NODE`] because the segment with
894    // the invalid point needs to be cut of too long to better
895    // determine the boundary.  Only the absolute value of the cost
896    // matters for the priority queue (see `segment`), its sign must
897    // reflect the orientation of the angle.
898    //
899    // The cost of a point is apportioned to the segments of which it is
900    // an endpoint according to their relative lengths.  More precisely,
901    // the cost c of a point p is distributed on the segments s1 and s2
902    // (of respective lengths l1 and l2) it is an endpoint of as
903    //
904    //   c * l1/(l1+l2) for s1 and c * l2/(l1+l2) for s2.
905    //
906    // In order to be able to update the cost of s1 without accessing
907    // s2, p.cost holds c/(l1+l2).
908
909    /// Cost for new "hanging" nodes — nodes created splitting a
910    /// segment with an invalid endpoint.  Note that this cost will be
911    /// multiplied by a function of `dt` in `segment` so it must be
912    /// set high enough to ensure proper resolution of the endpoints
913    /// of the domain.
914    pub const HANGING_NODE: f64 = 5e5;
915
916    /// Set the cost of the middle point `pm`.  Assumes `p0`, `pm`,
917    /// and `p1` are valid points.
918    #[inline]
919    pub(crate) fn set_middle(p0: &Point, pm: &mut Point, p1: &Point,
920                             len: Lengths)
921    {
922        let [x0, y0] = p0.xy;
923        let [xm, ym] = pm.xy;
924        let [x1, y1] = p1.xy;
925        let dx0m = (x0 - xm) / len.x;
926        let dy0m = (y0 - ym) / len.y;
927        let dx1m = (x1 - xm) / len.x;
928        let dy1m = (y1 - ym) / len.y;
929        let len0m = dx0m.hypot(dy0m);
930        let len1m = dx1m.hypot(dy1m);
931        if len0m == 0. || len1m == 0. {
932            pm.cost = 0.; // Do not subdivide
933        } else {
934            let dx = - dx0m * dx1m - dy0m * dy1m;
935            let dy = dy0m * dx1m - dx0m * dy1m;
936            pm.cost = dy.atan2(dx); // ∈ [-π, π]
937            debug_assert!(!pm.cost.is_nan());
938        }
939    }
940
941    /// Compute the cost of a segment according to the costs of its
942    /// endpoints unless `in_vp` is false in which case -∞ is returned.
943    #[inline]
944    pub(crate) fn segment_vp(p0: &Point, p1: &Point, len: Lengths,
945                             in_vp: bool) -> f64 {
946        if ! in_vp { return f64::NEG_INFINITY }
947        segment(p0, p1, len)
948    }
949
950    /// Compute the cost of a segment according to the costs of its
951    /// endpoints.
952    #[inline]
953    pub(crate) fn segment(p0: &Point, p1: &Point, len: Lengths) -> f64 {
954        let dt = (p1.t - p0.t).abs() / len.t; // ∈ [0,1]
955        debug_assert!((0. ..=1.).contains(&dt), "dt = {dt} ∉ [0,1]");
956        // Put less efforts when `dt` is small.  For functions, the
957        // Y-variation may be large but, if it happens for a small range
958        // of `t`, there is no point in adding indistinguishable details.
959        let [x0, y0] = p0.xy;
960        let [x1, y1] = p1.xy;
961        let dx = ((x1 - x0) / len.x).abs();
962        let dy = ((y1 - y0) / len.y).abs();
963        let mut cost = p0.cost.abs() + p1.cost.abs(); // ≥ 0
964        if p0.cost * p1.cost < 0. {
965            // zigzag are bad on a large scale but less important on a
966            // small scale.
967            if dx <= 0.01 && dy <= 0.01 { cost *= 0.5 }
968            else if !(dx <= 0.05 && dy <= 0.05) { cost *= 8. }
969        }
970        if dt >= 0.8 { cost }
971        else {
972            let dt = dt / 0.8;
973            dt * dt * (6. + (-8. + 3. * dt) * dt) * cost
974        }
975    }
976
977}
978
979/// Compute the cost of the segment \[`p0`, `p1`\] (taking into
980/// account `in_vp`) and push it to the queue `pq`.  `p0` is pointed
981/// to by a list-witness so one can update it from the PQ elements.
982/// `p0` is updated with the PQ-witness.
983fn push_segment(pq: &mut PQ,
984                p0: &mut list::Witness<Point>, p1: &Point,
985                len: Lengths, in_vp: bool) {
986    // FIXME: do we really want to push the segment when `!in_vp`?
987    // In not all points are in the queue, one must beware of the
988    // validity of witnesses though.
989    let cost_segment = cost::segment_vp(unsafe { p0.as_ref() },
990                                        p1, len, in_vp);
991    // The segment is referred to by its first point.
992    let w = pq.push(cost_segment, p0.clone());
993    unsafe { p0.as_mut().witness = Some(w) }
994}
995
996/// With the (new) costs in points `p0` and `p1`, update the position
997/// of the segment \[`p0`, `p1`\] in the priority queue of `self`.
998///
999/// # Safety
1000/// `p0` must be in `pq`, otherwise it is UB.
1001unsafe fn update_segment(pq: &mut PQ, p0: &Point, p1: &Point, len: Lengths) {
1002    match &p0.witness {
1003        Some(w) => {
1004            let priority = cost::segment(p0, p1, len);
1005            pq.increase_priority(w, priority)
1006        }
1007        None => panic!("Sampling::update_segment: unset witness"),
1008    }
1009}
1010
1011////////////////////////////////////////////////////////////////////////
1012//
1013// Function sampling
1014
1015/// Update the cost of all points in the sampling and add segments
1016/// to the priority queue.
1017fn compute(s: &mut Sampling, in_vp: impl Fn(&Point) -> bool) {
1018    if let Some(len) = s.len_txy() {
1019        macro_rules! r { ($x: ident) => { unsafe { $x.as_ref() } } }
1020        macro_rules! m { ($x: ident) => { unsafe { $x.as_mut() } } }
1021        // Path is not empty.
1022        let mut pts = s.path.iter_witness_mut();
1023        let mut p0 = pts.next().unwrap();
1024        m!(p0).cost = 0.;
1025        let mut p0_is_valid = r!(p0).is_valid();
1026        let mut p0_in_vp = p0_is_valid && in_vp(r!(p0));
1027        let mut pm = match pts.next() {
1028            Some(p) => p,
1029            None => return };
1030        for p1 in pts {
1031            let pm_is_valid = r!(pm).is_valid();
1032            let pm_in_vp;
1033            if pm_is_valid {
1034                pm_in_vp = in_vp(r!(pm));
1035                if p0_is_valid && r!(p1).is_valid() {
1036                    cost::set_middle(r!(p0), m!(pm), r!(p1), len);
1037                } else {
1038                    m!(pm).cost = cost::HANGING_NODE;
1039                }
1040            } else { // pm is the location of a cut
1041                pm_in_vp = false;
1042                m!(pm).cost = 0.;
1043            }
1044            if p0_is_valid || pm_is_valid {
1045                // Add segment [p0, pm] to the PQ and set `p0` witness.
1046                push_segment(&mut s.pq, &mut p0, r!(pm), len,
1047                             p0_in_vp || pm_in_vp);
1048            }
1049            p0 = pm;
1050            p0_is_valid = pm_is_valid;
1051            p0_in_vp = pm_in_vp;
1052            pm = p1;
1053        }
1054        m!(pm).cost = 0.; // last point
1055        if p0_is_valid || r!(pm).is_valid() {
1056            let mut vp = p0_in_vp;
1057            if r!(pm).is_valid() { vp = vp || in_vp(r!(pm)) };
1058            push_segment(&mut s.pq, &mut p0, r!(pm), len, vp);
1059        }
1060    }
1061}
1062
1063fn refine_gen(s: &mut Sampling, n: usize,
1064              mut f: impl FnMut(f64) -> Point,
1065              in_vp: impl Fn(&Point) -> bool) {
1066    let len = match s.len_txy() {
1067        Some(txy) => txy,
1068        None => return };
1069    s.guess_len.set(s.guess_len.get() + n);
1070    macro_rules! r { ($x: ident) => { unsafe { $x.as_ref() } } }
1071    macro_rules! m { ($x: ident) => { unsafe { $x.as_mut() } } }
1072    for _ in 0 .. n {
1073        let mut p0: list::Witness<Point> = match s.pq.pop() {
1074            None => break,
1075            Some(p) => p };
1076        m!(p0).witness = None; // PQ element it points to just popped.
1077        let mut p1 = unsafe { p0.next().unwrap() };
1078        // Refine the segment [p0, p1] inserting a middle point `pm`.
1079        let t = (r!(p0).t + r!(p1).t) * 0.5;
1080        let mut pm = f(t);
1081        if r!(p0).is_valid() {
1082            if r!(p1).is_valid() {
1083                let mut pm = unsafe { s.path.insert_after(&mut p0, pm) };
1084                let mut pm_in_vp = false;
1085                if r!(pm).is_valid() {
1086                    pm_in_vp = in_vp(r!(pm));
1087                    cost::set_middle(r!(p0), m!(pm), r!(p1), len);
1088                    if let Some(p_1) = unsafe { p0.prev() } {
1089                        if r!(p_1).is_valid() {
1090                            cost::set_middle(r!(p_1), m!(p0), r!(pm), len);
1091                            unsafe {
1092                                update_segment(&mut s.pq, p_1.as_ref(),
1093                                               p0.as_ref(), len) }
1094                        }
1095                    }
1096                    if let Some(p2) = unsafe { p1.next() } {
1097                        if r!(p2).is_valid() {
1098                            cost::set_middle(r!(pm), m!(p1), r!(p2), len);
1099                            unsafe {
1100                                update_segment(&mut s.pq, p1.as_ref(),
1101                                               p2.as_ref(), len) }
1102                        }
1103                    }
1104                } else { // `pm` invalid ⟹ cut between `p0` and `p1`
1105                    m!(p0).cost = cost::HANGING_NODE;
1106                    m!(pm).cost = 0.;
1107                    m!(p1).cost = cost::HANGING_NODE;
1108                    unsafe {
1109                        if let Some(p_1) = p0.prev() {
1110                            update_segment(&mut s.pq, p_1.as_ref(),
1111                                           p0.as_ref(), len)
1112                        }
1113                        if let Some(p2) = p1.next() {
1114                            update_segment(&mut s.pq, p1.as_ref(),
1115                                           p2.as_ref(), len);
1116                        }
1117                    }
1118                }
1119                let vp = pm_in_vp || in_vp(r!(p0));
1120                push_segment(&mut s.pq, &mut p0, r!(pm), len, vp);
1121                let vp = pm_in_vp || in_vp(r!(p1));
1122                push_segment(&mut s.pq, &mut pm, r!(p1), len, vp);
1123            } else { // `p0` valid, `p1` invalid (i.e. is a cut)
1124                // Thus `p0` is a hanging node.
1125                if pm.is_valid() {
1126                    pm.cost = cost::HANGING_NODE;
1127                    let mut pm = unsafe { s.path.insert_after(&mut p0, pm) };
1128                    if let Some(p_1) = unsafe { p0.prev() } {
1129                        if r!(p_1).is_valid() {
1130                            cost::set_middle(r!(p_1), m!(p0), r!(pm), len);
1131                            unsafe{update_segment(&mut s.pq, p_1.as_ref(),
1132                                                  p0.as_ref(), len)}
1133                        }
1134                    }
1135                    let pm_in_vp = in_vp(r!(pm));
1136                    let vp = pm_in_vp || in_vp(r!(p0));
1137                    push_segment(&mut s.pq, &mut p0, r!(pm), len, vp);
1138                    push_segment(&mut s.pq, &mut pm, r!(p1), len, pm_in_vp)
1139                } else { // `pm` invalid
1140                    // Insert only \[`p0`, `pm`\] and forget
1141                    // \[`pm`, `p1`\].  The cost of `p0` stays
1142                    // `cost::HANGING_NODE`.  We can see this as
1143                    // reducing the uncertainty of the boundary in the
1144                    // segment \[`p0`, `p1`\].
1145                    pm.cost = 0.;
1146                    let pm = unsafe {
1147                        if p1.as_ref().witness.is_none() {
1148                            // `p1` is not part of a segment.  One can
1149                            // replace it by `pm`.
1150                            s.path.replace(&mut p1, pm);
1151                            p1 // witness for `pm` now.
1152                        } else {
1153                            s.path.insert_after(&mut p0, pm)
1154                        } };
1155                    let vp = in_vp(r!(p0));
1156                    push_segment(&mut s.pq, &mut p0, r!(pm), len, vp)
1157                }
1158            }
1159        } else { // `p0` invalid (i.e., cut) ⟹ `p1` valid
1160            debug_assert!(r!(p1).is_valid());
1161            if pm.is_valid() {
1162                pm.cost = cost::HANGING_NODE;
1163                let mut pm = unsafe { s.path.insert_after(&mut p0, pm) };
1164                if let Some(p2) = unsafe { p1.next() } {
1165                    if r!(p2).is_valid() {
1166                        cost::set_middle(r!(pm), m!(p1), r!(p2), len);
1167                        unsafe{update_segment(&mut s.pq, p1.as_ref(),
1168                                              p2.as_ref(), len)}
1169                    }
1170                }
1171                let pm_in_vp = in_vp(r!(pm));
1172                push_segment(&mut s.pq, &mut p0, r!(pm), len, pm_in_vp);
1173                push_segment(&mut s.pq, &mut pm, r!(p1), len,
1174                             pm_in_vp || in_vp(r!(p1)))
1175            } else { // `pm` invalid ⟹ drop segment \[`p0`, `pm`\].
1176                // Cost of `p1` stays `cost::HANGING_NODE`.
1177                pm.cost = 0.;
1178                let mut pm = unsafe {
1179                    if let Some(p_1) = p0.prev() {
1180                        if p_1.as_ref().is_valid() {
1181                            s.path.insert_after(&mut p0, pm)
1182                        } else {
1183                            // `p_1` is the cut ending the previous segment.
1184                            s.path.replace(&mut p0, pm);
1185                            p0
1186                        }
1187                    } else {
1188                        s.path.insert_after(&mut p0, pm)
1189                    } };
1190                let vp = in_vp(r!(p1));
1191                push_segment(&mut s.pq, &mut pm, r!(p1), len, vp)
1192            }
1193        }
1194    }
1195}
1196
1197fn push_almost_uniform_sampling(points: &mut Vec<Point>,
1198                                f: &mut impl FnMut(f64) -> Point,
1199                                a: f64, b: f64, n: usize) {
1200    debug_assert!(n >= 4);
1201    let dt = (b - a) / (n - 3) as f64;
1202    // Pseudorandom number generator from the "Xorshift RNGs" paper by
1203    // George Marsaglia.
1204    // See https://matklad.github.io/2023/01/04/on-random-numbers.html and
1205    // https://github.com/rust-lang/rust/blob/1.55.0/library/core/src/slice/sort.rs#L559-L573
1206    let mut random = (n as u32).wrapping_mul(1_000_000);
1207    const NORMALIZE_01: f64 = 1. / u32::MAX as f64;
1208    let mut rand = move || {
1209        random ^= random << 13;
1210        random ^= random >> 17;
1211        random ^= random << 5;
1212        random as f64 * NORMALIZE_01
1213    };
1214    points.push(f(a));
1215    points.push(f(a + 0.0625 * dt));
1216    for i in 1 ..= n - 4 {
1217        let j = i as f64 + rand() * 0.125 - 0.0625;
1218        points.push(f(a + j * dt));
1219    }
1220    points.push(f(b - 0.0625 * dt));
1221    points.push(f(b));
1222}
1223
1224impl Sampling {
1225    /// Return a sampling from the initial list of `points`.
1226    fn build(mut points: Vec<Point>,
1227             mut f: impl FnMut(f64) -> Point,
1228             a: f64, b: f64, n: usize,
1229             viewport: Option<BoundingBox>) -> Sampling {
1230        if a == b {
1231            let p = f(a);
1232            if p.is_valid() {
1233                return Sampling::singleton(p);
1234            } else {
1235                return Sampling::empty()
1236            }
1237        }
1238        // Uniform sampling requires ≥ 4 points but actually makes no
1239        // sense with less than 10 points.
1240        let n0 = (n / 10).max(10);
1241        // FIXME: Why don't we push the points directly to the sampling?
1242        push_almost_uniform_sampling(&mut points, &mut f, a, b, n0);
1243        let mut s = Sampling::from_vec(points, a < b);
1244        match viewport {
1245            Some(vp) => {
1246                let in_vp = |p: &Point| vp.contains(p);
1247                compute(&mut s, in_vp);
1248                refine_gen(&mut s, n - n0, &mut f, in_vp)
1249            }
1250            None => {
1251                compute(&mut s, |_| true);
1252                refine_gen(&mut s, n - n0, &mut f, |_| true)
1253            }
1254        }
1255        s
1256    }
1257}
1258
1259
1260new_sampling_fn!(
1261    /// Create a sampling of the *graph* of `f` on the interval
1262    /// \[`a`, `b`\] by evaluating `f` at `n` points.
1263    ,
1264    /// # Example
1265    ///
1266    /// ```
1267    /// use std::{fs::File, io::BufWriter};
1268    /// use curve_sampling::Sampling;
1269    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1270    /// let s = Sampling::fun(|x| x.sin(), 0., 4.).build();
1271    /// s.write(&mut BufWriter::new(File::create("target/fun.dat")?))?;
1272    /// # Ok(()) }
1273    /// ```
1274    fun -> f64,
1275    /// Options for sampling graphs of functions ℝ → ℝ.
1276    /// See [`Sampling::fun`].
1277    Fun,
1278    FnPoint);
1279
1280impl<F> Fun<F>
1281where F: FnMut(f64) -> f64 {
1282    /// Return the sampling.
1283    pub fn build(mut self) -> Sampling {
1284        self.eval_init();
1285        Sampling::build(self.init_pt, |x| self.f.eval(x),
1286                        self.a, self.b, self.n, self.viewport)
1287    }
1288}
1289
1290
1291new_sampling_fn!(
1292    /// Create a sampling of the *image* of `f` on the interval
1293    /// \[`a`, `b`\] by evaluating `f` at `n` points.
1294    ,
1295    /// # Example
1296    ///
1297    /// ```
1298    /// use std::{fs::File, io::BufWriter};
1299    /// use curve_sampling::Sampling;
1300    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1301    /// let s = Sampling::param(|t| [t.sin(), t.cos()], 0., 4.).build();
1302    /// s.write(&mut BufWriter::new(File::create("target/param.dat")?))?;
1303    /// # Ok(()) }
1304    /// ```
1305    param -> [f64; 2],
1306    /// Options for sampling the image of functions ℝ → ℝ².
1307    /// See [`Sampling::param`].
1308    Param,
1309    ParamPoint);
1310
1311impl<F> Param<F>
1312where F: FnMut(f64) -> [f64; 2] {
1313    /// Return the sampling.
1314    pub fn build(mut self) -> Sampling {
1315        self.eval_init();
1316        Sampling::build(self.init_pt, |t| self.f.eval(t),
1317                        self.a, self.b, self.n, self.viewport)
1318    }
1319}
1320
1321
1322////////////////////////////////////////////////////////////////////////
1323//
1324// Output
1325
1326/// LaTeX output, created by [`Sampling::latex`].
1327///
1328/// # Example
1329///
1330/// ```
1331/// use std::fs::File;
1332/// use curve_sampling::Sampling;
1333/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1334/// let s = Sampling::from_iter([[0., 0.], [1., 1.]]);
1335/// s.latex().write(&mut File::create("target/sampling.tex")?)?;
1336/// # Ok(()) }
1337/// ```
1338pub struct LaTeX<'a> {
1339    sampling: &'a Sampling,
1340    n: usize,
1341    color: Option<RGB8>,
1342    arrow: Option<&'a str>,
1343    arrow_pos: Option<f64>,
1344}
1345
1346impl<'a> LaTeX<'a> {
1347    #[inline]
1348    fn new(s: &'a Sampling) -> Self {
1349        Self { sampling: s,  n: 20_000,  color: None,
1350               arrow: None,  arrow_pos: None }
1351    }
1352
1353    /// Set the maximum number of points of a PGF path to `n`.  If it
1354    /// contains more than `n` points, the sampling curve is drawn as
1355    /// several PGF paths.  Default: 20_000.
1356    pub fn n(&mut self, n: usize) -> &mut Self {
1357        self.n = n;
1358        self
1359    }
1360
1361    /// Set the color of the curve to `color`.  If not specified the
1362    /// active LaTeX color will be used.
1363    pub fn color(&mut self, color: RGB8) -> &mut Self {
1364        self.color = Some(color);
1365        self
1366    }
1367
1368    /// Set the type of arrow to draw to `arrow`.
1369    /// See the documentation of `\pgfsetarrowsend` in the
1370    /// [TikZ manual](https://tikz.dev/base-actions#sec-103.3).
1371    pub fn arrow(&mut self, arrow: &'a str) -> &mut Self {
1372        self.arrow = Some(arrow);
1373        self
1374    }
1375
1376    /// The position of the arrow as a percent of the curve length (in
1377    /// the interval \[0.,1.\]).  If [`LaTeX::arrow`] is specified but
1378    /// not this, it defaults to `0.5`.
1379    pub fn arrow_pos(&mut self, arrow_pos: f64) -> &mut Self {
1380        if ! arrow_pos.is_finite() {
1381            panic!("curve_sampling::LaTeX::arrow_pos: \
1382                    position must be finite");
1383        }
1384        self.arrow_pos = Some(arrow_pos.clamp(0., 1.));
1385        self
1386    }
1387
1388    /// Write the sampling with lines segments.
1389    fn write_with_lines(&self, f: &mut impl Write) -> Result<(), io::Error> {
1390        let mut n = 0;
1391        let mut new_path = true;
1392        for [x,y] in self.sampling.iter() {
1393            if x.is_nan() {
1394                writeln!(f, "\\pgfusepath{{stroke}}")?;
1395                n = 0;
1396                new_path = true;
1397            } else {
1398                n += 1;
1399                if new_path {
1400                    writeln!(f, "\\pgfpathmoveto{{\\pgfpointxy\
1401                                 {{{:.16}}}{{{:.16}}}}}", x, y)?
1402                } else if n >= self.n {
1403                    write!(f, "\\pgfpathlineto{{\\pgfpointxy\
1404                               {{{:.16}}}{{{:.16}}}}}\n\
1405                               \\pgfusepath{{stroke}}\n\
1406                               \\pgfpathmoveto{{\\pgfpointxy\
1407                               {{{:.16}}}{{{:.16}}}}}\n", x, y, x, y)?;
1408                    n = 0;
1409                } else {
1410                    writeln!(f, "\\pgfpathlineto{{\\pgfpointxy\
1411                                 {{{:.16}}}{{{:.16}}}}}", x, y)?
1412                }
1413                new_path = false;
1414            }
1415        }
1416        Ok(())
1417    }
1418
1419    /// Write the path, each continuous sub-path with an arrow.
1420    fn write_with_arrows(&self, f: &mut impl Write, arrow: &str,
1421                         arrow_pos: f64) -> Result<(), io::Error> {
1422        // Compute the length of all sub-paths.
1423        let mut lens = vec![];
1424        let mut prev_pt: Option<[f64; 2]> = None;
1425        let mut cur_len = 0.;
1426        for p @ [x, y] in self.sampling.iter() {
1427            if x.is_nan() {
1428                lens.push(arrow_pos * cur_len);
1429                prev_pt = None;
1430                cur_len = 0.;
1431            } else {
1432                if let Some([x0, y0]) = prev_pt {
1433                    cur_len += (x - x0).hypot(y - y0);
1434                }
1435                prev_pt = Some(p);
1436            }
1437        }
1438        lens.push(arrow_pos * cur_len);
1439        if lens.is_empty() { return Ok(()) }
1440        let mut lens = lens.iter();
1441        let mut rem_len = *lens.next().unwrap(); // remaining before arrow
1442        prev_pt = None;
1443        let mut n = 0;
1444        for p @ [x, y] in self.sampling.iter() {
1445            if x.is_nan() {
1446                writeln!(f, "\\pgfusepath{{stroke}}")?;
1447                rem_len = *lens.next().unwrap();
1448                prev_pt = None;
1449            } else {
1450                n += 1;
1451                if let Some([x0, y0]) = prev_pt {
1452                    let dx = x - x0;
1453                    let dy = y - y0;
1454                    let l = dx.hypot(dy);
1455                    if rem_len <= l {
1456                        writeln!(f, "\\pgfusepath{{stroke}}")?;
1457                        // Drawing a long path with an arrow specified is
1458                        // extremely expensive.  Just draw the current segment.
1459                        let pct = rem_len / l;
1460                        if pct <= 1e-14 {
1461                            write!(f, "\\pgfsetarrowsstart{{{}}}\n\
1462                                       \\pgfpathmoveto{{\\pgfpointxy
1463                                       {{{:.16}}}{{{:.16}}}}}\n\
1464                                       \\pgfpathlineto{{\\pgfpointxy\
1465                                       {{{:.16}}}{{{:.16}}}}}\n\
1466                                       \\pgfusepath{{stroke}}\n\
1467                                       \\pgfsetarrowsend{{}}\n\
1468                                       \\pgfpathmoveto{{\\pgfpointxy\
1469                                       {{{:.16}}}{{{:.16}}}}}\n",
1470                                   arrow, x0, y0, x, y, x, y)?;
1471                        } else {
1472                            let xm = x0 + pct * dx;
1473                            let ym = y0 + pct * dy;
1474                            write!(f, "\\pgfsetarrowsend{{{}}}\n\
1475                                       \\pgfpathmoveto{{\\pgfpointxy\
1476                                       {{{:.16}}}{{{:.16}}}}}\n\
1477                                       \\pgfpathlineto{{\\pgfpointxy\
1478                                       {{{:.16}}}{{{:.16}}}}}\n\
1479                                       \\pgfusepath{{stroke}}\n\
1480                                       \\pgfsetarrowsend{{}}\n\
1481                                       \\pgfpathmoveto{{\\pgfpointxy\
1482                                       {{{:.16}}}{{{:.16}}}}}\n\
1483                                       \\pgfpathlineto{{\\pgfpointxy\
1484                                       {{{:.16}}}{{{:.16}}}}}\n",
1485                                   arrow, x0, y0, xm, ym, xm, ym, x, y)?;
1486                            n = 2;
1487                        }
1488                        rem_len = f64::INFINITY; // No more arrow for this sub-path
1489                    } else if n >= self.n {
1490                        write!(f, "\\pgfpathlineto{{\\pgfpointxy\
1491                                   {{{:.16}}}{{{:.16}}}}}\n\
1492                                   \\pgfusepath{{stroke}}\n\
1493                                   \\pgfpathmoveto{{\\pgfpointxy\
1494                                   {{{:.16}}}{{{:.16}}}}}\n", x, y, x, y)?;
1495                        n = 0;
1496                    } else {
1497                        writeln!(f, "\\pgfpathlineto{{\\pgfpointxy\
1498                                     {{{:.16}}}{{{:.16}}}}}", x, y)?
1499                    }
1500                    rem_len -= l;
1501                } else {
1502                    // No previous point.  New sub-path.
1503                    writeln!(f, "\\pgfpathmoveto{{\\pgfpointxy\
1504                                 {{{:.16}}}{{{:.16}}}}}", x, y)?
1505                }
1506                prev_pt = Some(p);
1507            }
1508        }
1509        Ok(())
1510    }
1511
1512    /// Write the sampling to the formatter as PGF/TikZ commands.
1513    pub fn write(&self, f: &mut impl Write) -> Result<(), io::Error> {
1514        writeln!(f, "% Written by the Rust curve-sampling crate.")?;
1515        writeln!(f, "\\begin{{pgfscope}}")?;
1516        if let Some(RGB8 {r, g, b}) = self.color {
1517            write!(f, "\\definecolor{{RustCurveSamplingColor}}{{RGB}}\
1518                       {{{},{},{}}}\n\
1519                       \\pgfsetstrokecolor{{RustCurveSamplingColor}}\n",
1520                   r, g, b)?
1521        }
1522        match (self.arrow, self.arrow_pos) {
1523            (None, None) => self.write_with_lines(f)?,
1524            (Some(arrow), None) =>
1525                self.write_with_arrows(f, arrow, 0.5)?,
1526            (None, Some(arrow_pos)) =>
1527                self.write_with_arrows(f, ">",arrow_pos)?,
1528            (Some(arrow), Some(arrow_pos)) =>
1529                self.write_with_arrows(f, arrow, arrow_pos)?,
1530        }
1531        write!(f, "\\pgfusepath{{stroke}}\n\\end{{pgfscope}}\n")
1532    }
1533}
1534
1535/// # Output
1536impl Sampling {
1537    /// Write the sampling `self` using PGF/TikZ commands.
1538    pub fn latex(&self) -> LaTeX<'_> { LaTeX::new(self) }
1539
1540    /// Write the sampling to `f` in a tabular form: each point is
1541    /// written as "x y" on a single line (in scientific notation).
1542    /// If the path is interrupted, a blank line is printed.  This
1543    /// format is compatible with Gnuplot.
1544    pub fn write(&self, f: &mut impl Write) -> Result<(), io::Error> {
1545        for [x, y] in self.iter() {
1546            if x.is_nan() {
1547                writeln!(f)?
1548            } else {
1549                writeln!(f, "{:e} {:e}", x, y)?
1550            }
1551        }
1552        Ok(())
1553    }
1554}
1555
1556impl Display for Sampling {
1557    /// Display the sampling in a tabular form: each point is written
1558    /// as "x y" on a single line (in scientific notation).  If the
1559    /// path is interrupted, a blank line is printed.  This format is
1560    /// compatible with Gnuplot.
1561    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
1562        for [x, y] in self.iter() {
1563            if x.is_nan() {
1564                writeln!(f)?
1565            } else {
1566                writeln!(f, "{:e} {:e}", x, y)?
1567            }
1568        }
1569        Ok(())
1570    }
1571}
1572
1573////////////////////////////////////////////////////////////////////////
1574//
1575// Tests
1576
1577#[cfg(test)]
1578mod tests {
1579    use std::{error::Error,
1580              fs::File,
1581              io::Write, path::Path};
1582    use crate::{Sampling, BoundingBox, Point};
1583
1584    type R<T> = Result<T, Box<dyn Error>>;
1585
1586    fn xy_of_sampling(s: &Sampling) -> Vec<Option<(f64, f64)>> {
1587        s.path.iter().map(|p| {
1588            if p.is_valid() { Some((p.xy[0], p.xy[1])) } else { None }
1589        })
1590            .collect()
1591    }
1592
1593    #[test]
1594    fn clone_sampling() {
1595        let s = Sampling::from_iter([[0.,0.], [1.,1.]]);
1596        let xy0: Vec<_> = s.iter().collect();
1597        let xy1: Vec<_> = s.clone().iter().collect();
1598        assert_eq!(xy0, xy1)
1599    }
1600
1601    #[test]
1602    fn io() {
1603        let s = Sampling::from_iter([[0.,0.], [1.,1.]]);
1604        assert_eq!(xy_of_sampling(&s), vec![Some((0.,0.)), Some((1.,1.))]);
1605        let s = Sampling::from_iter([[0.,0.], [1.,1.], [f64::NAN, 1.],
1606                                     [2.,2.]]);
1607        assert_eq!(xy_of_sampling(&s),
1608                   vec![Some((0.,0.)), Some((1.,1.)), None, Some((2.,2.))]);
1609    }
1610
1611    #[test]
1612    fn bounding_box_singleton() {
1613        let s = Sampling::singleton(
1614            Point::new_unchecked(0., [1., 2.]));
1615        let bb = BoundingBox {xmin: 1., xmax: 1., ymin: 2., ymax: 2.};
1616        assert_eq!(s.bounding_box(), bb);
1617    }
1618
1619    fn test_clip(bb: BoundingBox,
1620                 path: Vec<[f64;2]>,
1621                 expected: Vec<Option<(f64,f64)>>) {
1622        let s = Sampling::from_iter(path).clip(bb);
1623        assert_eq!(xy_of_sampling(&s), expected);
1624    }
1625
1626    #[test]
1627    fn clip_base () {
1628        let bb = BoundingBox { xmin: 0.,  xmax: 3., ymin: 0.,  ymax: 2.};
1629        test_clip(bb, vec![[0.,1.], [2.,3.]],
1630                  vec![Some((0.,1.)), Some((1.,2.))]);
1631        test_clip(bb,
1632                  vec![[-1.,0.], [2.,3.], [4.,1.]],
1633                  vec![Some((0.,1.)), Some((1.,2.)), None, Some((3., 2.))]);
1634        test_clip(bb,
1635                  vec![[0.,1.], [2.,3.], [4.,1.], [2.,1.], [2., -1.],
1636                       [0., -1.], [0., 1.] ],
1637                  vec![Some((0.,1.)), Some((1.,2.)), None,
1638                       Some((3., 2.)), None,
1639                       Some((3., 1.)), Some((2., 1.)), Some((2., 0.)), None,
1640                       Some((0., 0.)), Some((0., 1.))]);
1641    }
1642
1643    #[test]
1644    fn clip_empty() {
1645        let bb = BoundingBox {xmin: 0., xmax: 1., ymin: 0., ymax: 1.};
1646        let path = xy_of_sampling(&Sampling::empty().clip(bb));
1647        assert_eq!(path, vec![]);
1648    }
1649
1650    #[test]
1651    fn clip_double_cut() {
1652        let bb = BoundingBox { xmin: 0.,  xmax: 4., ymin: 0.,  ymax: 2.};
1653        test_clip(bb,
1654                  vec![[1., 2.], [2.,3.], [5.,0.], [-1., 0.] ],
1655                  vec![Some((1., 2.)), None,
1656                       Some((3., 2.)), Some((4., 1.)), None,
1657                       Some((4., 0.)), Some((0., 0.)) ]);
1658    }
1659
1660    #[test]
1661    fn clip_almost_horiz() {
1662        let bb = BoundingBox { xmin: 0.,  xmax: 2., ymin: -1.,  ymax: 1.};
1663        test_clip(bb,
1664                  vec![[1., 1e-100], [3., -1e-100] ],
1665                  vec![Some((1., 1e-100)), Some((2., 0.))]);
1666    }
1667
1668    #[test]
1669    fn clip_touches_1pt_cut() {
1670        let bb = BoundingBox {xmin: 0., xmax: 2., ymax: 0., ymin: -1.};
1671        let cut = [f64::NAN, f64::NAN];
1672        test_clip(bb,
1673                  vec![[0.,1.], cut, [1., 0.], cut, cut, [2., 1.]],
1674                  vec![Some((1., 0.))])
1675    }
1676
1677    #[test]
1678    fn clip_final_cut() {
1679        let bb = BoundingBox {xmin: 0., xmax: 1., ymin: 0., ymax: 2.};
1680        test_clip(bb,
1681                  vec![[0., 0.], [2., 2.]],
1682                  vec![Some((0., 0.)), Some((1., 1.))])
1683    }
1684
1685    #[test]
1686    fn uniform1() {
1687        let s = Sampling::uniform(|x| x, 0., 4.).n(3)
1688            .init(&[1.]).init_pt(&[(3., 0.)]).build();
1689        assert_eq!(xy_of_sampling(&s),
1690                   vec![Some((0.,0.)), Some((1.,1.)), Some((2.,2.)),
1691                        Some((3., 0.)), Some((4.,4.))]);
1692    }
1693
1694    #[test]
1695    fn uniform2() {
1696        let s = Sampling::uniform(|x| (4. - x).sqrt(), 0., 6.).n(4).build();
1697        assert_eq!(xy_of_sampling(&s),
1698                   vec![Some((0.,2.)), Some((2., 2f64.sqrt())),
1699                        Some((4., 0.)), None]);
1700    }
1701
1702    /// In order the judge the quality of the sampling, we save it
1703    /// with the internal cost data.
1704    fn write_with_point_costs(s: &Sampling, fname: impl AsRef<Path>) -> R<()> {
1705        let mut fh = File::create(fname)?;
1706        for p in s.path.iter() {
1707            if p.is_valid() {
1708                let [x, y] = p.xy;
1709                writeln!(fh, "{x} {y} {}", p.cost)?;
1710            } else {
1711                writeln!(fh)?;
1712            }
1713        }
1714        Ok(())
1715    }
1716
1717    fn write_segments(mut s: Sampling, fname: impl AsRef<Path>) -> R<()> {
1718        let mut fh = File::create(fname)?;
1719        let mut seg: Vec<(f64, Point, Point, f64)> = vec![];
1720        loop {
1721            let priority = s.pq.max_priority();
1722            if let Some(p0) = s.pq.pop() {
1723                let p1 = unsafe { p0.next().unwrap() };
1724                let p1 = unsafe { p1.as_ref() };
1725                let p0 = unsafe { p0.as_ref() };
1726                let tm = (p0.t + p1.t) / 2.;
1727                seg.push((tm, p0.clone(), p1.clone(), priority))
1728            } else {
1729                break;
1730            }
1731        }
1732        seg.sort_by(|(t1,_,_,_), (t2,_,_,_)| t1.partial_cmp(t2).unwrap());
1733        for (tm, p0, p1, priority) in seg {
1734            let [x0, y0] = p0.xy;
1735            let [x1, y1] = p1.xy;
1736            writeln!(fh, "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
1737                     tm, p0.t, x0, y0,  p1.t, x1, y1, priority)?;
1738        }
1739        Ok(())
1740    }
1741
1742    #[derive(Clone)]
1743    struct Plot {
1744        xmin: f64,  xmax: f64,
1745        ymin: f64,  ymax: f64,
1746        n: usize,
1747        init: Vec<f64>,
1748    }
1749
1750    impl Plot {
1751        /// Return the Gnuplot instructions to plot the data.
1752        fn plot<F>(&self, f: F, title: &str) -> R<String>
1753        where F: FnMut(f64) -> f64 {
1754            let vp = BoundingBox { xmin: self.xmin, xmax: self.xmax,
1755                                   ymin: self.ymin, ymax: self.ymax };
1756            let s = Sampling::fun(f, self.xmin, self.xmax)
1757                .n(self.n).init(self.init.iter()).viewport(vp).build();
1758            static mut NDAT: usize = 0;
1759            let ndat = unsafe { NDAT += 1;  NDAT };
1760            let dir = Path::new("target");
1761            let fname = format!("horror{}.dat", ndat);
1762            s.write(&mut File::create(dir.join(&fname))?)?;
1763            let fname_p = format!("horror{}_p.dat", ndat);
1764            write_with_point_costs(&s, dir.join(&fname_p))?;
1765            let fname_s = format!("horror{}_s.dat", ndat);
1766            write_segments(s, dir.join(&fname_s))?;
1767        Ok(format!(
1768            "unset title\n\
1769             unset y2tics\n\
1770             plot [{}:{}] \"{}\" with l lt 5 title \"{}\", \
1771             \"{}\" with p lt 3 pt 6 ps 0.2 title \"n={}\"\n\
1772             set title \"Restricted to viewport [{}:{}]×[{}:{}]\"\n\
1773             set y2tics\n\
1774             set y2range [-1e-6:]\n\
1775             plot [{}:{}] [{}:{}] \"{}\" with l lt 5 title \"{}\", \
1776             \"{}\" with p lt 3 pt 7 ps 0.2 title \"n={}\", \
1777             \"{}\" using 1:3  with lp ps 0.2 lt rgb \"#760b0b\" \
1778             title \"cost points\", \
1779             \"{}\" using 1:8 with lp ps 0.2 lt rgb \"#909090\" \
1780             axes x1y2 title \"cost segments\"\n",
1781            self.xmin, self.xmax, &fname, title, &fname, self.n,
1782            self.xmin, self.xmax, self.ymin, self.ymax,
1783            self.xmin, self.xmax, self.ymin, self.ymax, &fname, title,
1784            &fname, self.n, &fname_p, &fname_s))
1785        }
1786    }
1787
1788    #[test]
1789    #[cfg_attr(miri, ignore)] // very slow under Miri
1790    fn horror() -> R<()> {
1791        let d = Plot {
1792            xmin: -5., xmax: 5., ymin: -5., ymax: 5., n: 100, init: vec![] };
1793        macro_rules! p {
1794            ($($id:ident $l:tt $e:expr),*) => {
1795                Plot{ $($id: $e,)* ..d.clone() } };
1796        }
1797        let s = [
1798            p!(n: 10).plot(|_| 2., "x ↦ 2")?,
1799            p!().plot(|x| x, "x ↦ x")?,
1800            p!().plot(|x| 5. * x, "x ↦ 5x")?,
1801            p!().plot(|x| 1e6 * x, "x ↦ 10⁶ x")?, // high slope
1802            p!().plot(|x| 1e50 * x, "x ↦ 10⁵⁰ x")?, // high slope
1803            p!().plot(|x| 1. / x, "x ↦ 1/x")?, // check singularity
1804            p!(xmin: 0., xmax: 5., ymax: 100.).plot(
1805                |x| 1. / x, "x ↦ 1/x")?, // singularity at starting point
1806            p!(xmin: -0.4, xmax: 2., ymin: 0., ymax: 1.6, n: 50).plot(
1807                |x| x.sqrt(), "x ↦ √x")?,
1808            // Test cuts also to the right:
1809            p!(xmin: -2., xmax: 1., ymin: 0., ymax: 1.6, n: 50).plot(
1810                |x| (-x).sqrt(), "x ↦ √(-x)")?,
1811            p!(n: 200).plot(|x| x.tan(), "tan")?,
1812            p!().plot(|x| 1. / x.abs(), "x ↦ 1/|x|")?,
1813            p!(xmin: -6., xmax: 6., ymin: -2., ymax: 2.).plot(
1814                |x| (1. + x.cos().sin()).ln(), "1 + sin(cos x)")?,
1815            p!(xmin: 0., xmax: 6.28, ymin: -1.5, ymax: 1.5, n: 400).plot(
1816                |x| x.powi(3).sin() + x.powi(3).cos(), "sin x³ + cos x³")?,
1817            p!(xmin: -5., xmax:200., ymin: -1., ymax: 1., n: 400).plot(
1818                |x| x.sin(), "sin")?,
1819            // Examples from R. Avitzur, O. Bachmann, N. Kajler, "From
1820            // Honest to Intelligent Plotting", proceedings of ISSAC'
1821            // 95, pages 32-41, July 1995.
1822            p!(xmin: -4., xmax: 4., ymin: -1., ymax: 1.).plot(
1823                |x| (300. * x).sin(), "sin(300 x)")?,
1824            p!(xmin: -4., xmax: 4., ymin: -1., ymax: 1., n: 1000).plot(
1825                |x| (300. * x).sin(), "sin(300 x)")?,
1826            p!(xmin: -2., xmax: 2., ymin: 0., ymax: 3.).plot(
1827                |x| 1. + x * x + 0.0125 * (1. - 3. * (x - 1.)).abs().ln(),
1828                "1 + x² + 0.0125 ln|1 - 3(x-1)|")?,
1829            p!(xmin: -2., xmax: 2., ymin: 0., ymax: 3., n: 300,
1830               init:vec![4. / 3.]).plot(
1831                |x| 1. + x * x + 0.0125 * (1. - 3. * (x - 1.)).abs().ln(),
1832                "1 + x² + 0.0125 ln|1 - 3(x-1)| (specifying x:4/3")?,
1833            p!(xmin: -0.5, xmax: 0.5, ymin: -1., ymax: 1.).plot(
1834                |x| x * (1. / x).sin(), "x sin(1/x)")?,
1835            p!(xmin: -0.5, xmax: 0.5, ymin: -1., ymax: 1., n:200).plot(
1836                |x| x * (1. / x).sin(), "x sin(1/x)")?,
1837            p!(xmin: -2., xmax: 2., ymin: -1., ymax: 1.).plot(
1838                |x| (1. / x).sin(), "sin(1/x)")?,
1839            p!(xmin: -2., xmax: 2., ymin: -1., ymax: 1., n: 400).plot(
1840                |x| (1. / x).sin(), "sin(1/x)")?,
1841            p!(xmin: -4., xmax: 4., ymin: -1., ymax: 1.).plot(
1842                |x| x.powi(4).sin(), "sin(x⁴)")?,
1843            p!(xmin: -4., xmax: 4., ymin: -1., ymax: 1., n: 600).plot(
1844                |x| x.powi(4).sin(), "sin(x⁴)")?,
1845            p!(xmin: -6., xmax: 6., ymin: -1., ymax: 1.).plot(
1846                |x| x.exp().sin(), "sin(exp x)")?,
1847            p!(xmin: -6., xmax: 6., ymin: -1., ymax: 1., n: 500).plot(
1848                |x| x.exp().sin(), "sin(exp x)")?,
1849            p!(xmin: -10., xmax: 10., ymin: 0., ymax: 10.).plot(
1850                |x| 1. / x.sin(), "1 / sin x")?,
1851            p!(xmin: -6., xmax: 6., ymin: 0., ymax: 2.).plot(
1852                |x| x.sin() / x, "(sin x)/x")?,
1853            p!(xmin: -2., xmax: 2., ymin: -15., ymax: 15.).plot(
1854                |x| (x.powi(3) - x + 1.).tan() + 1. / (x + 3. * x.exp()),
1855                "tan(x³ - x + 1) + 1/(x + 3 eˣ)")?,
1856            p!(xmin: 0., xmax: 17., ymin: 0., ymax: 2.).plot(
1857                |x| (1. + x.cos()) * (-0.1 * x).exp(),
1858                "(1 + cos x) exp(-x/10)")?,
1859            p!(xmin: -2., xmax: 17., ymin: 0., ymax: 2.).plot(
1860                |x| (1. + x.cos()) * (-0.1 * x).exp(),
1861                "(1 + cos x) exp(-x/10)")?,
1862            p!(xmin: 0., xmax: 17., ymin: 0., ymax: 2.).plot(
1863                |x| (1. + x.cos()) * (-0.01 * x * x).exp(),
1864                "(1 + cos x) exp(-x²/100)")?,
1865        ].join("");
1866        let mut fh = File::create("target/horror.gp")?;
1867        write!(fh, "set terminal pdfcairo\n\
1868                    set output \"horror.pdf\"\n\
1869                    set grid\n\
1870                    {}\n", s)?;
1871        Ok(())
1872    }
1873
1874
1875}