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
//! Modelling code
//!
//! This module contains all code used to model data produced by the main executable.

use std::{fmt::Display, fs::File, iter::zip, ops::Index};

//~~~~~~~~~~~~~~
// Tallies data
//~~~~~~~~~~~~~~

/// Number of fields presented in the tallies report produced
/// by `fastiron`.
pub const N_TALLIED_DATA: usize = 17;

/// Enum used to represent & map tallied data and their indexes.
#[derive(Debug, Clone, Copy)]
pub enum TalliedData {
    /// Cycle index.
    Cycle = 0,
    /// Number of particles at the start of the cycle.
    Start = 1,
    /// Number of particles sourced.
    Source = 2,
    /// Number of particles Russian-Rouletted.
    Rr = 3,
    /// Number of split particles.
    Split = 4,
    /// Number of absorbed particles.
    Absorb = 5,
    /// Number of particles that underwent a scatter reaction.
    Scatter = 6,
    /// Number of particles that underwent a fission reaction.
    Fission = 7,
    /// Number of particles produced by a fission reaction.
    Produce = 8,
    /// Number of particles that underwent a reaction.
    Collision = 9,
    /// Number of particles that escaped the problem.
    Escape = 10,
    /// Number of particles that reached census.
    Census = 11,
    /// Number of segments computed this cycle.
    NumSeg = 12,
    /// Overall scalar flux value this cycle.
    ScalarFlux = 13,
    /// Time spent section this cycle.
    PopulationControl = 14,
    /// Time spent section this cycle.
    CycleTracking = 15,
    /// Time spent section this cycle.
    CycleSync = 16,
}

/// Custom [`Display`] implementation for easier tics generation when plotting.
impl Display for TalliedData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match *self {
                TalliedData::Cycle => "Cycle",
                TalliedData::Start => "Start",
                TalliedData::Source => "Source",
                TalliedData::Rr => "Rr",
                TalliedData::Split => "Split",
                TalliedData::Absorb => "Absorb",
                TalliedData::Scatter => "Scatter",
                TalliedData::Fission => "Fission",
                TalliedData::Produce => "Produce",
                TalliedData::Collision => "Collision",
                TalliedData::Escape => "Escape",
                TalliedData::Census => "Census",
                TalliedData::NumSeg => "NumSeg",
                TalliedData::ScalarFlux => "ScalarFlux",
                TalliedData::PopulationControl => "PopulationControl",
                TalliedData::CycleTracking => "CycleTracking",
                TalliedData::CycleSync => "CycleSync",
            }
        )
    }
}

/// Structure used to model tallied events, interpreted as discrete finite random
/// variables.
///
/// This structure is not meant to be modified. It should be initialized with all
/// values using the provided constructor.
#[derive(Debug)]
pub struct TalliedVariable {
    /// Values taken by the random variables.
    pub values: Vec<f64>,
    /// Associated mean.
    pub mean: f64,
    /// Associated variance.
    pub variance: f64,
}

impl TalliedVariable {
    /// Constructor. Takes a slice as values and computes key associated values
    /// before returning the object.
    pub fn new(values: &[f64]) -> Self {
        let n_val = values.len() as f64;
        let val = values.to_vec();
        let mut mean = val.iter().sum();
        mean /= n_val;
        let mut var = val.iter().map(|xi| (xi - mean) * (xi - mean)).sum();
        var /= n_val;

        Self {
            values: val,
            mean,
            variance: var,
        }
    }

    /// Returns the number of values taken by the (discrete, finite) random variable
    pub fn n_val(&self) -> usize {
        self.values.len()
    }
}

/// Returns the covariance of two given [TalliedVariable].
pub fn covariance(x: &TalliedVariable, y: &TalliedVariable) -> f64 {
    assert_eq!(x.n_val(), y.n_val());
    let iter = zip(x.values.iter(), y.values.iter());
    let mut cov = iter.map(|(xi, yi)| (xi - x.mean) * (yi - y.mean)).sum();
    cov /= x.n_val() as f64;
    cov
}

/// Returns the correlation coefficient of two given [TalliedVariable].
///
/// The function checks if `x` and `y` have non-zero variance. If this is the case,
/// 0 is returned. It means variables are independent. While this may be technically
/// false, it allows for generic computations.
pub fn correlation(x: &TalliedVariable, y: &TalliedVariable) -> f64 {
    if (x.variance == 0.0) | (y.variance == 0.0) {
        //
        return 0.0;
    }
    let cov = covariance(x, y);
    cov / (x.variance * y.variance).sqrt()
}

