Master class

Contents

Description

This is the main class for running simulations and tests in DEMLab.

It is responsible for managing the high-level tasks and call the appropriate methods to perform each stage of a simulation, from the reading of input files to the showing of results.

classdef Master < handle

Public properties

    properties (SetAccess = public, GetAccess = public)
        echo     uint8   = uint8.empty;     % echo level (amount of information to be printed in command window)
        path_in  string  = string.empty;    % path to input files folder
        files    string  = string.empty;    % full name of input files (with path)
        cur_file string  = string.empty;    % name of current file being run
        is_last  logical = logical.empty;   % flag for last input file to run
    end

Constructor method

    methods
        function this = Master()
            this.setDefaultProps();
        end
    end

Public methods: main functions

    methods
        %------------------------------------------------------------------
        function setDefaultProps(this)
            this.echo = 1;
        end

        %------------------------------------------------------------------
        function runSimulations(this,echo)
            if (nargin > 1)
                this.echo = echo;
            end

            % Print header
            this.printHeader();

            % Get input files
            if (~this.getInputFiles())
                return;
            end

            % Display input files
            this.displayInputFiles();

            % Run each input file
            for i = 1:length(this.files)
                this.is_last = (i == length(this.files));

                % Clear driver
                clearvars drv;

                % Get current file name and extension
                this.cur_file = this.files(i);
                [~,~,ext] = fileparts(this.cur_file);

                % Run according to file extension
                if (strcmp(ext,'.json'))
                    [status,drv] = this.runAnalysis();
                    if (~status)
                        continue;
                    end
                elseif (strcmp(ext,'.mat'))
                    [status,drv] = this.loadResults();
                    if (~status)
                        continue;
                    end
                else
                    fprintf('Input file:\n%s\n',this.cur_file);
                    fprintf(2,'\nInvalid input file extension.\n');
                    this.printExit();
                    continue;
                end

                % Pos-process
                drv.posProcess();

                % Finish current analysis
                fprintf('\nFinished!\n');
                if (~this.is_last)
                    fprintf('\n------------------------------------------------------------------\n\n');
                end
            end
        end

        %------------------------------------------------------------------
        function runTests(this,mode)
            this.echo = 0;

            % Print header
            this.printHeader();

            % Check input
            if (mode ~= 1 && mode ~= 2)
                fprintf('Invalid testing mode!\n');
                fprintf('\nExiting program...\n');
                return;
            end

            % Get input files
            if (~this.getInputFiles())
                return;
            end

            % Display input files
            this.displayInputFiles();

            % Run each input file
            for i = 1:length(this.files)
                this.is_last = (i == length(this.files));

                % Clear driver
                clearvars drv;

                % Get current file name and extension
                this.cur_file = this.files(i);
                [~,~,ext] = fileparts(this.cur_file);
                if (~strcmp(ext,'.json'))
                    fprintf('Input file:\n%s\n',this.cur_file);
                    fprintf(2,'\nInvalid input file extension.\n');
                    this.printExit();
                    continue;
                end

                % Run simulation
                [status,drv] = this.runAnalysis();
                if (~status)
                    continue;
                end

                % Pos-process
                drv.posProcess();

                % Perform action according to testing mode
                if (mode == 1) % compare results file with reference
                    this.compareTest(drv);
                elseif (mode == 2) % generate/update reference results
                   this.renameRefResultFile(drv);
                end

                % Finish current analysis
                if (~this.is_last)
                    fprintf('\n------------------------------------------------------------------\n\n');
                end
            end
            fprintf('\nFinished!\n');
        end

        %------------------------------------------------------------------
        function [status,drv] = runAnalysis(this)
            % Open parameters file
            fprintf('Parameters file:\n%s\n',this.cur_file);
            fid = fopen(this.cur_file,'rt');
            if (fid < 0)
                fprintf(2,'\nError opening parameters file.\n');
                this.printExit();
                status = 0;
                return;
            end

            % Read parameters file
            if (this.echo > 0)
                fprintf('\nReading parameters file...\n');
            end
            read = Read();
            [status,drv,storage_file] = read.execute(this.path_in,fid);

            % Pre analysis tasks
            if (status == 0)
                this.printExit();
                return;

            elseif (status == 1) % start analysis from the beggining
                % Check input data
                if (this.echo > 0)
                    fprintf('\nChecking consistency of input data...\n');
                end
                if (~read.check(drv))
                    this.printExit();
                    status = 0;
                    return;
                end

                % Pre-process
                if (this.echo > 0)
                    fprintf('\nPre-processing...\n');
                end
                if (~drv.preProcess())
                    this.printExit();
                    status = 0;
                    return;
                end

                % Print simulation information
                if (this.echo > 0)
                    this.printSimulationInfo(drv);
                    fprintf('\nStarting analysis:\n');
                    fprintf('%s\n',datestr(now));
                end

            elseif (status == 2) % continue analysis from previous state
                f = dir(storage_file);
                fprintf('\nStorage file found (%.3f Mb):\n%s\n',f.bytes/10e5,storage_file);

                % Load storage file
                [loaded,drv] = this.loadStorageFile(storage_file);
                if (~loaded)
                    status = 0;
                    return;
                end

                % Set output folder to current folder
                drv.path_out = this.path_in;

                % Update starting elapsed time
                drv.start_time = drv.total_time;

                % Print simulation information
                if (this.echo > 0)
                    this.printSimulationInfo(drv);
                    fprintf('\nStarting analysis from previous results:\n');
                    fprintf('%s\n',datestr(now));
                end
            end

            % Show starting configuration
            if (this.echo > 0)
                Animation().curConfig(drv,'Starting');
            end

            % Execute analysis
            tic;
            drv.process();

            % Print finished status
            if (this.echo > 0)
                this.printFinishedStatus(drv,status);
            end
        end

        %------------------------------------------------------------------
        function [status,drv] = loadResults(this)
            status = 1;
            f = dir(this.cur_file);
            fprintf('Storage file (%.3f Mb):\n%s\n',f.bytes/10e5,this.cur_file);

            % Load storage file
            [loaded,drv] = this.loadStorageFile(this.cur_file);
            if (~loaded)
                status = 0;
                return;
            end

            % Set output folder to current folder
            drv.path_out = this.path_in;

            % Show current configuration
            if (this.echo > 0)
                Animation().curConfig(drv,'');
            end
        end
    end

