User:DukeEgr93/CBApp

From PrattWiki
Jump to navigation Jump to search

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