1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
use{
    crate::*,
    std::{
        io::Write,
        borrow::*,
        convert::*
    },
    transpose::*
};

#[cfg(feature = "serde_support")]
use serde::{Serialize, Deserialize};

/// # Heatmap
/// * stores heatmap in row-major order: the rows of the heatmap are contiguous,
/// and the columns are strided
/// * enables you to quickly create a heatmap
/// * you can create gnuplot scripts to plot the heatmap
/// * you can transpose the heatmap
/// * …
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct HeatmapF64<HistWidth, HistHeight>{
    pub(crate) hist_width: HistWidth,
    pub(crate) hist_height: HistHeight,
    pub(crate) width: usize,
    pub(crate) height: usize,
    pub(crate) heatmap: Vec<f64>, // stored width, height
    pub(crate) error_count: usize
}

impl<HistWidth, HistHeight> From<HeatmapU<HistWidth, HistHeight>> for HeatmapF64<HistWidth, HistHeight>
where 
    HistWidth: Histogram,
    HistHeight: Histogram,
{
    fn from(other: HeatmapU<HistWidth, HistHeight>) -> Self {
        let mut heatmap = Vec::with_capacity(other.heatmap().len());
        heatmap.extend(
            other.heatmap()
                .iter()
                .map(|&val| val as f64)
        );
        Self{
            heatmap,
            width: other.width,
            height: other.height,
            hist_width: other.hist_width,
            hist_height: other.hist_height,
            error_count: other.error_count,
        }
    }
}

impl <HistWidth, HistHeight> HeatmapF64<HistWidth, HistHeight>
where 
    HistWidth: Clone,
    HistHeight: Clone,
{
    /// # Use this to get a "flipped" heatmap
    /// * creates a transposed heatmap
    /// * also look at [`self.transpose_inplace`](#method.transpose_inplace)
    pub fn transpose(&self) -> HeatmapF64<HistHeight, HistWidth>
    {
        let mut transposed = vec![0.0; self.heatmap.len()];
        transpose(
            &self.heatmap,
            &mut transposed,
            self.width,
            self.height
        );
        HeatmapF64{
            hist_width: self.hist_height.clone(),
            hist_height: self.hist_width.clone(),
            width: self.height,
            height: self.width,
            error_count: self.error_count,
            heatmap: transposed,
        }
    }
}

impl <HistWidth, HistHeight> HeatmapF64<HistWidth, HistHeight>
{

    /// # Use this to get a "flipped" heatmap
    /// * transposes the heatmap inplace
    pub fn transpose_inplace(mut self) -> HeatmapF64<HistHeight, HistWidth>
    {
        let mut scratch = vec![0.0; self.width.max(self.height)];
        transpose_inplace(&mut self.heatmap, &mut scratch, self.width, self.height);
        HeatmapF64{
            hist_width: self.hist_height,
            hist_height: self.hist_width,
            width: self.height,
            height: self.width,
            error_count: self.error_count,
            heatmap: self.heatmap
        }
    }

    /// x = j
    /// y = i
    #[inline(always)]
    fn index(&self, x: usize, y: usize) -> usize
    {
        heatmap_index(self.width, x, y)
    }

    /// Returns value stored in the heatmap at specified 
    /// coordinates, or `None`, if out of Bounds
    pub fn get(&self, x: usize, y: usize) -> Option<f64>
    {
        self.heatmap.get(self.index(x, y)).copied()
    }

    /// # row of the heatmap
    /// * `None` if out of bounds
    /// * otherwise it is a slice of the row at height `y`
    /// # Note
    /// *  there is no `get_column` method, because, due to implementation details,
    /// it is way less efficient, and could not be returned as slice
    pub fn get_row(&self, y: usize) -> Option<&[f64]>
    {
        let fin = self.index(self.width, y);
        if fin > self.heatmap.len(){
            None
        } else {
            let start = self.index(0, y);
            Some(
                &self.heatmap[start..fin]
            )
        }
    }

    /// # row of the heatmap
    /// * returns reference of Slice of the specifed row of the heatmap without checking for bounds 
    /// * Generally not recommended, use with caution! 
    /// ## Safety 
    /// Calling this with out-of-bounds index will result in undefined behavior!
    pub unsafe fn get_row_unchecked(&self, y: usize) -> &[f64]
    {
        let fin = self.index(self.width, y);
        let start = self.index(0, y);
        self.heatmap.get_unchecked(start..fin)
    }

