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
//! # lmb_engine_simulator
//!
//! The `lmb_engine_simulator` crate provides an easy way to simulate internal combustion engines.
//!
//! This library employed the **Builder Pattern** so the user feels as she/he is acctually building
//! an engine system. To construct (build) the system, the struct [`SystemBuilder`](core/system_builder/struct.SystemBuilder.html)
//! is used to add the desired components. After finishing building, the method `build_system()` can
//! be used to return an object [`System`](core/system/struct.System.html) which is used to solve the components numerically.
//!
//! ## Current stage
//!
//! The library allows you to build [Gas](reaction/gas/index.html), [Zero Dimensional](zero_dim/index.html) and [Connector](connector/index.html) objects.
//! The basic logic of the program is to use the `SystemBuilder` to build all the objects and then
//! indicate how the objects are connected with each other. Basicly, all of the dimensional objects
//! require a `Gas` to be created and a `Gas` object is created from a .json file. Currently, the
//! only available one is `air.json`. However, they were made in a way to be easily contructed.
//! Once all dimensional objects and connections were added, the `SystemBuilder` object can use the
//! method `build_system()` to create an object of type `System`, which will carry all the
//! [Zero Dimensional](zero_dim/index.html) and [Connector](connector/index.html) objects. The `System`
//! object is responsible to manage the interaction between the objects, the simulation process, the storable variables.
//!
//! In order to simulate, two methods can be used: [`advance(dt)`](core/system/struct.System.html#method.advance) 
//! which advance the state of the objects by `dt` and [`advance_to_steady_state()`](core/system/struct.System.html#method.advance_to_steady_state)
//! which advances until the system reaches steady state. **OBS: So far, steady state conditions are not checked. 
//! The method runs long enough that almost every system will have reached steady state by then.**
//! For engine simulation, most commonly, it is used [`advance_to_steady_state()`](core/system/struct.System.html#method.advance_to_steady_state).
//! After the simulation is finished, all stored variables can only be accessed by writing them into a 
//! file via system method [`write_to_file()`](core/system/struct.System.html#method.write_to_file) 
//!
//! ### Example
//! A simple system with a [Reservoir](zero_dim/reservoir/struct.Reservoir.html) and [Environment](zero_dim/environment/struct.Environment.html)
//! connected by an [Orifice](connector/orifice/struct.Orifice.html) is created and simulated until steady state. After, the stored data is 
//! written into two files.
//! ```
//! use lmb::Gas;
//! use lmb_engine_simulator as lmb;
//! 
//! fn main() {
//!     let mut gas_ambient = Gas::new("air.json");
//!     gas_ambient.TPX(293.0, 2.0*101325.0, "O2:0.21, N2:0.79");
//!     let mut gas_chamber = Gas::new("air.json");
//!     gas_chamber.TPX(293.0, 101325.0, "O2:0.21, N2:0.79");
//!     let mut builder = lmb::SystemBuilder::new();
//!     builder
//!         .add_environment("ambient", &gas_ambient)
//!         .add_reservoir("chamber", 500.0, &gas_chamber)
//!         .add_orifice("orifice", 50.0, 0.9, vec!["ambient", "chamber"]);
//!
//!     let mut system = builder.build_system();
//!    
//!     // Calculating
//!     system.advance_to_steady_state();
//!    
//!     // Writting data
//!     system.write_to_file("chamber.txt", "chamber", None);
//!     system.write_to_file("orifice.txt", "orifice", None);
//! }