Public methods: auxiliary functions

    methods
        %------------------------------------------------------------------
        function printHeader(~)
            fprintf('==================================================================\n');
            fprintf('           DEMLab - Discrete Element Method Laboratory            \n');
            fprintf('                      Version 1.0 - May 2022                      \n');
            fprintf(' International Center for Numerical Methods in Engineering (CIMNE)\n');
            fprintf('      Polytechnic University of Catalonia (UPC BarcelonaTech)     \n');
            fprintf('==================================================================\n\n');
        end

        %------------------------------------------------------------------
        function status = getInputFiles(this)
            status  = 1;

            % Get files from dialog
            filter  = {'*.json','Parameters File (*.json)';'*.mat','Storage File (*.mat)'};
            title   = 'DEMLab - Input file';
            default = 'ProjectParameters.json';
            [file_names,path] = uigetfile(filter,title,default,'MultiSelect','on');
            if (isequal(file_names,0))
                fprintf('No file selected.\n');
                fprintf('\nExiting program...\n');
                status = 0;
                return;
            end
            file_fullnames = fullfile(path,file_names);

            % Convert to string array
            this.files   = string(file_fullnames);
            this.path_in = string(path);
        end

        %------------------------------------------------------------------
        function displayInputFiles(this)
            n_files = length(this.files);
            fprintf('%d input files selected:\n',n_files);
            for i = 1:n_files
                fprintf('%s\n',this.files(i));
            end
            fprintf('\n------------------------------------------------------------------\n\n');
        end

        %------------------------------------------------------------------
        function printSimulationInfo(~,drv)
            fprintf('\nSimulation ready:\n');
            if (~isempty(drv.name))
                fprintf('Name...................: %s\n',drv.name);
            end
            switch drv.type
                case drv.MECHANICAL
                    fprintf('Type...................: Mechanical\n');
                case drv.type == drv.THERMAL
                    fprintf('Type...................: Thermal\n');
                case drv.THERMO_MECHANICAL
                    fprintf('Type...................: Thermo-mechanical\n');
            end
            if (~isempty(drv.n_walls))
                fprintf('Number of walls........: %d\n',drv.n_walls);
            end
            if (~isempty(drv.n_particles))
                fprintf('Number of particles....: %d\n',drv.n_particles);
            end
            if (~isempty(drv.particles))
                ravg = mean([drv.particles.radius]);
                rdev = std([drv.particles.radius]);
                rmin = min([drv.particles.radius]);
                rmax = max([drv.particles.radius]);
                if (rdev/rmin < 1e-8)
                    rdev = 0;
                end
                fprintf('Average radius.........: %.3e\n',ravg);
                fprintf('Radius deviation.......: %.3e\n',rdev);
                if (rdev ~= 0)
                    fprintf('Min radius.............: %.3e\n',rmin);
                    fprintf('Max radius.............: %.3e\n',rmax);
                end
            end
            if (~isempty(drv.particles) &&...
               (drv.type == drv.THERMAL || drv.type == drv.THERMO_MECHANICAL))
                tavg = mean([drv.particles.temperature]);
                tdev = std([drv.particles.temperature]);
                tmin = min([drv.particles.temperature]);
                tmax = max([drv.particles.temperature]);
                fprintf('Average temperature....: %.3e\n',tavg);
                fprintf('Temperature deviation..: %.3e\n',tdev);
                if (tdev ~= 0)
                    fprintf('Min temperature........: %.3e\n',tmin);
                    fprintf('Max temperature........: %.3e\n',tmax);
                end
            end
            if (~isempty(drv.mass_particle))
                fprintf('Total mass.............: %.3e\n',drv.mass_particle);
            end
            if (~isempty(drv.time_step))
                fprintf('Time step..............: %.3e\n',drv.time_step);
            end
            if (~isempty(drv.max_time))
                fprintf('Final time.............: %f\n',drv.max_time);
            end
        end

        %------------------------------------------------------------------
        function printFinishedStatus(~,drv,status)
            fprintf('\n\nAnalysis finished:\n');
            fprintf('%s\n',datestr(now));
            if (status == 1)
                time            = seconds(toc);
                avg_time        = time/drv.step;
                time.Format     = 'hh:mm:ss.SS';
                avg_time.Format = 's';
                fprintf('Total time:.....: %s\n',string(time));
                fprintf('Avg step time:..: %s\n',string(avg_time));
            elseif (status == 2)
                curr_time         = seconds(toc);
                total_time        = seconds(drv.total_time);
                avg_time          = total_time/drv.step;
                curr_time.Format  = 'hh:mm:ss.SS';
                total_time.Format = 'hh:mm:ss.SS';
                avg_time.Format   = 's';
                fprintf('Current analysis time:..: %s\n',string(curr_time));
                fprintf('Total simulation time:..: %s\n',string(total_time));
                fprintf('Avg step time:..........: %s\n',string(avg_time));
            end
        end

        %------------------------------------------------------------------
        function printExit(this)
            if (this.is_last)
                fprintf('\nExiting program...\n');
            else
                fprintf('\nAborted!\n');
                fprintf('\n------------------------------------------------------------------\n\n');
            end
        end

        %------------------------------------------------------------------
        function [status,drv] = loadStorageFile(this,storage_file)
            status = 1;
            try
                warning off MATLAB:load:variableNotFound
                load(storage_file,'drv');
                warning on MATLAB:load:variableNotFound
            catch
                fprintf(2,'\nError loading storage file.\n');
                this.printExit();
                status = 0;
                return;
            end
            if (~exist('drv','var') || isempty(drv))
                fprintf(2,'\nInvalid stored data.\n');
                this.printExit();
                status = 0;
                return;
            end
        end

        %------------------------------------------------------------------
        function compareTest(this,drv)
            % Check if reference and current results files exist
            name_ref = strcat(drv.path_in,drv.name,"_ref.pos");
            name_cur = strcat(drv.path_out,drv.name,".pos");
            if (exist(name_ref,'file') ~= 2)
                fprintf(2,'\nMissing reference results file!\n');
                this.deleteFile(name_cur);
                status = rmdir(drv.path_out); %#ok<NASGU>
                return;
            end
            if (exist(name_cur,'file') ~= 2)
                fprintf(2,'\nCurrent results file was not generated correctly!\n');
                this.deleteFile(name_cur);
                status = rmdir(drv.path_out); %#ok<NASGU>
                return;
            end

            % Compare contents of files
            file_ref = javaObject('java.io.File',name_ref);
            file_cur = javaObject('java.io.File',name_cur);
            is_equal = javaMethod('contentEquals','org.apache.commons.io.FileUtils',file_ref,file_cur);
            if (is_equal)
                fprintf(1,'\nTest passed!\n');
            else
                fprintf(2,'\nResults are different!\n');
            end

            % Delete current results file and folder (if empty)
            this.deleteFile(name_cur);
            status = rmdir(drv.path_out); %#ok<NASGU>
        end

        %------------------------------------------------------------------
        function renameRefResultFile(this,drv)
            % Check if current results file exist
            name_cur = strcat(drv.path_out,drv.name,".pos");
            if (exist(name_cur,'file') ~= 2)
                fprintf(2,'\nCurrent results file was not generated correctly!\n');
                this.deleteFile(name_cur);
                status = rmdir(drv.path_out); %#ok<NASGU>
                return;
            end

            % Move current results file out of output folder
            if (~movefile(name_cur,drv.path_in))
                fprintf(2,'\nCurrent results file was not generated correctly!\n');
                this.deleteFile(name_cur);
                status = rmdir(drv.path_out); %#ok<NASGU>
                return;
            end

            % Delete output folder (if empty)
            status = rmdir(drv.path_out); %#ok<NASGU>

            % Rename current results file to reference results file
            name_cur = strcat(drv.path_in,drv.name,".pos");
            name_ref = strcat(drv.path_in,drv.name,"_ref.pos");
            movefile(name_cur,name_ref);
            fprintf(1,'\nReference results generated!\n');
        end

        %------------------------------------------------------------------
        function deleteFile(~,file_name)
            if (exist(file_name,'file') == 2)
                warning off MATLAB:DELETE:FileNotFound
                delete(sprintf('%s',file_name));
                warning on MATLAB:DELETE:FileNotFound
            end
        end

        %------------------------------------------------------------------
        % To be called before the analysis (currently not available)
        function startParallel(~,drv)
            p = gcp('nocreate');
            if (drv.parallel)
                max_workers = parcluster('local').NumWorkers;
                if (drv.workers > max_workers)
                    drv.workers = max_workers;
                    warning('off','backtrace');
                    warning('The selected number of workers is greater than the available number of workers in this machine. The simulation will proceed with the %d available workers',max_workers);
                    warning('on','backtrace');
                end
                if (isempty(p))
                    fprintf('\n');
                    parpool(drv.workers);
                elseif (p.NumWorkers ~= drv.workers)
                    fprintf('\n');
                    delete(p)
                    parpool(drv.workers);
                end
            elseif (~isempty(p))
                fprintf('\n');
                delete(p);
            end
            ps = parallel.Settings;
            ps.Pool.AutoCreate = false;
        end
    end
end