    /// Returns value stored in the heatmap at specified 
    /// coordinates without performing bound checks.
    /// ## Safety
    /// **undefined behavior** if coordinates are out of bounds
    pub unsafe fn get_unchecked(&self, x: usize, y: usize) -> f64
    {
        *self.heatmap.get_unchecked(self.index(x, y))
    }

    /// # returns width of the heatmap
    /// * the width is the same size, as the `self.width_projection().bin_count()` 
    pub fn width(&self) -> usize
    {
        self.width
    }

    /// # returns height of the heatmap
    /// * the height is the same size, as the `self.height_projection().bin_count()` 
    pub fn height(&self) -> usize
    {
        self.height
    } 


    /// # Returns reference to current width Histogram
    /// * statistical information of how often a count hit a specific width
    pub fn width_count_hist(&self) -> &HistWidth{
        &self.hist_width
    }

    /// # Returns reference to current height Histogram
     /// * statistical information of how often a count hit a specific height
    pub fn height_count_hist(&self) -> &HistHeight{
        &self.hist_height
    }

    /// # Returns reference to current width Histogram
    /// * histogram used to bin in the "width" direction
    /// * all `counts` are counted here -> this is a projection of the heatmap
    pub fn width_hist(&self) -> &HistWidth{
        &self.hist_width
    }

    /// # Returns reference to current height Histogram
    /// * histogram used to bin in the "height" direction
    /// * all `counts` are counted here -> this is a projection of the heatmap
    pub fn height_hist(&self) -> &HistHeight{
        &self.hist_height
    }
}


impl<HistWidth, HistHeight> HeatmapF64<HistWidth, HistHeight>
where 
    HistWidth: Histogram,
    HistHeight: Histogram,
{

    /// # Create a new Heatmap
    /// * heatmap will have width `width_hist.bin_count()` 
    /// and height `height_hist.bin_count()`
    /// * histograms will be reset (zeroed) here, so it does not matter, if they 
    /// were used before and contain Data
    pub fn new(mut width_hist: HistWidth, mut height_hist: HistHeight) -> Self {
        let width = width_hist.bin_count();
        let height = height_hist.bin_count();
        width_hist.reset();
        height_hist.reset();
        let heatmap = vec![0.0; width * height];
        Self{
            width,
            height,
            heatmap,
            hist_width: width_hist,
            hist_height: height_hist,
            error_count: 0
        }
    }

    /// # Reset
    /// * resets histograms 
    /// * heatmap is reset to contain only 0's
    /// * miss_count is reset to 0
    pub fn reset(&mut self)
    {
        self.hist_width.reset();
        self.hist_height.reset();
        self.heatmap.iter_mut().for_each(|v| *v = 0.0);
        self.error_count = 0;
    }

    /// # "combine" heatmaps
    /// * heatmaps have to have the same dimensions
    /// * miss counts of other will be added to self
    /// * with and hight histogram counts will be added to self
    /// * `self.heatmap` will be modified at each index by 
    /// `self.heatmap[i] = combine_fn(self.heatmap[i], other.heatmap[i])`
    /// # Usecase
    /// * e.g. if you want to add, subtract or multiply two heatmaps
    pub fn combine<OtherHW, OtherHH, F>
    (
        &mut self,
        other: &HeatmapF64<OtherHW, OtherHH>,
        combine_fn: F
    ) -> Result<(), HeatmapError>
    where OtherHW: Histogram,
        OtherHH: Histogram,
        F: Fn(f64, f64) -> f64
    {
        if self.width != other.width || self.height != other.height
        {
            return Err(HeatmapError::Dimension);
        }
        self.heatmap
            .iter_mut()
            .zip(
                other.heatmap.iter()
            ).for_each(
                |(this, &other)|
                {
                    *this = combine_fn(*this, other);
                }
            );
        
        for (i, &count) in other.hist_width.hist().iter().enumerate()
        {
            self.hist_width
                .count_multiple_index(i, count)
                .unwrap()
        }

        for (i, &count) in other.hist_height.hist().iter().enumerate()
        {
            self.hist_height
                .count_multiple_index(i, count)
                .unwrap()
        }
        self.error_count += other.error_count;
        
        Ok(())
    }

    /// # counts how often the heatmap was hit
    /// * should be equal to `self.heatmap.iter().sum::<usize>()` but more efficient
    /// * Note: it calculates this in O(min(self.width, self.height))
    pub fn total(&self) -> usize {
        if self.width <= self.height {
            self.hist_width.hist().iter().sum()
        } else {
            self.hist_height.hist().iter().sum()
        }
    }

    /// # Counts how often the Heatmap was missed, i.e., you tried to count a value (x,y), which was outside the Heatmap
    pub fn total_misses(&self) -> usize
    {
        self.error_count
    }


    /// # returns heatmap
    /// * each vector entry will contain the number of times, the corresponding bin was hit
    /// * an entry is 0 if it was never hit
    /// # Access indices; understanding how the data is mapped
    /// * A specific heatmap location `(x,y)`
    /// corresponds to the index `y * self.width() + x`
    /// * you can use the `heatmap_index` function to calculate the index
    pub fn heatmap(&self) -> &Vec<f64>
    {
        &self.heatmap
    }

    /// # Normalizes self
    /// * Afterwards sum over all entrys (within numerical precision) should be 1.0
    pub fn normalize_total(&mut self)
    {
        let sum = self.heatmap.iter().sum::<f64>();
        
        self.heatmap
            .iter_mut()
            .for_each(|val| *val /= sum);
    }

    /// # Normalizes self
    /// * Afterwards the sum of each column (fixed x) will be 1.0, if the sum of the row was not 0.0 before
    ///  If it did not, the column will only consist of 0.0
    pub fn normalize_columns(&mut self)
    {

        for x in 0..self.width {
            let denominator: f64 = (0..self.height)
                .map(|y| unsafe{self.get_unchecked(x, y)})
                .sum();

            if denominator != 0.0 {
                for y in 0..self.height {
                    let index = self.index(x, y);
                    unsafe {
                        *self.heatmap.get_unchecked_mut(index) /= denominator;
                    }
                }
            }
        }
    }

    /// # Normalizes self
    /// * Afterwards the sum of each row (fixed y) will be 1.0, if the sum of the row was not 0.0 before
    pub fn heatmap_normalize_rows(&mut self)
    {
        for y in 0..self.height {
            let row_sum = unsafe {self.get_row_unchecked(y).iter().sum::<f64>()};

            if row_sum != 0.0 {
                let index = self.index(0, y);
                for i in index..index + self.width {
                    unsafe {
                        *self.heatmap.get_unchecked_mut(i) /=  row_sum;
                    }
                }
            }
        }
    }
}

