From 82fb8f19fd3fafa7d8aa10b6462b4b90771409fc Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Thu, 22 Aug 2024 00:31:09 -0400 Subject: [PATCH] [feat] allow mcxlab and pmcx to use cfg.flog to control log printing --- mcxlab/mcxlab.m | 6 ++++++ src/mcx_core.cu | 14 ++++++++------ src/mcx_utils.c | 4 +++- src/mcx_utils.h | 6 +++--- src/mcxlab.cpp | 24 ++++++++++++++++++++++++ src/pmcx.cpp | 23 ++++++++++++++++++++++- 6 files changed, 66 insertions(+), 11 deletions(-) diff --git a/mcxlab/mcxlab.m b/mcxlab/mcxlab.m index 44f02b1a..07d6d2f8 100644 --- a/mcxlab/mcxlab.m +++ b/mcxlab/mcxlab.m @@ -317,6 +317,12 @@ % 'M': return photon trajectory data as the 5th output % 'P': show progress bar % 'T': save photon trajectory data only, as the 1st output, disable flux/detp/seeds outputs +% cfg.flog: [2] log printing control; if set to a string, it defines a file path +% at which location the log will be printed in append mode; on Linux and Mac OS, +% one can use special paths such as /dev/null; if set to an integer, +% 2 (default): stderr +% 1: stdout +% 0: stdout but suppress printing MCX banner % cfg.istrajstokes [0]: if set to 1, traj.iquv output contains the Stokes IQUV vector along trajectories % cfg.maxjumpdebug: [10000000|int] when trajectory is requested in the output, % use this parameter to set the maximum position stored. By default, diff --git a/src/mcx_core.cu b/src/mcx_core.cu index 52e2900f..78fc821e 100644 --- a/src/mcx_core.cu +++ b/src/mcx_core.cu @@ -2573,14 +2573,14 @@ int mcx_list_gpu(Config* cfg, GPUInfo** info) { } if (deviceCount == 0) { - MCX_FPRINTF(stderr, S_RED "ERROR: No CUDA-capable GPU device found\n" S_RESET); + MCX_FPRINTF(cfg->flog, S_RED "ERROR: No CUDA-capable GPU device found\n" S_RESET); return 0; } *info = (GPUInfo*)calloc(deviceCount, sizeof(GPUInfo)); if (cfg->gpuid && cfg->gpuid > deviceCount) { - MCX_FPRINTF(stderr, S_RED "ERROR: Specified GPU ID is out of range\n" S_RESET); + MCX_FPRINTF(cfg->flog, S_RED "ERROR: Specified GPU ID is out of range\n" S_RESET); return 0; } @@ -2614,7 +2614,7 @@ int mcx_list_gpu(Config* cfg, GPUInfo** info) { (*info)[dev].autoblock = MAX((*info)[dev].maxmpthread / mcx_smxblock(dp.major, dp.minor), 64); if ((*info)[dev].autoblock == 0) { - MCX_FPRINTF(stderr, S_RED "WARNING: maxThreadsPerMultiProcessor can not be detected\n" S_RESET); + MCX_FPRINTF(cfg->flog, S_RED "WARNING: maxThreadsPerMultiProcessor can not be detected\n" S_RESET); (*info)[dev].autoblock = 64; } @@ -2944,7 +2944,7 @@ void mcx_run_simulation(Config* cfg, GPUInfo* gpu) { /** Here we determine if the GPU memory of the current device can store all time gates, if not, disabling normalization */ if (totalgates > gpu[gpuid].maxgate && cfg->isnormalized) { - MCX_FPRINTF(stderr, S_RED "WARNING: GPU memory can not hold all time gates, disabling normalization to allow multiple runs\n" S_RESET); + MCX_FPRINTF(cfg->flog, S_RED "WARNING: GPU memory can not hold all time gates, disabling normalization to allow multiple runs\n" S_RESET); cfg->isnormalized = 0; } @@ -3135,7 +3135,7 @@ void mcx_run_simulation(Config* cfg, GPUInfo* gpu) { * Saving detected photon is enabled by default, but in case if a user disabled this feature, a warning is printed */ if (cfg->issavedet) { - MCX_FPRINTF(stderr, S_RED "WARNING: this MCX binary can not save partial path, please recompile mcx and make sure -D SAVE_DETECTORS is used by nvcc\n" S_RESET); + MCX_FPRINTF(cfg->flog, S_RED "WARNING: this MCX binary can not save partial path, please recompile mcx and make sure -D SAVE_DETECTORS is used by nvcc\n" S_RESET); cfg->issavedet = 0; } @@ -3177,7 +3177,8 @@ void mcx_run_simulation(Config* cfg, GPUInfo* gpu) { */ tic = StartTimer(); #pragma omp master - { + + if (cfg->printnum >= 0) { mcx_printheader(cfg); #ifdef MCX_TARGET_NAME @@ -3190,6 +3191,7 @@ void mcx_run_simulation(Config* cfg, GPUInfo* gpu) { MCX_FPRINTF(cfg->flog, "- compiled with: RNG [%s] with Seed Length [%d]\n", MCX_RNG_NAME, (int)((sizeof(RandType)*RAND_BUF_LEN) >> 2)); fflush(cfg->flog); } + #pragma omp barrier /** diff --git a/src/mcx_utils.c b/src/mcx_utils.c index dfc37772..82e6db3a 100644 --- a/src/mcx_utils.c +++ b/src/mcx_utils.c @@ -5393,7 +5393,8 @@ int mcx_run_from_json(char* jsonstr) { */ void mcx_printheader(Config* cfg) { - MCX_FPRINTF(cfg->flog, S_MAGENTA"\ + if (cfg->printnum >= 0 ) { + MCX_FPRINTF(cfg->flog, S_MAGENTA"\ ###############################################################################\n\ # Monte Carlo eXtreme (MCX) -- CUDA #\n\ # Copyright (c) 2009-2024 Qianqian Fang #\n\ @@ -5412,6 +5413,7 @@ void mcx_printheader(Config* cfg) { ###############################################################################\n\ $Rev:: $" S_GREEN MCX_VERSION S_MAGENTA " $Date:: $ by $Author:: $\n\ ###############################################################################\n" S_RESET); + } } /** diff --git a/src/mcx_utils.h b/src/mcx_utils.h index df13658c..b4950e25 100644 --- a/src/mcx_utils.h +++ b/src/mcx_utils.h @@ -199,7 +199,7 @@ typedef struct MCXConfig { unsigned int maxgate; /**>;\n", arraydim[0]*arraydim[1]); + } else if (strcmp(name, "flog") == 0) { + int len = mxGetNumberOfElements(item); + char logfile[MAX_SESSION_LENGTH] = {'\0'}; + + if (mxIsChar(item)) { + if (len > 0) { + mxGetString(item, logfile, MAX_SESSION_LENGTH); + cfg->flog = fopen(logfile, "a+"); + + if (cfg->flog == NULL) { + mexErrMsgTxt("Log output file can not be written"); + } + } else { + cfg->flog = stdout; + } + } else { + double* val = mxGetPr(item); + + if (len > 0 && val[0] <= 2) { + cfg->flog = ((int)val[0] == 2 ? stderr : ((int)val[0] == 1 ? stdout : (cfg->printnum = -1, stdout))); + } + } + + printf("mcx.flog=%d;\n", cfg->flog); } else { printf(S_RED "WARNING: redundant field '%s'\n" S_RESET, name); } diff --git a/src/pmcx.cpp b/src/pmcx.cpp index 56732389..87373a2c 100644 --- a/src/pmcx.cpp +++ b/src/pmcx.cpp @@ -399,7 +399,7 @@ void parseVolume(const py::dict& user_cfg, Config& mcx_config) { void parse_config(const py::dict& user_cfg, Config& mcx_config) { mcx_initcfg(&mcx_config); - mcx_config.flog = stdout; + mcx_config.flog = stderr; GET_SCALAR_FIELD(user_cfg, mcx_config, nphoton, py::int_); GET_SCALAR_FIELD(user_cfg, mcx_config, nblocksize, py::int_); GET_SCALAR_FIELD(user_cfg, mcx_config, nthread, py::int_); @@ -1003,6 +1003,27 @@ void parse_config(const py::dict& user_cfg, Config& mcx_config) { } } + if (user_cfg.contains("flog")) { + auto logfile_id_value = user_cfg["flog"]; + + if (py::int_::check_(logfile_id_value)) { + auto logid = py::int_(logfile_id_value); + mcx_config.flog = (logid >= 2 ? stderr : (logid == 1 ? stdout : (mcx_config.printnum = -1, stdout))); + } else if (py::str::check_(logfile_id_value)) { + std::string logfile_id_string_value = py::str(logfile_id_value); + + if (logfile_id_string_value.empty()) { + throw py::value_error("the 'flog' field must be an integer or non-empty string"); + } + + mcx_config.flog = fopen(logfile_id_string_value.c_str(), "a+"); + + if (mcx_config.flog == NULL) { + throw py::value_error("Log output file can not be written"); + } + } + } + // Output arguments parsing GET_SCALAR_FIELD(user_cfg, mcx_config, issave2pt, py::bool_); GET_SCALAR_FIELD(user_cfg, mcx_config, issavedet, py::bool_);