classdef lu_deb_sinha
    %{
        Finding Reliable Solutions in Bilevel Optimization Problems Under Uncertainties.
        Zhichao Lu, Kalyanmoy Deb, Ankur Sinha.
        In Proceedings of the Genetic and Evolutionary Computation Conference 2016. 
        https://www.egr.msu.edu/~kdeb/papers/c2016026.pdf
        https://doi.org/10.1145/2908812.2908925
    %}
    properties(Constant)
        name = 'lu_deb_sinha';
        category = 'textbook';
        subcategory = '';
        datasets = {
            'lu_deb_sinha_a.txt';  % Unconstrained and sensitive objective functions at both levels;
            'lu_deb_sinha_b.txt';  % Unconstrained and sensitive objective function at lower-level alone;
            'lu_deb_sinha_c.txt';  % Unconstrained and sensitive objective function at upper-level alone;
            'lu_deb_sinha_d.txt';  % Constraint functions at both levels;
            'lu_deb_sinha_e.txt';  % Constraint functions at lower-level only;
            'lu_deb_sinha_f.txt';  % Constraint functions at upper-level only;
        };
        paths = fullfile('bolib3', 'data', 'nonlinear', lu_deb_sinha.datasets);
    end


    methods(Static)
    

        % Upper-level objective function
        function obj = F(x, y, data)
            switch data
                case {'a','c'}
                    obj = 2.0 ...
                        - exp(-(((0.2*y(1) - x(1) + 0.6)/0.055) .^ 0.4)) ...
                        - 0.8 * exp(-(((0.15*y(1) - 0.4 + x(1))/0.3) .^ 2));
                case 'b'
                    obj = (x(1) - 0.5).^2 + (y(1) - 1.0).^2;
        
                case {'d','f'}
                    obj = -x(2);
        
                case 'e'
                    obj = (( (y(2) - 50) / 30 ) .^ 2) ...
                        + ((( (x(1) - 2.5) / 0.2 ) .^ 2));
                otherwise
                    error('lu_deb_sinha must be parameterised by one of the six cases ''a'',''b'',''c'',''d'',''e'',''f''.');
            end
        end

        
        % Upper-level inequality constraints
        function val = G(x, y, data)
            switch data
                case {'a','b','c'}
                    val = [
                        x(1);
                        1 - x(1);
                        y(1);
                        2 - y(1)
                    ];
                case 'd'
                    t = (y(1)/14) + (16/7);
                    val = [ 
                        t*((x(1)-2)^2) - x(2);
                        x(2) - 12.5*t*(x(1)-5);
                        5*(x(1)+4 - t)*(x(1)+8 - t) - x(2);
                        4 + x(1);
                        10 - x(1);
                        100 + x(2);
                        200 - x(2);
                        4 + y(1);
                        10 - y(1);
                        100 + y(2);
                        200 - y(2)
                    ];
                case 'e'
                    val = [
                         x(1) - 2;
                        -x(1) + 3;
                         y(1) + 4;
                        -y(1) + 10;
                         y(2) + 100;
                        -y(2) + 200
                    ];
                case 'f'
                    t = x(1)/20;
                    val = [
                        y(1)*((t - 2)^2) + x(2);
                        x(2) - 12.5*y(1)*(t - 5);
                          5*(t + 4 - y(1))*(t + 8 - y(1)) - x(2);
                          x(1) + 80;
                         -x(1) + 200;
                          x(2) + 100;
                         -x(2) + 200;
                          y(1) - 2;
                         -y(1) + 4
                    ];
                otherwise
                    error('lu_deb_sinha must be parameterised by one of the six cases ''a'',''b'',''c'',''d'',''e'',''f''.');
            end
        end

        
        % Upper-level equality constraints
        function val = H(~, ~, ~)
            val = [];
        end
        
        % Lower-level objective function
        function obj = f(x, y, data)
            switch data
                case {'a','b'}
                    obj = 2.0 ...
                        - exp(-(((1.5*y(1) - x(1))/0.055) ^ 0.4)) ...
                        - 0.8 * exp(-(((2*y(1) - 3 + x(1))/0.5) ^ 2));
                case 'c'
                    obj = (x(1) - 0.5)^2 ...
                        + (y(1) - 1)^2;
                case {'d','e'}
                    obj = -y(2);
                case 'f'
                    obj = (((x(1) - 50)/28) ^ 2) ...
                        + (((y(1) - 2.5)/0.2) ^ 2);
                otherwise
                    error('lu_deb_sinha must be parameterised by one of the six cases ''a'',''b'',''c'',''d'',''e'',''f''.');
            end
        end

        
        % Lower-level inequality constraints
        function gvals = g(x, y, data)
            switch data
                case {'a','b','c','f'}
                    gvals = [];
                case 'd'
                    t = (x(1)/14) + (16/7);
                    gvals = [
                        t*((x(1)-2)^2) - y(2);
                        y(2) - 12.5*t*(y(1)-5);
                        5*(y(1)+4 - t)*(y(1)+8 - t) - y(2)
                    ];
                case 'e'
                    gvals = [
                        x(1)*(y(1)-2)^2 - y(2);
                        y(2) - 12.5*x(1)*(y(1)-5);
                        5*(y(1)+4 - x(1))*(y(1)+8 - x(1)) - y(2)
                    ];
               otherwise
                    error('lu_deb_sinha must be parameterised by one of the six cases ''a'',''b'',''c'',''d'',''e'',''f''.');
            end
        end

        
        % Lower-level equality constraints
        function val = h(~, ~, ~)
            val = [];
        end

        % read_data loads the Lu–Deb–Sinha case identifier from a text file.
        function data = read_data(filepath)

        
            % Validate the input is a valid filepath
            if ~(ischar(filepath) || isstring(filepath))
                error('read_data:InvalidPathType', ...
                      'filepath must be a character vector or string.');
            end
            filepath = char(filepath);
            if isempty(strtrim(filepath))
                error('read_data:EmptyPath', 'filepath is empty.');
            end
            if ~exist(filepath, 'file')
                error('read_data:FileNotFound', 'File not found: %s', filepath);
            end
        
            % Read the data
            try
                raw = fileread(filepath);
            catch ME
                error('read_data:ReadError', ...
                      'Unable to read file "%s": %s', filepath, ME.message);
            end
        
            % Strip surrounding matching quotes if present
            s = strtrim(raw);
            if ~isempty(s) && numel(s) >= 2
                if (s(1) == '"'  && s(end) == '"') || (s(1) == '''' && s(end) == '''')
                    s = strtrim(s(2:end-1));
                end
            end
            s = lower(s);
        
            % Validate
            valid = ['a','b','c','d','e','f'];
            if isempty(s) || numel(s) ~= 1 || ~ismember(s, valid)
                error('read_data:InvalidData', ...
                      'File "%s" must contain exactly one of {a,b,c,d,e,f}. Found: "%s"', ...
                      filepath, s);
            end
        
            % Return the case identifier
            data = s;
        end


        % Dimension
        function n = dimension(key, data)        
            % ---- Base dimensions ----
            dims = struct( ...
                'x', 1, ...   % upper-level variables
                'y', 1, ...   % lower-level variables
                'F', 1, ...   % upper-level objective
                'G', 4, ...   % upper-level inequality constraints
                'H', 0, ...   % upper-level equality constraints
                'f', 1, ...   % lower-level objective
                'g', 0, ...   % lower-level inequality constraints
                'h', 0);      % lower-level equality constraints
        
            % ---- Adjust for case ----
            switch data
                case 'd'
                    dims.x = 2; dims.y = 2; dims.G = 11; dims.g = 3;
                case 'e'
                    dims.y = 2; dims.G = 6;  dims.g = 3;
                case 'f'
                    dims.x = 2; dims.G = 9;
            end
        
            % ---- Return result ----
            if isempty(key)
                n = dims;
            else
                if ~isfield(dims, key)
                    error('dimension:InvalidKey', ...
                          'Invalid key "%s". Must be one of x,y,F,G,H,f,g,h.', key);
                end
                n = dims.(key);
            end
        end
    end
end