classdef (Sealed) StructuralDampingAssignment  < handle 
% StructuralDampingAssignment - Damping assignment for a structural analysis model.
% Structural analysis workflow in PDE Toolbox supports modeling
% proportional (Rayleigh) damping applied to the entire model. The
% StructuralDampingAssignment object stores the specified proportional
% damping parameters and appends the object to DampingModels property of
% the StructuralModel object.
%
% Instances of this class can only be created by calling the
% structuralDamping method of the StructuralModel class.
%
% See also pde.StructuralModel, pde.StructuralModel/structuralDamping

% Copyright 2017-2021 The MathWorks, Inc.

properties (SetAccess = private)
    %RegionType - Type of geometric region to which the damping is assigned
    % The type of geometric region is internally assigned as 'Cell' or
    % 'Face' for 3-D and 2-D models, respectively.
    RegionType;
    
    %RegionID - IDs of the geometric regions to which the damping is assigned
    % RegionID is internally assigned as all geometric cells or faces for
    % 3-D and 2-D, respectively.
    RegionID;    
    
    %DampingModel - Name of the damping model.
    % Specified as 'proportional', representing the proportional or Rayleigh
    % damping model.
    DampingModel;
end

properties
    %Alpha - Value of mass proportional damping
    Alpha;
    
    %Beta - Value of stiffness proportional damping
    Beta;
    
    %Zeta - Value of modal damping ratio
    Zeta; 
end

methods
    
    function self = StructuralDampingAssignment(sdmr,varargin)
        self.RecordOwner = sdmr;
        parser = inputParser;
        parser.addParameter('Face',[],@pde.EquationModel.isValidEntityID);
        parser.addParameter('Cell',[],@pde.EquationModel.isValidEntityID);
        parser.addParameter('Alpha',[],@pde.EquationModel.isValidAttributeVal);
        parser.addParameter('Beta',[],@pde.EquationModel.isValidAttributeVal);
        parser.addParameter('Zeta',[],@pde.EquationModel.isValidAttributeVal);
        parser.addParameter('DampingModel',[],@pde.StructuralModel.isValidDampingModel);
        parser.parse(varargin{:});
        
        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;
        end
        
        self.Alpha = parser.Results.Alpha;
        self.Beta = parser.Results.Beta;
        self.Zeta = parser.Results.Zeta;
        if isempty(parser.Results.DampingModel)
            self.DampingModel = self.determineDampingModel();
        else
            self.DampingModel = self.validateDampingModel(parser.Results.DampingModel);
        end
        self.checkAllDampingParameterSize();
    end
    
end

methods % Setter methods

    function set.RegionType(self, rtype)
        %This is currently a private property as only global damping is allowed.
        self.RegionType = rtype;
    end
    
    function set.RegionID(self, rids)
        %This is currently a private property as only global damping is allowed.
        self.RegionID = rids;
    end
    
    function set.DampingModel(self, coef)
        %This is a private internal property, prevalidated.
        self.DampingModel = coef;
    end
    
    function set.Alpha(self, coef)
        self.CoefPrecheck(coef,'Alpha');
        self.Alpha = coef;
    end    
    
    function set.Beta(self, coef)
        self.CoefPrecheck(coef,'Beta');
        self.Beta = coef;
    end
    
    function set.Zeta(self, coef)
        self.CoefPrecheck(coef,'Zeta');
        self.Zeta = coef;
    end
    
end % end of setter methods



methods(Hidden=true, Access = {?pde.StructuralDampingAssignmentRecords})
    function performSolverPrecheck(self)
        checkAllDampingParameterSize(self);
    end
end


methods(Hidden=true, Access = private)
    function checkAllDampingParameterSize(self)
        if ~isempty(self.Alpha)
            self.checkDampingAlphaSize(self.Alpha);
        end
        
        if ~isempty(self.Beta)
            self.checkDampingBetaSize(self.Beta);
        end
        
        if ~isempty(self.Zeta)
            self.checkDampingZetaSize(self.Zeta);
         end
        
    end
    %
    function dm = determineDampingModel(self)
        if ~isempty(self.Zeta) && isempty(self.Alpha) && isempty(self.Beta)
            dm = "modal";
        elseif isempty(self.Zeta) && (~isempty(self.Alpha) || ~isempty(self.Beta) )       
            dm = "proportional";
        else
            error(message('pde:dampingModelSpecification:invalidDampingModel'));
        end
    end
    
    function dmDeduced= validateDampingModel(self,dm)
        dmDeduced = determineDampingModel(self);
        if ~contains(dmDeduced,dm,'IgnoreCase',true)
            error(message('pde:dampingModelSpecification:invalidDampingModel'));
        end
    end
    
end % Private methods




methods(Static, Access = private)
 
    function ok=CoefPrecheck(coef,coefName)
        validateattributes(coef,{'double','function_handle'},{},'',coefName)
        if ~isa(coef,'function_handle')
            validateattributes(coef,{'double'},{'nonnan','nonsparse','real','finite','nonnegative'},coefName);
        end
        ok = true;
    end

    function checkDampingAlphaSize(coef)
        if isa(coef, 'function_handle')
            error(message('pde:dampingModelSpecification:noFuncHandleProportionalDamping'));
        end
        
        if ~isscalar(coef) 
            error(message('pde:dampingModelSpecification:noFuncHandleProportionalDamping'));
        end
    end

    
    function checkDampingBetaSize(coef)
        if isa(coef, 'function_handle')
            error(message('pde:dampingModelSpecification:noFuncHandleProportionalDamping'));
        end
        
        if ~isscalar(coef) 
            error(message('pde:dampingModelSpecification:noFuncHandleProportionalDamping'));
        end
    end
    
    function checkDampingZetaSize(coef)
        if ~(isscalar(coef) || isa(coef, 'function_handle'))
            error(message('pde:dampingModelSpecification:invalidModalDamping'));
        end
        
        if isa(coef, 'function_handle')
            % check for single input and output arguments
            numInputs = nargin(coef);
            numOutputs = nargout(coef); % returns -1 for ananymous function, hence abs is used below.
            
            if numInputs ~= 1 || abs(numOutputs) ~= 1
                error(message('pde:dampingModelSpecification:incorrectNumArgsZetaFcn'));
            end
            
        end
    end    
end

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


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

    
end
