diff --git a/common/lib/share/mccode-r.c b/common/lib/share/mccode-r.c index f93ae38bfb..3b37e4e096 100644 --- a/common/lib/share/mccode-r.c +++ b/common/lib/share/mccode-r.c @@ -49,6 +49,8 @@ #define pclose _pclose #endif #include +#include +#include // UNIX specific headers (non-Windows) #if defined(__unix__) || defined(__APPLE__) @@ -831,7 +833,8 @@ MCDETECTOR mcdetector_statistics( double *this_p1=NULL; /* new 1D McCode array [x I E N]. Freed after writing data */ /* if McCode/PGPLOT and rank==1 we create a new m*4 data block=[x I E N] */ - if (detector.rank == 1 && strcasestr(detector.format,"McCode")) { + /* (not for lists: they carry no histogram statistics and must keep their data) */ + if (detector.rank == 1 && strcasestr(detector.format,"McCode") && !strcasestr(detector.format,"list")) { this_p1 = (double *)calloc(detector.m*detector.n*detector.p*4, sizeof(double)); if (!this_p1) exit(-fprintf(stderr, "Error: Out of memory creating %zi 1D " MCCODE_STRING " data set for file '%s' (detector_import)\n", @@ -1328,18 +1331,43 @@ void siminfo_out(char *format, ...) } /* siminfo_out */ +/* Unknown-count event streams reserve these fields in the normal ASCII + * metadata lines and replace the placeholder at finalization. The fixed + * field keeps the existing header order and avoids copying a completed data + * file just to insert a header at its beginning. */ +#define MC_EVENT_STREAM_COUNT_DIGITS 20 +typedef struct { + long type_count_offset; + long limits_count_offset; + int valid; +} MC_EVENT_ASCII_PATCH; + /******************************************************************************* * mcdatainfo_out: output detector header * mcdatainfo_out(prefix, file_handle, detector) writes info to data file *******************************************************************************/ static void -mcdatainfo_out(char *pre, FILE *f, MCDETECTOR detector) +mcdatainfo_out_ex(char *pre, FILE *f, MCDETECTOR detector, + MC_EVENT_ASCII_PATCH *patch) { + long line_offset; + long type_line_offset; + int prefix_length; + int type_prefix_length; + if (!f || !detector.m || mcdisable_output_files) return; /* output data ============================================================ */ fprintf(f, "%sDate: %s (%li)\n", pre, detector.date, detector.date_l); - fprintf(f, "%stype: %s\n", pre, detector.type); + if (patch) { + type_line_offset = ftell(f); + prefix_length = fprintf(f, "%stype: list(%ld, ", pre, detector.m); + fprintf(f, "%0*lld)\n", MC_EVENT_STREAM_COUNT_DIGITS, 0LL); + type_prefix_length = prefix_length; + patch->type_count_offset = type_line_offset + type_prefix_length; + } else { + fprintf(f, "%stype: %s\n", pre, detector.type); + } fprintf(f, "%sSource: %s\n", pre, detector.instrument); fprintf(f, "%scomponent: %s\n", pre, detector.component); fprintf(f, "%sposition: %s\n", pre, detector.position); @@ -1369,10 +1397,21 @@ mcdatainfo_out(char *pre, FILE *f, MCDETECTOR detector) } } - fprintf(f, - abs(detector.rank)==1 ? - "%sxlimits: %s\n" : - "%sxylimits: %s\n", pre, detector.limits); + if (patch) { + line_offset = ftell(f); + prefix_length = fprintf(f, "%s%slimits: 1 ", pre, + abs(detector.rank) == 1 ? "x" : "xy"); + fprintf(f, "%0*lld 1 %ld\n", MC_EVENT_STREAM_COUNT_DIGITS, 0LL, + detector.m); + patch->limits_count_offset = line_offset + prefix_length; + patch->valid = type_line_offset >= 0 && type_prefix_length >= 0 + && line_offset >= 0 && prefix_length >= 0; + } else { + fprintf(f, + abs(detector.rank)==1 ? + "%sxlimits: %s\n" : + "%sxylimits: %s\n", pre, detector.limits); + } fprintf(f, "%svariables: %s\n", pre, strcasestr(detector.format, "list") ? detector.ylabel : detector.variables); @@ -1380,6 +1419,12 @@ mcdatainfo_out(char *pre, FILE *f, MCDETECTOR detector) } /* mcdatainfo_out */ +static void +mcdatainfo_out(char *pre, FILE *f, MCDETECTOR detector) +{ + mcdatainfo_out_ex(pre, f, detector, NULL); +} + /* mcdetector_out_array_ascii: output a single array to a file * m: columns * n: rows @@ -2014,6 +2059,79 @@ mcdatainfo_out_nexus(NXhandle f, MCDETECTOR detector) } /* NXdetector (instrument) */ } /* mcdatainfo_out_nexus */ +/* Update attributes on an already-created event group. Re-running + * mcdatainfo_out_nexus attempts to recreate the group hierarchy, which some + * NeXus backends reject before reaching the existing group. Streaming needs + * only the final metadata update, so navigate the established hierarchy. */ +static int +mcevent_update_nexus_metadata(NXhandle f, MCDETECTOR detector) +{ + char data_name[CHAR_BUF_LENGTH]; + + if (!f || !detector.m || mcdisable_output_files) return(NX_ERROR); + strcpy_valid(data_name, strlen(detector.filename) ? + detector.filename : detector.component); + if (NXopengroup(f, "instrument", "NXinstrument") != NX_OK) return(NX_ERROR); + if (NXopengroup(f, "components", "NXdata") != NX_OK) { + NXclosegroup(f); + return(NX_ERROR); + } + if (NXopengroup(f, detector.nexuscomp, "NXdata") != NX_OK) { + NXclosegroup(f); + NXclosegroup(f); + return(NX_ERROR); + } + if (NXopengroup(f, "output", "NXdetector") != NX_OK) { + NXclosegroup(f); + NXclosegroup(f); + NXclosegroup(f); + return(NX_ERROR); + } + if (NXopengroup(f, data_name, "NXdata") != NX_OK) { + NXclosegroup(f); + NXclosegroup(f); + NXclosegroup(f); + NXclosegroup(f); + return(NX_ERROR); + } + + nxprintattr(f, "Date", detector.date); + nxprintattr(f, "type", detector.type); + nxprintattr(f, "Source", detector.instrument); + nxprintattr(f, "component", detector.component); + nxprintattr(f, "position", detector.position); + nxprintattr(f, "title", detector.title); + nxprintattr(f, !mcget_run_num() || mcget_run_num() >= mcget_ncount() ? + "Ncount" : "ratio", detector.ncount); + if (strlen(detector.filename)) + nxprintattr(f, "filename", detector.filename); + nxprintattr(f, "statistics", detector.statistics); + nxprintattr(f, "signal", detector.signal); + nxprintattr(f, "values", detector.values); + if (detector.rank >= 1) { + nxprintattr(f, "xvar", detector.xvar); + nxprintattr(f, "yvar", detector.yvar); + nxprintattr(f, "xlabel", detector.xlabel); + nxprintattr(f, "ylabel", detector.ylabel); + if (detector.rank > 1) { + nxprintattr(f, "zvar", detector.zvar); + nxprintattr(f, "zlabel", detector.zlabel); + } + } + nxprintattr(f, abs(detector.rank) == 1 ? "xlimits" : "xylimits", + detector.limits); + nxprintattr(f, "variables", + strcasestr(detector.format, "list") ? + detector.ylabel : detector.variables); + + NXclosegroup(f); + NXclosegroup(f); + NXclosegroup(f); + NXclosegroup(f); + NXclosegroup(f); + return(NX_OK); +} + /******************************************************************************* * mcdetector_out_axis_nexus: write detector axis into current NXdata * requires: NXdata to be opened @@ -2138,6 +2256,8 @@ int mcdetector_out_array_nexus(NXhandle f, char *part, double *data, MCDETECTOR int mcdetector_out_data_nexus(NXhandle f, MCDETECTOR detector) { char data_name[CHAR_BUF_LENGTH]; + int ret = NX_OK; + int written = 0; if (!f || !detector.m || mcdisable_output_files) return(NX_OK); @@ -2175,14 +2295,20 @@ int mcdetector_out_data_nexus(NXhandle f, MCDETECTOR detector) /* write the actual data (appended if already exists) */ if (!strcasestr(detector.format, "list") && !strcasestr(detector.format, "pixels")) { - mcdetector_out_array_nexus(f, "data", detector.p1, detector); - mcdetector_out_array_nexus(f, "errors", detector.p2, detector); - mcdetector_out_array_nexus(f, "ncount", detector.p0, detector); + if (mcdetector_out_array_nexus(f, "data", detector.p1, detector) != NX_OK) + ret = NX_ERROR; + if (mcdetector_out_array_nexus(f, "errors", detector.p2, detector) != NX_OK) + ret = NX_ERROR; + if (mcdetector_out_array_nexus(f, "ncount", detector.p0, detector) != NX_OK) + ret = NX_ERROR; } else if (strcasestr(detector.format, "pixels")) { - mcdetector_out_array_nexus( f, "pixels", detector.p1, detector); + if (mcdetector_out_array_nexus(f, "pixels", detector.p1, detector) != NX_OK) + ret = NX_ERROR; } else { - mcdetector_out_array_nexus( f, "events", detector.p1, detector); + if (mcdetector_out_array_nexus(f, "events", detector.p1, detector) != NX_OK) + ret = NX_ERROR; } + written = 1; NXclosegroup(f); NXopengroup(f, data_name, "NXdata"); NXgetgroupID(nxhandle, &pLink); @@ -2203,7 +2329,8 @@ int mcdetector_out_data_nexus(NXhandle f, MCDETECTOR detector) NXclosegroup(f); } } - return(NX_OK); + if (!written) ret = NX_ERROR; + return(ret); } /* mcdetector_out_array_nexus */ #ifdef USE_MPI @@ -2606,6 +2733,137 @@ MCDETECTOR mcdetector_out_2D(char *t, char *xl, char *yl, } /* mcdetector_out_2D */ +/* Host-only wrapper for rank-local resolution-matrix averaging. */ +long mcdetector_out_2D_average(char *title, char *xl, char *yl, + double x1, double x2, double y1, double y2, + long m, long n, const double *matrix, int local_valid, + char *filename, char *component, Coords position, + Rotation rotation, int index) +{ + int local_dimensions_ok = m > 0 && n > 0; +#ifdef USE_MPI + long local_dimensions[2] = {m, n}; + long minimum_dimensions[2]; + long maximum_dimensions[2]; + int all_dimensions_ok = local_dimensions_ok; + if (mpi_node_count > 1) { + int mpi_status; + int dimensions_status = MPI_SUCCESS; + /* Keep every rank in the same collective sequence even if MPI reports an + error, rather than short-circuiting later Allreduce calls. */ + mpi_status = MPI_Allreduce(local_dimensions, minimum_dimensions, 2, + MPI_LONG, MPI_MIN, MPI_COMM_WORLD); + if (mpi_status != MPI_SUCCESS) + dimensions_status = mpi_status; + mpi_status = MPI_Allreduce(local_dimensions, maximum_dimensions, 2, + MPI_LONG, MPI_MAX, MPI_COMM_WORLD); + if (mpi_status != MPI_SUCCESS) + dimensions_status = mpi_status; + mpi_status = MPI_Allreduce(&local_dimensions_ok, &all_dimensions_ok, 1, + MPI_INT, MPI_MIN, MPI_COMM_WORLD); + if (mpi_status != MPI_SUCCESS) + dimensions_status = mpi_status; + if (dimensions_status != MPI_SUCCESS) + return 0; + if (!all_dimensions_ok + || minimum_dimensions[0] != maximum_dimensions[0] + || minimum_dimensions[1] != maximum_dimensions[1]) + return 0; + } else if (!local_dimensions_ok) { + return 0; + } +#else + if (!local_dimensions_ok) + return 0; +#endif + + int local_contribution = local_valid && matrix != NULL; + long local_valid_count = local_contribution ? 1 : 0; + long global_valid_count = 0; + +#ifdef USE_MPI + if (mpi_node_count > 1) { + if (MPI_Allreduce(&local_valid_count, &global_valid_count, 1, MPI_LONG, + MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS) + return 0; + } else { + global_valid_count = local_valid_count; + } +#else + global_valid_count = local_valid_count; +#endif + + if (global_valid_count <= 0) + return 0; + + size_t element_count = 0; + int dimensions_ok = (uintmax_t)m <= (uintmax_t)SIZE_MAX / (uintmax_t)n; + if (dimensions_ok) { + uintmax_t product = (uintmax_t)m * (uintmax_t)n; + if (product > (uintmax_t)SIZE_MAX) + dimensions_ok = 0; + else + element_count = (size_t)product; + } + if (dimensions_ok && element_count > SIZE_MAX / sizeof(double)) + dimensions_ok = 0; + + double *p0 = NULL; + double *p1 = NULL; + double *p2 = NULL; + int local_allocation_ok = dimensions_ok; + if (local_allocation_ok) { + p0 = (double *)calloc(element_count, sizeof(double)); + p1 = (double *)calloc(element_count, sizeof(double)); + p2 = (double *)calloc(element_count, sizeof(double)); + if (!p0 || !p1 || !p2) + local_allocation_ok = 0; + } + + int allocation_ok = local_allocation_ok; +#ifdef USE_MPI + if (mpi_node_count > 1) { + if (MPI_Allreduce(&local_allocation_ok, &allocation_ok, 1, MPI_INT, + MPI_MIN, MPI_COMM_WORLD) != MPI_SUCCESS) { + free(p0); + free(p1); + free(p2); + return 0; + } + } +#endif + if (!allocation_ok) { + if (!local_allocation_ok) + fprintf(stderr, + "Error: mcdetector_out_2D_average: unable to allocate temporary arrays for %ld x %ld matrix.\n", + m, n); + free(p0); + free(p1); + free(p2); + return 0; + } + + if (local_contribution) { + size_t i; + double scale = 1.0 / (double)global_valid_count; + for (i = 0; i < element_count; i++) { + p0[i] = 1.0; + p1[i] = matrix[i] * scale; + p2[i] = p1[i] * p1[i]; + } + } + + MCDETECTOR detector = mcdetector_out_2D( + title, xl, yl, x1, x2, y1, y2, m, n, + p0, p1, p2, filename, component, position, rotation, index); + if (detector.p1 && detector.p1 != p1) + free(detector.p1); + free(p0); + free(p1); + free(p2); + return global_valid_count; +} + /******************************************************************************* * mcdetector_out_2D_list: List mode 2D including forwarding "options" from * Monitor_nD @@ -2722,6 +2980,1794 @@ MCDETECTOR mcdetector_out_list(char *t, char *xl, char *yl, return(detector); } +/* ========================================================================== */ +/* Generic event sessions */ +/* ========================================================================== */ + +/* Event output is host-side. A session lets every MPI rank describe its + * local chunks first, then lets rank zero drain complete rank streams in a + * fixed order. This deliberately does not use the legacy list barrier loop: + * local row counts and local chunk counts may differ between ranks. */ + +static MCDETECTOR mcevent_invalid_detector(void) +{ + MCDETECTOR detector; + memset(&detector, 0, sizeof(detector)); + detector.filename[0] = '\0'; + detector.m = 0; + return(detector); +} + +static void mcevent_copy_string(char *dst, const char *src) +{ + if (!dst) return; + if (!src) src = ""; + strncpy(dst, src, CHAR_BUF_LENGTH-1); + dst[CHAR_BUF_LENGTH-1] = '\0'; +} + +static int mcevent_chunk_summary(MC_EVENT_CHUNK *chunks, long width, + long long *rows, long long *chunk_count) +{ + MC_EVENT_CHUNK *chunk; + long long total = 0; + long long count = 0; + int valid = width > 0; + + for (chunk = chunks; chunk; chunk = chunk->next) { + count++; + if (chunk->count < 0 || (chunk->count > 0 && chunk->data == NULL)) + valid = 0; + if (chunk->count > 0) { + if (width <= 0 || (uintmax_t)chunk->count > + (uintmax_t)SIZE_MAX / (uintmax_t)width / sizeof(double)) + valid = 0; + if (total > LLONG_MAX - (long long)chunk->count) + valid = 0; + else + total += (long long)chunk->count; + } + } + + if (rows) *rows = total; + if (chunk_count) *chunk_count = count; + return(valid); +} + +/* Build the detector descriptor without requiring a full event payload. A + * one-element dummy keeps detector_import's metadata/statistics path valid; + * list data are never inspected by mcdetector_statistics. */ +static MCDETECTOR mcevent_session_detector(char *title, char *xl, + char *columns, long count, long width, + char *filename, char *component, Coords position, + Rotation rotation, char *options, int index) +{ + char format[CHAR_BUF_LENGTH]; + const char *base = mcformat && strlen(mcformat) ? mcformat : "McCode"; + double dummy = 0; + int nexus = strcasestr(base, "NeXus") != NULL; + MCDETECTOR detector; + + snprintf(format, CHAR_BUF_LENGTH, "%s list", base); + detector = detector_import(format, + component, title, + 1, 1, 1, + xl, columns, "Signal per bin", + "x", "y", "I", + 1, count, 1, width, 0, 0, + filename, NULL, &dummy, NULL, + position, rotation, index); + + /* Normalize dimensions and metadata for the two backends. ASCII uses the + * historical transposed detector layout to print row-major lines; NeXus + * stores the natural (rows, columns) shape. */ + detector.rank = 2; + detector.p = 1; + detector.istransposed = nexus ? 0 : 1; + if (nexus) { + detector.m = count; + detector.n = width; + snprintf(detector.type, CHAR_BUF_LENGTH, "list(%ld, %ld)", count, width); + } else { + detector.m = width; + detector.n = count; + snprintf(detector.type, CHAR_BUF_LENGTH, "list(%ld, %ld)", width, count); + } + detector.xmin = 1; + detector.xmax = count; + detector.ymin = 1; + detector.ymax = width; + snprintf(detector.limits, CHAR_BUF_LENGTH, "1 %ld 1 %ld", count, width); + mcevent_copy_string(detector.xlabel, xl && strlen(xl) ? xl : "List of events"); + mcevent_copy_string(detector.ylabel, columns && strlen(columns) ? columns : "None"); + mcevent_copy_string(detector.options, options && strlen(options) ? options : "None"); + detector.p0 = NULL; + detector.p1 = NULL; + detector.p2 = NULL; + return(detector); +} + +typedef struct { + MCDETECTOR detector; + FILE *ascii_file; + int nexus; + int ready; + int failed; + int stream; + int opened; + long stream_width; + Coords position; + Rotation rotation; + int index; + MC_EVENT_ASCII_PATCH siminfo_patch; + MC_EVENT_ASCII_PATCH data_patch; +} MC_EVENT_OUTPUT; + +static int mcevent_output_begin(MC_EVENT_OUTPUT *output, MCDETECTOR detector) +{ + int exists = 0; + + memset(output, 0, sizeof(*output)); + output->detector = detector; + output->nexus = strcasestr(detector.format, "NeXus") != NULL; + + if (mcdisable_output_files) { + output->ready = 1; + return(1); + } + + if (output->nexus) { +#ifdef USE_NEXUS + mcdatainfo_out_nexus(nxhandle, detector); + output->ready = 1; +#else + output->failed = 1; +#endif + return(output->ready); + } + + output->ascii_file = mcnew_file(detector.filename, "dat", &exists); + if (!output->ascii_file) return(0); + + if (!exists) { + siminfo_out("\nbegin data\n"); + mcdatainfo_out(" ", siminfo_file, detector); + siminfo_out("end data\n"); + + mcruninfo_out("# ", output->ascii_file); + mcdatainfo_out("# ", output->ascii_file, detector); + if (strcasestr(detector.format, "list")) + printf("Events: \"%s\"\n", + strlen(detector.filename) ? detector.filename : detector.component); + } + fprintf(output->ascii_file, "# Data [%s/%s] %s:\n", + detector.component, detector.filename, detector.zvar); + output->ready = 1; + if (ferror(output->ascii_file)) output->failed = 1; + return(!output->failed); +} + +static int mcevent_output_begin_stream(MC_EVENT_OUTPUT *output, + MCDETECTOR detector, Coords position, + Rotation rotation, int index) +{ + if (!output) return(0); + memset(output, 0, sizeof(*output)); + output->detector = detector; + output->nexus = strcasestr(detector.format, "NeXus") != NULL; + output->stream = 1; + output->stream_width = output->nexus ? detector.n : detector.m; + output->position = position; + rot_copy(output->rotation, rotation); + output->index = index; + /* Delay creating the final file until the first non-empty chunk. This keeps + the established zero-event behavior: an empty stream creates no output. */ + output->ready = 1; + return(1); +} + +static int mcevent_output_open_stream(MC_EVENT_OUTPUT *output) +{ + char *path; + FILE *existing; + int exists = 0; + + if (!output || !output->stream || !output->ready) return(0); + if (output->opened) return(!output->failed); + if (mcdisable_output_files) { + output->opened = 1; + return(1); + } + + if (output->nexus) { +#ifdef USE_NEXUS + mcdatainfo_out_nexus(nxhandle, output->detector); + output->opened = 1; + return(1); +#else + output->failed = 1; + return(0); +#endif + } + + path = mcfull_file(output->detector.filename, "dat"); + existing = fopen(path, "r"); + if (existing) { + fclose(existing); + exists = 1; + } + if (exists) { + /* Existing append files have no patch locations for this stream. Keep + the legacy spooled writer as the compatible append path. */ + fprintf(stderr, + "WARNING: unknown-count event stream requires a new output file: '%s'\n", + output->detector.filename); + free(path); + output->failed = 1; + return(0); + } + output->ascii_file = fopen(path, "w+"); + free(path); + if (!output->ascii_file) { + output->failed = 1; + return(0); + } + + siminfo_out("\nbegin data\n"); + mcdatainfo_out_ex(" ", siminfo_file, output->detector, + &output->siminfo_patch); + siminfo_out("end data\n"); + + mcruninfo_out("# ", output->ascii_file); + mcdatainfo_out_ex("# ", output->ascii_file, output->detector, + &output->data_patch); + if (!output->data_patch.valid || !output->siminfo_patch.valid) { + output->failed = 1; + return(0); + } + printf("Events: \"%s\"\n", + strlen(output->detector.filename) ? output->detector.filename + : output->detector.component); + fprintf(output->ascii_file, "# Data [%s/%s] %s:\n", + output->detector.component, output->detector.filename, + output->detector.zvar); + output->opened = 1; + if (ferror(output->ascii_file)) output->failed = 1; + return(!output->failed); +} + +static int mcevent_patch_ascii_count(FILE *file, + MC_EVENT_ASCII_PATCH *patch, + long long rows) +{ + char value[MC_EVENT_STREAM_COUNT_DIGITS + 1]; + + if (!file || !patch || !patch->valid || rows < 0) return(0); + snprintf(value, sizeof(value), "%-*lld", MC_EVENT_STREAM_COUNT_DIGITS, rows); + + if (fseek(file, patch->type_count_offset, SEEK_SET) != 0 + || fwrite(value, 1, MC_EVENT_STREAM_COUNT_DIGITS, file) + != MC_EVENT_STREAM_COUNT_DIGITS + || fseek(file, patch->limits_count_offset, SEEK_SET) != 0 + || fwrite(value, 1, MC_EVENT_STREAM_COUNT_DIGITS, file) + != MC_EVENT_STREAM_COUNT_DIGITS + || fflush(file) != 0 + || fseek(file, 0, SEEK_END) != 0) + return(0); + return(1); +} + +static int mcevent_patch_siminfo_count(MC_EVENT_ASCII_PATCH *patch, + long long rows) +{ + char *path; + FILE *file; + int result; + + if (!patch || !patch->valid) return(0); + path = mcfull_file(siminfo_name, "sim"); + file = fopen(path, "r+"); + free(path); + if (!file) return(0); + result = mcevent_patch_ascii_count(file, patch, rows); + fclose(file); + return(result); +} + +static MCDETECTOR mcevent_stream_detector(MC_EVENT_OUTPUT *output, + long long rows) +{ + MCDETECTOR detector = output->detector; + long width = output->stream_width; + + detector.rank = 2; + detector.p = 1; + if (output->nexus) { + detector.m = (long)rows; + detector.n = width; + snprintf(detector.type, CHAR_BUF_LENGTH, "list(%ld, %ld)", + (long)rows, width); + detector.istransposed = 0; + } else { + detector.m = width; + detector.n = (long)rows; + snprintf(detector.type, CHAR_BUF_LENGTH, "list(%ld, %ld)", + width, (long)rows); + detector.istransposed = 1; + } + detector.xmin = 1; + detector.xmax = (long)rows; + detector.ymin = 1; + detector.ymax = width; + snprintf(detector.limits, CHAR_BUF_LENGTH, "1 %ld 1 %ld", + (long)rows, width); + detector.p0 = NULL; + detector.p1 = NULL; + detector.p2 = NULL; + return(detector); +} + +static int mcevent_output_finalize_stream(MC_EVENT_OUTPUT *output, + long long rows) +{ + MCDETECTOR detector; + + if (!output || !output->stream || !output->opened || output->failed) + return(output && !output->failed); + if (mcdisable_output_files) return(1); + if (rows <= 0 || rows > LONG_MAX || output->stream_width <= 0) + return(0); + detector = mcevent_stream_detector(output, rows); + if (!detector.m) return(0); + + if (output->nexus) { +#ifdef USE_NEXUS + /* Event datasets are created with an unlimited row dimension. Rewriting + the normal metadata at end keeps type/limits identical to a known-count + event output while the payload was written incrementally. */ + return(mcevent_update_nexus_metadata(nxhandle, detector) == NX_OK); +#else + return(0); +#endif + } + + if (!mcevent_patch_ascii_count(output->ascii_file, + &output->data_patch, rows)) + return(0); + if (!mcevent_patch_siminfo_count(&output->siminfo_patch, rows)) + return(0); + return(1); +} + +static int mcevent_output_chunk(MC_EVENT_OUTPUT *output, long rows, + double *data) +{ + if (!output || !output->ready) return(0); + if (rows <= 0) return(1); + if (!data || output->failed) return(0); + if (mcdisable_output_files) return(1); + if (output->stream && !mcevent_output_open_stream(output)) return(0); + + if (output->nexus) { +#ifdef USE_NEXUS + MCDETECTOR chunk = output->detector; + chunk.m = rows; + chunk.n = output->detector.n; + chunk.p = 1; + chunk.p1 = data; + if (mcdetector_out_data_nexus(nxhandle, chunk) != NX_OK) + output->failed = 1; +#else + output->failed = 1; +#endif + } else { + mcdetector_out_array_ascii(output->detector.m, rows, data, + output->ascii_file, + output->detector.istransposed); + if (!output->ascii_file || ferror(output->ascii_file)) + output->failed = 1; + } + return(!output->failed); +} + +static int mcevent_output_end(MC_EVENT_OUTPUT *output) +{ + int result = 1; + if (!output) return(0); + if (output->ascii_file && fclose(output->ascii_file) != 0) result = 0; + output->ascii_file = NULL; + output->ready = 0; + output->opened = 0; + if (output->failed) result = 0; + return(result); +} + +static int mcevent_write_local_chunks(MC_EVENT_OUTPUT *output, + MC_EVENT_CHUNK *chunks) +{ + MC_EVENT_CHUNK *chunk; + for (chunk = chunks; chunk; chunk = chunk->next) + if (!mcevent_output_chunk(output, chunk->count, chunk->data)) return(0); + return(1); +} + +typedef struct { + MC_EVENT_CHUNK *chunks; + FILE *spool; + long long rows; + long long chunk_count; + int valid; +} MC_EVENT_SOURCE; + +static int mcevent_source_read_chunk(FILE *spool, long width, + long *rows, double **data) +{ + long long stored_rows; + size_t values; + size_t bytes; + double *chunk_data; + + if (!spool || !rows || !data || width <= 0) return(0); + *rows = 0; + *data = NULL; + if (fread(&stored_rows, sizeof(stored_rows), 1, spool) != 1) + return(0); + if (stored_rows <= 0 || stored_rows > LONG_MAX + || (uintmax_t)stored_rows > + (uintmax_t)SIZE_MAX / (uintmax_t)width / sizeof(double)) + return(0); + + values = (size_t)stored_rows * (size_t)width; + bytes = values * sizeof(double); + chunk_data = (double *)malloc(bytes); + if (!chunk_data) return(0); + if (fread(chunk_data, sizeof(double), values, spool) != values) { + free(chunk_data); + return(0); + } + *rows = (long)stored_rows; + *data = chunk_data; + return(1); +} + +static int mcevent_write_local_source(MC_EVENT_OUTPUT *output, + MC_EVENT_SOURCE *source, long width) +{ + long long sequence; + + if (!source || !source->valid) return(0); + if (!source->spool) { + return(mcevent_write_local_chunks(output, source->chunks)); + } + + rewind(source->spool); + for (sequence = 0; sequence < source->chunk_count; sequence++) { + long rows; + double *data = NULL; + if (!mcevent_source_read_chunk(source->spool, width, &rows, &data)) + return(0); + if (!mcevent_output_chunk(output, rows, data)) { + free(data); + return(0); + } + free(data); + } + return(ferror(source->spool) == 0); +} + +#ifdef USE_MPI + +#define MC_EVENT_MPI_MAGIC 0x4d434556544c5353LL +#define MC_EVENT_MPI_TAG_HEADER 2001 +#define MC_EVENT_MPI_TAG_CHUNK 2002 +#define MC_EVENT_MPI_TAG_DATA 2003 +#define MC_EVENT_MPI_TAG_END 2004 +#define MC_EVENT_MPI_TAG_READY 2005 +#define MC_EVENT_MPI_BLOCK_BYTES 65536 + +typedef struct { + long long magic; + long long width; + long long rows; + long long chunk_count; + int index; + double position[3]; + double rotation[9]; + char title[CHAR_BUF_LENGTH]; + char columns[CHAR_BUF_LENGTH]; + char filename[CHAR_BUF_LENGTH]; + char component[CHAR_BUF_LENGTH]; + char xlabel[CHAR_BUF_LENGTH]; + char options[CHAR_BUF_LENGTH]; + int valid; +} MC_EVENT_MPI_HEADER; + +typedef struct { + long long magic; + long long sequence; + long long rows; +} MC_EVENT_MPI_CHUNK_HEADER; + +typedef struct { + long long magic; + long long chunks; + int valid; +} MC_EVENT_MPI_END; + +static int mcevent_mpi_send_bytes(const void *data, size_t bytes, + int destination, int tag) +{ + size_t offset = 0; + size_t block = MC_EVENT_MPI_BLOCK_BYTES; + + while (offset < bytes) { + size_t part = bytes - offset; + if (part > block) part = block; + if (MPI_Send((void *)((const char *)data + offset), (int)part, + MPI_BYTE, destination, tag, MPI_COMM_WORLD) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + offset += part; + } + return(MPI_SUCCESS); +} + +static int mcevent_mpi_recv_bytes(void *data, size_t bytes, int source, int tag) +{ + size_t offset = 0; + size_t block = MC_EVENT_MPI_BLOCK_BYTES; + unsigned char scratch[MC_EVENT_MPI_BLOCK_BYTES]; + while (offset < bytes) { + size_t part = bytes - offset; + if (part > block) part = block; + if (MPI_Recv(data ? (void *)((char *)data + offset) : (void *)scratch, + (int)part, MPI_BYTE, source, tag, MPI_COMM_WORLD, + MPI_STATUS_IGNORE) != MPI_SUCCESS) { + return(MPI_ERR_COUNT); + } + offset += part; + } + return(MPI_SUCCESS); +} + +static int mcevent_mpi_send_header(MC_EVENT_MPI_HEADER *header, int destination) +{ + return(mcevent_mpi_send_bytes(header, sizeof(*header), destination, + MC_EVENT_MPI_TAG_HEADER)); +} + +static int mcevent_mpi_recv_header(MC_EVENT_MPI_HEADER *header, int source) +{ + return(mcevent_mpi_recv_bytes(header, sizeof(*header), source, + MC_EVENT_MPI_TAG_HEADER)); +} + +static int mcevent_mpi_send_ready(int destination) +{ + int ready = 1; + return(mcevent_mpi_send_bytes(&ready, sizeof(ready), destination, + MC_EVENT_MPI_TAG_READY)); +} + +static int mcevent_mpi_recv_ready(int source) +{ + int ready = 0; + if (mcevent_mpi_recv_bytes(&ready, sizeof(ready), source, + MC_EVENT_MPI_TAG_READY) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + return(ready == 1 ? MPI_SUCCESS : MPI_ERR_OTHER); +} + +static int mcevent_mpi_header(MC_EVENT_MPI_HEADER *header, long width, + long long rows, long long chunk_count, + char *title, char *xl, char *columns, + char *filename, char *component, char *options, + Coords position, Rotation rotation, int index, + int valid) +{ + int i, j; + memset(header, 0, sizeof(*header)); + header->magic = MC_EVENT_MPI_MAGIC; + header->width = width; + header->rows = rows; + header->chunk_count = chunk_count; + header->index = index; + header->position[0] = position.x; + header->position[1] = position.y; + header->position[2] = position.z; + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + header->rotation[3*i+j] = rotation[i][j]; + mcevent_copy_string(header->title, title); + mcevent_copy_string(header->columns, columns); + mcevent_copy_string(header->filename, filename); + mcevent_copy_string(header->component, component); + mcevent_copy_string(header->xlabel, xl); + mcevent_copy_string(header->options, options); + header->valid = valid; + return(0); +} + +static int mcevent_mpi_headers_match(MC_EVENT_MPI_HEADER *a, + MC_EVENT_MPI_HEADER *b) +{ + int i; + + if (a->magic != b->magic || a->width != b->width || a->index != b->index + || strcmp(a->title, b->title) != 0 + || strcmp(a->columns, b->columns) != 0 + || strcmp(a->filename, b->filename) != 0 + || strcmp(a->component, b->component) != 0 + || strcmp(a->xlabel, b->xlabel) != 0 + || strcmp(a->options, b->options) != 0) + return(0); + for (i = 0; i < 3; i++) + if (a->position[i] != b->position[i]) return(0); + for (i = 0; i < 9; i++) + if (a->rotation[i] != b->rotation[i]) return(0); + return(1); +} + +static int mcevent_mpi_send_chunks(MC_EVENT_CHUNK *chunks, long width, + int destination) +{ + MC_EVENT_CHUNK *chunk; + MC_EVENT_MPI_CHUNK_HEADER chunk_header; + MC_EVENT_MPI_END end_record; + long long sequence = 0; + int result = MPI_SUCCESS; + + for (chunk = chunks; chunk; chunk = chunk->next) { + int send_data = chunk->count > 0 && chunk->data && width > 0; + chunk_header.magic = MC_EVENT_MPI_MAGIC; + chunk_header.sequence = sequence++; + chunk_header.rows = send_data ? chunk->count : 0; + if (send_data && ((uintmax_t)chunk->count > + (uintmax_t)SIZE_MAX / (uintmax_t)width / sizeof(double))) { + send_data = 0; + chunk_header.rows = 0; + result = MPI_ERR_COUNT; + } + if (mcevent_mpi_send_bytes(&chunk_header, sizeof(chunk_header), + destination, MC_EVENT_MPI_TAG_CHUNK) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + if (send_data) { + if (mcevent_mpi_send_bytes(chunk->data, + (size_t)chunk_header.rows * (size_t)width * sizeof(double), + destination, MC_EVENT_MPI_TAG_DATA) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + } + } + end_record.magic = MC_EVENT_MPI_MAGIC; + end_record.chunks = sequence; + end_record.valid = result == MPI_SUCCESS; + if (mcevent_mpi_send_bytes(&end_record, sizeof(end_record), destination, + MC_EVENT_MPI_TAG_END) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + return(result); +} + +static int mcevent_mpi_send_spool_bytes(FILE *spool, size_t bytes, + int destination) +{ + unsigned char block[MC_EVENT_MPI_BLOCK_BYTES]; + size_t offset = 0; + int result = MPI_SUCCESS; + + while (offset < bytes) { + size_t part = bytes - offset; + size_t got; + if (part > sizeof(block)) part = sizeof(block); + got = fread(block, 1, part, spool); + if (got != part) { + memset(block + got, 0, part - got); + result = MPI_ERR_COUNT; + } + if (mcevent_mpi_send_bytes(block, part, destination, + MC_EVENT_MPI_TAG_DATA) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + offset += part; + } + return(result); +} + +static int mcevent_mpi_send_source(MC_EVENT_SOURCE *source, long width, + int destination) +{ + MC_EVENT_MPI_CHUNK_HEADER chunk_header; + MC_EVENT_MPI_END end_record; + long long sequence; + int result = MPI_SUCCESS; + + if (!source) return(MPI_ERR_COUNT); + if (!source->spool) + return(mcevent_mpi_send_chunks(source->chunks, width, destination)); + if (width <= 0) return(MPI_ERR_COUNT); + + rewind(source->spool); + for (sequence = 0; sequence < source->chunk_count; sequence++) { + long long stored_rows = 0; + size_t values = 0; + size_t bytes = 0; + int valid = 1; + + if (fread(&stored_rows, sizeof(stored_rows), 1, source->spool) != 1) + valid = 0; + if (stored_rows <= 0 || stored_rows > LONG_MAX + || (uintmax_t)stored_rows > + (uintmax_t)SIZE_MAX / (uintmax_t)width / sizeof(double)) + valid = 0; + if (valid) { + values = (size_t)stored_rows * (size_t)width; + bytes = values * sizeof(double); + } + + chunk_header.magic = MC_EVENT_MPI_MAGIC; + chunk_header.sequence = sequence; + chunk_header.rows = valid ? stored_rows : 0; + if (!valid) result = MPI_ERR_COUNT; + if (mcevent_mpi_send_bytes(&chunk_header, sizeof(chunk_header), + destination, MC_EVENT_MPI_TAG_CHUNK) + != MPI_SUCCESS) + return(MPI_ERR_COUNT); + if (valid && mcevent_mpi_send_spool_bytes(source->spool, bytes, + destination) != MPI_SUCCESS) + result = MPI_ERR_COUNT; + } + + end_record.magic = MC_EVENT_MPI_MAGIC; + end_record.chunks = source->chunk_count; + end_record.valid = result == MPI_SUCCESS; + if (mcevent_mpi_send_bytes(&end_record, sizeof(end_record), destination, + MC_EVENT_MPI_TAG_END) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + return(result); +} + +static int mcevent_mpi_receive_chunks(MC_EVENT_OUTPUT *output, long width, + MC_EVENT_MPI_HEADER *header, int source, + int *valid) +{ + long long i; + long long expected = header ? header->chunk_count : -1; + MC_EVENT_MPI_CHUNK_HEADER chunk_header; + MC_EVENT_MPI_END end_record; + + if (expected < 0) { + *valid = 0; + } + + for (i = 0; expected < 0 || i < expected; i++) { + double *data = NULL; + size_t bytes = 0; + long rows = 0; + int receive_data = 0; + + if (expected < 0) { + MPI_Status status; + if (MPI_Probe(source, MPI_ANY_TAG, MPI_COMM_WORLD, &status) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + if (status.MPI_TAG == MC_EVENT_MPI_TAG_END) break; + if (status.MPI_TAG != MC_EVENT_MPI_TAG_CHUNK) { + *valid = 0; + return(MPI_ERR_TAG); + } + } + + if (mcevent_mpi_recv_bytes(&chunk_header, sizeof(chunk_header), source, + MC_EVENT_MPI_TAG_CHUNK) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + if (chunk_header.magic != MC_EVENT_MPI_MAGIC + || chunk_header.sequence != i) + *valid = 0; + if (chunk_header.rows < 0 || chunk_header.rows > LONG_MAX) { + *valid = 0; + } else if (chunk_header.rows > 0 && width > 0 + && (uintmax_t)chunk_header.rows <= + (uintmax_t)SIZE_MAX / (uintmax_t)width / sizeof(double)) { + rows = (long)chunk_header.rows; + bytes = (size_t)rows * (size_t)width * sizeof(double); + data = (double *)malloc(bytes); + receive_data = data != NULL; + if (!receive_data) *valid = 0; + } else if (chunk_header.rows > 0) { + *valid = 0; + } + + if (chunk_header.rows > 0) { + if (mcevent_mpi_recv_bytes(data, bytes, source, MC_EVENT_MPI_TAG_DATA) + != MPI_SUCCESS) { + free(data); + return(MPI_ERR_COUNT); + } + if (receive_data && *valid + && !mcevent_output_chunk(output, rows, data)) + *valid = 0; + free(data); + } + } + + if (mcevent_mpi_recv_bytes(&end_record, sizeof(end_record), source, + MC_EVENT_MPI_TAG_END) != MPI_SUCCESS) + return(MPI_ERR_COUNT); + if (end_record.magic != MC_EVENT_MPI_MAGIC + || (expected >= 0 && end_record.chunks != expected) + || !end_record.valid) + *valid = 0; + return(MPI_SUCCESS); +} + +#endif /* USE_MPI */ + +static MCDETECTOR mcevent_write_session_source(char *title, char *xl, + char *columns, long width, MC_EVENT_SOURCE *source, + char *filename, char *component, Coords position, + Rotation rotation, char *options, int index) +{ + long long local_rows = source ? source->rows : 0; + long long local_chunks = source ? source->chunk_count : 0; + int local_valid = source && source->valid && width > 0; + +#ifdef USE_MPI + if (mpi_node_count > 1) { + MC_EVENT_MPI_HEADER local_header; + MC_EVENT_OUTPUT output; + MC_EVENT_MPI_HEADER *headers = NULL; + MCDETECTOR detector = mcevent_invalid_detector(); + long long total_rows = local_rows; + int session_valid = local_valid; + int node_i; + + mcevent_mpi_header(&local_header, width, local_rows, local_chunks, + title, xl, columns, filename, component, options, + position, rotation, index, local_valid); + + if (mpi_node_rank != mpi_node_root) { + if (mcevent_mpi_send_header(&local_header, mpi_node_root) != MPI_SUCCESS) + return(mcevent_invalid_detector()); + if (mcevent_mpi_recv_ready(mpi_node_root) != MPI_SUCCESS) + return(mcevent_invalid_detector()); + mcevent_mpi_send_source(source, width, mpi_node_root); + return(mcevent_invalid_detector()); + } + + headers = (MC_EVENT_MPI_HEADER *)calloc((size_t)mpi_node_count, + sizeof(*headers)); + if (!headers) { + MC_EVENT_MPI_HEADER discarded_header; + + fprintf(stderr, "WARNING: event session metadata allocation failed; draining MPI event streams\n"); + session_valid = 0; + for (node_i = 0; node_i < mpi_node_count; node_i++) { + if (node_i == mpi_node_root) continue; + if (mcevent_mpi_recv_header(&discarded_header, node_i) != MPI_SUCCESS) + session_valid = 0; + } + for (node_i = 0; node_i < mpi_node_count; node_i++) { + if (node_i == mpi_node_root) continue; + mcevent_mpi_send_ready(node_i); + } + memset(&output, 0, sizeof(output)); + for (node_i = 0; node_i < mpi_node_count; node_i++) { + if (node_i == mpi_node_root) continue; + mcevent_mpi_receive_chunks(&output, width, NULL, node_i, + &session_valid); + } + return(mcevent_invalid_detector()); + } else { + headers[mpi_node_root] = local_header; + for (node_i = 0; node_i < mpi_node_count; node_i++) { + if (node_i == mpi_node_root) continue; + if (mcevent_mpi_recv_header(&headers[node_i], node_i) != MPI_SUCCESS) { + memset(&headers[node_i], 0, sizeof(headers[node_i])); + headers[node_i].chunk_count = -1; + session_valid = 0; + continue; + } + if (!mcevent_mpi_headers_match(&local_header, &headers[node_i]) + || !headers[node_i].valid) + session_valid = 0; + if (headers[node_i].rows < 0 + || total_rows > LLONG_MAX - headers[node_i].rows) + session_valid = 0; + else + total_rows += headers[node_i].rows; + } + for (node_i = 0; node_i < mpi_node_count; node_i++) { + if (node_i == mpi_node_root) continue; + if (mcevent_mpi_send_ready(node_i) != MPI_SUCCESS) + session_valid = 0; + } + } + + if (session_valid && total_rows > 0 && total_rows <= LONG_MAX + && width > 0 && width <= LONG_MAX) { + detector = mcevent_session_detector(title, xl, columns, + (long)total_rows, width, + filename, component, position, + rotation, options, index); + if (!detector.m) session_valid = 0; + else if (!mcevent_output_begin(&output, detector)) session_valid = 0; + } else { + memset(&output, 0, sizeof(output)); + } + + /* Rank zero writes its own block first, then drains each slave completely + * before advancing. Slaves always send their explicit end record. */ + if (session_valid && !mcevent_write_local_source(&output, source, width)) + session_valid = 0; + for (node_i = 0; node_i < mpi_node_count; node_i++) { + if (node_i == mpi_node_root) continue; + if (headers && mcevent_mpi_receive_chunks( + &output, width, &headers[node_i], node_i, + &session_valid) != MPI_SUCCESS) + session_valid = 0; + } + if (output.ready && !mcevent_output_end(&output)) + session_valid = 0; + free(headers); + if (!session_valid) detector = mcevent_invalid_detector(); + return(detector); + } +#endif + + if (!local_valid || local_rows <= 0 || local_rows > LONG_MAX || width <= 0) + return(mcevent_invalid_detector()); + + { + MCDETECTOR detector = mcevent_session_detector(title, xl, columns, + (long)local_rows, width, + filename, component, + position, rotation, + options, index); + MC_EVENT_OUTPUT output; + if (!detector.m) return(detector); + if (!mcevent_output_begin(&output, detector)) { + if (output.ready) mcevent_output_end(&output); + return(mcevent_invalid_detector()); + } + if (!mcevent_write_local_source(&output, source, width)) { + mcevent_output_end(&output); + return(mcevent_invalid_detector()); + } + if (!mcevent_output_end(&output)) + return(mcevent_invalid_detector()); + return(detector); + } +} + +static MCDETECTOR mcevent_write_session(char *title, char *xl, char *columns, + long width, MC_EVENT_CHUNK *chunks, char *filename, + char *component, Coords position, Rotation rotation, + char *options, int index) +{ + MC_EVENT_SOURCE source; + memset(&source, 0, sizeof(source)); + source.chunks = chunks; + source.valid = mcevent_chunk_summary(chunks, width, + &source.rows, &source.chunk_count); + return(mcevent_write_session_source(title, xl, columns, width, &source, + filename, component, position, rotation, + options, index)); +} + +MCDETECTOR mcevent_out_list_nd_chunks(char *title, char *xl, char *columns, + long width, MC_EVENT_CHUNK *chunks, + char *filename, char *component, Coords position, + Rotation rotation, char *options, int index) +{ + return(mcevent_write_session(title, xl, columns, width, chunks, + filename, component, position, rotation, + options, index)); +} + +/******************************************************************************* +* mcevent_out_list_impl: shared validated delegation to the low-level +* mcdetector_out_list. See mcevent_out_list for the argument/behavior contract. +* xl: x-axis label written to the data-set header (e.g. "List of events"). +* The Monitor_nD variant passes its own flavor-specific label. +* options: string forwarded to the detector metadata (the NeXus 'options' +* attribute). mcevent_out_list passes "None"; the Monitor_nD variant +* forwards its own options string. +* The negative row count hides the internal negative-dimension convention +* used by mcdetector_out_list to force the row-oriented list output. +*******************************************************************************/ +static MCDETECTOR mcevent_out_list_impl(char *title, char *xl, char *columns, + long count, long width, double *data, char *filename, + char *component, Coords position, Rotation rotation, + char *options, int index) +{ + MC_EVENT_CHUNK chunk; + + /* MPI ranks still enter the session for rejected/empty local payloads so a + * valid peer cannot wait for a rank that returned before the protocol. */ + if (count < 0 || width < 0) { + fprintf(stderr, "WARNING: mcevent_out_list: invalid count=%ld or width=%ld; no list written\n", count, width); + } else if (width == 0 && count > 0) { + fprintf(stderr, "WARNING: mcevent_out_list: width=0 with count=%ld; no list written\n", count); + } else if (data == NULL && count > 0) { + fprintf(stderr, "WARNING: mcevent_out_list: null data for count=%ld width=%ld; no list written\n", count, width); + } + + chunk.data = data; + chunk.count = count; + chunk.next = NULL; + +#ifdef USE_MPI + if (mpi_node_count > 1) + return(mcevent_write_session(title, xl, columns ? columns : "", width, + &chunk, filename, component, position, + rotation, options, index)); +#endif + + if (count <= 0 || width <= 0 || data == NULL) + return(mcevent_invalid_detector()); /* documented serial no-op/rejection */ + + return(mcevent_write_session(title, xl, columns ? columns : "", width, + &chunk, filename, component, position, + rotation, options, index)); +} + +/******************************************************************************* +* mcevent_out_list: generic public event-list output wrapper. +* Accepts positive, conventional dimensions and delegates (through +* mcevent_out_list_impl) to the existing low-level mcdetector_out_list, which +* uses a negative row count to force the row-oriented (one event per line) +* list output. The public API uses the neutral x-label "List of events" and +* does not forward any Monitor-specific options ("None"). +* title: title of the data set +* columns: whitespace-separated column names (may be empty/NULL) +* count: number of valid rows (events); 0 gives a documented no-op +* width: number of columns per row +* data: row-major buffer data[r*width+c], non-NULL when count>0 +* filename: output file name (extension/output directory handled by backend) +* Returns the MCDETECTOR structure. A rejected/empty request returns a +* structure with m=0 and an empty filename (the invalid-detector convention) +* and writes no output. Existing McCode ASCII, NeXus, MPI rank-local and +* filename behaviors are preserved. +*******************************************************************************/ +MCDETECTOR mcevent_out_list(char *title, char *columns, long count, long width, + double *data, char *filename, + char *component, Coords position, Rotation rotation, int index) +{ + return(mcevent_out_list_impl(title, "List of events", columns, + count, width, + data, filename, + component, position, rotation, "None", index)); +} + +/******************************************************************************* +* mcevent_out_list_nd: Monitor_nD-specific event-list output entry point. +* Same validated, positive-dimension contract as mcevent_out_list (shares +* mcevent_out_list_impl), but preserves the two Monitor_nD-specific pieces of +* metadata that the generic public API intentionally omits: +* - the flavor-specific x-label (xl, e.g. "List of neutron events" for +* McStas, "List of photon events" for McXtrace) written to the header, and +* - the Monitor_nD options string (options, forwarded to the detector +* metadata / NeXus 'options' attribute). +* This keeps Monitor_nD's list output byte-compatible with its pre-migration +* low-level mcdetector_out_list call until a generic metadata API exists. +* Host-only output plumbing (no acc routine). +*******************************************************************************/ +MCDETECTOR mcevent_out_list_nd(char *title, char *xl, char *columns, + long count, long width, double *data, char *filename, + char *component, Coords position, Rotation rotation, + char *options, int index) +{ + return(mcevent_out_list_impl(title, xl, columns, + count, width, + data, filename, + component, position, rotation, options, index)); +} + +/******************************************************************************* + * mc_event_particle_row: copy the canonical particle state into an event row. + * The destination must have MC_EVENT_PARTICLE_WIDTH values. The helper is + * device-callable and performs no allocation or I/O; null arguments are a + * safe no-op. + *******************************************************************************/ +void mc_event_particle_row(double *row, _class_particle *particle) +{ + if (row == NULL || particle == NULL) return; + + #pragma acc atomic write + row[0] = particle->x; + #pragma acc atomic write + row[1] = particle->y; + #pragma acc atomic write + row[2] = particle->z; +#if MCCODE_PARTICLE_CODE == 2112 + #pragma acc atomic write + row[3] = particle->vx; + #pragma acc atomic write + row[4] = particle->vy; + #pragma acc atomic write + row[5] = particle->vz; + #pragma acc atomic write + row[6] = particle->t; + #pragma acc atomic write + row[7] = particle->sx; + #pragma acc atomic write + row[8] = particle->sy; + #pragma acc atomic write + row[9] = particle->sz; + #pragma acc atomic write + row[10] = particle->p; +#elif MCCODE_PARTICLE_CODE == 22 + #pragma acc atomic write + row[3] = particle->kx; + #pragma acc atomic write + row[4] = particle->ky; + #pragma acc atomic write + row[5] = particle->kz; + #pragma acc atomic write + row[6] = particle->t; + #pragma acc atomic write + row[7] = particle->Ex; + #pragma acc atomic write + row[8] = particle->Ey; + #pragma acc atomic write + row[9] = particle->Ez; + #pragma acc atomic write + row[10] = particle->p; +#endif +} + +/******************************************************************************* +* mc_event_buffer_init: allocate a fixed-capacity event buffer (host only). +* buffer: MC_EVENT_BUFFER to initialize (must be non-NULL) +* capacity: maximum number of rows that fit (must be >= 0; 0 is a valid +* zero-capacity buffer that accepts no rows) +* width: number of columns per row (must be >= 0) +* Returns 0 on success, nonzero on error (null buffer, negative +* capacity/width, or allocation failure). On error the buffer is left zeroed +* (data=NULL, capacity=0) so any later append is a safe no-op that only +* increments `dropped`. +* +* width==0 with capacity>0 is a documented, allowed degenerate case: init +* succeeds, no storage is allocated (data stays NULL), appends reserve slots +* and are accepted (count increments) while copying zero values, and saving +* such a buffer through DETECTOR_OUT_LIST is a no-op, consistent with +* mcevent_out_list's width==0 handling. +*******************************************************************************/ +int mc_event_buffer_init(MC_EVENT_BUFFER *buffer, long capacity, long width) +{ + if (buffer == NULL) return(1); + + memset(buffer, 0, sizeof(*buffer)); + if (capacity < 0) return(1); + if (width < 0) return(1); + + buffer->width = width; + buffer->capacity = capacity; + + /* one flat allocation so buffer->data can go straight to DETECTOR_OUT_LIST */ + if (capacity > 0 && width > 0) { + if ((uintmax_t)capacity > + (uintmax_t)SIZE_MAX / (uintmax_t)width / sizeof(double)) { + memset(buffer, 0, sizeof(*buffer)); + return(1); + } + buffer->data = (double*)malloc((size_t)capacity * (size_t)width * sizeof(double)); + if (buffer->data == NULL) { + /* allocation failed: degrade to a safe zero-capacity buffer */ + memset(buffer, 0, sizeof(*buffer)); + return(1); + } + } + return(0); +} + +/******************************************************************************* +* mc_event_buffer_free: release the buffer allocation (host only). +* Safe after init or for a zero-initialized empty buffer (data==NULL -> +* nothing freed) and idempotent: the fields are reset to zero and data set +* to NULL, so a second call is a no-op and cannot double-free. +*******************************************************************************/ +void mc_event_buffer_free(MC_EVENT_BUFFER *buffer) +{ + if (buffer == NULL) return; + if (buffer->data != NULL) free(buffer->data); + memset(buffer, 0, sizeof(*buffer)); +} + +/******************************************************************************* +* mc_event_buffer_append: store one row in the buffer (OpenACC-safe). +* Reserves the next row slot (atomically when compiled for device code, +* sequentially on the plain CPU path), checks capacity, then copies exactly +* `width` values for an accepted row. No allocation, I/O or printf here, so +* it may run on the device. Overflow never writes out of bounds: the +* rejected reservation is counted in `dropped` and the append returns 0. +* Returns nonzero only when the row was stored; 0 for a full buffer, a null +* buffer, or a null row (which leaves all counters unchanged). +* +* OpenACC: the buffer struct and its `data` array must be in device memory; +* the caller owns that transfer (see the plan's OpenACC limitations). The CPU +* (serial) path stores rows in insertion order; on the device only bounds and +* the accepted count are guaranteed, not row order. +*******************************************************************************/ +int mc_event_buffer_append(MC_EVENT_BUFFER *buffer, const double *row) +{ + long slot, c; + + if (buffer == NULL || row == NULL) return(0); + + /* reserve the next slot before writing anything; the #pragma is active + only when compiling OpenACC device code and ignored on the CPU path */ + #pragma acc atomic capture + { + slot = buffer->next++; + } + + /* overflow: count the rejection and never write out of bounds */ + if (slot >= buffer->capacity) { + #pragma acc atomic + buffer->dropped++; + return(0); + } + + /* accepted: copy exactly width values (a no-op when width==0, where data + is NULL and the loop body never executes) */ + for (c = 0; c < buffer->width; c++) + buffer->data[slot * buffer->width + c] = row[c]; + + #pragma acc atomic + buffer->count++; + + return(1); +} + +/******************************************************************************* +* mc_event_buffer_save: save the accepted rows of an event buffer (host only). +* Saves exactly `count` rows through mcevent_out_list (never `capacity`), +* so the normal extension/output-directory/format behavior applies. If +* `dropped` is nonzero (an overflow happened), a single WARNING containing +* the dropped count is printed to stderr before saving; the warning is not +* fatal and the buffer is left unmodified, so the caller can still report +* the counters itself. +* buffer: MC_EVENT_BUFFER to save (a NULL buffer is a documented no-op) +* title: title of the data set +* columns: whitespace-separated column names (may be empty/NULL) +* filename: output file name (extension/output directory handled by backend) +* component/position/rotation/index: metadata (pass NAME_CURRENT_COMP, +* POS_A_CURRENT_COMP, ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP) +* Returns the MCDETECTOR structure. A rejected/empty request returns the +* invalid-detector sentinel (m=0, empty filename) and writes no output. +*******************************************************************************/ +MCDETECTOR mc_event_buffer_save(MC_EVENT_BUFFER *buffer, char *title, + char *columns, char *filename, + char *component, Coords position, + Rotation rotation, int index) +{ + if (buffer == NULL) + return(mcevent_out_list(title, columns, 0, 0, NULL, filename, + component, position, rotation, index)); + + /* report the overflow once; not fatal and the buffer is left unmodified */ + if (buffer->dropped > 0) + fprintf(stderr, + "WARNING: mc_event_buffer_save: %ld events dropped (capacity exceeded), saving %ld rows to '%s'\n", + buffer->dropped, buffer->count, filename ? filename : ""); + + return(mcevent_out_list(title, columns, buffer->count, buffer->width, + buffer->data, filename, + component, position, rotation, index)); +} + +int mc_event_chunk_append(MC_EVENT_CHUNK **head, MC_EVENT_CHUNK **tail, + double *data, long count) +{ + MC_EVENT_CHUNK *chunk; + if (!head || !tail || count < 0 || (count > 0 && !data)) return(1); + chunk = (MC_EVENT_CHUNK *)malloc(sizeof(*chunk)); + if (!chunk) return(1); + chunk->data = data; + chunk->count = count; + chunk->next = NULL; + if (*tail) (*tail)->next = chunk; + else *head = chunk; + *tail = chunk; + return(0); +} + +void mc_event_chunk_free(MC_EVENT_CHUNK **head) +{ + MC_EVENT_CHUNK *chunk; + if (!head) return; + chunk = *head; + while (chunk) { + MC_EVENT_CHUNK *next = chunk->next; + free(chunk->data); + free(chunk); + chunk = next; + } + *head = NULL; +} + +#ifdef OPENACC +typedef struct mc_event_writer_openacc_entry_struct { + MC_EVENT_WRITER *writer; + struct mc_event_writer_openacc_entry_struct *next; +} MC_EVENT_WRITER_OPENACC_ENTRY; + +static MC_EVENT_WRITER_OPENACC_ENTRY *mc_event_writer_openacc_entries = NULL; + +static int mc_event_writer_openacc_register(MC_EVENT_WRITER *writer) +{ + MC_EVENT_WRITER_OPENACC_ENTRY *entry; + + if (!writer) return(0); + for (entry = mc_event_writer_openacc_entries; entry; entry = entry->next) + if (entry->writer == writer) return(1); + + entry = (MC_EVENT_WRITER_OPENACC_ENTRY *)malloc(sizeof(*entry)); + if (!entry) return(0); + entry->writer = writer; + entry->next = mc_event_writer_openacc_entries; + mc_event_writer_openacc_entries = entry; + return(1); +} + +static void mc_event_writer_openacc_unregister(MC_EVENT_WRITER *writer) +{ + MC_EVENT_WRITER_OPENACC_ENTRY **link = &mc_event_writer_openacc_entries; + + while (*link) { + if ((*link)->writer == writer) { + MC_EVENT_WRITER_OPENACC_ENTRY *entry = *link; + *link = entry->next; + free(entry); + return; + } + link = &(*link)->next; + } +} + +/* Component instances are mapped as one OpenACC object. Synchronizing only + * the nested buffer avoids copying every component at every GPU batch. The + * row allocation is normally managed memory; update it explicitly as well so + * the same path also works with an explicit OpenACC data mapping. */ +static int mc_event_writer_openacc_update_host(MC_EVENT_WRITER *writer) +{ + size_t values; + + if (!writer) return(0); + acc_update_self(&writer->buffer, sizeof(writer->buffer)); + if (writer->buffer.capacity < 0 || writer->buffer.width < 0) return(0); + if (writer->buffer.capacity > 0 && writer->buffer.width > 0 + && (uintmax_t)writer->buffer.capacity > + (uintmax_t)SIZE_MAX / (uintmax_t)writer->buffer.width) + return(0); + + if (!writer->buffer.data || writer->buffer.capacity <= 0 + || writer->buffer.width <= 0) + return(1); + + values = (size_t)writer->buffer.capacity * (size_t)writer->buffer.width; + if (values > SIZE_MAX / sizeof(double)) return(0); + acc_update_self(writer->buffer.data, values * sizeof(double)); + return(1); +} + +static void mc_event_writer_openacc_update_device(MC_EVENT_WRITER *writer) +{ + if (writer) acc_update_device(&writer->buffer, sizeof(writer->buffer)); +} +#endif + +int mc_event_writer_begin(MC_EVENT_WRITER *writer, char *title, char *columns, + long width, long chunk_capacity, char *filename, + char *component, Coords position, Rotation rotation, + int index) +{ + if (!writer) return(1); + + /* Make a rejected begin safe to finish with mc_event_writer_end. The caller + must not reuse an active writer without ending it first. */ + memset(writer, 0, sizeof(*writer)); + if (width <= 0 || chunk_capacity <= 0) return(1); + + writer->width = width; + writer->chunk_capacity = chunk_capacity; + writer->position = position; + rot_copy(writer->rotation, rotation); + writer->index = index; + mcevent_copy_string(writer->title, title); + mcevent_copy_string(writer->columns, columns); + mcevent_copy_string(writer->filename, filename); + mcevent_copy_string(writer->component, component); + + writer->spool = tmpfile(); + if (!writer->spool) { + memset(writer, 0, sizeof(*writer)); + return(1); + } + if (mc_event_buffer_init(&writer->buffer, chunk_capacity, width)) { + fclose(writer->spool); + memset(writer, 0, sizeof(*writer)); + return(1); + } + writer->active = 1; +#ifdef OPENACC + if (!mc_event_writer_openacc_register(writer)) { + mc_event_buffer_free(&writer->buffer); + fclose(writer->spool); + memset(writer, 0, sizeof(*writer)); + return(1); + } +#endif + return(0); +} + +int mc_event_writer_begin_direct(MC_EVENT_WRITER *writer, char *title, + char *columns, long count, long width, + long chunk_capacity, char *filename, + char *component, Coords position, + Rotation rotation, int index) +{ + MC_EVENT_OUTPUT *output; + + if (!writer) return(1); + memset(writer, 0, sizeof(*writer)); + if (count <= 0 || width <= 0 || chunk_capacity <= 0) return(1); +#ifdef USE_MPI + if (mpi_node_count > 1) return(1); +#endif + + writer->width = width; + writer->chunk_capacity = chunk_capacity; + writer->expected_rows = count; + writer->position = position; + rot_copy(writer->rotation, rotation); + writer->index = index; + mcevent_copy_string(writer->title, title); + mcevent_copy_string(writer->columns, columns); + mcevent_copy_string(writer->filename, filename); + mcevent_copy_string(writer->component, component); + + if (mc_event_buffer_init(&writer->buffer, chunk_capacity, width)) { + memset(writer, 0, sizeof(*writer)); + return(1); + } + + output = (MC_EVENT_OUTPUT *)malloc(sizeof(*output)); + if (!output) { + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(1); + } + /* mcevent_session_detector can reject the descriptor before the output + backend initializes its state. Keep the rejected-begin cleanup safe. */ + memset(output, 0, sizeof(*output)); + writer->direct_detector = mcevent_session_detector( + title, "List of events", columns ? columns : "", count, width, + filename, component, position, rotation, "None", index); + if (!writer->direct_detector.m + || !mcevent_output_begin(output, writer->direct_detector)) { + if (output->ready) mcevent_output_end(output); + free(output); + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(1); + } + + writer->direct_output = output; + writer->direct = 1; + writer->active = 1; +#ifdef OPENACC + if (!mc_event_writer_openacc_register(writer)) { + mcevent_output_end(output); + free(output); + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(1); + } +#endif + return(0); +} + +int mc_event_writer_begin_stream(MC_EVENT_WRITER *writer, char *title, + char *columns, long width, + long chunk_capacity, char *filename, + char *component, Coords position, + Rotation rotation, int index) +{ + MC_EVENT_OUTPUT *output; + MCDETECTOR detector; + + if (!writer) return(1); + memset(writer, 0, sizeof(*writer)); + if (width <= 0 || chunk_capacity <= 0) return(1); +#ifdef USE_MPI + if (mpi_node_count > 1) return(1); +#endif + + writer->width = width; + writer->chunk_capacity = chunk_capacity; + writer->position = position; + rot_copy(writer->rotation, rotation); + writer->index = index; + mcevent_copy_string(writer->title, title); + mcevent_copy_string(writer->columns, columns); + mcevent_copy_string(writer->filename, filename); + mcevent_copy_string(writer->component, component); + + if (mc_event_buffer_init(&writer->buffer, chunk_capacity, width)) { + memset(writer, 0, sizeof(*writer)); + return(1); + } + + output = (MC_EVENT_OUTPUT *)malloc(sizeof(*output)); + if (!output) { + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(1); + } + memset(output, 0, sizeof(*output)); + /* A positive placeholder lets both existing metadata writers initialize; + the final detector descriptor is rebuilt from the actual row count. */ + detector = mcevent_session_detector(title, "List of events", + columns ? columns : "", 1, width, + filename, component, position, rotation, + "None", index); + if (!detector.m || !mcevent_output_begin_stream(output, detector, + position, rotation, index)) { + free(output); + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(1); + } + writer->stream_output = output; + writer->stream = 1; + writer->active = 1; +#ifdef OPENACC + if (!mc_event_writer_openacc_register(writer)) { + mcevent_output_end(output); + free(output); + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(1); + } +#endif + return(0); +} + +/* Write one already-detached chunk through the selected host-side sink. The + * caller owns the source buffer and may reset/reuse it after this returns. */ +static int mc_event_writer_write_rows(MC_EVENT_WRITER *writer, long count, + double *data) +{ + MC_EVENT_OUTPUT *output; + size_t values; + long long stored_rows; + + if (!writer || count <= 0 || !data || writer->failed) return(1); + if (writer->width <= 0 || (uintmax_t)count > + (uintmax_t)SIZE_MAX / (uintmax_t)writer->width + || (uintmax_t)count * (uintmax_t)writer->width > + (uintmax_t)SIZE_MAX / sizeof(double)) + return(1); + if (writer->rows > LLONG_MAX - (long long)count + || writer->chunks == LLONG_MAX) + return(1); + + values = (size_t)count * (size_t)writer->width; + stored_rows = (long long)count; + if (writer->direct) { + output = (MC_EVENT_OUTPUT *)writer->direct_output; + if (!output || !mcevent_output_chunk(output, count, data)) return(1); + } else if (writer->stream) { + output = (MC_EVENT_OUTPUT *)writer->stream_output; + if (!output || !mcevent_output_chunk(output, count, data)) return(1); + } else { + if (!writer->spool + || fwrite(&stored_rows, sizeof(stored_rows), 1, writer->spool) != 1 + || fwrite(data, sizeof(double), values, writer->spool) != values + || fflush(writer->spool) != 0) + return(1); + } + + writer->rows += (long long)count; + writer->chunks++; + return(0); +} + +int mc_event_writer_flush(MC_EVENT_WRITER *writer) +{ + long count; + + if (!writer || !writer->active || writer->failed + || (!writer->spool && !writer->direct_output && !writer->stream_output)) + return(1); + count = writer->buffer.count; + if (count <= 0) return(0); + if (mc_event_writer_write_rows(writer, count, writer->buffer.data)) { + writer->failed = 1; + return(1); + } + + mc_event_buffer_free(&writer->buffer); + if (mc_event_buffer_init(&writer->buffer, writer->chunk_capacity, + writer->width)) { + writer->failed = 1; + return(1); + } + return(0); +} + +int mc_event_writer_append(MC_EVENT_WRITER *writer, const double *row) +{ + if (!writer || !writer->active || writer->failed || !row) return(0); + if (writer->buffer.count >= writer->buffer.capacity + && mc_event_writer_flush(writer)) + return(0); + if (writer->direct + && writer->rows + (long long)writer->buffer.count >= writer->expected_rows) + return(0); + return(mc_event_buffer_append(&writer->buffer, row)); +} + +#pragma acc routine +int mc_event_writer_append_openacc(MC_EVENT_WRITER *writer, + const double *row) +{ +#ifdef OPENACC + /* The device path cannot touch FILEs or MPI. A host batch service drains + this bounded buffer after the surrounding GPU kernel completes. */ + if (!writer || !writer->active || !row) return(0); + return(mc_event_buffer_append(&writer->buffer, row)); +#else + return(mc_event_writer_append(writer, row)); +#endif +} + +int mc_event_writer_write(MC_EVENT_WRITER *writer, long count, double *data) +{ + long row; + + if (!writer || !writer->active || writer->failed || count < 0 + || (count > 0 && !data)) return(0); + if (count > 0 && (uintmax_t)count > + (uintmax_t)SIZE_MAX / (uintmax_t)writer->width / sizeof(double)) + return(0); + for (row = 0; row < count; row++) + if (!mc_event_writer_append(writer, data + + (size_t)row * (size_t)writer->width)) + return(0); + return(1); +} + +void mc_event_writer_openacc_service(void) +{ +#ifdef OPENACC + MC_EVENT_WRITER_OPENACC_ENTRY *entry; + + for (entry = mc_event_writer_openacc_entries; entry; entry = entry->next) { + MC_EVENT_WRITER *writer = entry->writer; + long count; + + if (!writer || !writer->active) continue; + if (!mc_event_writer_openacc_update_host(writer)) { + writer->failed = 1; + writer->buffer.capacity = 0; + } + + if (writer->buffer.dropped > 0) { + if (writer->dropped > LLONG_MAX - (long long)writer->buffer.dropped) + writer->failed = 1; + else + writer->dropped += (long long)writer->buffer.dropped; + } + count = writer->buffer.count; + if (!writer->failed && count > 0 + && mc_event_writer_write_rows(writer, count, writer->buffer.data)) + writer->failed = 1; + + /* Keep the allocation and its device mapping. Only the counters are + reset, so the next kernel can reuse the same bounded chunk. */ + writer->buffer.count = 0; + writer->buffer.next = 0; + writer->buffer.dropped = 0; + if (writer->failed) writer->buffer.capacity = 0; + mc_event_writer_openacc_update_device(writer); + } +#endif +} + +MCDETECTOR mc_event_writer_end(MC_EVENT_WRITER *writer) +{ + MCDETECTOR detector; + MC_EVENT_SOURCE source; + MC_EVENT_OUTPUT *output; + + if (!writer || !writer->active) + return(mcevent_invalid_detector()); + +#ifdef OPENACC + mc_event_writer_openacc_unregister(writer); +#endif + if (writer->buffer.dropped > 0) { + if (writer->dropped > LLONG_MAX - (long long)writer->buffer.dropped) + writer->failed = 1; + else + writer->dropped += (long long)writer->buffer.dropped; + writer->buffer.dropped = 0; + } + if (writer->dropped > 0) + fprintf(stderr, + "WARNING: mc_event_writer: %lld events dropped in device buffers for '%s'\n", + writer->dropped, writer->filename); + + if (writer->stream) { + output = (MC_EVENT_OUTPUT *)writer->stream_output; + if (mc_event_writer_flush(writer)) writer->failed = 1; + detector = (!writer->failed && writer->rows > 0 && output) ? + mcevent_stream_detector(output, writer->rows) : + mcevent_invalid_detector(); + if (!writer->failed && writer->rows > 0 + && !mcevent_output_finalize_stream(output, writer->rows)) + writer->failed = 1; + if (output && !mcevent_output_end(output)) writer->failed = 1; + free(output); + mc_event_buffer_free(&writer->buffer); + if (writer->failed) detector = mcevent_invalid_detector(); + memset(writer, 0, sizeof(*writer)); + return(detector); + } + + if (writer->direct) { + if (mc_event_writer_flush(writer)) writer->failed = 1; + if (writer->rows != writer->expected_rows) writer->failed = 1; + output = (MC_EVENT_OUTPUT *)writer->direct_output; + detector = writer->direct_detector; + if (output && !mcevent_output_end(output)) writer->failed = 1; + free(output); + mc_event_buffer_free(&writer->buffer); + if (writer->failed) detector = mcevent_invalid_detector(); + memset(writer, 0, sizeof(*writer)); + return(detector); + } + + /* Even after a local spool failure, enter the collective session with the + successfully written prefix. This keeps other MPI ranks from waiting. */ + if (mc_event_writer_flush(writer)) writer->failed = 1; + memset(&source, 0, sizeof(source)); + source.spool = writer->spool; + source.rows = writer->rows; + source.chunk_count = writer->chunks; + source.valid = !writer->failed && writer->spool != NULL; + detector = mcevent_write_session_source( + writer->title, "List of events", writer->columns, writer->width, + &source, writer->filename, writer->component, + writer->position, writer->rotation, "None", writer->index); + if (writer->spool) fclose(writer->spool); + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); + return(detector); +} + +void mc_event_writer_discard(MC_EVENT_WRITER *writer) +{ + MC_EVENT_OUTPUT *output; + + if (!writer) return; +#ifdef OPENACC + mc_event_writer_openacc_unregister(writer); +#endif + if (writer->stream_output) { + output = (MC_EVENT_OUTPUT *)writer->stream_output; + mcevent_output_end(output); + free(output); + } + if (writer->direct_output) { + output = (MC_EVENT_OUTPUT *)writer->direct_output; + mcevent_output_end(output); + free(output); + } + if (writer->spool) fclose(writer->spool); + mc_event_buffer_free(&writer->buffer); + memset(writer, 0, sizeof(*writer)); +} + /******************************************************************************* * mcuse_dir: set data/sim storage directory and create it, * or exit with error if exists diff --git a/common/lib/share/mccode-r.h.in b/common/lib/share/mccode-r.h.in index a9896d0988..5734e4f845 100644 --- a/common/lib/share/mccode-r.h.in +++ b/common/lib/share/mccode-r.h.in @@ -973,11 +973,198 @@ MCDETECTOR mcdetector_out_2D(char *t, char *xl, char *yl, double x1, double x2, double y1, double y2, long m, long n, double *p0, double *p1, double *p2, char *f, char *c, Coords pos, Rotation rot, int index); +/* Host-only wrapper for averaging rank-local 2D matrices. Every MPI rank must + * call this function with the same positive dimensions and detector metadata. + * A rank contributes only when local_valid is nonzero and matrix is non-NULL. + * MPI ranks must provide matching positive dimensions; a mismatch is rejected + * collectively before any detector reduction. + * The wrapper normalizes each valid matrix by the global contributor count, + * delegates ASCII/NeXus output to mcdetector_out_2D, and frees its temporary + * detector arrays before returning. It returns the global contributor count, + * or zero when there are no valid contributors or setup fails. */ +long mcdetector_out_2D_average(char *title, char *xl, char *yl, + double x1, double x2, double y1, double y2, long m, + long n, const double *matrix, int local_valid, + char *filename, char *component, Coords position, + Rotation rotation, int index); MCDETECTOR mcdetector_out_list(char *t, char *xl, char *yl, long m, long n, double *p1, char *f, char *c, Coords posa, Rotation rot,char* options, int index); +/* generic event-list output wrapper: positive count/width dimensions (see mcevent_out_list) */ +MCDETECTOR mcevent_out_list(char *title, char *columns, + long count, long width, + double *data, char *filename, + char *component, Coords position, Rotation rotation, int index); + +/* Monitor_nD-specific event-list output entry point (see mcevent_out_list_nd). + * Same validated, positive-dimension contract as mcevent_out_list, but preserves + * the flavor-specific x-label (xl) and the Monitor_nD options string (options, + * forwarded to the NeXus 'options' attribute) that the generic public API omits. */ +MCDETECTOR mcevent_out_list_nd(char *title, char *xl, char *columns, + long count, long width, + double *data, char *filename, + char *component, Coords position, Rotation rotation, + char *options, int index); + +/* generic fixed-capacity event buffer (see mc_event_buffer_init/free/append). + * Row-major flat storage: data[r*width+c]. `next` is a reservation counter, + * kept separate from `count` because OpenACC threads may reserve slots + * concurrently and reservations may overflow the configured capacity (those + * are counted in `dropped` and never written). `data` is one flat allocation + * so it can be passed directly to DETECTOR_OUT_LIST. No hidden global state. + */ +typedef struct { + double *data; /* flat row-major storage, NULL when uninitialized/freed */ + long width; /* columns per row (>= 0) */ + long capacity; /* maximum number of rows that fit (>= 0) */ + long count; /* number of accepted (stored) rows (<= capacity) */ + long next; /* next row slot to reserve (may exceed capacity) */ + long dropped; /* appends rejected because the buffer was full */ +} MC_EVENT_BUFFER; + +/* A linked list of row-major event chunks. The list is consumed by the + * collective writer but the nodes and data remain owned by the caller. */ +typedef struct mc_event_chunk_struct { + double *data; + long count; + struct mc_event_chunk_struct *next; +} MC_EVENT_CHUNK; + +/* Host-side ownership helpers for components that detach and queue filled + * buffers. append transfers ownership of `data` to the new node. */ +int mc_event_chunk_append(MC_EVENT_CHUNK **head, MC_EVENT_CHUNK **tail, + double *data, long count); +void mc_event_chunk_free(MC_EVENT_CHUNK **head); + +/* Host-side chunk writer. Rows are accumulated in a bounded current buffer; + * flush spools that buffer and end performs one collective serialization. + * The writer never calls MPI or writes event output from append/flush, so a + * caller may use it while preparing chunks at different rates on different + * ranks. The temporary spool keeps memory bounded until end. */ +typedef struct { + MC_EVENT_BUFFER buffer; + FILE *spool; + long width; + long chunk_capacity; + long long rows; + long long chunks; + char title[CHAR_BUF_LENGTH]; + char columns[CHAR_BUF_LENGTH]; + char filename[CHAR_BUF_LENGTH]; + char component[CHAR_BUF_LENGTH]; + Coords position; + Rotation rotation; + int index; + int failed; + int active; + long long dropped; + void *direct_output; + MCDETECTOR direct_detector; + long long expected_rows; + int direct; + void *stream_output; + int stream; +} MC_EVENT_WRITER; + +/* Internal-facing list entry point used by Monitor_nD and the writer. It + * preserves Monitor_nD's x-label/options metadata and writes all chunks in + * local list order through one collective event session. */ +MCDETECTOR mcevent_out_list_nd_chunks(char *title, char *xl, char *columns, + long width, MC_EVENT_CHUNK *chunks, + char *filename, char *component, Coords position, + Rotation rotation, char *options, int index); + +/* Canonical particle-state event row. The row contains position, propagation + * state, polarisation/field state, time, and weight in the flavor-specific + * order named by MC_EVENT_PARTICLE_COLUMNS. */ +#define MC_EVENT_PARTICLE_WIDTH 11 +#if MCCODE_PARTICLE_CODE == 2112 +# define MC_EVENT_PARTICLE_COLUMNS "x y z vx vy vz t sx sy sz p" +#elif MCCODE_PARTICLE_CODE == 22 +# define MC_EVENT_PARTICLE_COLUMNS "x y z kx ky kz t Ex Ey Ez p" +#endif + +#pragma acc routine +void mc_event_particle_row(double *row, _class_particle *particle); + +/* Fixed-capacity event-buffer lifecycle. + * mc_event_buffer_init: owns the allocation (host only); call it on an + * uninitialized or already-freed buffer. It returns 0 on success, + * nonzero on error (null buffer, negative capacity/width, overflow, or + * allocation failure; on error the buffer is left zeroed). + * mc_event_buffer_free: host only; safe after init or for a zero-initialized + * empty buffer, and idempotent (a second call is a no-op, never a + * double-free). + * mc_event_buffer_append: callable from OpenACC device code; performs no + * allocation, I/O or printf; returns nonzero only when the row was + * stored, overflow increments `dropped` and returns 0. + */ +int mc_event_buffer_init(MC_EVENT_BUFFER *buffer, long capacity, long width); +void mc_event_buffer_free(MC_EVENT_BUFFER *buffer); +#pragma acc routine +int mc_event_buffer_append(MC_EVENT_BUFFER *buffer, const double *row); + +/* Host-only save path for a fixed-capacity event buffer. Saves exactly + * `count` accepted rows through mcevent_out_list (never `capacity`), reports + * `dropped` once through a non-fatal WARNING, and leaves the buffer + * unmodified. A NULL buffer is a documented no-op. See mc_event_buffer_save. + */ +MCDETECTOR mc_event_buffer_save(MC_EVENT_BUFFER *buffer, char *title, + char *columns, char *filename, + char *component, Coords position, + Rotation rotation, int index); + +/* Host-side generic chunked event writer. begin clears the writer and, on + * success, allocates one bounded chunk buffer, append adds one row, flush + * spools the current chunk, and end serializes every spooled chunk through the + * common ASCII/NeXus/MPI backend. The begin_direct variant streams serial + * output when the final row count is known; it is not valid for MPI. A + * rejected begin leaves the writer safely inactive, so end is still safe. + * `chunk_capacity` must be positive. The spool is temporary and is removed by + * the platform when closed; it bounds event-data memory independently of the + * total number of rows. */ +int mc_event_writer_begin(MC_EVENT_WRITER *writer, char *title, char *columns, + long width, long chunk_capacity, char *filename, + char *component, Coords position, Rotation rotation, + int index); +/* Begin a serial writer with a known final row count. Chunks are sent directly + * to the normal ASCII/NeXus backend at flush time instead of being spooled. + * The known count is required because both backends publish it in metadata + * before the first payload. MPI callers must use mc_event_writer_begin(). */ +int mc_event_writer_begin_direct(MC_EVENT_WRITER *writer, char *title, + char *columns, long count, long width, + long chunk_capacity, char *filename, + char *component, Coords position, + Rotation rotation, int index); +/* Begin a serial unknown-count stream. The output sink reserves the normal + * ASCII metadata count fields and patches them at end; NeXus uses its + * unlimited event dimension and rewrites the final detector metadata. MPI + * callers should use mc_event_writer_begin() until the host service protocol + * is available. */ +int mc_event_writer_begin_stream(MC_EVENT_WRITER *writer, char *title, + char *columns, long width, + long chunk_capacity, char *filename, + char *component, Coords position, + Rotation rotation, int index); +int mc_event_writer_append(MC_EVENT_WRITER *writer, const double *row); +/* OpenACC device-side append. This only reserves/copies into the writer's + * current buffer; the generated host batch boundary services that buffer. */ +#pragma acc routine +int mc_event_writer_append_openacc(MC_EVENT_WRITER *writer, + const double *row); +int mc_event_writer_write(MC_EVENT_WRITER *writer, long count, double *data); +int mc_event_writer_flush(MC_EVENT_WRITER *writer); +MCDETECTOR mc_event_writer_end(MC_EVENT_WRITER *writer); +/* Host-side OpenACC batch service. It synchronizes registered writer buffers, + * spools or writes one bounded chunk, resets the device buffer, and leaves + * final ASCII/NeXus/MPI serialization to mc_event_writer_end. */ +void mc_event_writer_openacc_service(void); +/* Discard a writer without entering MPI or serializing output. This is for + * host-side error/unwind cleanup when the matching collective end is skipped. */ +void mc_event_writer_discard(MC_EVENT_WRITER *writer); + /* wrappers to output functions, that automatically set NAME and POSITION */ #define DETECTOR_OUT(p0,p1,p2) mcdetector_out_0D(NAME_CURRENT_COMP,p0,p1,p2,NAME_CURRENT_COMP,POS_A_CURRENT_COMP,ROT_A_CURRENT_COMP,INDEX_CURRENT_COMP) #define DETECTOR_OUT_0D(t,p0,p1,p2) mcdetector_out_0D(t,p0,p1,p2,NAME_CURRENT_COMP,POS_A_CURRENT_COMP,ROT_A_CURRENT_COMP,INDEX_CURRENT_COMP) @@ -985,6 +1172,10 @@ MCDETECTOR mcdetector_out_list(char *t, char *xl, char *yl, mcdetector_out_1D(t,xl,yl,xvar,x1,x2,n,p0,p1,p2,f,NAME_CURRENT_COMP,POS_A_CURRENT_COMP,ROT_A_CURRENT_COMP,INDEX_CURRENT_COMP) #define DETECTOR_OUT_2D(t,xl,yl,x1,x2,y1,y2,m,n,p0,p1,p2,f) \ mcdetector_out_2D(t,xl,yl,x1,x2,y1,y2,m,n,p0,p1,p2,f,NAME_CURRENT_COMP,POS_A_CURRENT_COMP,ROT_A_CURRENT_COMP,INDEX_CURRENT_COMP) +#define DETECTOR_OUT_2D_AVERAGE(t,xl,yl,x1,x2,y1,y2,m,n,matrix,local_valid,f) \ + mcdetector_out_2D_average(t,xl,yl,x1,x2,y1,y2,m,n,matrix,local_valid,f,NAME_CURRENT_COMP,POS_A_CURRENT_COMP,ROT_A_CURRENT_COMP,INDEX_CURRENT_COMP) +#define DETECTOR_OUT_LIST(title, columns, count, width, data, filename) \ + mcevent_out_list(title, columns, count, width, data, filename, NAME_CURRENT_COMP, POS_A_CURRENT_COMP, ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP) #ifdef USE_NEXUS #include "napi.h" diff --git a/docs/manuals/mcstas/monitors/Event_monitor_simple.tex b/docs/manuals/mcstas/monitors/Event_monitor_simple.tex index df66be50e5..664fa9e7fa 100644 --- a/docs/manuals/mcstas/monitors/Event_monitor_simple.tex +++ b/docs/manuals/mcstas/monitors/Event_monitor_simple.tex @@ -1,5 +1,6 @@ \section{The \texttt{Event\_monitor\_simple} McStas Component} -Low-key event-monitor for debugging purposes. +Low-key event-monitor for debugging purposes. It writes a standard McCode +event list through the common runtime output path. \subsection*{Identification} \begin{itemize} @@ -9,7 +10,13 @@ \subsection*{Identification} \end{itemize} \subsection*{Description} -Simple, low-key event-monitor for debugging purposes. No propagation, no MPI support. Simply prints the event list to a log file in the SAVE section. The filename is "comp-instance".log +Simple, low-key event-monitor for debugging purposes. It does not propagate +neutrons and writes the configured number of rows through the standard event +list output path. The UID-indexed collector remains serial-only. The output +filename is the component instance name with the normal McCode extension. +Columns are \texttt{id x y z vx vy vz t sx sy sz p}. The common runtime's +\texttt{mc\_event\_particle\_row} helper supplies the eleven particle-state +columns after the component writes the event id. \subsection*{Input parameters} Parameters in \textbf{boldface} are required; the others are optional. @@ -19,7 +26,7 @@ \subsection*{Input parameters} \textbf{Name} & \textbf{Unit} & \textbf{Description} & \textbf{Default} \\ \midrule \endhead -nevents & 1 & Number of events to store and print & 1e6 \\ +nevents & 1 & Number of events to store and save & 1e6 \\ \bottomrule \end{longtable} @@ -27,4 +34,4 @@ \subsection*{Links} \begin{itemize} \item Component source code found in file \texttt{Event\_monitor\_simple.comp}. \end{itemize} -\IfFileExists{monitors/Event_monitor_simple_static.tex}{\input{monitors/Event_monitor_simple_static.tex}}{} \ No newline at end of file +\IfFileExists{monitors/Event_monitor_simple_static.tex}{\input{monitors/Event_monitor_simple_static.tex}}{} diff --git a/docs/manuals/mcstas/monitors/Res_monitor.tex b/docs/manuals/mcstas/monitors/Res_monitor.tex index 6a8f3b5d3b..046bb3c60a 100644 --- a/docs/manuals/mcstas/monitors/Res_monitor.tex +++ b/docs/manuals/mcstas/monitors/Res_monitor.tex @@ -11,6 +11,8 @@ \subsection*{Identification} \subsection*{Description} A single detector/monitor, used together with the Res\_sample component to compute instrument resolution functions. Outputs a list of neutron scattering events in the sample along with their intensities in the detector. The output file may be analyzed with the mcresplot front-end. +The event rows have the fixed column order \texttt{ki\_x ki\_y ki\_z kf\_x kf\_y kf\_z x y z p\_i p\_f}. They are collected in a fixed-capacity generic event buffer and written through the standard event-list output path. + Example: Res\_monitor(filename="Output.res", res\_sample\_comp="RSample", xmin=-0.1, xmax=0.1, ymin=-0.1, ymax=0.1) Setting the monitor geometry. The optional parameter 'options' may be set as a string with the following keywords. Default is rectangular ('square'): @@ -55,4 +57,4 @@ \subsection*{Links} \begin{itemize} \item Component source code found in file \texttt{Res\_monitor.comp}. \end{itemize} -\IfFileExists{monitors/Res_monitor_static.tex}{\input{monitors/Res_monitor_static.tex}}{} \ No newline at end of file +\IfFileExists{monitors/Res_monitor_static.tex}{\input{monitors/Res_monitor_static.tex}}{} diff --git a/docs/manuals/mcstas/monitors/TOFRes_monitor.tex b/docs/manuals/mcstas/monitors/TOFRes_monitor.tex index a70a50cb7e..caef5ee3d2 100644 --- a/docs/manuals/mcstas/monitors/TOFRes_monitor.tex +++ b/docs/manuals/mcstas/monitors/TOFRes_monitor.tex @@ -11,6 +11,8 @@ \subsection*{Identification} \subsection*{Description} A single detector/monitor, used together with the TOFRes\_sample component to compute instrument resolution functions. Outputs a list of neutron scattering events in the sample along with their intensities in the detector. The output file may be analyzed with the mcresplot front-end. +The event rows have the fixed column order \texttt{ki\_x ki\_y ki\_z kf\_x kf\_y kf\_z x y z p\_i p\_f}. They are collected in a fixed-capacity generic event buffer and written through the standard event-list output path. + Example: TOFRes\_monitor(filename="Output.res", res\_sample\_comp="RSample", xmin=-0.1, xmax=0.1, ymin=-0.1, ymax=0.1) Setting the monitor geometry. The optional parameter 'options' may be set as a string with the following keywords. Default is rectangular ('square'): @@ -55,4 +57,4 @@ \subsection*{Links} \begin{itemize} \item Component source code found in file \texttt{TOFRes\_monitor.comp}. \end{itemize} -\IfFileExists{monitors/TOFRes_monitor_static.tex}{\input{monitors/TOFRes_monitor_static.tex}}{} \ No newline at end of file +\IfFileExists{monitors/TOFRes_monitor_static.tex}{\input{monitors/TOFRes_monitor_static.tex}}{} diff --git a/docs/manuals/mcxtrace/monitors/Event_monitor_simple.tex b/docs/manuals/mcxtrace/monitors/Event_monitor_simple.tex index 9bcdb52704..8fab5eae7c 100644 --- a/docs/manuals/mcxtrace/monitors/Event_monitor_simple.tex +++ b/docs/manuals/mcxtrace/monitors/Event_monitor_simple.tex @@ -1,5 +1,6 @@ \section{The \texttt{Event\_monitor\_simple} McXtrace Component} -Low-key event-monitor for debugging purposes. +Low-key event-monitor for debugging purposes. It writes a standard McCode +event list through the common runtime output path. \subsection*{Identification} \begin{itemize} @@ -9,7 +10,13 @@ \subsection*{Identification} \end{itemize} \subsection*{Description} -Simple, low-key event-monitor for debugging purposes. No propagation, no MPI support. Simply prints the event list to a log file in the SAVE section. The filename is "comp-instance".log +Simple, low-key event-monitor for debugging purposes. It does not propagate +photons and writes the configured number of rows through the standard event +list output path. The UID-indexed collector remains serial-only. The output +filename is the component instance name with the normal McCode extension. +Columns are \texttt{id x y z kx ky kz t Ex Ey Ez p}. The common runtime's +\texttt{mc\_event\_particle\_row} helper supplies the eleven particle-state +columns after the component writes the event id. \subsection*{Input parameters} Parameters in \textbf{boldface} are required; the others are optional. @@ -19,7 +26,7 @@ \subsection*{Input parameters} \textbf{Name} & \textbf{Unit} & \textbf{Description} & \textbf{Default} \\ \midrule \endhead -nevents & 1 & Number of events to store and print & 1e6 \\ +nevents & 1 & Number of events to store and save & 1e6 \\ \bottomrule \end{longtable} @@ -27,4 +34,4 @@ \subsection*{Links} \begin{itemize} \item Component source code found in file \texttt{Event\_monitor\_simple.comp}. \end{itemize} -\IfFileExists{monitors/Event_monitor_simple_static.tex}{\input{monitors/Event_monitor_simple_static.tex}}{} \ No newline at end of file +\IfFileExists{monitors/Event_monitor_simple_static.tex}{\input{monitors/Event_monitor_simple_static.tex}}{} diff --git a/mccode/src/cogen.c.in b/mccode/src/cogen.c.in index a4384f7f0c..0d1077dabd 100644 --- a/mccode/src/cogen.c.in +++ b/mccode/src/cogen.c.in @@ -227,6 +227,40 @@ codeblock_out_brace(struct code_block *code) code_reset_source(); } +/* The OpenACC writer service needs the mapped component object on the host; + avoid synchronizing every component for instruments that do not use it. */ +static int +codeblock_contains_text(struct code_block *code, const char *text) +{ + List_handle liter; + char *line; + + if (!code || !text) return(0); + liter = list_iterate(code->lines); + while((line = list_next(liter)) != NULL) { + if (strstr(line, text)) { + list_iterate_end(liter); + return(1); + } + } + list_iterate_end(liter); + return(0); +} + +static int +component_uses_event_writer(struct comp_inst *comp) +{ + struct comp_def *def; + + if (!comp || !comp->def) return(0); + def = comp->def; + return(codeblock_contains_text(def->decl_code, "MC_EVENT_WRITER") + || codeblock_contains_text(def->init_code, "mc_event_writer_") + || codeblock_contains_text(def->trace_code, "mc_event_writer_") + || codeblock_contains_text(def->save_code, "mc_event_writer_") + || codeblock_contains_text(def->finally_code, "mc_event_writer_")); +} + /** Output the lines of a literal block, encased in double quotes, and with any contained double quotes escaped. * * This form allows for automatic line-breaking string concatenation @@ -1809,6 +1843,7 @@ int cogen_trace_functions(struct instr_def *instr) int cogen_raytrace(struct instr_def *instr) { List_handle liter; + List_handle sync_liter; struct comp_inst *comp = NULL; int warnings = 0; int i = 0; @@ -2103,7 +2138,7 @@ int cogen_raytrace(struct instr_def *instr) coutf(" for (unsigned long pidx=0 ; pidx < gpu_innerloop ; pidx++) {"); coutf(" _class_particle particleN = mcgenstate(); // initial particle"); coutf(" _class_particle* _particle = &particleN;"); - coutf(" particleN._uid = pidx;"); + coutf(" particleN._uid = cloop * gpu_innerloop + pidx;"); coutf(" #ifdef USE_MPI"); coutf(" particleN._uid += mpi_node_rank * ncount; "); coutf(" #endif"); @@ -2112,6 +2147,21 @@ int cogen_raytrace(struct instr_def *instr) coutf(""); coutf(" raytrace(_particle);"); coutf(" } /* inner for */"); + coutf(" #ifdef OPENACC"); + sync_liter = list_iterate(instr->complist); + while((comp = list_next(sync_liter)) != NULL) { + if (component_uses_event_writer(comp)) + coutf(" #pragma acc update host(_%s_var)", comp->name); + } + list_iterate_end(sync_liter); + coutf(" mc_event_writer_openacc_service();"); + sync_liter = list_iterate(instr->complist); + while((comp = list_next(sync_liter)) != NULL) { + if (component_uses_event_writer(comp)) + coutf(" #pragma acc update device(_%s_var)", comp->name); + } + list_iterate_end(sync_liter); + coutf(" #endif"); coutf(" seed = seed+gpu_innerloop;"); coutf(" } /* CPU for */"); coutf(" /* if on GPU, printf has been globally nullified, re-enable here */"); @@ -2147,6 +2197,7 @@ int cogen_raytrace(struct instr_def *instr) int cogen_rt_funnel(struct instr_def *instr) { List_handle liter; + List_handle sync_liter; struct comp_inst *comp = NULL; int warnings = 0; int i = 0; @@ -2242,7 +2293,7 @@ int cogen_rt_funnel(struct instr_def *instr) coutf(" // generate particle state, set loop index and seed"); coutf(" particles[pidx] = mcgenstate();"); coutf(" _class_particle* _particle = particles + pidx;"); - coutf(" _particle->_uid = pidx;"); + coutf(" _particle->_uid = cloop * gpu_innerloop + pidx;"); coutf(" #ifdef USE_MPI"); coutf(" _particle->_uid += mpi_node_rank * ncount; "); coutf(" #endif"); @@ -2354,6 +2405,21 @@ int cogen_rt_funnel(struct instr_def *instr) } /* end while comp list (case) */ coutf(""); coutf(" }"); + coutf(" #ifdef OPENACC"); + sync_liter = list_iterate(instr->complist); + while((comp = list_next(sync_liter)) != NULL) { + if (component_uses_event_writer(comp)) + coutf(" #pragma acc update host(_%s_var)", comp->name); + } + list_iterate_end(sync_liter); + coutf(" mc_event_writer_openacc_service();"); + sync_liter = list_iterate(instr->complist); + while((comp = list_next(sync_liter)) != NULL) { + if (component_uses_event_writer(comp)) + coutf(" #pragma acc update device(_%s_var)", comp->name); + } + list_iterate_end(sync_liter); + coutf(" #endif"); cout( ""); list_iterate_end(liter); diff --git a/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_list/README.md b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_list/README.md new file mode 100644 index 0000000000..d145942b51 --- /dev/null +++ b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_list/README.md @@ -0,0 +1,59 @@ +# The `Test_Monitor_nD_list` Instrument + +*McStas: Exercise Monitor_nD's final list save across the list options matrix* + +## Identification + +- **Site:** Tests_monitors +- **Author:** McCode event-output work (Step 6) +- **Origin:** DTU +- **Date:** September 2026 + +## Description + +```text +Exercise Monitor_nD's final list save (migrated to the generic +mcevent_out_list_nd event API) across the list options matrix: + - a two-column custom list (x y list) + - a list containing a pixel ID column (x y pixel) + - a fixed-capacity list with overflow (x y list=5) + - list all (x y list all) + - auto limits combined with list output (auto x y list) +``` + +Each `Monitor_nD` instance is given a distinct `filename`, so the list +output is written as `_list.p....` (no extension) alongside the +normal `.x_y` histogram. All list files carry the header +`xlabel: List of neutron events` and the per-instance column names. + +Expected list outputs (with `NCount=1000`): + +| Instance | options | type | data rows | +|----------|---------|------|-----------| +| `ND_xy` | `x y list` | `list(3, 1000)` | 1000 | +| `ND_pix` | `x bins=10 y bins=10 pixel list` | `list(4, 1000)` | 1000 (extra `id` pixel column) | +| `ND_ovf` | `x y list=5` | `list(3, 5)` | 5 (buffer overflow) | +| `ND_all` | `x y list all` | `list(3, 1000)` | 1000 | +| `ND_auto` | `auto x bins=10 y bins=10 list` | `list(3, 1000)` | 1000 | + +The histogram (`.x_y`) always accumulates all 1000 events regardless of +the list buffer capacity (verified for `ND_ovf`), i.e. the list capacity does +not affect the histogram. + +## Examples + +- **Test: NCount=1000 Detector: FlexRef_I=1000** + +## Input parameters + +Parameters in **boldface** are required; the others are optional. + +| Name | Unit | Description | Default | +|------|------|-------------|---------| +| **NCount** | 1 | Statistic to run with | 1000 | + +## Links + +- [Source code](Test_Monitor_nD_list.instr) for `Test_Monitor_nD_list.instr`. + +--- diff --git a/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_list/Test_Monitor_nD_list.instr b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_list/Test_Monitor_nD_list.instr new file mode 100644 index 0000000000..adc5b135ef --- /dev/null +++ b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_list/Test_Monitor_nD_list.instr @@ -0,0 +1,86 @@ +/******************************************************************************* +* Instrument: Test_Monitor_nD_list +* +* %I +* Written by: McCode event-output work (Step 6) +* Date: September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_monitors +* +* %D +* Exercise Monitor_nD's final list save (migrated to the generic +* mcevent_out_list_nd event API) across the list options matrix: +* - a two-column custom list (x y list) +* - a list containing a pixel ID column (x y pixel) +* - a fixed-capacity list with overflow (x y list=5) +* - list all (x y list all) +* - auto limits combined with list output (auto x y list) +* +* %Example: NCount=1000 Detector: FlexRef_I=1000 +* +* %P +* NCount: [1] Statistic to run with +* +* %L +* +* %E +*******************************************************************************/ +DEFINE INSTRUMENT Test_Monitor_nD_list(NCount=1000) + +DECLARE +%{ +%} + +USERVARS +%{ + double One; +%} + +INITIALIZE +%{ + mcset_ncount((unsigned long long)NCount); +%} + +TRACE +COMPONENT origin = Progress_bar() +AT (0, 0, 0) RELATIVE ABSOLUTE + +COMPONENT src = Source_simple( + lambda0=2, + dlambda=0.02, dist=1, focus_xw=0.1, focus_yh=0.1) +AT (0, 0, 0) RELATIVE PREVIOUS +EXTEND %{ + One=1; +%} + +/* (1) two-column custom list: columns p x y, full capacity (all events) */ +COMPONENT ND_xy = Monitor_nD(xwidth=0.4, yheight=0.4, restore_neutron=1, + filename="ND_xy", options="x y list") +AT (0, 0, 0) RELATIVE PREVIOUS + +/* (2) list containing a pixel ID column: x/y binned, pixel computed */ +COMPONENT ND_pix = Monitor_nD(xwidth=0.4, yheight=0.4, restore_neutron=1, + filename="ND_pix", options="x bins=10 y bins=10 pixel list") +AT (0, 0, 0) RELATIVE PREVIOUS + +/* (3) fixed-capacity list with overflow: buffer holds only 5 rows */ +COMPONENT ND_ovf = Monitor_nD(xwidth=0.4, yheight=0.4, restore_neutron=1, + filename="ND_ovf", options="x y list=5") +AT (0, 0, 0) RELATIVE PREVIOUS + +/* (4) list all: every event */ +COMPONENT ND_all = Monitor_nD(xwidth=0.4, yheight=0.4, restore_neutron=1, + filename="ND_all", options="x y list all") +AT (0, 0, 0) RELATIVE PREVIOUS + +/* (5) auto limits combined with list output */ +COMPONENT ND_auto = Monitor_nD(xwidth=0.4, yheight=0.4, restore_neutron=1, + filename="ND_auto", options="auto x bins=10 y bins=10 list") +AT (0, 0, 0) RELATIVE PREVIOUS + +/* reference 2D monitor to confirm the same neutrons reach the monitors */ +COMPONENT FlexRef = Flex_monitor_2D(nU1=3, nU2=3, Umin1=-1, Umax1=1, + Umin2=-1, Umax2=1, ustring1="x", ustring2="y", signal="One") +AT (0, 0, 0) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_noacc/README.md b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_noacc/README.md new file mode 100644 index 0000000000..d686f1c6b0 --- /dev/null +++ b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_noacc/README.md @@ -0,0 +1,22 @@ +# The `Test_Monitor_nD_noacc` Instrument + +*McStas: compare ordinary event-list storage in `Monitor_nD` and `Monitor_nD_noacc`* + +## Description + +The instrument sends the same source rays through matching OpenACC-capable +and non-OpenACC monitor implementations. Each pair exercises a normal list +and a fixed-capacity overflowing list. + +With `NCount=200`, `ND_acc` and `ND_noacc` each contain 200 rows, while +`ND_acc_ovf` and `ND_noacc_ovf` each contain the first 5 accepted rows. The +numeric rows are identical for each pair on the CPU path, and the associated +histograms still include all 200 events. + +## Examples + +- **Test: NCount=200 Detector: FlexRef_I=200** + +## Links + +- [Source code](Test_Monitor_nD_noacc.instr) for `Test_Monitor_nD_noacc.instr`. diff --git a/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_noacc/Test_Monitor_nD_noacc.instr b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_noacc/Test_Monitor_nD_noacc.instr new file mode 100644 index 0000000000..52acb00a0a --- /dev/null +++ b/mcstas-comps/examples/Tests_monitors/Test_Monitor_nD_noacc/Test_Monitor_nD_noacc.instr @@ -0,0 +1,71 @@ +/******************************************************************************* +* Instrument: Test_Monitor_nD_noacc +* +* %I +* Written by: McCode event-output work (Step 7) +* Date: September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_monitors +* +* %D +* Compare ordinary fixed-capacity list output from Monitor_nD and its +* non-OpenACC implementation. Both monitors see the same source and use +* identical list options, including a small overflowing list. +* +* %Example: NCount=200 Detector: FlexRef_I=200 +* +* %P +* NCount: [1] Statistic to run with +* +* %L +* +* %E +*******************************************************************************/ +DEFINE INSTRUMENT Test_Monitor_nD_noacc(NCount=200) + +DECLARE +%{ +%} + +USERVARS +%{ + double One; +%} + +INITIALIZE +%{ + mcset_ncount((unsigned long long)NCount); +%} + +TRACE +COMPONENT origin = Progress_bar() +AT (0, 0, 0) RELATIVE ABSOLUTE + +COMPONENT src = Source_simple( + lambda0=2, dlambda=0.02, dist=1, focus_xw=0.1, focus_yh=0.1) +AT (0, 0, 0) RELATIVE PREVIOUS +EXTEND %{ + One=1; +%} + +COMPONENT ND_acc = Monitor_nD(xwidth=0.4, yheight=0.4, restore_neutron=1, + filename="ND_acc", options="x y list") +AT (0, 0, 0) RELATIVE PREVIOUS + +COMPONENT ND_noacc = Monitor_nD_noacc(xwidth=0.4, yheight=0.4, + restore_neutron=1, filename="ND_noacc", options="x y list") +AT (0, 0, 0) RELATIVE PREVIOUS + +COMPONENT ND_acc_ovf = Monitor_nD(xwidth=0.4, yheight=0.4, + restore_neutron=1, filename="ND_acc_ovf", options="x y list=5") +AT (0, 0, 0) RELATIVE PREVIOUS + +COMPONENT ND_noacc_ovf = Monitor_nD_noacc(xwidth=0.4, yheight=0.4, + restore_neutron=1, filename="ND_noacc_ovf", options="x y list=5") +AT (0, 0, 0) RELATIVE PREVIOUS + +COMPONENT FlexRef = Flex_monitor_2D(nU1=3, nU2=3, Umin1=-1, Umax1=1, + Umin2=-1, Umax2=1, ustring1="x", ustring2="y", signal="One") +AT (0, 0, 0) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventBuffer/EventBuffer.comp b/mcstas-comps/examples/Tests_other/Test_EventBuffer/EventBuffer.comp new file mode 100644 index 0000000000..64f1c593ab --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventBuffer/EventBuffer.comp @@ -0,0 +1,101 @@ +/******************************************************************************* +* +* McStas, neutron ray-tracing package +* Copyright 1997-2002, All rights reserved +* Risoe National Laboratory, Roskilde, Denmark +* Institut Laue Langevin, Grenoble, France +* +* %I +* Written by: McCode development +* Date: 18 September 2026 +* Origin: DTU +* + * Deterministic MC_EVENT_BUFFER fixture for the fixed-capacity event buffer + * added to the common runtime (Step 3 of the generic event functions plan) + * and the mc_event_buffer_save helper (Step 4). In SAVE it initializes a + * buffer, appends deterministic rows row[r][c] = r*10 + c (independent of + * ncount and seed), saves only the accepted rows, prints deterministic + * diagnostics (accepted/count/next/dropped) to stderr, and frees the buffer + * exactly once (plus an optional safe second free). Neutron physics is a + * pass-through no-op, so the behavior is fully deterministic in a single + * process. + * + * %P + * cap: [1] maximum number of rows the buffer can hold (0 -> zero-capacity) + * ncols: [1] number of columns per row (0 -> degenerate no-data buffer) + * nrows: [1] number of rows appended (drives capacity overflow) + * filename: [string] output file name (extension added by the runtime) + * columns: [string] whitespace-separated column names + * save_helper: [1] if set, save with mc_event_buffer_save (Step 4) instead + * of the explicit DETECTOR_OUT_LIST call; both paths must + * produce identical output + * free_twice: [1] if set, call mc_event_buffer_free a second time (safe no-op) + * nowritefile: [1] if set, skip writing + * +* %E + ******************************************************************************/ + +DEFINE COMPONENT EventBuffer + +SETTING PARAMETERS (int cap=4, int ncols=3, int nrows=6, + string filename="EventBuf", string columns="a b c", + int save_helper=0, int free_twice=0, int nowritefile=0) + +TRACE +%{ + /* Fixture: the neutron state is left untouched (pass-through). + The event buffer is filled deterministically in SAVE. */ +%} + +SAVE +%{ + MC_EVENT_BUFFER buf = {0}; + double *row = (double*)malloc((ncols > 0 ? ncols : 1) * sizeof(double)); + long r, c; + long accepted = 0; + + /* 1) initialize (null/negative arguments and zero capacity are safe) */ + mc_event_buffer_init(&buf, cap, ncols); + + /* 2) append deterministic rows: row r, column c = r*10 + c */ + for (r = 0; r < nrows; r++) { + for (c = 0; c < ncols; c++) + row[c] = r * 10.0 + (double)c; + if (mc_event_buffer_append(&buf, row)) + accepted++; + } + /* a null row must be rejected without touching any counter */ + if (mc_event_buffer_append(&buf, NULL)) + fprintf(stderr, "EventBuffer[%s]: TEST-FAIL null row accepted\n", NAME_CURRENT_COMP); + + /* 3) save only the valid (accepted) rows, never the full capacity; + save_helper=1 uses the Step 4 mc_event_buffer_save wrapper, which must + produce output identical to the explicit DETECTOR_OUT_LIST path */ + if (!nowritefile) { + if (save_helper) + mc_event_buffer_save(&buf, "Event buffer rows", columns, filename, + NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); + else + DETECTOR_OUT_LIST("Event buffer rows", columns, + buf.count, buf.width, buf.data, filename); + } + + /* 4) deterministic diagnostics: the save helper does not modify the + buffer, so the counters still reflect this SAVE (the helper prints its + own one-time dropped warning) */ + fprintf(stderr, + "EventBuffer[%s]: capacity=%d width=%d rows=%d " + "accepted=%ld count=%ld next=%ld dropped=%ld\n", + NAME_CURRENT_COMP, cap, ncols, nrows, + accepted, buf.count, buf.next, buf.dropped); + + /* 5) exactly-once free, plus an optional safe second free */ + mc_event_buffer_free(&buf); + if (free_twice) + mc_event_buffer_free(&buf); + + free(row); +%} + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventBuffer/README.md b/mcstas-comps/examples/Tests_other/Test_EventBuffer/README.md new file mode 100644 index 0000000000..ddfe9d11a4 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventBuffer/README.md @@ -0,0 +1,123 @@ +# The `Test_EventBuffer` Fixture + +*McStas: deterministic fixture for the fixed-capacity `MC_EVENT_BUFFER` API* + +## Identification + +- **Site:** Tests_other +- **Origin:** DTU +- **Date:** 18 September 2026 + +## Description + +A small, self-contained fixture that exercises the fixed-capacity event +buffer `MC_EVENT_BUFFER`, its lifecycle functions +`mc_event_buffer_init` / `mc_event_buffer_free` / `mc_event_buffer_append` +(Step 3) and the `mc_event_buffer_save` helper (Step 4) added to the common +runtime (`common/lib/share/mccode-r.{h.in,c}`). It is isolated from the +Step 1/2 `Test_EventOutList` fixture and does not depend on it. + +The `EventBuffer` fixture component initializes a buffer in its `SAVE` +section, appends `rows` deterministic rows (`row[r][c] = r*10 + c`, +independent of `ncount` and seed), saves only the accepted rows (through the +explicit `DETECTOR_OUT_LIST` call or the `mc_event_buffer_save` helper, +selected by `save_helper`), prints deterministic diagnostics +(`accepted/count/next/dropped`) to stderr, and frees the buffer exactly once +(plus an optional safe second free). Neutron physics is a pass-through +no-op, so the behavior is fully deterministic in a single process. + +One instrument run covers the case matrix: + +| Instance | Params | Expected | +|----------------|------------------------------------|-----------------------------------------------------------------------| +| `evFill` | `cap=4, ncols=3, nrows=6` | `BufFill.dat`: 4 rows in insertion order; 2 dropped; no capacity rows | +| `evZeroCap` | `cap=0, ncols=3, nrows=3` | no file (count=0 no-op); 3 dropped; no out-of-bounds write | +| `evFreeTwice` | `cap=4, ncols=3, nrows=2, free_twice=1` | `BufFreeTwice.dat`: 2 rows; second free is a safe no-op | +| `evZeroWid` | `cap=3, ncols=0, nrows=2` | no file (width=0 rejected by `mcevent_out_list`); no storage allocated | +| `evSave` | `cap=4, ncols=3, nrows=6, save_helper=1` | `BufSave.dat` byte-identical to `BufFill.dat` plus a one-time `WARNING: mc_event_buffer_save: 2 events dropped ...` | +| `evSaveEmpty` | `cap=2, ncols=3, nrows=0, save_helper=1` | documented zero-event no-op through the helper: no file, no warning | + +Documented buffer semantics exercised here: + +- Fixed capacity; appending beyond capacity increments `dropped`, returns 0 + and never writes out of bounds. +- `count` equals the number of accepted rows and is what gets saved (never + `capacity`), so no uninitialized capacity rows can appear in the output. +- CPU appends store rows in insertion order; `next` (reservations) is kept + separate from `count` and may exceed `capacity`. +- Zero capacity is a valid safe buffer: appends are rejected and counted in + `dropped`. +- `width==0` with `capacity>0` is an allowed degenerate case: init succeeds, + no storage is allocated, appends are accepted while copying nothing, and + saving is a `mcevent_out_list` no-op. +- `mc_event_buffer_free` is exactly-once and idempotent after initialization (a + second call is a no-op, never a double-free); a zero-initialized empty buffer + is also safe to free. +- A null row pointer is rejected without touching any counter. + +Overflow is reported twice by design: the fixture's own stderr diagnostics +(always) and, when saved through `mc_event_buffer_save`, a one-time non-fatal +`WARNING` from the helper (Step 4). + +## How to run + +The component is not part of the installed library; pass its directory with +`-I`: + +```sh +mcrun -n 1000 -I $(pwd) Test_EventBuffer.instr +``` + +Expected deterministic data for `BufFill.dat` (rows = events, in insertion +order, exactly `count=4` rows despite `capacity=4 < rows=6`): + +``` +0 1 2 +10 11 12 +20 21 22 +30 31 32 +``` + +Expected stderr diagnostics (order follows the component instances; the +save-helper warning precedes the instance diagnostics for `evSave`): + +``` +EventBuffer[evFill]: capacity=4 width=3 rows=6 accepted=4 count=4 next=6 dropped=2 +EventBuffer[evZeroCap]: capacity=0 width=3 rows=3 accepted=0 count=0 next=3 dropped=3 +EventBuffer[evFreeTwice]: capacity=4 width=3 rows=2 accepted=2 count=2 next=2 dropped=0 +WARNING: mcevent_out_list: width=0 with count=2; no list written +EventBuffer[evZeroWid]: capacity=3 width=0 rows=2 accepted=2 count=2 next=2 dropped=0 +WARNING: mc_event_buffer_save: 2 events dropped (capacity exceeded), saving 4 rows to 'BufSave' +EventBuffer[evSave]: capacity=4 width=3 rows=6 accepted=4 count=4 next=6 dropped=2 +EventBuffer[evSaveEmpty]: capacity=2 width=3 rows=0 accepted=0 count=0 next=0 dropped=0 +``` + +## Save helper (Step 4) + +`mc_event_buffer_save` saves exactly `count` accepted rows (never `capacity`) +through `mcevent_out_list`, reports `dropped` once through a non-fatal +`WARNING` when it is nonzero, and leaves the buffer unmodified. A NULL buffer +is a documented no-op. The `evSave` instance (same parameters as `evFill`, +`save_helper=1`) verifies that the helper path produces output identical to +the explicit `DETECTOR_OUT_LIST` path plus the one-time dropped warning, and +`evSaveEmpty` verifies the documented zero-event no-op (no file, no warning). +A McXtrace companion +(`mcxtrace-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer_mcxtrace.instr`, +flavor-neutral `EventBuffer.comp` plus `Source_pt`) exercises the same two +paths on the McXtrace runtime. + +## OpenACC + +`mc_event_buffer_append` is annotated for OpenACC device code (`acc routine` +prototype, atomic reservation of `next`, atomic `dropped`/`count` updates) +and performs no allocation, I/O or `printf`. Device execution requires the +buffer struct and its `data` array to be in device memory; the caller owns +that transfer, and this CPU fixture does not exercise the device path. + +## Links + +- [Source code](Test_EventBuffer.instr) for `Test_EventBuffer.instr`. +- [Component](EventBuffer.comp) for `EventBuffer.comp`. +- [McXtrace companion](../../../../mcxtrace-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer_mcxtrace.instr). + +--- diff --git a/mcstas-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer.instr b/mcstas-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer.instr new file mode 100644 index 0000000000..4d22e99d46 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer.instr @@ -0,0 +1,67 @@ +/******************************************************************************* +* McStas instrument definition URL=http://www.mcstas.org +* +* Instrument: Test the fixed-capacity MC_EVENT_BUFFER API +* +* %Identification +* Written by: McCode development +* Date: 18 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description + * Fixture instrument that drives the EventBuffer fixture component through the + * MC_EVENT_BUFFER case matrix in a single run: + * - BufFill : cap=4, nrows=6 -> 4 accepted rows in insertion order, + * 2 dropped, only the 4 valid rows written (no capacity rows) + * - BufZeroCap : cap=0, nrows=3 -> zero-capacity append safety, all + * rows dropped, no file (count=0 no-op) + * - BufFreeTwice: free_twice=1 -> exactly-once free plus a safe second free + * - BufZeroWid : ncols=0, cap=3 -> documented degenerate case: no + * storage allocated, appends copy nothing, no file + * - BufSave : save_helper=1, same params as BufFill -> output must be + * identical to BufFill.dat plus a one-time dropped warning + * - BufSaveEmpty: save_helper=1, nrows=0 -> documented zero-event no-op + * through the save helper: no file, no warning +* Rows are deterministic: row r, column c = r*10 + c. Diagnostics +* (accepted/count/next/dropped per instance) are printed to stderr. +* +* %Example: mcstas_test +* +* %Parameters +* (none) +* +* %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventBuffer() + +/* The TRACE section: a trivial source and one fixture instance per test case. */ +/* Neutron physics is irrelevant; the buffers are filled in SAVE. */ +TRACE + +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT evFill = EventBuffer(cap=4, ncols=3, nrows=6, filename="BufFill") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evZeroCap = EventBuffer(cap=0, ncols=3, nrows=3, filename="BufZeroCap") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evFreeTwice = EventBuffer(cap=4, ncols=3, nrows=2, filename="BufFreeTwice", + free_twice=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evZeroWid = EventBuffer(cap=3, ncols=0, nrows=2, filename="BufZeroWid") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evSave = EventBuffer(cap=4, ncols=3, nrows=6, filename="BufSave", + save_helper=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evSaveEmpty = EventBuffer(cap=2, ncols=3, nrows=0, filename="BufSaveEmpty", + save_helper=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +/* The END token marks the instrument definition end */ +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/EventBufferOpenACC.comp b/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/EventBufferOpenACC.comp new file mode 100644 index 0000000000..e1bc4ebf7e --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/EventBufferOpenACC.comp @@ -0,0 +1,54 @@ +/******************************************************************************* + * + * %I + * Written by: McCode development + * Date: 22 September 2026 + * Origin: DTU + * + * %D + * OpenACC fixture for MC_EVENT_BUFFER. Rows are appended from TRACE and saved + * after the device kernel completes. Device row order is intentionally not + * guaranteed; the fixed capacity and accepted/dropped counters are checked. + * + * %P + * capacity: [1] maximum number of rows in the device event buffer + * + * %E + *******************************************************************************/ +DEFINE COMPONENT EventBufferOpenACC + +SETTING PARAMETERS (int capacity=8) + +DECLARE +%{ + MC_EVENT_BUFFER events; +%} + +INITIALIZE +%{ + mc_event_buffer_init(&events, capacity, 3); +%} + +TRACE +%{ + double row[3]; + row[0] = x; + row[1] = y; + row[2] = p; + mc_event_buffer_append(&events, row); +%} + +SAVE +%{ + fprintf(stderr, "OpenACC event buffer[%s]: count=%ld next=%ld dropped=%ld\n", + NAME_CURRENT_COMP, events.count, events.next, events.dropped); + DETECTOR_OUT_LIST("OpenACC event rows", "x y p", + events.count, events.width, events.data, "EventRows"); +%} + +FINALLY +%{ + mc_event_buffer_free(&events); +%} + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/README.md b/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/README.md new file mode 100644 index 0000000000..81d4e59c29 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/README.md @@ -0,0 +1,14 @@ +# Test_EventBufferOpenACC + +This fixture appends three-column rows from an OpenACC `TRACE` kernel to a +fixed-capacity `MC_EVENT_BUFFER`. With the default `NCount=64`, eight rows are +accepted and 56 reservations are dropped. Device row order is intentionally +not part of the assertion; the output must contain exactly eight complete +rows and no out-of-bounds data. + +Run it on a host with an OpenACC compiler and GPU, for example: + +```sh +mcrun -c --no-mpi --openacc -n 64 -s 42 \ + -I . -d /tmp/event_buffer_openacc Test_EventBufferOpenACC.instr +``` diff --git a/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/Test_EventBufferOpenACC.instr b/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/Test_EventBufferOpenACC.instr new file mode 100644 index 0000000000..44f7c5e49f --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventBufferOpenACC/Test_EventBufferOpenACC.instr @@ -0,0 +1,40 @@ +/******************************************************************************* + * Instrument: Test_EventBufferOpenACC + * + * %Identification + * Written by: McCode development + * Date: 22 September 2026 + * Origin: DTU + * %INSTRUMENT_SITE: Tests_other + * + * %Description + * Exercise MC_EVENT_BUFFER appends from an OpenACC TRACE kernel. The buffer + * uses managed memory, so the runtime can copy its accepted rows after the + * device kernel has completed. Row order is intentionally not asserted. + * + * %Example: NCount=64 + * + * %Parameters + * NCount: [1] number of particles to append + * + * %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventBufferOpenACC(NCount=64) + +DECLARE +%{ +%} + +INITIALIZE +%{ + mcset_ncount((unsigned long long)NCount); +%} + +TRACE +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT events = EventBufferOpenACC(capacity=8) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventMonitorSimple/README.md b/mcstas-comps/examples/Tests_other/Test_EventMonitorSimple/README.md new file mode 100644 index 0000000000..aaa3478138 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventMonitorSimple/README.md @@ -0,0 +1,33 @@ +# Event_monitor_simple test + +This fixture verifies that `Event_monitor_simple` uses the common event-list +writer instead of a private `.log` file. The McStas output is `events.dat` with +three rows and twelve columns: + +```text +id x y z vx vy vz t sx sy sz p +``` + +The McXtrace companion writes the corresponding photon columns: + +```text +id x y z kx ky kz t Ex Ey Ez p +``` + +Run the McStas fixture from this directory after rebuilding the McStas +installation: + +```sh +mcrun -c -n 3 -I . -d /tmp/event_monitor_simple Test_EventMonitorSimple.instr +``` + +Run the McXtrace companion from the repository root after rebuilding the +McXtrace installation: + +```sh +mxrun -c -n 3 -I . -d /tmp/event_monitor_simple_mcxtrace \ + mcxtrace-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple_mcxtrace.instr +``` + +Both outputs should have `type: list(12, 3)`, a `variables:` header matching +the columns above, and component metadata identifying `events`. diff --git a/mcstas-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple.instr b/mcstas-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple.instr new file mode 100644 index 0000000000..c962d61ea9 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple.instr @@ -0,0 +1,31 @@ +/******************************************************************************* + * McStas instrument definition + * + * Instrument: Test_EventMonitorSimple + * + * %Identification + * Written by: McCode development + * Date: 21 September 2026 + * Origin: DTU + * %INSTRUMENT_SITE: Tests_other + * + * %Description + * Focused fixture for the standard event-list output of Event_monitor_simple. + * The component is configured for three rows so the output shape and column + * order can be checked without depending on source intensity. + * + * %Example: mcstas_test + * + * %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventMonitorSimple() + +TRACE + +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT events = Event_monitor_simple(nevents=3) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventOutList/EventOutList.comp b/mcstas-comps/examples/Tests_other/Test_EventOutList/EventOutList.comp new file mode 100644 index 0000000000..2058b837f5 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventOutList/EventOutList.comp @@ -0,0 +1,63 @@ +/******************************************************************************* +* +* McStas, neutron ray-tracing package +* Copyright 1997-2002, All rights reserved +* Risoe National Laboratory, Roskilde, Denmark +* Institut Laue Langevin, Grenoble, France +* +* %I +* Written by: McCode development +* Date: 18 September 2026 +* Origin: DTU +* +* Deterministic event-list output fixture for the generic event-output API. +* In SAVE it builds a fixed row-major double buffer buf[r*ncols+c] = r*10 + c +* and writes it as an event list. Neutron physics is intentionally a +* pass-through no-op so the written list is fully deterministic (independent +* of ncount and seed). +* +* %P +* nevents: [1] number of rows/events (0 -> no-op, <0 -> rejected) +* ncols: [1] number of columns (0 -> rejected) +* filename: [string] output file name (extension added by the runtime) +* columns: [string] whitespace-separated column names +* nullbuf: [1] if set, pass a NULL data pointer (invalid-input test) +* nowritefile:[1] if set, skip writing +* +* %E +******************************************************************************/ + +DEFINE COMPONENT EventOutList + +SETTING PARAMETERS (int nevents=3, int ncols=3, string filename="EventList", + string columns="x y z", int nullbuf=0, int nowritefile=0) + +TRACE +%{ + /* Fixture: the neutron state is left untouched (pass-through). + The event list is produced deterministically in SAVE. */ +%} + +SAVE +%{ + long cnt = nevents; + long wdt = ncols; + double *buf = 0; + long i, j; + + if (cnt > 0 && wdt > 0 && !nullbuf) { + buf = (double*)calloc(cnt*wdt, sizeof(double)); + for (i = 0; i < cnt; i++) + for (j = 0; j < wdt; j++) + buf[i*wdt + j] = i*10.0 + (double)j; + } + + if (!nowritefile) { + /* Generic public API: hides the negative-dimension convention of the + low-level list backend (see mcevent_out_list in the runtime). */ + DETECTOR_OUT_LIST("Deterministic event list", columns, cnt, wdt, buf, filename); + } + if (buf) free(buf); +%} + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventOutList/README.md b/mcstas-comps/examples/Tests_other/Test_EventOutList/README.md new file mode 100644 index 0000000000..fea9a225f1 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventOutList/README.md @@ -0,0 +1,80 @@ +# The `Test_EventOutList` Fixture + +*McStas: deterministic fixture for the generic event-list output API* + +## Identification + +- **Site:** Tests_other +- **Origin:** DTU +- **Date:** 18 September 2026 + +## Description + +A small, self-contained fixture that exercises the generic event-list output +API `mcevent_out_list` (and the `DETECTOR_OUT_LIST` component macro) added to +the common runtime (`common/lib/share/mccode-r.{h.in,c}`). It establishes a +baseline against the existing low-level `mcdetector_out_list` path. + +The `EventOutList` fixture component builds a deterministic row-major buffer in +its `SAVE` section (`buf[r*ncols+c] = r*10 + c`, independent of `ncount` and +seed) and writes it through `DETECTOR_OUT_LIST`. Neutron physics is a +pass-through no-op, so the written list is fully deterministic. + +One instrument run covers the whole case matrix: + +| Instance | Params | Expected | +|------------|-------------------------------|-----------------------------------------------------| +| `evMulti` | `nevents=3, ncols=3` | `ListMulti.dat`: 3 rows x 3 cols, no extension added| +| `evOneCol` | `nevents=3, ncols=1` | `ListOneCol.dat`: 3 values, one column | +| `evOne` | `nevents=1, ncols=3` | `ListOneEv.dat`: 1 event x 3 columns | +| `evZero` | `nevents=0` | no-op, no file written | +| `evBadCnt` | `nevents=-1` | rejected with a warning, no file | +| `evBadWid` | `ncols=0` | rejected with a warning, no file | +| `evNull` | `nullbuf=1` | rejected (null data), warning, no file | +| `evEmpty` | `columns=""` | `ListEmpty.dat`: safe output with unnamed columns | + +## How to run + +The component is not part of the installed library; pass its directory with +`-I`: + +```sh +mcrun -n 1000 -I $(pwd) Test_EventOutList.instr # McCode ASCII +mcrun -n 1000 --format=NeXus -I $(pwd) Test_EventOutList.instr +``` + +Expected deterministic data (rows = events, columns as named): + +``` +0 1 2 +10 11 12 +20 21 22 +``` + +The MPI companion exercises unequal local event counts in both directions: +rank zero is empty for `ListRankUnequal`, and rank one is empty for +`ListRankReverse`. Both files must contain exactly `Rows` rows, with one +collective save per component: + +```sh +mcrun -n 4 --mpi=2 -I $(pwd) Test_EventOutList_mpi.instr Rows=10000 +mcrun -n 4 --mpi=2 --format=NeXus -I $(pwd) Test_EventOutList_mpi.instr Rows=3 +``` + +The flavor-neutral component also has a minimal McXtrace companion instrument: + +```sh +mxrun -n 1000 \ + -I mcstas-comps/examples/Tests_other/Test_EventOutList \ + mcxtrace-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mcxtrace.instr +``` + +## Links + +- [Source code](Test_EventOutList.instr) for `Test_EventOutList.instr`. +- [McXtrace source code](../../../../mcxtrace-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mcxtrace.instr) + for the companion `Test_EventOutList_mcxtrace.instr`. +- [Component](EventOutList.comp) for `EventOutList.comp`. +- [MPI source code](Test_EventOutList_mpi.instr) for the unequal-rank fixture. + +--- diff --git a/mcstas-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList.instr b/mcstas-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList.instr new file mode 100644 index 0000000000..d4e744df61 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList.instr @@ -0,0 +1,65 @@ +/******************************************************************************* +* McStas instrument definition URL=http://www.mcstas.org +* +* Instrument: Test the generic event-list output API (mcevent_out_list) +* +* %Identification +* Written by: McCode development +* Date: 18 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* Fixture instrument that drives the EventOutList fixture component through the +* full matrix of deterministic event-list output cases in a single run: +* - ListMulti : 3 events x 3 columns, filename without extension (baseline) +* - ListOneCol: 3 events x 1 column +* - ListZero : 0 events (documented no-op, no file) + * - ListBadCnt: negative event count (rejected, no file) + * - ListBadWid: zero column count (rejected, no file) + * - ListNull : NULL data pointer for nonzero rows (rejected, no file) + * - ListEmpty : empty column description (safe output) +* The written rows are deterministic: row r, column c = r*10 + c. +* +* %Example: mcstas_test +* +* %Parameters +* (none) +* +* %End +*******************************************************************************/ +DEFINE INSTRUMENT Test_EventOutList() + +/* The TRACE section: a trivial source and one fixture instance per test case. */ +/* Neutron physics is irrelevant; the event lists are produced in SAVE. */ +TRACE + +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT evMulti = EventOutList(nevents=3, ncols=3, filename="ListMulti") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evOneCol = EventOutList(nevents=3, ncols=1, filename="ListOneCol", columns="v") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evOne = EventOutList(nevents=1, ncols=3, filename="ListOneEv") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evZero = EventOutList(nevents=0, ncols=3, filename="ListZero") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evBadCnt = EventOutList(nevents=-1, ncols=3, filename="ListBadCnt") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evBadWid = EventOutList(nevents=3, ncols=0, filename="ListBadWid") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evNull = EventOutList(nevents=3, ncols=3, filename="ListNull", nullbuf=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evEmpty = EventOutList(nevents=3, ncols=3, filename="ListEmpty", columns="") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +/* The END token marks the instrument definition end */ +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mpi.instr b/mcstas-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mpi.instr new file mode 100644 index 0000000000..f0d43d4fee --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mpi.instr @@ -0,0 +1,30 @@ +/******************************************************************************* + * Instrument: Test_EventOutList_mpi + * + * MPI protocol fixture: rank zero emits no rows while the other rank emits + * three rows. The event output call must still be collective. + * + * %Parameters + * Rows: [3] rows emitted by the non-empty rank + * + * %Example: mcstas_test + * + * %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventOutList_mpi(Rows=3) + +TRACE +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT events = EventOutList( + nevents=(mpi_node_rank == 0 ? 0 : Rows), + ncols=3, filename="ListRankUnequal") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT eventsReverse = EventOutList( + nevents=(mpi_node_rank == 0 ? Rows : 0), + ncols=3, filename="ListRankReverse") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriter/EventWriter.comp b/mcstas-comps/examples/Tests_other/Test_EventWriter/EventWriter.comp new file mode 100644 index 0000000000..36f83b7c34 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriter/EventWriter.comp @@ -0,0 +1,163 @@ +/******************************************************************************* + * + * McStas, neutron ray-tracing package + * Copyright 1997-2002, All rights reserved + * Risoe National Laboratory, Roskilde, Denmark + * Institut Laue Langevin, Grenoble, France + * + * %I + * Written by: McCode development + * Date: 28 September 2026 + * Origin: DTU + * + * Deterministic fixture for the bounded generic event writer. Save-time rows + * are independent of the source; use_trace additionally exercises appends + * during ray tracing with the same deterministic row values. + * + * %P + * nrows: [1] number of rows to write + * chunk: [1] maximum rows held before a local spool flush + * filename: [string] output file name + * use_write: [1] use mc_event_writer_write instead of append + * use_direct: [1] stream directly when nrows is the known final count + * use_trace: [1] append rows during ray tracing and finish in SAVE + * use_stream: [1] stream unknown-count rows to the final serial output + * expect_begin_failure: [1] treat a rejected begin as an expected case + * expect_end_failure: [1] treat an output serialization failure as expected + * expect_failure_in_nexus: [1] also expect end failure in NeXus mode + * + * %E + *******************************************************************************/ + +DEFINE COMPONENT EventWriter + +SETTING PARAMETERS (int nrows=11, int chunk=3, string filename="EventWriter", + int use_write=0, int use_direct=0, int use_trace=0, + int use_stream=0, + int expect_begin_failure=0, int expect_end_failure=0, + int expect_failure_in_nexus=0) + +DECLARE +%{ + MC_EVENT_WRITER trace_writer; + long trace_count; + int trace_status; +%} + +INITIALIZE +%{ + trace_count = 0; + trace_status = 0; + if (use_trace || use_stream) { + if (use_stream) + trace_status = mc_event_writer_begin_stream( + &trace_writer, "Bounded event writer", "x y z", 3, chunk, + filename, NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); + else + trace_status = mc_event_writer_begin( + &trace_writer, "Bounded event writer", "x y z", 3, chunk, + filename, NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); + if (trace_status && !expect_begin_failure) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL begin\n", NAME_CURRENT_COMP); + } +%} + +TRACE +%{ + if ((use_trace || use_stream) && !trace_status && trace_count < nrows) { + double row[3]; + long c; + for (c = 0; c < 3; c++) row[c] = trace_count*10.0 + (double)c; + if (!mc_event_writer_append(&trace_writer, row)) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL trace append row %ld\n", + NAME_CURRENT_COMP, trace_count); + else + trace_count++; + } +%} + +SAVE +%{ + MC_EVENT_WRITER writer; + double row[3]; + double *rows = NULL; + MCDETECTOR detector; + long i, c; + int status; + int output_failure_expected; + + if (use_trace || use_stream) { + if (trace_status && expect_begin_failure) + fprintf(stderr, "EventWriter[%s]: expected begin rejection\n", + NAME_CURRENT_COMP); + if (!trace_status) + fprintf(stderr, "EventWriter[%s]: trace rows=%ld chunks=%lld%s\n", + NAME_CURRENT_COMP, trace_count, + trace_writer.chunks + (trace_writer.buffer.count > 0 ? 1 : 0), + use_stream ? " (stream)" : ""); + detector = mc_event_writer_end(&trace_writer); + } else { + output_failure_expected = expect_end_failure + && !mcdisable_output_files + && (expect_failure_in_nexus + || !(mcformat && strcasestr(mcformat, "NeXus"))); + + if (use_direct) + status = mc_event_writer_begin_direct(&writer, "Bounded event writer", + "x y z", nrows, 3, chunk, filename, + NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); + else + status = mc_event_writer_begin(&writer, "Bounded event writer", + "x y z", 3, chunk, filename, + NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); + if (status) { + if (!expect_begin_failure) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL begin\n", NAME_CURRENT_COMP); + else + fprintf(stderr, "EventWriter[%s]: expected begin rejection\n", + NAME_CURRENT_COMP); + } else if (use_write) { + if (nrows > 0) + rows = (double*)malloc((size_t)nrows * 3 * sizeof(double)); + for (i = 0; i < nrows; i++) + for (c = 0; c < 3; c++) + rows[3*i+c] = i*10.0 + (double)c; + if (!mc_event_writer_write(&writer, nrows, rows)) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL write\n", NAME_CURRENT_COMP); + } else { + for (i = 0; i < nrows; i++) { + for (c = 0; c < 3; c++) + row[c] = i*10.0 + (double)c; + if (!mc_event_writer_append(&writer, row)) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL append row %ld\n", + NAME_CURRENT_COMP, i); + if (i == 4 && mc_event_writer_flush(&writer)) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL explicit flush\n", + NAME_CURRENT_COMP); + } + } + + if (!status) + fprintf(stderr, "EventWriter[%s]: rows=%lld chunks=%lld\n", + NAME_CURRENT_COMP, writer.rows + writer.buffer.count, + writer.chunks + (writer.buffer.count > 0 ? 1 : 0)); + detector = mc_event_writer_end(&writer); + if (output_failure_expected && detector.m != 0) + fprintf(stderr, "EventWriter[%s]: TEST-FAIL expected end failure\n", + NAME_CURRENT_COMP); + } + if (rows) free(rows); +%} + +FINALLY +%{ + /* SAVE normally ends the trace writer; keep cleanup safe if SAVE is skipped. */ + if ((use_trace || use_stream) && trace_writer.active) + mc_event_writer_discard(&trace_writer); +%} + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriter/README.md b/mcstas-comps/examples/Tests_other/Test_EventWriter/README.md new file mode 100644 index 0000000000..e30802f1ec --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriter/README.md @@ -0,0 +1,74 @@ +# The `Test_EventWriter` Fixture + +This fixture exercises the host-side bounded generic event writer from the +common runtime. The writer keeps only one configured chunk in event-data +memory; completed chunks are spooled temporarily and replayed at `end` through +the normal ASCII, NeXus, and MPI output paths. + +The append instance writes 11 rows with a chunk capacity of 3 and calls an +explicit flush after row 4. The write instance uses the bulk-row API with 8 +rows and a capacity of 2. Both files contain rows in the deterministic form +`row[r][c] = r*10 + c`: + +``` +0 1 2 +10 11 12 +20 21 22 +... +100 101 102 +``` + +`WriterDirect.dat` uses `mc_event_writer_begin_direct` with the known final +count of 11. Its three-row chunks are written directly through the serial +McCode backend; the normal `mc_event_writer_begin` path remains the choice for +unknown counts and MPI. + +`WriterTrace.dat` starts the normal writer in `INITIALIZE`, appends one row per +ray in `TRACE`, and lets the bounded buffer flush chunks while the simulation is +running. The final `SAVE` call only performs the collective serialization. + +`WriterStream.dat` uses the unknown-count serial stream sink. It opens the +normal output format when the first chunk is ready, writes subsequent chunks +directly during tracing, and patches the reserved row-count fields in the +ASCII header (or rewrites the NeXus detector metadata) at `SAVE`. The logical +header format is unchanged; the fixed-width fields only exist while the +stream is incomplete. This mode requires a new output file and is currently +serial; MPI uses the bounded spool path below until its host service protocol +is added. + +`WriterEmpty` is a zero-event no-op. The companion +`Test_EventWriter_mpi.instr` puts rows on only one rank in each direction and +then gives the ranks different positive row/chunk counts (`Rows=7` produces +seven rows on rank 0 and ten on rank 1 for the unequal writer). Those three writers +append and flush during `TRACE`; `WriterAllEmpty` checks that every rank can +enter an empty session. With `--mpi=2`, all four writers complete even when a +rank has no local chunks. NeXus event datasets for the serial fixture have +shapes `(11, 3)`, `(8, 3)`, `(11, 3)`, `(11, 3)`, and `(11, 3)` for the +append, bulk-write, direct, staged-trace, and unknown-count stream writers. +The MPI fixture also gives `WriterMetadataMismatch` different component +positions on the two ranks; the collective rejects the save without hanging +instead of silently accepting root metadata. + +`WriterInvalidBegin` verifies that a rejected `chunk=0` begin leaves the +writer safely inactive, so the matching `end` call is harmless. `WriterInvalidEnd` +uses a path below a directory that does not exist. In ASCII mode the failed +open is reported as an invalid detector when `end` enters the output session, +after which the writer still releases its spool and buffer; this ASCII open +failure is the asserted case. NeXus instead treats the same value as a dataset +name; it sanitizes the path separator, so the save still produces a valid +`(1, 3)` events dataset. The fixture does not assert that NeXus case, keeping +the component focused on the ASCII open-failure path. + +`WriterInvalidDirectBegin` and `WriterInvalidStreamBegin` exercise the same +rejected-begin/end contract for the serial known-count and unknown-count +constructors. Neither case creates an output file. + +Run from this directory with: + +```sh +mcrun -n 1000 -I . Test_EventWriter.instr +``` + +The McXtrace companion is +`mcxtrace-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mcxtrace.instr`; +it also exercises the serial unknown-count stream sink. diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter.instr b/mcstas-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter.instr new file mode 100644 index 0000000000..1f2f00c9ba --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter.instr @@ -0,0 +1,68 @@ +/******************************************************************************* +* McStas instrument definition URL=http://www.mcstas.org +* +* Instrument: Test the bounded generic event writer +* +* %Identification +* Written by: McCode development +* Date: 28 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* Deterministic fixture for the host-side chunked event writer. The append +* case forces automatic and explicit local spool flushes, the write case uses +* the bulk-row API, and the empty case verifies the zero-event no-op. +* +* %Example: mcstas_test +* +* %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventWriter() + +TRACE + +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT append = EventWriter(nrows=11, chunk=3, filename="WriterAppend") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT write = EventWriter(nrows=8, chunk=2, filename="WriterWrite", use_write=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT direct = EventWriter(nrows=11, chunk=3, filename="WriterDirect", use_direct=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT stream = EventWriter(nrows=11, chunk=2, filename="WriterTrace", use_trace=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT live = EventWriter(nrows=11, chunk=2, filename="WriterStream", use_stream=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT live_empty = EventWriter(nrows=0, chunk=2, filename="WriterStreamEmpty", use_stream=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT empty = EventWriter(nrows=0, chunk=2, filename="WriterEmpty") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT invalid_begin = EventWriter(nrows=3, chunk=0, + filename="WriterInvalidBegin", expect_begin_failure=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT invalid_direct_begin = EventWriter(nrows=0, chunk=1, + filename="WriterInvalidDirectBegin", use_direct=1, + expect_begin_failure=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT invalid_stream_begin = EventWriter(nrows=11, chunk=0, + filename="WriterInvalidStreamBegin", use_stream=1, + expect_begin_failure=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT invalid_end = EventWriter(nrows=1, chunk=1, + filename="missing-event-writer-dir/WriterInvalidEnd", + expect_end_failure=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mpi.instr b/mcstas-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mpi.instr new file mode 100644 index 0000000000..d28f21e617 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mpi.instr @@ -0,0 +1,55 @@ +/******************************************************************************* +* McStas instrument definition URL=http://www.mcstas.org +* +* Instrument: MPI protocol test for the bounded generic event writer +* +* %Identification +* Written by: McCode development +* Date: 28 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* The first two writers deliberately put rows on only one rank in opposite +* directions. The third writer gives the ranks different positive row and +* chunk counts. Every writer still enters one collective end operation. +* +* %Parameters +* Rows: [7] rows used by the non-empty rank and the base of the unequal case +* +* %Example: mcstas_test +* +* %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventWriter_mpi(Rows=7) + +TRACE + +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT rootEmpty = EventWriter( + nrows=(mpi_node_rank == 0 ? 0 : Rows), chunk=3, use_trace=1, + filename="WriterRootEmpty") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT slaveEmpty = EventWriter( + nrows=(mpi_node_rank == 0 ? Rows : 0), chunk=3, use_trace=1, + filename="WriterSlaveEmpty") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT unequal = EventWriter( + nrows=Rows + 3 * mpi_node_rank, chunk=3, use_trace=1, + filename="WriterUnequal") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT allEmpty = EventWriter( + nrows=0, chunk=2, use_trace=1, filename="WriterAllEmpty") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT metadata_mismatch = EventWriter( + nrows=1, chunk=1, filename="WriterMetadataMismatch", + expect_end_failure=1, expect_failure_in_nexus=1) + AT (0.01 * mpi_node_rank, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/EventWriterOpenACC.comp b/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/EventWriterOpenACC.comp new file mode 100644 index 0000000000..b8b6568c32 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/EventWriterOpenACC.comp @@ -0,0 +1,67 @@ +/******************************************************************************* + * + * %I + * Written by: McCode development + * Date: 29 September 2026 + * Origin: DTU + * + * OpenACC fixture for the host-serviced generic event writer. TRACE only + * appends to the writer's device-safe bounded buffer; generated GPU batch + * boundaries transfer and spool the completed chunk on the host. + * + * %P + * chunk: [1] rows retained for one GPU batch + * filename: [string] output file name + * + * %E + *******************************************************************************/ + +DEFINE COMPONENT EventWriterOpenACC + +SETTING PARAMETERS (int chunk=4, string filename="WriterOpenACC") + +DECLARE +%{ + MC_EVENT_WRITER writer; + int status; +%} + +INITIALIZE +%{ + status = mc_event_writer_begin( + &writer, "OpenACC event writer", "uid x p", 3, chunk, + filename, NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); + if (status) + fprintf(stderr, "EventWriterOpenACC[%s]: begin failed\n", NAME_CURRENT_COMP); +%} + +TRACE +%{ + double row[3]; + row[0] = (double)_particle->_uid; + row[1] = row[0] + 1.0; + row[2] = row[0] + 2.0; +#ifdef OPENACC + mc_event_writer_append_openacc(&writer, row); +#else + if (!status) mc_event_writer_append(&writer, row); +#endif +%} + +SAVE +%{ + MCDETECTOR detector; + if (!status) + fprintf(stderr, "EventWriterOpenACC[%s]: rows=%lld chunks=%lld\n", + NAME_CURRENT_COMP, writer.rows, writer.chunks); + detector = mc_event_writer_end(&writer); + (void)detector; +%} + +FINALLY +%{ + if (writer.active) mc_event_writer_discard(&writer); +%} + +END diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/README.md b/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/README.md new file mode 100644 index 0000000000..6b678e50a0 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/README.md @@ -0,0 +1,22 @@ +# Test_EventWriterOpenACC + +This fixture appends one three-column row per ray from an OpenACC `TRACE` +kernel. The generated ray loop calls the host event-writer service after each +GPU batch; the service synchronizes the bounded buffer, spools one chunk, and +resets the buffer before the next batch. Final output still uses the normal +ASCII or NeXus writer and the MPI root-drained session. + +Use `NCount` divisible by `Chunk`, and set `--gpu_innerloop=Chunk` so a run has +multiple batches without padded histories. For `NCount=16` and `Chunk=4`, the +ASCII output contains rows `0 1 2` through `15 16 17`, with one row per event. + +Example serial ASCII run: + +```sh +mcrun -c --no-mpi --openacc --gpu_innerloop=4 -n 16 -I . \ + -d /tmp/event-writer-openacc Test_EventWriterOpenACC.instr +``` + +The same instrument can be run with two MPI ranks. Each rank contributes its +local rows to the common root-drained event session; NeXus uses the same chunk +transport and produces one `(16, 3)` event dataset. diff --git a/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/Test_EventWriterOpenACC.instr b/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/Test_EventWriterOpenACC.instr new file mode 100644 index 0000000000..56a6073587 --- /dev/null +++ b/mcstas-comps/examples/Tests_other/Test_EventWriterOpenACC/Test_EventWriterOpenACC.instr @@ -0,0 +1,37 @@ +/******************************************************************************* + * Instrument: Test_EventWriterOpenACC + * + * %Identification + * Written by: McStas development + * Date: 29 September 2026 + * Origin: DTU + * %INSTRUMENT_SITE: Tests_other + * + * %Description + * Exercise host-serviced bounded event chunks from OpenACC TRACE kernels. + * Choose NCount divisible by Chunk and use --gpu_innerloop=Chunk to force + * multiple device batches without padded histories. + * + * %Parameters + * NCount: [1] number of rows to emit + * Chunk: [1] writer/device-batch capacity + * + * %Example: NCount=16,Chunk=4 + * + * %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventWriterOpenACC(NCount=16, Chunk=4) + +INITIALIZE +%{ + mcset_ncount((unsigned long long)NCount); +%} + +TRACE +COMPONENT src = Source_simple(radius=0.1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT events = EventWriterOpenACC(chunk=Chunk, filename="WriterOpenACC") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcstas-comps/examples/Tests_samples/Test_TOFRes_sample/README.md b/mcstas-comps/examples/Tests_samples/Test_TOFRes_sample/README.md index 67e0b8e061..50b01bf4b1 100644 --- a/mcstas-comps/examples/Tests_samples/Test_TOFRes_sample/README.md +++ b/mcstas-comps/examples/Tests_samples/Test_TOFRes_sample/README.md @@ -41,4 +41,34 @@ Parameters in **boldface** are required; the others are optional. - [Source code](Test_TOFRes_sample.instr) for `Test_TOFRes_sample.instr`. +## Resolution events + +The `TOFRes_monitor` instance writes `TOFres.dat` through the generic +event-list output path. Its fixed column order is: + +```text +ki_x ki_y ki_z kf_x kf_y kf_z x y z p_i p_f +``` + +The monitor's `bufsize` parameter controls the fixed event-buffer capacity; +`bufsize=0` stores up to the instrument ray count. Overflow is reported as a +warning and the accepted rows are still saved. + +## MPI zero-row regression + +This fixed-seed run intentionally gives one rank no accepted resolution events +while the other rank accepts one event. Both ranks must complete the +collective matrix save: + +```sh +mcrun -c -y --mpi=2 -n 400 -s 18 \ + -I mcstas-comps/monitors \ + -d /tmp/test_tofres_mpi_zero \ + mcstas-comps/examples/Tests_samples/Test_TOFRes_sample/Test_TOFRes_sample.instr +``` + +The McCode run writes one-row `TOFres.dat` plus 4x4 covariance and resolution +matrix outputs. Add `--format=NeXus` to check the matching `(1, 11)` event and +`(4, 4)` matrix datasets. + --- diff --git a/mcstas-comps/examples/Tests_samples/Test_TasReso/README.md b/mcstas-comps/examples/Tests_samples/Test_TasReso/README.md index 29f3288696..036ef2a1c4 100644 --- a/mcstas-comps/examples/Tests_samples/Test_TasReso/README.md +++ b/mcstas-comps/examples/Tests_samples/Test_TasReso/README.md @@ -58,4 +58,34 @@ Parameters in **boldface** are required; the others are optional. - [Source code](Test_TasReso.instr) for `Test_TasReso.instr`. +## Resolution events + +The `Res_monitor` instance writes `reso.dat` through the generic event-list +output path. Its fixed column order is: + +```text +ki_x ki_y ki_z kf_x kf_y kf_z x y z p_i p_f +``` + +The monitor's `bufsize` parameter controls the fixed event-buffer capacity; +`bufsize=0` stores up to the instrument ray count. Overflow is reported as a +warning and the accepted rows are still saved. + +## MPI zero-row regression + +This fixed-seed run intentionally gives rank 0 no accepted resolution events +while rank 1 accepts one event. Both ranks must complete the collective matrix +save: + +```sh +mcrun -c -y --mpi=2 -n 1478 -s 170 \ + -I mcstas-comps/monitors \ + -d /tmp/test_tasreso_mpi_zero \ + mcstas-comps/examples/Tests_samples/Test_TasReso/Test_TasReso.instr +``` + +The McCode run writes one-row `reso.dat`, `resmon_covar.dat`, and +`resmon_resol.dat` matrix outputs. Add `--format=NeXus` to check the matching +`(1, 11)` event and `(4, 4)` matrix datasets. + --- diff --git a/mcstas-comps/monitors/Event_monitor_simple.comp b/mcstas-comps/monitors/Event_monitor_simple.comp index 95a1a5f967..4ebc37e39e 100644 --- a/mcstas-comps/monitors/Event_monitor_simple.comp +++ b/mcstas-comps/monitors/Event_monitor_simple.comp @@ -14,14 +14,13 @@ * Low-key event-monitor for debugging purposes. * * %D -* Simple, low-key event-monitor for debugging purposes. No propagation, -* no MPI support. Simply prints the event list to a log file in the SAVE section. -* The filename is "comp-instance".log +* Simple, low-key event-monitor for debugging purposes. No propagation and +* no MPI support; the saved rows use the standard McCode event-list path. * * %P * INPUT PARAMETERS: * -* nevents: [1] Number of events to store and print +* nevents: [1] Number of events to store and save * * %E *******************************************************************************/ @@ -32,24 +31,13 @@ SETTING PARAMETERS (nevents=1e6) DECLARE %{ DArray2d Events; - unsigned long Nevents; - char fullfile[1024]; - char outputdir[512]; + long Nevents; %} INITIALIZE %{ - Nevents = ceil (nevents); - Events = create_darr2d (Nevents, 11); - if (dirname == NULL) { - sprintf (outputdir, "."); - } else { - sprintf (outputdir, dirname); - } - #ifndef USE_MPI - sprintf (fullfile, "%s/%s.%s", outputdir, NAME_CURRENT_COMP, "log"); - #else - sprintf (fullfile, "%s/%s_%i.%s", outputdir, NAME_CURRENT_COMP, mpi_node_rank, "log"); - #endif + Nevents = (long)ceil (nevents); + if (Nevents < 0) Nevents = 0; + Events = Nevents ? create_darr2d (Nevents, 12) : NULL; %} TRACE @@ -57,57 +45,20 @@ TRACE unsigned long i = _particle->_uid; if (i < Nevents) { - #pragma acc atomic write - Events[i][0] = x; - #pragma acc atomic write - Events[i][1] = y; - #pragma acc atomic write - Events[i][2] = z; - #pragma acc atomic write - Events[i][3] = vx; - #pragma acc atomic write - Events[i][4] = vy; - #pragma acc atomic write - Events[i][5] = vz; - #pragma acc atomic write - Events[i][6] = t; - #pragma acc atomic write - Events[i][7] = sx; - #pragma acc atomic write - Events[i][8] = sy; - #pragma acc atomic write - Events[i][9] = sz; - #pragma acc atomic write - Events[i][10] = p; + mc_event_particle_row(&Events[i][1], _particle); } %} SAVE %{ - FILE* fp; - printf ("Storing event logfile --> %s\n", fullfile); - fp = fopen (fullfile, "w+"); - if (!fp) { - fprintf (stderr, "Could not open file %s in w+ mode\n", fullfile); - exit (-1); - } - fprintf (fp, "# Event-file %s, contains %lu events\n", fullfile, Nevents); - fprintf (fp, "########################################### START EVENT LIST ##############################################\n"); - fprintf (fp, "# id x y z vx vy vz t sx sy sz p # \n"); - fprintf (fp, "###########################################################################################################\n"); - unsigned long i; - int j; - for (i = 0; i < Nevents; i++) { - fprintf (fp, "%6lu ", i); - for (j = 0; j < 11; j++) { - fprintf (fp, "%8.2g ", Events[i][j]); - } - fprintf (fp, "\n"); - } - fprintf (fp, "###########################################################################################################\n"); - fprintf (fp, "############################################ END EVENT LIST! ##############################################\n"); - fclose (fp); - printf ("Done storing logfile.\n"); + long i; + for (i = 0; i < Nevents; i++) Events[i][0] = i; + DETECTOR_OUT_LIST("Event monitor events", + "id " MC_EVENT_PARTICLE_COLUMNS, + Nevents, MC_EVENT_PARTICLE_WIDTH + 1, + Nevents ? Events[0] : NULL, + NAME_CURRENT_COMP); + if (Nevents) destroy_darr2d (Events); %} MCDISPLAY diff --git a/mcstas-comps/monitors/Res_monitor.comp b/mcstas-comps/monitors/Res_monitor.comp index e1b95a2ab0..a4a07adad3 100644 --- a/mcstas-comps/monitors/Res_monitor.comp +++ b/mcstas-comps/monitors/Res_monitor.comp @@ -87,6 +87,7 @@ SHARE %{ DECLARE%{ MonitornD_Defines_type DEFS; MonitornD_Variables_type Vars; + MC_EVENT_BUFFER event_buffer; unsigned long num_events; double* reso_events; @@ -102,14 +103,6 @@ DECLARE%{ char res_rx_var[20]; char res_ry_var[20]; char res_rz_var[20]; - /* Arrays for storing resolution matrix */ - DArray2d Covar_p; - DArray2d Covar_p2; - DArray2d Covar_N; - DArray2d Res_p; - DArray2d Res_p2; - DArray2d Res_N; - int num_cores; %} @@ -123,12 +116,26 @@ INITIALIZE %{ reso_events = 0; reso_probabilities = 0; + long event_capacity = bufsize > 0 ? (long)bufsize : (long)mcget_ncount(); + if (mc_event_buffer_init(&event_buffer, event_capacity, 11)) + exit(fprintf(stderr, "Res_monitor: %s: Error allocating event buffer.\n", NAME_CURRENT_COMP)); + if(live_calc) { - reso_events = malloc(mcget_ncount()*4*sizeof(double)); - reso_probabilities = malloc(mcget_ncount()*sizeof(double)); + if (event_capacity > 0 + && (uintmax_t)event_capacity + > (uintmax_t)SIZE_MAX / (4 * sizeof(double))) + exit(fprintf(stderr, "Res_monitor: %s: Error allocating resolution event arrays.\n", NAME_CURRENT_COMP)); + if (event_capacity > 0) { + reso_events = malloc((size_t)event_capacity*4*sizeof(double)); + reso_probabilities = malloc((size_t)event_capacity*sizeof(double)); + if (!reso_events || !reso_probabilities) { + free(reso_events); + free(reso_probabilities); + exit(fprintf(stderr, "Res_monitor: %s: Error allocating resolution event arrays.\n", NAME_CURRENT_COMP)); + } + } } - int i; char tmp[1024]; strcpy(Vars.compcurname, NAME_CURRENT_COMP); @@ -139,34 +146,12 @@ INITIALIZE %{ if (strstr(tmp, "list")) exit(fprintf(stderr, "Res_monitor: %s: Error: Only use geometry keywords (remove list from 'option').\n", NAME_CURRENT_COMP)); - if (!bufsize) - sprintf(Vars.option, "%s borders list all, ud1 ud2 ud3 ud4 ud5 ud6 ud7 ud8 ud9 ud10 n", tmp); - else - sprintf(Vars.option, "%s borders list=%f, ud1 ud2 ud3 ud4 ud5 ud6 ud7 ud8 ud9 ud10 n", tmp, bufsize); + sprintf(Vars.option, "%s", tmp); if (radius) xwidth = 2*radius; Monitor_nD_Init(&DEFS, &Vars, xwidth, yheight, zdepth, xmin, xmax, ymin, ymax, zmin, zmax, 0, 0); - Vars.Coord_Type[0] = DEFS.COORD_USERDOUBLE0; /* otherwise p is always the first variable */ - - if (Vars.Coord_Number != 11) - exit(fprintf(stderr,"Res_monitor: %s: Error: Invalid number of variables to monitor (%li).\n", NAME_CURRENT_COMP, Vars.Coord_Number+1)); - - /* set the labels */ - /* we have to record ki_x ki_y ki_z kf_x kf_y kf_z x y z p_i p_f */ - int idx = 0; - strcpy(tmp,"ki_x"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"ki_y"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"ki_z"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"kf_x"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"kf_y"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"kf_z"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"x"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"y"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"z"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"p_i"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"p_f"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); if (filename != NULL) strncpy(Vars.Mon_File, filename, 128); @@ -183,20 +168,6 @@ INITIALIZE %{ sprintf(res_rx_var, "res_rx_%i", index); sprintf(res_ry_var, "res_ry_%i", index); sprintf(res_rz_var, "res_rz_%i", index); - if(live_calc) { - /* Allocate arrays to save cov and res matrix */ - Covar_p = create_darr2d(4, 4); - Covar_p2 = create_darr2d(4, 4); - Covar_N = create_darr2d(4, 4); - Res_p = create_darr2d(4, 4); - Res_p2 = create_darr2d(4, 4); - Res_N = create_darr2d(4, 4); - #ifdef USE_MPI - num_cores=mpi_node_count; - #else - num_cores=1; - #endif - } %} @@ -253,7 +224,7 @@ TRACE %{ } /* Now fetch data from the Res_sample. */ - if(p > 0. && (!bufsize || num_events < bufsize)) + if(p > 0.) { /* old behaviour not supported by openacc: struct Res_sample_struct *s = @@ -281,35 +252,35 @@ TRACE %{ event_pos[1] = particle_getvar(_particle, res_ry_var, NULL); event_pos[2] = particle_getvar(_particle, res_rz_var, NULL); - #pragma acc atomic capture - { - event_idx = num_events++; - } - - /* variables for Monitor_nD */ - Vars.UserDoubles[0] = event_ki[0]; - Vars.UserDoubles[1] = event_ki[1]; - Vars.UserDoubles[2] = event_ki[2]; - Vars.UserDoubles[3] = event_kf[0]; - Vars.UserDoubles[4] = event_kf[1]; - Vars.UserDoubles[5] = event_kf[2]; - Vars.UserDoubles[6] = event_pos[0]; - Vars.UserDoubles[7] = event_pos[1]; - Vars.UserDoubles[8] = event_pos[2]; - Vars.UserDoubles[9] = event_pi; - Vars.UserDoubles[10] = event_pf; - - Monitor_nD_Trace(&DEFS, &Vars, _particle); - - /* live calculation */ - if(live_calc) { - reso_events[event_idx*4 + 0] = event_ki[0] - event_kf[0]; - reso_events[event_idx*4 + 1] = event_ki[1] - event_kf[1]; - reso_events[event_idx*4 + 2] = event_ki[2] - event_kf[2]; - reso_events[event_idx*4 + 3] = tl2_k_to_E( - event_ki[0], event_ki[1], event_ki[2], - event_kf[0], event_kf[1], event_kf[2]); - reso_probabilities[event_idx] = event_pi * event_pf; + double event_row[11]; + event_row[0] = event_ki[0]; + event_row[1] = event_ki[1]; + event_row[2] = event_ki[2]; + event_row[3] = event_kf[0]; + event_row[4] = event_kf[1]; + event_row[5] = event_kf[2]; + event_row[6] = event_pos[0]; + event_row[7] = event_pos[1]; + event_row[8] = event_pos[2]; + event_row[9] = event_pi; + event_row[10] = event_pf; + + if (mc_event_buffer_append(&event_buffer, event_row)) { + #pragma acc atomic capture + { + event_idx = num_events++; + } + + /* live calculation */ + if(live_calc) { + reso_events[event_idx*4 + 0] = event_ki[0] - event_kf[0]; + reso_events[event_idx*4 + 1] = event_ki[1] - event_kf[1]; + reso_events[event_idx*4 + 2] = event_ki[2] - event_kf[2]; + reso_events[event_idx*4 + 3] = tl2_k_to_E( + event_ki[0], event_ki[1], event_ki[2], + event_kf[0], event_kf[1], event_kf[2]); + reso_probabilities[event_idx] = event_pi * event_pf; + } } } @@ -326,12 +297,15 @@ TRACE %{ SAVE %{ int dbg_save_events = 0; - /* save results, but do not free pointers */ - Monitor_nD_Save(&DEFS, &Vars); + mc_event_buffer_save(&event_buffer, + "Resolution monitor events", + "ki_x ki_y ki_z kf_x kf_y kf_z x y z p_i p_f", + filename, + NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); - /* live calculation */ - if(live_calc && num_events > 0) { - if(dbg_save_events) { + if(live_calc) { + if(dbg_save_events && num_events > 0) { /* save individual neutron events */ #ifndef USE_MPI const char* event_filename = "reso_events.dat"; @@ -343,49 +317,40 @@ SAVE %{ tl2_save_events(reso_events, reso_probabilities, event_filename, num_events); } - double cov[4*4], res[4*4]; - if(tl2_reso(reso_events, reso_probabilities, cov, res, num_events)) { - printf("Resolution calculation used %d neutron events.\n", num_events); - tl2_print_mat(cov, "Covariance matrix", 4, 4); - tl2_print_mat(res, "Resolution matrix", 4, 4); - printf("Please run \"mcresplot %s\" for a full analysis.\n", filename); - } - else { - printf("Error: Resolution matrix could not be calculated."); - } - int i,j; - for(i=0;i<4;i++) { - for(j=0;j<4;j++) { - /* "Events" */ - Covar_N[i][j]=1; - Res_N[i][j]=1; - /* Covariance/resolution matrixces (potentiall pr. MPI node) */ - Covar_p[i][j]=cov[4*i+j]/num_cores; - Res_p[i][j]=res[4*i+j]/num_cores; - /* "errors" */ - Covar_p2[i][j]=(cov[4*i+j]/num_cores)*(cov[4*i+j]/num_cores); - Res_p2[i][j]=(res[4*i+j]/num_cores)*(res[4*i+j]/num_cores); + double cov[4*4] = {0}; + double res[4*4] = {0}; + int local_success = 0; + if (num_events > 0) { + local_success = tl2_reso(reso_events, reso_probabilities, cov, res, num_events); + if(local_success) { + printf("Resolution calculation used %lu neutron events.\n", num_events); + tl2_print_mat(cov, "Covariance matrix", 4, 4); + tl2_print_mat(res, "Resolution matrix", 4, 4); + printf("Please run \"mcresplot %s\" for a full analysis.\n", filename); + } + else { + printf("Error: Resolution matrix could not be calculated."); } } - /* Nasty call to DETECTOR_OUT_2D to store covariance matrix */ + char covar_fname[1024]; char res_fname[1024]; sprintf(covar_fname,"%s_%s", _comp->_name, "covar"); sprintf(res_fname,"%s_%s", _comp->_name, "resol"); - DETECTOR_OUT_2D("Covariance", - "Columns", - "Rows", - 0.0, 4.0, 0.0, 4.0, - 4, 4, - &Covar_N[0][0],&Covar_p[0][0],&Covar_p2[0][0], - covar_fname); - DETECTOR_OUT_2D("Resolution", - "Columns", - "Rows", - 0.0, 4.0, 0.0, 4.0, - 4, 4, - &Res_N[0][0],&Res_p[0][0],&Res_p2[0][0], - res_fname); + DETECTOR_OUT_2D_AVERAGE("Covariance", + "Columns", + "Rows", + 0.0, 4.0, 0.0, 4.0, + 4, 4, + cov, local_success, + covar_fname); + DETECTOR_OUT_2D_AVERAGE("Resolution", + "Columns", + "Rows", + 0.0, 4.0, 0.0, 4.0, + 4, 4, + res, local_success, + res_fname); } %} @@ -393,17 +358,12 @@ SAVE %{ FINALLY %{ /* free pointers */ Monitor_nD_Finally(&DEFS, &Vars); + mc_event_buffer_free(&event_buffer); if(live_calc) { free(reso_events); free(reso_probabilities); } - destroy_darr2d(Covar_p); - destroy_darr2d(Covar_p2); - destroy_darr2d(Covar_N); - destroy_darr2d(Res_p); - destroy_darr2d(Res_p2); - destroy_darr2d(Res_N); %} diff --git a/mcstas-comps/monitors/TOFRes_monitor.comp b/mcstas-comps/monitors/TOFRes_monitor.comp index 4ef725b307..be1ba4ff10 100644 --- a/mcstas-comps/monitors/TOFRes_monitor.comp +++ b/mcstas-comps/monitors/TOFRes_monitor.comp @@ -87,6 +87,7 @@ SHARE %{ DECLARE%{ MonitornD_Defines_type DEFS; MonitornD_Variables_type Vars; + MC_EVENT_BUFFER event_buffer; unsigned long num_events; double* reso_events; @@ -102,14 +103,6 @@ DECLARE%{ char res_rx_var[20]; char res_ry_var[20]; char res_rz_var[20]; - /* Arrays for storing resolution matrix */ - DArray2d Covar_p; - DArray2d Covar_p2; - DArray2d Covar_N; - DArray2d Res_p; - DArray2d Res_p2; - DArray2d Res_N; - int num_cores; %} @@ -123,12 +116,26 @@ INITIALIZE %{ reso_events = 0; reso_probabilities = 0; + long event_capacity = bufsize > 0 ? (long)bufsize : (long)mcget_ncount(); + if (mc_event_buffer_init(&event_buffer, event_capacity, 11)) + exit(fprintf(stderr, "TOFRes_monitor: %s: Error allocating event buffer.\n", NAME_CURRENT_COMP)); + if(live_calc) { - reso_events = malloc(mcget_ncount()*4*sizeof(double)); - reso_probabilities = malloc(mcget_ncount()*sizeof(double)); + if (event_capacity > 0 + && (uintmax_t)event_capacity + > (uintmax_t)SIZE_MAX / (4 * sizeof(double))) + exit(fprintf(stderr, "TOFRes_monitor: %s: Error allocating resolution event arrays.\n", NAME_CURRENT_COMP)); + if (event_capacity > 0) { + reso_events = malloc((size_t)event_capacity*4*sizeof(double)); + reso_probabilities = malloc((size_t)event_capacity*sizeof(double)); + if (!reso_events || !reso_probabilities) { + free(reso_events); + free(reso_probabilities); + exit(fprintf(stderr, "TOFRes_monitor: %s: Error allocating resolution event arrays.\n", NAME_CURRENT_COMP)); + } + } } - int i; char tmp[1024]; strcpy(Vars.compcurname, NAME_CURRENT_COMP); @@ -139,34 +146,12 @@ INITIALIZE %{ if (strstr(tmp, "list")) exit(fprintf(stderr, "TOFRes_monitor: %s: Error: Only use geometry keywords (remove list from 'option').\n", NAME_CURRENT_COMP)); - if (!bufsize) - sprintf(Vars.option, "%s borders list all, ud1 ud2 ud3 ud4 ud5 ud6 ud7 ud8 ud9 ud10 n", tmp); - else - sprintf(Vars.option, "%s borders list=%f, ud1 ud2 ud3 ud4 ud5 ud6 ud7 ud8 ud9 ud10 n", tmp, bufsize); + sprintf(Vars.option, "%s", tmp); if (radius) xwidth = 2*radius; Monitor_nD_Init(&DEFS, &Vars, xwidth, yheight, zdepth, xmin, xmax, ymin, ymax, zmin, zmax, 0, 0); - Vars.Coord_Type[0] = DEFS.COORD_USERDOUBLE0; /* otherwise p is always the first variable */ - - if (Vars.Coord_Number != 11) - exit(fprintf(stderr,"TOFRes_monitor: %s: Error: Invalid number of variables to monitor (%li).\n", NAME_CURRENT_COMP, Vars.Coord_Number+1)); - - /* set the labels */ - /* we have to record ki_x ki_y ki_z kf_x kf_y kf_z x y z p_i p_f */ - int idx = 0; - strcpy(tmp,"ki_x"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"ki_y"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"ki_z"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"kf_x"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"kf_y"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"kf_z"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"x"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"y"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"z"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"p_i"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); - strcpy(tmp,"p_f"); strcpy(Vars.Coord_Label[idx], tmp); strcpy(Vars.Coord_Var[idx++], tmp); if (filename != NULL) strncpy(Vars.Mon_File, filename, 128); @@ -183,20 +168,6 @@ INITIALIZE %{ sprintf(res_rx_var, "res_rx_%i", index); sprintf(res_ry_var, "res_ry_%i", index); sprintf(res_rz_var, "res_rz_%i", index); - if(live_calc) { - /* Allocate arrays to save cov and res matrix */ - Covar_p = create_darr2d(4, 4); - Covar_p2 = create_darr2d(4, 4); - Covar_N = create_darr2d(4, 4); - Res_p = create_darr2d(4, 4); - Res_p2 = create_darr2d(4, 4); - Res_N = create_darr2d(4, 4); - #ifdef USE_MPI - num_cores=mpi_node_count; - #else - num_cores=1; - #endif - } %} @@ -253,7 +224,7 @@ TRACE %{ } /* Now fetch data from the TOFRes_sample. */ - if(p > 0. && (!bufsize || num_events < bufsize)) + if(p > 0.) { /* old behaviour not supported by openacc: struct Res_sample_struct *s = @@ -281,35 +252,35 @@ TRACE %{ event_pos[1] = particle_getvar(_particle, res_ry_var, NULL); event_pos[2] = particle_getvar(_particle, res_rz_var, NULL); - #pragma acc atomic capture - { - event_idx = num_events++; - } - - /* variables for Monitor_nD */ - Vars.UserDoubles[0] = event_ki[0]; - Vars.UserDoubles[1] = event_ki[1]; - Vars.UserDoubles[2] = event_ki[2]; - Vars.UserDoubles[3] = event_kf[0]; - Vars.UserDoubles[4] = event_kf[1]; - Vars.UserDoubles[5] = event_kf[2]; - Vars.UserDoubles[6] = event_pos[0]; - Vars.UserDoubles[7] = event_pos[1]; - Vars.UserDoubles[8] = event_pos[2]; - Vars.UserDoubles[9] = event_pi; - Vars.UserDoubles[10] = event_pf; - - Monitor_nD_Trace(&DEFS, &Vars, _particle); - - /* live calculation */ - if(live_calc) { - reso_events[event_idx*4 + 0] = event_ki[0] - event_kf[0]; - reso_events[event_idx*4 + 1] = event_ki[1] - event_kf[1]; - reso_events[event_idx*4 + 2] = event_ki[2] - event_kf[2]; - reso_events[event_idx*4 + 3] = tl2_k_to_E( - event_ki[0], event_ki[1], event_ki[2], - event_kf[0], event_kf[1], event_kf[2]); - reso_probabilities[event_idx] = event_pi * event_pf; + double event_row[11]; + event_row[0] = event_ki[0]; + event_row[1] = event_ki[1]; + event_row[2] = event_ki[2]; + event_row[3] = event_kf[0]; + event_row[4] = event_kf[1]; + event_row[5] = event_kf[2]; + event_row[6] = event_pos[0]; + event_row[7] = event_pos[1]; + event_row[8] = event_pos[2]; + event_row[9] = event_pi; + event_row[10] = event_pf; + + if (mc_event_buffer_append(&event_buffer, event_row)) { + #pragma acc atomic capture + { + event_idx = num_events++; + } + + /* live calculation */ + if(live_calc) { + reso_events[event_idx*4 + 0] = event_ki[0] - event_kf[0]; + reso_events[event_idx*4 + 1] = event_ki[1] - event_kf[1]; + reso_events[event_idx*4 + 2] = event_ki[2] - event_kf[2]; + reso_events[event_idx*4 + 3] = tl2_k_to_E( + event_ki[0], event_ki[1], event_ki[2], + event_kf[0], event_kf[1], event_kf[2]); + reso_probabilities[event_idx] = event_pi * event_pf; + } } } @@ -326,12 +297,15 @@ TRACE %{ SAVE %{ int dbg_save_events = 0; - /* save results, but do not free pointers */ - Monitor_nD_Save(&DEFS, &Vars); + mc_event_buffer_save(&event_buffer, + "TOF resolution monitor events", + "ki_x ki_y ki_z kf_x kf_y kf_z x y z p_i p_f", + filename, + NAME_CURRENT_COMP, POS_A_CURRENT_COMP, + ROT_A_CURRENT_COMP, INDEX_CURRENT_COMP); - /* live calculation */ - if(live_calc && num_events > 0) { - if(dbg_save_events) { + if(live_calc) { + if(dbg_save_events && num_events > 0) { /* save individual neutron events */ #ifndef USE_MPI const char* event_filename = "reso_events.dat"; @@ -343,49 +317,40 @@ SAVE %{ tl2_save_events(reso_events, reso_probabilities, event_filename, num_events); } - double cov[4*4], res[4*4]; - if(tl2_reso(reso_events, reso_probabilities, cov, res, num_events)) { - printf("Resolution calculation used %d neutron events.\n", num_events); - tl2_print_mat(cov, "Covariance matrix", 4, 4); - tl2_print_mat(res, "Resolution matrix", 4, 4); - printf("Please run \"mcresplot %s\" for a full analysis.\n", filename); - } - else { - printf("Error: Resolution matrix could not be calculated."); - } - int i,j; - for(i=0;i<4;i++) { - for(j=0;j<4;j++) { - /* "Events" */ - Covar_N[i][j]=1; - Res_N[i][j]=1; - /* Covariance/resolution matrixces (potentiall pr. MPI node) */ - Covar_p[i][j]=cov[4*i+j]/num_cores; - Res_p[i][j]=res[4*i+j]/num_cores; - /* "errors" */ - Covar_p2[i][j]=(cov[4*i+j]/num_cores)*(cov[4*i+j]/num_cores); - Res_p2[i][j]=(res[4*i+j]/num_cores)*(res[4*i+j]/num_cores); + double cov[4*4] = {0}; + double res[4*4] = {0}; + int local_success = 0; + if (num_events > 0) { + local_success = tl2_reso(reso_events, reso_probabilities, cov, res, num_events); + if(local_success) { + printf("Resolution calculation used %lu neutron events.\n", num_events); + tl2_print_mat(cov, "Covariance matrix", 4, 4); + tl2_print_mat(res, "Resolution matrix", 4, 4); + printf("Please run \"mcresplot %s\" for a full analysis.\n", filename); + } + else { + printf("Error: Resolution matrix could not be calculated."); } } - /* Nasty call to DETECTOR_OUT_2D to store covariance matrix */ + char covar_fname[1024]; char res_fname[1024]; sprintf(covar_fname,"%s_%s", _comp->_name, "covar"); sprintf(res_fname,"%s_%s", _comp->_name, "resol"); - DETECTOR_OUT_2D("Covariance", - "Columns", - "Rows", - 0.0, 4.0, 0.0, 4.0, - 4, 4, - &Covar_N[0][0],&Covar_p[0][0],&Covar_p2[0][0], - covar_fname); - DETECTOR_OUT_2D("Resolution", - "Columns", - "Rows", - 0.0, 4.0, 0.0, 4.0, - 4, 4, - &Res_N[0][0],&Res_p[0][0],&Res_p2[0][0], - res_fname); + DETECTOR_OUT_2D_AVERAGE("Covariance", + "Columns", + "Rows", + 0.0, 4.0, 0.0, 4.0, + 4, 4, + cov, local_success, + covar_fname); + DETECTOR_OUT_2D_AVERAGE("Resolution", + "Columns", + "Rows", + 0.0, 4.0, 0.0, 4.0, + 4, 4, + res, local_success, + res_fname); } %} @@ -393,17 +358,12 @@ SAVE %{ FINALLY %{ /* free pointers */ Monitor_nD_Finally(&DEFS, &Vars); + mc_event_buffer_free(&event_buffer); if(live_calc) { free(reso_events); free(reso_probabilities); } - destroy_darr2d(Covar_p); - destroy_darr2d(Covar_p2); - destroy_darr2d(Covar_N); - destroy_darr2d(Res_p); - destroy_darr2d(Res_p2); - destroy_darr2d(Res_N); %} diff --git a/mcstas-comps/share/monitor_nd-lib.c b/mcstas-comps/share/monitor_nd-lib.c index 04e918ddab..5f6f2f1501 100644 --- a/mcstas-comps/share/monitor_nd-lib.c +++ b/mcstas-comps/share/monitor_nd-lib.c @@ -748,10 +748,18 @@ void Monitor_nD_Init(MonitornD_Defines_type *DEFS, { Vars->Flag_Multiple = 1; /* default is n1D */ if (Vars->Coord_Number != Vars->Coord_NumberNoPixel) Vars->Flag_List = 1; } - /* list and auto limits case : Vars->Flag_List or Vars->Flag_Auto_Limits - * -> Buffer to flush and suppress after Vars->Flag_Auto_Limits - */ - if ((Vars->Flag_Auto_Limits || Vars->Flag_List) && Vars->Coord_Number) + /* The generic event buffer owns ordinary fixed-size lists. Keep the + * legacy buffer for auto-limit replay and list-all flushing. */ + Vars->Mon2D_Buffer = NULL; + Vars->List_Buffer.data = NULL; + Vars->List_Buffer.width = 0; + Vars->List_Buffer.capacity = 0; + Vars->List_Buffer.count = 0; + Vars->List_Buffer.next = 0; + Vars->List_Buffer.dropped = 0; + Vars->List_Chunks = NULL; + Vars->List_Chunks_Tail = NULL; + if ((Vars->Flag_Auto_Limits || Vars->Flag_List >= 2) && Vars->Coord_Number) { /* Dim : (Vars->Coord_Number+1)*Vars->Buffer_Block matrix (for p, dp) */ Vars->Mon2D_Buffer = (double *)malloc((Vars->Coord_Number+1)*Vars->Buffer_Block*sizeof(double)); if (Vars->Mon2D_Buffer == NULL) @@ -762,6 +770,16 @@ void Monitor_nD_Init(MonitornD_Defines_type *DEFS, } Vars->Buffer_Size = Vars->Buffer_Block; } + else if (Vars->Flag_List == 1 && Vars->Coord_Number) + { + if (mc_event_buffer_init(&Vars->List_Buffer, (long)Vars->Buffer_Block, + (long)(Vars->Coord_Number+1))) + { + printf("Monitor_nD: %s cannot allocate list event buffer (%li events). No list.\n", + Vars->compcurname, (long)Vars->Buffer_Block); + Vars->Flag_List = 0; + } + } /* 1D and n1D case : Vars->Flag_Multiple */ if (Vars->Flag_Multiple && Vars->Coord_NumberNoPixel) @@ -1173,13 +1191,35 @@ int Monitor_nD_Trace(MonitornD_Defines_type *DEFS, MonitornD_Variables_type *Var if ((Vars->Buffer_Counter >= Vars->Buffer_Block) && (Vars->Flag_List >= 2)) { if (Vars->Buffer_Size >= 1000000 || Vars->Flag_List == 3) - { /* save current (possibly append) and re-use Buffer */ - - Monitor_nD_Save(DEFS, Vars); - Vars->Flag_List = 3; - Vars->Buffer_Block = Vars->Buffer_Size; - Vars->Buffer_Counter = 0; - Vars->Neutron_Counter = 0; + { /* detach current rows and re-use a bounded trace buffer */ + if (!Vars->Flag_Auto_Limits) { + double *filled = Vars->Mon2D_Buffer; + double *replacement = (double *)malloc((Vars->Coord_Number+1) + * Vars->Buffer_Size + * sizeof(double)); + if (!replacement + || mc_event_chunk_append(&Vars->List_Chunks, + &Vars->List_Chunks_Tail, + filled, (long)Vars->Buffer_Counter)) { + free(replacement); + printf("Monitor_nD: %s cannot queue filled event chunk. Stopping list output.\n", + Vars->compcurname); + Vars->Flag_List = 1; + } else { + Vars->Mon2D_Buffer = replacement; + Vars->Flag_List = 3; + Vars->Buffer_Block = Vars->Buffer_Size; + Vars->Buffer_Counter = 0; + Vars->Neutron_Counter = 0; + } + } else { + /* Auto-limit replay still owns this scratch buffer until SAVE. */ + Monitor_nD_Save(DEFS, Vars); + Vars->Flag_List = 3; + Vars->Buffer_Block = Vars->Buffer_Size; + Vars->Buffer_Counter = 0; + Vars->Neutron_Counter = 0; + } } else { @@ -1542,13 +1582,23 @@ int Monitor_nD_Trace(MonitornD_Defines_type *DEFS, MonitornD_Variables_type *Var } /* end (Vars->Flag_Auto_Limits != 1) */ if (Vars->Flag_Auto_Limits != 2 && !outsidebounds) /* not when reading auto limits Buffer */ - { /* now store Coord into Buffer (no index needed) if necessary (list or auto limits) */ - if ((Vars->Buffer_Counter < Vars->Buffer_Block) && ((Vars->Flag_List) || (Vars->Flag_Auto_Limits == 1))) + { /* store ordinary lists in the generic buffer; retain the legacy path + for auto limits and list-all mode */ + if (Vars->List_Buffer.data) + { + if (mc_event_buffer_append(&Vars->List_Buffer, Coord) + && Vars->Flag_Verbose && Vars->Flag_List == 1 + && Vars->List_Buffer.capacity > 0 + && Vars->List_Buffer.count >= Vars->List_Buffer.capacity) + printf("Monitor_nD: %s %li neutrons stored in List.\n", + Vars->compcurname, Vars->List_Buffer.count); + } + else if ((Vars->Buffer_Counter < Vars->Buffer_Block) && ((Vars->Flag_List) || (Vars->Flag_Auto_Limits == 1))) { double *Mon2D_Buffer = Vars->Mon2D_Buffer; for (i = 0; i <= Vars->Coord_Number; i++) { - // This is is where the list is appended. How to make this "atomic"? + // This is is where the list is appended. How to make this "atomic"? #pragma acc atomic write Mon2D_Buffer[i + Vars->Buffer_Counter*(Vars->Coord_Number+1)] = Coord[i]; } @@ -1603,6 +1653,9 @@ MCDETECTOR Monitor_nD_Save(MonitornD_Defines_type *DEFS, MonitornD_Variables_typ double XY=0, pp=0; double Coord[MONnD_COORD_NMAX]; long Coord_Index[MONnD_COORD_NMAX]; + long List_Count; + double *List_Data; + MC_EVENT_CHUNK final_chunk; char label[CHAR_BUF_LENGTH]; MCDETECTOR detector; @@ -1762,12 +1815,18 @@ MCDETECTOR Monitor_nD_Save(MonitornD_Defines_type *DEFS, MonitornD_Variables_typ if (strlen(Vars->Mon_File) > 0) { fname = (char*)malloc(strlen(Vars->Mon_File)+10*Vars->Coord_Number); - if (Vars->Flag_List && Vars->Mon2D_Buffer) /* List: DETECTOR_OUT_2D */ + if (Vars->Flag_List) /* List */ { - - if (Vars->Flag_List >= 2) Vars->Buffer_Size = Vars->Neutron_Counter; - if (Vars->Buffer_Size >= Vars->Neutron_Counter) - Vars->Buffer_Size = Vars->Neutron_Counter; + if (Vars->List_Buffer.data) + { + List_Count = Vars->List_Buffer.count; + List_Data = Vars->List_Buffer.data; + } + else + { + List_Count = Vars->Buffer_Counter; + List_Data = Vars->Mon2D_Buffer; + } strcpy(fname,Vars->Mon_File); if (strchr(Vars->Mon_File,'.') == NULL) strcat(fname, "_list"); @@ -1784,11 +1843,26 @@ MCDETECTOR Monitor_nD_Save(MonitornD_Defines_type *DEFS, MonitornD_Variables_typ /* handle the type of list output */ strcpy(label, Vars->Monitor_Label); - detector = mcdetector_out_list( - label, "List of neutron events", Coord_X_Label, - -Vars->Buffer_Size, Vars->Coord_Number+1, - Vars->Mon2D_Buffer, - fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, Vars->option,Vars->compcurindex); + /* Serialize queued full chunks and the final partial chunk through one + collective session. The temporary tail node remains caller-owned. */ + final_chunk.data = List_Data; + final_chunk.count = List_Count; + final_chunk.next = NULL; + if (Vars->List_Chunks) { + Vars->List_Chunks_Tail->next = &final_chunk; + detector = mcevent_out_list_nd_chunks( + label, "List of neutron events", Coord_X_Label, + Vars->Coord_Number+1, Vars->List_Chunks, + fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, + Vars->option, Vars->compcurindex); + Vars->List_Chunks_Tail->next = NULL; + } else { + detector = mcevent_out_list_nd( + label, "List of neutron events", Coord_X_Label, + List_Count, Vars->Coord_Number+1, List_Data, + fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, + Vars->option, Vars->compcurindex); + } } if (Vars->Flag_Multiple) /* n1D: DETECTOR_OUT_1D */ { @@ -1998,6 +2072,9 @@ void Monitor_nD_Finally(MonitornD_Defines_type *DEFS, { /* Dim : (Vars->Coord_Number+1)*Vars->Buffer_Block matrix (for p, dp) */ if (Vars->Mon2D_Buffer != NULL) free(Vars->Mon2D_Buffer); } + mc_event_buffer_free(&Vars->List_Buffer); + mc_event_chunk_free(&Vars->List_Chunks); + Vars->List_Chunks_Tail = NULL; /* 1D and n1D case : Vars->Flag_Multiple */ if (Vars->Flag_Multiple && Vars->Coord_Number) diff --git a/mcstas-comps/share/monitor_nd-lib.h b/mcstas-comps/share/monitor_nd-lib.h index 37b969235e..a7a15f4aa2 100644 --- a/mcstas-comps/share/monitor_nd-lib.h +++ b/mcstas-comps/share/monitor_nd-lib.h @@ -197,6 +197,9 @@ double **Mon2D_p; double **Mon2D_p2; double *Mon2D_Buffer; + MC_EVENT_BUFFER List_Buffer; + MC_EVENT_CHUNK *List_Chunks; + MC_EVENT_CHUNK *List_Chunks_Tail; unsigned long PixelID; double mxmin,mxmax,mymin,mymax,mzmin,mzmax; diff --git a/mcstas-comps/share/monitor_nd_noacc-lib.c b/mcstas-comps/share/monitor_nd_noacc-lib.c index 98116ec411..09006d4cde 100644 --- a/mcstas-comps/share/monitor_nd_noacc-lib.c +++ b/mcstas-comps/share/monitor_nd_noacc-lib.c @@ -756,10 +756,18 @@ void Monitor_nd_noaccInit(Monitornd_noaccDefines_type *DEFS, { Vars->Flag_Multiple = 1; /* default is n1D */ if (Vars->Coord_Number != Vars->Coord_NumberNoPixel) Vars->Flag_List = 1; } - /* list and auto limits case : Vars->Flag_List or Vars->Flag_Auto_Limits - * -> Buffer to flush and suppress after Vars->Flag_Auto_Limits - */ - if ((Vars->Flag_Auto_Limits || Vars->Flag_List) && Vars->Coord_Number) + /* The generic event buffer owns ordinary fixed-size lists. Keep the + * legacy buffer for auto-limit replay and list-all flushing. */ + Vars->Mon2D_Buffer = NULL; + Vars->List_Buffer.data = NULL; + Vars->List_Buffer.width = 0; + Vars->List_Buffer.capacity = 0; + Vars->List_Buffer.count = 0; + Vars->List_Buffer.next = 0; + Vars->List_Buffer.dropped = 0; + Vars->List_Chunks = NULL; + Vars->List_Chunks_Tail = NULL; + if ((Vars->Flag_Auto_Limits || Vars->Flag_List >= 2) && Vars->Coord_Number) { /* Dim : (Vars->Coord_Number+1)*Vars->Buffer_Block matrix (for p, dp) */ Vars->Mon2D_Buffer = (double *)malloc((Vars->Coord_Number+1)*Vars->Buffer_Block*sizeof(double)); if (Vars->Mon2D_Buffer == NULL) @@ -770,6 +778,16 @@ void Monitor_nd_noaccInit(Monitornd_noaccDefines_type *DEFS, } Vars->Buffer_Size = Vars->Buffer_Block; } + else if (Vars->Flag_List == 1 && Vars->Coord_Number) + { + if (mc_event_buffer_init(&Vars->List_Buffer, (long)Vars->Buffer_Block, + (long)(Vars->Coord_Number+1))) + { + printf("Monitor_nD: %s cannot allocate list event buffer (%li events). No list.\n", + Vars->compcurname, (long)Vars->Buffer_Block); + Vars->Flag_List = 0; + } + } /* 1D and n1D case : Vars->Flag_Multiple */ if (Vars->Flag_Multiple && Vars->Coord_NumberNoPixel) @@ -1184,13 +1202,35 @@ int Monitor_nd_noaccTrace(Monitornd_noaccDefines_type *DEFS, Monitornd_noaccVari if ((Vars->Buffer_Counter >= Vars->Buffer_Block) && (Vars->Flag_List >= 2)) { if (Vars->Buffer_Size >= 1000000 || Vars->Flag_List == 3) - { /* save current (possibly append) and re-use Buffer */ - - Monitor_nd_noaccSave(DEFS, Vars); - Vars->Flag_List = 3; - Vars->Buffer_Block = Vars->Buffer_Size; - Vars->Buffer_Counter = 0; - Vars->Neutron_Counter = 0; + { /* detach current rows and re-use a bounded trace buffer */ + if (!Vars->Flag_Auto_Limits) { + double *filled = Vars->Mon2D_Buffer; + double *replacement = (double *)malloc((Vars->Coord_Number+1) + * Vars->Buffer_Size + * sizeof(double)); + if (!replacement + || mc_event_chunk_append(&Vars->List_Chunks, + &Vars->List_Chunks_Tail, + filled, (long)Vars->Buffer_Counter)) { + free(replacement); + printf("Monitor_nD: %s cannot queue filled event chunk. Stopping list output.\n", + Vars->compcurname); + Vars->Flag_List = 1; + } else { + Vars->Mon2D_Buffer = replacement; + Vars->Flag_List = 3; + Vars->Buffer_Block = Vars->Buffer_Size; + Vars->Buffer_Counter = 0; + Vars->Neutron_Counter = 0; + } + } else { + /* Auto-limit replay still owns this scratch buffer until SAVE. */ + Monitor_nd_noaccSave(DEFS, Vars); + Vars->Flag_List = 3; + Vars->Buffer_Block = Vars->Buffer_Size; + Vars->Buffer_Counter = 0; + Vars->Neutron_Counter = 0; + } } else { @@ -1540,8 +1580,18 @@ int Monitor_nd_noaccTrace(Monitornd_noaccDefines_type *DEFS, Monitornd_noaccVari } /* end (Vars->Flag_Auto_Limits != 1) */ if (Vars->Flag_Auto_Limits != 2 && !outsidebounds) /* not when reading auto limits Buffer */ - { /* now store Coord into Buffer (no index needed) if necessary (list or auto limits) */ - if ((Vars->Buffer_Counter < Vars->Buffer_Block) && ((Vars->Flag_List) || (Vars->Flag_Auto_Limits == 1))) + { /* store ordinary lists in the generic buffer; retain the legacy path + for auto limits and list-all mode */ + if (Vars->List_Buffer.data) + { + if (mc_event_buffer_append(&Vars->List_Buffer, Coord) + && Vars->Flag_Verbose && Vars->Flag_List == 1 + && Vars->List_Buffer.capacity > 0 + && Vars->List_Buffer.count >= Vars->List_Buffer.capacity) + printf("Monitor_nD: %s %li neutrons stored in List.\n", + Vars->compcurname, Vars->List_Buffer.count); + } + else if ((Vars->Buffer_Counter < Vars->Buffer_Block) && ((Vars->Flag_List) || (Vars->Flag_Auto_Limits == 1))) { for (i = 0; i <= Vars->Coord_Number; i++) { @@ -1592,6 +1642,9 @@ MCDETECTOR Monitor_nd_noaccSave(Monitornd_noaccDefines_type *DEFS, Monitornd_noa double XY=0, pp=0; double Coord[MONnD_COORD_NMAX]; long Coord_Index[MONnD_COORD_NMAX]; + long List_Count; + double *List_Data; + MC_EVENT_CHUNK final_chunk; char label[CHAR_BUF_LENGTH]; MCDETECTOR detector; @@ -1750,12 +1803,18 @@ MCDETECTOR Monitor_nd_noaccSave(Monitornd_noaccDefines_type *DEFS, Monitornd_noa if (strlen(Vars->Mon_File) > 0) { fname = (char*)malloc(strlen(Vars->Mon_File)+10*Vars->Coord_Number); - if (Vars->Flag_List && Vars->Mon2D_Buffer) /* List: DETECTOR_OUT_2D */ + if (Vars->Flag_List) /* List */ { - - if (Vars->Flag_List >= 2) Vars->Buffer_Size = Vars->Neutron_Counter; - if (Vars->Buffer_Size >= Vars->Neutron_Counter) - Vars->Buffer_Size = Vars->Neutron_Counter; + if (Vars->List_Buffer.data) + { + List_Count = Vars->List_Buffer.count; + List_Data = Vars->List_Buffer.data; + } + else + { + List_Count = Vars->Buffer_Counter; + List_Data = Vars->Mon2D_Buffer; + } strcpy(fname,Vars->Mon_File); if (strchr(Vars->Mon_File,'.') == NULL) strcat(fname, "_list"); @@ -1772,11 +1831,26 @@ MCDETECTOR Monitor_nd_noaccSave(Monitornd_noaccDefines_type *DEFS, Monitornd_noa /* handle the type of list output */ strcpy(label, Vars->Monitor_Label); - detector = mcdetector_out_list( - label, "List of neutron events", Coord_X_Label, - -Vars->Buffer_Size, Vars->Coord_Number+1, - Vars->Mon2D_Buffer, - fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, Vars->option,Vars->compcurindex); + /* Serialize queued full chunks and the final partial chunk through one + collective session. The temporary tail node remains caller-owned. */ + final_chunk.data = List_Data; + final_chunk.count = List_Count; + final_chunk.next = NULL; + if (Vars->List_Chunks) { + Vars->List_Chunks_Tail->next = &final_chunk; + detector = mcevent_out_list_nd_chunks( + label, "List of neutron events", Coord_X_Label, + Vars->Coord_Number+1, Vars->List_Chunks, + fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, + Vars->option, Vars->compcurindex); + Vars->List_Chunks_Tail->next = NULL; + } else { + detector = mcevent_out_list_nd( + label, "List of neutron events", Coord_X_Label, + List_Count, Vars->Coord_Number+1, List_Data, + fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, + Vars->option, Vars->compcurindex); + } } if (Vars->Flag_Multiple) /* n1D: DETECTOR_OUT_1D */ { @@ -1989,6 +2063,9 @@ void Monitor_nd_noaccFinally(Monitornd_noaccDefines_type *DEFS, { /* Dim : (Vars->Coord_Number+1)*Vars->Buffer_Block matrix (for p, dp) */ if (Vars->Mon2D_Buffer != NULL) free(Vars->Mon2D_Buffer); } + mc_event_buffer_free(&Vars->List_Buffer); + mc_event_chunk_free(&Vars->List_Chunks); + Vars->List_Chunks_Tail = NULL; /* 1D and n1D case : Vars->Flag_Multiple */ if (Vars->Flag_Multiple && Vars->Coord_Number) diff --git a/mcstas-comps/share/monitor_nd_noacc-lib.h b/mcstas-comps/share/monitor_nd_noacc-lib.h index 6ecb1b7cde..e22a12b902 100644 --- a/mcstas-comps/share/monitor_nd_noacc-lib.h +++ b/mcstas-comps/share/monitor_nd_noacc-lib.h @@ -204,6 +204,9 @@ double **Mon2D_p; double **Mon2D_p2; double *Mon2D_Buffer; + MC_EVENT_BUFFER List_Buffer; + MC_EVENT_CHUNK *List_Chunks; + MC_EVENT_CHUNK *List_Chunks_Tail; unsigned long PixelID; double mxmin,mxmax,mymin,mymax,mzmin,mzmax; diff --git a/mcxtrace-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer_mcxtrace.instr b/mcxtrace-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer_mcxtrace.instr new file mode 100644 index 0000000000..001fd48386 --- /dev/null +++ b/mcxtrace-comps/examples/Tests_other/Test_EventBuffer/Test_EventBuffer_mcxtrace.instr @@ -0,0 +1,36 @@ +/******************************************************************************* +* McXtrace instrument definition +* +* Instrument: Test the fixed-capacity MC_EVENT_BUFFER API with McXtrace +* +* %Identification +* Written by: McCode development +* Date: 18 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* Minimal McXtrace companion for the flavor-neutral EventBuffer component +* (used by the McStas Test_EventBuffer.instr). It exercises both the explicit +* DETECTOR_OUT_LIST path and the mc_event_buffer_save helper; both must +* produce the same deterministic rows (row r, column c = r*10 + c). +* +* %Example: mcxtrace_test +* +* %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventBuffer_mcxtrace() + +TRACE + +COMPONENT src = Source_pt(focus_xw=0.1, focus_yh=0.1, dist=1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT evFill = EventBuffer(cap=4, ncols=3, nrows=6, filename="BufFill") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT evSave = EventBuffer(cap=4, ncols=3, nrows=6, filename="BufSave", + save_helper=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcxtrace-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple_mcxtrace.instr b/mcxtrace-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple_mcxtrace.instr new file mode 100644 index 0000000000..6e651f33c6 --- /dev/null +++ b/mcxtrace-comps/examples/Tests_other/Test_EventMonitorSimple/Test_EventMonitorSimple_mcxtrace.instr @@ -0,0 +1,29 @@ +/******************************************************************************* + * McXtrace instrument definition + * + * Instrument: Test_EventMonitorSimple_mcxtrace + * + * %Identification + * Written by: McCode development + * Date: 21 September 2026 + * Origin: DTU + * %INSTRUMENT_SITE: Tests_other + * + * %Description + * Focused fixture for the standard event-list output of Event_monitor_simple. + * + * %Example: mcxtrace_test + * + * %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventMonitorSimple_mcxtrace() + +TRACE + +COMPONENT src = Source_pt(focus_xw=0.1, focus_yh=0.1, dist=1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT events = Event_monitor_simple(nevents=3) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcxtrace-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mcxtrace.instr b/mcxtrace-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mcxtrace.instr new file mode 100644 index 0000000000..a9f5ace5fe --- /dev/null +++ b/mcxtrace-comps/examples/Tests_other/Test_EventOutList/Test_EventOutList_mcxtrace.instr @@ -0,0 +1,31 @@ +/******************************************************************************* +* McXtrace instrument definition +* +* Instrument: Test the generic event-list output API with McXtrace +* +* %Identification +* Written by: McCode development +* Date: 18 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* Minimal McXtrace companion for the flavor-neutral EventOutList component. +* The source is only needed to exercise McXtrace code generation; the fixture +* writes deterministic rows from SAVE. +* +* %Example: mcxtrace_test +* +* %End +*******************************************************************************/ +DEFINE INSTRUMENT Test_EventOutList_mcxtrace() + +TRACE + +COMPONENT src = Source_pt(focus_xw=0.1, focus_yh=0.1, dist=1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT events = EventOutList(nevents=3, ncols=3, filename="ListMulti") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcxtrace-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mcxtrace.instr b/mcxtrace-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mcxtrace.instr new file mode 100644 index 0000000000..ec1288ac26 --- /dev/null +++ b/mcxtrace-comps/examples/Tests_other/Test_EventWriter/Test_EventWriter_mcxtrace.instr @@ -0,0 +1,38 @@ +/******************************************************************************* +* McXtrace instrument definition +* +* Instrument: Test the bounded generic event writer with McXtrace +* +* %Identification +* Written by: McCode development +* Date: 28 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* McXtrace companion for the flavor-neutral EventWriter fixture. +* +* %Example: mcxtrace_test +* +* %End + *******************************************************************************/ +DEFINE INSTRUMENT Test_EventWriter_mcxtrace() + +TRACE + +COMPONENT src = Source_pt(focus_xw=0.1, focus_yh=0.1, dist=1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT append = EventWriter(nrows=11, chunk=3, filename="WriterAppend") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT write = EventWriter(nrows=8, chunk=2, filename="WriterWrite", use_write=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT direct = EventWriter(nrows=11, chunk=3, filename="WriterDirect", use_direct=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT live = EventWriter(nrows=11, chunk=2, filename="WriterStream", use_stream=1) + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcxtrace-comps/examples/Tests_other/Test_Monitor_nD_list/Test_Monitor_nD_list_mcxtrace.instr b/mcxtrace-comps/examples/Tests_other/Test_Monitor_nD_list/Test_Monitor_nD_list_mcxtrace.instr new file mode 100644 index 0000000000..e39b981fd7 --- /dev/null +++ b/mcxtrace-comps/examples/Tests_other/Test_Monitor_nD_list/Test_Monitor_nD_list_mcxtrace.instr @@ -0,0 +1,42 @@ +/******************************************************************************* +* McXtrace instrument definition +* +* Instrument: Test Monitor_nD's final list save (generic event API) in McXtrace +* +* %Identification +* Written by: McCode event-output work (Step 6) +* Date: 18 September 2026 +* Origin: DTU +* %INSTRUMENT_SITE: Tests_other +* +* %Description +* McXtrace companion for the McStas Test_Monitor_nD_list.instr. It exercises +* Monitor_nD's migrated final list save (mcevent_out_list_nd) for photons: a +* two-column list (x y list), a fixed-capacity overflow list (x y list=5), and +* list all (x y list all). Every list file must carry the McXtrace flavor +* header label "List of photon events" and the correct columns. +* +* %Example: NCount=1000 +* +* %End +*******************************************************************************/ +DEFINE INSTRUMENT Test_Monitor_nD_list_mcxtrace() + +TRACE + +COMPONENT src = Source_pt(focus_xw=0.1, focus_yh=0.1, dist=1, lambda0=1) + AT (0, 0, 0) ABSOLUTE + +COMPONENT ND_xy = Monitor_nD(xwidth=0.4, yheight=0.4, + filename="ND_xy", options="x y list") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT ND_ovf = Monitor_nD(xwidth=0.4, yheight=0.4, + filename="ND_ovf", options="x y list=5") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +COMPONENT ND_all = Monitor_nD(xwidth=0.4, yheight=0.4, + filename="ND_all", options="x y list all") + AT (0, 0, 0.1) RELATIVE PREVIOUS + +END diff --git a/mcxtrace-comps/monitors/Event_monitor_simple.comp b/mcxtrace-comps/monitors/Event_monitor_simple.comp index b299c5b606..ed568444a7 100644 --- a/mcxtrace-comps/monitors/Event_monitor_simple.comp +++ b/mcxtrace-comps/monitors/Event_monitor_simple.comp @@ -14,14 +14,13 @@ * Low-key event-monitor for debugging purposes. * * %D -* Simple, low-key event-monitor for debugging purposes. No propagation, -* no MPI support. Simply prints the event list to a log file in the SAVE section. -* The filename is "comp-instance".log +* Simple, low-key event-monitor for debugging purposes. No propagation and +* no MPI support; the saved rows use the standard McCode event-list path. * * %P * INPUT PARAMETERS: * -* nevents: [1] Number of events to store and print +* nevents: [1] Number of events to store and save * * %E *******************************************************************************/ @@ -32,24 +31,13 @@ SETTING PARAMETERS (nevents=1e6) DECLARE %{ DArray2d Events; - unsigned long Nevents; - char fullfile[1024]; - char outputdir[512]; + long Nevents; %} INITIALIZE %{ - Nevents = ceil (nevents); - Events = create_darr2d (Nevents, 11); - if (dirname == NULL) { - sprintf (outputdir, "."); - } else { - sprintf (outputdir, dirname); - } - #ifndef USE_MPI - sprintf (fullfile, "%s/%s.%s", outputdir, NAME_CURRENT_COMP, "log"); - #else - sprintf (fullfile, "%s/%s_%i.%s", outputdir, NAME_CURRENT_COMP, mpi_node_rank, "log"); - #endif + Nevents = (long)ceil (nevents); + if (Nevents < 0) Nevents = 0; + Events = Nevents ? create_darr2d (Nevents, 12) : NULL; %} TRACE @@ -57,57 +45,20 @@ TRACE unsigned long i = _particle->_uid; if (i < Nevents) { - #pragma acc atomic write - Events[i][0] = x; - #pragma acc atomic write - Events[i][1] = y; - #pragma acc atomic write - Events[i][2] = z; - #pragma acc atomic write - Events[i][3] = kx; - #pragma acc atomic write - Events[i][4] = ky; - #pragma acc atomic write - Events[i][5] = kz; - #pragma acc atomic write - Events[i][6] = t; - #pragma acc atomic write - Events[i][7] = Ex; - #pragma acc atomic write - Events[i][8] = Ey; - #pragma acc atomic write - Events[i][9] = Ez; - #pragma acc atomic write - Events[i][10] = p; + mc_event_particle_row(&Events[i][1], _particle); } %} SAVE %{ - FILE* fp; - printf ("Storing event logfile --> %s\n", fullfile); - fp = fopen (fullfile, "w+"); - if (!fp) { - fprintf (stderr, "Could not open file %s in w+ mode\n", fullfile); - exit (-1); - } - fprintf (fp, "# Event-file %s, contains %lu events\n", fullfile, Nevents); - fprintf (fp, "########################################### START EVENT LIST ##############################################\n"); - fprintf (fp, "# id x y z kx ky kz t Ex Ey Ez p # \n"); - fprintf (fp, "###########################################################################################################\n"); - unsigned long i; - int j; - for (i = 0; i < Nevents; i++) { - fprintf (fp, "%6lu ", i); - for (j = 0; j < 11; j++) { - fprintf (fp, "%8.2g ", Events[i][j]); - } - fprintf (fp, "\n"); - } - fprintf (fp, "###########################################################################################################\n"); - fprintf (fp, "############################################ END EVENT LIST! ##############################################\n"); - fclose (fp); - printf ("Done storing logfile.\n"); + long i; + for (i = 0; i < Nevents; i++) Events[i][0] = i; + DETECTOR_OUT_LIST("Event monitor events", + "id " MC_EVENT_PARTICLE_COLUMNS, + Nevents, MC_EVENT_PARTICLE_WIDTH + 1, + Nevents ? Events[0] : NULL, + NAME_CURRENT_COMP); + if (Nevents) destroy_darr2d (Events); %} MCDISPLAY diff --git a/mcxtrace-comps/share/monitor_nd-lib.c b/mcxtrace-comps/share/monitor_nd-lib.c index 825cf299d2..983bfd65db 100644 --- a/mcxtrace-comps/share/monitor_nd-lib.c +++ b/mcxtrace-comps/share/monitor_nd-lib.c @@ -689,10 +689,18 @@ void Monitor_nD_Init(MonitornD_Defines_type *DEFS, { Vars->Flag_Multiple = 1; /* default is n1D */ if (Vars->Coord_Number != Vars->Coord_NumberNoPixel) Vars->Flag_List = 1; } - /* list and auto limits case : Vars->Flag_List or Vars->Flag_Auto_Limits - * -> Buffer to flush and suppress after Vars->Flag_Auto_Limits - */ - if ((Vars->Flag_Auto_Limits || Vars->Flag_List) && Vars->Coord_Number) + /* The generic event buffer owns ordinary fixed-size lists. Keep the + * legacy buffer for auto-limit replay and list-all flushing. */ + Vars->Mon2D_Buffer = NULL; + Vars->List_Buffer.data = NULL; + Vars->List_Buffer.width = 0; + Vars->List_Buffer.capacity = 0; + Vars->List_Buffer.count = 0; + Vars->List_Buffer.next = 0; + Vars->List_Buffer.dropped = 0; + Vars->List_Chunks = NULL; + Vars->List_Chunks_Tail = NULL; + if ((Vars->Flag_Auto_Limits || Vars->Flag_List >= 2) && Vars->Coord_Number) { /* Dim : (Vars->Coord_Number+1)*Vars->Buffer_Block matrix (for p, dp) */ Vars->Mon2D_Buffer = (double *)malloc((Vars->Coord_Number+1)*Vars->Buffer_Block*sizeof(double)); if (Vars->Mon2D_Buffer == NULL) @@ -703,6 +711,16 @@ void Monitor_nD_Init(MonitornD_Defines_type *DEFS, } Vars->Buffer_Size = Vars->Buffer_Block; } + else if (Vars->Flag_List == 1 && Vars->Coord_Number) + { + if (mc_event_buffer_init(&Vars->List_Buffer, (long)Vars->Buffer_Block, + (long)(Vars->Coord_Number+1))) + { + printf("Monitor_nD: %s cannot allocate list event buffer (%li events). No list.\n", + Vars->compcurname, (long)Vars->Buffer_Block); + Vars->Flag_List = 0; + } + } /* 1D and n1D case : Vars->Flag_Multiple */ if (Vars->Flag_Multiple && Vars->Coord_NumberNoPixel) @@ -926,13 +944,35 @@ int Monitor_nD_Trace(MonitornD_Defines_type *DEFS, MonitornD_Variables_type *Var if ((Vars->Buffer_Counter >= Vars->Buffer_Block) && (Vars->Flag_List >= 2)) { if (Vars->Buffer_Size >= 1000000 || Vars->Flag_List == 3) - { /* save current (possibly append) and re-use Buffer */ - - Monitor_nD_Save(DEFS, Vars); - Vars->Flag_List = 3; - Vars->Buffer_Block = Vars->Buffer_Size; - Vars->Buffer_Counter = 0; - Vars->Photon_Counter = 0; + { /* detach current rows and re-use a bounded trace buffer */ + if (!Vars->Flag_Auto_Limits) { + double *filled = Vars->Mon2D_Buffer; + double *replacement = (double *)malloc((Vars->Coord_Number+1) + * Vars->Buffer_Size + * sizeof(double)); + if (!replacement + || mc_event_chunk_append(&Vars->List_Chunks, + &Vars->List_Chunks_Tail, + filled, (long)Vars->Buffer_Counter)) { + free(replacement); + printf("Monitor_nD: %s cannot queue filled event chunk. Stopping list output.\n", + Vars->compcurname); + Vars->Flag_List = 1; + } else { + Vars->Mon2D_Buffer = replacement; + Vars->Flag_List = 3; + Vars->Buffer_Block = Vars->Buffer_Size; + Vars->Buffer_Counter = 0; + Vars->Photon_Counter = 0; + } + } else { + /* Auto-limit replay still owns this scratch buffer until SAVE. */ + Monitor_nD_Save(DEFS, Vars); + Vars->Flag_List = 3; + Vars->Buffer_Block = Vars->Buffer_Size; + Vars->Buffer_Counter = 0; + Vars->Photon_Counter = 0; + } } else { @@ -1259,8 +1299,18 @@ int Monitor_nD_Trace(MonitornD_Defines_type *DEFS, MonitornD_Variables_type *Var } /* end (Vars->Flag_Auto_Limits != 1) */ if (Vars->Flag_Auto_Limits != 2 && !outsidebounds) /* not when reading auto limits Buffer */ - { /* now store Coord into Buffer (no index needed) if necessary (list or auto limits) */ - if ((Vars->Buffer_Counter < Vars->Buffer_Block) && ((Vars->Flag_List) || (Vars->Flag_Auto_Limits == 1))) + { /* store ordinary lists in the generic buffer; retain the legacy path + for auto limits and list-all mode */ + if (Vars->List_Buffer.data) + { + if (mc_event_buffer_append(&Vars->List_Buffer, Coord) + && Vars->Flag_Verbose && Vars->Flag_List == 1 + && Vars->List_Buffer.capacity > 0 + && Vars->List_Buffer.count >= Vars->List_Buffer.capacity) + printf("Monitor_nD: %s %li photons stored in List.\n", + Vars->compcurname, Vars->List_Buffer.count); + } + else if ((Vars->Buffer_Counter < Vars->Buffer_Block) && ((Vars->Flag_List) || (Vars->Flag_Auto_Limits == 1))) { double *Mon2D_Buffer = Vars->Mon2D_Buffer; for (i = 0; i <= Vars->Coord_Number; i++) @@ -1320,6 +1370,9 @@ MCDETECTOR Monitor_nD_Save(MonitornD_Defines_type *DEFS, MonitornD_Variables_typ double XY=0, pp=0; double Coord[MONnD_COORD_NMAX]; long Coord_Index[MONnD_COORD_NMAX]; + long List_Count; + double *List_Data; + MC_EVENT_CHUNK final_chunk; char label[CHAR_BUF_LENGTH]; double ratio; @@ -1481,12 +1534,18 @@ MCDETECTOR Monitor_nD_Save(MonitornD_Defines_type *DEFS, MonitornD_Variables_typ if (strlen(Vars->Mon_File) > 0) { fname = (char*)malloc(strlen(Vars->Mon_File)+10*Vars->Coord_Number); - if (Vars->Flag_List && Vars->Mon2D_Buffer) /* List: DETECTOR_OUT_2D */ + if (Vars->Flag_List) /* List */ { - - if (Vars->Flag_List >= 2) Vars->Buffer_Size = Vars->Photon_Counter; - if (Vars->Buffer_Size >= Vars->Photon_Counter) - Vars->Buffer_Size = Vars->Photon_Counter; + if (Vars->List_Buffer.data) + { + List_Count = Vars->List_Buffer.count; + List_Data = Vars->List_Buffer.data; + } + else + { + List_Count = Vars->Buffer_Counter; + List_Data = Vars->Mon2D_Buffer; + } strcpy(fname,Vars->Mon_File); if (strchr(Vars->Mon_File,'.') == NULL) strcat(fname, "_list"); @@ -1503,11 +1562,26 @@ MCDETECTOR Monitor_nD_Save(MonitornD_Defines_type *DEFS, MonitornD_Variables_typ /* handle the type of list output */ strcpy(label, Vars->Monitor_Label); - detector = mcdetector_out_list( - label, "List of photon events", Coord_X_Label, - -Vars->Buffer_Size, Vars->Coord_Number+1, - Vars->Mon2D_Buffer, - fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, Vars->option,Vars->compcurindex); + /* Serialize queued full chunks and the final partial chunk through one + collective session. The temporary tail node remains caller-owned. */ + final_chunk.data = List_Data; + final_chunk.count = List_Count; + final_chunk.next = NULL; + if (Vars->List_Chunks) { + Vars->List_Chunks_Tail->next = &final_chunk; + detector = mcevent_out_list_nd_chunks( + label, "List of photon events", Coord_X_Label, + Vars->Coord_Number+1, Vars->List_Chunks, + fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, + Vars->option, Vars->compcurindex); + Vars->List_Chunks_Tail->next = NULL; + } else { + detector = mcevent_out_list_nd( + label, "List of photon events", Coord_X_Label, + List_Count, Vars->Coord_Number+1, List_Data, + fname, Vars->compcurname, Vars->compcurpos, Vars->compcurrot, + Vars->option, Vars->compcurindex); + } } if (Vars->Flag_Multiple) /* n1D: DETECTOR_OUT_1D */ { @@ -1717,6 +1791,9 @@ void Monitor_nD_Finally(MonitornD_Defines_type *DEFS, { /* Dim : (Vars->Coord_Number+1)*Vars->Buffer_Block matrix (for p, dp) */ if (Vars->Mon2D_Buffer != NULL) free(Vars->Mon2D_Buffer); } + mc_event_buffer_free(&Vars->List_Buffer); + mc_event_chunk_free(&Vars->List_Chunks); + Vars->List_Chunks_Tail = NULL; /* 1D and n1D case : Vars->Flag_Multiple */ if (Vars->Flag_Multiple && Vars->Coord_Number) diff --git a/mcxtrace-comps/share/monitor_nd-lib.h b/mcxtrace-comps/share/monitor_nd-lib.h index 6b721ee8a0..ee66f097d2 100644 --- a/mcxtrace-comps/share/monitor_nd-lib.h +++ b/mcxtrace-comps/share/monitor_nd-lib.h @@ -193,6 +193,9 @@ double **Mon2D_p; double **Mon2D_p2; double *Mon2D_Buffer; + MC_EVENT_BUFFER List_Buffer; + MC_EVENT_CHUNK *List_Chunks; + MC_EVENT_CHUNK *List_Chunks_Tail; unsigned long PixelID; double mxmin,mxmax,mymin,mymax,mzmin,mzmax;