From 704aabe519e78418162b4453ec60bfebdd2cb63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David--Cl=C3=A9ris=20Timoth=C3=A9e?= Date: Sat, 12 Sep 2026 16:48:58 +0000 Subject: [PATCH 1/6] [CI] Cancel merge-queue run when dockerbuild/doc/phystest fails On the Mergify merge queue, a failure in build_push_docker, make_documentation, or shamrock_linux_acpp_phystests already dooms the merge gate. Cancel the whole run right away instead of waiting on the remaining CI jobs (asan, ubsan, tidy, coverage, pylib) to finish. Assisted-by: Claude --- .github/workflows/main_workflow.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/main_workflow.yml b/.github/workflows/main_workflow.yml index 0f7ddf3f9f..f80e6fa14b 100644 --- a/.github/workflows/main_workflow.yml +++ b/.github/workflows/main_workflow.yml @@ -170,6 +170,27 @@ jobs: with: fail_fast: ${{ inputs.is_merge_queue }} + # On the Mergify merge queue, dockerbuild/doc/phystest failing means the + # merge gate ("all") will fail anyway, so cancel the whole run early + # instead of waiting on the remaining CI jobs (asan, ubsan, tidy, coverage, + # pylib) to finish. + cancel_on_merge_queue_phystest_failure: + name: Cancel run (merge queue phystest failure) + needs: + [build_push_docker, make_documentation, shamrock_linux_acpp_phystests] + if: ${{ always() && inputs.is_merge_queue && contains(needs.*.result, 'failure') }} + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - name: Cancel this workflow run + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + echo "Mergify merge queue: dockerbuild/doc/phystest failed, cancelling run ${{ github.run_id }}." + gh run cancel "${{ github.run_id }}" + shamrock_linux_acpp_pylib: needs: [src_check] name: Tests From 24d9a7bd11f734006aae945a95a3a09cf94bdfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20David--Cl=C3=A9ris?= Date: Wed, 16 Sep 2026 21:49:01 +0200 Subject: [PATCH 2/6] [SPH] extract walltime budget tracking out of evolve_until evolve_until() mixed timestep-loop control flow with wall-clock-budget bookkeeping (when to next check walltime, whether the limit was hit). Move that logic into a standalone WalltimeLimiter struct so the loop body reads as plain control flow. NFC. Assisted-by: Claude Code --- .../sph/include/shammodels/sph/Solver.hpp | 139 +++++++++++------- 1 file changed, 83 insertions(+), 56 deletions(-) diff --git a/src/shammodels/sph/include/shammodels/sph/Solver.hpp b/src/shammodels/sph/include/shammodels/sph/Solver.hpp index cc314fa889..1abecabcdf 100644 --- a/src/shammodels/sph/include/shammodels/sph/Solver.hpp +++ b/src/shammodels/sph/include/shammodels/sph/Solver.hpp @@ -62,6 +62,85 @@ namespace shammodels::sph { i32 iter_count; }; + /** + * @brief Tracks the wall-clock budget for Solver::evolve_until(): decides when the next + * walltime check is due, and whether the limit has been exceeded. + */ + struct WalltimeLimiter { + bool active; + f64 max_walltime; + f64 start_wall_time; + i32 next_check_iter; + + inline WalltimeLimiter(bool active, f64 max_walltime) + : active(active), max_walltime(max_walltime) { + start_wall_time = active ? synced_wtime() : 0; + next_check_iter = active ? 1 : std::numeric_limits::max(); + } + + inline f64 synced_wtime() { + if (active) { + return shamalgs::collective::allreduce_max(shambase::details::get_wtime()); + } + return 0; + } + + /// True if the next walltime check is due at this iteration count + inline bool due(i32 iter_count) const { return active && iter_count >= next_check_iter; } + + /// Must only be called when due(iter_count) is true. Returns true if the walltime + /// limit has been reached, otherwise updates the next check iteration estimate. + inline bool exceeded(i32 iter_count) { + f64 global_walltime = synced_wtime(); + + // if the global walltime is greater than the max walltime + if (global_walltime >= max_walltime) { + if (shamcomm::world_rank() == 0) { + logger::info_ln( + "SPH", + sham::format( + "stopping evolve until because of " + "max_walltime = {:.2f}s > {:.2f}s", + global_walltime, + max_walltime)); + } + return true; + } + + f64 sec_per_iter = (global_walltime - start_wall_time) / static_cast(iter_count); + + auto get_remaining_iters = [&](f64 delta_walltime, f64 factor) -> i32 { + if (sec_per_iter > 0) { + f64 tmp = factor * delta_walltime / sec_per_iter; + if (tmp > std::numeric_limits::max()) { + return std::numeric_limits::max(); + } + return static_cast(tmp); + } + return 1000; // default to 1000 iterations if sec_per_iter is 0 + }; + + i32 iters_to_limit = get_remaining_iters(max_walltime - global_walltime, 0.25); + i32 iters_to_next_check = iters_to_limit; + + next_check_iter = iter_count + std::max(1, iters_to_next_check); + + if (shamcomm::world_rank() == 0) { + logger::info_ln( + "SPH", + sham::format( + "next walltime check in {:.2f}s (niter = {}) global walltime = " + "{:.2f}s (max_walltime = {:.2f}s)", + iters_to_next_check * sec_per_iter, + iters_to_next_check, + global_walltime, + max_walltime)); + } + + return false; + } + }; + /** * @brief The shamrock SPH model * @@ -320,13 +399,6 @@ namespace shammodels::sph { max_walltime)); } - auto synced_wtime = [&]() -> f64 { - if (walltime_limit_active) { - return shamalgs::collective::allreduce_max(shambase::details::get_wtime()); - } - return 0; - }; - auto step = [&]() { Tscal dt = get_dt_sph(); Tscal t = get_time(); @@ -342,10 +414,7 @@ namespace shammodels::sph { evolve_once(); }; - f64 start_wall_time = (walltime_limit_active) ? synced_wtime() : 0; - - i32 next_walltime_check_iter - = walltime_limit_active ? 1 : std::numeric_limits::max(); + WalltimeLimiter walltime_limiter(walltime_limit_active, max_walltime); i32 iter_count = 0; @@ -368,20 +437,9 @@ namespace shammodels::sph { } // if walltime limit is active and the next walltime check is due - if (walltime_limit_active && iter_count >= next_walltime_check_iter) { - f64 global_walltime = synced_wtime(); - - // if the global walltime is greater than the max walltime - if (global_walltime >= max_walltime) { - if (shamcomm::world_rank() == 0) { - logger::info_ln( - "SPH", - sham::format( - "stopping evolve until because of " - "max_walltime = {:.2f}s > {:.2f}s", - global_walltime, - max_walltime)); - } + if (walltime_limiter.due(iter_count)) { + // must be inside the .due if since there is a MPI reduction in exceeded + if (walltime_limiter.exceeded(iter_count)) { return { .reach_target_time = false, .reach_niter_max = false, @@ -389,37 +447,6 @@ namespace shammodels::sph { .iter_count = iter_count, }; } - - f64 sec_per_iter - = (global_walltime - start_wall_time) / static_cast(iter_count); - - auto get_remaining_iters = [&](f64 delta_walltime, f64 factor) -> i32 { - if (sec_per_iter > 0) { - f64 tmp = factor * delta_walltime / sec_per_iter; - if (tmp > std::numeric_limits::max()) { - return std::numeric_limits::max(); - } - return static_cast(tmp); - } - return 1000; // default to 1000 iterations if sec_per_iter is 0 - }; - - i32 iters_to_limit = get_remaining_iters(max_walltime - global_walltime, 0.25); - i32 iters_to_next_check = iters_to_limit; - - next_walltime_check_iter = iter_count + std::max(1, iters_to_next_check); - - if (shamcomm::world_rank() == 0) { - logger::info_ln( - "SPH", - sham::format( - "next walltime check in {:.2f}s (niter = {}) global walltime = " - "{:.2f}s (max_walltime = {:.2f}s)", - iters_to_next_check * sec_per_iter, - iters_to_next_check, - global_walltime, - max_walltime)); - } } } From e73d4be17826c9fe9e837d815df977bb38ba26b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20David--Cl=C3=A9ris?= Date: Thu, 17 Sep 2026 12:01:28 +0200 Subject: [PATCH 3/6] [SPH] move sink predictor step into solvergraph Register the sink predictor's velocity/position leapfrog update as a "sink predictor" node in the solver graph, gated on the "has_sinks" edge via OperationIf (mirroring "sink ext force"), instead of building the nodes ad hoc inside SinkParticlesUpdate::predictor_step every timestep. The call site now just evaluates the registered node. Assisted-by: Claude Code --- .../sph/modules/SinkParticlesUpdate.hpp | 1 - src/shammodels/sph/src/Solver.cpp | 60 ++++++++++++++++++- .../sph/src/modules/SinkParticlesUpdate.cpp | 41 ------------- 3 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/shammodels/sph/include/shammodels/sph/modules/SinkParticlesUpdate.hpp b/src/shammodels/sph/include/shammodels/sph/modules/SinkParticlesUpdate.hpp index d128c429c1..0e02247395 100644 --- a/src/shammodels/sph/include/shammodels/sph/modules/SinkParticlesUpdate.hpp +++ b/src/shammodels/sph/include/shammodels/sph/modules/SinkParticlesUpdate.hpp @@ -41,7 +41,6 @@ namespace shammodels::sph::modules { SinkParticlesUpdate(ShamrockCtx &context, Config &solver_config, Storage &storage) : context(context), solver_config(solver_config), storage(storage) {} - void predictor_step(Tscal dt); void compute_sph_forces(); void corrector_step(Tscal dt); diff --git a/src/shammodels/sph/src/Solver.cpp b/src/shammodels/sph/src/Solver.cpp index 7a33897a74..9051eba2f4 100644 --- a/src/shammodels/sph/src/Solver.cpp +++ b/src/shammodels/sph/src/Solver.cpp @@ -111,6 +111,8 @@ #include "shamsolvergraph/SolverGraph.hpp" #include "shamsolvergraph/edge/IDataEdge.hpp" #include "shamsolvergraph/edge/IDataEdgeSerializable.hpp" +#include "shamsolvergraph/node/ForwardEulerHost.hpp" +#include "shamsolvergraph/node/ForwardEulerHost2Deriv.hpp" #include "shamsolvergraph/node/NodeFreeAlloc.hpp" #include "shamsolvergraph/node/NodeMapEdge.hpp" #include "shamsolvergraph/node/NodeSetEdge.hpp" @@ -845,6 +847,62 @@ void shammodels::sph::Solver::init_solver_graph() { .set_edges(solver_graph.get_edge_ptr>("has_sinks")); } + //////////////////////////////////////////////////////////////////////////////////////// + // sink predictor step (leapfrog kick-drift of the sink particles themselves) + //////////////////////////////////////////////////////////////////////////////////////// + { + solver_graph.register_edge( + "sink_predictor_dt_half", IDataEdge("dt_half", "\\frac{dt}{2}")); + + auto sink_predictor_dt_to_half_dt = solver_graph.register_node( + "sink_predictor_dt_to_half_dt", + NodeMapEdge, IDataEdge>{ + [](const IDataEdge &dt, IDataEdge &half_dt) { + half_dt.data = dt.data / 2; + }}); + shambase::get_check_ref(sink_predictor_dt_to_half_dt) + .set_edges( + sync_data.get_edge_ptr>("dt"), + solver_graph.get_edge_ptr>("sink_predictor_dt_half")); + + auto sink_predictor_vel_update = solver_graph.register_node( + "sink_predictor_vel_update", ForwardEulerHost2Deriv{}); + shambase::get_check_ref(sink_predictor_vel_update) + .set_edges( + solver_graph.get_edge_ptr>("sink_predictor_dt_half"), + sync_data.get_edge_ptr>>("sink_acc_sph"), + sync_data.get_edge_ptr>>("sink_acc_ext"), + sync_data.get_edge_ptr>>("sink_vel")); + + auto sink_predictor_pos_update = solver_graph.register_node( + "sink_predictor_pos_update", ForwardEulerHost{}); + shambase::get_check_ref(sink_predictor_pos_update) + .set_edges( + sync_data.get_edge_ptr>("dt"), + sync_data.get_edge_ptr>>("sink_vel"), + sync_data.get_edge_ptr>>("sink_pos")); + + auto sink_predictor_body = solver_graph.register_node( + "sink_predictor_body", + OperationSequence( + "sink predictor body", + { + // recompute the sink self-gravity at the current (pre-predictor) sink + // positions before using it to kick the sink velocities + solver_graph.get_node_ptr_base("sink ext force"), + sink_predictor_dt_to_half_dt, + sink_predictor_vel_update, + sink_predictor_pos_update, + })); + + // register the actual node that will be used, gated on the same "has_sinks" edge + // maintained by the "sink accretion" section above + auto sink_predictor = solver_graph.register_node( + "sink predictor", OperationIf("sink predictor", sink_predictor_body)); + shambase::get_check_ref(sink_predictor) + .set_edges(solver_graph.get_edge_ptr>("has_sinks")); + } + //////////////////////////////////////////////////////////////////////////////////////// // external force (point mass) accretion //////////////////////////////////////////////////////////////////////////////////////// @@ -2233,7 +2291,7 @@ shammodels::sph::TimestepLog shammodels::sph::Solver::evolve_once() modules::SinkParticlesUpdate sink_update(context, solver_config, storage); - sink_update.predictor_step(dt); + storage.solver_graph.get_node_ref_base("sink predictor").evaluate(); { // beginning of SolverGraph migration diff --git a/src/shammodels/sph/src/modules/SinkParticlesUpdate.cpp b/src/shammodels/sph/src/modules/SinkParticlesUpdate.cpp index 6b6e55e1ee..773203a0f2 100644 --- a/src/shammodels/sph/src/modules/SinkParticlesUpdate.cpp +++ b/src/shammodels/sph/src/modules/SinkParticlesUpdate.cpp @@ -18,49 +18,8 @@ #include "shammodels/sph/modules/SinkParticlesUpdate.hpp" #include "shammath/sphkernels.hpp" #include "shammodels/sph/sink_edges_helper.hpp" -#include "shamsolvergraph/edge/IDataEdge.hpp" -#include "shamsolvergraph/edge/IDataEdgeSerializable.hpp" -#include "shamsolvergraph/node/ForwardEulerHost.hpp" -#include "shamsolvergraph/node/ForwardEulerHost2Deriv.hpp" #include -template class SPHKernel> -void shammodels::sph::modules::SinkParticlesUpdate::predictor_step(Tscal dt) { - - StackEntry stack_loc{}; - - auto &sync = scheduler().synchronized_data; - auto &pos = get_sink_pos(sync); - if (pos.empty()) { - return; - } - - storage.solver_graph.get_node_ref_base("sink ext force").evaluate(); - - using VecEdge = shamrock::solvergraph::IDataEdgeSerializable>; - - auto dt_half_edge = shamrock::solvergraph::IDataEdge::make_shared("dt_half", "dt/2"); - dt_half_edge->data = dt / 2; - - shamrock::solvergraph::ForwardEulerHost2Deriv vel_update{}; - vel_update.set_edges( - dt_half_edge, - sync.template get_edge_ptr("sink_acc_sph"), - sync.template get_edge_ptr("sink_acc_ext"), - sync.template get_edge_ptr("sink_vel")); - vel_update.evaluate(); - - auto dt_edge = shamrock::solvergraph::IDataEdge::make_shared("dt", "dt"); - dt_edge->data = dt; - - shamrock::solvergraph::ForwardEulerHost pos_update{}; - pos_update.set_edges( - dt_edge, - sync.template get_edge_ptr("sink_vel"), - sync.template get_edge_ptr("sink_pos")); - pos_update.evaluate(); -} - template class SPHKernel> void shammodels::sph::modules::SinkParticlesUpdate::corrector_step(Tscal dt) { From bac105943019ce2c926743c3a0ddaa1dfd7dcda9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David--Cl=C3=A9ris=20Timoth=C3=A9e?= Date: Thu, 17 Sep 2026 11:26:28 +0000 Subject: [PATCH 4/6] [solvergraph] Migrate remaining INode classes to EXPAND_NODE_EDGES Converts ExchangeGhostLayerDebugDotGraph, ExtractCounts, GetFieldRefFromLayer and GetObjCntFromLayer to the EXPAND_NODE_EDGES macro, replacing the hand-written Edges struct/set_edges/get_edges boilerplate with the generated equivalent, matching the pattern used elsewhere in shamrock/solvergraph. Assisted-by: Claude --- .../ExchangeGhostLayerDebugDotGraph.hpp | 28 +++++++------------ .../shamrock/solvergraph/ExtractCounts.hpp | 26 +++++++---------- .../solvergraph/GetFieldRefFromLayer.hpp | 27 +++++++----------- .../solvergraph/GetObjCntFromLayer.hpp | 27 +++++++----------- 4 files changed, 40 insertions(+), 68 deletions(-) diff --git a/src/shamrock/include/shamrock/solvergraph/ExchangeGhostLayerDebugDotGraph.hpp b/src/shamrock/include/shamrock/solvergraph/ExchangeGhostLayerDebugDotGraph.hpp index c5ce69d58e..ca5709b633 100644 --- a/src/shamrock/include/shamrock/solvergraph/ExchangeGhostLayerDebugDotGraph.hpp +++ b/src/shamrock/include/shamrock/solvergraph/ExchangeGhostLayerDebugDotGraph.hpp @@ -24,6 +24,13 @@ #include "shamrock/solvergraph/ScalarsEdge.hpp" #include "shamsolvergraph/node/INode.hpp" +#define NODE_EDGES(X_RO, X_RW) \ + /* ------------------- inputs ------------------- */ \ + X_RO(shamrock::solvergraph::ScalarsEdge, object_counts) \ + \ + /* ------------------- outputs ------------------- */ \ + X_RW(shamrock::solvergraph::PatchDataLayerDDShared, ghost_layer) + namespace shamrock::solvergraph { class ExchangeGhostLayerDebugDotGraph : public shamrock::solvergraph::INode { @@ -33,24 +40,7 @@ namespace shamrock::solvergraph { public: ExchangeGhostLayerDebugDotGraph() {} - struct Edges { - const shamrock::solvergraph::ScalarsEdge &object_counts; - shamrock::solvergraph::PatchDataLayerDDShared &ghost_layer; - }; - - inline void set_edges( - std::shared_ptr> object_counts, - std::shared_ptr ghost_layer) { - __internal_set_ro_edges({object_counts}); - __internal_set_rw_edges({ghost_layer}); - } - - inline Edges get_edges() { - return Edges{ - .object_counts = get_ro_edge>(0), - .ghost_layer = get_rw_edge(0), - }; - } + EXPAND_NODE_EDGES(NODE_EDGES) void _impl_evaluate_internal() { auto edges = get_edges(); @@ -155,3 +145,5 @@ namespace shamrock::solvergraph { inline virtual std::string _impl_get_tex() const { return ""; }; }; } // namespace shamrock::solvergraph + +#undef NODE_EDGES diff --git a/src/shamrock/include/shamrock/solvergraph/ExtractCounts.hpp b/src/shamrock/include/shamrock/solvergraph/ExtractCounts.hpp index d60d083fb0..bdba3c0559 100644 --- a/src/shamrock/include/shamrock/solvergraph/ExtractCounts.hpp +++ b/src/shamrock/include/shamrock/solvergraph/ExtractCounts.hpp @@ -21,6 +21,13 @@ #include "shamrock/solvergraph/Indexes.hpp" #include "shamsolvergraph/node/INode.hpp" +#define NODE_EDGES(X_RO, X_RW) \ + /* ------------------- inputs ------------------- */ \ + X_RO(shamrock::solvergraph::IPatchDataLayerRefs, refs) \ + \ + /* ------------------- outputs ------------------- */ \ + X_RW(shamrock::solvergraph::Indexes, counts) + namespace shamrock::solvergraph { class ExtractCounts : public INode { @@ -28,22 +35,7 @@ namespace shamrock::solvergraph { public: ExtractCounts() {} - struct Edges { - const IPatchDataLayerRefs &refs; - Indexes &counts; - }; - - void set_edges( - std::shared_ptr refs, std::shared_ptr> counts) { - __internal_set_ro_edges({refs}); - __internal_set_rw_edges({counts}); - } - - Edges get_edges() { - return Edges{ - .refs = get_ro_edge(0), - .counts = get_rw_edge>(0)}; - } + EXPAND_NODE_EDGES(NODE_EDGES) void _impl_evaluate_internal() { auto edges = get_edges(); @@ -58,3 +50,5 @@ namespace shamrock::solvergraph { std::string _impl_get_tex() const { return "TODO"; } }; } // namespace shamrock::solvergraph + +#undef NODE_EDGES diff --git a/src/shamrock/include/shamrock/solvergraph/GetFieldRefFromLayer.hpp b/src/shamrock/include/shamrock/solvergraph/GetFieldRefFromLayer.hpp index 6166351067..5dbc3a3204 100644 --- a/src/shamrock/include/shamrock/solvergraph/GetFieldRefFromLayer.hpp +++ b/src/shamrock/include/shamrock/solvergraph/GetFieldRefFromLayer.hpp @@ -23,6 +23,13 @@ #include "shamsolvergraph/node/INode.hpp" #include +#define NODE_EDGES(X_RO, X_RW) \ + /* ------------------- inputs ------------------- */ \ + X_RO(shamrock::solvergraph::IPatchDataLayerRefs, source) \ + \ + /* ------------------- outputs ------------------- */ \ + X_RW(shamrock::solvergraph::FieldRefs, out_ref) + namespace shamrock::solvergraph { template @@ -42,23 +49,7 @@ namespace shamrock::solvergraph { const std::string &field_name) : GetFieldRefFromLayer(shambase::get_check_ref(layout), field_name) {} - struct Edges { - const IPatchDataLayerRefs &source; - shamrock::solvergraph::FieldRefs &out_ref; - }; - - void set_edges( - std::shared_ptr source, - std::shared_ptr> out_ref) { - __internal_set_ro_edges({source}); - __internal_set_rw_edges({out_ref}); - } - - Edges get_edges() { - return Edges{ - get_ro_edge(0), - get_rw_edge>(0)}; - } + EXPAND_NODE_EDGES(NODE_EDGES) void _impl_evaluate_internal() { auto edges = get_edges(); @@ -76,3 +67,5 @@ namespace shamrock::solvergraph { std::string _impl_get_tex() const { return "TODO"; } }; } // namespace shamrock::solvergraph + +#undef NODE_EDGES diff --git a/src/shamrock/include/shamrock/solvergraph/GetObjCntFromLayer.hpp b/src/shamrock/include/shamrock/solvergraph/GetObjCntFromLayer.hpp index b04439bb6a..2e91b2f3df 100644 --- a/src/shamrock/include/shamrock/solvergraph/GetObjCntFromLayer.hpp +++ b/src/shamrock/include/shamrock/solvergraph/GetObjCntFromLayer.hpp @@ -22,6 +22,13 @@ #include "shamsolvergraph/node/INode.hpp" #include +#define NODE_EDGES(X_RO, X_RW) \ + /* ------------------- inputs ------------------- */ \ + X_RO(shamrock::solvergraph::IPatchDataLayerRefs, source) \ + \ + /* ------------------- outputs ------------------- */ \ + X_RW(shamrock::solvergraph::Indexes, out_ref) + namespace shamrock::solvergraph { class GetObjCntFromLayer : public INode { @@ -29,23 +36,7 @@ namespace shamrock::solvergraph { public: GetObjCntFromLayer() {} - struct Edges { - const IPatchDataLayerRefs &source; - shamrock::solvergraph::Indexes &out_ref; - }; - - void set_edges( - std::shared_ptr source, - std::shared_ptr> out_ref) { - __internal_set_ro_edges({source}); - __internal_set_rw_edges({out_ref}); - } - - Edges get_edges() { - return Edges{ - .source = get_ro_edge(0), - .out_ref = get_rw_edge>(0)}; - } + EXPAND_NODE_EDGES(NODE_EDGES) void _impl_evaluate_internal() { auto edges = get_edges(); @@ -61,3 +52,5 @@ namespace shamrock::solvergraph { std::string _impl_get_tex() const { return "TODO"; } }; } // namespace shamrock::solvergraph + +#undef NODE_EDGES From a91287db477e6817f4bcdd4e18a4776f1e9104ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20David--Cl=C3=A9ris?= Date: Thu, 17 Sep 2026 15:26:01 +0200 Subject: [PATCH 5/6] fix double extension in name --- .../{run_init_sim_from_other.py.py => run_init_sim_from_other.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/sph/{run_init_sim_from_other.py.py => run_init_sim_from_other.py} (100%) diff --git a/examples/sph/run_init_sim_from_other.py.py b/examples/sph/run_init_sim_from_other.py similarity index 100% rename from examples/sph/run_init_sim_from_other.py.py rename to examples/sph/run_init_sim_from_other.py From 37d01f3839bec3d0d7223fc9b373722feaf482bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:59:20 +0000 Subject: [PATCH 6/6] [gh-action] trigger CI with empty commit