ubc-solar-physics 1.0.3__cp310-cp310-win32.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.cp310-win32.pyd +0 -0
- physics/__init__.py +12 -0
- physics/__version__.py +16 -0
- physics/environment/__init__.py +22 -0
- physics/environment/environment.rs +2 -0
- physics/environment/gis/__init__.py +7 -0
- physics/environment/gis/base_gis.py +24 -0
- physics/environment/gis/gis.py +337 -0
- physics/environment/gis/gis.rs +25 -0
- physics/environment/gis.rs +1 -0
- physics/environment/meteorology/__init__.py +3 -0
- physics/environment/meteorology/base_meteorology.py +69 -0
- physics/environment/meteorology/clouded_meteorology.py +601 -0
- physics/environment/meteorology/irradiant_meteorology.py +106 -0
- physics/environment/meteorology/meteorology.rs +138 -0
- physics/environment/meteorology.rs +1 -0
- physics/environment/race.py +89 -0
- physics/environment.rs +2 -0
- physics/lib.rs +98 -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 +174 -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-1.0.3.dist-info/LICENSE +21 -0
- ubc_solar_physics-1.0.3.dist-info/METADATA +136 -0
- ubc_solar_physics-1.0.3.dist-info/RECORD +52 -0
- ubc_solar_physics-1.0.3.dist-info/WHEEL +5 -0
- ubc_solar_physics-1.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
from physics.environment.meteorology.base_meteorology import BaseMeteorology
|
2
|
+
from physics.environment.gis.gis import calculate_path_distances
|
3
|
+
import numpy as np
|
4
|
+
from physics.environment.race import Race
|
5
|
+
import core
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
|
9
|
+
class IrradiantMeteorology(BaseMeteorology):
|
10
|
+
"""
|
11
|
+
IrradiantMeteorology encapsulates meteorological data that includes
|
12
|
+
solar irradiance data, but not cloud cover.
|
13
|
+
|
14
|
+
"""
|
15
|
+
def __init__(self, race, weather_forecasts):
|
16
|
+
self._race = race
|
17
|
+
self._raw_weather_data = weather_forecasts
|
18
|
+
|
19
|
+
self._time_dt: Optional[np.ndarray] = None
|
20
|
+
self._ghi: Optional[np.ndarray] = None
|
21
|
+
self._longitude: Optional[np.ndarray] = None
|
22
|
+
self._latitude: Optional[np.ndarray] = None
|
23
|
+
|
24
|
+
self.last_updated_time = self._raw_weather_data[0, 0, 0]
|
25
|
+
|
26
|
+
super().__init__()
|
27
|
+
|
28
|
+
def spatially_localize(self, cumulative_distances: np.ndarray) -> None:
|
29
|
+
"""
|
30
|
+
|
31
|
+
:param np.ndarray cumulative_distances: NumPy Array representing cumulative distances theoretically achievable for a given input speed array
|
32
|
+
"""
|
33
|
+
# if racing FSGP, there is no need for distance calculations. We will return only the origin coordinate
|
34
|
+
# This characterizes the weather at every point along the FSGP tracks
|
35
|
+
# with the weather at a single coordinate on the track, which is great for reducing the API calls and is a
|
36
|
+
# reasonable assumption to make for FSGP only.
|
37
|
+
if self._race.race_type == Race.FSGP:
|
38
|
+
self._weather_indices = np.zeros_like(cumulative_distances, dtype=int)
|
39
|
+
return
|
40
|
+
|
41
|
+
# a list of all the coordinates that we have weather data for
|
42
|
+
weather_coords = self._raw_weather_data[:, 0, 1:3]
|
43
|
+
|
44
|
+
# distances between all the coordinates that we have weather data for
|
45
|
+
weather_path_distances = calculate_path_distances(weather_coords)
|
46
|
+
cumulative_weather_path_distances = np.cumsum(weather_path_distances)
|
47
|
+
|
48
|
+
# makes every even-index element negative, this allows the use of np.diff() to calculate the sum of consecutive
|
49
|
+
# elements
|
50
|
+
cumulative_weather_path_distances[::2] *= -1
|
51
|
+
|
52
|
+
# contains the average distance between two consecutive elements in the cumulative_weather_path_distances array
|
53
|
+
average_distances = np.abs(np.diff(cumulative_weather_path_distances) / 2)
|
54
|
+
|
55
|
+
self._weather_indices = core.closest_weather_indices_loop(cumulative_distances, average_distances)
|
56
|
+
|
57
|
+
def temporally_localize(self, unix_timestamps, start_time, tick) -> None:
|
58
|
+
"""
|
59
|
+
|
60
|
+
Takes in an array of indices of the weather_forecast array, and an array of timestamps. Uses those to figure out
|
61
|
+
what the weather forecast is at each time step being simulated.
|
62
|
+
|
63
|
+
we only have weather at discrete timestamps. The car however can be in any timestamp in
|
64
|
+
between. Therefore, we must be able to choose the weather timestamp that is closest to the one that the car is in
|
65
|
+
so that we can more accurately determine the weather experienced by the car at that timestamp.
|
66
|
+
|
67
|
+
For example, imagine the car is at some coordinate (x,y) at timestamp 100. Imagine we know the weather forecast
|
68
|
+
at (x,y) for five different timestamps: 0, 30, 60, 90, and 120. Which weather forecast should we
|
69
|
+
choose? Clearly, we should choose the weather forecast at 90 since it is the closest to 100. That's what the
|
70
|
+
below code is accomplishing.
|
71
|
+
|
72
|
+
:param np.ndarray unix_timestamps: (int[N]) unix timestamps of the vehicle's journey
|
73
|
+
:param int start_time: time since the start of the race that simulation is beginning
|
74
|
+
:param int tick: length of a tick in seconds
|
75
|
+
:returns: a SolcastEnvironment object with time_dt, latitude, longitude, wind_speed, wind_direction, and ghi.
|
76
|
+
:rtype: SolcastEnvironment
|
77
|
+
"""
|
78
|
+
forecasts_array = core.weather_in_time(unix_timestamps.astype(np.int64), self._weather_indices.astype(np.int64),
|
79
|
+
self._raw_weather_data, 0)
|
80
|
+
|
81
|
+
self._time_dt = forecasts_array[:, 0]
|
82
|
+
self._latitude = forecasts_array[:, 1]
|
83
|
+
self._longitude = forecasts_array[:, 2]
|
84
|
+
self._wind_speed = forecasts_array[:, 3]
|
85
|
+
self._wind_direction = forecasts_array[:, 4]
|
86
|
+
self._solar_irradiance = forecasts_array[:, 5]
|
87
|
+
|
88
|
+
def calculate_solar_irradiances(self, coords, time_zones, local_times, elevations):
|
89
|
+
"""
|
90
|
+
Calculates the Global Horizontal Irradiance from the Sun, relative to a location
|
91
|
+
on the Earth, for arrays of coordinates, times, elevations and weathers
|
92
|
+
https://www.pveducation.org/pvcdrom/properties-of-sunlight/calculation-of-solar-insolation
|
93
|
+
Note: If local_times and time_zones are both unadjusted for Daylight Savings, the
|
94
|
+
calculation will end up just the same
|
95
|
+
|
96
|
+
:param np.ndarray coords: (float[N][lat, lng]) array of latitudes and longitudes
|
97
|
+
:param np.ndarray time_zones: (int[N]) time zones at different locations in seconds relative to UTC
|
98
|
+
:param np.ndarray local_times: (int[N]) unix time that the vehicle will be at each location. (Adjusted for Daylight Savings)
|
99
|
+
:param np.ndarray elevations: (float[N]) elevation from sea level in m
|
100
|
+
:returns: (float[N]) Global Horizontal Irradiance in W/m2
|
101
|
+
:rtype: np.ndarray
|
102
|
+
|
103
|
+
"""
|
104
|
+
return self.solar_irradiance
|
105
|
+
|
106
|
+
|
@@ -0,0 +1,138 @@
|
|
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
|
+
}
|
117
|
+
|
118
|
+
pub fn rust_calculate_array_ghi_times<'a>(
|
119
|
+
local_times: ArrayViewD<'_, u64>,
|
120
|
+
) -> (Vec<f64>, Vec<f64>) {
|
121
|
+
let mut datetimes: Vec<_> = Vec::with_capacity(local_times.len());
|
122
|
+
|
123
|
+
for &unix_time_stamp in local_times {
|
124
|
+
let datetime = NaiveDateTime::from_timestamp_opt(unix_time_stamp as i64, 0).unwrap();
|
125
|
+
datetimes.push(datetime);
|
126
|
+
}
|
127
|
+
|
128
|
+
let day_of_year_out: Vec<f64> = datetimes
|
129
|
+
.iter()
|
130
|
+
.map(|&date| date.date().ordinal() as f64)
|
131
|
+
.collect();
|
132
|
+
let local_time_out: Vec<f64> = datetimes
|
133
|
+
.iter()
|
134
|
+
.map(|&date| date.time().num_seconds_from_midnight() as f64 / 3600.0)
|
135
|
+
.collect();
|
136
|
+
|
137
|
+
(day_of_year_out, local_time_out)
|
138
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
pub mod meteorology;
|
@@ -0,0 +1,89 @@
|
|
1
|
+
"""
|
2
|
+
This class collects the constants that are related to a specific competition.
|
3
|
+
"""
|
4
|
+
import pathlib
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
import pickle
|
8
|
+
import enum
|
9
|
+
import json
|
10
|
+
import os
|
11
|
+
|
12
|
+
|
13
|
+
class Race:
|
14
|
+
class RaceType(enum.Enum):
|
15
|
+
ASC = "ASC"
|
16
|
+
FSGP = "FSGP"
|
17
|
+
|
18
|
+
def __str__(self):
|
19
|
+
match self.value:
|
20
|
+
case "ASC":
|
21
|
+
return "ASC"
|
22
|
+
case "FSGP":
|
23
|
+
return "FSGP"
|
24
|
+
|
25
|
+
def __reduce__(self):
|
26
|
+
return self.__class__, (self.name,)
|
27
|
+
|
28
|
+
def __contains__(self, item):
|
29
|
+
return item == "ASC" or item == "FSGP"
|
30
|
+
|
31
|
+
def __repr__(self):
|
32
|
+
return str(self)
|
33
|
+
|
34
|
+
ASC = RaceType.ASC
|
35
|
+
FSGP = RaceType.FSGP
|
36
|
+
|
37
|
+
def __init__(self, race_type: RaceType, race_constants: dict):
|
38
|
+
self.race_type = race_type
|
39
|
+
|
40
|
+
self.days = race_constants["days"]
|
41
|
+
self.tiling = race_constants["tiling"]
|
42
|
+
self.date = (race_constants["start_year"], race_constants["start_month"], race_constants["start_day"])
|
43
|
+
|
44
|
+
self.race_duration = len(self.days) * 24 * 60 * 60 # Duration (s)
|
45
|
+
self.driving_boolean = self.make_time_boolean("driving")
|
46
|
+
self.charging_boolean = self.make_time_boolean("charging")
|
47
|
+
|
48
|
+
def __str__(self):
|
49
|
+
return str(self.race_type)
|
50
|
+
|
51
|
+
def write(self, race_directory: pathlib.Path):
|
52
|
+
with open(race_directory / f"{str(self.race_type)}.pkl", 'wb') as outfile:
|
53
|
+
pickle.dump(self, outfile, protocol=pickle.HIGHEST_PROTOCOL)
|
54
|
+
|
55
|
+
def make_time_boolean(self, boolean_type: str):
|
56
|
+
boolean: np.ndarray = np.empty(self.race_duration, dtype=np.int8)
|
57
|
+
DAY_LENGTH: int = 24 * 60 * 60 # Length of a day in seconds
|
58
|
+
|
59
|
+
for tick in range(len(boolean)):
|
60
|
+
day: int = tick // DAY_LENGTH # Integer division to determine how many days have passed
|
61
|
+
time_of_day = tick % DAY_LENGTH # Time of day in seconds where 0 is midnight and 43200 is noon
|
62
|
+
begin, end = self.days[str(day)][boolean_type]
|
63
|
+
|
64
|
+
# If the time of day is between the beginning and end, then the boolean is True, else False
|
65
|
+
boolean[tick] = begin <= time_of_day < end
|
66
|
+
|
67
|
+
return boolean
|
68
|
+
|
69
|
+
|
70
|
+
def load_race(race_type: Race.RaceType, race_directory: pathlib.Path) -> Race:
|
71
|
+
with open(race_directory / f"{str(race_type)}.pkl", 'rb') as infile:
|
72
|
+
return pickle.load(infile)
|
73
|
+
|
74
|
+
|
75
|
+
def compile_races(config_directory: pathlib.Path, race_directory: pathlib.Path):
|
76
|
+
fsgp_config_path = os.path.join(config_directory, f"settings_FSGP.json")
|
77
|
+
asc_config_path = os.path.join(config_directory, f"settings_ASC.json")
|
78
|
+
|
79
|
+
with open(fsgp_config_path) as f:
|
80
|
+
fsgp_race_constants = json.load(f)
|
81
|
+
|
82
|
+
with open(asc_config_path) as f:
|
83
|
+
asc_race_constants = json.load(f)
|
84
|
+
|
85
|
+
fsgp = Race(Race.FSGP, fsgp_race_constants)
|
86
|
+
fsgp.write(race_directory)
|
87
|
+
|
88
|
+
asc = Race(Race.ASC, asc_race_constants)
|
89
|
+
asc.write(race_directory)
|
physics/environment.rs
ADDED
physics/lib.rs
ADDED
@@ -0,0 +1,98 @@
|
|
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::meteorology::meteorology::{rust_calculate_array_ghi_times, rust_closest_weather_indices_loop, rust_weather_in_time, rust_closest_timestamp_indices};
|
11
|
+
|
12
|
+
fn constrain_speeds(speed_limits: ArrayViewD<f64>, speeds: ArrayViewD<f64>, tick: i32) -> Vec<f64> {
|
13
|
+
let mut distance: f64 = 0.0;
|
14
|
+
static KMH_TO_MS: f64 = 1.0 / 3.6;
|
15
|
+
|
16
|
+
let ret: Vec<f64> = speeds.iter().map(| speed: &f64 | {
|
17
|
+
let speed_limit: f64 = speed_limits[distance.floor() as usize];
|
18
|
+
let vehicle_speed: f64 =f64::min(speed_limit, *speed);
|
19
|
+
distance += vehicle_speed * KMH_TO_MS * tick as f64;
|
20
|
+
vehicle_speed
|
21
|
+
}).collect();
|
22
|
+
|
23
|
+
return ret
|
24
|
+
}
|
25
|
+
|
26
|
+
/// A Python module implemented in Rust. The name of this function is the Rust module name!
|
27
|
+
#[pymodule]
|
28
|
+
#[pyo3(name = "core")]
|
29
|
+
fn rust_simulation(_py: Python, m: &PyModule) -> PyResult<()> {
|
30
|
+
#[pyfn(m)]
|
31
|
+
#[pyo3(name = "constrain_speeds")]
|
32
|
+
fn constrain_speeds_py<'py>(py: Python<'py>, x: PyReadwriteArrayDyn<'py, f64>, y: PyReadwriteArrayDyn<'py, f64>, z: i32) -> &'py PyArrayDyn<f64> {
|
33
|
+
let x = x.as_array();
|
34
|
+
let y = y.as_array();
|
35
|
+
let result = constrain_speeds(x, y, z);
|
36
|
+
return PyArray::from_vec(py, result).to_dyn();
|
37
|
+
}
|
38
|
+
|
39
|
+
#[pyfn(m)]
|
40
|
+
#[pyo3(name = "calculate_array_ghi_times")]
|
41
|
+
fn calculate_array_ghi_times<'py>(
|
42
|
+
py: Python<'py>,
|
43
|
+
python_local_times: PyReadwriteArrayDyn<'py, u64>,
|
44
|
+
) -> (&'py PyArrayDyn<f64>, &'py PyArrayDyn<f64>) {
|
45
|
+
let local_times = python_local_times.as_array();
|
46
|
+
let (day_of_year_out, local_time_out) = rust_calculate_array_ghi_times(local_times);
|
47
|
+
let py_day_out = PyArray::from_vec(py, day_of_year_out).to_dyn();
|
48
|
+
let py_time_out = PyArray::from_vec(py, local_time_out).to_dyn();
|
49
|
+
(py_day_out, py_time_out)
|
50
|
+
}
|
51
|
+
|
52
|
+
#[pyfn(m)]
|
53
|
+
#[pyo3(name = "closest_gis_indices_loop")]
|
54
|
+
fn closest_gis_indices_loop<'py>(
|
55
|
+
py: Python<'py>,
|
56
|
+
python_cumulative_distances: PyReadwriteArrayDyn<'py, f64>,
|
57
|
+
python_average_distances: PyReadwriteArrayDyn<'py, f64>,
|
58
|
+
) -> &'py PyArrayDyn<i64> {
|
59
|
+
let average_distances = python_average_distances.as_array();
|
60
|
+
let cumulative_distances = python_cumulative_distances.as_array();
|
61
|
+
let result = rust_closest_gis_indices_loop(cumulative_distances, average_distances);
|
62
|
+
let py_result = PyArray::from_vec(py, result).to_dyn();
|
63
|
+
py_result
|
64
|
+
}
|
65
|
+
|
66
|
+
#[pyfn(m)]
|
67
|
+
#[pyo3(name = "closest_weather_indices_loop")]
|
68
|
+
fn closest_weather_indices_loop<'py>(
|
69
|
+
py: Python<'py>,
|
70
|
+
python_cumulative_distances: PyReadwriteArrayDyn<'py, f64>,
|
71
|
+
python_average_distances: PyReadwriteArrayDyn<'py, f64>,
|
72
|
+
) -> &'py PyArrayDyn<i64> {
|
73
|
+
let average_distances = python_average_distances.as_array();
|
74
|
+
let cumulative_distances = python_cumulative_distances.as_array();
|
75
|
+
let result = rust_closest_weather_indices_loop(cumulative_distances, average_distances);
|
76
|
+
let py_result = PyArray::from_vec(py, result).to_dyn();
|
77
|
+
py_result
|
78
|
+
}
|
79
|
+
|
80
|
+
#[pyfn(m)]
|
81
|
+
#[pyo3(name = "weather_in_time")]
|
82
|
+
fn weather_in_time<'py>(
|
83
|
+
py: Python<'py>,
|
84
|
+
python_unix_timestamps: PyReadwriteArrayDyn<'py, i64>,
|
85
|
+
python_indices: PyReadwriteArrayDyn<'py, i64>,
|
86
|
+
python_weather_forecast: PyReadwriteArrayDyn<'py, f64>,
|
87
|
+
index: u8
|
88
|
+
) -> &'py PyArrayDyn<f64> {
|
89
|
+
let unix_timestamps = python_unix_timestamps.as_array();
|
90
|
+
let indices = python_indices.as_array();
|
91
|
+
let weather_forecast = python_weather_forecast.as_array();
|
92
|
+
let mut result = rust_weather_in_time(unix_timestamps, indices, weather_forecast, index);
|
93
|
+
let py_result = PyArray::from_array(py, &mut result).to_dyn();
|
94
|
+
py_result
|
95
|
+
}
|
96
|
+
|
97
|
+
Ok(())
|
98
|
+
}
|
@@ -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")
|