classdef ThermalModel < pde.EquationModel
%pde.ThermalModel  A container that represents a thermal analysis model
%  A thermal model is a container that stores the geometry, material
%  properties, internal heat sources, temperature on the boundaries, heat
%  fluxes through the boundaries, mesh, and initial conditions that define
%  a thermal problem. A ThermalModel is generally not created directly, it
%  is created using the CREATEPDE function. The ThermalModel provides
%  functions that allow you to: define 2D geometry or import 3D geometry,
%  generate and store a mesh for these geometries, specify and store
%  internal heat sources and thermal properties of the material, apply and
%  store boundary conditions that define the behavior at the boundary of
%  the geometry, apply and store initial conditions that define the
%  solution at the beginning of a transient analysis, and solve the thermal
%  analysis model. The properties of the ThermalModel allow you to access
%  this information easily.
%
%
% ThermalModel methods:
%  geometryFromEdges       - Create 2D geometry from DECSG or PDEGEOM
%  importGeometry          - Create 3D geometry from file (STL only)
%  geometryFromMesh        - Create 3D geometry from a triangulated mesh
%  thermalProperties      -  Specify thermal material properties over a domain or subdomain
%  internalHeatSource      - Specify internal heat source over a domain or subdomain
%  thermalBC               - Apply a thermal boundary conditions to the geometric boundary
%  thermalIC               - Set the initial conditions for the model over a domain or subdomain
%  generateMesh            - Generate a mesh from the geometry
%  solve                   - Solve the model
%
% ThermalModel properties:
%
%  AnalysisType            - Type of analysis: 'steadystate' or 'transient'
%  Geometry                - Geometric representation of the domain
%  MaterialProperties      - Thermal material properties within the domain or subdomain
%  HeatSources             - Internal heat generation within the domain or subdomain
%  BoundaryConditions      - Thermal boundary conditions applied to the geometric boundary
%  InitialConditions       - Initial temperature/guess
%  Mesh                    - Discretization of the domain
%  StefanBoltzmannConstant - Stefan-Boltzmann constant for radiation BC (consistent with the units of the model)
%  SolverOptions           - Algorithm options for the solvers
%
%    See also createpde, pde.ThermalModel/thermalProperties,
%    pde.ThermalModel/thermalBC, pde.ThermalModel/thermalIC,
%    pde.ThermalModel/internalHeatSource, pde.ThermalModel/solve,
%    pde.PDEModel

% Copyright 2016-2020 The MathWorks, Inc.

    
    
properties (SetAccess='private')
    AnalysisType;
end

properties

    % MaterialProperties - Thermal material properties within the domain or subdomain
    %    An object that contains the thermal material property assignments
    %    within the geometric domain. Material properties are specified and
    %    assigned to the geometry using the thermalProperties function. If
    %    multiple assignments are made, then they are all recorded within
    %    this object. Material properties must be specified in units
    %    consistent with the geometry and other model attributes.
    %
    %    See also pde.MaterialAssignmentRecords/findThermalProperties,
    %             pde.materialAssignmentRecords/globalMaterialProperties
    MaterialProperties;

    % HeatSources - Internal heat generation within the domain or subdomain
    %    An object that contains the internal heat source assignments
    %    within the geometric domain. Heat sources are specified and
    %    assigned to the geometry using the internalHeatSource function. If
    %    multiple assignments are made, then they are all recorded within
    %    this object. Heat sources must be specified in units consistent
    %    with geometry and other model attributes.
    %
    %    See also pde.materialAssignmentRecords/findHeatSource,
    %             pde.materialAssignmentRecords/globalHeatSource
    HeatSources;

    % BoundaryConditions - Thermal boundary conditions applied to the geometric boundary
    %    An object that contains the thermal boundary condition assignments
    %    on the boundary of the geometry. Boundary conditions are specified
    %    and assigned to the geometry using the thermalBC function. If
    %    multiple assignments are made, then they are all recorded within
    %    this object.
    %
    %    See also pde.ThermalBCRecords/findThermalBC
    BoundaryConditions;

    %InitialConditions - Initial temperature/guess
    %    An object that contains the initial temperature assignments within
    %    the geometric domain. Initial temperature is specified and
    %    assigned to the geometry using the thermalIC function. If multiple
    %    assignments are made, then they are all recorded within this object.
    %
    %    See also pde.ThermalICRecords/findThermalIC,
    %             pde.GeometricThermalIC, pde.NodalThermalIC
    InitialConditions;

    % StefanBoltzmannConstant - Stefan-Boltzmann constant for radiation BC (consistent with the units of the model)
    %   Constant of proportionality in Stefan-Boltzmann law governing
    %   radiation heat transfer. Specify a value that is consistent with
    %   the units of the model. For example, values of the Stefan-Boltzmann
    %   constant in commonly used systems of units are:
    %
    %   SI            : 5.670367E-8  W/(m^2 K^4)
    %   CGS           : 5.6704E-5    erg/(cm^2 s K^4)
    %   US customary  : 1.714E-9     BTU/(hr ft^2 R^4)
    %
    %   When solving problems involving heat transfer due to radiation,
    %   always specify the temperature in absolute scale.
    StefanBoltzmannConstant;
