1use std::fmt;
4use std::rc::Rc;
5
6use ndarray::{Array, Array2, array, s};
7
8use crate::calc::{ColVec, integrate_simpson, is_close};
9use crate::func::{RealValuedFunction};
10use crate::ode::{explicit_rk4, explicit_rk4_fvp};
11use crate::opt::{OptimizeResult, MinimizerKind, MinimizerParam, minimize, minimize_brent};
12use crate::tep::{Tep};
13use crate::bc::{BoundaryCondition, OptVarConverter, FullOptVar};
14use crate::simul::{SimulationErrorKind, convert_opt_error_to_simul_error};
15use crate::interp::{PiecewiseCubicPolynomialConstExt};
16
17pub mod prelude;
18
19
20pub struct SimulationParam {
26 pub dx: f64,
28 pub dtemp: f64,
30 pub min_num_x_mesh: usize,
32 pub min_num_temp_mesh: usize,
34 pub max_num_x_mesh: usize,
36 pub max_num_temp_mesh: usize,
38 pub minimizer_kind: MinimizerKind,
40 pub minimizer_param: MinimizerParam,
42 pub rel_tol_temp: f64,
44 pub abs_tol_temp: f64,
46 pub rel_tol_heat_rate: f64,
48 pub abs_tol_heat_rate: f64,
50 pub max_iter_brent: usize,
52 pub abs_tol_elec_cur_brent: f64,
54}
55
56impl SimulationParam {
57 pub fn default() -> Self {
60 SimulationParam {
61 dx: 1e-4, dtemp: 10.0, min_num_x_mesh: 11,
64 min_num_temp_mesh: 11,
65 max_num_x_mesh: 101,
66 max_num_temp_mesh: 101,
67 minimizer_kind: MinimizerKind::Bfgs,
68 minimizer_param: MinimizerParam::default(MinimizerKind::Bfgs),
69 rel_tol_temp: 1e-4,
70 abs_tol_temp: 1e-2,
71 rel_tol_heat_rate: 1e-4,
72 abs_tol_heat_rate: 1e-4,
73 max_iter_brent: 40,
74 abs_tol_elec_cur_brent: 1e-6,
75 }
76 }
77}
78
79
80pub struct LegSimulationResult {
82 pub temp: Rc<dyn RealValuedFunction>,
84 pub heat_flux: Rc<dyn RealValuedFunction>,
86 pub elec_cur: f64,
88 pub voltage: f64,
90 pub internal_resistance: f64,
92 pub templ: f64,
94 pub tempr: f64,
96 pub heat_ratel: f64,
98 pub heat_rater: f64,
100 pub power: f64,
102 pub efficiency: f64,
104 pub success: bool,
106 pub error: Option<SimulationErrorKind>,
108 pub num_iter: usize,
110}
111
112impl fmt::Display for LegSimulationResult {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "LegSimulationResult {{ success: {}, elec_cur: {}, voltage: {}, internal_resistance: {}, templ: {}, tempr: {}, heat_ratel: {}, heat_rater: {}, power: {}, efficiency: {}, .. }}",
115 self.success, self.elec_cur, self.voltage, self.internal_resistance, self.templ, self.tempr, self.heat_ratel, self.heat_rater, self.power, self.efficiency)
116 }
117}
118
119impl LegSimulationResult {
120 #[inline]
122 pub fn total_resistance(&self) -> f64 {
123 self.voltage / self.elec_cur
124 }
125
126 #[inline]
128 pub fn load_resistance(&self) -> f64 {
129 self.voltage / self.elec_cur - self.internal_resistance
130 }
131
132 #[inline]
134 pub fn load_ratio(&self) -> f64 {
135 self.voltage / (self.elec_cur * self.internal_resistance) - 1.0
136 }
137}
138
139
140pub struct SingleLeg {
200 pub tep: Tep,
202 pub length: f64,
204 pub area: f64,
206 pub simulation_param: SimulationParam,
208 pub xs: ColVec,
210 xs_left: ColVec,
212 xs_right: ColVec,
214}
215
216impl SingleLeg {
217 pub fn new(tep: &Tep, length: f64, area: f64, simulation_param: SimulationParam) -> Self {
223 if length <= 0.0 { panic!("`length` must be a positive number!"); }
224 if area <= 0.0 { panic!("`area` must be a positive number!"); }
225
226 let num_x_mesh: usize = get_num_mesh(length, simulation_param.dx, simulation_param.min_num_x_mesh, simulation_param.max_num_x_mesh);
228 let xs: ColVec = Array::linspace(0.0, length, num_x_mesh);
229 let half_num_x_mesh: usize = (num_x_mesh+1)/2; let xs_left: ColVec = xs.slice(s![0..half_num_x_mesh]).to_owned();
231 let xs_right: ColVec = xs.slice(s![half_num_x_mesh-1..num_x_mesh]).to_owned();
232
233 SingleLeg { tep: tep.clone(), length, area, simulation_param, xs, xs_left, xs_right }
235 }
236
237 pub fn simulate_fixed_elec_cur(&self, fixed_elec_cur: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
241 let fixed_elec_cur_density: f64 = fixed_elec_cur / self.area;
242 let heat_rate_hint: f64 = self.estimate_heat_flux(templ_hint, tempr_hint, fixed_elec_cur_density) * self.area;
243 let init_var: FullOptVar = FullOptVar { templ: templ_hint, heat_ratel: heat_rate_hint, tempr: tempr_hint, heat_rater: heat_rate_hint, elec_cur: fixed_elec_cur };
244
245 self.simulate_fixed_elec_cur_with_manual_init(fixed_elec_cur, left_bc, right_bc, &init_var)
246 }
247
248 pub fn simulate_fixed_elec_cur_with_manual_init(&self, fixed_elec_cur: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), init_var: &FullOptVar ) -> LegSimulationResult {
251 let converter: OptVarConverter = OptVarConverter::new(left_bc, right_bc, Some(fixed_elec_cur), self.area);
252 let n_half_cols: usize = self.xs_left.len();
253
254 let cost_f = |var: &ColVec| -> f64 {
255 let full_var: FullOptVar = converter.to_full_var(left_bc, right_bc, var);
256 let elec_cur_density: f64 = full_var.elec_cur / self.area;
257 let ivp_ys = self.solve_ivp(elec_cur_density, full_var.templ, full_var.heat_ratel/self.area, &self.xs_left);
258 let fvp_ys = self.solve_fvp(elec_cur_density, full_var.tempr, full_var.heat_rater/self.area, &self.xs_right);
259
260 let ivp_temp_mid = ivp_ys[[0, n_half_cols-1]];
261 let ivp_heat_rate_mid = ivp_ys[[1, n_half_cols-1]] * self.area;
262 let fvp_temp_mid = fvp_ys[[0, 0]];
263 let fvp_heat_rate_mid = fvp_ys[[1, 0]] * self.area;
264
265 let mut cost = (ivp_temp_mid - fvp_temp_mid).powi(2) + (ivp_heat_rate_mid - fvp_heat_rate_mid).powi(2);
266 cost *= 0.5;
267
268 cost
269 };
270
271 self.minimize_cost_by_shooting(&cost_f, left_bc, right_bc, &converter, init_var)
272 }
273
274 pub fn simulate_fixed_load_resistance(&self, fixed_load_resistance: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), elec_cur_hint: f64, templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
278 let elec_cur_density_hint: f64 = elec_cur_hint / self.area;
279 let heat_rate_hint: f64 = self.estimate_heat_flux(templ_hint, tempr_hint, elec_cur_density_hint) * self.area;
280 let init_var: FullOptVar = FullOptVar { templ: templ_hint, heat_ratel: heat_rate_hint, tempr: tempr_hint, heat_rater: heat_rate_hint, elec_cur: elec_cur_hint };
281
282 self.simulate_fixed_load_resistance_with_manual_init(fixed_load_resistance, left_bc, right_bc, &init_var)
283 }
284
285 pub fn simulate_fixed_load_resistance_with_manual_init(&self, fixed_load_resistance: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), init_var: &FullOptVar) -> LegSimulationResult {
288 let converter: OptVarConverter = OptVarConverter::new(left_bc, right_bc, None, self.area);
289 let n_half_cols: usize = self.xs_left.len();
290 let n_cols: usize = self.xs.len();
291
292 let cost_f = |var: &ColVec| -> f64 {
293 let full_var = converter.to_full_var(left_bc, right_bc, var);
294 let elec_cur_density: f64 = full_var.elec_cur / self.area;
295 let ivp_ys = self.solve_ivp(elec_cur_density, full_var.templ, full_var.heat_ratel / self.area, &self.xs);
296 let fvp_ys = self.solve_fvp(elec_cur_density, full_var.tempr, full_var.heat_rater / self.area, &self.xs);
297 let ivp_temp_mid = ivp_ys[[0, n_half_cols-1]];
298 let ivp_heat_rate_mid = ivp_ys[[1, n_half_cols-1]] * self.area;
299 let fvp_temp_mid = fvp_ys[[0, n_half_cols-1]];
300 let fvp_heat_rate_mid = fvp_ys[[1, n_half_cols-1]] * self.area;
301 let ys = (1.0-&self.xs/self.length) * &ivp_ys + &self.xs/self.length * &fvp_ys; let voltage = self.compute_voltage(ys[[0, 0]], ys[[0, n_cols-1]]);
304 let internal_resistance = self.compute_internal_resistance(&ys.row(0).to_owned());
305 let elec_cur = full_var.elec_cur;
306
307 let mut cost = (ivp_temp_mid - fvp_temp_mid).powi(2) + (ivp_heat_rate_mid - fvp_heat_rate_mid).powi(2);
308 cost *= 0.5;
309 cost += (voltage - elec_cur*(internal_resistance + fixed_load_resistance)).powi(2) * 1e3;
310
311 cost
312 };
313
314 self.minimize_cost_by_shooting(&cost_f, left_bc, right_bc, &converter, init_var)
315 }
316
317 pub fn simulate_fixed_load_ratio(&self, fixed_load_ratio: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), elec_cur_hint: f64, templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
321 let elec_cur_density_hint: f64 = elec_cur_hint / self.area;
322 let heat_rate_hint: f64 = self.estimate_heat_flux(templ_hint, tempr_hint, elec_cur_density_hint) * self.area;
323 let init_var: FullOptVar = FullOptVar { templ: templ_hint, heat_ratel: heat_rate_hint, tempr: tempr_hint, heat_rater: heat_rate_hint, elec_cur: elec_cur_hint };
324
325 self.simulate_fixed_load_ratio_with_manual_init(fixed_load_ratio, left_bc, right_bc, &init_var)
326 }
327
328 pub fn simulate_fixed_load_ratio_with_manual_init(&self, fixed_load_ratio: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), init_var: &FullOptVar) -> LegSimulationResult {
335 if fixed_load_ratio.is_infinite() {
336 panic!("`fixed_load_ratio` must be a finite value!");
337 }
338 if fixed_load_ratio < 0.0 {
339 panic!("`fixed_load_ratio` must be a nonngeative value!");
340 }
341
342 let converter: OptVarConverter = OptVarConverter::new(left_bc, right_bc, None, self.area);
343 let n_half_cols: usize = self.xs_left.len();
344 let n_cols: usize = self.xs.len();
345
346 let cost_f = |var: &ColVec| -> f64 {
347 let full_var = converter.to_full_var(left_bc, right_bc, var);
348 let elec_cur_density: f64 = full_var.elec_cur / self.area;
349 let ivp_ys = self.solve_ivp(elec_cur_density, full_var.templ, full_var.heat_ratel / self.area, &self.xs);
350 let fvp_ys = self.solve_fvp(elec_cur_density, full_var.tempr, full_var.heat_rater / self.area, &self.xs);
351 let ivp_temp_mid = ivp_ys[[0, n_half_cols-1]];
352 let ivp_heat_rate_mid = ivp_ys[[1, n_half_cols-1]] * self.area;
353 let fvp_temp_mid = fvp_ys[[0, n_half_cols-1]];
354 let fvp_heat_rate_mid = fvp_ys[[1, n_half_cols-1]] * self.area;
355 let ys = (1.0-&self.xs/self.length) * &ivp_ys + &self.xs/self.length * &fvp_ys; let voltage = self.compute_voltage(ys[[0, 0]], ys[[0, n_cols-1]]);
358 let internal_resistance = self.compute_internal_resistance(&ys.row(0).to_owned());
359 let elec_cur = full_var.elec_cur;
360
361 let mut cost = (ivp_temp_mid - fvp_temp_mid).powi(2) + (ivp_heat_rate_mid - fvp_heat_rate_mid).powi(2);
362 cost *= 0.5;
363 cost += (voltage - elec_cur*(1.0 + fixed_load_ratio)*internal_resistance).powi(2) * 1e3;
364
365 cost
368 };
369
370 self.minimize_cost_by_shooting(&cost_f, left_bc, right_bc, &converter, init_var)
371 }
372
373 fn minimize_cost_by_shooting(&self, cost_f: &(dyn Fn(&ColVec) -> f64), left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), converter: &OptVarConverter, init_var: &FullOptVar) -> LegSimulationResult {
376 let var0: ColVec = converter.to_opt_var(init_var);
377 let n_half_cols: usize = self.xs_left.len();
378 let optimize_result: OptimizeResult = minimize(&cost_f, &var0, self.simulation_param.minimizer_kind, &self.simulation_param.minimizer_param);
379 let full_var: FullOptVar = converter.to_full_var(left_bc, right_bc, &optimize_result.x);
380
381 let mut success: bool = true;
383 let mut error: Option<SimulationErrorKind> = None;
384 let elec_cur_density: f64 = full_var.elec_cur / self.area;
386 let ivp_ys = self.solve_ivp(elec_cur_density, full_var.templ, full_var.heat_ratel / self.area, &self.xs);
387 let fvp_ys = self.solve_fvp(elec_cur_density, full_var.tempr, full_var.heat_rater / self.area, &self.xs);
388 let ys = (1.0-&self.xs/self.length) * &ivp_ys + &self.xs/self.length * &fvp_ys; if !(is_close(ivp_ys[[0, n_half_cols]], fvp_ys[[0, n_half_cols]], self.simulation_param.rel_tol_temp, self.simulation_param.abs_tol_temp))
391 || !(is_close(ivp_ys[[1, n_half_cols]]/self.area, fvp_ys[[1, n_half_cols]]/self.area, self.simulation_param.rel_tol_heat_rate, self.simulation_param.abs_tol_heat_rate)) {
392 success = false;
393 error = Some(SimulationErrorKind::BcNotMatchError);
394 }
395 if !(optimize_result.success) {
397 success = false;
398 error = Some(convert_opt_error_to_simul_error(optimize_result.error.unwrap()));
399 }
400
401 let temps: ColVec = ys.row(0).to_owned();
402 let heat_fluxs: ColVec = ys.row(1).to_owned();
403 let heat_fluxl: f64 = full_var.heat_ratel/self.area;
404 let heat_fluxr: f64 = full_var.heat_rater/self.area;
405
406 LegSimulationResult {
407 temp: Rc::new(PiecewiseCubicPolynomialConstExt::new(&self.xs, &temps, full_var.templ, full_var.tempr)),
408 heat_flux: Rc::new(PiecewiseCubicPolynomialConstExt::new(&self.xs, &heat_fluxs, heat_fluxl, heat_fluxr)),
409 elec_cur: full_var.elec_cur,
410 voltage: self.compute_voltage(full_var.templ, full_var.tempr),
411 internal_resistance: self.compute_internal_resistance(&temps),
412 templ: full_var.templ,
413 tempr: full_var.tempr,
414 heat_ratel: full_var.heat_ratel,
415 heat_rater: full_var.heat_rater,
416 power: self.get_power(heat_fluxl, heat_fluxr),
417 efficiency: get_efficiency(heat_fluxl, heat_fluxr),
418 success,
419 error,
420 num_iter: optimize_result.num_iter,
421 }
422 }
423
424 pub fn simulate_fixed_load_ratio_in_range(&self, fixed_load_ratio: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), min_elec_cur: f64, max_elec_cur: f64, templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
428 let elec_cur_hint: f64 = (min_elec_cur + max_elec_cur) * 0.5;
429 let elec_cur_density_hint: f64 = elec_cur_hint / self.area;
430 let heat_rate_hint: f64 = self.estimate_heat_flux(templ_hint, tempr_hint, elec_cur_density_hint) * self.area;
431 let init_var: FullOptVar = FullOptVar { templ: templ_hint, heat_ratel: heat_rate_hint, tempr: tempr_hint, heat_rater: heat_rate_hint, elec_cur: elec_cur_hint };
432
433 self.simulate_fixed_load_ratio_in_range_with_manual_init(fixed_load_ratio, left_bc, right_bc, min_elec_cur, max_elec_cur, &init_var)
434 }
435
436 pub fn simulate_fixed_load_ratio_in_range_with_manual_init(&self, fixed_load_ratio: f64, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), min_elec_cur: f64, max_elec_cur: f64, init_var: &FullOptVar) -> LegSimulationResult {
440 let cost_f = |elec_cur: f64| -> f64 {
441 let result_bc = self.simulate_fixed_elec_cur_with_manual_init(elec_cur, left_bc, right_bc, init_var);
442
443 let cost: f64 = (result_bc.load_ratio() - fixed_load_ratio).powi(2);
444
445 cost
446 };
447 let optimize_result: OptimizeResult = minimize_brent(&cost_f, min_elec_cur, max_elec_cur, self.simulation_param.max_iter_brent, self.simulation_param.abs_tol_elec_cur_brent);
448 let elec_cur = optimize_result.x[0];
449 let result_bc = self.simulate_fixed_elec_cur_with_manual_init(elec_cur, left_bc, right_bc, init_var);
450
451 result_bc
452 }
453
454 pub fn maximize_power_auto_range(&self, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
458 let elec_cur_hint: f64 = self.estimate_elec_cur_for_max_power(templ_hint, tempr_hint);
459 let min_elec_cur: f64;
460 let max_elec_cur: f64;
461 if elec_cur_hint > 0.0 {
462 min_elec_cur = elec_cur_hint * 0.5;
463 max_elec_cur = elec_cur_hint * 1.5;
464 } else {
465 min_elec_cur = elec_cur_hint * 1.5;
466 max_elec_cur = elec_cur_hint * 0.5;
467 }
468
469 self.maximize_power_in_range(left_bc, right_bc, min_elec_cur, max_elec_cur, templ_hint, tempr_hint)
470 }
471
472 pub fn maximize_power_in_range(&self, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), min_elec_cur: f64, max_elec_cur: f64, templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
476 let elec_cur_hint: f64 = (min_elec_cur + max_elec_cur) * 0.5;
477 let elec_cur_density_hint: f64 = elec_cur_hint / self.area;
478 let heat_rate_hint: f64 = self.estimate_heat_flux(templ_hint, tempr_hint, elec_cur_density_hint) * self.area;
479 let init_var: FullOptVar = FullOptVar { templ: templ_hint, heat_ratel: heat_rate_hint, tempr: tempr_hint, heat_rater: heat_rate_hint, elec_cur: elec_cur_hint };
480
481 self.maximize_power_in_range_with_manual_init(left_bc, right_bc, min_elec_cur, max_elec_cur, &init_var)
482 }
483
484 pub fn maximize_power_in_range_with_manual_init(&self, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), min_elec_cur: f64, max_elec_cur: f64, init_var: &FullOptVar) -> LegSimulationResult {
488 let cost_f = |elec_cur: f64| -> f64 {
489 let result_bc = self.simulate_fixed_elec_cur_with_manual_init(elec_cur, left_bc, right_bc, init_var);
490
491 let cost: f64 = -result_bc.power;
492
493 cost
494 };
495 let optimize_result: OptimizeResult = minimize_brent(&cost_f, min_elec_cur, max_elec_cur, self.simulation_param.max_iter_brent, self.simulation_param.abs_tol_elec_cur_brent);
496 let elec_cur = optimize_result.x[0];
497 let result_bc = self.simulate_fixed_elec_cur_with_manual_init(elec_cur, left_bc, right_bc, init_var);
498
499 result_bc
500 }
501
502 pub fn maximize_efficiency_auto_range(&self, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
506 let elec_cur_hint: f64 = self.estimate_elec_cur_for_max_efficiency(templ_hint, tempr_hint);
507 let min_elec_cur: f64;
508 let max_elec_cur: f64;
509 if elec_cur_hint > 0.0 {
510 min_elec_cur = elec_cur_hint * 0.5;
511 max_elec_cur = elec_cur_hint * 1.5;
512 } else {
513 min_elec_cur = elec_cur_hint * 1.5;
514 max_elec_cur = elec_cur_hint * 0.5;
515 }
516
517 self.maximize_efficiency_in_range(left_bc, right_bc, min_elec_cur, max_elec_cur, templ_hint, tempr_hint)
518 }
519
520 pub fn maximize_efficiency_in_range(&self, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), min_elec_cur: f64, max_elec_cur: f64, templ_hint: f64, tempr_hint: f64) -> LegSimulationResult {
524 let elec_cur_hint: f64 = (min_elec_cur + max_elec_cur) * 0.5;
525 let elec_cur_density_hint: f64 = elec_cur_hint / self.area;
526 let heat_rate_hint: f64 = self.estimate_heat_flux(templ_hint, tempr_hint, elec_cur_density_hint) * self.area;
527 let init_var: FullOptVar = FullOptVar { templ: templ_hint, heat_ratel: heat_rate_hint, tempr: tempr_hint, heat_rater: heat_rate_hint, elec_cur: elec_cur_hint };
528
529 self.maximize_efficiency_in_range_with_manual_init(left_bc, right_bc, min_elec_cur, max_elec_cur, &init_var)
530 }
531
532 pub fn maximize_efficiency_in_range_with_manual_init(&self, left_bc: &(dyn BoundaryCondition), right_bc: &(dyn BoundaryCondition), min_elec_cur: f64, max_elec_cur: f64, init_var: &FullOptVar) -> LegSimulationResult {
536 let cost_f = |elec_cur: f64| -> f64 {
537 let result_bc = self.simulate_fixed_elec_cur_with_manual_init(elec_cur, left_bc, right_bc, init_var);
538
539 let cost: f64 = -result_bc.efficiency;
540
541 cost
542 };
543 let optimize_result: OptimizeResult = minimize_brent(&cost_f, min_elec_cur, max_elec_cur, self.simulation_param.max_iter_brent, self.simulation_param.abs_tol_elec_cur_brent);
544 let elec_cur = optimize_result.x[0];
545 let result_bc = self.simulate_fixed_elec_cur_with_manual_init(elec_cur, left_bc, right_bc, init_var);
546
547 result_bc
548 }
549
550 fn solve_ivp(&self, elec_cur_density: f64, templ: f64, heat_fluxl: f64, xs: &ColVec) -> Array2<f64> {
553 let f = |_x: f64, y: &ColVec| -> ColVec {
554 teg_rhs(&self.tep, y[0], y[1], elec_cur_density)
555 };
556
557 explicit_rk4(&f, xs, &array![templ, heat_fluxl])
558 }
559
560 fn solve_fvp(&self, elec_cur_density: f64, tempr: f64, heat_fluxr: f64, xs: &ColVec) -> Array2<f64> {
563 let f = |_x: f64, y: &ColVec| -> ColVec {
564 teg_rhs(&self.tep, y[0], y[1], elec_cur_density)
565 };
566
567 explicit_rk4_fvp(&f, xs, &array![tempr, heat_fluxr])
568 }
569
570 fn compute_voltage(&self, templ: f64, tempr: f64) -> f64 {
572 let temp_diff = templ - tempr;
573 if temp_diff == 0.0 {
574 return 0.0;
575 }
576
577 let num_temp_mesh: usize = get_num_mesh(temp_diff.abs(), self.simulation_param.dtemp, self.simulation_param.min_num_temp_mesh, self.simulation_param.max_num_temp_mesh);
578 let temp_mesh: ColVec;
579 let h: f64;
580
581 if tempr < templ {
582 temp_mesh = Array::linspace(tempr, templ, num_temp_mesh);
583 h = temp_mesh[1] - temp_mesh[0];
584 } else {
585 temp_mesh = Array::linspace(templ, tempr, num_temp_mesh);
586 h = temp_mesh[0] - temp_mesh[1]; }
588
589 integrate_simpson(&self.tep.seebeck_ats_incr(&temp_mesh), h)
590 }
591
592 fn compute_internal_resistance(&self, temps: &ColVec) -> f64 {
595 let rho_of_x: ColVec = self.tep.elec_resi_ats(temps);
596 let h = self.xs[1] - self.xs[0];
597
598 integrate_simpson(&rho_of_x, h) / self.area
599 }
600
601 #[inline]
603 fn get_power(&self, heat_fluxl: f64, heat_fluxr: f64) -> f64 {
604 (heat_fluxl - heat_fluxr) * self.area
605 }
606
607 pub fn estimate_heat_flux(&self, templ: f64, tempr: f64, elec_cur_density: f64) -> f64 {
609 let alpha = self.tep.seebeck_at(templ);
610 let kappa = self.tep.thrm_cond_at(templ);
611
612 alpha*templ*elec_cur_density - kappa*(tempr-templ)/self.length
613 }
614
615 pub fn estimate_max_efficiency(&self, templ: f64, tempr: f64) -> f64 {
619 let hot_temp: f64;
620 let cold_temp: f64;
621
622 if templ < tempr {
623 cold_temp = templ;
624 hot_temp = tempr;
625 } else {
626 cold_temp = tempr;
627 hot_temp = templ;
628 }
629
630 let delta_temp: f64 = hot_temp - cold_temp;
631 let num_temp_mesh: usize = get_num_mesh(delta_temp, self.simulation_param.dtemp, self.simulation_param.min_num_temp_mesh, self.simulation_param.max_num_temp_mesh);
632 let temp_mesh: ColVec = Array::linspace(cold_temp, hot_temp, num_temp_mesh);
633 let h: f64 = temp_mesh[1] - temp_mesh[0];
634
635 let alpha_bar: f64 = integrate_simpson(&self.tep.seebeck_ats_incr(&temp_mesh), h) / delta_temp;
636 let rho_kappa_bar: f64 = integrate_simpson(&(self.tep.elec_resi_ats_incr(&temp_mesh)*self.tep.thrm_cond_ats_incr(&temp_mesh)), h) / delta_temp;
637
638 let z_gen: f64 = alpha_bar.powi(2) / rho_kappa_bar;
640 let tau: f64 = (-1.0/6.0) * (self.tep.seebeck_at(hot_temp) - self.tep.seebeck_at(cold_temp)) / alpha_bar;
641 let beta: f64 = (1.0/6.0) * (self.tep.elec_resi_at(hot_temp)*self.tep.thrm_cond_at(hot_temp) - self.tep.elec_resi_at(cold_temp)*self.tep.thrm_cond_at(cold_temp)) / rho_kappa_bar;
642
643 let temp_hp: f64 = hot_temp - tau * delta_temp;
645 let temp_cp: f64 = cold_temp - (tau + beta)*delta_temp;
646 let temp_midp: f64 = (temp_hp+temp_cp)*0.5;
647
648 let approx_max_eff = (delta_temp/temp_hp) * ((1.0+z_gen*temp_midp).sqrt()-1.0) / ((1.0+z_gen*temp_midp).sqrt()+(temp_cp/temp_hp));
650
651 approx_max_eff
652 }
653
654 pub fn estimate_max_efficiency_by_z(&self, templ: f64, tempr: f64) -> f64 {
658 let hot_temp: f64;
659 let cold_temp: f64;
660
661 if templ < tempr {
662 cold_temp = templ;
663 hot_temp = tempr;
664 } else {
665 cold_temp = tempr;
666 hot_temp = templ;
667 }
668
669 let delta_temp: f64 = hot_temp - cold_temp;
670 let num_temp_mesh: usize = get_num_mesh(delta_temp, self.simulation_param.dtemp, self.simulation_param.min_num_temp_mesh, self.simulation_param.max_num_temp_mesh);
671 let temp_mesh: ColVec = Array::linspace(cold_temp, hot_temp, num_temp_mesh);
672 let h: f64 = temp_mesh[1] - temp_mesh[0];
673
674 let alpha_bar: f64 = integrate_simpson(&self.tep.seebeck_ats_incr(&temp_mesh), h) / delta_temp;
675 let rho_kappa_bar: f64 = integrate_simpson(&(self.tep.elec_resi_ats_incr(&temp_mesh)*self.tep.thrm_cond_ats_incr(&temp_mesh)), h) / delta_temp;
676
677 let z_gen: f64 = alpha_bar.powi(2) / rho_kappa_bar;
679
680 let mid_temp: f64 = (hot_temp + cold_temp) * 0.5;
682
683 let approx_max_eff = (delta_temp/hot_temp) * ((1.0+z_gen*mid_temp).sqrt()-1.0) / ((1.0+z_gen*mid_temp).sqrt()+(cold_temp/hot_temp));
685
686 approx_max_eff
687 }
688
689 pub fn estimate_max_power(&self, templ: f64, tempr: f64) -> f64 {
694 let hot_temp: f64;
695 let cold_temp: f64;
696
697 if templ < tempr {
698 cold_temp = templ;
699 hot_temp = tempr;
700 } else {
701 cold_temp = tempr;
702 hot_temp = templ;
703 }
704
705 let delta_temp: f64 = hot_temp - cold_temp;
706 let num_temp_mesh: usize = get_num_mesh(delta_temp, self.simulation_param.dtemp, self.simulation_param.min_num_temp_mesh, self.simulation_param.max_num_temp_mesh);
707 let temp_mesh: ColVec = Array::linspace(cold_temp, hot_temp, num_temp_mesh);
708 let h: f64 = temp_mesh[1] - temp_mesh[0];
709
710 let alpha_bar: f64 = integrate_simpson(&self.tep.seebeck_ats_incr(&temp_mesh), h) / delta_temp;
711 let rho_kappa_bar: f64 = integrate_simpson(&(self.tep.elec_resi_ats_incr(&temp_mesh)*self.tep.thrm_cond_ats_incr(&temp_mesh)), h) / delta_temp;
712 let kappa_bar: f64 = integrate_simpson(&self.tep.thrm_cond_ats_incr(&temp_mesh), h) / delta_temp;
713
714 let alpha0: f64 = alpha_bar;
716 let rho0: f64 = rho_kappa_bar / kappa_bar;
717
718 let voltage: f64 = alpha0 * delta_temp;
720 let internal_resistance: f64 = rho0 * self.length / self.area;
721
722 let approx_max_power = voltage.powi(2) / (4.0 * internal_resistance);
724
725 approx_max_power
726 }
727
728 pub fn estimate_elec_cur_for_max_power(&self, templ: f64, tempr: f64) -> f64 {
733 let hot_temp: f64;
734 let cold_temp: f64;
735
736 if templ < tempr {
737 cold_temp = templ;
738 hot_temp = tempr;
739 } else {
740 cold_temp = tempr;
741 hot_temp = templ;
742 }
743
744 let delta_temp: f64 = hot_temp - cold_temp;
745 let num_temp_mesh: usize = get_num_mesh(delta_temp, self.simulation_param.dtemp, self.simulation_param.min_num_temp_mesh, self.simulation_param.max_num_temp_mesh);
746 let temp_mesh: ColVec = Array::linspace(cold_temp, hot_temp, num_temp_mesh);
747 let h: f64 = temp_mesh[1] - temp_mesh[0];
748
749 let alpha_bar: f64 = integrate_simpson(&self.tep.seebeck_ats_incr(&temp_mesh), h) / delta_temp;
750 let rho_kappa_bar: f64 = integrate_simpson(&(self.tep.elec_resi_ats_incr(&temp_mesh)*self.tep.thrm_cond_ats_incr(&temp_mesh)), h) / delta_temp;
751 let kappa_bar: f64 = integrate_simpson(&self.tep.thrm_cond_ats_incr(&temp_mesh), h) / delta_temp;
752
753 let alpha0: f64 = alpha_bar;
755 let rho0: f64 = rho_kappa_bar / kappa_bar;
756
757 let voltage: f64 = alpha0 * delta_temp;
759 let internal_resistance: f64 = rho0 * self.length / self.area;
760
761 let load_ratio = 1.0;
763 let approx_elec_cur_for_max_power = voltage / ((1.0 + load_ratio) * internal_resistance);
764
765 approx_elec_cur_for_max_power
766 }
767
768 pub fn estimate_elec_cur_for_max_efficiency(&self, templ: f64, tempr: f64) -> f64 {
773 let hot_temp: f64;
774 let cold_temp: f64;
775
776 if templ < tempr {
777 cold_temp = templ;
778 hot_temp = tempr;
779 } else {
780 cold_temp = tempr;
781 hot_temp = templ;
782 }
783
784 let delta_temp: f64 = hot_temp - cold_temp;
785 let mid_temp: f64 = (hot_temp + cold_temp) * 0.5;
786 let num_temp_mesh: usize = get_num_mesh(delta_temp, self.simulation_param.dtemp, self.simulation_param.min_num_temp_mesh, self.simulation_param.max_num_temp_mesh);
787 let temp_mesh: ColVec = Array::linspace(cold_temp, hot_temp, num_temp_mesh);
788 let h: f64 = temp_mesh[1] - temp_mesh[0];
789
790 let alpha_bar: f64 = integrate_simpson(&self.tep.seebeck_ats_incr(&temp_mesh), h) / delta_temp;
791 let rho_kappa_bar: f64 = integrate_simpson(&(self.tep.elec_resi_ats_incr(&temp_mesh)*self.tep.thrm_cond_ats_incr(&temp_mesh)), h) / delta_temp;
792 let kappa_bar: f64 = integrate_simpson(&self.tep.thrm_cond_ats_incr(&temp_mesh), h) / delta_temp;
793
794 let alpha0: f64 = alpha_bar;
796 let rho0: f64 = rho_kappa_bar / kappa_bar;
797
798 let voltage: f64 = alpha0 * delta_temp;
800 let internal_resistance: f64 = rho0 * self.length / self.area;
801
802 let z_gen: f64 = alpha_bar.powi(2) / rho_kappa_bar;
804
805 let load_ratio = (1.0 + z_gen*mid_temp).sqrt();
807 let approx_elec_cur_for_max_efficiency = voltage / ((1.0+load_ratio)*internal_resistance);
808
809 approx_elec_cur_for_max_efficiency
810 }
811}
812
813
814fn teg_rhs(tep: &Tep, temp: f64, heat_flux: f64, elec_cur_density: f64) -> ColVec {
824 let alpha = tep.seebeck_at(temp);
825 let rho = tep.elec_resi_at(temp);
826 let kappa = tep.thrm_cond_at(temp);
827 let q = heat_flux;
828 let j = elec_cur_density;
829
830 let dtempdx = (alpha*temp*j - q)/kappa;
831 let dqdx = (alpha*dtempdx + rho*j) * j;
832
833 array![dtempdx, dqdx]
834}
835
836
837fn get_num_mesh(total_size: f64, suggested_mesh_size: f64, min_num_mesh: usize, max_num_mesh: usize) -> usize {
847 if total_size <= 0.0 {
848 panic!("`total_size` must be a positive number!");
849 }
850 if suggested_mesh_size <= 0.0 {
851 panic!("`suggested_mesh_size` must be a positive number!");
852 }
853 let mut num_mesh: usize = (total_size / suggested_mesh_size).ceil() as usize + 1;
854 if num_mesh > max_num_mesh {
855 num_mesh = max_num_mesh - 1;
856 }
857 if num_mesh < min_num_mesh {
858 num_mesh = min_num_mesh;
859 }
860 if num_mesh < 4 {
861 num_mesh = 4;
862 }
863 if num_mesh > max_num_mesh {
864 num_mesh = max_num_mesh - 1;
865 }
866 if num_mesh & 1 == 0 {
868 num_mesh += 1;
869 }
870
871 num_mesh
872}
873
874#[inline]
876fn get_efficiency(heat_fluxl: f64, heat_fluxr: f64) -> f64 {
877 1.0 - heat_fluxr/heat_fluxl
878}
879
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use std::time::Instant;
885
886 use crate::calc::{all_close, is_close, max};
887 use crate::func::{ConstantFunction, ExplicitFunction};
888 use crate::interp::PiecewiseLinearConstExt;
889 use crate::bc::{FixedTempBc, FixedHeatFluxBc, ConvectionBc};
890
891 #[test]
893 #[ignore]
894 fn test_estimate_max_power_for_linear_thrm_cond() {
895 let hot_temp: f64 = 500.0;
897 let cold_temp: f64 = 300.0;
898
899 let alpha0: f64 = 150e-6; let rho0: f64 = 1e-5; let kappa0: f64 = 2.0; let temp0: f64 = 300.0; let seebeck = ConstantFunction::new(alpha0);
905 let elec_resi = ConstantFunction::new(rho0);
906 let thrm_cond = ExplicitFunction::new(Rc::new(move |temp: f64| { max(kappa0*temp/temp0, kappa0*cold_temp/temp0) })); let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
908 let length = 1e-3; let area = 1e-6; let minimizer_kind = MinimizerKind::Bfgs;
913 let minimizer_param = MinimizerParam {
914 grad_tol: 1e-5,
915 max_iter: 100,
916 ..MinimizerParam::default(minimizer_kind)
917 };
918 let simul_param = SimulationParam {
919 dx: length/10.0,
920 abs_tol_elec_cur_brent: 1e-10,
921 minimizer_kind,
922 minimizer_param,
923 ..SimulationParam::default()
924 };
925
926 let leg = SingleLeg::new(&tep, length, area, simul_param);
928
929 let now = Instant::now();
931 let simul_result = leg.maximize_power_auto_range(&FixedTempBc::new(500.0), &FixedTempBc::new(300.0), hot_temp, cold_temp);
932 println!("test_estimate_max_power_for_linear_thrm_cond: Elapsed {:.2?}", now.elapsed());
933 if !simul_result.success {
934 println!("error: {:?}", simul_result.error);
935 }
936 assert!(simul_result.success);
937 let approx_power: f64 = leg.estimate_max_power(hot_temp, cold_temp);
938 let approx_elec_cur: f64 = leg.estimate_elec_cur_for_max_power(hot_temp, cold_temp);
939 println!("estimated power={}, numerical power={}", approx_power, simul_result.power);
940 assert!(is_close(simul_result.power, approx_power, 5e-06, 0.0)); println!("estimated elec_cur={}, numerical elec_cur={}", approx_elec_cur, simul_result.elec_cur);
942 assert!(is_close(approx_elec_cur, simul_result.elec_cur, 5e-06, 0.0)); }
944
945 #[test]
947 #[ignore]
948 fn test_estimate_max_efficiency_for_linear_thrm_cond() {
949 let hot_temp: f64 = 500.0;
951 let cold_temp: f64 = 300.0;
952
953 let alpha0: f64 = 150e-6; let rho0: f64 = 1e-5; let kappa0: f64 = 2.0; let temp0: f64 = 300.0; let seebeck = ConstantFunction::new(alpha0);
959 let elec_resi = ConstantFunction::new(rho0);
960 let thrm_cond = ExplicitFunction::new(Rc::new(move |temp: f64| { max(kappa0*temp/temp0, kappa0*cold_temp/temp0) })); let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
962 let length = 1e-3; let area = 1e-6; let minimizer_kind = MinimizerKind::Bfgs;
967 let minimizer_param = MinimizerParam {
968 grad_tol: 1e-5,
969 max_iter: 100,
970 ..MinimizerParam::default(minimizer_kind)
971 };
972 let simul_param = SimulationParam {
973 dx: length/10.0,
974 abs_tol_elec_cur_brent: 1e-6,
975 minimizer_kind,
976 minimizer_param,
977 ..SimulationParam::default()
978 };
979
980 let leg = SingleLeg::new(&tep, length, area, simul_param);
982
983 let now = Instant::now();
985 let simul_result = leg.maximize_efficiency_auto_range(&FixedTempBc::new(500.0), &FixedTempBc::new(300.0), hot_temp, cold_temp);
986 println!("test_estimate_max_efficiency_for_linear_thrm_cond: Elapsed {:.2?}", now.elapsed());
987 if !simul_result.success {
988 println!("error: {:?}", simul_result.error);
989 }
990 assert!(simul_result.success);
991 let approx_eff: f64 = leg.estimate_max_efficiency(hot_temp, cold_temp);
992 let approx_eff_by_z: f64 = leg.estimate_max_efficiency_by_z(hot_temp, cold_temp);
993 let approx_elec_cur: f64 = leg.estimate_elec_cur_for_max_efficiency(hot_temp, cold_temp);
994 println!("estimated eff by Ztb={}, estimated eff by CSM={}, numerical eff={}", approx_eff, approx_eff_by_z, simul_result.efficiency);
995 assert!(is_close(simul_result.efficiency, approx_eff, 2e-03, 0.0)); assert!(is_close(simul_result.efficiency, approx_eff_by_z, 5e-06, 0.0)); println!("estimated elec_cur={}, numerical elec_cur={}", approx_elec_cur, simul_result.elec_cur);
998 assert!(is_close(approx_elec_cur, simul_result.elec_cur, 5e-05, 0.0)); }
1000
1001 #[test]
1004 #[ignore]
1005 fn test_maximize_power_for_cpm() {
1006 let hot_temp: f64 = 500.0;
1008 let cold_temp: f64 = 300.0;
1009 let delta_temp = hot_temp - cold_temp;
1010
1011 let alpha0: f64 = 150e-6;
1013 let rho0: f64 = 1e-5;
1014 let kappa0: f64 = 2.0;
1015 let seebeck = ConstantFunction::new(alpha0);
1016 let elec_resi = ConstantFunction::new(rho0);
1017 let thrm_cond = ConstantFunction::new(kappa0);
1018 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1019 let length = 1e-3;
1020 let area = 1e-6;
1021
1022 let minimizer_kind = MinimizerKind::Bfgs;
1024 let minimizer_param = MinimizerParam {
1025 grad_tol: 1e-5,
1026 a_max: 2.0,
1027 max_iter: 200,
1028 ..MinimizerParam::default(minimizer_kind)
1029 };
1030 let simul_param = SimulationParam {
1031 dx: length/10.0,
1032 dtemp: (hot_temp-cold_temp)/10.0,
1033 abs_tol_elec_cur_brent: 1e-6,
1034 minimizer_kind,
1035 minimizer_param,
1036 ..SimulationParam::default()
1037 };
1038
1039 let leg = SingleLeg::new(&tep, length, area, simul_param);
1041 let left_bc = FixedTempBc::new(hot_temp);
1042 let right_bc = FixedTempBc::new(cold_temp);
1043
1044 let internal_resistance = rho0*length/area;
1046 let voltage = alpha0*delta_temp;
1047 let max_power = voltage.powi(2)/(4.0 * internal_resistance);
1048 let estimated_elec_cur = voltage/(2.0 * internal_resistance);
1049 println!("estimated_elec_cur={}", estimated_elec_cur);
1050
1051 let now = Instant::now();
1052 let result_fixed_temp = leg.maximize_power_auto_range(&left_bc, &right_bc, hot_temp, cold_temp); println!("test_maximize_power_for_cpm(): elapsed: {:?}", now.elapsed());
1055 assert!(result_fixed_temp.success);
1056
1057 println!("{}", result_fixed_temp);
1058 println!("max_power={}, internal_resistance={}", max_power, internal_resistance);
1059
1060 assert!(is_close(result_fixed_temp.power, max_power, 1e-04, 0.0)); assert!(is_close(result_fixed_temp.internal_resistance, internal_resistance, 1e-04, 0.0));
1062 }
1063
1064 #[test]
1066 #[ignore]
1067 fn test_maximize_efficiency_for_linear_elec_resi() {
1068 let hot_temp: f64 = 500.0;
1070 let cold_temp: f64 = 300.0;
1071 let delta_temp = hot_temp - cold_temp;
1072 let mid_temp: f64 = (hot_temp+cold_temp) * 0.5;
1073
1074 let alpha0: f64 = 150e-6;
1076 let rho0: f64 = 1e-5;
1077 let kappa0: f64 = 2.0;
1078 let temp0: f64 = 400.0;
1079 let seebeck = ConstantFunction::new(alpha0);
1080 let elec_resi = ExplicitFunction::new(Rc::new(move |temp: f64| { max(rho0*temp/temp0, 1e-08) })); let thrm_cond = ConstantFunction::new(kappa0);
1082 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1083 let length = 1e-3;
1084 let area = 1e-6;
1085
1086 let minimizer_kind = MinimizerKind::Bfgs;
1088 let minimizer_param = MinimizerParam {
1089 grad_tol: 1e-7,
1090 a_max: 2.0,
1091 max_iter: 200,
1092 ..MinimizerParam::default(minimizer_kind)
1093 };
1094 let simul_param = SimulationParam {
1095 dx: length/10.0,
1096 dtemp: (hot_temp-cold_temp)/10.0,
1097 abs_tol_elec_cur_brent: 1e-6,
1098 minimizer_kind,
1099 minimizer_param,
1100 ..SimulationParam::default()
1101 };
1102
1103 let leg = SingleLeg::new(&tep, length, area, simul_param);
1105
1106 let left_bc = FixedTempBc::new(hot_temp);
1108 let right_bc = FixedTempBc::new(cold_temp);
1109 let result_fixed_temp = leg.maximize_efficiency_auto_range(&left_bc, &right_bc, hot_temp, cold_temp);
1111 assert!(result_fixed_temp.success);
1112 println!("result = {}", result_fixed_temp);
1113 let load_resistance = result_fixed_temp.load_resistance();
1115 assert!(is_close(result_fixed_temp.total_resistance(), result_fixed_temp.internal_resistance + load_resistance, 1e-05, 0.0));
1116 assert!(is_close(result_fixed_temp.load_ratio(), load_resistance / result_fixed_temp.internal_resistance, 1e-05, 0.0));
1117 let z = alpha0.powi(2)/(rho0*kappa0);
1119 let max_efficiency = (delta_temp/hot_temp) * ((1.0+z*mid_temp).sqrt()-1.0) / ((1.0+z*mid_temp).sqrt()+(cold_temp/hot_temp));
1120 println!("computed eff={}, exact max eff={}", result_fixed_temp.efficiency, max_efficiency);
1121 assert!(is_close(result_fixed_temp.efficiency, max_efficiency, 1e-04, 0.0)); }
1123
1124 #[test]
1126 #[ignore]
1127 fn test_maximize_efficiency_for_cpm() {
1128 let hot_temp: f64 = 500.0;
1130 let cold_temp: f64 = 300.0;
1131 let delta_temp = hot_temp-cold_temp;
1132 let mid_temp: f64 = (hot_temp+cold_temp) * 0.5;
1133
1134 let alpha0: f64 = 150e-6; let rho0: f64 = 1e-5; let kappa0: f64 = 2.0; let seebeck = ConstantFunction::new(alpha0);
1139 let elec_resi = ConstantFunction::new(rho0);
1140 let thrm_cond = ConstantFunction::new(kappa0);
1141 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1142 let length = 1e-3; let area = 1e-6; let z = alpha0.powi(2)/(rho0*kappa0);
1147 let max_efficiency = (delta_temp/hot_temp) * ((1.0+z*mid_temp).sqrt()-1.0) / ((1.0+z*mid_temp).sqrt()+(cold_temp/hot_temp));
1148
1149 let minimizer_kind = MinimizerKind::Bfgs;
1151 let minimizer_param = MinimizerParam {
1152 grad_tol: 1e-5,
1153 a_max: 2.0,
1154 max_iter: 200,
1155 ..MinimizerParam::default(minimizer_kind)
1156 };
1157 let simul_param = SimulationParam {
1158 dx: length/10.0,
1159 dtemp: (hot_temp-cold_temp)/10.0,
1160 abs_tol_elec_cur_brent: 1e-6,
1161 minimizer_kind,
1162 minimizer_param,
1163 ..SimulationParam::default()
1164 };
1165
1166 let leg = SingleLeg::new(&tep, length, area, simul_param);
1168
1169 let left_bc = FixedTempBc::new(hot_temp);
1171 let right_bc = FixedTempBc::new(cold_temp);
1172 let result_fixed_temp = leg.maximize_efficiency_auto_range(&left_bc, &right_bc, hot_temp, cold_temp);
1174 println!("result={}", result_fixed_temp);
1175 println!("numerical eff={}, exact max eff={}", result_fixed_temp.efficiency, max_efficiency);
1176 assert!(result_fixed_temp.success);
1177 assert!(is_close(result_fixed_temp.efficiency, max_efficiency, 1e-04, 0.0)); }
1179
1180 #[test]
1182 #[ignore]
1183 fn test_single_leg_simulate_fixed_load_ratio_for_cpm() {
1184 let hot_temp: f64 = 500.0;
1186 let cold_temp: f64 = 300.0;
1187 let delta_temp: f64 = hot_temp - cold_temp;
1188
1189 let alpha0: f64 = 150e-6;
1191 let rho0: f64 = 1e-5;
1192 let kappa0: f64 = 2.0;
1193 let seebeck = ConstantFunction::new(alpha0);
1194 let elec_resi = ConstantFunction::new(rho0);
1195 let thrm_cond = ConstantFunction::new(kappa0);
1196 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1197 let length = 1e-3;
1198 let area = 1e-6;
1199 let elec_cur: f64 = 1.0;
1200
1201 let minimizer_kind = MinimizerKind::DampedBfgs;
1203 let minimizer_param = MinimizerParam {
1204 grad_tol: 1e-7,
1205 a_max: 2.0,
1206 max_iter: 200,
1207 ..MinimizerParam::default(minimizer_kind)
1208 };
1209 let simul_param = SimulationParam {
1210 dx: length/10.0,
1211 dtemp: delta_temp/10.0,
1212 minimizer_kind,
1213 minimizer_param,
1214 ..SimulationParam::default()
1215 };
1216
1217 let leg = SingleLeg::new(&tep, length, area, simul_param);
1219
1220 let left_bc = FixedTempBc::new(hot_temp);
1222 let right_bc = FixedTempBc::new(cold_temp);
1223 let simul_result_fixed_elec_cur = leg.simulate_fixed_elec_cur(elec_cur, &left_bc, &right_bc, hot_temp, cold_temp);
1224 assert!(simul_result_fixed_elec_cur.success);
1225 let fixed_load_ratio = simul_result_fixed_elec_cur.load_ratio();
1226
1227 let now = Instant::now();
1229 let simul_result_fixed_load_ratio = leg.simulate_fixed_load_ratio(fixed_load_ratio, &left_bc, &right_bc, elec_cur/2.0, hot_temp, cold_temp);
1231 println!("For fixed BC and fixed load ratio: Elapsed {:.2?}", now.elapsed());
1232 assert!(simul_result_fixed_load_ratio.success);
1233 assert!(is_close(simul_result_fixed_load_ratio.load_ratio(), fixed_load_ratio, 2e-04, 0.0));
1234 assert!(is_close(simul_result_fixed_load_ratio.elec_cur, elec_cur, 2e-04, 0.0));
1235 println!("result={}", simul_result_fixed_load_ratio);
1236 println!("computed load_ratio={}", simul_result_fixed_load_ratio.load_ratio());
1237
1238 let left_bc = FixedHeatFluxBc::new(100.0);
1240 let right_bc = ConvectionBc::new(30.0, 8.0);
1241 let now = Instant::now();
1243 let simul_result_fixed_load_ratio = leg.simulate_fixed_load_ratio(fixed_load_ratio, &left_bc, &right_bc, elec_cur, hot_temp, cold_temp);
1244 println!("For RTG BC and fixed load ratio: Elapsed {:.2?}", now.elapsed());
1245 println!("result={}", simul_result_fixed_load_ratio);
1246 println!("computed load_ratio={}", simul_result_fixed_load_ratio.load_ratio());
1247 println!("templ={}, tempr={}", simul_result_fixed_load_ratio.temp.at(0.0), simul_result_fixed_load_ratio.temp.at(leg.length));
1248 if !simul_result_fixed_load_ratio.success {
1249 println!("rtg error={:?}", simul_result_fixed_load_ratio.error);
1250 }
1251 assert!(is_close(simul_result_fixed_load_ratio.load_ratio(), fixed_load_ratio, 2e-04, 0.0));
1253 }
1254
1255 #[test]
1258 #[ignore]
1259 fn test_single_leg_simulate_fixed_load_resistance_for_linear_elec_resi() {
1260 let hot_temp: f64 = 500.0;
1262 let cold_temp: f64 = 300.0;
1263
1264 let alpha0: f64 = 150e-6;
1266 let rho0: f64 = 1e-5;
1267 let temp0: f64 = 400.0;
1268 let kappa0: f64 = 2.0;
1269 let seebeck = PiecewiseLinearConstExt::new(&array![0.0, cold_temp, hot_temp], &array![0.0, alpha0, alpha0], 0.0, alpha0); let elec_resi = ExplicitFunction::new(Rc::new(move |temp: f64| {
1271 if temp > hot_temp {
1272 return rho0*hot_temp/temp0; }
1274 max(rho0*temp/temp0, rho0*cold_temp/temp0) }));
1276 let thrm_cond = ConstantFunction::new(kappa0);
1277 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1278 let length = 1e-3;
1279 let area = 1e-6;
1280 let elec_cur: f64 = 1.0;
1281 let j: f64 = elec_cur / area; let minimizer_kind = MinimizerKind::Bfgs;
1285 let minimizer_param = MinimizerParam {
1286 grad_tol: 1e-5,
1287 a_max: 2.0,
1288 max_iter: 200,
1289 ..MinimizerParam::default(minimizer_kind)
1290 };
1291 let simul_param = SimulationParam {
1292 dx: length/10.0,
1293 dtemp: (hot_temp-cold_temp)/10.0,
1294 minimizer_kind,
1295 minimizer_param,
1296 ..SimulationParam::default()
1297 };
1298
1299 let num_x_mesh: usize = 31;
1301 let x_mesh: ColVec = Array::linspace(0.0, length, num_x_mesh);
1302 let omega: f64 = (rho0/(kappa0*temp0)).sqrt() * j;
1303 let cos_omega_x: ColVec = x_mesh.mapv(|x| (omega*x).cos());
1304 let sin_omega_x: ColVec = x_mesh.mapv(|x| (omega*x).sin());
1305 let temp_exact: ColVec = hot_temp * &cos_omega_x + (cold_temp - hot_temp*(omega*length).cos())/(omega*length).sin() * &sin_omega_x;
1306 let dtempdx_exact: ColVec = -hot_temp * omega * &sin_omega_x + (cold_temp - hot_temp*(omega*length).cos())/(omega*length).sin() * omega * &cos_omega_x;
1307 let heat_flux_exact: ColVec = alpha0*&temp_exact*j - kappa0*&dtempdx_exact;
1308
1309 let leg = SingleLeg::new(&tep, length, area, simul_param);
1311
1312 let left_bc = FixedTempBc::new(hot_temp);
1314 let right_bc = FixedTempBc::new(cold_temp);
1315 let simul_result_fixed_elec_cur = leg.simulate_fixed_elec_cur(elec_cur, &left_bc, &right_bc, hot_temp, cold_temp);
1316 assert!(simul_result_fixed_elec_cur.success);
1317 let load_resistance = simul_result_fixed_elec_cur.load_resistance();
1319 assert!(is_close(simul_result_fixed_elec_cur.total_resistance(), simul_result_fixed_elec_cur.internal_resistance + load_resistance, 1e-04, 0.0));
1320 assert!(is_close(simul_result_fixed_elec_cur.load_ratio(), load_resistance / simul_result_fixed_elec_cur.internal_resistance, 1e-04, 1.0));
1321 let simul_result_fixed_load_resistance = leg.simulate_fixed_load_resistance(load_resistance, &left_bc, &right_bc, 0.5, hot_temp, cold_temp);
1324 println!("dirichlet; numerical elec_cur={}, exact elec_cur={}", simul_result_fixed_load_resistance.elec_cur, elec_cur);
1325 println!("dirichlet; numerical load resistance={}, exact load resistance={}", simul_result_fixed_load_resistance.load_resistance(), load_resistance);
1326 println!("dirichlet; result={}", simul_result_fixed_load_resistance);
1327 assert!(simul_result_fixed_load_resistance.success);
1328 assert!(is_close(simul_result_fixed_load_resistance.elec_cur, elec_cur, 2e-03, 0.0)); assert!(is_close(simul_result_fixed_load_resistance.load_resistance(), load_resistance, 2e-03, 0.0));
1330
1331 let heat_fluxl: f64 = heat_flux_exact[0];
1333 let fluid_temp: f64 = cold_temp - 100.0;
1334 let h: f64 = heat_flux_exact[num_x_mesh-1] / (temp_exact[num_x_mesh-1] - fluid_temp);
1335 let left_bc = FixedHeatFluxBc::new(heat_fluxl);
1336 let right_bc = ConvectionBc::new(h, fluid_temp);
1337 let elec_cur: f64 = 1.2;
1338 let simul_result_fixed_elec_cur = leg.simulate_fixed_elec_cur(elec_cur, &left_bc, &right_bc, hot_temp, cold_temp);
1339 assert!(simul_result_fixed_elec_cur.success);
1340 println!("rtg result={}", simul_result_fixed_elec_cur);
1342 let load_resistance = simul_result_fixed_elec_cur.load_resistance();
1343 let simul_result_fixed_load_resistance = leg.simulate_fixed_load_resistance(load_resistance, &left_bc, &right_bc, elec_cur*0.9, hot_temp, cold_temp);
1346 println!("rtg; numerical elec_cur={}, exact elec_cur={}", simul_result_fixed_load_resistance.elec_cur, elec_cur);
1348 println!("rtg; numerical load resistance={}, exact load resistance={}", simul_result_fixed_load_resistance.load_resistance(), load_resistance);
1349 println!("rtg; result={}", simul_result_fixed_load_resistance);
1350 assert!(simul_result_fixed_load_resistance.success);
1351 assert!(is_close(simul_result_fixed_load_resistance.elec_cur, elec_cur, 2e-03, 0.0)); assert!(is_close(simul_result_fixed_load_resistance.load_resistance(), load_resistance, 2e-03, 0.0));
1353 }
1354
1355 #[test]
1358 #[ignore]
1359 fn test_single_leg_simulate_fixed_elec_cur_for_log_seebeck() {
1360 let hot_temp: f64 = 500.0;
1362 let cold_temp: f64 = 300.0;
1363
1364 let tau0: f64 = 150e-6;
1366 let rho0: f64 = 1e-5;
1367 let kappa0: f64 = 2.0;
1368 let seebeck = ExplicitFunction::new(Rc::new(move |temp: f64| { tau0*(max(temp/cold_temp, 1.0)).ln() })); let elec_resi = ConstantFunction::new(rho0);
1370 let thrm_cond = ConstantFunction::new(kappa0);
1371 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1372 let length = 1e-3;
1373 let area = 1e-6;
1374 let elec_cur: f64 = 1.0;
1375 let j: f64 = elec_cur / area; let minimizer_kind = MinimizerKind::Bfgs;
1379 let minimizer_param = MinimizerParam {
1380 grad_tol: 1e-5,
1381 max_iter: 100,
1382 ..MinimizerParam::default(minimizer_kind)
1383 };
1384 let simul_param = SimulationParam {
1385 dx: length/10.0,
1386 minimizer_kind,
1387 minimizer_param,
1388 ..SimulationParam::default()
1389 };
1390
1391 let num_x_mesh: usize = 31;
1393 let x_mesh: ColVec = Array::linspace(0.0, length, num_x_mesh);
1394 let temp_exact: ColVec = hot_temp + ((cold_temp-hot_temp) - (rho0*j/tau0)*length)/(1.0-(tau0*j*length/kappa0).exp()) * (1.0 - x_mesh.mapv(|x| { (tau0*j*x/kappa0).exp() })) + rho0*j/tau0*&x_mesh;
1395 assert!(all_close(&array![temp_exact[0], temp_exact[num_x_mesh-1]], &array![hot_temp, cold_temp], 1e-04, 0.0));
1396
1397 let leg = SingleLeg::new(&tep, length, area, simul_param);
1399
1400 let simul_result = leg.simulate_fixed_elec_cur(elec_cur, &FixedTempBc::new(hot_temp), &FixedTempBc::new(cold_temp), hot_temp, cold_temp);
1402 assert!(simul_result.success);
1403 assert!(all_close(&simul_result.temp.ats_incr(&x_mesh), &temp_exact, 1e-04, 0.0));
1404 }
1405
1406 #[test]
1409 #[ignore]
1410 fn test_single_leg_simulate_fixed_elec_cur_for_linear_elec_resi() {
1411 let hot_temp: f64 = 500.0;
1413 let cold_temp: f64 = 300.0;
1414
1415 let alpha0: f64 = 150e-6;
1417 let rho0: f64 = 1e-5;
1418 let kappa0: f64 = 2.0;
1419 let temp0: f64 = 400.0;
1420 let seebeck = PiecewiseLinearConstExt::new(&array![0.0, cold_temp, hot_temp], &array![0.0, alpha0, alpha0], 0.0, alpha0); let elec_resi = ExplicitFunction::new(Rc::new(move |temp: f64| { max(rho0*temp/temp0, rho0*cold_temp/temp0) }));
1422 let thrm_cond = ConstantFunction::new(kappa0);
1423 let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1424 let length = 1e-3;
1425 let area = 1e-6;
1426 let elec_cur: f64 = 1.0;
1427 let j: f64 = elec_cur / area; let minimizer_kind = MinimizerKind::Bfgs;
1432 let minimizer_param = MinimizerParam {
1433 grad_tol: 1e-5,
1434 max_iter: 100,
1435 ..MinimizerParam::default(minimizer_kind)
1436 };
1437 let simul_param = SimulationParam {
1438 dx: length/10.0,
1439 minimizer_kind,
1440 minimizer_param,
1441 ..SimulationParam::default()
1442 };
1443
1444 let num_x_mesh: usize = 31;
1446 let x_mesh: ColVec = Array::linspace(0.0, length, num_x_mesh);
1447 let omega: f64 = (rho0/(kappa0*temp0)).sqrt() * j;
1448 let cos_omega_x: ColVec = x_mesh.mapv(|x| (omega*x).cos());
1449 let sin_omega_x: ColVec = x_mesh.mapv(|x| (omega*x).sin());
1450 let temp_exact: ColVec = hot_temp * &cos_omega_x + (cold_temp - hot_temp*(omega*length).cos())/(omega*length).sin() * &sin_omega_x;
1451 let dtempdx_exact: ColVec = -hot_temp * omega * &sin_omega_x + (cold_temp - hot_temp*(omega*length).cos())/(omega*length).sin() * omega * &cos_omega_x;
1452 let heat_flux_exact: ColVec = alpha0*&temp_exact*j - kappa0*&dtempdx_exact;
1453
1454 let leg = SingleLeg::new(&tep, length, area, simul_param);
1456
1457 let now = Instant::now();
1459 let dirichlet_simul_result = leg.simulate_fixed_elec_cur(elec_cur, &FixedTempBc::new(hot_temp), &FixedTempBc::new(cold_temp), hot_temp, cold_temp);
1460 println!("test_single_leg_simulate_fixed_elec_cur_for_linear_elec_resi--case 1: Elapsed {:.2?}", now.elapsed());
1461 if !dirichlet_simul_result.success {
1462 println!("dirichlet failed: error={:?}", dirichlet_simul_result.error);
1463 }
1464 assert!(dirichlet_simul_result.success);
1465 assert!(all_close(&dirichlet_simul_result.temp.ats_incr(&x_mesh), &temp_exact, 1e-03, 0.0));
1466 assert!(all_close(&dirichlet_simul_result.heat_flux.ats_incr(&x_mesh), &heat_flux_exact, 1e-03, 0.0));
1467
1468 let heat_fluxl: f64 = heat_flux_exact[0];
1470 let fluid_temp: f64 = cold_temp - 100.0;
1471 let h: f64 = heat_flux_exact[num_x_mesh-1] / (temp_exact[num_x_mesh-1] - fluid_temp);
1472 let now = Instant::now();
1473 let rtg_simul_result = leg.simulate_fixed_elec_cur(elec_cur, &FixedHeatFluxBc::new(heat_fluxl), &ConvectionBc::new(h, fluid_temp), hot_temp, cold_temp);
1474 println!("test_single_leg_simulate_fixed_elec_cur_for_linear_elec_resi--case 2: Elapsed {:.2?}", now.elapsed());
1475 if !rtg_simul_result.success {
1476 println!("rtg failed: error={:?}", rtg_simul_result.error);
1477 }
1478 assert!(rtg_simul_result.success);
1479 println!("rtg numerical temp = {}", rtg_simul_result.temp.ats_incr(&x_mesh));
1480 println!("rtg exact temp = {}", temp_exact);
1481 println!("rtg numerical heat_flux = {}", rtg_simul_result.heat_flux.ats_incr(&x_mesh));
1482 println!("rtg exact heat_flux = {}", heat_flux_exact);
1483 assert!(all_close(&rtg_simul_result.temp.ats_incr(&x_mesh), &temp_exact, 1e-03, 0.0));
1484 assert!(all_close(&rtg_simul_result.heat_flux.ats_incr(&x_mesh), &heat_flux_exact, 1e-03, 0.0));
1485 }
1486
1487 #[test]
1490 #[ignore]
1491 fn test_single_leg_simulate_fixed_elec_cur_for_linear_thrm_cond() {
1492 let hot_temp: f64 = 500.0;
1494 let cold_temp: f64 = 300.0;
1495
1496 let alpha0: f64 = 150e-6; let rho0: f64 = 1e-5; let kappa0: f64 = 2.0; let temp0: f64 = 300.0; let seebeck = ConstantFunction::new(alpha0);
1502 let elec_resi = ConstantFunction::new(rho0);
1503 let thrm_cond = ExplicitFunction::new(Rc::new(move |temp: f64| { max(kappa0*temp/temp0, kappa0*cold_temp/temp0) })); let tep = Tep::new(Rc::new(seebeck), Rc::new(elec_resi), Rc::new(thrm_cond));
1505 let length = 1e-3; let area = 1e-6; let elec_cur: f64 = 1.0;
1508 let j: f64 = elec_cur / area; let minimizer_kind = MinimizerKind::Bfgs;
1512 let minimizer_param = MinimizerParam {
1513 grad_tol: 1e-5,
1514 max_iter: 100,
1515 ..MinimizerParam::default(minimizer_kind)
1516 };
1517 let simul_param = SimulationParam {
1518 dx: length/10.0,
1519 minimizer_kind,
1520 minimizer_param,
1521 ..SimulationParam::default()
1522 };
1523
1524 let num_x_mesh: usize = 31;
1526 let x_mesh: ColVec = Array::linspace(0.0, length, num_x_mesh);
1527 let temp_exact: ColVec = ( hot_temp.powi(2) + (cold_temp.powi(2)-hot_temp.powi(2))*&x_mesh/length + j.powi(2)*temp0*rho0/kappa0*&x_mesh*(length-&x_mesh) ).mapv(|e| {e.sqrt()});
1528 assert!(all_close(&array![temp_exact[0], temp_exact[num_x_mesh-1]], &array![hot_temp, cold_temp], 0.0, 0.0)); let leg = SingleLeg::new(&tep, length, area, simul_param);
1532
1533 let now = Instant::now();
1535 let simul_result = leg.simulate_fixed_elec_cur(elec_cur, &FixedTempBc::new(hot_temp), &FixedTempBc::new(cold_temp), hot_temp, cold_temp);
1536 println!("test_single_leg_simulate_fixed_elec_cur_for_linear_thrm_cond: Elapsed {:.2?}", now.elapsed());
1537 if !simul_result.success {
1538 println!("error: {:?}", simul_result.error);
1539 }
1540 assert!(simul_result.success);
1541 println!("result = {}", simul_result);
1542 println!("num_iter = {}", simul_result.num_iter);
1543 println!("fixed cur, linear_thrm_cond={}", simul_result);
1544 println!("temps={}", simul_result.temp.ats_incr(&x_mesh));
1545 assert!(all_close(&simul_result.temp.ats_incr(&x_mesh), &temp_exact, 2e-04, 0.0)); }
1547
1548 #[test]
1549 fn test_get_num_mesh() {
1550 assert_eq!(get_num_mesh(10.0, 1.0, 5, 11), 11);
1551 assert_eq!(get_num_mesh(10.0, 10.0, 3, 11), 5);
1552 }
1553}