Penn Electric Racing Data Analyzer: PERDA#

PERDA is PER’s in-house data analysis library designed to streamline the process of analyzing vehicle data logs.

Load Data#

[ ]:
from perda.analyzer import Analyzer

csvPath = "csv_files/20thMarch13-21.csv"
aly = Analyzer(csvPath)
[ ]:
# Print summary of the loaded data
print(aly)

Search Data#

[ ]:
aly.search("vectornav")

Inspect Data#

[ ]:
from perda.utils import data_instance_summary

# Print individual variable's info
data_instance_summary(
    aly.data["bms.pack.current"], source_time_unit="us"
)  # change source_time_unit to us if v2.0 logs

print()

# Print the same variable, but accessing the data structure using variable ID instead
data_instance_summary(aly.data[357], source_time_unit="us")

Graph GPS#

[ ]:
from perda.utils import create_representative_gps_image

lat = aly.data["pcm.vnav.posLla.latitude"]  # Put latitude data here, if any
lon = aly.data["pcm.vnav.posLla.longitude"]  # Put longitude data here, if any
vel = aly.data[
    "pcm.wheelSpeeds.frontLeft"
]  # Put velocity data here, optional but recommended

create_representative_gps_image(lat, lon, vel)

Graph Variables (Single Y-Axis)#

[ ]:
# Variables to graph
variables = [
    "pcm.moc.motor.requestedTorque",
    "pcm.moc.motor.wheelSpeed",
    "bms.pack.power",
    "bms.pack.voltage",
]
aly.plot(variables)
[ ]:
# We can also plot small chunks of the data to prevent too much data points
aly.plot(variables, ts_start=150, ts_end=300)

Graph Variables (Dual Y-Axis)#

[ ]:
# Variables to graph
variables_left = [
    "pcm.wheelSpeeds.frontLeft",
    "pcm.wheelSpeeds.frontRight",
    "pcm.wheelSpeeds.backLeft",
    "pcm.wheelSpeeds.backRight",
]

variables_right = [
    "bms.stack.mma.temp.avg",
    "bms.stack.mma.dieTemp.avg",
    357,  # We can also specify variables by variable ID
]

aly.plot(
    var_1=variables_left,
    var_2=variables_right,
    ts_start=600,
    ts_end=800,
    title="Dual Y-Axis Example",
    y_label_1="Wheel Speeds (MPH)",
    y_label_2="Minion Temperature Data",
)

Data Manipulation#

The core of PERDA is the DataInstance class. Each variable is stored as a DataInstance object.

[ ]:
from perda.utils import data_instance_summary

# Retrieve DataInstances
current = aly.data["bms.pack.current"]
voltage = aly.data["bms.pack.voltage"]

# Print info for current
print()
data_instance_summary(current)

# Basic arithmetic operators are overloaded for DataInstances
current_plus = current + 10  # Creates a new DataInstance that is
# 10 higher than current at every timestamp
current_plus.label = "current + 10"  # Assign our new DataInstance a label
print()
data_instance_summary(current_plus)

# One example of a practical use case is calculating power from current and voltage
power_cal = (current * voltage) / 1000
power_cal.label = "power calculated"
[ ]:
# Let's compare calculated power with bms.pack.power
variables = [
    "bms.pack.power",
    power_cal,
]
aly.plot(var_1=variables)

Custom Variables#

[ ]:
from perda.analyzer import DataInstance
from perda.utils import data_instance_summary
import numpy as np

# We can also create our own DataInstances
my_ts = np.arange(2000000)
my_val = my_ts / 1e3 - 1000
my_di = DataInstance(timestamp_np=my_ts, value_np=my_val, label="Line_Data", var_id=1)
data_instance_summary(my_di)
[ ]:
aly.plot(var_1=my_di)

Frequency Analysis#

[ ]:
# Retrieve DataInstances
aly.analyze_frequency("bms.stack.thermistors.temperature[38]")

CSV Compare (diff)#

[ ]:
from perda.analyzer import Analyzer

csvBase = "csv_files/09thMarch02-19_from_pi.csv"
csvIncom = "csv_files/9thMar06-19-18_from_dataserver.csv"

# Parse two csv files for compare
alyBase = Analyzer(csvBase)
alyIncom = Analyzer(csvIncom)
[ ]:
# Compare base and incoming data
graph = alyBase.diff(alyIncom.data, diff_rtol=1e-3, diff_atol=1e-3)

graph.show()

Concat CSVs#

[ ]:
from perda.analyzer import Analyzer, concat

# We can concate multiple csv files into one readings, start with base
alyBase = Analyzer("csv_files/22ndMar09-53-00")

# Setup logs we want to merge
paths = [
    "csv_files/22ndMar10-08-00",
    "csv_files/22ndMar10-20-00",
    "csv_files/22ndMar11-16-00",
    "csv_files/22ndMar11-48-00",
    "csv_files/22ndMar11-52-00",
    "csv_files/22ndMar12-00-00",
    "csv_files/22ndMar12-52-00",
    "csv_files/22ndMar14-16-00",
    "csv_files/22ndMar14-22-00",
    "csv_files/22ndMar14-25-00",
    "csv_files/22ndMar14-27-00",
]

# This might take a while since we are parsing many runs
for path in paths:
    # Parse incoming log
    alyIncom = Analyzer(path)
    # Concat the second analyzer after first analyzer
    alyBase = concat(alyBase, alyIncom)
[ ]:
# Plot cell voltage over time for multiple runs
variables = [
    "bms.stack.mma.cellV.min",
    "bms.stack.mma.cellV.max",
    "bms.stack.mma.cellV.avg",
]

alyBase.plot(variables)