end


methods
    function self = ThermalModel(varargin)
        self@pde.EquationModel(1)
        
        if nargin > 0
            [varargin{:}] = convertStringsToChars(varargin{:});
        end
        
        if ~isempty(varargin)
            analysisTypeInput = varargin{1};
            
            if isempty(analysisTypeInput) % String special cases: '',"",0x1,0x0, as convertStringsToChars returns an empty cell for 0x1,0x0 strings.
                error(message('pde:ThermalModel:invalidAnalysisType'));
            end
            
            %Only valid input argument from this point on must be a char/string.
            validateattributes(varargin{1}, {'char','string'},{'scalartext'});
            
            if (startsWith("transient",analysisTypeInput,'IgnoreCase',true))
                self.IsTimeDependent = true;
                self.AnalysisType = 'transient';
            elseif(startsWith("steadystate",analysisTypeInput,'IgnoreCase',true))
                self.IsTimeDependent = false;
                self.AnalysisType = 'steadystate';
            elseif(startsWith("transient-axisymmetric",analysisTypeInput,'IgnoreCase',true))
                self.IsTimeDependent = true;
                self.AnalysisType = 'transient-axisymmetric';
            elseif(startsWith("steadystate-axisymmetric",analysisTypeInput,'IgnoreCase',true))
                self.IsTimeDependent = false;
                self.AnalysisType = 'steadystate-axisymmetric';                
            else
                error(message('pde:ThermalModel:invalidAnalysisType'));
            end
            
        else
            %default thermal analysis type is 'steadystate'
            self.AnalysisType = 'steadystate';
            self.IsTimeDependent = false;
        end
        
    end

    mtl = thermalProperties(self,varargin)
    src = internalHeatSource(self,varargin)
    tbc = thermalBC(self,varargin)
    tic = thermalIC(self,varargin);
    sol = solve(self,varargin);


    function set.BoundaryConditions(self, bc)
        if(~isempty(bc) && ~isa(bc, 'pde.ThermalBCRecords'))
            error(message('pde:pdeModel:invalidBoundaryConditions'));
        end
        if ~isscalar(bc) && ~isempty(bc)
            bcTemp = bc(1);
            for i = 2:length(bc)
                bcTemp.BoundaryConditionAssignments(end+1) =  bc(i).BoundaryConditionAssignments;
            end
            bc = bcTemp;
        end
        if isa(bc,'pde.ThermalBCRecords') && isempty(bc.ParentPdemodel.Geometry)
            bc.ParentPdemodel = self;
        end
        self.BoundaryConditions = bc;
    end

    function set.InitialConditions(self, icr)
        if(~isempty(icr) && ~isa(icr, 'pde.ThermalICRecords'))
            error(message('pde:pdeModel:invalidInitialConditions'));
        end
        if ~isscalar(icr) && ~isempty(icr)
            error(message('pde:pdeModel:nonScalarInitialConditions'));
        end
        self.InitialConditions = icr;
    end



    function set.MaterialProperties(self, mp)
        if(~isempty(mp) && ~isa(mp, 'pde.MaterialAssignmentRecords'))
            error(message('pde:ThermalModel:invalidMaterialProperties'));
        end
        if ~isscalar(mp) && ~isempty(mp)
            error(message('pde:ThermalModel:nonScalarMaterialProperties'));
        end
        self.MaterialProperties = mp;
    end


    function set.HeatSources(self, ihs)
        if(~isempty(ihs) && ~isa(ihs, 'pde.HeatSourceAssignmentRecords'))
            error(message('pde:ThermalModel:invalidHeatSource'));
        end
        if ~isscalar(ihs) && ~isempty(ihs)
            error(message('pde:ThermalModel:nonScalarHeatSource'));
        end
        self.HeatSources = ihs;
    end


    function set.StefanBoltzmannConstant(self, sbc)
        if (~isscalar(sbc) || ~isnumeric(sbc) || (sbc <= 0) || ...
                ~isreal(sbc) || isnan(sbc) || isinf(sbc))
            error(message('pde:ThermalModel:nonNumericSBC'));
        end
        self.StefanBoltzmannConstant = sbc;
    end