/// Structure modelling a report produced by the main executable.
pub struct TalliesReport {
    /// Data represented as variable.
    pub tallies_data: [TalliedVariable; N_TALLIED_DATA],
}

/// Custom [`From`] implementation for processing at initialization.
impl From<File> for TalliesReport {
    fn from(file: File) -> Self {
        let mut reader = csv::ReaderBuilder::new().delimiter(b';').from_reader(file);
        let mut values: [Vec<f64>; N_TALLIED_DATA] = Default::default();
        values.iter_mut().for_each(|v| v.reserve(100));
        // for each line
        for result in reader.records() {
            let mut record = result.unwrap();
            record.trim();
            // for each column
            (0..N_TALLIED_DATA).for_each(|idx| {
                let val = record.get(idx).unwrap();
                values[idx].push(val.parse().unwrap())
            })
        }
        // convert value vectors to our structure
        Self {
            tallies_data: values.map(|val| TalliedVariable::new(&val)),
        }
    }
}

impl Index<TalliedData> for TalliesReport {
    type Output = TalliedVariable;

    fn index(&self, tallied_data: TalliedData) -> &Self::Output {
        &self.tallies_data[tallied_data as usize]
    }
}

//~~~~~~~~~~~~
// Timer data
//~~~~~~~~~~~~

/// Number of sections in a timers report.
pub const N_TIMERS: usize = 6;

/// Array of the sections of a timers report.
pub const TIMERS_ARR: [TimerSV; N_TIMERS] = [
    TimerSV::Main,
    TimerSV::PopulationControl,
    TimerSV::CycleTracking,
    TimerSV::CycleTrackingProcess,
    TimerSV::CycleTrackingSort,
    TimerSV::CycleSync,
];

/// Enum used to represent & map timers breakdown and their indexes.
#[derive(Debug, Clone, Copy)]
pub enum TimerSV {
    Main = 0,
    PopulationControl = 1,
    CycleTracking = 2,
    CycleTrackingProcess = 3,
    CycleTrackingSort = 4,
    CycleSync = 5,
}

/// Custom [`Display`] implementation for easier tics generation when plotting.
impl Display for TimerSV {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match *self {
                TimerSV::Main => "Section::Main",
                TimerSV::PopulationControl => "Section::PopulationControl",
                TimerSV::CycleTracking => "Section::CycleTracking",
                TimerSV::CycleTrackingProcess => "Section::CycleTrackingProcess",
                TimerSV::CycleTrackingSort => "Section::CycleTrackingSort",
                TimerSV::CycleSync => "Section::CycleSync",
            }
        )
    }
}

/// Structure used to represent the summarized data of the timers.
#[derive(Default, Clone, Copy, Debug)]
pub struct SummarizedVariable {
    /// Average value taken by the timer.
    pub mean: f64,
    /// Lowest value taken by the timer.
    pub lowest: f64,
    /// Highest value taken by the timer.
    pub highest: f64,
    /// Sum of all value taken by the timer.
    pub total: f64,
}

/// Structure used to represent the entire timer report of a single simulation
pub struct TimerReport {
    /// Array of the section timers.
    pub timers_data: [SummarizedVariable; N_TIMERS],
}

/// Custom [`From`] implementation for processing at initialization.
impl From<File> for TimerReport {
    fn from(file: File) -> Self {
        let mut res = [SummarizedVariable::default(); N_TIMERS];
        let mut reader = csv::ReaderBuilder::new().delimiter(b';').from_reader(file);

        // for each line
        for (timer_idx, result) in reader.records().enumerate() {
            let mut record = result.unwrap();
            record.trim();
            // lmao
            res[timer_idx].lowest = record.get(2).unwrap().parse().unwrap();
            res[timer_idx].mean = record.get(3).unwrap().parse().unwrap();
            res[timer_idx].highest = record.get(4).unwrap().parse().unwrap();
            res[timer_idx].total = record.get(5).unwrap().parse().unwrap();
        }

        Self { timers_data: res }
    }
}

impl Index<TimerSV> for TimerReport {
    type Output = SummarizedVariable;

    fn index(&self, timer: TimerSV) -> &Self::Output {
        &self.timers_data[timer as usize]
    }
}