%% Customize FIS Tuning Process
% You can customize the FIS tuning process by specifying either a custom cost function or a custom optimization method.
% For more information on tuning a FIS, see Tune Fuzzy Rules and Membership Functions and Tune Fuzzy Trees.
%% Tune FIS Using Custom Cost Function
% You can specify a custom cost function for tuning your fuzzy system. Doing so is useful for:
% Training a FIS using a custom model without using input/output training data. For an example, see Tune Fuzzy Robot Obstacle Avoidance System Using Custom Cost Function.
% Combining the outputs of the component FISs of a FIS tree using mathematical operations, as shown in this example.
% As an example, consider the FIS tree from Tune Fuzzy Trees. Suppose you cant you want to modify the FIS tree as shown in the following diagram, combining the FIS outputs using known mathematical operations from the training data.
%
% Create the FIS tree.

fis1 = sugfis('Name','fis1');
fis1 = addInput(fis1,[0 10],'NumMFs',3,'MFType','gaussmf');
fis1 = addOutput(fis1,[-1 1],'NumMFs',3);

fis2 = sugfis('Name','fis2');
fis2 = addInput(fis2,[0 10],'NumMFs',3,'MFType','gaussmf');
fis2 = addOutput(fis2,[-1 1],'NumMFs',3);

fis3 = sugfis('Name','fis3');
fis3 = addInput(fis3,[0 10],'NumMFs',3,'MFType','gaussmf');
fis3 = addOutput(fis3,[0 1],'NumMFs',3);

con = ["fis1/input1" "fis2/input1";"fis2/input1" "fis3/input1"];

fisT = fistree([fis1 fis2 fis3],con);

% Generate training data.

x = (0:0.1:10)';
y1 = sin(x)+cos(x);
y2 = y1./exp(x);
y = [y1;y2];

% To implement the addition and multiplication operations, use a custom cost function. For this example, use the function customcostfcn, included at the end of the example. Learn a rule base using this cost function.

options = tunefisOptions('Method',"particleswarm",'OptimizationType',"learning");
options.MethodOptions.MaxIterations = 5;
rng('default')
fisTout1 = tunefis(fisT,[],@(fis)customcostfcn(fis,x,y),options);

%
% Next, tune all the parameters of the FIS tree.

options.Method = 'patternsearch';
options.MethodOptions.MaxIterations = 25;
[in,out,rule] = getTunableSettings(fisTout1);
rng('default')
fisTout2 = tunefis(fisTout1,[in;out;rule],@(fis)customcostfcn(fis,x,y),options);

% You can add more input/output MFs and specify additional FIS tree outputs to improve the tuning performance. Using additional MF parameters and more training data for additional FIS tree outputs can further fine tune the outputs of fis1, fis2, and fis3.
%
%% Tune FIS Using Custom Optimization Method
% You can also implement your own FIS parameter optimization method using getTunableSettings, getTunableValues, and setTunableValues. This example uses these functions to tune a rule base of a fuzzy system.
% Create a FIS to approximate \sin \left(\theta \right), where \theta varies from 0 to 2\pi.

fisin = mamfis;

% Add an input with a range of [0, 2\pi] and having five Gaussian MFs. Also, ass an output with a range of [–1, 1] and having five Gaussian MFs.

fisin = addInput(fisin,[0 2*pi],'NumMFs',5,'MFType','gaussmf');
fisin = addOutput(fisin,[-1 1],'NumMFs',5,'MFType','gaussmf');

% Add five rules.

fisin = addRule(fisin,[1 1 1 1;2 2 1 1;3 3 1 1;4 4 1 1;5 5 1 1]);
fisin.Rules

% For a faster FIS update, set DisableStructuralChecks to true.

fisin.DisableStructuralChecks = true;

% Obtain the rule parameter settings.

[~,~,rule] = getTunableSettings(fisin);

% Make the rule antecedents nontunable. In the rule consequents, do not allow NOT logic (negative MF indices) or empty variables (zero MF indices).

for i = 1:numel(rule)
    rule(i).Antecedent.Free = false;
    rule(i).Consequent.AllowNot = false;
    rule(i).Consequent.AllowEmpty = false;
