From ac347708514d37ad05a72c659670f970bbe78f33 Mon Sep 17 00:00:00 2001 From: Hanno Spreeuw Date: Thu, 26 Feb 2026 13:07:00 +0100 Subject: [PATCH 01/12] Make "nj_max" readable outside Sapporo Light The goal here is to make "nj_max" accessible at the level of the controlling Python script. The approach is to add a Sapporo Light function that will return "nj_max", next add a PH4 function that can call this function and, subsequentlty, provide a Python interface to this function, i.e. a layered approach. --- lib/sapporo_light/sapporo.cpp | 16 ++++++++++++++++ lib/sapporo_light/sapporo.h | 2 ++ src/amuse_ph4/interface.cc | 10 ++++++++++ src/amuse_ph4/interface.py | 8 ++++++++ 4 files changed, 36 insertions(+) diff --git a/lib/sapporo_light/sapporo.cpp b/lib/sapporo_light/sapporo.cpp index 3ad6e5ab83..8136715760 100644 --- a/lib/sapporo_light/sapporo.cpp +++ b/lib/sapporo_light/sapporo.cpp @@ -270,3 +270,19 @@ int sapporo::get_ngb_list(int cluster_id, sort(nbl, nbl + min(nblen, maxlength)); return overflow; } + +int sapporo::get_nj_max() const +{ + return nj_max; +} + +extern "C" { + +int sapporo_get_nj_max(sapporo *s, int *value) +{ + if (!s) return -1; + *value = s->get_nj_max(); + return 0; +} + +} \ No newline at end of file diff --git a/lib/sapporo_light/sapporo.h b/lib/sapporo_light/sapporo.h index cb8b60ce59..ed0a9fcad6 100644 --- a/lib/sapporo_light/sapporo.h +++ b/lib/sapporo_light/sapporo.h @@ -182,8 +182,10 @@ class sapporo { device.acc_i = NULL; device.vel_i = NULL; device.jrk_i = NULL; + }; + int get_nj_max() const; ~sapporo() {}; int open(int cluster_id); diff --git a/src/amuse_ph4/interface.cc b/src/amuse_ph4/interface.cc index 970b9f4409..f1f6a9b192 100644 --- a/src/amuse_ph4/interface.cc +++ b/src/amuse_ph4/interface.cc @@ -936,3 +936,13 @@ int get_id_of_updated_particle(int index, int * index_of_particle, int * status) *status = jd->UpdatedParticles[index].status; return 0; } + +extern "C" { + int sapporo_get_nj_max(int *value); +} + +int get_nj_max(int *value) +{ + if (!jd) return -1; + return sapporo_get_nj_max(value); +} diff --git a/src/amuse_ph4/interface.py b/src/amuse_ph4/interface.py index 0c9f26062c..6f8e784d84 100644 --- a/src/amuse_ph4/interface.py +++ b/src/amuse_ph4/interface.py @@ -464,6 +464,14 @@ def recompute_timesteps(): function.result_type = 'int32' return function + def get_nj_max(self): + """ + Returns the maximum number of particles supported by Sapporo Light. + """ + function = LegacyFunctionSpecification() + return function( + returns=int, + ) class ph4(GravitationalDynamics,GravityFieldCode): From 9c465b5d4242781c89d1c4edc69e7e5f63e2b3c3 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 4 Mar 2026 11:09:28 +0100 Subject: [PATCH 02/12] This code should not be needed. According to the AMUSE guideline https://amuse.readthedocs.io/en/latest/tutorial/legacy_code.html we should be able to leave "src/amuse_ph4/interface.cc" untouched, i.e. exactly the same as in the "main" branch. I.e., this code should not be necessary. --- src/amuse_ph4/interface.cc | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/amuse_ph4/interface.cc b/src/amuse_ph4/interface.cc index f1f6a9b192..9fe8462479 100644 --- a/src/amuse_ph4/interface.cc +++ b/src/amuse_ph4/interface.cc @@ -935,14 +935,4 @@ int get_id_of_updated_particle(int index, int * index_of_particle, int * status) *index_of_particle = jd->UpdatedParticles[index].index_of_particle; *status = jd->UpdatedParticles[index].status; return 0; -} - -extern "C" { - int sapporo_get_nj_max(int *value); -} - -int get_nj_max(int *value) -{ - if (!jd) return -1; - return sapporo_get_nj_max(value); -} + } From d868f8a4a0204b10e5553b1b09e788b9277eb615 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 4 Mar 2026 11:14:16 +0100 Subject: [PATCH 03/12] Added space --- src/amuse_ph4/interface.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amuse_ph4/interface.cc b/src/amuse_ph4/interface.cc index 9fe8462479..f964ac0d0f 100644 --- a/src/amuse_ph4/interface.cc +++ b/src/amuse_ph4/interface.cc @@ -935,4 +935,4 @@ int get_id_of_updated_particle(int index, int * index_of_particle, int * status) *index_of_particle = jd->UpdatedParticles[index].index_of_particle; *status = jd->UpdatedParticles[index].status; return 0; - } + } \ No newline at end of file From 07a18207308c8c751646c3b466d871d0103e39f3 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 4 Mar 2026 11:16:06 +0100 Subject: [PATCH 04/12] Make "interface.cc" exactly the same as in "main" --- src/amuse_ph4/interface.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amuse_ph4/interface.cc b/src/amuse_ph4/interface.cc index f964ac0d0f..970b9f4409 100644 --- a/src/amuse_ph4/interface.cc +++ b/src/amuse_ph4/interface.cc @@ -935,4 +935,4 @@ int get_id_of_updated_particle(int index, int * index_of_particle, int * status) *index_of_particle = jd->UpdatedParticles[index].index_of_particle; *status = jd->UpdatedParticles[index].status; return 0; - } \ No newline at end of file +} From 7c9bb6385a89ec13a2044d76a493a441d55a27d0 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Fri, 6 Mar 2026 10:37:59 +0100 Subject: [PATCH 05/12] Move "get_nj_max" to "sapporoG6lib.cpp" In "sapporoG6lib.cpp" we can do "return grav.get_nj_max()" since that has "sapporo grav;". --- lib/sapporo_light/sapporo.cpp | 11 ----------- lib/sapporo_light/sapporo.h | 12 +++++++++++- lib/sapporo_light/sapporoG6lib.cpp | 6 ++++++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/sapporo_light/sapporo.cpp b/lib/sapporo_light/sapporo.cpp index 8136715760..1afd0c1c4e 100644 --- a/lib/sapporo_light/sapporo.cpp +++ b/lib/sapporo_light/sapporo.cpp @@ -275,14 +275,3 @@ int sapporo::get_nj_max() const { return nj_max; } - -extern "C" { - -int sapporo_get_nj_max(sapporo *s, int *value) -{ - if (!s) return -1; - *value = s->get_nj_max(); - return 0; -} - -} \ No newline at end of file diff --git a/lib/sapporo_light/sapporo.h b/lib/sapporo_light/sapporo.h index ed0a9fcad6..73ecd4f1ff 100644 --- a/lib/sapporo_light/sapporo.h +++ b/lib/sapporo_light/sapporo.h @@ -169,7 +169,7 @@ class sapporo { predict = false; nj_modified = 0; - + device.address_j = NULL; device.t_j = NULL; @@ -232,3 +232,13 @@ class sapporo { }; #endif + +#ifdef __cplusplus +extern "C" { +#endif + +int g6_get_nj_max_(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/sapporo_light/sapporoG6lib.cpp b/lib/sapporo_light/sapporoG6lib.cpp index 3aff64394f..37ad6692fd 100644 --- a/lib/sapporo_light/sapporoG6lib.cpp +++ b/lib/sapporo_light/sapporoG6lib.cpp @@ -81,3 +81,9 @@ extern "C" { } +extern "C" { + int g6_get_nj_max_() { + return grav.get_nj_max(); + } +} + From fdf0468a9a579c59b6b189216f94c27a987ff006 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Fri, 6 Mar 2026 10:52:36 +0100 Subject: [PATCH 06/12] This compiles, but does not run This compiles, i.e. "./setup install sapporo_light" and "./setup install amuse-ph4" will complete without error. However, Erwan's script with an additional "print(f"{code.get_nj_max() = }, ")" will yield " TypeError: ph4Interface.get_nj_max() takes 0 positional arguments but 1 was given " --- src/amuse_ph4/interface.cc | 7 +++++++ src/amuse_ph4/interface.py | 10 +++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/amuse_ph4/interface.cc b/src/amuse_ph4/interface.cc index 970b9f4409..9efaced7a7 100644 --- a/src/amuse_ph4/interface.cc +++ b/src/amuse_ph4/interface.cc @@ -936,3 +936,10 @@ int get_id_of_updated_particle(int index, int * index_of_particle, int * status) *status = jd->UpdatedParticles[index].status; return 0; } + +extern "C" int g6_get_nj_max_(); + +int get_nj_max() +{ + return g6_get_nj_max_(); +} \ No newline at end of file diff --git a/src/amuse_ph4/interface.py b/src/amuse_ph4/interface.py index 6f8e784d84..c1675660c5 100644 --- a/src/amuse_ph4/interface.py +++ b/src/amuse_ph4/interface.py @@ -464,14 +464,10 @@ def recompute_timesteps(): function.result_type = 'int32' return function - def get_nj_max(self): - """ - Returns the maximum number of particles supported by Sapporo Light. - """ + def get_nj_max(): function = LegacyFunctionSpecification() - return function( - returns=int, - ) + function.result_type = 'int32' + return function class ph4(GravitationalDynamics,GravityFieldCode): From a10558bbad3060a992147255ec7056f85128baee Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 11 Mar 2026 20:06:17 +0100 Subject: [PATCH 07/12] This function definition need not be separate This function definition does not need to be separated. --- lib/sapporo_light/sapporoG6lib.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/sapporo_light/sapporoG6lib.cpp b/lib/sapporo_light/sapporoG6lib.cpp index 37ad6692fd..c4c4481949 100644 --- a/lib/sapporo_light/sapporoG6lib.cpp +++ b/lib/sapporo_light/sapporoG6lib.cpp @@ -49,6 +49,9 @@ extern "C" { return grav.calc_lasthalf2(*cluster_id, *nj, *ni, index, xi, vi, *eps2, h2, acc, jerk, pot, inn); } + int g6_get_nj_max_() { + return grav.get_nj_max(); + } int g6_initialize_jp_buffer_(int* cluster_id, int* buf_size) {return 0;} int g6_flush_jp_buffer_(int* cluster_id) {return 0;} @@ -80,10 +83,3 @@ extern "C" { } - -extern "C" { - int g6_get_nj_max_() { - return grav.get_nj_max(); - } -} - From f8feb3968738d2d3e3f494dcfd04fd3131c57e4b Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 11 Mar 2026 20:10:03 +0100 Subject: [PATCH 08/12] 'extern "C"' not needed here 1) 'extern "C"' not needed here 2) We need to adhere to AMUSE standards, I guess the legacy decorator implies that we need to set an argument 'int * value' and '*value = jd->get_nj_max(); return 0;' or we will be returned an empty list instead of an integer. 3) As a consequence of 2) we need to add an argument to the function: 'function.addParameter(...'. --- src/amuse_ph4/interface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/amuse_ph4/interface.py b/src/amuse_ph4/interface.py index c1675660c5..dca15222bf 100644 --- a/src/amuse_ph4/interface.py +++ b/src/amuse_ph4/interface.py @@ -464,8 +464,11 @@ def recompute_timesteps(): function.result_type = 'int32' return function + @legacy_function def get_nj_max(): function = LegacyFunctionSpecification() + function.addParameter('nj_max', dtype='int32', + direction=function.OUT) function.result_type = 'int32' return function From 3321825b9e899991978bc9c778b5faad6d52d106 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 11 Mar 2026 20:21:44 +0100 Subject: [PATCH 09/12] "interface.cc" should have been added "interface.cc" should have been added as part of the previous commit. --- src/amuse_ph4/interface.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/amuse_ph4/interface.cc b/src/amuse_ph4/interface.cc index 9efaced7a7..7357091895 100644 --- a/src/amuse_ph4/interface.cc +++ b/src/amuse_ph4/interface.cc @@ -937,9 +937,8 @@ int get_id_of_updated_particle(int index, int * index_of_particle, int * status) return 0; } -extern "C" int g6_get_nj_max_(); - -int get_nj_max() +int get_nj_max(int * value) { - return g6_get_nj_max_(); -} \ No newline at end of file + *value = jd->get_nj_max(); + return 0; +} From c65f686b9a1f7400ccd2104381752bf7971ec7f4 Mon Sep 17 00:00:00 2001 From: HannoSpreeuw Date: Wed, 11 Mar 2026 20:28:44 +0100 Subject: [PATCH 10/12] Prototype and implementation of jdata::get_nj_max. Essential is the '#ifdef GPU', since, when one executes "./setup install amuse-ph4" one will not link with Sapporo as one would when executing "./setup install amuse-ph4-sapporo". So "GPU" and "Sapporo" go hand in hand and one needs to make sure that "./setup install amuse-ph4" will complete without error when no GPU is available. --- src/amuse_ph4/src/jdata.cc | 10 ++++++++++ src/amuse_ph4/src/jdata.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/amuse_ph4/src/jdata.cc b/src/amuse_ph4/src/jdata.cc index f5530ee36a..2b48614eb6 100644 --- a/src/amuse_ph4/src/jdata.cc +++ b/src/amuse_ph4/src/jdata.cc @@ -34,6 +34,7 @@ #ifdef GPU #include "grape.h" +#include "sapporo.h" #endif // AMUSE STOPPING CONDITIONS SUPPORT @@ -1153,3 +1154,12 @@ real jdata::get_tnext() int ndum; return sched->get_list(NULL, ndum); } + +int jdata::get_nj_max() +{ +#ifdef GPU + return g6_get_nj_max_(); +#else + return 0; +#endif +} diff --git a/src/amuse_ph4/src/jdata.h b/src/amuse_ph4/src/jdata.h index 9ec464dfb7..069bd57095 100644 --- a/src/amuse_ph4/src/jdata.h +++ b/src/amuse_ph4/src/jdata.h @@ -212,6 +212,7 @@ class jdata { void spec_output(const char *s = NULL); void to_com(); real get_tnext(); + int get_nj_max(); // In gpu.cc: From e71a85a4dbe82e6fe28e5c85781f51a28d39522d Mon Sep 17 00:00:00 2001 From: Hanno Spreeuw Date: Thu, 12 Mar 2026 16:51:47 +0100 Subject: [PATCH 11/12] Things will go wrong when there's a header include loop Following@LourensVeen recommendation to prevent things from going wrong when there's a header include loop. --- lib/sapporo_light/sapporo.h | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/sapporo_light/sapporo.h b/lib/sapporo_light/sapporo.h index 73ecd4f1ff..1a2469e8ef 100644 --- a/lib/sapporo_light/sapporo.h +++ b/lib/sapporo_light/sapporo.h @@ -103,6 +103,9 @@ extern "C" { int get_device_count(); cudaError_t host_evaluate_gravity(sapporo_multi_struct); +#ifdef __cplusplus + int g6_get_nj_max_(); +#endif } class sapporo { @@ -185,12 +188,12 @@ class sapporo { }; - int get_nj_max() const; ~sapporo() {}; int open(int cluster_id); int close(int cluster_id); int get_n_pipes(); + int get_nj_max() const; int set_ti(int cluster_id, double ti); int set_j_particle(int cluster_id, @@ -232,13 +235,3 @@ class sapporo { }; #endif - -#ifdef __cplusplus -extern "C" { -#endif - -int g6_get_nj_max_(); - -#ifdef __cplusplus -} -#endif \ No newline at end of file From dd08d1f38c8bbd031894c2e6ce55d42d469d92d5 Mon Sep 17 00:00:00 2001 From: Hanno Spreeuw Date: Thu, 12 Mar 2026 16:55:34 +0100 Subject: [PATCH 12/12] Accommodate CPU users Following @LourensVeen's recommendation: When running PH4 simulations on a CPU instead of a GPU, Sapporo Light's "nj_max" does not play a role, so one will want to return a different very large number indicating the maximum number of stars allowed for the simulation. In case the number of stars in a simulation running on a CPU will approach "nj_max = std::numeric_limits::max();" one will probably run into a memory error, but that's another matter. --- src/amuse_ph4/src/jdata.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amuse_ph4/src/jdata.cc b/src/amuse_ph4/src/jdata.cc index 2b48614eb6..9acef22d21 100644 --- a/src/amuse_ph4/src/jdata.cc +++ b/src/amuse_ph4/src/jdata.cc @@ -1160,6 +1160,6 @@ int jdata::get_nj_max() #ifdef GPU return g6_get_nj_max_(); #else - return 0; + return std::numeric_limits::max(); #endif }