Refactoring roadmap (architecture)¶
This document tracks planned and in-progress structural improvements discussed for OpenPFC: clearer layering, less duplication, and alignment with SOLID-style practices. It complements architecture.md.
Phase A — Communicator consistency¶
Goal: Model::is_rank0() and Simulator / SimulationContext use the same MPI_Comm for “rank 0” semantics.
Done:
Optional
MPI_Comm mpi_comm = MPI_COMM_WORLDonModel; rank-0 viampi_comm_rank_is_zero(same helper asSimulationContext); accessorModel::mpi_comm().SpectralSimulationSessionconstructsConcreteModel(fft, world, m_stack.mpi_comm())so JSON-driven apps align with the simulator communicator.Shipped / example models updated (
Aluminum,TungstenCPU/CUDA/HIP,examples/10_ui_register_ic.cpp).CUDA/HIP Tungsten: HeFFTe GPU FFT and rank/size queries use
Model::mpi_comm()(no hardcodedMPI_COMM_WORLDinset_cuda_fft/set_hip_fftor constructors).Tungsten CPU/CUDA/HIP: NaN check macros use
CHECK_AND_ABORT_IF_NANS_MPI(..., mpi_comm())soMPI_Abortand rank reporting match the model’s communicator.CHECK_AND_ABORT_IF_NAN/CHECK_AND_ABORT_IF_NANSusedefault_nan_check_mpi_comm();App::maincallsset_default_nan_check_mpi_comm(m_comm)so the default matches non-world app communicators.pfc::mpi::get_rank()/get_size()without a communicator are documented as deprecated (world-only).
Phase B — Simulator decomposition¶
Goal: Reduce responsibilities of Simulator (time orchestration vs modifier application vs results scheduling) and keep App thin versus the spectral JSON run pipeline.
Done:
apply_field_modifier_listinsimulator_field_modifiers_dispatch.hpp— shared apply loop for IC and BC lists;Simulatordelegates to it.try_push_field_modifier_with_model_checkand message constants insimulator_modifier_registration.hpp— IC/BC registration rules and warning strings in one place;Simulatoruses them instead of a private duplicate loop.simulation_wiring_conditions.hpp:detail::wire_field_modifiers_from_json_array— one implementation for parsinginitial_conditions/boundary_conditionsJSON arrays (injectadd_initial_conditionsvsadd_boundary_conditionsvia callback).Tungsten CUDA/HIP VTK integration tests call
add_initial_conditions_from_json/add_boundary_conditions_from_jsoninstead of duplicating factory loops; removed accidental doubleapply_initial_conditions()before the first step.from_json.hpp:set_from_json_log_rank/get_from_json_log_rankreplace static loggers fixed at rank-1;App::mainsets the rank so FFT / HeFFTe parse diagnostics align with other MPI-aware logs. Split intofrom_json_*headers under the same umbrella; GPU stack factory includesfrom_json_heffte.hpponly.ResultsWriterMapalias inresults_writer.hpp;Simulator::results_writers()const accessor;write_results_for_registered_fieldstakesResultsWriterMap(named type for tests / tooling). Doxygen onwrite_results/ dispatch +io_results.mdcall out the narrow test seam.Deprecated
pfc::get_field(Model&)andSimulator::get_field()/pfc::get_field(Simulator&)removed; diffusion fixtures register"default"alongside"density"and dropget_field()overrides.Model::get_field()remains deprecated for out-of-tree subclasses.Field modifier catalog: header +
extending_openpfc/README.mddocument singleton vs explicit-catalog DI;App::set_field_modifier_catalogforwards an explicit catalog intowire_simulator_from_settings(app_pipeline.md).app_spectral_run.hpp:SpectralJsonAppRunowns the post-settings spectral pipeline (session → wire → integrate);Appkeeps settings I/O and pre-run logs.simulator_integrator.hpp/simulator_queries.hpp: post-classSimulatorhelpers (scheduled writes, integrator seam,get_model/get_time/ …) split out ofsimulator.hppfor readability (SRP); single include ofsimulator.hppremains the public entry point.model_free_functions.hpp: non-memberModelAPI (get_world,has_field,step, …) split out ofmodel.hpp(same include-once pattern assimulator.hpp).Deprecation hygiene:
DiscreteFieldmemberinterpolateequivalence test suppresses Clang/MSVC warnings;Model::get_field()Doxygen expanded for migration and out-of-tree overrides.
Phase C — Unified config-driven stack (CPU / GPU)¶
Goal: One JSON → session pipeline for spectral runs, parameterized by FFT backend instead of CPU-only SpectralCpuStack.
Done (foundation):
spectral_cpu_stack_detail.hpp:cpu_spectral_plan_options_from_jsonandcpu_fft_from_json_and_decompositioncentralize JSON → HeFFTe CPU FFT construction;SpectralCpuStackcalls these (extension point for a future GPU stack builder using the same JSON surface).CPU spectral
backendalignment:cpu_spectral_plan_options_from_jsonmerges a root-level"backend"into theplan_optionsobject when the latter omits it; rejects"cuda"on this path (alwaysfft::CpuFft/ FFTW). Seeapp_pipeline.md.spectral_fft_stack_factory.hpp:merged_spectral_plan_options_json(shared merge);cuda_spectral_plan_options_from_json/hip_spectral_plan_options_from_jsonapply the same HeFFTe JSON overlay as CPU but start from cuFFT / ROCm defaults (GPU integration tests and future GPUApppaths).
Planned steps:
Optional: templated
SpectralSimulationSessionor type-erased FFT at the session boundary soAppcan skip constructing a dummyCpuFftfor GPU-only models. (Design note inspectral_cpu_stack.hppDoxygen@note.)Documented interim policy (Doxygen): reuse the one
SpectralCpuStackCpuFftforModel(fft, world, comm)when adding GPU drivers; usespectral_fft_stack_factory.hppfor cuFFT/ROCm plan JSON only—no second throwaway CPU FFT in app code.
Phase C spike (time-boxed exploration)¶
Purpose: validate a single JSON document driving either CPU or GPU spectral stacks without committing to a full App rewrite.
Spike scope (1–2 weeks of prototyping, not merge criteria by itself):
Build a throwaway or feature-flagged “spectral session” type that owns
World,Decomposition, and an FFT handle produced either fromcpu_fft_from_json_and_decompositionor from the GPU plan builders inspectral_fft_stack_factory.hpp, usingmerged_spectral_plan_options_jsonso rootbackendandplan_optionsbehave like today’s CPU path.Wire
Timeand a minimalModelstub (existing mock or smallest example model) through the samewire_simulator_and_runtime_from_jsonentry points to prove IC/BC/result wiring does not depend onfft::CpuFftspecifically.Measure what must become type-erased at the session boundary (e.g.
IFFT &vs concreteCpuFft) and list API breaks for shipped apps ifSpectralSimulationSessionwere templated on FFT type.
Exit criteria for closing the spike (documentation-only deliverable is OK):
Short decision: templated session vs type-erased FFT interface vs defer until a GPU-first
Appis scheduled.List of must-keep JSON keys and test gaps (MPI rank, GPU-aware MPI, VTK writers) before any production merge.
Phase E — Wiring and driver ergonomics¶
Goal: Fewer repeated parameters at JSON → Simulator boundaries; clearer seams for custom drivers and tests.
Layering: Confirmed include/openpfc/kernel / src/openpfc/kernel do not include openpfc/frontend headers (see architecture.md Include audit).
Done:
JsonWiringContext(simulation_wiring_context.hpp): bundlesMPI_Comm,mpi_rank, andrank0foradd_result_writers_from_json,add_initial_conditions_from_json,add_boundary_conditions_from_json, andwire_simulator_and_runtime_from_json. Legacy(comm, rank, rank0)overloads forward to the context form;SpectralSimulationSessionuses the context overload.configure_spectral_json_driver_hooks(spectral_json_driver_hooks.hpp): one call setsfrom_jsonlog rank and default NaN-check communicator;App::mainuses it instead of duplicating globals.write_scheduled_simulator_results(Simulator&)insimulator.hpp: extracted fromSimulator::write_results()so scheduled writes + counter bump live in one free-function seam (io_results.md).results_writer_catalog.hpp+ optionalfields[].writerstring:add_result_writers_from_jsonresolves writers throughResultsWriterCatalog(defaultbinary); inject a custom catalog at the wiring call site for tests and app-specific formats (app_pipeline.md).errors.hppsplit:errors_config_format.hpp(JSON field messages +get_json_value_string) anderrors_field_modifiers.hpp(unknown modifier type +list_valid_field_modifiers);errors.hppremains an umbrella include.from_json_world_time.hppincludes only the format header;field_modifier_registry.hppincludes only the modifier header.GPU FFT factories:
runtime/common/heffte_gpu_r2c_layout.hppshares MPI rank/size helpers, HeFFTe box conversion, default r2c layout boxes, and MPI/decomposition mismatch checks betweenfft_cuda.cppandfft_hip.cpp.JSON IC/BC
from_json:from_json_field_modifiers.hppcentralizes the repeated"type"guard indetail::throw_unless_json_modifier_type(same exception strings as before).pfc::time::: free spellings intime.hpp(current,dt,done,next,do_save, …) aligned withpfc::get_time/ simulator integrator usage.SimulatorIntegratorLoopEnv:app_integrator_loop.hpp— primaryrun_simulator_time_integration_loop(session, env)plus legacy overload that packs MPI rank, profiler, and logger.Test fixtures:
tests/fixtures/simulation_factories.hpp(make_world_cube_8,make_serial_decomposition) for repeated simulator/world setup.Include hygiene: include_hygiene.md and
scripts/check_minimal_includes.sh(optional gate: no umbrellaopenpfc/openpfc.hppintests/unit/kernel).
Backlog — larger SOLID-oriented refactors¶
High impact, not tied to a single PR; pick by maintenance pain.
Suggested PR-scale moves (free-function & data-centric API)¶
Aligned with the ownership and extension boundaries and the styleguide API shape: keep virtual boundaries thin; push mechanics to namespaced free functions.
Examples + apps: mechanical pass replacing
model.get_world()/get_fft()member spellings withpfc::get_world(model)/pfc::get_fft(model)(and simulator analogs) in touched files — high visibility, low risk.Shipped models (Tungsten, Aluminum, diffusion fixtures): extract
initialize/stepinternals intonamespace …::free functions; leaveModel::stepas a one-line forwarder (easier testing and profiling).Time: add small free wrappers (pfc::time::…orpfc::overloads) mirroring hot members (next,done, …) where it improves consistency withModel/Simulatorfree APIs.errors.hpp: split by concern + prefer freeformat_*/make_*helpers so parsers do not pull unrelated types.GPU runtime (
runtime/cudavsruntime/hip): deduplicate withruntime/commonfree helpers (plan/layout/device buffer) instead of parallel class hierarchies.JSON wiring: extend catalog/factory patterns (already: field modifiers, results writers) for any remaining
if (type == …)branches in wiring.SpectralCpuStack/ session: optional freeassemble_*+wire_*for drivers that skipApp. Note:fft::CpuFftis not movable; a “return struct of parts” API cannot move the FFT out of a temporary—use out-parameters, or keep constructingCpuFftinsideSpectralCpuStack’s initializer list (current approach).Integrator loop: narrow
run_simulator_time_integration_loopinputs to structs + free functions (less hidden state than callbacks on opaque objects). Done:SimulatorIntegratorLoopEnv+ primary overload inapp_integrator_loop.hpp.Tests: shared
tests/fixtures/free factories (make_world,make_mock_model, …). Started:simulation_factories.hpp(8³ world + serial decomposition); extend as more tests adopt it.Include hygiene: document + optionally CI-check “minimal includes”. Done: include_hygiene.md +
scripts/check_minimal_includes.sh.
Gradient / spatial-operator abstraction: unify spectral (FFT) and finite-difference evaluation of gradients and related operators where supported; track
adr/0002-gradient-operators-fd-vs-spectral.mdandwhen_not_to_use_openpfc.md.Simulator: If orchestration grows again, consider named collaborators (e.g. explicit IC/BC pipeline type vs results scheduling) on top of existing
*_dispatch.hpphelpers.Model: Narrower test- and tool-facing facades around field registry / world access (interface segregation) without a monolithic
Modelrewrite.Multi-backend apps: Share physics and parameters across Tungsten (and similar) CPU/CUDA/HIP; keep only execution and FFT device setup separate.
Configuration: One story for
ParameterValidator, JSONfrom_json, and docs formodel.paramsso validation behavior matches reader expectations.
Phase D — CMake library split¶
Goal: Enforce kernel vs frontend vs optional GPU objects at link time; faster incremental builds.
Done:
openpfc_kernel_objandopenpfc_frontend_objareOBJECTlibraries;openpfcis built from their objects (same installedlibopenpfc/OpenPFC::openpfcas before). Optional CUDA/HIP FFT.cppfiles stay in the kernel object list when enabled.
How to use this doc¶
Update the Phase A–E bullets and Backlog when work lands or priorities change. Link relevant PRs or commits in project notes if desired (not required in-repo).