clear all; close all;
%% This file contains the our proposed solution for the optimization problem.

% Load set of factor level. Fk is a matrix which each row represents each
% factor level
Fk = importdata('fk_datafile.mat');

% Load set of machines that provide level m. Pm is a matrix such that each
% row represents the levels a machine support
Pm = importdata('pm_datafile.mat');

% MACHINES_QUANT number of machines
MACHINES_QUANT = 8;

% FACTORS_QUANTITY: Number of factors
FACTORS_QUANTITY = 14;

% LEVEL_QUANTITY: Total number of factor levels
LEVEL_QUANTITY = 44;

% Matrix Bk contains the user-requested minimum coverage
% User requested coverages
frequency_scaling = [1 1] % Frequency saling on and off
filesystem_type = [0 0 0 0 0 1] % Cover filesystem BTRFS only
number_of_cores = [0 1 0 0] % Cover machines with 3 cores
architecture = [0 1] % Cover x86_64 machines
cpu_clock = [0 0 1 0] %Select a machine with 2GHz
ram_size = [0 0 0 1 1] % Select machines with 1 and 2 GB of ram
compiler_type = [1 0] % Select machines that support GCC
address_randomization = [1 1] % Cover address randomization ON and OFF
compiler_options = [0 1 0 0] % Cover complite optimization -O2
memory_padding = [1 0 0 0] % Cover 0 memory padding
link_order = [0 0 1] % Cover default link order
cache_dropping = [1 1] %Cover cache dropping ON and OFF
auto_group = [1 0] % Cover auto group ON
swap_partition = [1 0] %Cover swap partition ON

% Build Bk matrix containing the user requested minimum coverage
Bk = [frequency_scaling zeros(1,42); ...
zeros(1,2) filesystem_type zeros(1,36); ...
zeros(1,8) number_of_cores zeros(1,32);...
zeros(1,12) architecture zeros(1,30);...
zeros(1,14) cpu_clock zeros(1,26);...
zeros(1,18) ram_size zeros(1,21);...
zeros(1,23) compiler_type zeros(1,19);...
zeros(1,25) address_randomization zeros(1,17);...
zeros(1,27) compiler_options zeros(1,13);...
zeros(1,31) memory_padding zeros(1,9);...
zeros(1,35) link_order zeros(1,6);...
zeros(1,38) cache_dropping zeros(1,4);...
zeros(1,40) auto_group zeros(1,2);...
zeros(1,42) swap_partition];

%% Solving the optimization problem using SDPT3
cvx_solver sdpt3

cvx_begin
    % W is the set of workers
    variable W(MACHINES_QUANT)
    variable L(LEVEL_QUANTITY)

    minimize (ones(1,MACHINES_QUANT)*W)

    subject to
    
        % ensures picking a factor level means at least one machine with that factor level is added to the solution
        Pm'*W >= L

        % ensures that selecting a machine adds all factor levels it provides to the tally, which is checked
        for j=1:LEVEL_QUANTITY
            for i=1:MACHINES_QUANT
                L(j) >= Pm(i,j)*W(i)
            end
        end

        % guarantees that all user-provided constraints are met
        for f=1:FACTORS_QUANTITY
            L'.*Fk(f, :) >= Bk(f, :);
        end
        
        0 <= W <= 1
        0 <= L <= 1
cvx_end

selected_machines_relaxed = W;
selected_levels_relaxed = L;

%% Solving the optimization problem using Gurubi
cvx_solver gurobi;

cvx_begin
    % W is the set of workers
    variable W(MACHINES_QUANT) integer
    variable L(LEVEL_QUANTITY) integer

    minimize (ones(1,MACHINES_QUANT)*W)

    subject to
    
        % ensures picking a factor level means at least one machine with that factor level is added to the solution
        Pm'*W >= L

        % ensures that selecting a machine adds all factor levels it provides to the tally, which is checked
        for j=1:LEVEL_QUANTITY
            for i=1:MACHINES_QUANT
                L(j) >= Pm(i,j)*W(i)
            end
        end

        % guarantees that all user-provided constraints are met
        for f=1:FACTORS_QUANTITY
            L'.*Fk(f, :) >= Bk(f, :);
        end
        
        % Relaxed constraints W E [0,1], L E [0,1]
        0 <= W <= 1
        0 <= L <= 1
cvx_end

% Comparison between machines selected by gurobi (using integer
% constraints) and the default cvx solver (with relaxed constraints)
machines_selected_cvx_gurobi = [selected_machines_relaxed W]

% Comparison between levels selected by gurobi (using integer
% constraints) and the default cvx solver (with relaxed constraints)
levels_selected_cvx_gurobi = [selected_levels_relaxed L]