classdef FullProblem < handle
	% This class represents the full problem to be used in
	% findOptimalAllocationFullFmincon.
	
	properties (GetAccess = public, SetAccess = private)
		qn; % Queuing network
		constraints; % Constraints on the network
		resources; % Cost and speed of available resources
		evaluations; % Number of evaluations
	end
	
	methods (Access = public)
		
		function this = FullProblem(qn, constraints, resources)
			% Create a full problem using the given parameters
			% qn: initial queuing network
			% constraints: response time constraints
			% resources: types of available resources
			
			this.qn = qn;
			this.constraints = constraints;
			this.resources = resources;
			this.evaluations = 0;
		end
		
		
		function [c,ceq]=nnlcon(this, x)
			% Calculate the constraints on the response time
			% x: decision vairiable (matrix that allocates the rates of the stations
			% to the nodes).
			% c/ceq: fmincon non linear constraints
			
			
			allocatedQN = this.calculateAllocatedQN(x);
			[avgRT, cdfRTindex, cdfRTvalue] = evaluateRT(allocatedQN);
			this.evaluations = this.evaluations + 1;
			distance = this.constraints.distance(avgRT, cdfRTindex, cdfRTvalue);
			[u, v] = size(distance);
			
			% Calculate the constraints on the biggest resource
			maxEcu = max(this.resources.ecu);
			excessiveEcu = sum(x,1)-maxEcu;
			
			ceq = [];
			c = [reshape(distance, u*v, 1) ; excessiveEcu'] - eps;
		end

		function f = objfun(this, x)
			% Objective function for the problem that has to be minimized.
			% x: allocation matrix
			% f: cost for applying the current allocation matrix
			
			f = 0;
			[~, v] = size(x);
			
			% The goal is to minimize the sum of the cost of all the rented resources.
			for ii=1:v
				f = f + this.calculateCostLinear(sum(x(:,ii)));
				%f = f + this.calculateCostSigmoid(sum(x(:,ii)));
			end
			
			% add a small increment to make the objective function grow linearly
			% with respect to the number of resources.
			
			f = f + 0.001*sum(sum(x))/max(this.resources.ecu);
			
		end
		
		function [allocatedQN] = calculateAllocatedQN(this, x)
			% calculateAllocatedQN
			% Calculates a new queuing network from the original one, taking
			% into account the allocation of the resources.
			% x: allocation matrix / decision variable
			% allocatedQN: new recalculated queuing network

			delayNodes = getDelayNodes(this.qn);
			rates = sum(x,2);
			qnRates = avgRates(this.qn);
			qnRates(~delayNodes,:) = qnRates(~delayNodes,:).*repmat(rates,1,this.qn.K);
			myQN = setRates(this.qn, qnRates);
			
			allocatedQN = allocateQN(myQN, x, this.resources);
		end
		
		function [cost] = calculateCostLinear(~, rate)
			% This function calculates the cost of allocating a resource
			% that just equals to the rate. It means that the function
			% of rate vs cost is linear.
			% rate: new rates for the queuing network
			% cost: sum of the current rates
			
			cost = rate;
		end

		function [cost] = calculateCostSigmoid(this, rate)
			% This function canculates the best cost for allocating a resource
			% that has at least the given rate. This function is a sum of
			% sigmoid functions and is continuous, always-increasing, and
			% differentiable.
			%
			% Costs and rates are assumed to be ordered in ascending order.
			% rate: new rates for the queuing network
			% cost: cost of the current rates
			
			accuracy = 0.01;
			cost = this.resources.cost(1).*sigmf(rate, [1/accuracy 0]);
			
			
			for ii=1:this.resources.size-1
				cost = cost + (this.resources.cost(ii+1)-this.resources.cost(ii)).*sigmf(rate, [1/accuracy this.resources.ecu(ii)]);
			end
			
		end
	end
end
