classdef (Sealed) BodyLoadAssignment  < pde.PDECompactDisplay
% BodyLoadAssignment - Body load assignment for a structural analysis model.
% Structural analysis workflow in PDE Toolbox supports modeling
% gravitational effects as body load on the entire model. The
% BodyLoadAssignment object stores the specified body loads to the
% structural analysis model and appends the object to BodyLoads
% properties of the StructuralModel.
%
% Instances of this class can only be created by calling the
% structuralBodyLoad method of the StructuralModel class.
%
% See also pde.StructuralModel, pde.StructuralModel/structuralBodyLoad

% Copyright 2017-2023 The MathWorks, Inc.

properties (SetAccess = private)
    %RegionType - Type of geometric region to which the material properties are assigned
    % The type of geometric region specified as 'Cell' or 'Face'.
    RegionType;
    
    % RegionID - IDs of the geometric regions to which the material properties are assigned
    %  RegionID cannot exceed the number of faces for a 2-D geometry and
    %  the number of cells for a 3-D geometry.
    RegionID;    
end

properties
    %GravitationalAcceleration - Gravitational acceleration vector.
    % A numeric vector specifying the acceleration due to gravity in units
    % consistent with the geometry and material properties units.
    GravitationalAcceleration;
    
    %AngularVelocity - Angular velocity of a spinning axisymmetric structure.
    % Specify a numeric scalar to model the centrifugal loading. The
    % vertical axis is the default axis of rotation. Use units consistent
    % with the geometry and material properties units.
    AngularVelocity;
    
    %Temperature - Temperature or thermal analysis results
    % Either a numeric scalar for uniform temperature or a thermal analysis
    % results.
    Temperature;
    
    %TimeStep - TimeStep of transient thermal results
    TimeStep; 
    
    Label;
end


methods
    function self = BodyLoadAssignment(smar,blInput)
        arguments
            smar pde.BodyLoadAssignmentRecords
            blInput struct
        end

        self.RecordOwner = smar;
        self.needsInterpolation = false;
        inputFields = fields(blInput);
        for f = 1:numel(inputFields)
            self.(inputFields{f}) = blInput.(inputFields{f});
        end
        
        systemsize = 2;
        if self.RegionType == "Cell"
            systemsize = 3;
        end
        
        if ~isa(self.Temperature,'pde.TransientThermalResults') && ~isempty(self.TimeStep)
            error(message('pde:bodyLoadSpecification:timeStepMustBeWithTemperature'))
        end
        
        self.checkAllMatrixBodyLoadSizes(systemsize);
    end
end


methods % Setter methods

    function set.RegionType(self, rtype)
        self.RegionType = rtype;
    end
    
    function set.RegionID(self, rids)
        self.RegionID = rids;
    end
    
    function set.GravitationalAcceleration(self, coef)
        if ~isempty(coef)
            self.CoefPrecheck(coef,'GravitationalAcceleration');
        end
        self.GravitationalAcceleration = coef;
    end
    
   function set.AngularVelocity(self, coef)
       if ~isempty(coef)
        validateattributes(coef,{'double'},{'scalar','positive','real','nonsparse'},'AngularVelocity');
       end
        self.AngularVelocity = coef;
   end
    
    function set.Temperature(self, coef)
        if ~isempty(coef)
            self.CoefPrecheck(coef,'Temperature');
        end
        self.Temperature = coef;
    end
    
   function set.TimeStep(self, coef)
       if ~isempty(coef)
        validateattributes(coef,{'double'},{'scalar','integer','positive', 'nonzero','real', 'nonsparse'},'TimeStep');
       end
        self.TimeStep = coef;
   end
   
   function set.Label(self, Label)
       self.Label = convertStringsToChars(Label);
   end
end % end of setter methods


methods(Hidden=true, Access = {?pde.BodyLoadAssignmentRecords})
    function tf=sameCoefficients(self,other,varargin)
        tf = (isequal(self.GravitationalAcceleration,other.GravitationalAcceleration) && ...
              isequal(self.AngularVelocity,other.AngularVelocity) && ...
              isequal(self.Temperature, other.Temperature) && ...
              isequal(self.TimeStep, other.TimeStep) );
    end
    
    function performSolverPrecheck(self, systemsize)
        checkAllMatrixBodyLoadSizes(self, systemsize);
    end
end


methods(Hidden=true, Access = private)
    function checkAllMatrixBodyLoadSizes(self, systemsize)
        if ~isempty(self.GravitationalAcceleration)
            self.checkGravitationalAccelerationSize(self.GravitationalAcceleration, systemsize);
        end
        numNodes =[];
        if ~isempty(self.RecordOwner.ParentModel.Mesh)
            numNodes = size(self.RecordOwner.ParentModel.Mesh.Nodes,2);           
        end
        if ~isempty(self.Temperature)
            [interpolationFlag, numTimeSteps] = self.checkTemperatureSize(self.Temperature, numNodes);
            self.needsInterpolation = interpolationFlag;
            if ~isempty(self.TimeStep)
                self.checkTimeStepSize(self.TimeStep, numTimeSteps);
            end
        end
    end
end % Private methods


methods(Static, Access = private)
    function ok=CoefPrecheck(coef,coefName)
        validateattributes(coef,{'double','function_handle','pde.SteadyStateThermalResults','pde.TransientThermalResults'},{},'',coefName)
        if isa(coef,'function_handle')||  isa(coef,'pde.SteadyStateThermalResults') || isa(coef,'pde.TransientThermalResults')
            validateattributes(coef,{'function_handle','pde.SteadyStateThermalResults','pde.TransientThermalResults'},{'scalar'},'',coefName);
        else
            validateattributes(coef,{'double'},{'nonnan','nonempty','nonsparse','real','finite'},coefName);
        end
        
        ok = true;
    end

    function checkGravitationalAccelerationSize(coef, systemsize)
        if isa(coef, 'function_handle')
            error(message('pde:bodyLoadSpecification:noFuncHandleForGravity'));
        end
        
        fveclen = numel(coef);
        lengthok = (fveclen == systemsize );
        if ~(isvector(coef) && lengthok)
            error(message('pde:bodyLoadSpecification:invalidGravityLoadType'));
        end
    end
    
    function [needsInterpolation, numTimeSteps] = checkTemperatureSize(coef, numNodes)
        if isa(coef, 'function_handle')
            error(message('pde:bodyLoadSpecification:noFuncHandleForTemperature'));
        end
        
        if isnumeric(coef)
            fveclen = numel(coef);
            lengthok = (fveclen == 1 );
            if ~(isvector(coef) && lengthok)
                error(message('pde:bodyLoadSpecification:invalidTemperatureLoad'));
            end
        end
        
        needsInterpolation = false;
        numTimeSteps = 1;
        if isa(coef,'pde.SteadyStateThermalResults') || isa(coef,'pde.TransientThermalResults')
            resultNumNodes = size(coef.Mesh.Nodes,2);
            if ~(numNodes == resultNumNodes)
                needsInterpolation = true;
            end
            
            if isa(coef,'pde.TransientThermalResults')
                numTimeSteps = numel(coef.SolutionTimes);
            end
        end
    end
    
    
    function checkTimeStepSize(timeStepID, numTimeSteps)
        if ~isscalar(timeStepID) || (timeStepID > numTimeSteps )
            error(message('pde:bodyLoadSpecification:nonScalarOrInvalidTimeStep'));
        end
     end
end

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


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

properties (Hidden = true, Access={?pde.BodyLoadAssignmentRecords})
    needsInterpolation;
end

end