impl<HistWidth, HistHeight> HeatmapF64<HistWidth, HistHeight>
where 
    HistWidth: Histogram,
    HistHeight: Histogram,

{
    /// # update the heatmap
    /// * calculates the coordinate `(x, y)` of the bin corresponding
    /// to the given value pair `(width_val, height_val)`
    /// * if coordinate is out of bounds, it counts a "miss" and returns the HeatmapError
    /// * otherwise it counts the "hit" (by adding `val` to the heatmap at the corresponding location)
    /// and returns the coordinate `(x, y)` of the hit 
    pub fn count<A, B, X, Y>(&mut self, width_val: A, height_val: B, val: f64) -> Result<(usize, usize), HeatmapError>
    where 
        HistWidth: HistogramVal<X>,
        HistHeight: HistogramVal<Y>,
        A: Borrow<X>,
        B: Borrow<Y>
    {
        let x = self.hist_width
            .get_bin_index(width_val)
            .map_err(|e| {
                    self.error_count += 1;
                    HeatmapError::XError(e)
                }
            )?;
        let y = self.hist_height
            .count_val(height_val)
            .map_err(|e| {
                self.error_count += 1;
                HeatmapError::YError(e)
            }
        )?;
        
        let index = self.index(x, y);
        unsafe{
            *self.heatmap.get_unchecked_mut(index) += val;
        }

        self.hist_width
            .count_index(x)
            .unwrap();

        Ok((x, y))
    }

    /// # Write heatmap to file
    /// * writes data of heatmap to file.
    /// # file
    /// * lets assume `self.width()`is 4 and `self.height()` is 3
    /// * the resulting file could look like
    /// ```txt
    /// 0.1 1.0 0.0 10.0
    /// 100.0 0.2 0.3 1.1
    /// 2.2 9.3 1.0 0.0
    /// ```
    pub fn write_to<W>(&self, mut data_file: W) -> std::io::Result<()>
    where W: Write
    {
        for y in 0..self.height {
            let row = unsafe{ self.get_row_unchecked(y) };

            if let Some((last, slice)) = row.split_last() {
                for val in slice {
                    write!(data_file, "{:e} ", val)?;
                }
                writeln!(data_file, "{:e}", last)?;
            }
        }
        Ok(())
    }

    /// # Create a gnuplot script to plot your heatmap
    /// * `writer`: The gnuplot script will be written to this
    /// # Note
    /// * This is the same as calling [`gnuplot`](Self::gnuplot) with default
    /// `GnuplotSettings`
    /// * The default axis are the bin indices, which, e.g, means they always 
    /// begin at 0. You have to set the axis via the [GnuplotSettings](crate::heatmap::GnuplotSettings)
    pub fn gnuplot_quick<W>(
        &self,
        writer: W
    ) -> std::io::Result<()>
    where 
        W: std::io::Write
    {
        let mut d = GnuplotSettings::default();
        let default = d
            .terminal(GnuplotTerminal::Empty);
        self.gnuplot(
            writer,
            default
        )
    }

    /// # Create a gnuplot script to plot your heatmap
    /// This function writes a file, that can be plottet via the terminal via [gnuplot](http://www.gnuplot.info/)
    /// ```bash
    /// gnuplot gnuplot_file
    /// ```
    /// ## Parameter:
    /// * `gnuplot_writer`: writer gnuplot script will be written to
    /// * `gnuplot_output_name`: how shall the file, created by executing gnuplot, be called? Ending of file will be set automatically
    /// ## Note
    /// * The default axis are the bin indices, which, e.g, means they always 
    /// begin at 0. You have to set the axis via the [GnuplotSettings](crate::heatmap::GnuplotSettings)
    /// ## Example
    /// ```
    /// use rand_pcg::Pcg64;
    /// use rand::{SeedableRng, distributions::*};
    /// use sampling::*;
    /// use std::fs::File;
    /// use std::io::BufWriter;
    /// 
    /// // first randomly create a heatmap
    /// let h_x = HistUsizeFast::new_inclusive(0, 10).unwrap();
    /// let h_y = HistU8Fast::new_inclusive(0, 10).unwrap();
    ///
    /// let mut heatmap = HeatmapU::new(h_x, h_y);
    /// heatmap.count(0, 0).unwrap();
    /// heatmap.count(10, 0).unwrap();
    ///
    /// let mut rng = Pcg64::seed_from_u64(27456487);
    /// let x_distr = Uniform::new_inclusive(0, 10_usize);
    /// let y_distr = Uniform::new_inclusive(0, 10_u8);
    ///
    /// for _ in 0..100000 {
    ///     let x = x_distr.sample(&mut rng);
    ///     let y = y_distr.sample(&mut rng);
    ///     heatmap.count(x, y).unwrap();
    /// }
    /// 
    /// // create File for gnuplot skript
    /// let file = File::create("heatmap_normalized.gp").unwrap();
    /// let buf = BufWriter::new(file);
    ///
    /// // Choose settings for gnuplot
    /// let mut settings = GnuplotSettings::new();
    /// settings.x_axis(GnuplotAxis::new(-5.0, 5.0, 6))
    ///     .y_axis(GnuplotAxis::from_slice(&["a", "b", "c", "d"]))
    ///     .y_label("letter")
    ///     .x_label("number")
    ///     .title("Example")
    ///     .terminal(GnuplotTerminal::PDF("heatmap_normalized".to_owned()));
    ///
    /// 
    /// // norm heatmap row wise - this converts HeatmapU to HeatmapfF64
    /// let heatmap = heatmap.into_heatmap_normalized_rows();
    ///
    /// // create skript
    /// heatmap.gnuplot(
    ///     buf,
    ///     settings
    /// ).unwrap();
    /// ```
    /// Skript can now be plotted with
    /// ```bash
    /// gnuplot heatmap_normalized.gp
    /// ```
    pub fn gnuplot<W, GS>(
        &self,
        mut gnuplot_writer: W,
        settings: GS
    ) -> std::io::Result<()>
    where 
        W: Write,
        GS: Borrow<GnuplotSettings>
    {
        let settings: &GnuplotSettings = settings.borrow();
        let x_len = self.width;
        let y_len = self.height;
        settings.write_heatmap(
            &mut gnuplot_writer,
            |w| self.write_to(w),
            x_len,
            y_len
        )
    }

}