%QMU class definition
%
%   Usage:
%      qmu=qmu();

classdef qmu
	properties (SetAccess=public)
		isdakota                    = 0;
		output                      = 0;
		variables                   = struct();
		responses                   = struct();
		method                      = struct();
		params                      = struct();
		results                     = struct();
		vpartition                   = NaN;
		epartition                   = NaN;
		numberofpartitions          = 0;
		numberofresponses           = 0;
		variabledescriptors         = {};
		responsedescriptors         = {};
		mass_flux_profile_directory = NaN;
		mass_flux_profiles          = NaN;
		mass_flux_segments          = {};
		adjacency                   = NaN;
		vertex_weight               = NaN;
	end
	methods (Static)
		function self = loadobj(self) % {{{
			% This function is directly called by matlab when a model object is
			% loaded. If the input is a struct it is an old version of this class and
			% old fields must be recovered (make sure they are in the deprecated
			% model properties)

			if verLessThan('matlab','7.9'),
				disp('Warning: your matlab version is old and there is a risk that load does not work correctly');
				disp('         if the model is not loaded correctly, rename temporarily loadobj so that matlab does not use it');

				% This is a Matlab bug: all the fields of md have their default value
				% Example of error message:
				% Warning: Error loading an object of class 'model':
				% Undefined function or method 'exist' for input arguments of type 'cell'
				%
				% This has been fixed in MATLAB 7.9 (R2009b) and later versions
			end

			if isstruct(self)
				disp('Recovering qmu from older version');
				objstruct = self;
				self = structtoobj(qmu(),objstruct);

				%2019 Dec 7th
				if isfield(objstruct,'partition'),      self.vpartition     = objstruct.partition;       end;
			end

		end% }}}
	end
	methods
		function self = extrude(self,md) % {{{
			self.vpartition=project3d(md,'vector',self.vpartition','type','node');
			self.epartition=project3d(md,'vector',self.epartition','type','element');
		end % }}}
		function self = qmu(varargin) % {{{
			switch nargin
				case 0
					self=setdefaultparameters(self);
				otherwise
					error('constructor not supported');
			end
		end % }}}
		function self = setdefaultparameters(self) % {{{

		end % }}}
		function md = checkconsistency(self,md,solution,analyses) % {{{

			%Early return
			if ~md.qmu.isdakota, return; end

			version=IssmConfig('_DAKOTA_VERSION_'); version=str2num(version(1:3));

			if version < 6,
				if md.qmu.params.evaluation_concurrency~=1,
					md = checkmessage(md,['concurrency should be set to 1 when running dakota in library mode']);
				end
			else
				if ~strcmpi(self.params.evaluation_scheduling,'master'),
					md = checkmessage(md,['evaluation_scheduling in qmu.params should be set to ''master''']);
				end
				if md.cluster.np<=1,
					md = checkmessage(md,['in parallel library mode, Dakota needs to run on at least 2 cpus, 1 cpu for the master, 1 cpu for the slave. Modify md.cluster.np accordingly.']);
				end

				if self.params.processors_per_evaluation<1,
					md = checkmessage(md,['in parallel library mode, Dakota needs to run at least one slave on one cpu (md.qmu.params.processors_per_evaluation >=1)!']);
				end
				if mod(md.cluster.np-1,self.params.processors_per_evaluation),
					%md = checkmessage(md,['in parallel library mode, the requirement is for md.cluster.np = md.qmu.params.processors_per_evaluation * number_of_slaves, where number_of_slaves will automatically be determined by Dakota. Modify md.cluster.np accordingly']);
				end
			end
			if ~isempty(md.qmu.vpartition) & ~any(isnan(md.qmu.vpartition)),
				if size(md.qmu.vpartition,1)~=md.mesh.numberofvertices
					md = checkmessage(md,['user supplied vertex partition for qmu analysis should have size md.mesh.numberofvertices x 1']);
				end
				if min(md.qmu.vpartition)~=0,
					md = checkmessage(md,['vertex partition vector not indexed from 0 on']);
				end
				if max(md.qmu.vpartition)>=md.qmu.numberofpartitions,
					md = checkmessage(md,['for qmu analysis, vertex partitioning vector cannot go over npart, number of partition areas']);
				end
			end
			if ~isempty(md.qmu.epartition) & ~any(isnan(md.qmu.epartition)),
				if size(md.qmu.epartition,1)~=md.mesh.numberofelements,
					md = checkmessage(md,['user supplied element partition for qmu analysis should have size md.mesh.numberofelements x 1']);
				end
				if min(md.qmu.epartition)~=0,
					md = checkmessage(md,['element partition vector not indexed from 0 on']);
				end
				if max(md.qmu.epartition)>=md.qmu.numberofpartitions,
					md = checkmessage(md,['for qmu analysis, element partitioning vector cannot go over npart, number of partition areas']);
				end
			end
			if isempty(md.qmu.vpartition) | any(isnan(md.qmu.vpartition)) | isempty(md.qmu.epartition) | any(isnan(md.qmu.epartition)),
				md = checkmessage(md,['for qmu analysis, both an element and partitioning vectors need to be supplied with no nan values! One can be defaulted to all zeros.']);
			end

		end % }}}
		function disp(self) % {{{
			disp(sprintf('   qmu parameters:'));

			fielddisplay(self,'isdakota','is qmu analysis activated?');
			fielddisplay(self,'output','are we outputting ISSM results, default is 0');
			for i=1:numel(self.variables)
				disp(sprintf('         variables%s:  (arrays of each variable class)',...
					string_dim(self.variables,i)));
				fnames=fieldnames(self.variables(i));
				maxlen=0;
				for j=1:numel(fnames)
					maxlen=max(maxlen,length(fnames{j}));
				end

				for j=1:numel(fnames)
					disp(sprintf(['            %-' num2str(maxlen+1) 's:    [%ix%i]    ''%s'''],...
						fnames{j},size(self.variables.(fnames{j})),class(self.variables.(fnames{j}))));
				end
			end
			for i=1:numel(self.responses)
				disp(sprintf('         responses%s:  (arrays of each response class)',...
					string_dim(self.responses,i)));
				fnames=fieldnames(self.responses(i));
				maxlen=0;
				for j=1:numel(fnames)
					maxlen=max(maxlen,length(fnames{j}));
				end

				for j=1:numel(fnames)
					disp(sprintf(['            %-' num2str(maxlen+1) 's:    [%ix%i]    ''%s'''],...
						fnames{j},size(self.responses.(fnames{j})),class(self.responses.(fnames{j}))));
				end
			end
			fielddisplay(self,'numberofresponses','number of responses')
			for i=1:numel(self.method);
				if strcmp(class(self.method(i)),'dakota_method')
					disp(sprintf('            method%s :    ''%s''',...
						string_dim(self.method,i),self.method(i).method));
				end
			end
			for i=1:numel(self.params)
				disp(sprintf('         params%s:  (array of method-independent parameters)',...
					string_dim(self.params,i)));
				fnames=fieldnames(self.params(i));
				maxlen=0;
				for j=1:numel(fnames)
					maxlen=max(maxlen,length(fnames{j}));
				end

				for j=1:numel(fnames)
					disp(sprintf(['            %-' num2str(maxlen+1) 's: %s'],...
						fnames{j},any2str(self.params(i).(fnames{j}))));
				end
			end
			for i=1:numel(self.results)
				disp(sprintf('         results%s:  (information from dakota files)',...
					string_dim(self.results,i)));
				fnames=fieldnames(self.results(i));
				maxlen=0;
				for j=1:numel(fnames)
					maxlen=max(maxlen,length(fnames{j}));
				end

				for j=1:numel(fnames)
					disp(sprintf(['            %-' num2str(maxlen+1) 's:    [%ix%i]    ''%s'''],...
						fnames{j},size(self.results.(fnames{j})),class(self.results.(fnames{j}))));
				end
			end
			fielddisplay(self,'vpartition','user provided mesh partitioning (vertex based)');
			fielddisplay(self,'epartition','user provided mesh partitioning (element based)');
			fielddisplay(self,'numberofpartitions','number of partitions for semi-discrete qmu')
			fielddisplay(self,'variabledescriptors','');
			fielddisplay(self,'responsedescriptors','');
			fielddisplay(self,'method','array of dakota_method class');
			fielddisplay(self,'mass_flux_profile_directory','directory for mass flux profiles');
			fielddisplay(self,'mass_flux_profiles','list of mass_flux profiles');
			fielddisplay(self,'mass_flux_segments','');
			fielddisplay(self,'adjacency','');
			fielddisplay(self,'vertex_weight','weight applied to each mesh vertex');

		end % }}}
		function marshall(self,prefix,md,fid) % {{{
			WriteData(fid,prefix,'object',self,'fieldname','isdakota','format','Boolean');
			WriteData(fid,prefix,'object',self,'fieldname','output','format','Boolean');
			if ~self.isdakota,
				WriteData(fid,prefix,'data',false,'name','md.qmu.mass_flux_segments_present','format','Boolean');
				return;
			end
			WriteData(fid,prefix,'object',self,'fieldname','vpartition','format','DoubleMat','mattype',2);
			WriteData(fid,prefix,'object',self,'fieldname','epartition','format','DoubleMat','mattype',2);
			WriteData(fid,prefix,'object',self,'fieldname','numberofpartitions','format','Integer');
			WriteData(fid,prefix,'object',self,'fieldname','numberofresponses','format','Integer');
			WriteData(fid,prefix,'object',self,'fieldname','variabledescriptors','format','StringArray');
			WriteData(fid,prefix,'object',self,'fieldname','responsedescriptors','format','StringArray');
			if ~isempty(self.mass_flux_segments),
				WriteData(fid,prefix,'data',self.mass_flux_segments,'name','md.qmu.mass_flux_segments','format','MatArray');
				flag=true;
			else
				flag=false;
			end
			WriteData(fid,prefix,'data',flag,'name','md.qmu.mass_flux_segments_present','format','Boolean');
		end % }}}
		function savemodeljs(self,fid,modelname) % {{{

			if self.isdakota,
				error('qmu savemodeljs error message: not supported yet!');
			end

		end % }}}
	end
end
