function studyinfo=UpdateStudy(study_dir,varargin)
%% studyinfo=UpdateStudy(study_dir,varargin)
% purpose: helper function to keep track of study metadata when anything is
% saved. only updates simulation-specific info.
% - Update DynaSim studyinfo structure
% - Tracks status of simulations [and analyses]
% - Manage queue for simultaneous processes updating studyinfo structure
% inputs:
% - study_dir
% - options:
% - ...
% outputs:
% - studyinfo
%
% NOTE: this function is not intended for users. it is an internal function
% called by functions like SimulateModel, AnalyzeStudy, and CreateBatch.
%
% See also: CheckStudyinfo, SetupStudy, SimulateModel, CreateBatch, AnalyzeStudy
% note: this function should only update metadata stored in the
% studyinfo.simulations substructure.
% check inputs
options=CheckOptions(varargin,{...
'process_id',[],[],... % identifier for simulation to update
'status',[],[],... % status of simulation
'modifications_set',[],[],... % search space
'model',[],[],... % base_model or modified_model
'simulator_options',[],[],... % base_simulator_options
'solve_file',[],[],... % m- or mex-file solving the system
'prefix','study',[],... % prefix prepended to all output files
'email',[],[],... % email to send notification upon study completion
'verbose_flag',0,{0,1},...
'duration',[],[],... % process duration
},false);
% 'base_data_files',[],[],... % base data files to analyze
% 'project_id',[],[],... % unique identifier of parent project
% load most recent version of studyinfo structure from file
studyinfo=CheckStudyinfo(study_dir,'process_id',options.process_id);
already_finished = all(strcmp('finished',{studyinfo.simulations.status}));
% track status of simulations and analyses
% index to this simulation in studyinfo
sim_ind=find([studyinfo.simulations.sim_id]==options.process_id);
% update studyinfo.simulations
% update basic fields
if ~isempty(options.simulator_options)
studyinfo.simulations(sim_ind).simulator_options=options.simulator_options;
end
if ~isempty(options.solve_file)
studyinfo.simulations(sim_ind).solve_file=options.solve_file;
end
% status-specific updates
if ~isempty(options.status) && ~isempty(studyinfo.simulations)
if options.verbose_flag
fprintf('setting status=''%s'' where sim_id=%g\n',options.status,options.process_id);
end
studyinfo.simulations(sim_ind).status=options.status;
switch options.status
case 'finished'
% set stop_time
studyinfo.simulations(sim_ind).stop_time=datestr(now);
studyinfo.simulations(sim_ind).simulator_options=options.simulator_options;
% save DynaSim model structure
if isstruct(options.model)
model=options.model;
model_file=studyinfo.simulations(sim_ind).modified_model_file;
save(model_file,'model','-v7.3');
if options.verbose_flag
fprintf('model saved to %s\n',model_file);
end
end
% obtain and record machine info
try
[~,kernel] = system('uname -v');
[~,OS] = system('uname -o');
[~,home] = system('echo $HOME'); % home directory
[zzz, computername] = system('hostname'); % Uses linux system command to get the machine name of host.
[zzz, meminfo] = system('cat /proc/meminfo'); % Uses linux system command to get a report on system memory
total_memory = textscan(meminfo, '%*s %s %s', 1); % Parses the memory report for 2nd and 3rd space-delimited items of first line: memory amount.
total_memory = [total_memory{1}{1} ' ' total_memory{2}{1}]; % Extracts the info from cell array to create char array.
[zzz, cpuinfo] = system('cat /proc/cpuinfo'); % Uses linux system command to get a report on CPU types and speeds, etc.
cpuinfo = textscan(cpuinfo, '%*s %*s %*s %*s %s %*s %*s %s', 1, 'delimiter', '\n'); % Extracts lines 5 and 8 of report: Proc Type, and Cache
CPU_type = textscan(cpuinfo{1}{1}, '%*s %s', 1, 'delimiter' , ':'); % Parses line that includes CPU type.
CPU_type = [strrep(CPU_type{1}{1},' ','') ]; % ## Look for better way for this: collapse all repeated white space stretches to single spaces.
CPU_cache = textscan(cpuinfo{2}{1}, '%*s %s', 1, 'delimiter' , ':'); % Parses line that includes the cache info
CPU_cache = [CPU_cache{1}{1} ]; % Takes the cache info out of the cell array.
machine_info.host_name=strtrim(computername);
machine_info.total_memory=total_memory;
machine_info.CPU_type=CPU_type;
machine_info.CPU_cache=CPU_cache;
machine_info.num_cores=feature('numcores'); % Matlab command to get the number of processor cores on current host machine.
machine_info.operating_system=strtrim(OS);
machine_info.kernel=strtrim(kernel);
machine_info.home_dir=strtrim(home);
studyinfo.simulations(sim_ind).machine_info=machine_info;
end
% set duration
if isempty(options.duration)
%t1=datevec(studyinfo.simulations(sim_ind).start_time);
%t2=datevec(now);
duration=nan;%etime(t2,t1);
else
duration=options.duration;
end
studyinfo.simulations(sim_ind).duration=duration;
% copy cluster logs to study directory
if isdir(studyinfo.simulations(sim_ind).batch_dir)
log_dir=fullfile(studyinfo.study_dir,'logs');
log_file=fullfile(studyinfo.simulations(sim_ind).batch_dir,'pbsout',['sim_job' num2str(options.process_id) '.out']);
if exist(log_file,'file')
if ~exist(log_dir,'dir')
if options.verbose_flag
fprintf('creating study logs directory: %s\n',log_dir);
end
mkdir(log_dir);
end
if options.verbose_flag
fprintf('copying cluster log to study logs directory: %s\n',log_file);
end
[success,msg]=copyfile(log_file,log_dir);
if ~success, error(msg); end
end
end
case 'failed'
% set error_log
%studyinfo.simulations(sim_ind).error_log=MException.last;
% usage: DisplayError(studyinfo.simulations(k).error_log);
studyinfo.simulations(sim_ind).error_log=lasterr;
% copy cluster logs to study directory
if isdir(studyinfo.simulations(sim_ind).batch_dir)
log_dir=fullfile(studyinfo.study_dir,'logs');
log_file=fullfile(studyinfo.simulations(sim_ind).batch_dir,'pbsout',['sim_job' num2str(options.process_id) '.out']);
errfile=fullfile(studyinfo.simulations(sim_ind).batch_dir,'pbsout',['sim_job' num2str(options.process_id) '.err']);
if exist(log_file,'file')
if options.verbose_flag
fprintf('copying cluster logs to study logs directory...\n');
fprintf('\t%s\n',log_file);
end
copyfile(log_file,log_dir);
end
if exist(errfile,'file')
if options.verbose_flag
fprintf('\t%s\n',errfile);
end
copyfile(errfile,log_dir);
end
end
otherwise
% do nothing
end
end
% update studyinfo.analysis
if ~isempty(options.status) && ~isempty(studyinfo.analysis)
% ...
end
% check if study is complete; clean up if so (e.g, remove batchdirs)
if all(strcmp('finished',{studyinfo.simulations.status}))
fprintf('ALL SIMULATIONS HAVE FINISHED!\n');
% cleanup study
if ~already_finished % this is the first time the study has completed
if 0
% cleanup batch directory
batch_dirs=unique({studyinfo.simulations.batch_dir});
log_dir=fullfile(studyinfo.study_dir,'logs');
if ~exist(log_dir,'dir')
if options.verbose_flag
fprintf('creating log directory: %s\n',log_dir);
end
mkdir(log_dir);
end
for i=1:length(batch_dirs)
if ~exist(batch_dirs{i},'dir')
continue;
end
% copy log files from batch_dir to study_dir/logs
if exist(fullfile(batch_dirs{i},'pbsout'),'dir')
if options.verbose_flag
fprintf('copying log files from batch directory to study logs directory...\n');
end
copyfile(fullfile(batch_dirs{i},'pbsout'),log_dir);
end
% remove batch directory
if options.verbose_flag
fprintf('removing batch directory: %s\n',batch_dirs{i});
end
rmdir(batch_dirs{i},'s');
end
end
% email notification:
if ~isempty(options.email)
%% Send Email
if options.verbose_flag
fprintf('emailing notification of study completion to %s\n',options.email);
end
email_attachments = {};
reply_address = 'batch@infinitebrain.org';
try
setpref('Internet','SMTP_Server','127.0.0.1'); % Sets the outgoing mail server - often the default 127.0.0.1
setpref('Internet','E_mail',reply_address); % Sets the email FROM/reply address for all outgoing email reports.
if ~isempty(email_attachments)
fprintf('Files attached to report email:\n');
email_attachments{:}
end
sendmail(options.email,sprintf('Analysis report for study: "%s"',studyinfo.study_dir),...
[10 options.prefix '. Max simulation time: ' sprintf('%0.2f min',max([studyinfo.simulations.duration])/60) '. Browse models (results?) online at infinitebrain.org. Current time: ' datestr(now,31) ' (Automated message from DynaSim (github.com/dynasim/dynasim).)' 10],...
{email_attachments{:}});
fprintf('\nreport emailed successfully to: %s\n',options.email);
catch exception
fprintf('\nFailed to email report to: %s\n',options.email);
DisplayError(exception);
end
end
end
end
% todo: auto-relaunch batch? ...
% ...
% update studyinfo on disk
StudyinfoIO(studyinfo.simulations(sim_ind),study_dir,options.process_id,options.verbose_flag);
% case 'started'
% % set start_time
% studyinfo.simulations(sim_ind).start_time=datestr(now);
% studyinfo.simulations(sim_ind).simulator_options=options.simulator_options;
% % save DynaSim model structure
% if isstruct(options.model)
% model=options.model;
% model_file=studyinfo.simulations(sim_ind).modified_model_file;
% save(model_file,'model','-v7.3');
% if options.verbose_flag
% fprintf('model saved to %s\n',model_file);
% end
% end
% % obtain and record machine info
% try
% [~,kernel] = system('uname -v');
% [~,OS] = system('uname -o');
% [~,home] = system('echo $HOME'); % home directory
% [zzz, computername] = system('hostname'); % Uses linux system command to get the machine name of host.
% [zzz, meminfo] = system('cat /proc/meminfo'); % Uses linux system command to get a report on system memory
% total_memory = textscan(meminfo, '%*s %s %s', 1); % Parses the memory report for 2nd and 3rd space-delimited items of first line: memory amount.
% total_memory = [total_memory{1}{1} ' ' total_memory{2}{1}]; % Extracts the info from cell array to create char array.
% [zzz, cpuinfo] = system('cat /proc/cpuinfo'); % Uses linux system command to get a report on CPU types and speeds, etc.
% cpuinfo = textscan(cpuinfo, '%*s %*s %*s %*s %s %*s %*s %s', 1, 'delimiter', '\n'); % Extracts lines 5 and 8 of report: Proc Type, and Cache
% CPU_type = textscan(cpuinfo{1}{1}, '%*s %s', 1, 'delimiter' , ':'); % Parses line that includes CPU type.
% CPU_type = [strrep(CPU_type{1}{1},' ','') ]; % ## Look for better way for this: collapse all repeated white space stretches to single spaces.
% CPU_cache = textscan(cpuinfo{2}{1}, '%*s %s', 1, 'delimiter' , ':'); % Parses line that includes the cache info
% CPU_cache = [CPU_cache{1}{1} ]; % Takes the cache info out of the cell array.
% machine_info.host_name=strtrim(computername);
% machine_info.total_memory=total_memory;
% machine_info.CPU_type=CPU_type;
% machine_info.CPU_cache=CPU_cache;
% machine_info.num_cores=feature('numcores'); % Matlab command to get the number of processor cores on current host machine.
% machine_info.operating_system=strtrim(OS);
% machine_info.kernel=strtrim(kernel);
% machine_info.home_dir=strtrim(home);
% studyinfo.simulations(sim_ind).machine_info=machine_info;
% end