ubc-solar-physics 0.1.0__cp311-cp311-macosx_13_0_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- core.cpython-311-darwin.so +0 -0
- physics/__init__.py +14 -0
- physics/environment/__init__.py +33 -0
- physics/environment/base_environment.py +62 -0
- physics/environment/environment.rs +3 -0
- physics/environment/gis/__init__.py +7 -0
- physics/environment/gis/base_gis.py +24 -0
- physics/environment/gis/gis.py +374 -0
- physics/environment/gis/gis.rs +25 -0
- physics/environment/gis.rs +1 -0
- physics/environment/openweather_environment.py +18 -0
- physics/environment/race.py +89 -0
- physics/environment/solar_calculations/OpenweatherSolarCalculations.py +529 -0
- physics/environment/solar_calculations/SolcastSolarCalculations.py +41 -0
- physics/environment/solar_calculations/__init__.py +9 -0
- physics/environment/solar_calculations/base_solar_calculations.py +9 -0
- physics/environment/solar_calculations/solar_calculations.rs +24 -0
- physics/environment/solar_calculations.rs +1 -0
- physics/environment/solcast_environment.py +18 -0
- physics/environment/weather_forecasts/OpenWeatherForecast.py +308 -0
- physics/environment/weather_forecasts/SolcastForecasts.py +216 -0
- physics/environment/weather_forecasts/__init__.py +9 -0
- physics/environment/weather_forecasts/base_weather_forecasts.py +57 -0
- physics/environment/weather_forecasts/weather_forecasts.rs +116 -0
- physics/environment/weather_forecasts.rs +1 -0
- physics/environment.rs +3 -0
- physics/lib.rs +76 -0
- physics/models/__init__.py +13 -0
- physics/models/arrays/__init__.py +7 -0
- physics/models/arrays/arrays.rs +0 -0
- physics/models/arrays/base_array.py +6 -0
- physics/models/arrays/basic_array.py +39 -0
- physics/models/arrays.rs +1 -0
- physics/models/battery/__init__.py +7 -0
- physics/models/battery/base_battery.py +29 -0
- physics/models/battery/basic_battery.py +141 -0
- physics/models/battery/battery.rs +0 -0
- physics/models/battery.rs +1 -0
- physics/models/constants.py +23 -0
- physics/models/lvs/__init__.py +7 -0
- physics/models/lvs/base_lvs.py +6 -0
- physics/models/lvs/basic_lvs.py +18 -0
- physics/models/lvs/lvs.rs +0 -0
- physics/models/lvs.rs +1 -0
- physics/models/motor/__init__.py +7 -0
- physics/models/motor/base_motor.py +6 -0
- physics/models/motor/basic_motor.py +179 -0
- physics/models/motor/motor.rs +0 -0
- physics/models/motor.rs +1 -0
- physics/models/regen/__init__.py +7 -0
- physics/models/regen/base_regen.py +6 -0
- physics/models/regen/basic_regen.py +39 -0
- physics/models/regen/regen.rs +0 -0
- physics/models/regen.rs +1 -0
- physics/models.rs +5 -0
- ubc_solar_physics-0.1.0.dist-info/LICENSE +21 -0
- ubc_solar_physics-0.1.0.dist-info/METADATA +44 -0
- ubc_solar_physics-0.1.0.dist-info/RECORD +60 -0
- ubc_solar_physics-0.1.0.dist-info/WHEEL +5 -0
- ubc_solar_physics-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
use chrono::{Datelike, NaiveDateTime, Timelike};
|
2
|
+
use numpy::ndarray::{s, Array, Array2, ArrayViewD, ArrayViewMut2, ArrayViewMut3, Axis};
|
3
|
+
|
4
|
+
|
5
|
+
pub fn rust_closest_weather_indices_loop(
|
6
|
+
cumulative_distances: ArrayViewD<'_, f64>,
|
7
|
+
average_distances: ArrayViewD<'_, f64>,
|
8
|
+
) -> Vec<i64> {
|
9
|
+
let mut current_coord_index: usize = 0;
|
10
|
+
let mut result: Vec<i64> = Vec::with_capacity(cumulative_distances.len());
|
11
|
+
|
12
|
+
for &distance in cumulative_distances {
|
13
|
+
current_coord_index = std::cmp::min(current_coord_index, average_distances.len() - 1);
|
14
|
+
|
15
|
+
if distance > average_distances[current_coord_index] {
|
16
|
+
current_coord_index += 1;
|
17
|
+
|
18
|
+
current_coord_index =
|
19
|
+
std::cmp::min(current_coord_index, average_distances.len() - 1);
|
20
|
+
}
|
21
|
+
|
22
|
+
result.push(current_coord_index as i64);
|
23
|
+
}
|
24
|
+
|
25
|
+
result
|
26
|
+
}
|
27
|
+
|
28
|
+
pub fn rust_weather_in_time(
|
29
|
+
unix_timestamps: ArrayViewD<'_, i64>,
|
30
|
+
indices: ArrayViewD<'_, i64>,
|
31
|
+
weather_forecast: ArrayViewD<f64>,
|
32
|
+
dt_index: u8
|
33
|
+
) -> Array2<f64> {
|
34
|
+
// Obtain dimensions for arrays and slices
|
35
|
+
let weather_forecast_raw_dim = weather_forecast.raw_dim();
|
36
|
+
let full_forecast_shape = (
|
37
|
+
weather_forecast_raw_dim[0],
|
38
|
+
weather_forecast_raw_dim[1],
|
39
|
+
weather_forecast_raw_dim[2],
|
40
|
+
);
|
41
|
+
let weather_at_coord_shape = (full_forecast_shape.1, full_forecast_shape.2);
|
42
|
+
let weather_in_time_shape = (indices.len(), full_forecast_shape.2);
|
43
|
+
|
44
|
+
// Create an empty full_weather_forecast_at_coords array (all zeros)
|
45
|
+
let indexed_weather_shape = (indices.len(), full_forecast_shape.1, full_forecast_shape.2);
|
46
|
+
let mut placeholder1: Vec<f64> =
|
47
|
+
vec![0.0; indexed_weather_shape.0 * indexed_weather_shape.1 * indexed_weather_shape.2];
|
48
|
+
let mut indexed_forecast =
|
49
|
+
ArrayViewMut3::from_shape(indexed_weather_shape, &mut placeholder1).unwrap();
|
50
|
+
|
51
|
+
// Fill full_weather_forecast_at_coords with the 2d slices at [indices]
|
52
|
+
for (out_index, &coord_index) in indices.iter().enumerate() {
|
53
|
+
let slice_2d = weather_forecast
|
54
|
+
.slice(s![coord_index as usize, .., ..])
|
55
|
+
.into_shape(weather_at_coord_shape)
|
56
|
+
.unwrap();
|
57
|
+
indexed_forecast
|
58
|
+
.slice_mut(s![out_index, .., ..])
|
59
|
+
.assign(&slice_2d);
|
60
|
+
}
|
61
|
+
|
62
|
+
let mut dt_local_array = Vec::with_capacity(full_forecast_shape.1);
|
63
|
+
// Populate dt_local_array with the list of forecast's timestamps at the first coordinate
|
64
|
+
// I don't really understand how this works
|
65
|
+
let dt_local_arrayview = weather_forecast
|
66
|
+
.index_axis_move(Axis(0), 0)
|
67
|
+
.index_axis_move(Axis(1), dt_index as usize);
|
68
|
+
for ×tamp in dt_local_arrayview {
|
69
|
+
dt_local_array.push(timestamp as i64);
|
70
|
+
}
|
71
|
+
|
72
|
+
let closest_timestamp_indices =
|
73
|
+
rust_closest_timestamp_indices(unix_timestamps, dt_local_array);
|
74
|
+
|
75
|
+
// Create a mutable array of the desired shape with dummy initial values
|
76
|
+
let mut placeholder2: Vec<f64> =
|
77
|
+
vec![0.0; weather_in_time_shape.0 * weather_in_time_shape.1];
|
78
|
+
let mut weather_in_time_arrayview =
|
79
|
+
ArrayViewMut2::from_shape(weather_in_time_shape, &mut placeholder2).unwrap();
|
80
|
+
for (index_1, &index_2) in closest_timestamp_indices.iter().enumerate() {
|
81
|
+
let slice_1d = indexed_forecast
|
82
|
+
.slice(s![index_1, index_2, ..])
|
83
|
+
.into_shape(full_forecast_shape.2)
|
84
|
+
.unwrap();
|
85
|
+
weather_in_time_arrayview
|
86
|
+
.slice_mut(s![index_1, ..])
|
87
|
+
.assign(&slice_1d);
|
88
|
+
}
|
89
|
+
|
90
|
+
weather_in_time_arrayview.into_owned()
|
91
|
+
}
|
92
|
+
|
93
|
+
pub fn rust_closest_timestamp_indices(
|
94
|
+
unix_timestamps: ArrayViewD<'_, i64>,
|
95
|
+
dt_local_array: Vec<i64>,
|
96
|
+
) -> Vec<usize> {
|
97
|
+
let mut closest_time_stamp_indices: Vec<usize> = Vec::new();
|
98
|
+
|
99
|
+
for unix_timestamp in unix_timestamps {
|
100
|
+
let unix_timestamp_array =
|
101
|
+
Array::from_elem(dt_local_array.len(), unix_timestamp as &i64);
|
102
|
+
let mut differences: Vec<i64> = Vec::new();
|
103
|
+
|
104
|
+
for i in 0..unix_timestamp_array.len() {
|
105
|
+
differences.push((unix_timestamp - dt_local_array[i]).abs());
|
106
|
+
}
|
107
|
+
|
108
|
+
let (min_index, _) = differences
|
109
|
+
.iter()
|
110
|
+
.enumerate()
|
111
|
+
.min_by_key(|(_, &v)| v)
|
112
|
+
.unwrap();
|
113
|
+
closest_time_stamp_indices.push(min_index)
|
114
|
+
}
|
115
|
+
closest_time_stamp_indices
|
116
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
pub mod weather_forecasts;
|
physics/environment.rs
ADDED
physics/lib.rs
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
use chrono::{Datelike, NaiveDateTime, Timelike};
|
2
|
+
use numpy::ndarray::{s, Array, Array2, ArrayViewD, ArrayViewMut2, ArrayViewMut3, Axis};
|
3
|
+
use numpy::{PyArray, PyArrayDyn, PyReadwriteArrayDyn};
|
4
|
+
use pyo3::prelude::*;
|
5
|
+
use pyo3::types::PyModule;
|
6
|
+
|
7
|
+
pub mod environment;
|
8
|
+
pub mod models;
|
9
|
+
use crate::environment::gis::gis::rust_closest_gis_indices_loop;
|
10
|
+
use crate::environment::solar_calculations::solar_calculations::rust_calculate_array_ghi_times;
|
11
|
+
use crate::environment::weather_forecasts::weather_forecasts::{rust_closest_weather_indices_loop, rust_weather_in_time, rust_closest_timestamp_indices};
|
12
|
+
|
13
|
+
/// A Python module implemented in Rust. The name of this function is the Rust module name!
|
14
|
+
#[pymodule]
|
15
|
+
#[pyo3(name = "core")]
|
16
|
+
fn rust_simulation(_py: Python, m: &PyModule) -> PyResult<()> {
|
17
|
+
#[pyfn(m)]
|
18
|
+
#[pyo3(name = "calculate_array_ghi_times")]
|
19
|
+
fn calculate_array_ghi_times<'py>(
|
20
|
+
py: Python<'py>,
|
21
|
+
python_local_times: PyReadwriteArrayDyn<'py, u64>,
|
22
|
+
) -> (&'py PyArrayDyn<f64>, &'py PyArrayDyn<f64>) {
|
23
|
+
let local_times = python_local_times.as_array();
|
24
|
+
let (day_of_year_out, local_time_out) = rust_calculate_array_ghi_times(local_times);
|
25
|
+
let py_day_out = PyArray::from_vec(py, day_of_year_out).to_dyn();
|
26
|
+
let py_time_out = PyArray::from_vec(py, local_time_out).to_dyn();
|
27
|
+
(py_day_out, py_time_out)
|
28
|
+
}
|
29
|
+
|
30
|
+
#[pyfn(m)]
|
31
|
+
#[pyo3(name = "closest_gis_indices_loop")]
|
32
|
+
fn closest_gis_indices_loop<'py>(
|
33
|
+
py: Python<'py>,
|
34
|
+
python_cumulative_distances: PyReadwriteArrayDyn<'py, f64>,
|
35
|
+
python_average_distances: PyReadwriteArrayDyn<'py, f64>,
|
36
|
+
) -> &'py PyArrayDyn<i64> {
|
37
|
+
let average_distances = python_average_distances.as_array();
|
38
|
+
let cumulative_distances = python_cumulative_distances.as_array();
|
39
|
+
let result = rust_closest_gis_indices_loop(cumulative_distances, average_distances);
|
40
|
+
let py_result = PyArray::from_vec(py, result).to_dyn();
|
41
|
+
py_result
|
42
|
+
}
|
43
|
+
|
44
|
+
#[pyfn(m)]
|
45
|
+
#[pyo3(name = "closest_weather_indices_loop")]
|
46
|
+
fn closest_weather_indices_loop<'py>(
|
47
|
+
py: Python<'py>,
|
48
|
+
python_cumulative_distances: PyReadwriteArrayDyn<'py, f64>,
|
49
|
+
python_average_distances: PyReadwriteArrayDyn<'py, f64>,
|
50
|
+
) -> &'py PyArrayDyn<i64> {
|
51
|
+
let average_distances = python_average_distances.as_array();
|
52
|
+
let cumulative_distances = python_cumulative_distances.as_array();
|
53
|
+
let result = rust_closest_weather_indices_loop(cumulative_distances, average_distances);
|
54
|
+
let py_result = PyArray::from_vec(py, result).to_dyn();
|
55
|
+
py_result
|
56
|
+
}
|
57
|
+
|
58
|
+
#[pyfn(m)]
|
59
|
+
#[pyo3(name = "weather_in_time")]
|
60
|
+
fn weather_in_time<'py>(
|
61
|
+
py: Python<'py>,
|
62
|
+
python_unix_timestamps: PyReadwriteArrayDyn<'py, i64>,
|
63
|
+
python_indices: PyReadwriteArrayDyn<'py, i64>,
|
64
|
+
python_weather_forecast: PyReadwriteArrayDyn<'py, f64>,
|
65
|
+
index: u8
|
66
|
+
) -> &'py PyArrayDyn<f64> {
|
67
|
+
let unix_timestamps = python_unix_timestamps.as_array();
|
68
|
+
let indices = python_indices.as_array();
|
69
|
+
let weather_forecast = python_weather_forecast.as_array();
|
70
|
+
let mut result = rust_weather_in_time(unix_timestamps, indices, weather_forecast, index);
|
71
|
+
let py_result = PyArray::from_array(py, &mut result).to_dyn();
|
72
|
+
py_result
|
73
|
+
}
|
74
|
+
|
75
|
+
Ok(())
|
76
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from .arrays import BasicArray
|
2
|
+
from .battery import BasicBattery
|
3
|
+
from .lvs import BasicLVS
|
4
|
+
from .motor import BasicMotor
|
5
|
+
from .regen import BasicRegen
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"BasicArray",
|
9
|
+
"BasicBattery",
|
10
|
+
"BasicLVS",
|
11
|
+
"BasicMotor",
|
12
|
+
"BasicRegen"
|
13
|
+
]
|
File without changes
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from physics.models.arrays.base_array import BaseArray
|
2
|
+
|
3
|
+
|
4
|
+
class BasicArray(BaseArray):
|
5
|
+
|
6
|
+
# incident_sunlight:
|
7
|
+
def __init__(self, panel_efficiency, panel_size):
|
8
|
+
super().__init__()
|
9
|
+
|
10
|
+
# solar cell efficiency
|
11
|
+
self.panel_efficiency = panel_efficiency
|
12
|
+
|
13
|
+
# solar panel size in m^2
|
14
|
+
self.panel_size = panel_size
|
15
|
+
|
16
|
+
# please do not use this.
|
17
|
+
self.solar_irradiance = 1200
|
18
|
+
|
19
|
+
def calculate_produced_energy(self, solar_irradiance, tick):
|
20
|
+
"""
|
21
|
+
|
22
|
+
Returns a numpy array with the energy produced by the solar panels across
|
23
|
+
each the length of each tick
|
24
|
+
|
25
|
+
:param np.ndarray solar_irradiance: (float[N]) the global horizontal irradiance(GHI) at
|
26
|
+
each moment experienced by the vehicle, in W/m2
|
27
|
+
:param float tick: (float) the duration of a time step in seconds
|
28
|
+
|
29
|
+
:returns: (float[N]) array of energy produced by the solar panels on the vehicle
|
30
|
+
in Joules
|
31
|
+
:rtype: np.ndarray
|
32
|
+
|
33
|
+
"""
|
34
|
+
return solar_irradiance * self.panel_efficiency * self.panel_size * tick
|
35
|
+
|
36
|
+
def __str__(self):
|
37
|
+
return(f"BasicArray: incident_sunlight: {self.solar_irradiance}W/m^2\n"
|
38
|
+
f"BasicArray: panel_size: {self.panel_size}m^2\n"
|
39
|
+
f"BasicArray: panel_efficiency: {self.panel_efficiency * 100}%\n")
|
physics/models/arrays.rs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mod arrays;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
|
3
|
+
|
4
|
+
class BaseBattery(ABC):
|
5
|
+
def __init__(self, initial_energy, max_current_capacity, max_energy_capacity,
|
6
|
+
max_voltage, min_voltage, voltage, state_of_charge):
|
7
|
+
super().__init__()
|
8
|
+
|
9
|
+
# Constants
|
10
|
+
self.max_current_capacity = max_current_capacity # max capacity of battery (Ah)
|
11
|
+
self.max_energy_capacity = max_energy_capacity # max energy inside battery (Wh)
|
12
|
+
|
13
|
+
self.max_voltage = max_voltage # maximum battery voltage (V)
|
14
|
+
self.min_voltage = min_voltage # battery cut-off voltage (V)
|
15
|
+
|
16
|
+
# Variables
|
17
|
+
self.stored_energy = initial_energy # energy inside battery (Wh)
|
18
|
+
self.state_of_charge = state_of_charge # battery state of charge
|
19
|
+
self.voltage = voltage # terminal voltage of the battery (V)
|
20
|
+
|
21
|
+
if self.state_of_charge > 0:
|
22
|
+
self.empty = False # 1 if battery is empty, 0 if battery is not empty
|
23
|
+
else:
|
24
|
+
self.empty = True
|
25
|
+
|
26
|
+
def __str__(self):
|
27
|
+
return (f"Battery stored energy: {self.stored_energy:.2f}Wh\n"
|
28
|
+
f"Battery state of charge: {self.state_of_charge * 100:.1f}%\n"
|
29
|
+
f"Battery voltage: {self.voltage:.2f}V\n")
|
@@ -0,0 +1,141 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from numpy.polynomial import Polynomial
|
3
|
+
|
4
|
+
from physics.models.battery.base_battery import BaseBattery
|
5
|
+
|
6
|
+
|
7
|
+
class BasicBattery(BaseBattery):
|
8
|
+
"""
|
9
|
+
Class representing the DayBreak battery pack.
|
10
|
+
|
11
|
+
Attributes:
|
12
|
+
max_voltage (float): maximum voltage of the DayBreak battery pack (V)
|
13
|
+
min_voltage (float): minimum voltage of the DayBreak battery pack (V)
|
14
|
+
max_current_capacity (float): nominal capacity of the DayBreak battery pack (Ah)
|
15
|
+
max_energy_capacity (float): nominal energy capacity of the DayBreak battery pack (Wh)
|
16
|
+
|
17
|
+
state_of_charge (float): instantaneous battery state-of-charge (0.00 - 1.00)
|
18
|
+
discharge_capacity (float): instantaneous amount of charge extracted from battery (Ah)
|
19
|
+
voltage (float): instantaneous voltage of the battery (V)
|
20
|
+
stored_energy (float): instantaneous energy stored in the battery (Wh)
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, state_of_charge, max_voltage, min_voltage, max_current_capacity, max_energy_capacity):
|
24
|
+
"""
|
25
|
+
|
26
|
+
Constructor for BasicBattery class.
|
27
|
+
|
28
|
+
:param float state_of_charge: initial battery state of charge
|
29
|
+
|
30
|
+
"""
|
31
|
+
|
32
|
+
# ----- DayBreak battery constants -----
|
33
|
+
|
34
|
+
self.max_voltage = max_voltage
|
35
|
+
self.min_voltage = min_voltage
|
36
|
+
self.max_current_capacity = max_current_capacity
|
37
|
+
self.max_energy_capacity = max_energy_capacity
|
38
|
+
|
39
|
+
# ----- DayBreak battery equations -----
|
40
|
+
|
41
|
+
self.calculate_voltage_from_discharge_capacity = calculate_voltage_from_discharge_capacity()
|
42
|
+
|
43
|
+
self.calculate_energy_from_discharge_capacity = calculate_energy_from_discharge_capacity()
|
44
|
+
|
45
|
+
self.calculate_soc_from_discharge_capacity = calculate_soc_from_discharge_capacity(self.max_current_capacity)
|
46
|
+
|
47
|
+
self.calculate_discharge_capacity_from_soc = calculate_discharge_capacity_from_soc(self.max_current_capacity)
|
48
|
+
|
49
|
+
self.calculate_discharge_capacity_from_energy = calculate_discharge_capacity_from_energy()
|
50
|
+
|
51
|
+
# ----- DayBreak battery variables -----
|
52
|
+
|
53
|
+
self.state_of_charge = state_of_charge
|
54
|
+
|
55
|
+
# SOC -> discharge_capacity
|
56
|
+
self.discharge_capacity = self.calculate_discharge_capacity_from_soc(self.state_of_charge)
|
57
|
+
|
58
|
+
# discharge_capacity -> voltage
|
59
|
+
self.voltage = self.calculate_voltage_from_discharge_capacity(self.discharge_capacity)
|
60
|
+
|
61
|
+
# discharge_capacity -> energy
|
62
|
+
self.stored_energy = self.max_energy_capacity - self.calculate_energy_from_discharge_capacity(
|
63
|
+
self.discharge_capacity)
|
64
|
+
|
65
|
+
# ----- DayBreak battery initialisation -----
|
66
|
+
|
67
|
+
super().__init__(self.stored_energy, self.max_current_capacity, self.max_energy_capacity,
|
68
|
+
self.max_voltage, self.min_voltage, self.voltage, self.state_of_charge)
|
69
|
+
|
70
|
+
def update_array(self, cumulative_energy_array):
|
71
|
+
"""
|
72
|
+
Performs energy calculations with NumPy arrays
|
73
|
+
|
74
|
+
:param cumulative_energy_array: a NumPy array containing the cumulative energy changes at each time step
|
75
|
+
experienced by the battery
|
76
|
+
|
77
|
+
:return: soc_array – a NumPy array containing the battery state of charge at each time step
|
78
|
+
|
79
|
+
:return: voltage_array – a NumPy array containing the voltage of the battery at each time step
|
80
|
+
|
81
|
+
:return: stored_energy_array– a NumPy array containing the energy stored in the battery at each time step
|
82
|
+
|
83
|
+
"""
|
84
|
+
|
85
|
+
stored_energy_array = np.full_like(cumulative_energy_array, fill_value=self.stored_energy)
|
86
|
+
stored_energy_array += cumulative_energy_array / 3600
|
87
|
+
stored_energy_array = np.clip(stored_energy_array, a_min=0, a_max=self.max_energy_capacity)
|
88
|
+
|
89
|
+
energy_discharged_array = np.full_like(cumulative_energy_array, fill_value=self.max_energy_capacity) - \
|
90
|
+
stored_energy_array
|
91
|
+
|
92
|
+
discharge_capacity_array = self.calculate_discharge_capacity_from_energy(energy_discharged_array)
|
93
|
+
|
94
|
+
soc_array = self.calculate_soc_from_discharge_capacity(discharge_capacity_array)
|
95
|
+
voltage_array = self.calculate_voltage_from_discharge_capacity(discharge_capacity_array)
|
96
|
+
|
97
|
+
return soc_array, voltage_array, stored_energy_array
|
98
|
+
|
99
|
+
def get_raw_soc(self, cumulative_energy_array):
|
100
|
+
"""
|
101
|
+
|
102
|
+
Return the not truncated (SOC is allowed to go above 100% and below 0%) state of charge.
|
103
|
+
|
104
|
+
:param np.ndarray cumulative_energy_array: a NumPy array containing the cumulative energy changes at each time step
|
105
|
+
experienced by the battery
|
106
|
+
|
107
|
+
:return: a NumPy array containing the battery state of charge at each time step
|
108
|
+
:rtype: np.ndarray
|
109
|
+
|
110
|
+
"""
|
111
|
+
|
112
|
+
stored_energy_array = np.full_like(cumulative_energy_array, fill_value=self.stored_energy)
|
113
|
+
stored_energy_array += cumulative_energy_array / 3600
|
114
|
+
|
115
|
+
energy_discharged_array = np.full_like(cumulative_energy_array, fill_value=self.max_energy_capacity) - stored_energy_array
|
116
|
+
|
117
|
+
discharge_capacity_array = self.calculate_discharge_capacity_from_energy(energy_discharged_array)
|
118
|
+
|
119
|
+
soc_array = self.calculate_soc_from_discharge_capacity(discharge_capacity_array)
|
120
|
+
|
121
|
+
return soc_array
|
122
|
+
|
123
|
+
|
124
|
+
def calculate_voltage_from_discharge_capacity():
|
125
|
+
return Polynomial([117.6, -0.858896]) # -0.8589x + 117.6
|
126
|
+
|
127
|
+
|
128
|
+
def calculate_energy_from_discharge_capacity():
|
129
|
+
return Polynomial([0, 117.6, -0.429448]) # -0.4294x^2 + 117.6x
|
130
|
+
|
131
|
+
|
132
|
+
def calculate_soc_from_discharge_capacity(max_current_capacity):
|
133
|
+
return Polynomial([1, -1 / max_current_capacity])
|
134
|
+
|
135
|
+
|
136
|
+
def calculate_discharge_capacity_from_soc(max_current_capacity):
|
137
|
+
return Polynomial([max_current_capacity, -max_current_capacity])
|
138
|
+
|
139
|
+
|
140
|
+
def calculate_discharge_capacity_from_energy():
|
141
|
+
return lambda x: 136.92 - np.sqrt(18747.06027 - 2.32857 * x)
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
mod battery;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Radius of the Earth (m)
|
2
|
+
EARTH_RADIUS = 6371009
|
3
|
+
|
4
|
+
# Acceleration caused by gravity (m/s^2)
|
5
|
+
ACCELERATION_G = 9.81
|
6
|
+
|
7
|
+
# Density of Air at 15C and 101kPa (kg/m^3)
|
8
|
+
AIR_DENSITY = 1.225
|
9
|
+
|
10
|
+
# Maximum number of waypoints that can be given to generate route data
|
11
|
+
MAX_WAYPOINTS = 10
|
12
|
+
|
13
|
+
# Solar Irradiance (W/m^2)
|
14
|
+
SOLAR_IRRADIANCE = 1353
|
15
|
+
|
16
|
+
# As we currently have a limited number of API calls(60) every minute with the
|
17
|
+
# current Weather API, we must shrink the dataset significantly. As the
|
18
|
+
# OpenWeatherAPI models have a resolution of between 2.5 - 70 km, we will
|
19
|
+
# go for a resolution of 25km. Assuming we travel at 100km/h for 12 hours,
|
20
|
+
# 1200 kilometres/25 = 48 API calls
|
21
|
+
# As the Google Maps API has a resolution of around 40m between points,
|
22
|
+
# for ASC, we must cull at 625:1 (because 25,000m / 40m = 625)
|
23
|
+
REDUCTION_FACTOR = 625
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from physics.models.lvs.base_lvs import BaseLVS
|
2
|
+
|
3
|
+
|
4
|
+
class BasicLVS(BaseLVS):
|
5
|
+
|
6
|
+
def __init__(self, consumed_energy, lvs_current, lvs_voltage):
|
7
|
+
super().__init__(consumed_energy)
|
8
|
+
self.lvs_current = lvs_current
|
9
|
+
self.lvs_voltage = lvs_voltage
|
10
|
+
|
11
|
+
def get_consumed_energy(self, tick):
|
12
|
+
"""
|
13
|
+
Get the energy consumption of the Low Voltage System (current * voltage * time)
|
14
|
+
|
15
|
+
:param tick - (int) tick time passed
|
16
|
+
:returns: consumed_energy - (number) value of energy consumed
|
17
|
+
"""
|
18
|
+
return self.lvs_current * self.lvs_voltage * tick
|
File without changes
|
physics/models/lvs.rs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mod lvs;
|