Difference between revisions of "User:DukeEgr93/CBApp"
Jump to navigation
Jump to search
(3 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | + | = Goals = | |
− | * Select a set and load data | + | Create a MATLAB Application that will: |
− | + | * Load one of four different cantilever beam files - user decides the file | |
− | + | * Perform a polynomial fit of the data - user decides the order | |
− | + | * Present the data two different possible ways: | |
− | + | :* Graph the original data using black circles and the fit as a black line; use can select whether the data, fit, or both are graphed | |
− | + | :* Graph the estimate residuals | |
+ | * Calculate and display the goodness of fit | ||
+ | * Use color as a means of indicating goodness of fit | ||
+ | |||
+ | = Steps = | ||
+ | == Select a set and load data == | ||
+ | * Add a button and have it simply load Beam01.dat | ||
+ | :* Drag button onto app | ||
+ | :* Change text to Run | ||
+ | :* Since data set will likely be needed in various parts of app, in the Code View, add a public property called Force and a public property called Displacement: | ||
+ | ::<source lang=MATLAB> | ||
properties (Access = public) | properties (Access = public) | ||
Force % Force applied to beam | Force % Force applied to beam | ||
Disp % Displacement measured at end | Disp % Displacement measured at end | ||
end</source> | end</source> | ||
− | + | :* In component browser, right click app.RunButton and add a RunButtonPushed callback | |
− | + | :* Add code in this to load the data, split it, and put it into appropriate variables: | |
− | + | ::<source lang=MATLAB> | |
function RunButtonPushed(app, event) | function RunButtonPushed(app, event) | ||
Data = load('Beam01.dat'); | Data = load('Beam01.dat'); | ||
Line 18: | Line 28: | ||
app.Disp = 0.0254 * Data(:,2); | app.Disp = 0.0254 * Data(:,2); | ||
end</source> | end</source> | ||
− | + | :* Check this by putting a keyboard in the RunButtonPushed callback; run the app and at the keyboard prompt, see what Data, app.Force, and app.Displ look like. Use dbquit to get out of keyboard and if all is good, delete the keyboard command: | |
− | + | ::<source lang=MATLAB> | |
function RunButtonPushed(app, event) | function RunButtonPushed(app, event) | ||
Data = load('Beam01.dat'); | Data = load('Beam01.dat'); | ||
Line 27: | Line 37: | ||
end | end | ||
</source> | </source> | ||
− | + | ::Results:<source lang=MATLAB> | |
K>> Data | K>> Data | ||
Line 69: | Line 79: | ||
K>> dbquit | K>> dbquit | ||
</source> | </source> | ||
− | + | == Plot the data == | |
− | + | * Go into Design View and add a set of axes | |
− | + | :* Change the axis labels and titles as needed | |
− | + | * Go into the Code View and add code to the RunButtonPushed function to plot the data and add a grid | |
− | + | :<source lang=MATLAB> | |
function RunButtonPushed(app, event) | function RunButtonPushed(app, event) | ||
Data = load('Beam01.dat'); | Data = load('Beam01.dat'); | ||
Line 105: | Line 115: | ||
end | end | ||
</source> | </source> | ||
− | + | == Calculate and plot fit == | |
− | + | Next we will hard-code a first-order fit; later, we will make the code flexible for higher order fits. | |
− | + | * In Design View, add a spinner and call it Order | |
− | + | :* Click on app.OrderSpinner in the Component Browser; in the Spinner Properties - Configuration, change the minimum value to 0 and change the Display to Integers. | |
− | + | * You will most likely need to calculate the goodness of fit either when you click the Run button or when you change the spinner. This means it would be a good idea to make a separate function that calculates the fit; the callbacks for both the button and the spinner can then call this new function. | |
− | + | * In the Code View, make a new public function called CalcFit | |
+ | :<source lang=MATLAB> | ||
function results = CalcFit(app) | function results = CalcFit(app) | ||
end | end | ||
</source> | </source> | ||
− | + | * Various parts of the app will probably need to know the coefficients of the fit, so add a public property called Coefs. | |
− | + | :<source lang=MATLAB> | |
properties (Access = public) | properties (Access = public) | ||
Force % Force applied to beam | Force % Force applied to beam | ||
Line 123: | Line 134: | ||
end | end | ||
</source> | </source> | ||
− | + | * Add code to the CalcFit program that uses the value in the Order spinner to determine the coefficients of a fit of that order: | |
− | + | :<source lang=MATLAB> | |
function results = CalcFit(app) | function results = CalcFit(app) | ||
app.Coefs = polyfit(app.Force, app.Disp, app.OrderSpinner.Value); | app.Coefs = polyfit(app.Force, app.Disp, app.OrderSpinner.Value); | ||
end | end | ||
</source> | </source> | ||
− | + | * Add a changed callback to the OrderSpinner to update the plot when the spinner value changes: | |
− | + | :<source lang=MATLAB> | |
function OrderSpinnerValueChanged(app, event) | function OrderSpinnerValueChanged(app, event) | ||
− | |||
UpdatePlot(app); | UpdatePlot(app); | ||
end | end | ||
</source> | </source> | ||
− | + | * Add code to the UpdatePlot function to get the new coefficients and use them to plot a model: | |
− | + | :<source lang=MATLAB> | |
function results = UpdatePlot(app) | function results = UpdatePlot(app) | ||
plot(app.UIAxes, app.Force, app.Disp, 'ko') | plot(app.UIAxes, app.Force, app.Disp, 'ko') | ||
Line 148: | Line 158: | ||
hold(app.UIAxes, 'off') | hold(app.UIAxes, 'off') | ||
keyboard | keyboard | ||
+ | end | ||
+ | </source> | ||
+ | == Determine and display statistics == | ||
+ | Once again, determining and displaying statistics will be triggered by multiple events (hitting the button or changing the order) so there might as well be a function for it that various callbacks can use. | ||
+ | * In the Code View, create a public function called CalcStats | ||
+ | * Add code to CalcStats to calculate the coefficient of determination for the fit; leave the semi-colons off for now so you can see these values in the command window and see if they make sense. | ||
+ | <source lang=MATLAB> | ||
+ | function results = CalcStats(app) | ||
+ | Dbar = mean(app.Disp); | ||
+ | Dhat = polyval(app.Coefs, app.Force); | ||
+ | St = sum((app.Disp-Dbar).^2) | ||
+ | Sr = sum((app.Disp-Dhat).^2) | ||
+ | r2 = (St-Sr) / St | ||
+ | end | ||
+ | </source> | ||
+ | * Add code to the end of the callbacks for the button and the spinner to run this function: | ||
+ | <source lang=MATLAB> | ||
+ | % Button pushed function: RunButton | ||
+ | function RunButtonPushed(app, event) | ||
+ | Data = load('Beam01.dat'); | ||
+ | app.Force = 9.81 * Data(:,1); | ||
+ | app.Disp = 0.0254 * Data(:,2); | ||
+ | UpdatePlot(app); | ||
+ | CalcStats(app); | ||
+ | end | ||
+ | |||
+ | % Value changed function: OrderSpinner | ||
+ | function OrderSpinnerValueChanged(app, event) | ||
+ | UpdatePlot(app); | ||
+ | CalcStats(app); | ||
+ | end | ||
+ | </source> | ||
+ | * Test your code - for this data set, note that r2 for a 0 order fit is always 0; r2 for 6th order is almost exactly 1 and r2 for a 7th order fit is exactly 1. If your code is working at this point - congratulations! If not - go ahead and figure out how to fix it before moving on. | ||
+ | == Display stats in a table == | ||
+ | Now you are going to add a table to your app to display information about the number of points, order of fit, St, Sr, and r2 value. | ||
+ | * In the Design View, drag a Table in. | ||
+ | * There will be five columns named NumPts, Order, St, Sr, r2 -- make these changes in the Names dropdown in the Columns section of the Configuration block in the UITABLE PROPERTIES menu (whew!). | ||
+ | * Make the table wide enough to see all five columns. | ||
+ | * Go to the CodeView. Think about where it would make the most sense to update the table values. CalcStats is where the information will be calculated, so this is probably the right function to also update the table. | ||
+ | :* In the CalcStats function, add a line that will load the appropriate information into app.UITable.Data: | ||
+ | <source lang=MATLAB> | ||
+ | function results = CalcStats(app) | ||
+ | Dbar = mean(app.Disp); | ||
+ | Dhat = polyval(app.Coefs, app.Force); | ||
+ | St = sum((app.Disp-Dbar).^2); | ||
+ | Sr = sum((app.Disp-Dhat).^2); | ||
+ | r2 = (St-Sr) / St; | ||
+ | app.UITable.Data = [length(app.Force), app.OrderSpinner.Value, St, Sr, r2]; | ||
+ | end | ||
+ | </source> | ||
+ | * The table is clearly too tall - in the Design View, you can shrink the table to make the size make sense for the number of rows (1) you have. | ||
+ | == Change data sets == | ||
+ | One of the requirements of this app s that it be able to choose from among four data sets. One way of selecting a data set might be a discrete knob. | ||
+ | * In the Design view, drag a discrete knob onto the app and relabel it Data Set. | ||
+ | * Click on the app.DataSetKnob in the Component Browser, then in the Knob Properties, change the States Items to 1, 2, 3, 4. | ||
+ | * Now we need to change how the data set is loaded -- instead of always being Beam01.dat, we must dynamically create the function to load the data by taking into account the value of the know. It is important to note that the value of the know is a ''string'' not a ''number''. The following code will dynamically create the correct command to load the data: | ||
+ | <source lang=MATLAB> | ||
+ | % Button pushed function: RunButton | ||
+ | function RunButtonPushed(app, event) | ||
+ | Data = eval(sprintf('load(''Beam0%s.dat'')', app.DataSetKnob.Value)); | ||
+ | app.Force = 9.81 * Data(:,1); | ||
+ | app.Disp = 0.0254 * Data(:,2); | ||
+ | UpdatePlot(app); | ||
+ | CalcStats(app); | ||
+ | end | ||
+ | </source> | ||
+ | * One issue now is that when the knob is moved, the new data set is not immediately loaded; the user has to push the button for that. There are several ways to deal with this. You could add a callback to the knob such that it calls RunButton and the knob value changes. For this exercise, however, you are going to instead add an indicator light that lets the user know if the knob shows the actual data set being plotted. To do this, you are going to: | ||
+ | :* Create a property to keep track of the data set being plotted | ||
+ | ::* Create a public property called Set, initialize it to 0 | ||
+ | <source lang=MATLAB> | ||
+ | properties (Access = public) | ||
+ | Force % Force applied to beam | ||
+ | Disp % Displacement measured at end | ||
+ | Coefs % Fit coefficients | ||
+ | Set=1 % Data set in use | ||
+ | end | ||
+ | </source> | ||
+ | ::* Update it whenever the button is pushed: | ||
+ | <source lang=matlab> | ||
+ | % Button pushed function: RunButton | ||
+ | function RunButtonPushed(app, event) | ||
+ | Data = eval(sprintf('load(''Beam0%s.dat'')', app.DataSetKnob.Value)); | ||
+ | app.Force = 9.81 * Data(:,1); | ||
+ | app.Disp = 0.0254 * Data(:,2); | ||
+ | UpdatePlot(app); | ||
+ | CalcStats(app); | ||
+ | app.Set = app.DataSetKnob.Value; | ||
+ | end | ||
+ | </source> | ||
+ | :* Whenever the knob changes, compare the current set to the knob value and have a light show green if they are the same or red if not: | ||
+ | ::* In Design view, drag a lamp near the knob and call it Current | ||
+ | ::* Add a callback when the knob is changed to make the light green if the knob and the data set match and red otherwise: | ||
+ | <source lang=MATLAB> | ||
+ | % Value changed function: DataSetKnob | ||
+ | function DataSetKnobValueChanged(app, event) | ||
+ | value = app.DataSetKnob.Value; | ||
+ | if app.Set==num2str(value) | ||
+ | app.CurrentLamp.Color=[0 1 0]; | ||
+ | else | ||
+ | app.CurrentLamp.Color=[1 0 0]; | ||
+ | end | ||
+ | end | ||
+ | </source> | ||
+ | ::* This is fine when the knob changes, but what happens when you load a new data set? Since the knob didn't change, the lamp will not change color. You should also add code to the RunButton function that makes the lamp green again: | ||
+ | <source lang=MATLAB> | ||
+ | % Button pushed function: RunButton | ||
+ | function RunButtonPushed(app, event) | ||
+ | Data = eval(sprintf('load(''Beam0%s.dat'')', app.DataSetKnob.Value)); | ||
+ | app.Force = 9.81 * Data(:,1); | ||
+ | app.Disp = 0.0254 * Data(:,2); | ||
+ | UpdatePlot(app); | ||
+ | CalcStats(app); | ||
+ | app.Set = app.DataSetKnob.Value; | ||
+ | app.CurrentLamp.Color=[0 1 0]; | ||
end | end | ||
</source> | </source> |
Latest revision as of 00:09, 17 April 2018
Contents
Goals
Create a MATLAB Application that will:
- Load one of four different cantilever beam files - user decides the file
- Perform a polynomial fit of the data - user decides the order
- Present the data two different possible ways:
- Graph the original data using black circles and the fit as a black line; use can select whether the data, fit, or both are graphed
- Graph the estimate residuals
- Calculate and display the goodness of fit
- Use color as a means of indicating goodness of fit
Steps
Select a set and load data
- Add a button and have it simply load Beam01.dat
- Drag button onto app
- Change text to Run
- Since data set will likely be needed in various parts of app, in the Code View, add a public property called Force and a public property called Displacement:
properties (Access = public) Force % Force applied to beam Disp % Displacement measured at end end
- In component browser, right click app.RunButton and add a RunButtonPushed callback
- Add code in this to load the data, split it, and put it into appropriate variables:
function RunButtonPushed(app, event) Data = load('Beam01.dat'); app.Force = 9.81 * Data(:,1); app.Disp = 0.0254 * Data(:,2); end
- Check this by putting a keyboard in the RunButtonPushed callback; run the app and at the keyboard prompt, see what Data, app.Force, and app.Displ look like. Use dbquit to get out of keyboard and if all is good, delete the keyboard command:
function RunButtonPushed(app, event) Data = load('Beam01.dat'); app.Force = 9.81 * Data(:,1); app.Disp = 0.0254 * Data(:,2); keyboard end
- Results:
K>> Data Data = 0 0.0052 0.1135 0.1587 0.2273 0.3140 0.3408 0.4746 0.4558 0.6368 0.5693 0.7799 0.6836 0.9366 0.7971 1.0000 K>> app.Force ans = 0 1.1135 2.2296 3.3431 4.4715 5.5850 6.7064 7.8199 K>> app.Disp ans = 0.0001 0.0040 0.0080 0.0121 0.0162 0.0198 0.0238 0.0254 K>> dbquit
Plot the data
- Go into Design View and add a set of axes
- Change the axis labels and titles as needed
- Go into the Code View and add code to the RunButtonPushed function to plot the data and add a grid
function RunButtonPushed(app, event) Data = load('Beam01.dat'); app.Force = 9.81 * Data(:,1); app.Disp = 0.0254 * Data(:,2); plot(app.UIAxes, app.Force, app.Disp, 'ko') grid(app.UIAxes, 'on') end
- At this point, it would be good to take a moment to think about what processes this app will need to access based on making changes to various fields. Later, we are going to add the ability to fit the model with a polynomial of different orders, and as we change the order of the fit, we will probably also want the graph to update. Given that, it might be better to have a process to plot things that is separate from pushing the button.
- In Code View, add a new public function and call it UpdatePlot
function results = UpdatePlot(app) end
- Cut the plotting code from the RunButtonPushed function and paste it into the UpdatePlot function:
function results = UpdatePlot(app) plot(app.UIAxes, app.Force, app.Disp, 'ko') grid(app.UIAxes, 'on') end
- Add a call to the UpdatePlot function at the end of the RunButtonPushed function:
function RunButtonPushed(app, event) Data = load('Beam01.dat'); app.Force = 9.81 * Data(:,1); app.Disp = 0.0254 * Data(:,2); UpdatePlot(app); end
Calculate and plot fit
Next we will hard-code a first-order fit; later, we will make the code flexible for higher order fits.
- In Design View, add a spinner and call it Order
- Click on app.OrderSpinner in the Component Browser; in the Spinner Properties - Configuration, change the minimum value to 0 and change the Display to Integers.
- You will most likely need to calculate the goodness of fit either when you click the Run button or when you change the spinner. This means it would be a good idea to make a separate function that calculates the fit; the callbacks for both the button and the spinner can then call this new function.
- In the Code View, make a new public function called CalcFit
function results = CalcFit(app) end
- Various parts of the app will probably need to know the coefficients of the fit, so add a public property called Coefs.
properties (Access = public) Force % Force applied to beam Disp % Displacement measured at end Coefs % Fit coefficients end
- Add code to the CalcFit program that uses the value in the Order spinner to determine the coefficients of a fit of that order:
function results = CalcFit(app) app.Coefs = polyfit(app.Force, app.Disp, app.OrderSpinner.Value); end
- Add a changed callback to the OrderSpinner to update the plot when the spinner value changes:
function OrderSpinnerValueChanged(app, event) UpdatePlot(app); end
- Add code to the UpdatePlot function to get the new coefficients and use them to plot a model:
function results = UpdatePlot(app) plot(app.UIAxes, app.Force, app.Disp, 'ko') grid(app.UIAxes, 'on') CalcFit(app); FModel = linspace(min(app.Force), max(app.Force), 100); DModel = polyval(app.Coefs, FModel); hold(app.UIAxes, 'on') plot(app.UIAxes, FModel, DModel, 'k-') hold(app.UIAxes, 'off') keyboard end
Determine and display statistics
Once again, determining and displaying statistics will be triggered by multiple events (hitting the button or changing the order) so there might as well be a function for it that various callbacks can use.
- In the Code View, create a public function called CalcStats
- Add code to CalcStats to calculate the coefficient of determination for the fit; leave the semi-colons off for now so you can see these values in the command window and see if they make sense.
function results = CalcStats(app)
Dbar = mean(app.Disp);
Dhat = polyval(app.Coefs, app.Force);
St = sum((app.Disp-Dbar).^2)
Sr = sum((app.Disp-Dhat).^2)
r2 = (St-Sr) / St
end
- Add code to the end of the callbacks for the button and the spinner to run this function:
% Button pushed function: RunButton
function RunButtonPushed(app, event)
Data = load('Beam01.dat');
app.Force = 9.81 * Data(:,1);
app.Disp = 0.0254 * Data(:,2);
UpdatePlot(app);
CalcStats(app);
end
% Value changed function: OrderSpinner
function OrderSpinnerValueChanged(app, event)
UpdatePlot(app);
CalcStats(app);
end
- Test your code - for this data set, note that r2 for a 0 order fit is always 0; r2 for 6th order is almost exactly 1 and r2 for a 7th order fit is exactly 1. If your code is working at this point - congratulations! If not - go ahead and figure out how to fix it before moving on.
Display stats in a table
Now you are going to add a table to your app to display information about the number of points, order of fit, St, Sr, and r2 value.
- In the Design View, drag a Table in.
- There will be five columns named NumPts, Order, St, Sr, r2 -- make these changes in the Names dropdown in the Columns section of the Configuration block in the UITABLE PROPERTIES menu (whew!).
- Make the table wide enough to see all five columns.
- Go to the CodeView. Think about where it would make the most sense to update the table values. CalcStats is where the information will be calculated, so this is probably the right function to also update the table.
- In the CalcStats function, add a line that will load the appropriate information into app.UITable.Data:
function results = CalcStats(app)
Dbar = mean(app.Disp);
Dhat = polyval(app.Coefs, app.Force);
St = sum((app.Disp-Dbar).^2);
Sr = sum((app.Disp-Dhat).^2);
r2 = (St-Sr) / St;
app.UITable.Data = [length(app.Force), app.OrderSpinner.Value, St, Sr, r2];
end
- The table is clearly too tall - in the Design View, you can shrink the table to make the size make sense for the number of rows (1) you have.
Change data sets
One of the requirements of this app s that it be able to choose from among four data sets. One way of selecting a data set might be a discrete knob.
- In the Design view, drag a discrete knob onto the app and relabel it Data Set.
- Click on the app.DataSetKnob in the Component Browser, then in the Knob Properties, change the States Items to 1, 2, 3, 4.
- Now we need to change how the data set is loaded -- instead of always being Beam01.dat, we must dynamically create the function to load the data by taking into account the value of the know. It is important to note that the value of the know is a string not a number. The following code will dynamically create the correct command to load the data:
% Button pushed function: RunButton
function RunButtonPushed(app, event)
Data = eval(sprintf('load(''Beam0%s.dat'')', app.DataSetKnob.Value));
app.Force = 9.81 * Data(:,1);
app.Disp = 0.0254 * Data(:,2);
UpdatePlot(app);
CalcStats(app);
end
- One issue now is that when the knob is moved, the new data set is not immediately loaded; the user has to push the button for that. There are several ways to deal with this. You could add a callback to the knob such that it calls RunButton and the knob value changes. For this exercise, however, you are going to instead add an indicator light that lets the user know if the knob shows the actual data set being plotted. To do this, you are going to:
- Create a property to keep track of the data set being plotted
- Create a public property called Set, initialize it to 0
properties (Access = public)
Force % Force applied to beam
Disp % Displacement measured at end
Coefs % Fit coefficients
Set=1 % Data set in use
end
- Update it whenever the button is pushed:
% Button pushed function: RunButton
function RunButtonPushed(app, event)
Data = eval(sprintf('load(''Beam0%s.dat'')', app.DataSetKnob.Value));
app.Force = 9.81 * Data(:,1);
app.Disp = 0.0254 * Data(:,2);
UpdatePlot(app);
CalcStats(app);
app.Set = app.DataSetKnob.Value;
end
- Whenever the knob changes, compare the current set to the knob value and have a light show green if they are the same or red if not:
- In Design view, drag a lamp near the knob and call it Current
- Add a callback when the knob is changed to make the light green if the knob and the data set match and red otherwise:
% Value changed function: DataSetKnob
function DataSetKnobValueChanged(app, event)
value = app.DataSetKnob.Value;
if app.Set==num2str(value)
app.CurrentLamp.Color=[0 1 0];
else
app.CurrentLamp.Color=[1 0 0];
end
end
- This is fine when the knob changes, but what happens when you load a new data set? Since the knob didn't change, the lamp will not change color. You should also add code to the RunButton function that makes the lamp green again:
% Button pushed function: RunButton
function RunButtonPushed(app, event)
Data = eval(sprintf('load(''Beam0%s.dat'')', app.DataSetKnob.Value));
app.Force = 9.81 * Data(:,1);
app.Disp = 0.0254 * Data(:,2);
UpdatePlot(app);
CalcStats(app);
app.Set = app.DataSetKnob.Value;
app.CurrentLamp.Color=[0 1 0];
end