151 lines
5.7 KiB
Python
151 lines
5.7 KiB
Python
"""CSC110 Fall 2021 Assignment 3, Part 4: Forest Fires (SOLUTIONS)
|
|
|
|
Instructions (READ THIS FIRST!)
|
|
===============================
|
|
Implement each of the functions in this file. As usual, do not change any function headers
|
|
or preconditions. You do NOT need to add doctests.
|
|
|
|
Copyright and Usage Information
|
|
===============================
|
|
|
|
This file is provided solely for the personal and private use of students
|
|
taking CSC110 at the University of Toronto St. George campus. All forms of
|
|
distribution of this code, whether as given or with any changes, are
|
|
expressly prohibited. For more information on copyright for CSC110 materials,
|
|
please consult our Course Syllabus.
|
|
|
|
This file is Copyright (c) 2021 Mario Badr and Tom Fairgrieve.
|
|
"""
|
|
import csv
|
|
import plotly.graph_objects as go
|
|
|
|
from a3_ffwi_system import WeatherMetrics, FfwiOutput
|
|
import a3_ffwi_system as ffwi
|
|
|
|
|
|
def load_data(filename: str) -> tuple[list[WeatherMetrics], list[FfwiOutput]]:
|
|
"""Return a tuple of two parallel lists based on the data in filename. The first list contains
|
|
WeatherMetrics. The second list contains the corresponding FfwiOutput.
|
|
|
|
The data in filename is in a csv format with 12 columns. The first six columns correspond to
|
|
the month, day, temperature, relative humidity, wind speed, and precipitation, in that order.
|
|
The last six columns correspond to the FFMC, DMC, DC, ISI, BUI, and FWI values that would be
|
|
calculated based on the first six columns and the previous day's values.
|
|
"""
|
|
# ACCUMULATOR inputs_so_far: The WeatherMetrics parsed from filename so far
|
|
inputs_so_far = []
|
|
# ACCUMULATOR outputs_so_far: The FfwiOutputs parsed from filename so far
|
|
outputs_so_far = []
|
|
|
|
with open(filename) as f:
|
|
reader = csv.reader(f, delimiter=',')
|
|
next(reader) # skip the header
|
|
|
|
for row in reader:
|
|
assert len(row) == 12, 'Expected every row to contain 12 elements.'
|
|
month, day, temp, humidity, wind, rain, \
|
|
ffmc, dmc, dc, isi, bui, fwi = [float(a) for a in row]
|
|
inputs_so_far.append(WeatherMetrics(int(month), int(day), temp, humidity, wind, rain))
|
|
outputs_so_far.append(FfwiOutput(ffmc, dmc, dc, isi, bui, fwi))
|
|
|
|
return inputs_so_far, outputs_so_far
|
|
|
|
|
|
def calculate_ffwi_outputs(readings: list[WeatherMetrics]) -> dict[tuple[int, int], FfwiOutput]:
|
|
"""Return a dictionary mapping (month, day) tuples to their corresponding FfwiOutput based on
|
|
the daily weather measurements found in readings.
|
|
|
|
Use the functions in a3_ffwi_system for initial FFMC, DMC, and DC values and to calculate each
|
|
attribute needed for FfwiOutput.
|
|
|
|
Preconditions:
|
|
- Every reading in readings has a unique (month, day) pair
|
|
"""
|
|
# Accumulator
|
|
output: dict[tuple[int, int], FfwiOutput] = {}
|
|
|
|
# Initial values
|
|
# ASSUME that the input dates are CONTINUOUS
|
|
# ASSUME that the input dates does not wrap around a year
|
|
last = FfwiOutput(ffwi.INITIAL_FFMC, ffwi.INITIAL_DMC, ffwi.INITIAL_DC, 0, 0, 0)
|
|
|
|
# Loop through inputs
|
|
for x in readings:
|
|
ffmc = ffwi.calculate_ffmc(x, last.ffmc)
|
|
dmc = ffwi.calculate_dmc(x, last.dmc)
|
|
dc = ffwi.calculate_dc(x, last.dc)
|
|
isi = ffwi.calculate_isi(x, ffmc)
|
|
bui = ffwi.calculate_bui(dmc, dc)
|
|
fwi = ffwi.calculate_fwi(isi, bui)
|
|
output[(x.month, x.day)] = FfwiOutput(ffmc, dmc, dc, isi, bui, fwi)
|
|
|
|
return output
|
|
|
|
|
|
def get_xy_data(outputs: dict[tuple[int, int], FfwiOutput], attribute: str) -> \
|
|
tuple[list[str], list[float]]:
|
|
"""Return a tuple of two parallel lists. The first list contains the keys of outputs as
|
|
strings in the format 'month, day'. The second list contains the corresponding value of
|
|
the attribute of FfwiOutput.
|
|
|
|
You can access an attribute from a data class using the getattr built-in function. For example,
|
|
>>> output = FfwiOutput(2.0, 3.0, 4.0, 5.0, 6.0, 7.0)
|
|
>>> getattr(output, 'ffmc')
|
|
2.0
|
|
"""
|
|
# Accumulators
|
|
days: list[str] = []
|
|
data: list[float] = []
|
|
|
|
for k in outputs.keys():
|
|
month, day = k
|
|
days.append(f'{month}, {day}')
|
|
data.append(getattr(outputs[k], attribute))
|
|
|
|
return days, data
|
|
|
|
|
|
def plot_ffwi_attribute(outputs: dict[tuple[int, int], FfwiOutput], attribute: str) -> None:
|
|
"""Plot an attribute from FfwiOutput as a time series.
|
|
|
|
Preconditions:
|
|
- attribute in {'ffmc', 'dmc', 'dc', 'isi', 'bui', 'fwi'}
|
|
- outputs != {}
|
|
"""
|
|
# Convert the outputs into parallel x and y lists
|
|
x_data, y_data = get_xy_data(outputs, attribute)
|
|
|
|
# Create the figure
|
|
fig = go.Figure()
|
|
fig.add_trace(go.Scatter(x=x_data, y=y_data, name=attribute))
|
|
|
|
# Configure the figure
|
|
fig.update_layout(title=f'Time Series of {attribute}',
|
|
xaxis_title='(Month, Day)',
|
|
yaxis_title=f'Calculated {attribute}')
|
|
|
|
# Show the figure in the browser
|
|
fig.show()
|
|
# Is the above not working for you? Comment it out, and uncomment the following:
|
|
# fig.write_html('my_figure.html')
|
|
# You will need to manually open the my_figure.html file created above.
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import python_ta
|
|
import python_ta.contracts
|
|
python_ta.contracts.DEBUG_CONTRACTS = False
|
|
python_ta.contracts.check_all_contracts()
|
|
|
|
# When you are ready to check your work with python_ta, uncomment the following lines.
|
|
# (Delete the "#" and space before each line.)
|
|
# IMPORTANT: keep this code indented inside the "if __name__ == '__main__'" block
|
|
python_ta.check_all(config={
|
|
'allowed-io': ['load_data'],
|
|
'extra-imports': ['python_ta.contracts', 'csv', 'plotly.graph_objects', 'a3_ffwi_system'],
|
|
'max-line-length': 100,
|
|
'max-args': 6,
|
|
'max-locals': 25,
|
|
'disable': ['R1705'],
|
|
})
|