end

methods (Access = protected,Hidden = true)
    function group = getPropertyGroups(~)
        group = matlab.mixin.util.PropertyGroup({'AnalysisType','Geometry', ...
                            'MaterialProperties','HeatSources','StefanBoltzmannConstant',...
                            'BoundaryConditions','InitialConditions','Mesh',...
                            'SolverOptions'});
    end
end

    
properties (SetAccess='private',Hidden = true)
    IsTimeDependent;
    DCoefficientFlag = true; % First-order PDE, d-coeff is used.
end

methods(Hidden)
    function geometricConsistencyCheck(self,gm)
        if isempty(gm)
            return;
        end
        if contains(self.AnalysisType,'axisymmetric') && ~gm.IsTwoD
            error(message('pde:ThermalModel:invalid3DGeometryFor2DModel'));
        end
    end
    
    
    function [coefstruct,constCoeffs] = packCoefficients(self)
        mtlstruct = self.MaterialProperties.packProperties();
        if ~isempty(self.HeatSources)
            heatSrcStr = self.HeatSources.packHeatSource();
            constantHeatSource = self.HeatSources.allHeatSourceNumeric;
        else % No heat source was specified
            heatSrcStr.Coefficients.f = repmat({0},1,numel(mtlstruct.Coefficients.c));
            heatSrcStr.IsComplex =false;
            constantHeatSource = true;
        end
        
        coefstruct.ElementsInSubdomain = mtlstruct.ElementsInSubdomain;
        coefstruct.NumElementsInSubdomain = mtlstruct.NumElementsInSubdomain;
        coefstruct.Coefficients.m = mtlstruct.Coefficients.m;
        coefstruct.Coefficients.d = mtlstruct.Coefficients.d;
        if isempty(mtlstruct.Coefficients.d{1})
            coefstruct.Coefficients.d = mtlstruct.Coefficients.m; % Steady-state problem.
        end
        coefstruct.Coefficients.c = mtlstruct.Coefficients.c;
        coefstruct.Coefficients.a = mtlstruct.Coefficients.a;
        if numel(heatSrcStr.Coefficients.f) == numel(coefstruct.Coefficients.c)
            coefstruct.Coefficients.f = heatSrcStr.Coefficients.f;
        else
            coefstruct.Coefficients.f = repmat(heatSrcStr.Coefficients.f,1,numel(coefstruct.Coefficients.c));
        end
        coefstruct.IsComplex = [false;false;false;false;false];
        constCoeffs = constantHeatSource && self.MaterialProperties.allMaterialPropertiesNumeric();
    end
end

methods(Hidden = true, Access = {?pde.MaterialAssignmentRecords})
    function delistMaterialProperties(self)
        if isvalid(self)
            self.MaterialProperties = [];
        end
    end
end


methods(Hidden = true, Access = {?pde.HeatSourceAssignmentRecords})
    function delistHeatSources(self)
        if isvalid(self)
            self.HeatSources = [];
        end
    end
end


methods(Hidden = true, Access = {?pde.ThermalBCRecords})
    function delistBoundaryConditions(self)
        if isvalid(self)
            self.BoundaryConditions = [];
        end
    end
end

methods(Hidden = true, Access = {?pde.ThermalICRecords})
    function delistInitialConditions(self)
        if isvalid(self)
            self.InitialConditions = [];
        end
    end
end

end