//! ```
//! 
//! ## Simulating engines
//! 
//! To add an engine to the system, the method [`add_engine("engine_file.json", "gas")`](core/system_builder/struct.SystemBuilder.html#method.add_engine)
//! of `SystemBuilder` must be used. The `engine_file.json` is read into the struct [`json_engine`](engine/json_reader/struct.JsonEngine.html). 
//! This file **must** have at least the following attributes:
//! * "speed" in RPM,
//! * "eccentricity" in mm,
//! * "conrod" in mm,
//! * "displacement" in cm³,
//! * "bore" in mm,
//! * "firing_order" as a string - i.e "1-3-2",
//! * "cylinders" as a vector of structs [`json_cylinder`](engine/json_reader/struct.JsonCylinder.html)
//! 
//! All possible attributes of the `engine_file.json` file can be found at the full documentation at [`Json Reader`](engine/json_reader/index.html).
//! **Attention when entering the variables in crank-angle degree!** The reference, where crank-angle is zero, 
//! is at top-dead-center (TDC) of compression phase and it only accepts positive numbers. 
//! Therefore, the full cycle starts in 0 CA-deg and finishes at 720 CA-deg. 
//! 
//! ### Example
//! Let's simulate a simple engine system with intake and exhaust manifolds as [environments](zero_dim/environment/struct.Environment.html)
//! connected to a single cylinder through only two valves (intake and exhaust). The `engine.json` file will be
//! ```
//! {
//!     "speed": 3000.0,
//!     "eccentricity": 0.0,
//!     "conrod": 145.6,
//!     "displacement": 400.0,
//!     "bore": 80.0,
//!     "firing_order": "1",
//!     "combustion": {
//!         "model": "Two-zone model",        
//!         "comb_ini": 690.0,
//!         "wiebe": {
//!             "m": 2.0,
//!             "a": 6.908,
//!             "comb_duration": 40.0
//!         }
//!     },
//!     "injector": {
//!         "inj_type": "port",
//!         "air_fuel_ratio": 1.0,
//!         "fuel": {
//!             "name": "C2H5OH",
//!             "state": "liquid",
//!             "lhv": 25.858e06,
//!             "heat_vap": 900.0e3
//!         }
//!     },
//!     "cylinders": [
//!         {
//!             "name": "cyl_1",
//!             "compression_ratio": 12.50,
//!             "wall_temperature": 520.0,
//!             "store_species": true,
//!             "intake_valves": [
//!                 {
//!                     "name": "valve_int",
//!                     "opening_angle": 340.0,
//!                     "closing_angle": 570.0,
//!                     "diameter": 30.93,
//!                     "max_lift": 9.30
//!                 }
//!             ],
//!             "exhaust_valves": [
//!                 {
//!                     "name": "valve_exh",
//!                     "opening_angle": 130.0,
//!                     "closing_angle": 375.0,
//!                     "diameter": 28.27,
//!                     "max_lift": 8.48
//!                 }
//!             ]
//!         }
//!     ]
//! }
//! ```
//! 
//! In this example, we added an [`injector`](engine/json_reader/struct.JsonInjector.html), with relative air fuel ratio equal 1.0 
//! and ethanol (C2H5OH) as fuel, and a [`combustion`](engine/json_reader/struct.JsonCombustion.html) model. 
//! If they are not added, the engine will run as a motoring. 
//! Right now, the **only combustion model implemented** is the "Two-zone model". Notice that the 
//! [`cylinder`](engine/json_reader/struct.JsonCylinder.html) requires both intake and exhaust [`valves`](engine/json_reader/struct.JsonValve.html) 
//! connected to it. In the `main.rs`, we will need to connect these valves to their ports with 
//! [`connect_from_to("valve_name", "object_name")`](core/system_builder/struct.SystemBuilder.html#method.connect_from_to) method.
//! 
//! The `main.rs` can be written as: 
//! 
//! ```
//! use lmb::Gas;
//! use lmb_engine_simulator as lmb;
//!
//! fn main() {
//!     let gas_intake = Gas::new("air.json");
//!     let mut gas_exhaust = Gas::new("air.json");
//!     gas_exhaust.TPX(500.0, 101325.0, "N2:0.662586, H2O:0.202449, CO2:0.134965");
//!     let mut builder = lmb::SystemBuilder::new();
//!     builder
//!         .add_engine("engine.json", &gas_intake)
//!         .add_environment("intake_port", &gas_intake)
//!         .add_environment("exhaust_port", &gas_exhaust)
//!         .connect_from_to("valve_int", "intake_port")
//!         .connect_from_to("valve_exh", "exhaust_port");
//!
//!     let mut system = builder.build_system();
//!    
//!     // Calculating
//!     system.advance_to_steady_state();
//!    
//!     // Writting data
//!     system.write_to_file("cylinder.txt", "cyl_1", None);
//!     system.write_to_file("intake_valve.txt", "valve_int", None);
//!     system.write_to_file("exhaust_valve.txt", "valve_exh", None);
//! }
//! ```
//! 
//! For a real-life engine simulation, see [Engine Examples](doc/Ryobi_26cm3_engine/index.html)


use crate::base::constants::MAX_ARRAY_LEN;
use ndarray::*;
use std::io::Write;
use std::ops::Add;

pub mod base;
pub mod connector;
pub mod core;
pub mod engine;
pub mod numerics;
pub mod one_dim;
pub mod reaction;
pub mod zero_dim;
mod doc;

// Re-exporting
pub use crate::core::system_builder::SystemBuilder;
pub use crate::engine::engine::Engine;
pub use crate::reaction::combustion;
pub use crate::reaction::gas::Gas;

// Object's type
#[derive(Debug, Clone)]
pub enum ObjectType {
    ZeroDim,
    OneDim,
    Connector,
    Cylinder,
}

#[derive(Debug, Clone)]
pub struct BasicProperties<'a> {
    pub name: &'a str,
    pub pressure: f64,    // Pa
    pub temperature: f64, // K
    pub cp: f64,          // J/(kg.K)
    pub cv: f64,          // J/(kg.K)
    pub cp_cv: f64,
    pub gas_const: f64,           // J/(kg.K)
    pub crank_angle: Option<f64>, // CA rad
}

