# You must make sure to run all cells in sequence using shift + enter or you might encounter errors
from pykubegrader.initialize import initialize_assignment
responses = initialize_assignment("1_homework_q", "week_8", "homework", assignment_points = 75.0, assignment_tag = 'week8-homework')
# Initialize Otter
import otter
grader = otter.Notebook("1_homework_q.ipynb")
๐ Homework Week 8: Combining Concepts to Build Interoperable Code for Plotting ๐๐ง#
This lab uses all of the concepts we have learned in python programming to make reusable building blocks for generating, plotting, and fitting data.
As an engineer you will regularly be presented with noisy data and want to fit that data to a function. In this assignment, we will build some machinery and a coding schema to generate noisy data (since we do not have real data), plot data, and fit the results. We will build tools to simplify the visualization of the results. The code will be designed to be interoperable. If we were to have a new type of data or mathematical expression, we could reuse all the code we have written.
Question 1 (Points: 3.0): Importing Functions#
Here we will import some packages that we need for this assignment
import
numpy
asnp
import the submodule
matplotlib.pyplot
and assign it toplt
import from
scipy.optimize
thecurve_fit
function
# 1. you need to import numpy as np
...
# 2. You need to import the submodule matplotlib.pyplot and assign it to plt
...
# 3. You need to import from scipy.optimize the curve_fit function
...
grader.check("q1-Importing Functions")
Question 2 (Points: 13.0): 2. Implementing a Class for MathExpressions#
In Python and programming in general, it is common to have a base class that adds functionality to an object. For example, if you have a car that you are building, you might want to add blind spot detection. You can create the blind spot detection software and hardware and install it on multiple cars types. We will do the same with a math function. We will build a class that adds a methods to fit and evaluate a math expression.
When we build this class we need it to be flexible to accept math functions with a flexible number of input parameters. We implement a fit function where we can input any parameters into the function (this is important for optimization required to fit data), and an evaluate method which uses the parameters set during initialization.
Follow these steps:
Define a
class
MathExpression
.Build an initialization function.
The function should take a variable
func
which is a mathematical functionThe function should accept
**kwargs
The
kwargs
will be the parameters of the fitting functionWe want to save the
kwargs
, each key-value pair ofkwargs
where the key is the variable name, andfunc
as an attribute of the object
Define a method
fit
.
That accepts a required input
x
, and**kwargs
Return the result when you call the objects method
func
that accepts a required inputx
, and**kwargs
Define an method for the
class
evaluate
.
Takes a required input
x
Returns the result when you call the objects method
func
that accepts a required inputx
, and the**kwargs
values set at initialization.
# 1. define a class MathExpression
...
# 2. Use the built in initialization function
# The function should take a variable func which is a mathematical function for generation and fitting (we will define this later)
# The function should accept **kwargs
# The kwargs will be the parameters of the fitting function
...
# use the .items() method to extract from kwargs a list of tuples with the key value pairs
# Create a for loop that extracts the key value pairs from the kwargs
# make sure to unpack the tuple into the variables key and value
# Inside the for loop, use the built in python method setattr to add an attribute to the MathExpression Object
# for the key, use the variable key,
# for the value, use the variable value
...
# save the kwargs as an attribute of the object named kwargs
...
# save the function as an attribute of the object named func
...
# 3. for the class, define a method fit that accepts a required input x, and **kwargs
# x is the points to sample along the x axis
# **kwargs are new parameters to test
# Note: this is a requirement of the fitting function
...
# return the result when you call the object's method func that accepts a required input x and **kwargs
...
# 4. for the class, define a method evaluate that takes a required input x
...
# return the result when you call the object's method func that accepts a required input x and the kwargs values set at initialization.
...
grader.check("q2-Implement MathExpression")
Question 3 (Points: 3.0): 3. Define a Linear Function#
We want to define a simple linear function. A linear model is one of the simplest fitting functions used in science and engineering. It says that an independent variable directly influences a dependent variable.
Build a function
LinearFunction
LinearFunction
should take three inputs:x
for the independent variable,m
for the slope,b
for the intercept.Return the calculation of
y
, where y = slope * x + intercept.Make sure to replace slope and intercept with the correct variables.
# 1. build a function LinearFunction
# linear function should take two inputs m - for the slope, and b for the intercept
# return the calculation of y, where y = slope * x + intercept.
# Make sure to replace slope and intercept with the correct variables.
...
grader.check("q3 - Linear Function")
Question 4 (Points: 6.0): 4. Plotting#
One of the best ways to understand your code is doing what you expect is to visualize the results. We can do this by plotting the data.
Instantiate an object
Linear_
using theMathExpression
Class
.
The input
func
should be theLinearFunction
you wroteThe variables needed for kwargs must be provided as key value pairs.
In this case, those key value pairs are
m = 1
, andb = -3
.
Derive a linearly-spaced vector for the independent variable.
Use the
np.linspace
function to make a linearly-spaced array from0
to10
with100
steps
call the built-in
evaluate
method of theLinearFunction
object over the range defined byx
.
assign the output to a variable
y
Use the function
plt.plot
to plotx
vsy
.
assign the plot to a variable called
linear_plot
# 1. Instantiate an object Linear_ using the MathExpression Class
# The variables needed for the Kwargs m = 1, and b = -3 need to be provided as key value pairs.
# example my_function(x=1), this would provide a **kwargs key (x) value (1)
...
# 2. use the np.linspace function to make a linearly-spaced array from 0 to 10, with 100 steps
# save this to the variable x
...
# 3. call the built-in `evaluate` method of the LinearFunction object over the range defined by x
# assign the output to a variable y
...
# 4. use the function plt.plot to plot x vs y
...
# we do not need to add more labels to this plot. It was just for testing.
grader.check("q4 - Plotting")
5. A NoisyFunction#
You are responsible for making a 3-4 minute VoiceThread submission that briefly explains each function of this class. You are not responsible for writing any code or comments in this Task.
We have written a function with several useful methods that you might want to use as an engineer.
1 It inherits information from an object of type MathExpression
2 It can generate noisy data, which is to simulate real experimental data.
3 It can fit the data to an known function.
4 It can fit, plot and format the results for quick visualization.
# build a class Noisy Function that Inherits MathExpression
# This is simply done writing class NoisyFunction(MathExpression)
...
# This class does not require any specific initialization variables, but it will require that we pass the inherited variable through.
# we have done this for you
# you should not touch this code. If you do, here it is
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
...
# build a function generate noisy data with two required inputs
# x_range - a list of the minimum and max ranges for to generate the data
# noise_amplitude - a float from which sets the magnitude of the noise
...
# use np.linspace to generate x data along the range
# generate 100 points
...
# call the evaluate method of the base function object to return the true values
...
# generate the noise vector by multiplying the noise amplitude by an random noise vector of length x
# the random noise vector should be generated with np.random.randn
...
# add the noise to the y_true value
# save it in a new variable y
...
# return x, y, and y_true
...
# build a function fit_data that takes 2 inputs x and y
...
# call the curvefit function
# provide the inherited pointed to the function
# For the initial guess provide the true values they are contained within the saved variable kwargs
# You can get the values of a dictionary using the built-in method .values()
# P0 takes a list. You need to convert the values to a list.
# Curvefit should return two values assign them to popt and pcov.
#
# is the fit results
...
# Return popt and pcov
...
# build a function plot_fit_results that plots the raw and fit results
# This function should take two input parameters:
# x_range - a tuple defining the x range to plot
# noise_amplitude - a float used to control the magnitude of the noise
...
# use the subplots function in pyplot to create a multiplot figure with 2 columns and 1 row.
# set the figure size = 10, 5 - figsize takes a tuple
# assign the output to two variables fig and axs
...
# make a list of strings called labels
# The 0th index should be "True Fit"
# The 1st index should be "Noisy Fit"
...
# call the generate_noisy_data function from this class
# save the returned variables to x, y, and y_true
...
# make a look that loops over the axs with enumerate.
# set the iterator equal to the variable i
# set the value equal to the variable ax
...
# create a scatterplot on the axis object
# plot x vs y
# make the marker size = 5 (with the `s` tag)
# add a label 'Raw Data' (with the `label` tag)
...
# write an if statement that is true if the label of the graph is "True Fit". This is ordered by the list labels you created.
...
# if the statement was true on the axis object plot
# x vs y true
# make the line red using the 'r' flag
# add a label as indexed from the list
...
# add an elif statement that is true if the label of the graph is "Noisy Fit" this is ordered by the list labels you created.
...
# call the method in the class fit_data
# This should fit the x and y data
# have it return the variables popt and pcov
...
# use the predicted popt and the inherited function to generate the y values for the fit results
# make sure to unpack the popt list with the * operator
...
# on the axis object plot
# x vs y_noisy_fit
# make the line blue using the 'b' flag
# add a label as indexed from the list
...
# else passes to the next loop
...
# use the set_xlabel method to set the x-label of the graph to 'x'
# use the set_ylabel method to set the y-label of the graph to 'y'
...
# call the legend method of the axis object to show the legend
...
# return the fig object
...
grader.check("q5 - Noisy Function")
Question 6 (Points: 11.0): 6. Testing our Class#
In coding it is always a good idea to run all parts of your code to check it is performing as expected.
Create a
NoisyFunction
instance for a linear function
Assign this object to the variable
linear_func
The input
func
should be theLinearFunction
you wroteThe slope should be
3
and the intercept should be4
Call the proper method of
linear_func
to generate noisy data over the range from 0- \(2\pi\)
the noise amplitude is
1
save the results to
x
,y
, andy_true
Fit the
x
andy
data using the proper method oflinear_func
save the fit results to
popt
,pcov
(these names represent the optimal parameters (
popt
) and the parametersโ covariance (pcov
))
Use the built-in method to plot the fit result
set the range equal to
0
to16
set the noise amplitude equal to
0.3
Save this to the variable plot
seed = np.random.seed(42)
# 1. Create a NoisyFunction instance for a linear function
# Assign this object to the variable linear_func
# The slope should be 3 and the intercept should be 4
...
# 2. call the generate_noisy_data function of the object the linear_func to produce noisy data over the range from 0-2pi
# use a tuple to indicate the range from 0-2pi
# the noise amplitude is 1
# save the results to x, y, and y_true
...
# 3. Fit the data
# save the fit results to popt, pcov
...
# 4. Use the built-in method to plot the fit result
# set the range equal to 0 to 16
# set the noise amplitude equal to 0.3
# Save this to the variable plot
...
grader.check("q6 - plotting and fitting")
Question 7 (Points: 6.0): 7. Defining new math functions#
Now we can view the flexibility (interoperability) of our code.
Define two new functions. Follow the logic of the way you created LinearFunction
.
SineFunction
where \(A\) represents amplitude, \(f\) represents the frequency, and \(\phi\) represents the phase.
ExponentialFunction
# 1. Write the SineFunction here
# the inputs should be x, amplitude, frequency, phase
...
# 2. Write the ExponentialFunction here
# the inputs should be x, a, b
...
grader.check("q7-new functions")
8 Using our class with the sine function#
Now we can use nearly the same code from Task 7 to test our SineFunction
.
Create a
NoisyFunction
instance for the sine function
Assign this object to the variable
sine_func
The input
func
should be theSineFunction
you wroteThe amplitude should be 2, the frequency should be .2, and the phase should be .5
Apply the
generate_noisy_data
method ofsine_func
to produce noisy data over the range from 0-6 \(\pi\)
use a tuple to indicate the range from 0-6 \(\pi\)
the noise amplitude is 1.5
save the results to
x
,y
, andy_true
Fit the
x
andy
data using the proper method ofsine_func
save the fit results to
popt
,pcov
Use the built-in method to plot the fit results
set the range equal to 0 to 6 \(\pi\)
set the noise amplitude equal to 1.5
Save this to the variable
plot
seed = np.random.seed(42)
# 1. Create a NoisyFunction instance for a sine function
# Assign this object to the variable sine_func
# The amplitude should be 2, the frequency should be .2, and the phase should be .5
...
# 2. evaluate the sine_func to produce noisy data over the range from 0-6pi
# the noise amplitude is 1.5
# save the results to x, y, and y_true
...
# 3. Fit the data
# save the fit results to popt, pcov
...
# 4. Use the built-in method to plot the fit results
# set the range equal to 0 to 6 pi
# set the noise amplitude equal to 1.5
# Save this to the variable plot
...
grader.check("q8-using the class with new Sine functions")
9 Using our class with the Exponential function#
Now we can use nearly the exact same code to test our Exponential function. We wrote one code that we can use over and over again in different ways!
Create a
NoisyFunction
instance for a exponential function
Assign this object to the variable
exp_func
The input
func
should be theExponentialFunction
you wrotea
should be 2, andb
should be .2
Call the proper method of
exp_func
to generate noisy data over the range from 0-6 \(\pi\)
use a tuple to indicate the range from 0-6 \(\pi\)
the noise amplitude is 1.5
save the results to
x
,y
, andy_true
Fit the
x
andy
data using the proper method ofexp_func
save the fit results to
popt
,pcov
Use the built-in method to plot the fit results
set the range equal to 0 to 6 \(\pi\)
set the noise amplitude equal to 1.5
save this to the variable
plot
seed = np.random.seed(42)
# 1. Create a NoisyFunction instance for a exponential function
# Assign this object to the variable exp_func
# The a should be 2, and b should be .2
...
# 2. evaluate the exp_func to produce noisy data over the range from 0-6pi
# the noise amplitude is 1.5
# save the results to x, y, and y_true
...
# 3. Fit the data
# save the fit results to popt, pcov
...
# 4. Use the built-in method to plot the fit results
# set the range equal to 0 to 6 pi
# set the noise amplitude equal to 1.5
# Save this to the variable plot
...
grader.check("q9-using the class with new exponential functions")
Submitting Assignment#
Please run the following block of code using shift + enter
to submit your assignment, you should see your score.
from pykubegrader.submit.submit_assignment import submit_assignment
submit_assignment("week8-homework", "1_homework_q")