From 4cf199461f02621b14d45855e16c99ee8ef89f5e Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 08:08:23 +0700 Subject: [PATCH 01/17] fix census-based tally recombination --- CHANGELOG.md | 16 +++++++++ mcdc/main.py | 9 +++-- mcdc/output.py | 33 ++++++++++++++--- test/unit/test_output.py | 77 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 test/unit/test_output.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b8e0f152b..178dfcc11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +- Anticipate empty census-based tallies in batch runs for correct tally recombination, from [@ilhamv] + +### Security + ## [0.15.1] - 2026-08-12 ### Added diff --git a/mcdc/main.py b/mcdc/main.py index 7f017856a..e3c7ea1fd 100644 --- a/mcdc/main.py +++ b/mcdc/main.py @@ -54,8 +54,15 @@ def run_simulation(simulationPy: Simulation): time_simulation_start = MPI.Wtime() # Run simulation + import mcdc.output as output_module import mcdc.transport.simulation as simulation_module + # Prevent intermediate census tallies from a previous run from being recombined. + if settings.use_census_based_tally: + if master: + output_module.clear_census_based_tally_files(settings) + MPI.COMM_WORLD.Barrier() + if settings.neutron_eigenvalue_mode: simulation_module.eigenvalue_simulation(simulation_container, data) else: @@ -68,8 +75,6 @@ def run_simulation(simulationPy: Simulation): # Working on the output # ================================================================================== - import mcdc.output as output_module - # TIMER: output time_output_start = MPI.Wtime() diff --git a/mcdc/output.py b/mcdc/output.py index 5453ca25a..71dde4a9c 100644 --- a/mcdc/output.py +++ b/mcdc/output.py @@ -1,5 +1,7 @@ -import h5py import importlib.metadata +from pathlib import Path + +import h5py import numpy as np #### @@ -228,7 +230,7 @@ def generate_census_based_tally(mcdc, data): base_name = mcdc["settings"]["output_name"] # Create or get the file - file_name = f"{base_name}-batch_{idx_batch}-census_{idx_census}.h5" + file_name = census_based_tally_file_name(base_name, idx_batch, idx_census) file = h5py.File(file_name, "w") create_tally_dataset(file, mcdc, data) file.close() @@ -240,6 +242,20 @@ def replace_dataset(file, field, data): file.create_dataset(field, data=data) +def census_based_tally_file_name(base_name, idx_batch, idx_census): + """Return the intermediate tally path for one batch and time census.""" + return Path(f"{base_name}-batch_{idx_batch}-census_{idx_census}.h5") + + +def clear_census_based_tally_files(settings): + """Remove intermediate tallies that could otherwise leak across runs.""" + for idx_batch in range(settings.N_batch): + for idx_census in range(settings.N_census): + census_based_tally_file_name( + settings.output_name, idx_batch, idx_census + ).unlink(missing_ok=True) + + def recombine_tallies(simulationPy, simulation): """Combine census-based tally files into the main output file. @@ -274,7 +290,8 @@ def recombine_tallies(simulationPy, simulation): del main_file["tallies"] tally_group = main_file.create_group("tallies") - with h5py.File(f"{base_name}-batch_0-census_0.h5", "r") as reference_file: + reference_path = census_based_tally_file_name(base_name, 0, 0) + with h5py.File(reference_path, "r") as reference_file: for tally in simulationPy.tallies: name = f"tallies/{tally.name}" reference_file.copy(name, tally_group) @@ -310,7 +327,15 @@ def recombine_tallies(simulationPy, simulation): time_slice = tuple(time_slice) for i_batch in range(N_batch): - file_name = f"{base_name}-batch_{i_batch}-census_{i_census}.h5" + file_name = census_based_tally_file_name( + base_name, i_batch, i_census + ) + + # Empty particle banks end a batch before later census files are + # written. Those absent contributions are physically zero. + if not file_name.is_file(): + continue + with h5py.File(file_name, "r") as file: score_data = np.asarray( file[f"{score_name}/mean"][()] diff --git a/test/unit/test_output.py b/test/unit/test_output.py new file mode 100644 index 000000000..990d37555 --- /dev/null +++ b/test/unit/test_output.py @@ -0,0 +1,77 @@ +from types import SimpleNamespace + +import h5py +import numpy as np + +from mcdc.constant import SCORE_FLUX +from mcdc.output import clear_census_based_tally_files, recombine_tallies + + +def _write_census_tally(path, values): + with h5py.File(path, "w") as file: + tally = file.require_group("tallies/tracklength_tally_0") + tally.require_group("grid").create_dataset("time", data=[0.0, 1.0]) + score = tally.require_group("flux") + score.create_dataset("mean", data=values) + score.create_dataset("sdev", data=np.zeros_like(values)) + + +def test_clear_census_based_tally_files(tmp_path): + base_name = tmp_path / "output" + settings = SimpleNamespace(output_name=str(base_name), N_batch=2, N_census=3) + expected_files = [ + tmp_path / f"output-batch_{idx_batch}-census_{idx_census}.h5" + for idx_batch in range(settings.N_batch) + for idx_census in range(settings.N_census) + ] + for path in expected_files: + path.touch() + unrelated = tmp_path / "output-unrelated.h5" + unrelated.touch() + + clear_census_based_tally_files(settings) + + assert not any(path.exists() for path in expected_files) + assert unrelated.exists() + + +def test_recombine_tallies_zero_fills_censuses_missing_after_extinction(tmp_path): + base_name = tmp_path / "output" + settings = SimpleNamespace( + output_name=str(base_name), + use_census_based_tally=True, + census_tally_frequency=1, + census_time=np.array([1.0, 2.0, np.inf]), + N_batch=2, + N_census=3, + ) + tally = SimpleNamespace( + name="tracklength_tally_0", + scores=[SCORE_FLUX], + bin_shape=[1, 1, 1, 1, 2, 1], + ) + simulation_python = SimpleNamespace(settings=settings, tallies=[tally]) + simulation = {"mpi_master": True} + + with h5py.File(f"{base_name}.h5", "w"): + pass + + _write_census_tally(f"{base_name}-batch_0-census_0.h5", np.array([2.0, 4.0])) + _write_census_tally(f"{base_name}-batch_1-census_0.h5", np.array([4.0, 6.0])) + _write_census_tally(f"{base_name}-batch_1-census_1.h5", np.array([8.0, 10.0])) + + recombine_tallies(simulation_python, simulation) + + with h5py.File(f"{base_name}.h5", "r") as file: + tally_path = "tallies/tracklength_tally_0" + np.testing.assert_array_equal( + file[f"{tally_path}/grid/time"][:], [0.0, 1.0, 2.0] + ) + np.testing.assert_array_equal( + file[f"{tally_path}/flux/mean"][:], + [[3.0, 5.0], [4.0, 5.0]], + ) + np.testing.assert_array_equal( + file[f"{tally_path}/flux/sdev"][:], + [[1.0, 1.0], [4.0, 5.0]], + ) From 8ed6bd00f2cfa9d84612a562d327f1de104d7bcc Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 14 Aug 2026 10:58:41 +0700 Subject: [PATCH 02/17] fix concurrent simulation process issues --- mcdc/code_factory/gpu/program_builder.py | 28 +- mcdc/code_factory/numba_layers_generator.py | 302 ++++++++++++++------ mcdc/code_factory/rebuild_numba_support.py | 18 ++ mcdc/main.py | 8 - mcdc/numba_types.py | 53 +--- 5 files changed, 272 insertions(+), 137 deletions(-) create mode 100644 mcdc/code_factory/rebuild_numba_support.py diff --git a/mcdc/code_factory/gpu/program_builder.py b/mcdc/code_factory/gpu/program_builder.py index 961704044..3e5570c75 100644 --- a/mcdc/code_factory/gpu/program_builder.py +++ b/mcdc/code_factory/gpu/program_builder.py @@ -72,7 +72,31 @@ def adapt_transport_functions_post_setup(): alloc_device_bytes = None -def forward_declare_gpu_program(): +def prepare_gpu_program(simulation_dtype, data_size): + """Build shared GPU artifacts on rank zero before other ranks load them.""" + communicator = MPI.COMM_WORLD + master = communicator.Get_rank() == 0 + + if master: + _prepare_gpu_program(simulation_dtype, data_size) + + if communicator.Get_size() > 1: + communicator.Barrier() + + if not master: + _prepare_gpu_program(simulation_dtype, data_size) + + if communicator.Get_size() > 1: + communicator.Barrier() + + +def _prepare_gpu_program(simulation_dtype, data_size): + forward_declare_gpu_program(simulation_dtype) + adapt_transport_functions() + build_gpu_program(data_size) + + +def forward_declare_gpu_program(simulation_dtype): import harmonize import mcdc.numba_types as type_ @@ -97,7 +121,7 @@ def forward_declare_gpu_program(): # Main types: none, simulation structure, and simulation data none_type = nb.from_dtype(np.dtype([])) - simulation_type = nb.types.Array(nb.from_dtype(type_.simulation), (1,), "C") + simulation_type = nb.types.Array(nb.from_dtype(simulation_dtype), (1,), "C") data_type = nb.types.Array(nb.float64, 1, "C") # Set access functions diff --git a/mcdc/code_factory/numba_layers_generator.py b/mcdc/code_factory/numba_layers_generator.py index 47dac9ccd..4ad4c0fb2 100644 --- a/mcdc/code_factory/numba_layers_generator.py +++ b/mcdc/code_factory/numba_layers_generator.py @@ -6,7 +6,6 @@ import numba as nb import numpy as np -from mpi4py import MPI from numba import njit from numba.extending import intrinsic from pathlib import Path @@ -228,10 +227,6 @@ def generate_numba_layers(simulation): structure_order, ) - # Generate the accessor helper - if MPI.COMM_WORLD.Get_rank() == 0: - generate_mcdc_access(accessor_targets) - # Add ID for non-singleton for class_ in mcdc_classes: if issubclass(class_, MCDCObject): @@ -337,103 +332,28 @@ def generate_numba_layers(simulation): structures["simulation"] = new_structure + structures["simulation"] - # Print the fields - if MPI.COMM_WORLD.Get_rank() == 0: - with open(f"{Path(mcdc.__file__).parent}/numba_types.py", "w") as f: - text = ( - "# The following is automatically generated by " - "numba_layers_generator.py\n\n" - ) - text += "from numpy import bool_\n" - text += "from numpy import float64\n" - text += "from numpy import int64\n" - text += "from numpy import uint64\n" - text += "from numpy import uintp\n" - text += "\n###\n\n" - text += ( - "from mcdc.code_factory.numba_layers_generator import into_dtype\n\n" - ) - - for label in structure_order: - # Skip special types - if label in ["gpu_meta"] + bank_names + ["simulation"]: - continue - - text += f"{label} = into_dtype([\n" - structure = structures[label] - for item in structure: - text += decode_structure_item(item) - text += "])\n\n" - - # GPU meta - text += "gpu_meta = into_dtype([\n" - for item in structures["gpu_meta"]: - if item[0].endswith("pointer"): - text += f" ('{item[0]}', uintp),\n" - else: - text += decode_structure_item(item) - text += "])\n\n" - - # Particle banks - for label in bank_names: - structure = structures[label] - - text += f"{label} = None\n" - text += f"def set_{label}(N: dict):\n" - text += f" global {label}\n" - text += f" {label} = into_dtype([\n" - for item in structure: - if item[0] == "particle_data": - text += ( - f" ('{item[0]}', {item[0]}, (N['{item[0]}'],)),\n" - ) - else: - text += decode_structure_item(item, " ") - text += " ])\n\n" - - # Simulation - text += f"simulation = None\n" - text += f"def set_simulation(N: dict):\n" - text += f" global simulation\n" - text += f" simulation = into_dtype([\n" - for item in structures["simulation"]: - if type(item[1]) == np.dtypes.VoidDType and len(item) == 3: - singular_field = plural_to_singular(item[0]) - text += f" ('{item[0]}', {singular_field}, (N['{singular_field}'])),\n" - else: - text += decode_structure_item(item, " ") - text += " ])\n\n" - - f.write(text) - # ================================================================================== - # Set numba_types.py + # Build the problem-dependent Numba types # ================================================================================== import mcdc.numba_types as type_ - # Particle banks - type_.set_bank_active({"particle_data": simulation.bank_active.size[0]}) - type_.set_bank_census({"particle_data": simulation.bank_census.size[0]}) - type_.set_bank_source({"particle_data": simulation.bank_source.size[0]}) - type_.set_bank_future({"particle_data": simulation.bank_future.size[0]}) - - # Simulation N = {} + for name in bank_names: + bank_name = name.removeprefix("bank_") + N[f"{bank_name}_particle"] = int(getattr(simulation, name).size[0]) for item in structures["simulation"]: if type(item[1]) == np.dtypes.VoidDType and len(item) == 3: singular_field = plural_to_singular(item[0]) N[singular_field] = item[2] - type_.set_simulation(N) + simulation_dtype = type_.make_simulation_type(N) # ================================================================================== # GPU preparation: Adapt transport functions, forward declare, and build program # ================================================================================== if config.target == "gpu": - gpu_builder.forward_declare_gpu_program() - gpu_builder.adapt_transport_functions() - gpu_builder.build_gpu_program(data["size"]) + gpu_builder.prepare_gpu_program(simulation_dtype, data["size"]) # ================================================================================== # Allocate the flattened data and re-set the objects @@ -452,7 +372,7 @@ def generate_numba_layers(simulation): # The global structure/variable container mcdc_simulation_container, mcdc_simulation_pointer = create_simulation_container( - into_dtype(structures["simulation"]) + simulation_dtype ) mcdc_simulation = mcdc_simulation_container[0] @@ -1507,3 +1427,211 @@ def decode_structure_item(item, prefix=""): return f"{prefix} ('{item[0]}', {plural_to_singular(item[0])}, {item[2]}),\n" else: return f"{prefix} ('{item[0]}', {item[0]}),\n" + + +def build_structures(): + """Build the static dtype structures directly from the object annotations.""" + annotations = {} + structures = {} + accessor_targets = {} + + for mcdc_class in mcdc_classes: + annotations[mcdc_class.label] = {} + structures[mcdc_class.label] = [] + accessor_targets[mcdc_class.label] = [] + + for name in bank_names: + annotations[name] = {} + structures[name] = [] + accessor_targets[name] = [] + + # Simulation depends on all other structures, so build it last. + annotations["simulation"] = annotations.pop("simulation") + structures["simulation"] = structures.pop("simulation") + accessor_targets["simulation"] = accessor_targets.pop("simulation") + + for mcdc_class in mcdc_classes: + classes = [] + for item in mcdc_class.__mro__: + if item in base_classes: + break + classes.append(item) + + if issubclass(mcdc_class, MCDCPolymorphic): + classes = [mcdc_class] + + for class_ in classes: + new_annotations = { + key: value + for key, value in class_.__annotations__.items() + if key not in ["label", "non_numba"] + and ("non_numba" not in dir(class_) or key not in class_.non_numba) + } + if new_annotations and isinstance( + next(iter(new_annotations.values())), str + ): + new_annotations = parse_annotations_dict(new_annotations) + annotations[mcdc_class.label].update(new_annotations) + + for name in bank_names: + annotations[name] = { + key: value + for key, value in ParticleBank.__annotations__.items() + if key not in ["label", "non_numba"] + and ( + "non_numba" not in dir(ParticleBank) + or key not in ParticleBank.non_numba + ) + } + + simulation_object_structure = [] + for field, hint in annotations["simulation"].items(): + hint_origin = get_origin(hint) + hint_args = get_args(hint) + if hint in all_classes: + simulation_object_structure.append((field, hint)) + elif hint_origin == list and hint_args[0] in all_classes: + simulation_object_structure.append((field, list, hint_args[0])) + + structure_order = [] + completed_structures = set() + active_structures = set() + for label in annotations: + set_structure( + label, + structures, + accessor_targets, + annotations, + completed_structures, + active_structures, + structure_order, + ) + + for class_ in mcdc_classes: + if issubclass(class_, MCDCObject): + structures[class_.label].append(("ID", type_map[int])) + if issubclass(class_, MCDCPolymorphic): + if class_ in polymorphic_bases: + structures[class_.label].append(("sub_type", type_map[int])) + structures[class_.label].append(("sub_ID", type_map[int])) + else: + structures[class_.label].append(("base_ID", type_map[int])) + + # The generated bank and collection setters substitute their actual runtime sizes. + placeholder_size = (1,) + for name in bank_names: + structures[name].append( + ( + "particle_data", + into_dtype(structures["particle_data"]), + placeholder_size, + ) + ) + structures["simulation"] = [(name, into_dtype(structures[name]))] + structures[ + "simulation" + ] + + collection_structures = [] + for item in simulation_object_structure: + field = item[0] + if item[1] == list: + base_class = item[2] + if base_class not in polymorphic_bases: + collection_structures.append( + ( + field, + into_dtype(structures[base_class.label]), + placeholder_size, + ) + ) + collection_structures.append( + (f"N_{plural_to_singular(field)}", type_map[int]) + ) + else: + for class_ in mcdc_classes: + if issubclass(class_, base_class): + collection_structures.append( + ( + singular_to_plural(class_.label), + into_dtype(structures[class_.label]), + placeholder_size, + ) + ) + collection_structures.append( + (f"N_{class_.label}", type_map[int]) + ) + elif item[1] in mcdc_classes and issubclass(item[1], MCDCBase): + collection_structures.append((field, into_dtype(structures[item[1].label]))) + + structures["simulation"] = collection_structures + structures["simulation"] + return structures, accessor_targets, structure_order + + +def generate_numba_types(structures, structure_order): + text = "# The following is automatically generated by rebuild_numba_support.py\n\n" + text += "from numpy import bool_\n" + text += "from numpy import float64\n" + text += "from numpy import int64\n" + text += "from numpy import uint64\n" + text += "from numpy import uintp\n" + text += "\n###\n\n" + text += "from mcdc.code_factory.numba_layers_generator import into_dtype\n\n" + + for label in structure_order: + if label in ["gpu_meta"] + bank_names + ["simulation"]: + continue + text += f"{label} = into_dtype([\n" + for item in structures[label]: + text += decode_structure_item(item) + text += "])\n\n" + + text += "gpu_meta = into_dtype([\n" + for item in structures["gpu_meta"]: + if item[0].endswith("pointer"): + text += f" ('{item[0]}', uintp),\n" + else: + text += decode_structure_item(item) + text += "])\n\n" + + text += "# ======================================================================================\n" + text += "# Problem-dependent Numba type factories\n" + text += "# ======================================================================================\n\n" + + text += "def make_bank_type(particle_capacity: int):\n" + text += " return into_dtype([\n" + for item in structures[bank_names[0]]: + if item[0] == "particle_data": + text += " ('particle_data', particle_data, (particle_capacity,)),\n" + else: + text += decode_structure_item(item, " ") + text += " ])\n\n" + + text += "def make_simulation_type(N: dict):\n" + text += " return into_dtype([\n" + for item in structures["simulation"]: + if item[0] in bank_names: + bank_name = item[0].removeprefix("bank_") + text += ( + f" ('{item[0]}', " + f"make_bank_type(N['{bank_name}_particle'])),\n" + ) + elif type(item[1]) == np.dtypes.VoidDType and len(item) == 3: + singular_field = plural_to_singular(item[0]) + text += ( + f" ('{item[0]}', {singular_field}, " + f"(N['{singular_field}'])),\n" + ) + else: + text += decode_structure_item(item, " ") + text += " ])\n\n" + + (Path(mcdc.__file__).parent / "numba_types.py").write_text(text, encoding="utf-8") + + +def rebuild_numba_support(): + structures, accessor_targets, structure_order = build_structures() + generate_mcdc_access(accessor_targets) + generate_numba_types(structures, structure_order) + print( + f"Numba support (mcdc_get, mcdc_set, and numba_types.py)\n is generated in {Path(mcdc.__file__).parent}" + ) diff --git a/mcdc/code_factory/rebuild_numba_support.py b/mcdc/code_factory/rebuild_numba_support.py new file mode 100644 index 000000000..5488a1a65 --- /dev/null +++ b/mcdc/code_factory/rebuild_numba_support.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +# Allow this file to be run directly from any working directory. +if __package__ in (None, ""): + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + + +def main(): + from mcdc.code_factory.numba_layers_generator import rebuild_numba_support + + rebuild_numba_support() + + +if __name__ == "__main__": + main() diff --git a/mcdc/main.py b/mcdc/main.py index e3c7ea1fd..efdc49d7a 100644 --- a/mcdc/main.py +++ b/mcdc/main.py @@ -134,14 +134,6 @@ def prepare(simulationPy: Simulation): simulation_container, data = generate_numba_layers(simulationPy) simulation = simulation_container[0] - # Reload mcdc getters and setters - import importlib - import mcdc.mcdc_get as mcdc_get - import mcdc.mcdc_set as mcdc_set - - importlib.reload(mcdc_get) - importlib.reload(mcdc_set) - # Pick Python-version RNG if needed import mcdc.config as config import mcdc.transport.rng as rng diff --git a/mcdc/numba_types.py b/mcdc/numba_types.py index 33d5a3b42..3a699ac91 100644 --- a/mcdc/numba_types.py +++ b/mcdc/numba_types.py @@ -1,4 +1,4 @@ -# The following is automatically generated by numba_layers_generator.py +# The following is automatically generated by rebuild_numba_support.py from numpy import bool_ from numpy import float64 @@ -718,46 +718,19 @@ ('data_pointer', uintp), ]) -bank_active = None -def set_bank_active(N: dict): - global bank_active - bank_active = into_dtype([ - ('size', int64, (1,)), - ('tag', 'U32'), - ('particle_data', particle_data, (N['particle_data'],)), - ]) - -bank_census = None -def set_bank_census(N: dict): - global bank_census - bank_census = into_dtype([ - ('size', int64, (1,)), - ('tag', 'U32'), - ('particle_data', particle_data, (N['particle_data'],)), - ]) - -bank_source = None -def set_bank_source(N: dict): - global bank_source - bank_source = into_dtype([ - ('size', int64, (1,)), - ('tag', 'U32'), - ('particle_data', particle_data, (N['particle_data'],)), - ]) +# ====================================================================================== +# Problem-dependent Numba type factories +# ====================================================================================== -bank_future = None -def set_bank_future(N: dict): - global bank_future - bank_future = into_dtype([ +def make_bank_type(particle_capacity: int): + return into_dtype([ ('size', int64, (1,)), ('tag', 'U32'), - ('particle_data', particle_data, (N['particle_data'],)), + ('particle_data', particle_data, (particle_capacity,)), ]) -simulation = None -def set_simulation(N: dict): - global simulation - simulation = into_dtype([ +def make_simulation_type(N: dict): + return into_dtype([ ('data', data, (N['data'])), ('N_data', int64), ('none_data', none_data, (N['none_data'])), @@ -843,10 +816,10 @@ def set_simulation(N: dict): ('settings', settings), ('technique', technique), ('gpu_meta', gpu_meta), - ('bank_future', bank_future), - ('bank_source', bank_source), - ('bank_census', bank_census), - ('bank_active', bank_active), + ('bank_future', make_bank_type(N['future_particle'])), + ('bank_source', make_bank_type(N['source_particle'])), + ('bank_census', make_bank_type(N['census_particle'])), + ('bank_active', make_bank_type(N['active_particle'])), ('name', 'U32'), ('idx_work', int64), ('idx_cycle', int64), From b571b13637b3ab1c897aa1803fbaf84c42a04c2b Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 14 Aug 2026 11:11:49 +0700 Subject: [PATCH 03/17] let pycache handled by CPython --- mcdc/config.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mcdc/config.py b/mcdc/config.py index 7af96732f..01483806e 100644 --- a/mcdc/config.py +++ b/mcdc/config.py @@ -165,10 +165,9 @@ def _manage_runtime_caches() -> None: """Clear generated-code caches when caching is disabled or reset.""" should_clear = not caching or clear_cache if should_clear and MPI.COMM_WORLD.Get_rank() == 0: - cache_directories = ( - Path(__file__).resolve().parent / "__pycache__", - Path.cwd() / "__harmonize_cache__", - ) + # Python manages concurrent __pycache__ writes atomically. Removing that + # shared directory here can race with independent batch launches. + cache_directories = (Path.cwd() / "__harmonize_cache__",) for cache_directory in cache_directories: if cache_directory.exists(): shutil.rmtree(cache_directory) From 7bca245314250ec14eee2ba63c24338748c53a44 Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 14 Aug 2026 11:14:15 +0700 Subject: [PATCH 04/17] update test --- test/unit/test_config.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/unit/test_config.py b/test/unit/test_config.py index 210d81150..2ac19dff8 100644 --- a/test/unit/test_config.py +++ b/test/unit/test_config.py @@ -1,9 +1,36 @@ +from pathlib import Path +from types import SimpleNamespace + import mcdc import mcdc.config as config from mcdc.config import override_settings +class SingleRankCommunicator: + def Get_rank(self): + return 0 + + def Get_size(self): + return 1 + + +def test_cache_cleanup_does_not_remove_python_cache(monkeypatch): + removed = [] + communicator = SingleRankCommunicator() + + monkeypatch.setattr(config, "MPI", SimpleNamespace(COMM_WORLD=communicator)) + monkeypatch.setattr(config, "caching", False) + monkeypatch.setattr(config, "clear_cache", False) + monkeypatch.setattr(config.Path, "exists", lambda path: True) + monkeypatch.setattr(config.shutil, "rmtree", removed.append) + + config._manage_runtime_caches() + + assert removed == [Path.cwd() / "__harmonize_cache__"] + assert all(path.name != "__pycache__" for path in removed) + + def test_compilation_applies_command_line_overrides(monkeypatch): simulation = mcdc.Simulation() simulation.set_model([mcdc.Cell()]) From de012cb0d0b5cd0eaa8bec4c07e25060e0bff7ae Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 14 Aug 2026 11:34:39 +0700 Subject: [PATCH 05/17] make the automatic rebuild option --- mcdc/config.py | 27 +++++++++++++++++++++++++++ mcdc/main.py | 5 ++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/mcdc/config.py b/mcdc/config.py index 01483806e..686412bd6 100644 --- a/mcdc/config.py +++ b/mcdc/config.py @@ -45,6 +45,12 @@ def _build_parser() -> argparse.ArgumentParser: parser.add_argument("--clear_cache", action="store_true") parser.add_argument("--caching", action="store_true", default=False) parser.add_argument("--no_caching", dest="caching", action="store_false") + parser.add_argument( + "-r", + "--rebuild", + action="store_true", + help="Rebuild generated Numba support before runtime preparation.", + ) # GPU execution parser.add_argument( @@ -106,6 +112,8 @@ def _build_parser() -> argparse.ArgumentParser: caching = args.caching clear_cache = args.clear_cache +_numba_support_rebuilt = False + # ====================================================================================== # Simulation-setting overrides @@ -156,6 +164,25 @@ def set_setting(name, value): return changed +def rebuild_numba_support_if_requested() -> None: + """Rebuild generated Numba support once when explicitly requested.""" + global _numba_support_rebuilt + + if not args.rebuild or _numba_support_rebuilt: + return + + communicator = MPI.COMM_WORLD + if communicator.Get_rank() == 0: + from mcdc.code_factory.numba_layers_generator import rebuild_numba_support + + rebuild_numba_support() + + if communicator.Get_size() > 1: + communicator.Barrier() + + _numba_support_rebuilt = True + + # ====================================================================================== # Process-wide initialization # ====================================================================================== diff --git a/mcdc/main.py b/mcdc/main.py index efdc49d7a..a80218bf4 100644 --- a/mcdc/main.py +++ b/mcdc/main.py @@ -126,6 +126,10 @@ def prepare(simulationPy: Simulation): # Generate Numba runtime layers # ================================================================================== + import mcdc.config as config + + config.rebuild_numba_support_if_requested() + from mcdc.code_factory.numba_layers_generator import generate_numba_layers from mcdc.code_factory.literals_generator import make_literals @@ -135,7 +139,6 @@ def prepare(simulationPy: Simulation): simulation = simulation_container[0] # Pick Python-version RNG if needed - import mcdc.config as config import mcdc.transport.rng as rng if config.mode == "python": From b4c1cdc9d6b88892415ccc3034b936a1354ad190 Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 14 Aug 2026 20:00:23 +0700 Subject: [PATCH 06/17] move rebuild trigger ahead. consolidate calls --- mcdc/__init__.py | 6 ++++++ mcdc/config.py | 14 +++++--------- mcdc/main.py | 5 +---- mcdc/print_.py | 4 ++-- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/mcdc/__init__.py b/mcdc/__init__.py index 603e69feb..105cb0bcd 100644 --- a/mcdc/__init__.py +++ b/mcdc/__init__.py @@ -32,3 +32,9 @@ __version__: str = _version("mcdc") except _PackageNotFoundError: __version__ = "unknown" + + +# Evaluate developer options +import mcdc.config as _config + +_config.rebuild_numba_support_if_requested() diff --git a/mcdc/config.py b/mcdc/config.py index 686412bd6..ef6e7a266 100644 --- a/mcdc/config.py +++ b/mcdc/config.py @@ -49,7 +49,9 @@ def _build_parser() -> argparse.ArgumentParser: "-r", "--rebuild", action="store_true", - help="Rebuild generated Numba support before runtime preparation.", + help=( + "Rebuild generated Numba support " "(for active object-model development)." + ), ) # GPU execution @@ -112,8 +114,6 @@ def _build_parser() -> argparse.ArgumentParser: caching = args.caching clear_cache = args.clear_cache -_numba_support_rebuilt = False - # ====================================================================================== # Simulation-setting overrides @@ -165,10 +165,8 @@ def set_setting(name, value): def rebuild_numba_support_if_requested() -> None: - """Rebuild generated Numba support once when explicitly requested.""" - global _numba_support_rebuilt - - if not args.rebuild or _numba_support_rebuilt: + """Rebuild generated Numba support during package initialization.""" + if not args.rebuild: return communicator = MPI.COMM_WORLD @@ -180,8 +178,6 @@ def rebuild_numba_support_if_requested() -> None: if communicator.Get_size() > 1: communicator.Barrier() - _numba_support_rebuilt = True - # ====================================================================================== # Process-wide initialization diff --git a/mcdc/main.py b/mcdc/main.py index a80218bf4..efdc49d7a 100644 --- a/mcdc/main.py +++ b/mcdc/main.py @@ -126,10 +126,6 @@ def prepare(simulationPy: Simulation): # Generate Numba runtime layers # ================================================================================== - import mcdc.config as config - - config.rebuild_numba_support_if_requested() - from mcdc.code_factory.numba_layers_generator import generate_numba_layers from mcdc.code_factory.literals_generator import make_literals @@ -139,6 +135,7 @@ def prepare(simulationPy: Simulation): simulation = simulation_container[0] # Pick Python-version RNG if needed + import mcdc.config as config import mcdc.transport.rng as rng if config.mode == "python": diff --git a/mcdc/print_.py b/mcdc/print_.py index 17d319bf9..6b5773304 100644 --- a/mcdc/print_.py +++ b/mcdc/print_.py @@ -6,8 +6,6 @@ from colorama import Fore, Style from mpi4py import MPI -import mcdc.mcdc_get as mcdc_get - _IS_MASTER = MPI.COMM_WORLD.Get_rank() == 0 @@ -167,6 +165,8 @@ def print_progress_eigenvalue(simulation, data): if not _IS_MASTER: return + import mcdc.mcdc_get as mcdc_get + index = simulation["idx_cycle"] k_effective = simulation["k_eff"] k_average = simulation["k_avg_running"] From 3d7671f5cc415e985fc728a6d49fd0acb05b897f Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Fri, 14 Aug 2026 20:03:10 +0700 Subject: [PATCH 07/17] move rebuild trigger ahead. consolidate calls --- test/unit/test_config.py | 72 ++++++++++++++++++++++++++++++++++++++-- test/unit/test_print.py | 21 ++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/test/unit/test_config.py b/test/unit/test_config.py index 2ac19dff8..74636bdd6 100644 --- a/test/unit/test_config.py +++ b/test/unit/test_config.py @@ -1,18 +1,86 @@ from pathlib import Path +import subprocess +import sys from types import SimpleNamespace import mcdc import mcdc.config as config +import mcdc.code_factory.numba_layers_generator as numba_layers_generator -from mcdc.config import override_settings +from mcdc.config import _build_parser, override_settings class SingleRankCommunicator: + def __init__(self, size=1): + self.size = size + self.barrier_count = 0 + def Get_rank(self): return 0 def Get_size(self): - return 1 + return self.size + + def Barrier(self): + self.barrier_count += 1 + + +def test_parser_accepts_numba_support_rebuild(): + parser = _build_parser() + + assert parser.parse_args(["--rebuild"]).rebuild + assert parser.parse_args(["-r"]).rebuild + + +def test_package_import_calls_configured_rebuild_gate(): + check_trigger = """ +import sys +import types + +config = types.ModuleType("mcdc.config") +config.rebuild_numba_support_if_requested = lambda: print("rebuild-gate-called") +sys.modules["mcdc.config"] = config + +import mcdc +""" + + result = subprocess.run( + [sys.executable, "-c", check_trigger], + check=True, + capture_output=True, + text=True, + ) + + assert "rebuild-gate-called" in result.stdout + + +def test_requested_numba_support_rebuild_runs_on_master(monkeypatch): + rebuild_count = 0 + communicator = SingleRankCommunicator(size=2) + + def rebuild(): + nonlocal rebuild_count + rebuild_count += 1 + + monkeypatch.setattr(config, "MPI", SimpleNamespace(COMM_WORLD=communicator)) + monkeypatch.setattr(config.args, "rebuild", True) + monkeypatch.setattr(numba_layers_generator, "rebuild_numba_support", rebuild) + + config.rebuild_numba_support_if_requested() + + assert rebuild_count == 1 + assert communicator.barrier_count == 1 + + +def test_unrequested_numba_support_rebuild_does_nothing(monkeypatch): + communicator = SingleRankCommunicator(size=2) + + monkeypatch.setattr(config, "MPI", SimpleNamespace(COMM_WORLD=communicator)) + monkeypatch.setattr(config.args, "rebuild", False) + + config.rebuild_numba_support_if_requested() + + assert communicator.barrier_count == 0 def test_cache_cleanup_does_not_remove_python_cache(monkeypatch): diff --git a/test/unit/test_print.py b/test/unit/test_print.py index 3b50de23d..aa4409851 100644 --- a/test/unit/test_print.py +++ b/test/unit/test_print.py @@ -1,9 +1,30 @@ +import subprocess +import sys + import numpy as np import pytest import mcdc.print_ as print_module +def test_import_mcdc_does_not_import_generated_numba_support(): + check_imports = """ +import sys +import mcdc + +generated = [ + name + for name in sys.modules + if name == "mcdc.numba_types" + or name.startswith("mcdc.mcdc_get") + or name.startswith("mcdc.mcdc_set") +] +assert not generated, generated +""" + + subprocess.run([sys.executable, "-c", check_imports], check=True) + + def test_print_1d_array(): assert print_module.print_1d_array(np.array([])) == "(size=0): []" assert print_module.print_1d_array(np.array([1.0, 2.0])) == "(size=2): [1, 2]" From 59637dd0c8fed643fff9500d2bc68288c7bc0b46 Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 07:15:39 +0700 Subject: [PATCH 08/17] update docs --- docs/source/contributing/index.rst | 10 +++- .../developer_guide/architecture/index.rst | 23 +++++--- .../architecture/runtime_data_layout.rst | 53 +++++++++++++++++++ .../extending/extending_the_object_model.rst | 43 ++++++++++++--- 4 files changed, 113 insertions(+), 16 deletions(-) diff --git a/docs/source/contributing/index.rst b/docs/source/contributing/index.rst index 78ba61aaf..c88c782cc 100644 --- a/docs/source/contributing/index.rst +++ b/docs/source/contributing/index.rst @@ -117,7 +117,11 @@ In MC/DC the simulation functions (in ``mcdc/transport/simulation.py``) can be c Caching behavior is controlled via the ``--caching`` and ``--clear_cache`` command-line flags. To disable caching, omit the ``--caching`` flag (the default). -Alternatively a developer could delete the ``__pycache__`` directory or other cache directory which is system dependent (`see more about clearing the numba cache `_) +Python manages its own ``__pycache__`` directories, and MC/DC does not delete +them during startup. This allows independent batch launches to safely import +MC/DC from the same installation. If manual cache removal is necessary, ensure +that no running job is using the affected cache (`see more about clearing the +Numba cache `_). MC/DC may eventually enable `Numba's ahead-of-time compilation capabilities `_. @@ -142,6 +146,10 @@ Common input-related locations include: #. ``mcdc/object_/technique.py`` — variance reduction techniques #. ``mcdc/config.py`` — command-line argument definitions +Changes to runtime-visible fields in the object model also require rebuilding +the generated Numba support. Follow :ref:`rebuilding_numba_support` for the +command, edit-test shortcut, and concurrency constraint. + ------- Testing ------- diff --git a/docs/source/developer_guide/architecture/index.rst b/docs/source/developer_guide/architecture/index.rst index f2c2cd9fa..1fa12857f 100644 --- a/docs/source/developer_guide/architecture/index.rst +++ b/docs/source/developer_guide/architecture/index.rst @@ -75,14 +75,17 @@ Paths in the component column are relative to the top-level ``mcdc/`` package. - Runtime preparation - Derive and expose simulation-specific values that compiled transport requires as literals. * - ``code_factory/numba_layers_generator.py`` - - Runtime preparation - - Derives structured dtypes, packs runtime state, generates accessors, and initiates GPU-specific preparation when requested. + - Support generation and runtime preparation + - Generates the shared Numba support and derives problem-dependent dtypes and prepared runtime state. + * - ``numba_types.py``, ``mcdc_get/``, and ``mcdc_set/`` + - Generated Numba support + - Define dtypes for the shared runtime schema, pure factories for problem-dependent dtypes, and accessors for variable-length fields in ``data``. + * - ``code_factory/rebuild_numba_support.py`` + - Development-time generation + - Runs the support generator after changes to the object model or generation logic. * - Runtime ``simulation`` and ``data`` - - Prepared runtime data + - Prepared runtime state - Store fixed-layout state and variable-length numerical data generated by ``numba_layers_generator.py``. - * - ``mcdc_get/`` and ``mcdc_set/`` - - Runtime data access - - Provide generated access to variable-length fields stored in ``data``. * - ``transport/`` - Shared transport - Implements the particle-transport algorithms used by every execution mode. @@ -95,8 +98,12 @@ Paths in the component column are relative to the top-level ``mcdc/`` package. The ``mcdc/object_`` modules, :class:`mcdc.Simulation`, and ``python_objects_compiler.py`` implement the model-definition and simulation-compilation stages. :doc:`simulation_compilation` explains their relationships, while :doc:`../extending/extending_the_object_model` explains how contributors can extend them. -``main.prepare``, ``numba_layers_generator.py``, runtime ``simulation`` and ``data``, and generated ``mcdc_get`` and ``mcdc_set`` implement framework-level runtime preparation and form the data boundary between model compilation and transport. -:doc:`runtime_data_layout` explains their roles. +``main.prepare`` and ``numba_layers_generator.py`` use the generated Numba +support to create the runtime ``simulation`` and ``data`` objects that form the +data boundary between model compilation and transport. +:doc:`runtime_data_layout` explains the complete representation, +:ref:`generated_numba_support` distinguishes its three lifetimes, and +:ref:`rebuilding_numba_support` gives the object model development workflow. The ``mcdc/transport`` package implements the shared-transport stage. :doc:`transport_execution` explains how the execution modes run it. diff --git a/docs/source/developer_guide/architecture/runtime_data_layout.rst b/docs/source/developer_guide/architecture/runtime_data_layout.rst index 01f5b331c..e2e93e9f8 100644 --- a/docs/source/developer_guide/architecture/runtime_data_layout.rst +++ b/docs/source/developer_guide/architecture/runtime_data_layout.rst @@ -187,6 +187,59 @@ Packing is performed in two passes: The structured ``simulation`` dtype can then be finalized because collection sizes, particle-bank sizes, and nested record types are known. +.. _generated_numba_support: + +Generated Numba Support and Problem-Dependent Dtypes +---------------------------------------------------- + +The derived layout feeds artifacts with three different lifetimes: + +Generated Numba support + ``mcdc/numba_types.py`` and the modules under ``mcdc/mcdc_get`` and + ``mcdc/mcdc_set`` describe the runtime schema developed in the preceding + sections. These generated source files are shared by every simulation using + that MC/DC source tree. They change with the object model or Numba support + generator, not with an input problem. + +Problem-dependent dtypes + Each call to ``mcdc.main.prepare`` derives collection lengths, + particle-bank capacities, and other sizes from one compiled model. Pure + factories in ``mcdc.numba_types`` use those sizes to return simulation and + particle-bank dtypes local to that preparation. The factories do not + install the returned dtypes in shared module globals. + +Prepared runtime state + ``generate_numba_layers`` uses the problem-dependent dtypes to allocate and + pack that simulation's ``simulation`` and ``data`` objects. This state is + owned by the prepared simulation and used during transport. + +This separation allows independent processes to run differently sized +problems from the same installation. Each process creates and retains its own +problem-dependent dtypes, while the generated Numba support remains read-only. + +Import and Preparation Order +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Generated Numba support is established at the process level: + +#. Importing ``mcdc`` loads the complete object model. +#. ``mcdc.config`` parses ``-r`` or ``--rebuild`` with the other command-line + options, and package initialization calls its MPI-aware rebuild gate. When + rebuilding is requested, rank zero regenerates the Numba support and the + other ranks in that MPI launch wait for it to finish. +#. Runtime modules may then import ``numba_types``, ``mcdc_get``, and + ``mcdc_set``. + +This process-level step does not depend on a :class:`mcdc.Simulation` or its +compilation. Each simulation is subsequently compiled and prepared using the +support already established during import. Preparation creates fresh +problem-dependent dtypes and prepared runtime state for that simulation. + +The MPI barrier coordinates ranks within one launch, not independent launches. +Independent jobs sharing an MC/DC source tree must use previously generated, +read-only Numba support. See :ref:`rebuilding_numba_support` for the object +model development workflow and rebuild commands. + .. _simulation_specific_literals: Static Constants and Simulation-Specific Literals diff --git a/docs/source/developer_guide/extending/extending_the_object_model.rst b/docs/source/developer_guide/extending/extending_the_object_model.rst index 68a7cc667..162e2d472 100644 --- a/docs/source/developer_guide/extending/extending_the_object_model.rst +++ b/docs/source/developer_guide/extending/extending_the_object_model.rst @@ -321,14 +321,43 @@ For example, the structural part of a mesh subtype follows this pattern: No new ``register_object`` branch is needed for a subtype of an already registered family. The existing ``isinstance(..., MeshBase)`` or corresponding category check places it in the base collection, while ``sub_type`` and ``sub_ID`` connect it to its concrete packed collection. -Generated Runtime Layers and Accessors --------------------------------------- +.. _rebuilding_numba_support: -The annotation is the source of truth for generated runtime fields and accessors. -Do not edit ``mcdc/numba_types.py``, ``mcdc_get``, or ``mcdc_set`` to introduce a field. -Prepare a representative simulation so ``numba_layers_generator.py`` regenerates those files, then verify the access pattern predicted by the field representation chosen above. +Rebuilding Numba Support +------------------------ -For example, a variable-length ``Detector.response`` field produces element accessors associated with the ``detector`` label: +Changes to runtime-visible annotations or object types under ``mcdc/object_`` +require rebuilding the generated Numba support. The annotations are the source +of truth; do not edit ``mcdc/numba_types.py``, ``mcdc_get``, or ``mcdc_set`` +directly to introduce a field. + +Run the rebuild script after changing the object model: + +.. code-block:: console + + python mcdc/code_factory/rebuild_numba_support.py + +Then verify the access pattern predicted by the field representation chosen +above, and commit the regenerated files together with the object model change. + +During an active ``mcdc/object_`` edit-test cycle, add ``-r`` (or +``--rebuild``) to the test input-deck command instead. MC/DC then rebuilds the +generated Numba support during package initialization, after loading the full +object model and before importing the generated files. Developers who are not +changing the object model do not need this option. + +.. important:: + + Within one MPI launch, rank zero performs the rebuild and the other ranks + wait. Independent launches do not share that barrier, so do not use ``-r`` + or ``--rebuild`` from concurrent jobs that share an MC/DC source tree. + +Rebuilding refreshes the shared runtime schema. Problem-dependent dtypes and +prepared runtime state are still created separately for each simulation. See +:ref:`generated_numba_support` for those lifetimes and the import order. + +After rebuilding, a variable-length ``Detector.response`` field should produce +element accessors associated with the ``detector`` label: .. code-block:: python @@ -367,7 +396,7 @@ For example, a public ``Detector`` is re-exported from the package and listed by Verification Checklist ---------------------- -An object-model extension should verify all affected layers: +An object model extension should verify all affected layers: - Construction accepts valid input and rejects invalid shapes or types. - Compilation discovers the object from the intended root. From 6f8e44206b40db8fa9e6ec509f994d637bb3a88c Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 07:15:50 +0700 Subject: [PATCH 09/17] consistent terminology --- mcdc/code_factory/numba_layers_generator.py | 11 ++++++----- mcdc/config.py | 4 +--- mcdc/main.py | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/mcdc/code_factory/numba_layers_generator.py b/mcdc/code_factory/numba_layers_generator.py index 4ad4c0fb2..4dd538c10 100644 --- a/mcdc/code_factory/numba_layers_generator.py +++ b/mcdc/code_factory/numba_layers_generator.py @@ -99,14 +99,14 @@ def validate_unique_class_labels(classes): ] # ====================================================================================== -# Numba layer creation +# Runtime state preparation # ====================================================================================== def generate_numba_layers(simulation): - """Pack a finalized Python model into the shared runtime data layers.""" + """Pack a finalized Python model into prepared runtime state.""" # ================================================================================== - # Allocate key items for the Numba runtime layers: + # Allocate key items for runtime state preparation: # - Python annotations # - Numba structures # - Records @@ -333,7 +333,7 @@ def generate_numba_layers(simulation): structures["simulation"] = new_structure + structures["simulation"] # ================================================================================== - # Build the problem-dependent Numba types + # Build the problem-dependent dtypes # ================================================================================== import mcdc.numba_types as type_ @@ -1633,5 +1633,6 @@ def rebuild_numba_support(): generate_mcdc_access(accessor_targets) generate_numba_types(structures, structure_order) print( - f"Numba support (mcdc_get, mcdc_set, and numba_types.py)\n is generated in {Path(mcdc.__file__).parent}" + "Generated Numba support (numba_types.py, mcdc_get, and mcdc_set)" + f"\n is written to {Path(mcdc.__file__).parent}" ) diff --git a/mcdc/config.py b/mcdc/config.py index ef6e7a266..dfce1d626 100644 --- a/mcdc/config.py +++ b/mcdc/config.py @@ -49,9 +49,7 @@ def _build_parser() -> argparse.ArgumentParser: "-r", "--rebuild", action="store_true", - help=( - "Rebuild generated Numba support " "(for active object-model development)." - ), + help="Rebuild generated Numba support (for active object model development).", ) # GPU execution diff --git a/mcdc/main.py b/mcdc/main.py index efdc49d7a..89965aa29 100644 --- a/mcdc/main.py +++ b/mcdc/main.py @@ -123,7 +123,7 @@ def prepare(simulationPy: Simulation): the selected backend, and loads any external source-particle state. """ # ================================================================================== - # Generate Numba runtime layers + # Prepare problem-dependent runtime state # ================================================================================== from mcdc.code_factory.numba_layers_generator import generate_numba_layers From f6713c66b45e37af16f942d150492cb92c374a9a Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 07:21:37 +0700 Subject: [PATCH 10/17] organize docs --- README.md | 2 +- .../contributing/container_development.rst | 0 .../contributing/continuous_integration.rst | 0 .../contributing/example_validation.rst | 0 .../{ => developer_guide}/contributing/index.rst | 10 +++++----- .../contributing/pull_requests.rst | 0 docs/source/developer_guide/documentation/sphinx.rst | 3 ++- .../extending/extending_the_object_model.rst | 2 +- docs/source/developer_guide/extending/index.rst | 2 +- .../writing_numba_compatible_transport_code.rst | 4 ++-- docs/source/developer_guide/index.rst | 4 ++-- docs/source/index.rst | 4 ++-- docs/source/project/carre.rst | 2 +- .../{ => user_guide}/examples/c5g7_k_eigenvalue.rst | 2 +- .../{ => user_guide}/examples/c5g7_transient.rst | 2 +- .../examples/fuel_array_packaged.rst | 12 ++++++------ .../{ => user_guide}/examples/hybrid_multigroup.rst | 4 ++-- docs/source/{ => user_guide}/examples/index.rst | 6 +++--- .../examples/iterative_source_reweighting.rst | 4 ++-- .../{ => user_guide}/examples/kobayashi_dog_leg.rst | 12 ++++++------ .../{ => user_guide}/examples/kobayashi_td.rst | 6 +++--- .../{ => user_guide}/examples/moving_pellet.rst | 12 ++++++------ .../{ => user_guide}/examples/moving_source.rst | 10 +++++----- .../{ => user_guide}/examples/slab_shielding.rst | 6 +++--- .../{ => user_guide}/examples/sphere_in_cube.rst | 10 +++++----- docs/source/user_guide/execution/cpu.rst | 4 ++-- .../user_guide/getting_started/first_simulation.rst | 2 +- docs/source/user_guide/getting_started/index.rst | 2 +- docs/source/user_guide/index.rst | 4 ++-- 29 files changed, 66 insertions(+), 65 deletions(-) rename docs/source/{ => developer_guide}/contributing/container_development.rst (100%) rename docs/source/{ => developer_guide}/contributing/continuous_integration.rst (100%) rename docs/source/{ => developer_guide}/contributing/example_validation.rst (100%) rename docs/source/{ => developer_guide}/contributing/index.rst (94%) rename docs/source/{ => developer_guide}/contributing/pull_requests.rst (100%) rename docs/source/{ => user_guide}/examples/c5g7_k_eigenvalue.rst (96%) rename docs/source/{ => user_guide}/examples/c5g7_transient.rst (96%) rename docs/source/{ => user_guide}/examples/fuel_array_packaged.rst (92%) rename docs/source/{ => user_guide}/examples/hybrid_multigroup.rst (87%) rename docs/source/{ => user_guide}/examples/index.rst (87%) rename docs/source/{ => user_guide}/examples/iterative_source_reweighting.rst (88%) rename docs/source/{ => user_guide}/examples/kobayashi_dog_leg.rst (93%) rename docs/source/{ => user_guide}/examples/kobayashi_td.rst (91%) rename docs/source/{ => user_guide}/examples/moving_pellet.rst (92%) rename docs/source/{ => user_guide}/examples/moving_source.rst (92%) rename docs/source/{ => user_guide}/examples/slab_shielding.rst (74%) rename docs/source/{ => user_guide}/examples/sphere_in_cube.rst (92%) diff --git a/README.md b/README.md index fbf6cc806..3294bf1bf 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Complete documentation is available on [Read the Docs](https://mcdc.readthedocs. - [Getting Started](https://mcdc.readthedocs.io/en/dev/user_guide/getting_started/index.html) - [API Reference](https://mcdc.readthedocs.io/en/dev/reference/python_api/index.html) - [Developer Guide](https://mcdc.readthedocs.io/en/dev/developer_guide/index.html) -- [Contributing](https://mcdc.readthedocs.io/en/dev/contributing/index.html) +- [Contributing](https://mcdc.readthedocs.io/en/dev/developer_guide/contributing/index.html) ## Citing diff --git a/docs/source/contributing/container_development.rst b/docs/source/developer_guide/contributing/container_development.rst similarity index 100% rename from docs/source/contributing/container_development.rst rename to docs/source/developer_guide/contributing/container_development.rst diff --git a/docs/source/contributing/continuous_integration.rst b/docs/source/developer_guide/contributing/continuous_integration.rst similarity index 100% rename from docs/source/contributing/continuous_integration.rst rename to docs/source/developer_guide/contributing/continuous_integration.rst diff --git a/docs/source/contributing/example_validation.rst b/docs/source/developer_guide/contributing/example_validation.rst similarity index 100% rename from docs/source/contributing/example_validation.rst rename to docs/source/developer_guide/contributing/example_validation.rst diff --git a/docs/source/contributing/index.rst b/docs/source/developer_guide/contributing/index.rst similarity index 94% rename from docs/source/contributing/index.rst rename to docs/source/developer_guide/contributing/index.rst index c88c782cc..18a049693 100644 --- a/docs/source/contributing/index.rst +++ b/docs/source/developer_guide/contributing/index.rst @@ -11,9 +11,9 @@ Start with the setup steps below. Use :doc:`continuous_integration` to understand automated checks and :doc:`container_development` when developing in the project container. Use :doc:`example_validation` when changing the public API or example problems. Read :doc:`pull_requests` before preparing a contribution. -For software architecture and documentation practices, see the :doc:`../developer_guide/index`. +For software architecture and documentation practices, see the :doc:`../index`. -For implementation guidance specific to compiled transport functions, see :doc:`../developer_guide/extending/writing_numba_compatible_transport_code`. +For implementation guidance specific to compiled transport functions, see :doc:`../extending/writing_numba_compatible_transport_code`. Contributions target the ``dev`` branch. Prepare a development checkout with the following steps: @@ -36,7 +36,7 @@ Development Workflow pull_requests MC/DC documentation is an important part of the project and evolves alongside the codebase. -The :doc:`../developer_guide/documentation/index` guide describes the documentation philosophy, writing guidelines, and the tools used to build and maintain the documentation. +The :doc:`../documentation/index` guide describes the documentation philosophy, writing guidelines, and the tools used to build and maintain the documentation. Please note our `code of conduct `_, which we take seriously. @@ -132,7 +132,7 @@ However if absolutely required by users numba does allow for some `cache sharing Adding a New Input ------------------ -For architectural guidance on adding a model field, embedded configuration, registered object category, or polymorphic subtype, see :doc:`../developer_guide/extending/extending_the_object_model`. +For architectural guidance on adding a model field, embedded configuration, registered object category, or polymorphic subtype, see :doc:`../extending/extending_the_object_model`. Public model classes and configuration are primarily defined in ``mcdc/object_/``. Common input-related locations include: @@ -213,4 +213,4 @@ Adding Documentation Documentation is a core part of MC/DC. Contributions that introduce new features, modify existing behavior, or change developer workflows should update the relevant documentation accordingly. -See the :doc:`../developer_guide/documentation/index` guide for documentation philosophy, writing guidelines, and instructions for contributing to the documentation. +See the :doc:`../documentation/index` guide for documentation philosophy, writing guidelines, and instructions for contributing to the documentation. diff --git a/docs/source/contributing/pull_requests.rst b/docs/source/developer_guide/contributing/pull_requests.rst similarity index 100% rename from docs/source/contributing/pull_requests.rst rename to docs/source/developer_guide/contributing/pull_requests.rst diff --git a/docs/source/developer_guide/documentation/sphinx.rst b/docs/source/developer_guide/documentation/sphinx.rst index b8adbeedd..aec5b5cb5 100644 --- a/docs/source/developer_guide/documentation/sphinx.rst +++ b/docs/source/developer_guide/documentation/sphinx.rst @@ -67,7 +67,8 @@ For example, the following on ``index.rst`` creates a table of contents on the m .. toctree:: user_guide/index theory/index - examples/index + reference/index + developer_guide/index Sphinx will build an html file for all rst files in the source directory and its subdirectories. Sphinx will issue a warning if an html file isn't referenced in any toctree because that means that the generated webpage is not reachable through standard navigation. diff --git a/docs/source/developer_guide/extending/extending_the_object_model.rst b/docs/source/developer_guide/extending/extending_the_object_model.rst index 162e2d472..1b495268c 100644 --- a/docs/source/developer_guide/extending/extending_the_object_model.rst +++ b/docs/source/developer_guide/extending/extending_the_object_model.rst @@ -408,4 +408,4 @@ An object model extension should verify all affected layers: - API and developer documentation build without warnings. Add focused unit tests near ``test/unit/test_object_compilation.py`` for compilation behavior and near the relevant transport tests for runtime behavior. -Use :doc:`../../contributing/example_validation` when an extension changes public examples. +Use :doc:`../contributing/example_validation` when an extension changes public examples. diff --git a/docs/source/developer_guide/extending/index.rst b/docs/source/developer_guide/extending/index.rst index 4240efb0a..2aa3e32c8 100644 --- a/docs/source/developer_guide/extending/index.rst +++ b/docs/source/developer_guide/extending/index.rst @@ -20,7 +20,7 @@ Continue with :doc:`writing_numba_compatible_transport_code` when the extension For example, a new runtime field with no transport behavior uses the object-model guide, a numerical change using existing fields starts with the transport-code guide, and a new tally subtype follows both in that order. -Use the :doc:`../../contributing/index` for repository setup, test commands, continuous-integration coverage, and pull-request requirements. +Use the :doc:`../contributing/index` for repository setup, test commands, continuous-integration coverage, and pull-request requirements. .. toctree:: :maxdepth: 1 diff --git a/docs/source/developer_guide/extending/writing_numba_compatible_transport_code.rst b/docs/source/developer_guide/extending/writing_numba_compatible_transport_code.rst index 4a84436dd..c2d79377e 100644 --- a/docs/source/developer_guide/extending/writing_numba_compatible_transport_code.rst +++ b/docs/source/developer_guide/extending/writing_numba_compatible_transport_code.rst @@ -407,5 +407,5 @@ Before claiming Numba-GPU support: - GPU results preserve the same physical behavior within appropriate numerical and statistical tolerances. - GPU-specific adaptations and limitations are documented. -Use the :doc:`../../contributing/index` for repository commands, continuous-integration coverage, and regression-test options. -For changes affecting public inputs, follow :doc:`../../contributing/example_validation`. +Use the :doc:`../contributing/index` for repository commands, continuous-integration coverage, and regression-test options. +For changes affecting public inputs, follow :doc:`../contributing/example_validation`. diff --git a/docs/source/developer_guide/index.rst b/docs/source/developer_guide/index.rst index 049c2f9f8..51b3b765b 100644 --- a/docs/source/developer_guide/index.rst +++ b/docs/source/developer_guide/index.rst @@ -25,7 +25,7 @@ Where to Go Next - Read :doc:`architecture/index` to understand MC/DC's Python-first design and follow a transport model through simulation compilation, runtime preparation, shared transport algorithm, and the available execution modes. - Read :doc:`extending/index` when adding model fields, registered objects, polymorphic subtypes, or Numba-compatible transport behavior. - Read :doc:`documentation/index` when writing or reviewing project documentation. -- Use :doc:`../contributing/index` for repository setup, development workflow, testing, and pull-request requirements. +- Use :doc:`contributing/index` for repository setup, development workflow, testing, and pull-request requirements. - Use :doc:`release_policy` when preparing, validating, and publishing a release. .. toctree:: @@ -35,4 +35,4 @@ Where to Go Next extending/index documentation/index release_policy - Contributing <../contributing/index> + contributing/index diff --git a/docs/source/index.rst b/docs/source/index.rst index a687e3d71..009c122a2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -73,9 +73,9 @@ Choose the path that best matches what you want to accomplish. More resources -------------- -- Learn from complete input decks in :doc:`examples/index`. +- Learn from complete input decks in :doc:`user_guide/examples/index`. - Explore the ongoing :doc:`CARRE research program `, its collaboration opportunities, and the :doc:`MC/DC publication record `. -- Follow the contribution workflow in :doc:`contributing/index`. +- Follow the contribution workflow in :doc:`developer_guide/contributing/index`. .. admonition:: Recommended citation :class: tip diff --git a/docs/source/project/carre.rst b/docs/source/project/carre.rst index 3351a7407..4919c35e1 100644 --- a/docs/source/project/carre.rst +++ b/docs/source/project/carre.rst @@ -33,7 +33,7 @@ Users and Collaborators The CARRE work creates opportunities for users and collaborators interested in particle-interaction models, nuclear and atomic data, multiparticle coupling, uncertainty quantification, variance reduction, hybrid methods, Monte Carlo algorithms, V&V benchmarks, and high-performance computing. Prospective users can help shape the developing capabilities by sharing intended applications, workflow requirements, benchmark problems, and validation needs. -Researchers and developers are invited to explore the ongoing work and contribute through the :doc:`MC/DC development process <../contributing/index>`. +Researchers and developers are invited to explore the ongoing work and contribute through the :doc:`MC/DC development process <../developer_guide/contributing/index>`. Foundation in CEMeNT -------------------- diff --git a/docs/source/examples/c5g7_k_eigenvalue.rst b/docs/source/user_guide/examples/c5g7_k_eigenvalue.rst similarity index 96% rename from docs/source/examples/c5g7_k_eigenvalue.rst rename to docs/source/user_guide/examples/c5g7_k_eigenvalue.rst index a054ef719..df35a83b6 100644 --- a/docs/source/examples/c5g7_k_eigenvalue.rst +++ b/docs/source/user_guide/examples/c5g7_k_eigenvalue.rst @@ -46,7 +46,7 @@ Click here to view the input file: `examples/c5g7/k-eigenvalue/input.py `_. MC/DC also has the ability to run Numba in a debugging mode. @@ -47,7 +47,7 @@ This will result in less performant code and longer compile times but will allow For more information on the exact behavior of this option, see -:ref:`contributing/index:Debugging`. +:ref:`developer_guide/contributing/index:Debugging`. Using MPI --------- diff --git a/docs/source/user_guide/getting_started/first_simulation.rst b/docs/source/user_guide/getting_started/first_simulation.rst index d68511c10..49d2fd60e 100644 --- a/docs/source/user_guide/getting_started/first_simulation.rst +++ b/docs/source/user_guide/getting_started/first_simulation.rst @@ -253,4 +253,4 @@ After running the original problem, useful variations include: - Add an energy group or another spatial region. - Add a surface-crossing tally at the material interface. -See :doc:`../../examples/index` for examples involving lattices, moving geometry, time-dependent transport, and reactor benchmarks. +See :doc:`../examples/index` for examples involving lattices, moving geometry, time-dependent transport, and reactor benchmarks. diff --git a/docs/source/user_guide/getting_started/index.rst b/docs/source/user_guide/getting_started/index.rst index 97e5e7bb3..5565b8724 100644 --- a/docs/source/user_guide/getting_started/index.rst +++ b/docs/source/user_guide/getting_started/index.rst @@ -21,5 +21,5 @@ visualize, run, and post-process a complete transport problem. After completing these pages: - Continue through the :doc:`../index` for task-oriented guidance. -- Browse :doc:`../../examples/index` for complete models. +- Browse :doc:`../examples/index` for complete models. - Consult the :doc:`../../reference/index` for exact API behavior. diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst index 99d864677..d5dd2e77d 100644 --- a/docs/source/user_guide/index.rst +++ b/docs/source/user_guide/index.rst @@ -49,13 +49,13 @@ Execution Learning by Example ------------------- -Use the :doc:`../examples/index` to learn from complete, runnable input decks +Use :doc:`examples/index` to learn from complete, runnable input decks that progress from basic models to advanced benchmarks. .. toctree:: :maxdepth: 2 - Example Problems <../examples/index> + examples/index Help & Support -------------- From f1829eab2a292b018d1f73f162649ba54a808d6a Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 07:24:43 +0700 Subject: [PATCH 11/17] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 178dfcc11..babd25c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), ### Added +- Add `rebuild_numba_support.py` and the `-r`/`--rebuild` developer option for regenerating Numba support (mcdc_get, mcdc_set, numba_types.py) after object model changes, from [@ilhamv] + ### Changed +- Generate shared Numba support independently of simulation preparation and create problem-dependent dtypes locally through pure factories, from [@ilhamv] +- Organize example documentation under the User Guide and contribution documentation under the Developer Guide, from [@ilhamv] + ### Deprecated ### Removed @@ -17,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), ### Fixed - Anticipate empty census-based tallies in batch runs for correct tally recombination, from [@ilhamv] +- Prevent independent runs with different problem sizes from sharing mutable problem-dependent Numba types, and preserve Python-managed `__pycache__` directories during startup, from [@ilhamv] ### Security From ad74f7f5ed50fc1dd7d170fbfa5de076c17b3edd Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 09:30:42 +0700 Subject: [PATCH 12/17] update release docs --- .../source/developer_guide/release_policy.rst | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/source/developer_guide/release_policy.rst b/docs/source/developer_guide/release_policy.rst index dfbef51eb..72bd9d096 100644 --- a/docs/source/developer_guide/release_policy.rst +++ b/docs/source/developer_guide/release_policy.rst @@ -4,11 +4,15 @@ Release Policy and Process ========================== -MC/DC follows `Semantic Versioning `_ and maintains a human-readable release history in `CHANGELOG.md `_. +MC/DC uses `Semantic Versioning `_ as a guide for distinguishing minor and patch releases and maintains a human-readable release history in `CHANGELOG.md `_. Minor Releases -------------- +For MC/DC, a minor release primarily expands what the code can do. +Examples include an additional transport or execution capability, a new public Python API, a new command-line option, a new opt-in behavior, or a deprecation that users need time to accommodate. +A minor release may include compatible fixes and internal improvements alongside its features. + MC/DC plans one minor release in each three-month seasonal cycle. These releases collect compatible features, improvements, and fixes that have passed the project's required review and validation. For planning convenience, these cycles follow the Northern Hemisphere meteorological seasons: winter (Dec-Feb), spring (Mar-May), summer (Jun-Aug), and autumn/fall (Sep-Nov). @@ -19,14 +23,50 @@ A minor release may be delayed when additional testing, documentation, or integr Patch Releases -------------- +For MC/DC, a patch release primarily restores or strengthens behavior already expected from the current stable release. +A change belongs in a patch when it corrects intended or documented behavior, prevents an installation or runtime failure, or fixes an implementation defect without asking users to adopt a new interface. +Tests, documentation corrections, refactoring, build changes, and developer tooling may accompany a patch when they support or validate the fix and do not independently expand user-facing behavior. +If a change introduces a new option, API, capability, or opt-in behavior, it should normally be held for a minor release even when developed alongside a bug fix and separated from the patch branch. +When the classification is not obvious, use the purpose and user impact of the release: capability expansion points to a minor release, while correction of existing behavior points to a patch release. + Bug fixes are not held until the next seasonal minor release. Once a fix has passed review and the relevant validation and release checks, MC/DC publishes a patch release as soon as practical. Every patch release must include a non-empty ``Fixed`` section in ``CHANGELOG.md`` that describes the user-visible defect corrected by the release. +Place ``Fixed`` first in a patch release entry so the reason for the release is immediately visible. Supporting changes may also appear under other headings, but the defect that justifies the patch release must be stated under ``Fixed``. For dependency-compatibility patches, identify the installation or runtime failure prevented and the affected dependency or version range when known. Published versions and release notes are available from the `MC/DC releases page `_. +.. _release_branch_routes: + +Release Branch Routes +--------------------- + +MC/DC uses different integration routes for feature and patch releases because ``upstream/dev`` and ``upstream/main`` have different roles. +The development branch integrates work for the next minor release, while the main branch identifies the current stable release line. + +.. list-table:: Release workflow comparison + :header-rows: 1 + :widths: 24 24 28 44 + + * - Workflow + - Release branch base + - Route to release + - Purpose + * - Feature (minor-release) workflow + - ``upstream/dev`` + - release branch → ``upstream/dev`` → ``upstream/main`` + - Includes the compatible features, improvements, and fixes accumulated for the next minor version. + * - Patch workflow + - ``upstream/main`` + - patch branch → ``upstream/main`` + - Releases selected fixes from the current stable line without including unreleased features already present on ``upstream/dev``. + +If a patch fix was first developed on ``upstream/dev``, transfer only the fix and its necessary tests, documentation, and supporting changes onto a branch based on ``upstream/main``. +Do not merge ``upstream/dev`` into the patch branch. +After publishing the patch, merge ``upstream/main`` back into ``upstream/dev`` so subsequent minor releases retain the fix. + .. _release_checklist: Release Checklist @@ -39,9 +79,10 @@ Prepare the Release ^^^^^^^^^^^^^^^^^^^ #. Confirm the intended version and scope against the release policy above. +#. Select the appropriate :ref:`release_branch_routes` and create the release-preparation branch from its prescribed base. #. Review the ``Unreleased`` section of ``CHANGELOG.md``. Ensure every user-visible change is included under the correct heading, remove empty headings, and add contributor attribution where appropriate. - For a patch release, confirm that ``Fixed`` is non-empty and clearly states the defect that justifies the release. + For a patch release, place ``Fixed`` first and confirm that it is non-empty and clearly states the defect that justifies the release. #. Finalize the release version and date in ``CHANGELOG.md`` and ``CITATION.cff``, and update the stable entry's display name in ``docs/source/_static/switcher.json`` to the full ``X.Y.Z (stable)`` version while retaining ``stable`` as its version identifier and URL. #. Confirm that documentation, examples, deprecation notices, and migration guidance match the release behavior. #. Verify the supported Python versions in ``pyproject.toml``, continuous integration, and the user documentation agree. @@ -69,12 +110,21 @@ Validate the Release Integrate the Release ^^^^^^^^^^^^^^^^^^^^^ -In this workflow, ``upstream`` refers to the canonical ``mcdc-project/mcdc`` repository and ``make_release`` refers to the release-preparation branch. +Here, ``upstream`` refers to the canonical ``mcdc-project/mcdc`` repository and ``make_release`` refers to the release-preparation branch. + +For a minor release using the feature workflow: +#. Create ``make_release`` from ``upstream/dev`` and complete the release preparation and validation there. #. Merge the completed ``make_release`` branch into ``upstream/dev`` through a reviewed pull request. #. After the release candidate passes the required checks, merge ``upstream/dev`` into ``upstream/main`` through a reviewed pull request. #. Use the resulting ``upstream/main`` commit as the release base. +For a patch release using the dev-bypass workflow: + +#. Create ``make_release`` from the current stable commit on ``upstream/main`` and include only the selected fixes and necessary supporting changes. +#. Merge the completed ``make_release`` branch directly into ``upstream/main`` through a reviewed pull request, without routing it through ``upstream/dev``. +#. Use the resulting ``upstream/main`` commit as the release base. + Publish from Main ^^^^^^^^^^^^^^^^^ @@ -86,5 +136,6 @@ Return to Development ^^^^^^^^^^^^^^^^^^^^^ #. Merge ``upstream/main`` back into ``upstream/dev`` after the release is published and verified. +#. For a patch release, confirm that the back-merge retains the patch while preserving the unreleased feature work already on ``upstream/dev``. #. Prepare ``upstream/dev`` for the next development cycle and confirm its required checks pass. #. Remove the merged ``make_release`` branch when it is no longer needed. From 24b67465391e1ca6bd980e7e4be6335e90ec2ffb Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 09:31:49 +0700 Subject: [PATCH 13/17] update CHANGELOG --- CHANGELOG.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index babd25c19..a743dd7ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,15 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as a guide. ## [Unreleased] +### Fixed + +- Anticipate empty census-based tallies in batch runs for correct tally recombination, from [@ilhamv] +- Prevent independent runs with different problem sizes from sharing mutable problem-dependent Numba types, and preserve Python-managed `__pycache__` directories during startup, from [@ilhamv] + ### Added - Add `rebuild_numba_support.py` and the `-r`/`--rebuild` developer option for regenerating Numba support (mcdc_get, mcdc_set, numba_types.py) after object model changes, from [@ilhamv] @@ -19,15 +24,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), ### Removed -### Fixed - -- Anticipate empty census-based tallies in batch runs for correct tally recombination, from [@ilhamv] -- Prevent independent runs with different problem sizes from sharing mutable problem-dependent Numba types, and preserve Python-managed `__pycache__` directories during startup, from [@ilhamv] - ### Security ## [0.15.1] - 2026-08-12 +### Fixed + +- Prevent unbounded dependency resolution from selecting incompatible releases that break MC/DC by adding explicit upper bounds for all build, runtime, documentation, and development dependencies, from [@ilhamv] + ### Added - Add a dedicated CARRE project page, from [@ilhamv] @@ -38,10 +42,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), - Hide flyout in Read the Docs, from [@ilhamv] -### Fixed - -- Prevent unbounded dependency resolution from selecting incompatible releases that break MC/DC by adding explicit upper bounds for all build, runtime, documentation, and development dependencies, from [@ilhamv] - ## [0.15.0] - 2026-08-11 ### Added @@ -68,6 +68,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), ## [0.14.2] - 2026-07-15 +### Fixed + +- Fix 2D-vector setter writes nothing (- instead of =) from [@steps-re] +- Fix delayed neutrons are never sampled (transport/physics/neutron/native.py, fission()) from [@steps-re] +- Fix delayed emission time uses β instead of λ (transport/physics/neutron/native.py, fission())from [@steps-re] +- Fix swapped transverse-basis branches (transport/distribution.py, sample_direction()) from [@steps-re] +- Fix divide-by-zero for a -z reference (transport/distribution.py, sample_white_direction()) from [@steps-re] +- Fix tally polar_reference corrupted (object_/tally.py) from [@steps-re] + ### Added - Add layered documentation philosophy @@ -82,18 +91,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), - Combined `object_` and `transport` unit test for more efficient fixture reuse from [@massimolarsen] - Replace bare assert np.isclose with proper np.testing.assert_allclose from [@steps-re] -### Fixed - -- Fix 2D-vector setter writes nothing (- instead of =) from [@steps-re] -- Fix delayed neutrons are never sampled (transport/physics/neutron/native.py, fission()) from [@steps-re] -- Fix delayed emission time uses β instead of λ (transport/physics/neutron/native.py, fission())from [@steps-re] -- Fix swapped transverse-basis branches (transport/distribution.py, sample_direction()) from [@steps-re] -- Fix divide-by-zero for a -z reference (transport/distribution.py, sample_white_direction()) from [@steps-re] -- Fix tally polar_reference corrupted (object_/tally.py) from [@steps-re] - ## [0.14.1] - 2026-07-04 -### Changed +### Fixed - Documentation and packaging metadata fixes From d68ab904b1b2d8bb851dc5966e390be4d25317e6 Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 09:43:21 +0700 Subject: [PATCH 14/17] update metadata --- CHANGELOG.md | 9 ++------- CITATION.cff | 4 ++-- docs/source/_static/switcher.json | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a743dd7ce..ea6d6826b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as a guide. -## [Unreleased] +## [0.15.2] - 2026-08-15 ### Fixed @@ -20,12 +20,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), - Generate shared Numba support independently of simulation preparation and create problem-dependent dtypes locally through pure factories, from [@ilhamv] - Organize example documentation under the User Guide and contribution documentation under the Developer Guide, from [@ilhamv] -### Deprecated - -### Removed - -### Security - ## [0.15.1] - 2026-08-12 ### Fixed @@ -175,6 +169,7 @@ The pre-refactor implementation remains available in the `cement` branch as a re - Multi-table distribution table selection sampling from [@melekderman] +[0.15.2]: https://github.com/mcdc-project/mcdc/releases/tag/v0.15.2 [0.15.1]: https://github.com/mcdc-project/mcdc/releases/tag/v0.15.1 [0.15.0]: https://github.com/mcdc-project/mcdc/releases/tag/v0.15.0 [0.14.2]: https://github.com/mcdc-project/mcdc/releases/tag/v0.14.2 diff --git a/CITATION.cff b/CITATION.cff index f360ea3c8..f4432ffe8 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -34,8 +34,8 @@ keywords: - Numba - GPU license: BSD-3-Clause -version: 0.15.1 -date-released: '2026-08-12' +version: 0.15.2 +date-released: '2026-08-15' preferred-citation: type: article authors: diff --git a/docs/source/_static/switcher.json b/docs/source/_static/switcher.json index d86ebd60a..af71df44e 100644 --- a/docs/source/_static/switcher.json +++ b/docs/source/_static/switcher.json @@ -5,7 +5,7 @@ "url": "https://mcdc.readthedocs.io/en/dev/" }, { - "name": "0.15.1 (stable)", + "name": "0.15.2 (stable)", "version": "stable", "url": "https://mcdc.readthedocs.io/en/stable/", "preferred": true From 4df87d8f074f768de8ce45a08cfcbdf2d86f20dc Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 10:25:31 +0700 Subject: [PATCH 15/17] update release note --- .../source/developer_guide/release_policy.rst | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/docs/source/developer_guide/release_policy.rst b/docs/source/developer_guide/release_policy.rst index 72bd9d096..fddb3cdbb 100644 --- a/docs/source/developer_guide/release_policy.rst +++ b/docs/source/developer_guide/release_policy.rst @@ -73,64 +73,53 @@ Release Checklist ----------------- Use this checklist for every minor and patch release. -A patch release may omit items that do not apply, but it must still complete dependency review, validation, and publication checks. +Here, ``upstream`` refers to the canonical ``mcdc-project/mcdc`` repository and ``release_branch`` refers to the release-preparation branch. Prepare the Release ^^^^^^^^^^^^^^^^^^^ #. Confirm the intended version and scope against the release policy above. -#. Select the appropriate :ref:`release_branch_routes` and create the release-preparation branch from its prescribed base. +#. Select the appropriate :ref:`release_branch_routes` and create ``release_branch`` from ``upstream/dev`` for a minor release or the current stable commit on ``upstream/main`` for a patch release. + For a patch, include only the selected fixes and necessary supporting changes. #. Review the ``Unreleased`` section of ``CHANGELOG.md``. Ensure every user-visible change is included under the correct heading, remove empty headings, and add contributor attribution where appropriate. For a patch release, place ``Fixed`` first and confirm that it is non-empty and clearly states the defect that justifies the release. #. Finalize the release version and date in ``CHANGELOG.md`` and ``CITATION.cff``, and update the stable entry's display name in ``docs/source/_static/switcher.json`` to the full ``X.Y.Z (stable)`` version while retaining ``stable`` as its version identifier and URL. -#. Confirm that documentation, examples, deprecation notices, and migration guidance match the release behavior. -#. Verify the supported Python versions in ``pyproject.toml``, continuous integration, and the user documentation agree. - -Review and Consolidate Dependencies -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -#. Review every build, runtime, documentation, and development dependency in ``pyproject.toml``. -#. Check each dependency's current release, release notes, supported Python versions, and compatibility with MC/DC. -#. Consolidate the dependency declarations: remove unused or duplicate dependencies, keep shared constraints consistent, and ensure each direct dependency is declared in the appropriate group. -#. Set or update explicit lower bounds where MC/DC relies on a minimum feature and explicit upper bounds at the newest compatibility-tested release line. - Do not widen a ceiling until the new line has passed the relevant unit, regression, documentation, and type-checking workflows. -#. Test the resolved environment for every supported Python version. - Where practical, also test environments near the declared minimum and maximum bounds so that a successful default resolution does not hide an invalid constraint. -#. Record dependency additions, removals, or compatibility-bound changes in ``CHANGELOG.md``. - -Validate the Release -^^^^^^^^^^^^^^^^^^^^ - -#. Run the formatter, unit tests, public API type checks, regression tests, and documentation build. -#. Confirm all required continuous-integration jobs pass on the release commit, including the manually triggered compatibility jobs for supported Python versions and applicable CPU, MPI, and GPU configurations. -#. Build the source distribution and wheel, then install and smoke-test both artifacts in clean environments. -#. Check the package metadata, bundled files, version, license, project links, and ``CITATION.cff``. +#. Review the release diff for user-facing behavior. + Confirm that each affected interface or workflow is reflected in the relevant documentation and examples. + If existing users must change how they use MC/DC, include the necessary deprecation notice or migration guidance. +#. Confirm whether the release changes the supported Python versions. + If it does, update ``pyproject.toml``, the compatibility workflows, installation documentation, and ``CHANGELOG.md``. +#. Confirm the required local checks, continuous-integration workflows, and distribution artifact tests pass on the release candidate; resolve any dependency incompatibilities they expose and update ``pyproject.toml`` and ``CHANGELOG.md`` as needed. Integrate the Release ^^^^^^^^^^^^^^^^^^^^^ -Here, ``upstream`` refers to the canonical ``mcdc-project/mcdc`` repository and ``make_release`` refers to the release-preparation branch. - For a minor release using the feature workflow: -#. Create ``make_release`` from ``upstream/dev`` and complete the release preparation and validation there. -#. Merge the completed ``make_release`` branch into ``upstream/dev`` through a reviewed pull request. -#. After the release candidate passes the required checks, merge ``upstream/dev`` into ``upstream/main`` through a reviewed pull request. +#. Merge the completed ``release_branch`` into ``upstream/dev`` through a reviewed pull request. +#. Merge ``upstream/dev`` into ``upstream/main`` through a reviewed pull request. #. Use the resulting ``upstream/main`` commit as the release base. For a patch release using the dev-bypass workflow: -#. Create ``make_release`` from the current stable commit on ``upstream/main`` and include only the selected fixes and necessary supporting changes. -#. Merge the completed ``make_release`` branch directly into ``upstream/main`` through a reviewed pull request, without routing it through ``upstream/dev``. +#. Merge the completed ``release_branch`` directly into ``upstream/main`` through a reviewed pull request, without routing it through ``upstream/dev``. #. Use the resulting ``upstream/main`` commit as the release base. Publish from Main ^^^^^^^^^^^^^^^^^ -#. Create the ``v``-prefixed tag and GitHub release from the validated release commit on ``upstream/main``. -#. Confirm that the package, citation-metadata, and stable-documentation publication workflows succeed. -#. Install the published package from PyPI in a clean environment and run a minimal MC/DC simulation. +#. Create the ``v``-prefixed tag and GitHub Release from the validated release commit using the following settings: + + * **Target:** select ``main``. + * **Release title:** use the tag exactly, including the ``v`` prefix. + * **Previous tag:** select the previous published version, then click **Generate release notes**. + * **Release notes:** place a brief release summary first, followed by a ``## Changelog`` section containing the associated entry from ``CHANGELOG.md``, then the generated release notes. + * **Release label:** select **Latest**. + * **Finish:** click **Publish release** when creating the release, or **Update release** when editing an existing release. + +#. Confirm that the automatically triggered `Publish Python Package to PyPI `_ and `Check citation metadata `_ workflows complete successfully, and that the release is available from the `stable Read the Docs site `_. +#. Smoke-test the published PyPI package in a clean environment with ``python -m pip install "mcdc==X.Y.Z"``, then run a minimal MC/DC simulation. Return to Development ^^^^^^^^^^^^^^^^^^^^^ @@ -138,4 +127,4 @@ Return to Development #. Merge ``upstream/main`` back into ``upstream/dev`` after the release is published and verified. #. For a patch release, confirm that the back-merge retains the patch while preserving the unreleased feature work already on ``upstream/dev``. #. Prepare ``upstream/dev`` for the next development cycle and confirm its required checks pass. -#. Remove the merged ``make_release`` branch when it is no longer needed. +#. Remove the merged ``release_branch`` when it is no longer needed. From dc88eb639294c5494edc7344f7638daa1f34faf1 Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 10:35:04 +0700 Subject: [PATCH 16/17] minor organization --- mcdc/main.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mcdc/main.py b/mcdc/main.py index 89965aa29..2497571d1 100644 --- a/mcdc/main.py +++ b/mcdc/main.py @@ -35,6 +35,14 @@ def run_simulation(simulationPy: Simulation): simulation_container, data = prepare(simulationPy) simulation = simulation_container[0] + # Prevent intermediate census tallies from a previous run from being recombined. + import mcdc.output as output_module + + if settings.use_census_based_tally: + if master: + output_module.clear_census_based_tally_files(settings) + MPI.COMM_WORLD.Barrier() + # Print headers if master: print_module.print_banner() @@ -54,15 +62,8 @@ def run_simulation(simulationPy: Simulation): time_simulation_start = MPI.Wtime() # Run simulation - import mcdc.output as output_module import mcdc.transport.simulation as simulation_module - # Prevent intermediate census tallies from a previous run from being recombined. - if settings.use_census_based_tally: - if master: - output_module.clear_census_based_tally_files(settings) - MPI.COMM_WORLD.Barrier() - if settings.neutron_eigenvalue_mode: simulation_module.eigenvalue_simulation(simulation_container, data) else: From 0784f2a8f87cc770d92a7414b86f924fdafd4b3b Mon Sep 17 00:00:00 2001 From: Ilham Variansyah Date: Sat, 15 Aug 2026 10:41:51 +0700 Subject: [PATCH 17/17] minor edit --- mcdc/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mcdc/config.py b/mcdc/config.py index dfce1d626..ec42c2db7 100644 --- a/mcdc/config.py +++ b/mcdc/config.py @@ -186,8 +186,6 @@ def _manage_runtime_caches() -> None: """Clear generated-code caches when caching is disabled or reset.""" should_clear = not caching or clear_cache if should_clear and MPI.COMM_WORLD.Get_rank() == 0: - # Python manages concurrent __pycache__ writes atomically. Removing that - # shared directory here can race with independent batch launches. cache_directories = (Path.cwd() / "__harmonize_cache__",) for cache_directory in cache_directories: if cache_directory.exists():