impl<'a> std::fmt::Display for BasicProperties<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}: 
        pressure: {} [Pa] 
        temperature: {} [K]
        cp: {} [J/(kg.K)]
        cv: {} [J/(kg.K)]
        cp/cv: {}
        R: {} [J/(kg.K)]",
            self.name,
            self.pressure,
            self.temperature,
            self.cp,
            self.cv,
            self.cp_cv,
            self.gas_const
        )
    }
}

#[derive(Debug, Clone)]
pub struct FlowRatio {
    pub mass_flow: f64,     // kg/s
    pub enthalpy_flow: f64, // J/s
}

impl FlowRatio {
    pub fn new() -> FlowRatio {
        FlowRatio {
            mass_flow: 0.0,
            enthalpy_flow: 0.0,
        }
    }
}

impl Add for FlowRatio {
    type Output = FlowRatio;
    fn add(self, other: FlowRatio) -> FlowRatio {
        FlowRatio {
            mass_flow: self.mass_flow + other.mass_flow,
            enthalpy_flow: self.enthalpy_flow + other.enthalpy_flow,
        }
    }
}

impl<'a, 'b> Add<&'b FlowRatio> for &'a FlowRatio {
    type Output = FlowRatio;
    fn add(self, other: &'b FlowRatio) -> FlowRatio {
        FlowRatio {
            mass_flow: self.mass_flow + other.mass_flow,
            enthalpy_flow: self.enthalpy_flow + other.enthalpy_flow,
        }
    }
}

#[derive(Debug, Clone)]
pub struct StoreData {
    header: String,
    data: Array2<f64>,
    last_index: usize,
}

impl StoreData {
    fn new(header: &str, num_variables: usize) -> StoreData {
        StoreData {
            header: header.to_string(),
            data: Array::from_elem((MAX_ARRAY_LEN, num_variables), 0.),
            last_index: 0,
        }
    }

    fn add_data(&mut self, data: Array1<f64>) {
        if self.last_index == MAX_ARRAY_LEN - 1 {
            println!("Error! Maximum allow array length exceeded!");
            std::process::exit(1);
        }
        self.data.row_mut(self.last_index).assign(&data);
        self.last_index += 1;
    }

    fn get_data(&self, rows_range: (usize, usize), columns: Vec<usize>) -> Array2<f64> {
        let mut data: Array2<f64> = Array2::zeros((rows_range.1 - rows_range.0, columns.len()));
        for (i, col) in columns.iter().enumerate() {
            let filtered_data = self.data.slice(s![rows_range.0..rows_range.1, *col]);
            data.slice_mut(s![.., i]).assign(&filtered_data);
        }
        data
    }

    fn reset_data(&mut self) {
        let num_variables = self.data.ncols();
        self.data = Array::from_elem((MAX_ARRAY_LEN, num_variables), 0.);
        self.last_index = 0;
    }

    /// Write the stored data in `data` limited by the index `range`to a file,
    ///  the first line is the content in `header`.
    fn write_to_file(
        &self,
        file_name: &str,
        range: (usize, usize),
        additional_data: Option<(String, ArrayView2<f64>)>,
    ) {
        let data: ArrayView2<f64>;
        let tmp: Array2<f64>;
        let mut additional_header = String::from("");
        let filtered_data = self.data.slice(s![range.0..range.1, ..]);
        if let Some((header, add)) = additional_data {
            if filtered_data.nrows() != add.nrows() {
                println!(
                    "`additional_data` must have the same number of rows as the writable data"
                );
                println!(
                    " `additional_data`: {}, writable data: {}",
                    add.len(),
                    filtered_data.len()
                );
                std::process::exit(1);
            }
            tmp = stack![Axis(1), add, filtered_data];
            data = tmp.view();
            additional_header = header.to_string();
        } else {
            data = filtered_data;
        }
        let num_cols = data.ncols() - 1;
        let data: Vec<String> = data
            .indexed_iter()
            .map(|((_, j), d)| -> String {
                if j < num_cols {
                    format!("{:.6}", d) + "\t"
                } else {
                    format!("{:.6}", d) + "\n"
                }
            })
            .collect();
        let mut file = std::fs::File::create(file_name).expect("Error opening writing file");
        write!(file, "{}{}\n", additional_header, self.header).expect("Unable to write data");
        write!(file, "{}", data.join("")).expect("Unable to write data");
    }
}

#[derive(Debug, Clone)]
pub struct ObjectInfo {
    pub name: String,
    pub obj_type: ObjectType,
    pub index: usize,
    pub stored_data: StoreData,
}

impl ObjectInfo {
    pub fn new(
        name: String,
        obj_type: ObjectType,
        index: usize,
        stored_data: StoreData,
    ) -> ObjectInfo {
        ObjectInfo {
            name,
            obj_type,
            index,
            stored_data,
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}