Code covered by the BSD License  

Highlights from
Table Breakpoint Optimization

image thumbnail

Table Breakpoint Optimization

by

 

04 Apr 2012 (Updated )

A set of tools for finding the best way to reduce the size of a table.

TableOptimizerGui
% TableOptimizerGui
% 
% This class provides a GUI for the following table optimization methods.
% It can import data from the workspace, fit it with various requirements,
% and export the results back to the workspace or to a file.
%
% * Fitting a large table to a small table of a given size (1D or 2D)
% * Fitting a large table to a small table with given tolerable amounts of
%   error (mean squared error/maximum error anywhere)
%
% Requires the Optimization Toolbox (TM).
% Supports the Parallel Computing Toolbox (TM).
%
%  Tucker McClure @ The MathWorks
%  Copyright 2013 The MathWorks, Inc.

classdef TableOptimizerGui < handle

    properties
        
        % Storage for UI handles
        UiComponents;
        
        % Storage for original data
        Data;
        
        % Storage for results, including error and selected method
        Results;
        
        % Default values for workspace outputs.
        DefaultVariableNames1D = {'', '', '', '', ''};
        DefaultVariableNames2D = {'', '', '', '', '', ''};
        
        % All plots default to surfaces.
        ContourPlots = false(1, 3);
        
    end
    
    methods
        
        % When we construct, we'll just draw the GUI and wait for input.
        function this = TableOptimizerGui()
            this.DrawFigure();
        end
        
        % Draw the GUI and wait for user input.
        function DrawFigure(this)
            
            % Define the outer dimensions.
            width  = 800;
            height = 640;
            
            % Get the screen size.
            screen_size = feval(@(x) x(3:4), get(0, 'ScreenSize'));
            
            % Center a figure.
            h_figure = figure();
            set(h_figure, ...
                'Name',             'Table Optimizer', ...
                'Position',         [0.5*(screen_size - [width height]) ...
                                     width height], ...
                'NumberTitle',      'off', ...
                'Units',            'pixels', ...
                'Resize',           'off', ...
                'MenuBar',          'none', ...
                'ToolBar',          'figure', ...
                'KeyPressFcn',      @this.PressKey);
                
            % Panel for inputs
            h_panel = uipanel('Units',    'pixels', ...
                              'Position', [90 1 300 315]);
            
            % X data input
            tip = ['Enter the name of the workspace variable that ' ...
                   'contains the locations of the current X breakpoints.'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 294 70 26], ...
                      'String',              'X Breakpoints', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.x_0 = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [100 300 180 25], ...
                                        'String',   '', ...
                                        'Tooltip',  tip);
                                     
            % Y data input
            tip = ['Enter the name of the workspace variable that ' ...
                   'contains the locations of the current Y ' ...
                   'breakpoints (if using 2D data).'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 264 70 26], ...
                      'String',              'Y Breakpoints', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.y_0 = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [100 270 180 25], ...
                                        'String',   '', ...
                                        'Tooltip',  tip);

            % Table data input
            tip = ['Enter the name of workspace variable that contains '...
                   'the table data (if 2D, it should be in ' ...
                   '''meshgrid'' format.)'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 234 70 26], ...
                      'String',              'Table Data', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.z_0 = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [100 240 180 25], ...
                                        'String',   '', ...
                                        'Tooltip',  tip);

            % Number of X points
            tip = ['Enter the desired number of breakpoints for the X ' ...
                   'variable.'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 204 100 26], ...
                      'String',              'Number of X Points', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.n_x = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [170 210 110 25], ...
                                        'String',   '', ...
                                        'Tooltip',  tip);
            
            % Number of Y points
            tip = ['Enter the desired number of breakpoints for the Y ' ...
                   'variable (if using a 2D table).'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 174 100 26], ...
                      'String',              'Number of Y Points', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.n_y = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [170 180 110 25], ...
                                        'String',   '', ...
                                        'Tooltip',  tip);
            
            % Target mean squared error
            tip = ['Enter the maximum allowable mean squared error for '...
                   'reduced table. The mean squared error is ' ...
                   'calculated by evaluating the reduced table at the ' ...
                   'original breakpoints.'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 144 150 26], ...
                      'String',           'Target Mean Squared Error*', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.mse_target = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [170 150 110 25],...
                                        'String',   '', ...
                                        'Tooltip',  tip);
                                    
            % Target maximum error
            tip = ['Enter the maximum allowable error anywhere for ' ...
                   'reduced table. The error is ' ...
                   'calculated by evaluating the reduced table at the ' ...
                   'original breakpoints.'];
            uicontrol('Style',               'text', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 114 150 26], ...
                      'String',              'Target Maximum Error*', ...
                      'HorizontalAlignment', 'Left', ...
                      'Tooltip',             tip);
            this.UiComponents.max_error = uicontrol( ...
                                        'Style',    'edit', ...
                                        'Parent',   h_panel, ...
                                        'Position', [170 120 110 25],...
                                        'String',   '', ...
                                        'Tooltip',  tip);
                                    
            uicontrol('Style', 'text', ...
                      'Parent', h_panel, ...
                      'Position', [20 100 260 15], ...
                      'String', ['* Enter numbers of x/y points '...
                                 'OR max MSE/error.'], ...
                      'HorizontalAlignment', 'Left');
            

            % Method
            uicontrol('Style', 'text', ...
                      'Parent', h_panel, ...
                      'Position', [20 64 150 26], ...
                      'String', 'Method', ...
                      'HorizontalAlignment', 'Left');
            this.UiComponents.method = uicontrol('Style', 'popup', ...
                                        'BackgroundColor', 'w', ...
                                        'Parent', h_panel, ...
                                        'Position', [170 70 110 25],...
                                        'String', {'linear', 'spline', ...
                                                   'nearest'});
                                    
            % Result string
            this.UiComponents.results_text = uicontrol('Style', 'text', ...
                                      'Parent', h_panel, ...
                                      'Position', [20 53 260 15], ...
                                      'String', 'No results yet.', ...
                                      'HorizontalAlignment', 'Center');
                  
            % Go button
            uicontrol('Style',              'pushbutton', ...
                      'Parent',              h_panel, ...
                      'Position',            [20 20 80 30], ...
                      'String',              'Fit', ...
                      'Callback',            @this.Fit, ...
                      'HorizontalAlignment', 'Left');
                  
            % Export button
            this.UiComponents.export = uicontrol(...
                      'Style',               'pushbutton', ...
                      'Parent',              h_panel, ...
                      'Position',            [110 20 80 30], ...
                      'String',              'Export', ...
                      'Callback',            @this.Export, ...
                      'HorizontalAlignment', 'Left', ...
                      'Enable',              'off');
                  
            % Help button
            uicontrol('Style',               'pushbutton', ...
                      'Parent',              h_panel, ...
                      'Position',            [200 20 80 30], ...
                      'String',              'Help', ...
                      'Callback',            @this.Help, ...
                      'HorizontalAlignment', 'Left');
                  
            % Plots
            this.UiComponents.original  = subplot(2, 2, 1);
            this.UiComponents.fit       = subplot(2, 2, 2);
            this.UiComponents.residuals = subplot(2, 2, 4);
            this.ClearAxes();
            
            % Niceties
            background_color = 0.9 * [1 1 1];
            set(findobj('Parent', h_panel, ...
                        'Type',   'uicontrol', ...
                        'Style', 'text'), ...
                'Background', background_color);
            set(findobj('Parent', h_panel, ...
                        'Type',   'uicontrol', ...
                        'Style', 'edit'), ...
                'Background', [1 1 1]);
            set(findobj('Parent', h_panel, ...
                        'Type',   'popup', ...
                        'Style', 'edit'), ...
                'Background', [1 1 1]);
            set(h_figure, 'Color', background_color);
            set(h_panel, ...
                'BorderType', 'none', ...
                'BackgroundColor', background_color);

            % Done drawing. Lock down the figure.
            set(h_figure, 'HandleVisibility', 'on');
            
        end

        % Perform the actual fit by accessing the requested data, calling
        % the appropriate function, storing the results, and updating the
        % plots.
        function Fit(this, ~, ~)
                     
            % Clear out old results.
            this.ClearAxes();
            this.Results = [];
            set(this.UiComponents.export, 'Enable', 'off');
            drawnow();
            
            % Get all the input values.
            x_0    = this.GetValue(this.UiComponents.x_0);
            y_0    = this.GetValue(this.UiComponents.y_0);
            z_0    = this.GetValue(this.UiComponents.z_0);
            n_x    = this.GetValue(this.UiComponents.n_x);
            n_y    = this.GetValue(this.UiComponents.n_y);
            mse_t  = this.GetValue(this.UiComponents.mse_target);
            me_t   = this.GetValue(this.UiComponents.max_error);
            
            methods = get(this.UiComponents.method, 'String');
            method = methods{get(this.UiComponents.method, 'Value')};
            
            % Make sure x_0 is a vector.
            if ~isempty(x_0) && sum(size(x_0) > 1) > 1
                errordlg('X and Y values should be vectors.');
                return;
            end
            
            % Make sure x_0 is a vector.
            if ~isempty(y_0) && sum(size(y_0) > 1) > 1
                errordlg('X and Y values should be vectors.');
                return;
            end
            
            % If y and z are supplied, make sure z_0 is a matrix.
            if ~isempty(y_0) && ~isempty(z_0) && sum(size(z_0) > 1) ~= 2
                errordlg('Z values should be matrices.');
                return;
            end
            
            % Make sure n_x and n_y are integers.
            if    (~isempty(n_x) && (~isscalar(n_x) || mod(n_x,1) ~= 0))...
               || (~isempty(n_y) && (~isscalar(n_y) || mod(n_y,1) ~= 0))
                errordlg(['The number of breakpoints should be an ' ...
                          'integer.']);
                return;
            end
            
            % Make sure n_x and n_y are integers.
            if    (~isempty(n_x) && n_x < 3) ...
               || (~isempty(n_y) && n_y < 3)
                errordlg('At least three breakpoints are required.');
                return;
            end
            
            % Make sure MSE is a scalar.
            if ~isempty(mse_t) && (~isscalar(mse_t) || mse_t <= 0)
                errordlg(['The target mean squared error, if '...
                          'specified, should be a positive scalar.']);
                return;
            end
            
            % Make sure max error is a scalar.
            if ~isempty(me_t) && (~isscalar(me_t) || me_t <= 0)
                errordlg(['The maximum error, if '...
                          'specified, should be a positive scalar.']);
                return;
            end
            
            % Ready for the fits!
            fprintf('Fitting...\n');
            
            % Give the user a message box.
            h_msg = msgbox('Fitting! Use ctrl+c to cancel.', ...
                           'Fitting...', ...
                           'help');
            
            % User might not have entered Y data.
            y = [];
            
            % If 2d with breakpoints specified...
            if    ~isempty(x_0) && ~isempty(y_0) && ~isempty(z_0) ...
               && ~isempty(n_x) && ~isempty(n_y) && isempty([mse_t me_t])
           
                [x, y, z, mse, me] = find_best_table_2d(x_0, y_0, z_0, ...
                                                        n_x, n_y, ...
                                                        method);
                
            % If 2d with MSE specified...
            elseif ~isempty(x_0) && ~isempty(y_0) && ~isempty(z_0) ...
                &&  isempty(n_x) &&  isempty(n_y) && ~isempty([mse_t me_t])
               
                [x, y, z, mse, me] = find_best_table_2de(x_0, y_0, z_0, ...
                                                         mse_t, me_t, ...
                                                         method);
                
            % If 1d with breakpoints specified...
            elseif (   ~isempty(x_0) && ~isempty(y_0) &&  isempty(z_0)  ...
                    || ~isempty(x_0) &&  isempty(y_0) && ~isempty(z_0)) ...
                   && ~isempty(n_x) && isempty([mse_t me_t])
               
                % User might enter with y_0 or z_0. We'll use them anyway.
                if ~isempty(y_0)
                    [x, z, mse, me] = find_best_table_nd({x_0}, y_0(:), ...
                                                         n_x, method);
                    x = x{1};
                else
                    [x, z, mse, me] = find_best_table_nd({x_0}, z_0(:), ...
                                                         n_x, method);
                    x = x{1};
                end
                

            % If 1d with MSE specified...
            elseif (   ~isempty(x_0) && ~isempty(y_0) &&  isempty(z_0)  ...
                    || ~isempty(x_0) &&  isempty(y_0) && ~isempty(z_0)) ...
                   && isempty(n_x) && ~isempty([mse_t me_t])

                % User might enter with y_0 or z_0. We'll use them anyway.
                if ~isempty(y_0)
                    [x, z, mse, me] = find_best_table_nde({x_0}, y_0(:), ...
                                                          mse_t, me_t, ...
                                                          method);
                    x = x{1};
                else
                    [x, z, mse, me] = find_best_table_nde({x_0}, z_0(:), ...
                                                          mse_t, me_t, ...
                                                          method);
                    x = x{1};
                end
                
            else
                errordlg(['Please specify x or x, y, and table data ' ...
                          'to fit along with either a number of x and ' ...
                          'y breakpoints or a maximum error/MSE.']);
                return;
            end
            
            % Close the wait box.
            if ishandle(h_msg)
                close(h_msg);
            end
            
            % Store them for later use.
            this.Data.x_0       = x_0;
            this.Data.y_0       = y_0;
            this.Data.z_0       = z_0;
            this.Data.n_x       = n_x;
            this.Data.n_y       = n_y;
            this.Data.mse_t     = mse_t;
            this.Data.me_t      = me_t;
            this.Results.x      = x;
            this.Results.y      = y;
            this.Results.z      = z;
            this.Results.mse    = mse;
            this.Results.me     = me;
            this.Results.method = method;

            % Update all the plots.
            this.UpdatePlots();
            
            % Update the results string.
            if ~isempty(this.Results.y)
                set(this.UiComponents.results_text, ...
                    'String', sprintf(['Fit %dx%d table with ' ...
                                       'MSE %f, ME %f.'], ...
                                      length(x), length(y), mse, me));
            else
                set(this.UiComponents.results_text, ...
                    'String', sprintf(['Fit 1x%d table with ' ...
                                       'MSE %f, ME %f.'], length(x), ...
                                      mse, me));
            end
            
            set(this.UiComponents.export, 'Enable', 'on');
            
            fprintf('Done.\n');
            
        end
        
        % Update all three plots.
        function UpdatePlots(this)
            
            if ~isempty(this.Results.y) && ~isempty(this.Results.z)
                
                % Turn the original and resulting x and y into meshes, then
                % interpolate the fit z at the origianl data points. This
                % will help us show the method along with the final points
                % and will help us calculate the residuals.
                [xm, ym]   = meshgrid(this.Results.x, this.Results.y);
                [x0m, y0m] = meshgrid(this.Data.x_0, this.Data.y_0);
                zm = interp2(xm, ym, this.Results.z, x0m, y0m, ...
                             this.Results.method);
                
                % Update the plot of the original data.
                set(gcf, 'CurrentAxes', this.UiComponents.original);
                if this.ContourPlots(1)
                    contourf(x0m, y0m, this.Data.z_0, 20);
                    xlabel('x'); ylabel('y');
                    switch_to = 'surface';
                else
                    surf(this.Data.x_0, this.Data.y_0, this.Data.z_0);
                    shading interp;
                    xlabel('x'); ylabel('y'); zlabel('z');
                    switch_to = 'contour';
                end
                c_axis = caxis();
                title({'Original', ['(click here for ' switch_to ')']}, ...
                      'ButtonDownFcn', {@this.ToggleContour, 1});
                
                % Update the plot of the fit data.
                set(gcf, 'CurrentAxes', this.UiComponents.fit);
                if this.ContourPlots(2)
                    contourf(x0m, y0m, zm, 20);
                    hold on;
                    plot(xm(:), ym(:), 'k.', 'MarkerSize', 1);
                    hold off;
                    xlabel('x'); ylabel('y');
                    switch_to = 'surface';
                else
                    surf(x0m, y0m, zm);
                    hold on;
                    plot3(xm(:), ym(:), this.Results.z(:), 'k.', ...
                          'MarkerSize', 1);
                    hold off;
                    shading interp;
                    xlabel('x'); ylabel('y'); zlabel('z');
                    switch_to = 'contour';
                end
                caxis(c_axis);
                title({'Fit', ['(click here for ' switch_to ')']}, ...
                      'ButtonDownFcn', {@this.ToggleContour, 2});
                 
                % Show the residuals.
                residuals = this.Data.z_0 - zm;
                set(gcf, 'CurrentAxes', this.UiComponents.residuals);
                if this.ContourPlots(3)
                    contourf(x0m, y0m, residuals, 20);
                    xlabel('x'); ylabel('y');
                    switch_to = 'surface';
                else
                    surf(x0m, y0m, residuals);
                    shading interp;
                    xlabel('x'); ylabel('y'); zlabel('z');
                    switch_to = 'contour';
                end
                title({'Residuals', ['(click here for ' switch_to ')']},...
                      'ButtonDownFcn', {@this.ToggleContour, 3});
                
            else
                
                % The user can specify y or z data for the 1D case, so just
                % figure out which he used.
                if ~isempty(this.Data.y_0)
                    dep_original = this.Data.y_0;
                else
                    dep_original = this.Data.z_0;
                end
                
                % Update the plot of the original data.
                set(gcf, 'CurrentAxes', this.UiComponents.original);
                plot(this.Data.x_0, dep_original);
                xlabel('x'); zlabel('z');
                title('Original');
                 
                % Update the plot of the fit data.
                set(gcf, 'CurrentAxes', this.UiComponents.fit);
                plot(this.Data.x_0, interp1(this.Results.x, ...
                                            this.Results.z, ...
                                            this.Data.x_0, ...
                                            this.Results.method), 'b-', ...
                     this.Results.x, this.Results.z, 'b.');
                xlabel('x'); zlabel('z');
                title('Fit');
                 
                % Show the residuals.
                residuals = dep_original ...
                          - interp1(this.Results.x, this.Results.z, ...
                                    this.Data.x_0, this.Results.method);
                set(gcf, 'CurrentAxes', this.UiComponents.residuals);
                plot(this.Data.x_0, residuals);
                xlabel('x'); zlabel('z');
                title('Residuals');
                
            end
            
        end
        
        % Prompt the user for variable names and export to the workspace.
        function finished = ExportToWorkspace(this, header, required)
            
            % We're definitely not done yet.
            finished = false;

            % Default to no requirements.
            if nargin < 3, required = []; end
            
            % Add a header line to the export prompt.
            if nargin >= 2
                header = sprintf('%s\n\n', header);
            else
                header = [];
            end
            
            info = sprintf(['This will export the resulting table and ' ...
                            'error characteristics to the workspace ' ...
                            'with the provided names. For instance, ' ...
                            'you could output the X breakpoints to a ' ...
                            'variable named x_out or a structure such '...
                            'as my_results.x.\n\n']);
            
            % Record the question titles, defaults, and possible values.
            titles = {'X breakpoints', ...
                      'Y breakpoints', ...
                      'Table data (Z data)', ...
                      'Mean squared error', ...
                      'Maximum error', ...
                      'Method'};
            values = {this.Results.x, ...
                      this.Results.y, ...
                      this.Results.z, ...
                      this.Results.mse, ...
                      this.Results.me, ...
                      this.Results.method};

            % If this is only 2D, drop the y part.
            if isempty(this.Results.y)
                titles(2)   = [];
                values(2)   = [];
                defaults = this.DefaultVariableNames1D;
            else
                defaults = this.DefaultVariableNames2D;
            end

            % Ask for variable names.
            while true
                
                names = inputdlg({[header info titles{1}], ...
                                  titles{2:end}}, ...
                                 'Output Names', ...
                                 1, ...
                                 defaults);
                if isempty(names), return, end;
                
                % If the user didn't fill in any requirements.
                if any(cellfun(@(x) isempty(x), names(required)))
                    warndlg(['The following variables are required ' ...
                             'for this export operation: ' ...
                             sprintf('\n %s', titles{required})]);
                    uiwait();
                else
                    break
                end
                
            end

            % Write the names to the base workspace.
            for k = 1:length(names)

                % If the user entered something, try to save it.
                if ~isempty(names{k})

                    % The user could enter anything. Let's try to avoid 
                    % crashing.
                    try

                        % If it's a struct...
                        if strfind(names{k}, '.')

                            % Make a dummy variable, assign it in the
                            % workspace, create the struct with evalin and
                            % assign to the dummy, then clear the dummy.
                            var = ['temp_table_data_' ...
                                   datestr(now(), 'yyyy_mm_dd_HH_MM_SS')];
                            assignin('base', var, values{k});
                            evalin('base', [names{k} ' = ' var ';']);
                            evalin('base', ['clear ' var ';']);

                        % Otherwise, just use assignin.
                        else
                            assignin('base', names{k}, values{k});
                        end
                        
                    % Tell the user it didn't work and bail.
                    catch %#ok<CTCH>
                        warndlg(['There was an error exporting to ''' ...
                                 names{k} '''.']);
                        return;
                    end

        
                end

            end

            % If successful, store the names.
            if isempty(this.Results.y)
                this.DefaultVariableNames1D = names;
            else
                this.DefaultVariableNames2D = names;
            end

            finished = true;
            
        end
        
        % Save the results of the fit to the workspace, a .mat file, or a
        % Simulink block.
        function Export(this, ~, ~)
            
            % See if we have results to export.
            if isempty(this.Results)
                errordlg('There are no results to export yet.');
                return;
            end
            
            % Targets
            targets = {'Workspace', '.mat File'};
            
            % Add Simulink option if Simulink is installed.
            if exist('simulink', 'builtin')
                targets{end+1} = 'Simulink';
            else
                targets{end+1} = 'Cancel';
            end
            
            % Ask if the user would like to export to workspace or .mat
            % file.
            a = questdlg('Export to workspace or .mat file?', ...
                         'Export To?', targets{:}, 'Workspace');
                     
            switch a
                
                case 'Workspace'

                    this.ExportToWorkspace();
                    
                case '.mat File'
                    
                    [f, p] = uiputfile('*.mat', 'Save to .mat file.');
                    if ~ischar(f) || ~ischar(p)
                        return;
                    end
                    results = this.Results; %#ok<NASGU>
                    save([p f], 'results');
                    
                case 'Simulink'

                    info = ['First, the data must be exported to ' ...
                            'the workspace. The Simulink block will ' ...
                            'reference these variables.'];
                    if ~this.ExportToWorkspace(info, [true true true ...
                                                      false false false])
                        return;
                    end
                    
                    % Open Simulink and create a new temporary model.
                    m = ['TableOptimizerOutput_' ...
                         datestr(now(), 'yyyy_mm_dd_HH_MM_SS')];
                    mt = [m '/Optimized Lookup Table'];
                    simulink();
                    new_system(m);
                    open_system(m);
                     
                    % See if it's 1D or 2D.
                    if isempty(this.Results.y)
                        table_name = '1-D Lookup Table';
                        x_variable = this.DefaultVariableNames1D{1};
                        z_variable = this.DefaultVariableNames1D{3};
                    else
                        table_name = '2-D Lookup Table';
                        x_variable = this.DefaultVariableNames2D{1};
                        y_variable = this.DefaultVariableNames2D{2};
                        z_variable = this.DefaultVariableNames2D{3};
                    end
                    
                    method = this.Results.method;
                    if strcmp(method, 'spline')
                        method = 'Cubic spline';
                    elseif strcmp(method, 'nearest')
                        method = 'Flat';
                    end
                    
                    % Add the block and set up the parameters.
                    add_block(['simulink/Lookup Tables/' table_name], mt);
                    set_param(mt, ...
                        'BreakpointsForDimension1', x_variable, ...
                        'InterpMethod',             method,...
                        'ExtrapMethod',             'clip', ...
                        'UseLastTableValue',        'on');
                    
                    % If it's 1D, we can just use the table.
                    if isempty(this.Results.y)
                        set_param(mt, 'Table', z_variable);
                    else
                        % But if it's 2D, then meshgrid works such that 'x'
                        % changes across dimension 2 and 'y' across
                        % dimension 1. We'll make everything work by
                        % transposing the output table in the block.
                        set_param(mt, ...
                          'Table',                    [z_variable ''''],...
                          'BreakpointsForDimension2', y_variable);
                    end
                    
            end
            
        end
        
        % This is strictly so that the user can press F1 for help.
        function PressKey(this, ~, event)
            if strcmp(event.Key, 'f1')
                this.Help();
            end
        end
        
        function ToggleContour(this, ~, ~, n)
            this.ContourPlots(n) = ~this.ContourPlots(n);
            this.UpdatePlots();
        end
        
    end
    
    methods (Static = true)
        
        % Try to evaluate the string stored in 'handle' in the base
        % workspace. Return the value or empty if the string is empty.
        function v = GetValue(handle)
            
            s = get(handle, 'String');
            if ~isempty(s)
                try
                    v = evalin('base', s);
                catch the_error %#ok<NASGU>
                    warndlg(sprintf(['Could not interpret ''%s'' ' ...
                                     'in base workspace.'], s));
                end
            else
                v = [];
            end
                
        end
        
        % Open help example.
        function Help(varargin)
            edit('find_best_table_demo.m');
            open('find_best_table_demo.pdf');
        end
        
        % Just clean up those axes a bit.
        function ClearAxes()
            
            % clear the plots.
            subplot(2, 2, 1);
            cla(); title('Original');  xlabel('X'); ylabel('Z');
            subplot(2, 2, 2);
            cla(); title('Fit');       xlabel('X'); ylabel('Z');
            subplot(2, 2, 4);
            cla(); title('Residuals'); xlabel('X'); ylabel('Z');
            
        end
        
    end
    
end

Contact us