end

% Generate data for tuning.

x = (0:0.1:2*pi)';
y = sin(x);

% To tune the rule parameters, use the customtunefis function defined at the end of this example. Set the number of iterations to 2, and do not allow invalid parameter values when updating the FIS using setTunableValues.

numite = 2;
ignoreinvp = false;
fisout = customtunefis(fisin,rule,x,y,numite,ignoreinvp);

% Display the tuned rules.

fisout.Rules

%
% Allow NOT logic in the rules, and optimize the FIS again.

for i = 1:numel(rule)
    rule(i).Consequent.AllowNot = true;
end
fisout = customtunefis(fisin,rule,x,y,numite,ignoreinvp);
fisout.Rules

% With NOT logic, there are more combinations of rule parameters, and it generally takes more iterations to tune a FIS.
%
% Next, reset AllowNot to false and set AllowEmpty to true. In other words, allow the absence of variables (zero output MF indices) in the consequents. Tune the FIS with the updated rule parameter settings.

for i = 1:numel(rule)
    rule(i).Consequent.AllowNot = false;
    rule(i).Consequent.AllowEmpty = true;
end

try
    fisout = customtunefis(fisin,rule,x,y,numite,ignoreinvp);
catch me
    disp("Error: "+me.message)
end

% The tuning process fails since the FIS only contains one output, which must be nonzero (nonempty) in the rule consequent. To ignore invalid parameter values, specify IgnoreInvalidParameters with setTunableValues.
% Set ignoreinvp to true, which specifies IgnoreInvalidParameters value in the call to setTunableValues used in customtunefis.

ignoreinvp = true;
fisout = customtunefis(fisin,rule,x,y,numite,ignoreinvp);
fisout.Rules

% In this case, the tuning process bypasses the invalid values and uses only valid parameter values for optimization.
% By default, tunefis ignores invalid values when updating fuzzy system parameters. You can change this behavior by setting tunefisOptions.IgnoreInvalidParameters to false.
%
%% Local Functions

function cost = customcostfcn(fis,x,y)

tY = evalfis(fis,x);
sincosx = tY(:,1)+tY(:,2);
sincosexpx = sincosx.*tY(:,3);
actY = [sincosx;sincosexpx];
d = y(:)-actY;
cost = sqrt(mean(d.*d));

end

function fis = customtunefis(fis,rule,x,y,n,ignore)

% Show initial cost.
cost = findcost(fis,x,y);
fprintf('Initial cost = %f\n',cost);

% Optimize rule parameters.
numMFs = numel(fis.Outputs.MembershipFunctions);
for ite = 1:n
    for i = 1:numel(rule)
        % Get consequent value.
        pval = getTunableValues(fis,rule(i));
        % Loop through output MF indices to minimize the cost.
        % Use output indices according to AllowNot and AllowEmpty.
        allowNot = rule(i).Consequent.AllowNot;
        allowEmpty = rule(i).Consequent.AllowEmpty;
        if allowNot && allowEmpty
            mfID = -numMFs:numMFs;
        elseif allowNot && ~allowEmpty
            mfID = [-numMFs:-1 1:numMFs];
        elseif ~allowNot && allowEmpty
            mfID = 0:numMFs;
        else
            mfID = 1:numMFs;
        end
        cost = 1000;
        minCostFIS = fis;
        for j = 1:length(mfID)
            % Update consequent value.
            pval(1) = mfID(j);
            % Set updated consequent value to the FIS.
            fis = setTunableValues(fis,rule(i),pval,'IgnoreInvalidParameters',ignore);
            % Evaluate cost.
            rmse = findcost(fis,x,y);
            % Update FIS with the minimum cost.
            if rmse<cost
                cost = rmse;
                minCostFIS = fis;
            end
        end
        fis = minCostFIS;
    end
    fprintf('Iteration %d: Cost = %f\n',ite,cost);
end

end

function cost = findcost(fis,x,y)

actY = evalfis(fis,x);
d = y - actY;
cost = sqrt(mean(d.*d));

end

% Copyright 2019 The MathWorks, Inc.