11#include < iostream>
22#include < vector>
33#include < algorithm>
4- #include < execution>
54#include < cassert>
65#include < chrono>
76#include < functional>
87#include < cmath>
8+ #include < numeric>
9+
10+ // Check for parallel execution support
11+ #if defined(__cpp_lib_execution) && __cpp_lib_execution >= 201902L
12+ #include < execution>
13+ #define PARALLEL_EXECUTION_AVAILABLE
14+ #endif
915
1016/* *
1117 * @brief Test standard library parallelism integration
@@ -58,16 +64,29 @@ void test_std_execution_multiple_conditions() {
5864 std::iota (indices.begin (), indices.end (), 0 );
5965
6066 // Parallel execution using std::execution::par (no custom classes!)
67+ #ifdef PARALLEL_EXECUTION_AVAILABLE
6168 std::for_each (std::execution::par, indices.begin (), indices.end (),
62- [&](size_t i) {
63- auto ode_function = [&](double t, const std::vector<double >& y, std::vector<double >& dydt) {
69+ [&system, &states, ×, dt, steps ](size_t i) {
70+ auto ode_function = [&system ](double t, const std::vector<double >& y, std::vector<double >& dydt) {
6471 system (t, y, dydt);
6572 };
6673
67- for (int step = 0 ; step < steps; ++step ) {
74+ for (int j = 0 ; j < steps; ++j ) {
6875 euler_step (ode_function, states[i], times[i], dt);
6976 }
7077 });
78+ #else
79+ // Sequential fallback for platforms without parallel execution support
80+ for (size_t i : indices) {
81+ auto ode_function = [&system](double t, const std::vector<double >& y, std::vector<double >& dydt) {
82+ system (t, y, dydt);
83+ };
84+
85+ for (int j = 0 ; j < steps; ++j) {
86+ euler_step (ode_function, states[i], times[i], dt);
87+ }
88+ }
89+ #endif
7190
7291 // Verify exponential decay
7392 double expected = 1.0 * std::exp (-system.decay_rate * steps * dt);
@@ -94,8 +113,9 @@ void test_parameter_sweep() {
94113 std::iota (indices.begin (), indices.end (), 0 );
95114
96115 // Parallel parameter sweep using std::execution
116+ #ifdef PARALLEL_EXECUTION_AVAILABLE
97117 std::for_each (std::execution::par, indices.begin (), indices.end (),
98- [&](size_t i) {
118+ [&decay_rates, &results, ×, &initial_state, dt, steps ](size_t i) {
99119 results[i] = initial_state;
100120 times[i] = 0.0 ;
101121
@@ -104,10 +124,26 @@ void test_parameter_sweep() {
104124 dydt[0 ] = -decay_rate * y[0 ]; // Parameterized decay rate
105125 };
106126
107- for (int step = 0 ; step < steps; ++step ) {
127+ for (int j = 0 ; j < steps; ++j ) {
108128 euler_step (ode_function, results[i], times[i], dt);
109129 }
110130 });
131+ #else
132+ // Sequential fallback for platforms without parallel execution support
133+ for (size_t i : indices) {
134+ results[i] = initial_state;
135+ times[i] = 0.0 ;
136+
137+ double decay_rate = decay_rates[i];
138+ auto ode_function = [decay_rate](double t, const std::vector<double >& y, std::vector<double >& dydt) {
139+ dydt[0 ] = -decay_rate * y[0 ]; // Parameterized decay rate
140+ };
141+
142+ for (int j = 0 ; j < steps; ++j) {
143+ euler_step (ode_function, results[i], times[i], dt);
144+ }
145+ }
146+ #endif
111147
112148 // Verify that different parameters give different results
113149 assert (results.size () == decay_rates.size ());
@@ -140,21 +176,22 @@ void test_different_integrators() {
140176 std::iota (indices.begin (), indices.end (), 0 );
141177
142178 // Compare different integration methods in parallel
179+ #ifdef PARALLEL_EXECUTION_AVAILABLE
143180 std::for_each (std::execution::par, indices.begin (), indices.end (),
144- [&](size_t i) {
181+ [&system, &euler_results, &rk2_results, dt, steps ](size_t i) {
145182 double time_euler = 0.0 , time_rk2 = 0.0 ;
146183
147- auto ode_function = [&](double t, const std::vector<double >& y, std::vector<double >& dydt) {
184+ auto ode_function = [&system ](double t, const std::vector<double >& y, std::vector<double >& dydt) {
148185 system (t, y, dydt);
149186 };
150187
151188 // Euler method
152- for (int step = 0 ; step < steps; ++step ) {
189+ for (int j = 0 ; j < steps; ++j ) {
153190 euler_step (ode_function, euler_results[i], time_euler, dt);
154191 }
155192
156193 // Simple RK2 (midpoint method)
157- for (int step = 0 ; step < steps; ++step ) {
194+ for (int k = 0 ; k < steps; ++k ) {
158195 std::vector<double > k1 (1 ), k2 (1 ), temp_state (1 );
159196 double temp_time = time_rk2;
160197
@@ -166,6 +203,34 @@ void test_different_integrators() {
166203 time_rk2 += dt;
167204 }
168205 });
206+ #else
207+ // Sequential fallback for platforms without parallel execution support
208+ for (size_t i : indices) {
209+ double time_euler = 0.0 , time_rk2 = 0.0 ;
210+
211+ auto ode_function = [&system](double t, const std::vector<double >& y, std::vector<double >& dydt) {
212+ system (t, y, dydt);
213+ };
214+
215+ // Euler method
216+ for (int j = 0 ; j < steps; ++j) {
217+ euler_step (ode_function, euler_results[i], time_euler, dt);
218+ }
219+
220+ // Simple RK2 (midpoint method)
221+ for (int k = 0 ; k < steps; ++k) {
222+ std::vector<double > k1 (1 ), k2 (1 ), temp_state (1 );
223+ double temp_time = time_rk2;
224+
225+ ode_function (temp_time, rk2_results[i], k1);
226+ temp_state[0 ] = rk2_results[i][0 ] + dt * k1[0 ] / 2.0 ;
227+ temp_time += dt / 2.0 ;
228+ ode_function (temp_time, temp_state, k2);
229+ rk2_results[i][0 ] += dt * k2[0 ];
230+ time_rk2 += dt;
231+ }
232+ }
233+ #endif
169234
170235 // Verify both methods give reasonable results
171236 assert (euler_results[0 ][0 ] > 0.1 && euler_results[0 ][0 ] < 1.0 );
@@ -195,11 +260,11 @@ void test_openmp() {
195260 // OpenMP parallel loop - no custom classes needed!
196261 #pragma omp parallel for
197262 for (int i = 0 ; i < num_conditions; ++i) {
198- auto ode_function = [&](double t, const std::vector<double >& y, std::vector<double >& dydt) {
263+ auto ode_function = [&system ](double t, const std::vector<double >& y, std::vector<double >& dydt) {
199264 system (t, y, dydt);
200265 };
201266
202- for (int step = 0 ; step < steps; ++step ) {
267+ for (int j = 0 ; j < steps; ++j ) {
203268 euler_step (ode_function, states[i], times[i], dt);
204269 }
205270 }
@@ -217,7 +282,7 @@ void test_hardware_detection() {
217282 std::cout << " Testing standard library hardware detection...\n " ;
218283
219284 // std::execution availability
220- #ifdef __cpp_lib_execution
285+ #ifdef PARALLEL_EXECUTION_AVAILABLE
221286 bool std_execution_available = true ;
222287 #else
223288 bool std_execution_available = false ;
0 commit comments