classdef (Sealed) HeatSourceAssignment  < pde.PDECompactDisplay
% HeatSourceAssignment Specify heat source over domain or subdomain
%     For a thermal model, the toolbox solves equations of the following form:
%
% (MassDensity*SpecificHeat)*dT/dt - div(ThermalConductivity*grad(T)) = HeatSource
%
%     This method creates an object representing the HeatSource in a domain
%     or subdomain and appends the object to the HeatSourceAssignments
%     property.
%
%     Instances of this class can only be created by calling the
%     internalHeatSource method of the ThermalModel class.
%
% See also pde.ThermalModel, pde.ThermalModel/internalHeatSource

% Copyright 2016-2022 The MathWorks, Inc.

properties (SetAccess = private)
    % RegionType - Type of geometric region to which the heat source is specified.
    %   The RegionType can be one of the following geometric entities: 'cell'
    %    or 'face'.
    RegionType;
end

properties
    % RegionID - ID of the geometric regions the coefficients are assigned to.
    %    For 2-D each RegionID satisfies 0 < RegionID(j) < NumFaces in the
    %    geometry. For 3-D each RegionID satisfies 0 < RegionID(j) < NumCells
    %    in the geometry.
    RegionID;

    %HeatSource internal heat generation within a domain or subdomain
    %   A numerical scalar, vector or a matrix, or a function handle that
    %   compute heat source value for a give spatial location at a given
    %   temperature.
    HeatSource;
    Label;
end

methods
    function self = HeatSourceAssignment(tmar,varargin)
        self.RecordOwner = tmar;
        %self = self@pde.CoefficientAssignment(tcar);
        parser = inputParser;
        parser.addParameter('face', [],@pde.EquationModel.isValidEntityID);
        parser.addParameter('cell', [],@pde.EquationModel.isValidEntityID);
        parser.addParameter('HeatSource', []);
        parser.addParameter('Label', []);
        parser.parse(varargin{:});

        numdims = 2;
        if ~isempty(parser.Results.face)
            self.RegionType = 'face';
            self.RegionID = parser.Results.face;
        elseif ~isempty(parser.Results.cell)
            self.RegionType = 'cell';
            self.RegionID = parser.Results.cell;
            numdims = 3;
        end
        systemsize = 1;
        self.HeatSource = parser.Results.HeatSource;
        self.Label = parser.Results.Label;
        self.checkAllMatrixHeatSourceSizes(systemsize, numdims);
        self.checkFcnHdlArgCounts();

    end
end


methods % Setter methods

    function set.RegionType(self, rtype)
        self.RegionType = rtype;
    end

    function set.RegionID(self, rids)
        self.ValidateRegionID(rids);
        self.RegionID = rids;
    end

    function set.HeatSource(self, coef)
        self.CoefPrecheck(coef);
        self.HeatSource = coef;
    end
    
    function set.Label(self, Label)
        self.Label = convertStringsToChars(Label);
    end

end % end of setter methods


methods(Hidden=true, Access = {?pde.HeatSourceAssignmentRecords})
    function tf=sameCoefficients(self,other,varargin)
        if ~coefficientsMatch(self, other)
            tf = false;
            return
        end
        tf = isequal(self.HeatSource, other.HeatSource);
    end

    function tf=numericHeatSource(self,varargin)
        tf = ~isa(self.HeatSource, 'function_handle');
    end


    function tf = coefficientsMatch(self, other)
        tf = true;
        tf = tf & (self.HeatSourceDefined() == other.HeatSourceDefined());
    end

    function performSolverPrecheck(self, systemsize, numfaces, numcells)
        ndims = 2;
        if strcmp(self.RegionType, 'face')
            if any(self.RegionID > numfaces)
                error(message('pde:heatSourceSpecification:invalidFaceIndexPresolve'));
            end
        else
            if any(self.RegionID > numcells)
                error(message('pde:heatSourceSpecification:invalidCellIndexPresolve'));
            end
            ndims = 3;
        end
        checkAllMatrixHeatSourceSizes(self, systemsize, ndims);
        checkFcnHdlArgCounts(self)
    end

