classdef DipolarGas < handle & matlab.mixin.Copyable

    properties (Access = public)
        NumberOfAtoms;
        DipolarPolarAngle;
        DipolarAzimuthAngle
        ScatteringLength;
        TrapFrequencies;
        NumberOfGridPoints;
        Dimensions;
        Potential;
        
        SimulationMode; 
        TimeStepSize;
        MinimumTimeStepSize;
        TimeCutOff;
        EnergyTolerance;
        ResidualTolerance;
        NoiseScaleFactor;

        BiasWithAnsatz;
        Ansatz;

        MaxIterations;      
        VariationalWidth;
        WidthLowerBound;
        WidthUpperBound;
        VariationalWidthTolerance;
        VariationalEnergyTolerance;

        Calculator;
        
        SimulationParameters;
        IncludeDDICutOff;
        PlotLive;
        JobNumber;
        RunOnGPU;
        DebugMode;
        DoSave;
        SaveDirectory;
    end
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %- Methods
    
    methods
        function this  = DipolarGas(varargin)
            
            p = inputParser;
            p.KeepUnmatched = true;
            addParameter(p, 'NumberOfAtoms',     1E6,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'DipolarPolarAngle',     pi/2,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > -2*pi) && (x < 2*pi)));
            addParameter(p, 'DipolarAzimuthAngle',     pi/2,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > -2*pi) && (x < 2*pi)));
            addParameter(p, 'ScatteringLength',  120,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > -150) && (x < 150)));
            addParameter(p, 'TrapFrequencies',   100 * ones(1,3),...
                @(x) assert(isnumeric(x) && isvector(x) && all(x >= 0)));
            addParameter(p, 'NumberOfGridPoints',    128 * ones(1,2),...
                @(x) assert(isnumeric(x) && isvector(x) && all(x > 0)));
            addParameter(p, 'Dimensions',        10 * ones(1,2),...
                @(x) assert(isnumeric(x) && isvector(x) && all(x > 0)));
            addParameter(p, 'TimeStepSize',         5E-4,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'MinimumTimeStepSize',   1e-5,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));
            addParameter(p, 'TimeCutOff',            2e6,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'EnergyTolerance',   1e-10,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'ResidualTolerance',   1e-10,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'NoiseScaleFactor',       4,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'MaxIterations',          20,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'VariationalWidth',        4,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'WidthLowerBound',         2,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'WidthUpperBound',        12,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'VariationalWidthTolerance',  1e-2,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'VariationalEnergyTolerance', 1e-2,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'JobNumber',                0,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x >= 0)));
            addParameter(p, 'BiasWithAnsatz',  false,...
                @islogical);
            addParameter(p, 'Ansatz',         'None',...
                @ischar);
            addParameter(p, 'IncludeDDICutOff', true,...
                @islogical);
            addParameter(p, 'PlotLive',        false,...
                @islogical);
            addParameter(p, 'RunOnGPU',        false,...
                @islogical);
            addParameter(p, 'DebugMode',       false,...
                @islogical);
            addParameter(p, 'SaveData',        false,...
                @islogical);
            addParameter(p, 'SaveDirectory', './Data',...
                @ischar);
            
            p.parse(varargin{:});
            
            this.NumberOfAtoms        = p.Results.NumberOfAtoms;
            this.DipolarPolarAngle    = p.Results.DipolarPolarAngle;
            this.DipolarAzimuthAngle  = p.Results.DipolarAzimuthAngle;
            this.ScatteringLength     = p.Results.ScatteringLength;
            this.TrapFrequencies      = p.Results.TrapFrequencies;
            this.NumberOfGridPoints   = p.Results.NumberOfGridPoints;
            this.Dimensions           = p.Results.Dimensions;
            this.Potential            = NaN;
            this.TimeStepSize         = p.Results.TimeStepSize;
            this.MinimumTimeStepSize  = p.Results.MinimumTimeStepSize;
            this.TimeCutOff           = p.Results.TimeCutOff;
            this.EnergyTolerance      = p.Results.EnergyTolerance;
            this.ResidualTolerance    = p.Results.ResidualTolerance;
            this.NoiseScaleFactor     = p.Results.NoiseScaleFactor;
            
            this.BiasWithAnsatz       = p.Results.BiasWithAnsatz;
            this.Ansatz               = p.Results.Ansatz;

            this.MaxIterations        = p.Results.MaxIterations;
            this.VariationalWidth     = p.Results.VariationalWidth;
            this.WidthLowerBound      = p.Results.WidthLowerBound;
            this.WidthUpperBound      = p.Results.WidthUpperBound;
            this.VariationalWidthTolerance  = p.Results.VariationalWidthTolerance;
            this.VariationalEnergyTolerance = p.Results.VariationalEnergyTolerance;
            
            this.IncludeDDICutOff     = p.Results.IncludeDDICutOff;
            this.PlotLive             = p.Results.PlotLive;
            this.JobNumber            = p.Results.JobNumber;
            this.RunOnGPU             = p.Results.RunOnGPU;
            this.DebugMode            = p.Results.DebugMode;
            this.DoSave               = p.Results.SaveData;
            this.SaveDirectory        = p.Results.SaveDirectory;
            
            this.Calculator           = VariationalSolver2D.Calculator();

            this.SimulationParameters = this.setupParameters();

        end
    end

    methods
        function set.TimeStepSize(this, val)
            assert(val > 1e-06, 'Not time efficient to compute for time steps smaller than 1 microsecond!');
            this.TimeStepSize = val;
        end
        function ret = get.TimeStepSize(this)
            ret = this.TimeStepSize;
        end
        function set.TimeCutOff(this, val)
            assert(val <= 5E6, 'Not efficient to compute for time spans longer than 2E6 seconds!');
            this.TimeCutOff = val;
        end
        function ret = get.TimeCutOff(this)
            ret = this.TimeCutOff;
        end
        function set.NumberOfAtoms(this, val)
            assert(val <= 1E9, '!!Not time efficient to compute for atom numbers larger than 1,000,000,000!!');
            this.NumberOfAtoms = val;
        end
        function ret = get.NumberOfAtoms(this)
            ret = this.NumberOfAtoms;
        end
        function set.DebugMode(this, val)
            this.DebugMode = val;
        end
        function ret = get.DebugMode(this)
            ret = this.DebugMode;
        end
        function set.DoSave(this, val)
            this.DoSave = val;
        end
        function ret = get.DoSave(this)
            ret = this.DoSave;
        end
        function set.SaveDirectory(this, val)
            this.SaveDirectory = val;
        end
        function ret = get.SaveDirectory(this)
            ret = this.SaveDirectory;
        end
    end % - setters and getters
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %- Methods
    
    methods(Access = protected)
        function cp = copyElement(this)
            % Shallow copy object
            cp = copyElement@matlab.mixin.Copyable(this);
            
            % Forces the setter to redefine the function handles to the new copied object
            
            pl = properties(this);
            for k = 1:length(pl)
                sc = superclasses(this.(pl{k}));
                if any(contains(sc,{'matlab.mixin.Copyable'}))
                    cp.(pl{k}) = this.(pl{k}).copy();
                end
            end
        end
    end
    
    methods (Static)
        
        % Creates an Instance of Class, ensures singleton behaviour (that there
        % can only be one Instance of this class
        function singleObj = getInstance(varargin)
            % Creates an Instance of Class, ensures singleton behaviour
            persistent localObj;
            if isempty(localObj) || ~isvalid(localObj)
                localObj =  Simulator.DipolarGas(varargin{:});
            end
            singleObj = localObj;
        end
    end
    
end
