Skip to content

Commit b458c55

Browse files
authored
Directory-specific documentation update (#100)
* Displays documentation * Solar documentation * SOC quick update * Routemodel update, ? around speedlimits * Optimization documentation * Auxloss documentation and refactor imports * Motor torque documentation * Dynamics documentation * Add other files from data_retrieval
1 parent f74c653 commit b458c55

File tree

16 files changed

+211
-120
lines changed

16 files changed

+211
-120
lines changed

auxloss/README.md

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
1-
A directory for the car's model for all the auxiliary energy losses of the car, as well as data on the subject
2-
3-
## AuxPowerConsumption
4-
AuxPowerConsumption class contains `__init__` and two methods, `calculate_instantaneous_power` and `calculate_energy_usage`.
1+
# Auxloss
52

6-
### `__init__(self, power_budget)`
7-
+ Accepts a csv file (power budget).
8-
+ Reads and stores information about typical and max power usage, etc. of the various components into a dictionary called `power_consumptions`. Keys are component names, values are dictionaries with column names as values. Any empty keys are discarded.
9-
+ Headers expected: "Component", "Max Power (W)", "Typical Power (W)"
10-
+ Assumes Front and Rear Power infomration stored first. It skips past the first line (which is expected to contain title Front Power Distribution Board) to get the column headers from second line. Reads up until AUX Current Draw and AUX Current Protection title is reached.
3+
A directory for the car's model for all the auxiliary energy losses of the car, as well as data on the subject
114

12-
### `calculate_instantaneous_power(self, components_used)`
13-
+ Accepts a dictionary with a single key, "components", and a list of strings and tuples.
14-
+ Strings are component names. If not in tuple, assumes it is using typical power.
15-
+ Tuples should contain component name and percentage of max. power used: eg. `("Fan", 30)`. Assumes that percentage is provided, not in decimal form.
16-
+ Returns sum of power usage by each component
5+
## In this directory
176

18-
### `calculate_energy_usage(self, components_used, time_in_seconds)`
19-
+ Accepts a dictionary and time (s) that power is used. See calculate_instantaneous_power above for more information about dictionary.
20-
+ Returns energy = power * time
7+
+ `tests` directory
8+
+ `aux_power_consumption.py`: Class for auxiliary losses, based on power budget CSV
9+
+ `auxsystem.py`: Class for initial version of auxiliary loss, not based on power budget
10+
+ `MSXIV Power Budget.csv`: From hardware, CSV representing non-motor energy usage

auxloss/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

auxloss/aux_power_consumption.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
class AuxPowerConsumption:
44
def __init__(self, power_budget):
5+
"""
6+
Initializes AuxPowerConsumption object based on given power budget.
7+
8+
Reads and stores information about typical and max power usage, etc. of the various components into a dictionary called `power_consumptions`. Keys are component names, values are dictionaries with column names as values. Any empty keys are discarded.
9+
10+
:param power_budget: Path to power budget CSV file.
11+
Headers expected: "Component", "Max Power (W)", "Typical Power (W)"
12+
"""
13+
514
self.power_consumptions = {}
615

716
with open(power_budget, "r") as file:
@@ -23,15 +32,43 @@ def __init__(self, power_budget):
2332
self.power_consumptions[row["Component"]] = {k:v for k,v in row.items()}
2433

2534
def calculate_instantaneous_power(self, components_used):
35+
"""
36+
Calculates instantaneous power from auxiliary losses in power budget
37+
38+
:param components_used: dictionary with a single key, "components", and a list of components to consider. This list includes strings and/or (string, float) tuples, where string is component name and optional float specifies percentage of typical power used (30% -> 30, not decimal). If float not specified, assumes typical power. Any components not recognized are ignored.
39+
Example:
40+
{
41+
"components": [("Horn", 100), ("Fan", 50), "Telemetry"]
42+
}
43+
44+
:return: float, sum of power usage by each component
45+
"""
46+
2647
current_power = 0
2748
for component in components_used["components"]:
2849
if type(component) is tuple:
50+
if component[0] not in self.power_consumptions:
51+
print("{} is not a recognized component.".format(component[0]))
52+
continue
53+
2954
# assumes that percentage is provided (%, not decimal)
3055
current_power += float(self.power_consumptions[component[0]]["Max Power (W)"]) * component[1] * 0.01
3156
else:
57+
if component not in self.power_consumptions:
58+
print("{} is not a recognized component.".format(component))
59+
continue
60+
3261
current_power += float(self.power_consumptions[component]["Typical Power (W)"])
3362

3463
return current_power
3564

3665
def calculate_energy_usage(self, components, time_in_seconds):
37-
return self.calculate_instantaneous_power(components) * time_in_seconds
66+
"""
67+
Calculates energy usage from instantaneous auxiliary losses in power budget
68+
69+
:param components: dictionary, see `calculate_instantaneous_power(self, components_used)`
70+
:param time_in_seconds: float, time (s) that power is used
71+
72+
:return: float, energy = power * time
73+
"""
74+
return self.calculate_instantaneous_power(components) * time_in_seconds

auxloss/tests/test_aux_power_consumption.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import sys
22
import os.path
3-
sys.path.append(os.path.join(sys.path[0], '../../'))
3+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
4+
45
import csv
56
import pytest
67
import mock
7-
import auxloss
88

9-
budget_file = os.path.join(sys.path[0], '../', 'MSXIV Power Budget.csv')
9+
from aux_power_consumption import AuxPowerConsumption
10+
11+
budget_file = os.path.join(os.path.dirname(__file__), '..', 'MSXIV Power Budget.csv')
12+
1013
global auxpc
11-
auxpc = auxloss.aux_power_consumption.AuxPowerConsumption(budget_file)
14+
auxpc = AuxPowerConsumption(budget_file)
1215

1316
def test_expected_components_stored_as_keys():
1417
with open(budget_file, "r") as file:
@@ -28,15 +31,14 @@ def test_expected_components_stored_as_keys():
2831
assert(component in auxpc.power_consumptions)
2932

3033
for key in auxpc.power_consumptions:
31-
print(key)
3234
assert(key in column_names)
3335

3436
def test_expected_headers_stored():
35-
for key, value in auxpc.power_consumptions.items():
36-
assert("Max Power (W)" in value and "Typical Power (W)" in value)
37+
for headers in auxpc.power_consumptions.values():
38+
assert("Max Power (W)" in headers and "Typical Power (W)" in headers)
3739

3840
def test_calculate_instantaneous_power_string_names_only():
39-
assert(auxpc.calculate_instantaneous_power({"components": ["Center Console"]}) == 4.4)
41+
assert(auxpc.calculate_instantaneous_power({"components": ['Center Console']}) == 4.4)
4042
assert(auxpc.calculate_instantaneous_power({"components": ["Horn", "Motor Interface", "Solar Master"]}) == 0 + 2.4 + 4.0)
4143

4244
def test_calculate_instantaneous_power_tuples_only():
@@ -52,16 +54,21 @@ def test_calculate_instantaneous_power_unexpected_input_format():
5254
assert(auxpc.calculate_instantaneous_power("test"))
5355
assert(auxpc.calculate_instantaneous_power({"test2": ["hello"]}))
5456

57+
def test_calculate_instantaneous_power_ignores_unknown_components():
58+
assert(auxpc.calculate_instantaneous_power({"components": [("Horn", 10), "Center Console", "Some random unknown"]}) == 6.21 + 4.4)
59+
assert(auxpc.calculate_instantaneous_power({"components": [("Horn", 100), ("Fan", 50), "Telemetry", ("Unknowns", 200)]}) == 62.1 + 3 * 0.5 + 4.5)
60+
assert(auxpc.calculate_instantaneous_power({"components": [("Unknowns", 200), "Winnie the Pooh", ("Caillou", 2)]}) == 0)
61+
5562
def test_calculate_energy_usage_zero_power():
56-
with mock.patch('auxloss.aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=0):
63+
with mock.patch('aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=0):
5764
assert(auxpc.calculate_energy_usage({"components":[]}, 10) == 0)
5865

5966
def test_calculate_energy_usage_zero_time():
60-
with mock.patch('auxloss.aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=1.0):
67+
with mock.patch('aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=1.0):
6168
assert(auxpc.calculate_energy_usage("test", 0) == 0)
6269

6370
def test_calculate_energy_usage():
64-
with mock.patch('auxloss.aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=1.0):
71+
with mock.patch('aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=1.0):
6572
assert(auxpc.calculate_energy_usage("test", 100) == 100)
66-
with mock.patch('auxloss.aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=14.5):
67-
assert(round(auxpc.calculate_energy_usage("test", 87), 3) == round(87 * 14.5, 3))
73+
with mock.patch('aux_power_consumption.AuxPowerConsumption.calculate_instantaneous_power', return_value=14.5):
74+
assert(round(auxpc.calculate_energy_usage("test", 87), 3) == round(87 * 14.5, 3))

displays/ASC2021_route.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
print(distances)
2525

2626
plt.plot([d / 1000 for d in distances], elevations)
27-
# plt.grid(b=True)
2827
plt.title("Draft ASC 2021 route")
2928
plt.xlabel("Distance travelled (km)")
3029
plt.ylabel("Current elevation")

displays/README.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
Directory for generate graphs to visualize SOC and velocity profiles.
1+
# Displays
22

3-
## SOC_velocity_graph.py
4-
Methods to generate graph of state of charge and speed against time travelled. See https://www.mdpi.com/2071-1050/9/10/1576/htm for reference for the curve generated.
3+
Directory for generate graphs that visualize information about car and race data.
54

6-
### `calculate_SOC_values(v_profile, e_profile, distance, initial_soc, min_speed=None, max_speed=None)`
7-
+ Instantiates Car object (from car_model.py) and ColoumbCounter object (from SoCEstimation.py) object to energy_used calculation and monitoring SOC levels.
8-
+ Accepts v_profile, e_profile following parameter specifications in energy_used method. distance is a list of distances that we travel each speed in v_profile. initial_soc is a decimal representing the initial battery state (ie. 1 means fully charged). Accepts min_speed and max_speed with default values of None; this specifies the parameters for Car object initialization.
9-
+ Returns array of state of charges, starting with initial_soc, and state of charge at the end of each interval of velocity.
10-
+ Find energy used between each velocity/elevation interval and add to array. Last point is energy_used maintaining the last given velocity for the given distance.
5+
## In this directory
116

12-
### `generate_SOC_graph(v_profile, e_profile, distance, initial_soc=1)`
13-
+ Same parameters as above. Default value for initial_soc is fully charged, 1.
14-
+ Produces graph; x-axis is distance travelled, starting at 0 and distances over each interval.
15-
+ Left y-axis is velocity step function in red; right-axis is state of charge of battery in blue.
16-
+ Since step function is wanted where velocity is shown over the following interval, step function "post" mode is used, and added a point at beginning of v_profile before graphing to be 0.
7+
+ `tests` directory
8+
+ `SOC_velocity_graph.py`: Methods to graph of SOC and speed against distance travelled. See https://www.mdpi.com/2071-1050/9/10/1576/htm for reference for the curve generated.
9+
+ `ASC2021_route.py`: Script to graph preliminary details (elevations vs. distance travelled) on the ASC 2021 route, the Santa Fe trail.

displays/SOC_velocity_graph.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,25 @@
66
from optimization.car_model import Car
77
from soc.soc_deprecated.SoCEstimation import CoulombCounter
88

9-
def calculate_SOC_values(v_profile, e_profile, distance, initial_soc, min_speed=None, max_speed=None):
9+
def calculate_SOC_values(v_profile, e_profile, distances, initial_soc, min_speed=None, max_speed=None):
10+
"""
11+
Calculates state of charge (SOC) array, representing SOC points during race specified by parameters
12+
13+
:param v_profile: list of velocities that car travels (m/s)
14+
:param e_profile: list of elevations that car travels (m)
15+
:param distances: list of distances, specifying distance that
16+
each speed in v_profile is travelled (m)
17+
:param initial_soc: float representing the initial battery state
18+
(where 1 means fully charged, 0 is empty)
19+
:param min_speed: float representing minimum speed car must travel (m/s),
20+
defaults to None (no min)
21+
:param max_speed: float representing maximum speed car must travel (m/s),
22+
defaults to None (no max)
23+
24+
:return: list of state of charges, starting with initial_soc,
25+
and SOC at the end of each interval of velocity.
26+
"""
27+
1028
if min_speed is not None and max_speed is not None:
1129
car_model = Car(speed_min_ms=min_speed, speed_max_ms=max_speed)
1230
elif min_speed is not None:
@@ -18,30 +36,45 @@ def calculate_SOC_values(v_profile, e_profile, distance, initial_soc, min_speed=
1836

1937
soc_est = CoulombCounter()
2038

21-
soc_intervals = [initial_soc]
39+
soc_points = [initial_soc]
2240
soc_est.set_SoC(initial_soc)
2341

2442
for i in range(len(v_profile) - 1):
25-
energy_used = car_model.energy_used(v_profile[i: i + 2], e_profile[i: i + 2], distance[i: i + 1])
26-
#in SoC calculation, wanted units is W = Js^-1, cancelled out in function's conversion so using 1
27-
soc_est.discharge(energy_used / 1, 1, True)
28-
soc_intervals.append(soc_est.get_soc())
43+
# Find energy used within each velocity/elevation interval
44+
energy_used = car_model.energy_used(v_profile[i: i + 2], e_profile[i: i + 2], distances[i: i + 1])
45+
# in `discharge`, method parameter units is W = Js^-1, but energy_used is in J
46+
# assume time span of 1s, conversion cancels out
47+
soc_est.discharge(power_W=energy_used / 1, time_S=1, dirOUT=True)
48+
soc_points.append(soc_est.get_soc())
2949

3050
# last point: maintaining velocity for last given distance
3151
last_point_velocities = [v_profile[-1], v_profile[-1]]
3252
last_elevation = [e_profile[-1], e_profile[-1]]
33-
last_energy_used = car_model.energy_used(last_point_velocities, last_elevation, distance[-1])
53+
last_energy_used = car_model.energy_used(last_point_velocities, last_elevation, distances[-1])
3454
soc_est.discharge(last_energy_used / 1, 1, True)
35-
soc_intervals.append(soc_est.get_soc())
55+
soc_points.append(soc_est.get_soc())
56+
57+
return soc_points
58+
59+
def generate_SOC_graph(v_profile, e_profile, distances, initial_soc=1):
60+
"""
61+
Graphs velocity and state of charge (SOC) vs. distance travelled for some race defined by parameters.
3662
37-
return soc_intervals
63+
These parameters reflect those in energy_used from car_model.py
64+
:param v_profile: list of speeds that car travels (m/s)
65+
:param e_profile: list of elevations that car travels (m)
66+
:param distances: list of distances, specifying distance that
67+
each speed in v_profile is travelled (m)
68+
:param initial_soc: float representing the initial battery state
69+
(where 1 means fully charged, 0 is empty). Default is 1.
70+
"""
3871

39-
def generate_SOC_graph(v_profile, e_profile, distance, initial_soc=1):
40-
x_axis = [0]
41-
for interval_distance in distance:
42-
x_axis.append(x_axis[len(x_axis) - 1] + interval_distance)
72+
# start race at 0m travelled
73+
distance_travelled = [0]
74+
for interval_distance in distances:
75+
distance_travelled.append(distance_travelled[len(distance_travelled) - 1] + interval_distance)
4376

44-
soc_values = calculate_SOC_values(v_profile, e_profile, distance, initial_soc)
77+
soc_values = calculate_SOC_values(v_profile, e_profile, distances, initial_soc)
4578
soc_percentages = [soc * 100 for soc in soc_values]
4679

4780
# for purpose of graphing velocities as step, start at 0
@@ -54,16 +87,16 @@ def generate_SOC_graph(v_profile, e_profile, distance, initial_soc=1):
5487
ax1.set_ylabel('Velocity (m/s)')
5588
ax1.yaxis.label.set_color(color)
5689
ax1.spines['left'].set_color(color)
57-
ax1.step(x_axis, v_profile, where='pre', color=color)
90+
ax1.step(distance_travelled, v_profile, where='pre', color=color)
5891

5992
ax2 = ax1.twinx()
6093

6194
color = 'tab:blue'
6295
ax2.set_ylabel('State of Charge (%)')
6396
ax2.yaxis.label.set_color(color)
6497
ax2.spines['right'].set_color(color)
65-
ax2.plot(x_axis, soc_percentages, color=color)
98+
ax2.plot(distance_travelled, soc_percentages, color=color)
6699

67100
plt.title('State of Charge vs. Velocity Graph')
68101
fig.tight_layout()
69-
plt.show()
102+
plt.show()

dynamics/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,21 @@
1+
# Dynamics
2+
13
A directory for all the car's dynamics modelling and data
4+
5+
## In this directory
6+
7+
+ `tests` directory
8+
+ `CdACrrCalculator.py`: Extract aero drag and rolling resistance coefficients
9+
+ `parserolldowndata.py`: Method to produce cleaned dataframe from CSV rolldown
10+
11+
### `motor_efficiency` module
12+
13+
+ `motor_efficiency.py`: Class to generate and graph motor efficiency curves based on test data
14+
+ `HIData.csv`: Test data CSV for high coil, see [mechanical Confluence](https://uwmidsun.atlassian.net/wiki/spaces/MECH/pages/1628012551/Interpreting+the+Graphs+From+Nomura)
15+
+ `LOData.csv`: Test data CSV for low coil, see [mechanical Confluence](https://uwmidsun.atlassian.net/wiki/spaces/MECH/pages/1628012551/Interpreting+the+Graphs+From+Nomura)
16+
17+
### `rolldowndata` module
18+
19+
+ `canlog`: Directory of canlog data CSVs
20+
+ `rolldown`: Scripts for cleaning and processing rolldown CSV data
21+
+ `test.csv`: Sample rolldown CSV

dynamics/parserolldowndata.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import pandas as pd
2-
"""
3-
Input: Csv as generated by the test data
4-
Output: Cleaned DataFrame for usage in rolldownconverter.py
5-
"""
62

73

84
def clean(file, windspeed=0):
5+
"""
6+
:param file: Csv as generated by the test data
7+
8+
:return: Cleaned DataFrame for usage in rolldownconverter.py
9+
"""
910
data = pd.read_csv(file)
1011
if "test.csv" in file or 'canlog' in file:
1112
# Calculate the average measured velocity

motortorquecalculation/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Motor Torque Calculation
2+
3+
Directory for determining motor torque throughout a race and associated graphs.
4+
5+
## In this directory
6+
7+
+ `tests` directory
8+
+ `alt_gain.py`: Class for version of car model and script for determining motor torque necessary throughout a given route
9+
+ Assorted graphs for ASC and WSC

0 commit comments

Comments
 (0)