end


methods(Hidden=true, Access = private)
    function checkAllMatrixHeatSourceSizes(self, systemsize, ~)
        if ~isempty(self.HeatSource)
            self.checkHeatSourceSize(self.HeatSource, systemsize);
        end
    end
    %

    function checkFcnHdlArgCounts(self)
        if self.HeatSourceDefined()
            self.checkCoefFcnHdlArgCounts(self.HeatSource);
        end
    end
    
    function checkCoefFcnHdlArgCounts(self,coef)
        if ~isa(coef, 'function_handle')
            return
        end
        numFcnInputs = nargin(coef);
        numFcnOutputs = abs(nargout(coef)); % abs for anonymous function_handle
        if (numFcnInputs ~= 2) || (numFcnOutputs ~= 1)
            error(message('pde:heatSourceSpecification:invalidFcnHandleArgs'));
        end
        
        [location, state] = self.RecordOwner.ParentModel.getSampleLocState(1);
        
        try
            res = coef(location, state);
        catch ME
            throwAsCaller(ME);
        end
        
        if ~isa(res,'double')
            error(message('pde:heatSourceSpecification:invalidFcnHandleOutputType'));
        end
    end
    

end % Private methods



methods(Hidden=true, Access = {?pde.HeatSourceAssignmentRecords})
    function tf = HeatSourceDefined(self)
        tf = self.coefDefined(self.HeatSource);

    end

    function tf = hasComplexCoefficient(self, loc, state)
        tf = false;
        tf(1) = pde.HeatSourceAssignment.coefIsComplexNumericOrFcnHdl(self.HeatSource, loc, state);
        if any(tf)
            error(message('pde:heatSourceSpecification:invalidHeatSrcValueComplex'));
        end        
    end

end % end of methods


methods(Static, Access = private)
    function tf = coefDefined(coef)
        tf = (isnumeric(coef) && ~(isscalar(coef) && coef == 0) || isa(coef,'function_handle'));
    end
 
    function tf = coefIsComplexNumericOrFcnHdl(coef, loc, state)
        tf = false;
        if ~pde.HeatSourceAssignment.coefDefined(coef)
            return
        end
        if isnumeric(coef)
            if ~isreal(coef)
                tf = true;
            end
        else % isa(coef, 'function_handle')
            res = coef(loc, state);
            if ~isreal(res)
                tf = true;
            end
        end
    end

    
    function ok=ValidateRegionID(rgnid)
        % Must be real(non-complex), full, natural number.
        if ~isreal(rgnid) || ~all(rgnid(:) > 0) || issparse(rgnid) || any(mod(rgnid(:),1)~=0)
            error(message('pde:heatSourceSpecification:invalidRegionID'));
        end
        ok = true;
    end

    function ok=CoefPrecheck(coef)
      validateattributes(coef,{'double','function_handle'},{},'','')
        if isa(coef,'function_handle')
            validateattributes(coef,{'function_handle'},{'scalar'},'','');
        else
            validateattributes(coef,{'double'},{'nonnan','nonempty','nonsparse','real','finite'},'');
        end

        ok = true;
    end

    function checkHeatSourceSize(dcoef, systemsize)
        if isscalar(dcoef) && ~isa(dcoef, 'function_handle') && isempty(dcoef)
            return
        elseif isa(dcoef, 'function_handle')
            return
        end
        dveclen = numel(dcoef);
        lengthok = (dveclen == 0 || dveclen == 1 || dveclen == systemsize || ...
            dveclen == (systemsize*(systemsize+1)/2) || dveclen == systemsize*systemsize);
        if ~(isvector(dcoef)  && iscolumn(dcoef) && lengthok)
            error(message('pde:heatSourceSpecification:invalidHeatSrcSize'));
        end
    end

end

methods(Hidden=true)
    function delete(self)
        if ~isempty(self.RecordOwner) && isvalid(self.RecordOwner)
            self.RecordOwner.delistHeatSourceAssignments(self);
        end
    end
end


properties (Hidden = true, Access='private')
    RecordOwner;
end

end
