C++ Integration¶
This example demonstrates how to use fluids from cpp.
Source Code¶
1#include <pybind11/pybind11.h>
2#include <pybind11/embed.h> // Added this for interpreter management
3#include <pybind11/stl.h>
4#include <chrono>
5#include <iostream>
6#include <iomanip>
7#include <vector>
8
9namespace py = pybind11;
10PYBIND11_EMBEDDED_MODULE(dummy, m) {} // Needed for embedding
11
12class FluidsTester {
13private:
14 py::scoped_interpreter guard; // RAII management of the Python interpreter
15 py::module_ fluids;
16
17public:
18 FluidsTester() {
19 try {
20 // The interpreter is automatically initialized by scoped_interpreter
21 fluids = py::module_::import("fluids");
22 std::cout << "✓ Successfully imported fluids\n";
23 std::cout << "✓ Fluids version: " << fluids.attr("__version__").cast<std::string>() << "\n";
24 } catch (const std::exception& e) {
25 std::cerr << "Error initializing Python: " << e.what() << std::endl;
26 throw;
27 }
28 }
29
30 void test_fluids() {
31 try {
32 // Test basic Reynolds number calculation
33 auto Re = fluids.attr("Reynolds")(
34 py::arg("V") = 2.5,
35 py::arg("D") = 0.1,
36 py::arg("rho") = 1000,
37 py::arg("mu") = 0.001
38 ).cast<double>();
39 std::cout << "✓ Reynolds number calculation successful: " << Re << "\n";
40 assert(Re > 0);
41
42 // Test friction factor calculation
43 auto fd = fluids.attr("friction_factor")(
44 py::arg("Re") = 1e5,
45 py::arg("eD") = 0.0001
46 ).cast<double>();
47 std::cout << "✓ Friction factor calculation successful: " << fd << "\n";
48 assert(fd > 0 && fd < 1);
49
50 std::cout << "\nAll basic tests completed successfully!\n";
51 } catch (const std::exception& e) {
52 std::cerr << "Error in fluids tests: " << e.what() << std::endl;
53 throw;
54 }
55 }
56
57 void test_atmosphere() {
58 try {
59 // Test ATMOSPHERE_1976 class
60 auto atm = fluids.attr("ATMOSPHERE_1976")(py::arg("Z") = 5000);
61
62 std::cout << "\nTesting atmosphere at 5000m elevation:\n";
63 std::cout << "✓ Temperature: " << std::fixed << std::setprecision(4)
64 << atm.attr("T").cast<double>() << "\n";
65 std::cout << "✓ Pressure: " << atm.attr("P").cast<double>() << "\n";
66 std::cout << "✓ Density: " << std::setprecision(6)
67 << atm.attr("rho").cast<double>() << "\n";
68
69 // Test derived properties
70 std::cout << "✓ Gravity: " << atm.attr("g").cast<double>() << "\n";
71 std::cout << "✓ Viscosity: " << std::scientific
72 << atm.attr("mu").cast<double>() << "\n";
73 std::cout << "✓ Thermal conductivity: " << std::fixed
74 << atm.attr("k").cast<double>() << "\n";
75 std::cout << "✓ Sonic velocity: " << std::setprecision(4)
76 << atm.attr("v_sonic").cast<double>() << "\n";
77
78 // Test static methods
79 auto atm_class = fluids.attr("ATMOSPHERE_1976");
80 auto g_high = atm_class.attr("gravity")(py::arg("Z") = 1E5).cast<double>();
81 std::cout << "✓ High altitude gravity: " << std::setprecision(6)
82 << g_high << "\n";
83
84 } catch (const std::exception& e) {
85 std::cerr << "Error in atmosphere tests: " << e.what() << std::endl;
86 throw;
87 }
88 }
89
90 void test_tank() {
91 try {
92 // Test basic tank creation
93 auto T1 = fluids.attr("TANK")(
94 py::arg("V") = 10,
95 py::arg("L_over_D") = 0.7,
96 py::arg("sideB") = "conical",
97 py::arg("horizontal") = false
98 );
99
100 std::cout << "\nTesting tank calculations:\n";
101 std::cout << "✓ Tank length: " << std::fixed << std::setprecision(6)
102 << T1.attr("L").cast<double>() << "\n";
103 std::cout << "✓ Tank diameter: " << T1.attr("D").cast<double>() << "\n";
104
105 // Test torispherical tank
106 auto DIN = fluids.attr("TANK")(
107 py::arg("L") = 3,
108 py::arg("D") = 5,
109 py::arg("horizontal") = false,
110 py::arg("sideA") = "torispherical",
111 py::arg("sideB") = "torispherical",
112 py::arg("sideA_f") = 1,
113 py::arg("sideA_k") = 0.1,
114 py::arg("sideB_f") = 1,
115 py::arg("sideB_k") = 0.1
116 );
117
118 std::cout << "✓ Tank max height: " << DIN.attr("h_max").cast<double>() << "\n";
119 std::cout << "✓ Height at V=40: " << DIN.attr("h_from_V")(40).cast<double>() << "\n";
120 std::cout << "✓ Volume at h=4.1: " << std::setprecision(5)
121 << DIN.attr("V_from_h")(4.1).cast<double>() << "\n";
122
123 } catch (const std::exception& e) {
124 std::cerr << "Error in tank tests: " << e.what() << std::endl;
125 throw;
126 }
127 }
128void test_reynolds() {
129 try {
130 std::cout << "\nTesting Reynolds number calculations:\n";
131
132 // Test with density and viscosity
133 auto Re1 = fluids.attr("Reynolds")(
134 py::arg("V") = 2.5,
135 py::arg("D") = 0.25,
136 py::arg("rho") = 1.1613,
137 py::arg("mu") = 1.9E-5
138 ).cast<double>();
139 std::cout << "✓ Re (with rho, mu): " << std::fixed << std::setprecision(4) << Re1 << "\n";
140 assert(std::abs(Re1 - 38200.6579) < 0.1);
141
142 // Test with kinematic viscosity
143 auto Re2 = fluids.attr("Reynolds")(
144 py::arg("V") = 2.5,
145 py::arg("D") = 0.25,
146 py::arg("nu") = 1.636e-05
147 ).cast<double>();
148 std::cout << "✓ Re (with nu): " << Re2 << "\n";
149 assert(std::abs(Re2 - 38202.934) < 0.1);
150
151 } catch (const std::exception& e) {
152 std::cerr << "Error in Reynolds tests: " << e.what() << std::endl;
153 throw;
154 }
155}
156
157void test_psd() {
158 try {
159 std::cout << "\nTesting particle size distribution functionality:\n";
160
161 // Create vectors for discrete PSD
162 std::vector<double> ds = {240, 360, 450, 562.5, 703, 878, 1097, 1371, 1713,
163 2141, 2676, 3345, 4181, 5226, 6532};
164 std::vector<double> numbers = {65, 119, 232, 410, 629, 849, 990, 981, 825,
165 579, 297, 111, 21, 1};
166
167 // Create a discrete PSD
168 auto psd = fluids.attr("particle_size_distribution").attr("ParticleSizeDistribution")(
169 py::arg("ds") = ds,
170 py::arg("fractions") = numbers,
171 py::arg("order") = 0
172 );
173 std::cout << "✓ Created discrete PSD\n";
174
175 // Test mean sizes
176 auto d21 = psd.attr("mean_size")(2, 1).cast<double>();
177 std::cout << "✓ Size-weighted mean diameter: " << std::fixed
178 << std::setprecision(4) << d21 << "\n";
179 assert(std::abs(d21 - 1857.788) < 0.1);
180
181 auto d10 = psd.attr("mean_size")(1, 0).cast<double>();
182 std::cout << "✓ Arithmetic mean diameter: " << d10 << "\n";
183 assert(std::abs(d10 - 1459.372) < 0.1);
184
185 // Test percentile calculations
186 auto d10_percentile = psd.attr("dn")(0.1).cast<double>();
187 auto d90_percentile = psd.attr("dn")(0.9).cast<double>();
188 std::cout << "✓ D10: " << d10_percentile << "\n";
189 std::cout << "✓ D90: " << d90_percentile << "\n";
190
191 // Test probability functions
192 auto pdf_val = psd.attr("pdf")(1000).cast<double>();
193 auto cdf_val = psd.attr("cdf")(5000).cast<double>();
194 std::cout << "✓ PDF at 1000: " << std::scientific << pdf_val << "\n";
195 std::cout << "✓ CDF at 5000: " << std::fixed << std::setprecision(6) << cdf_val << "\n";
196
197 // Test lognormal distribution
198 auto psd_log = fluids.attr("particle_size_distribution").attr("PSDLognormal")(
199 py::arg("s") = 0.5,
200 py::arg("d_characteristic") = 5E-6
201 );
202 std::cout << "✓ Created lognormal PSD\n";
203
204 auto vssa = psd_log.attr("vssa").cast<double>();
205 std::cout << "✓ Volume specific surface area: " << std::setprecision(2)
206 << vssa << "\n";
207
208 auto span = psd_log.attr("dn")(0.9).cast<double>() -
209 psd_log.attr("dn")(0.1).cast<double>();
210 std::cout << "✓ Span: " << std::scientific << span << "\n";
211
212 auto ratio_7525 = (psd_log.attr("dn")(0.75).cast<double>() /
213 psd_log.attr("dn")(0.25).cast<double>());
214 std::cout << "✓ D75/D25 ratio: " << std::fixed << std::setprecision(6)
215 << ratio_7525 << "\n";
216
217 } catch (const std::exception& e) {
218 std::cerr << "Error in PSD tests: " << e.what() << std::endl;
219 throw;
220 }
221}
222 void benchmark_fluids() {
223 std::cout << "\nRunning benchmarks:\n";
224
225 // Benchmark friction factor calculation
226 std::cout << "\nBenchmarking friction_factor:\n";
227 auto start = std::chrono::high_resolution_clock::now();
228
229 for(int i = 0; i < 1000000; i++) {
230 fluids.attr("friction_factor")(
231 py::arg("Re") = 1e5,
232 py::arg("eD") = 0.0001
233 );
234 }
235
236 auto end = std::chrono::high_resolution_clock::now();
237 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
238
239 std::cout << "Time for 1e6 friction_factor calls: "
240 << duration.count() << " microseconds\n";
241 std::cout << "Average time per call: "
242 << duration.count() / 1000000.0 << " microseconds\n";
243
244 // Benchmark tank creation
245 std::cout << "\nBenchmarking TANK creation:\n";
246 start = std::chrono::high_resolution_clock::now();
247
248 for(int i = 0; i < 1000; i++) {
249 fluids.attr("TANK")(
250 py::arg("L") = 3,
251 py::arg("D") = 5,
252 py::arg("horizontal") = false,
253 py::arg("sideA") = "torispherical",
254 py::arg("sideB") = "torispherical",
255 py::arg("sideA_f") = 1,
256 py::arg("sideA_k") = 0.1,
257 py::arg("sideB_f") = 1,
258 py::arg("sideB_k") = 0.1
259 );
260 }
261
262 end = std::chrono::high_resolution_clock::now();
263 duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
264
265 std::cout << "Average time per tank creation: "
266 << duration.count() / 1000.0 << " microseconds\n";
267 }
268};
269
270int main() {
271 try {
272 std::cout << "Running fluids tests from C++...\n";
273 FluidsTester tester;
274 tester.test_fluids();
275 tester.test_atmosphere();
276 tester.test_tank();
277 tester.test_reynolds();
278 tester.test_psd();
279 tester.benchmark_fluids();
280 std::cout << "\nAll tests completed!\n";
281 return 0;
282 } catch (const std::exception& e) {
283 std::cerr << "Fatal error: " << e.what() << std::endl;
284 return 1;
285 }
286}
Requirements¶
Python with fluids installed
pybind11
Usage Notes¶
The example demonstrates basic integration with fluids
Essentially zero overhead - 1.7 microseconds for friction factor, 8 microseconds for tank creation; only C using the has been observed to have a faster interface, able to save < 0.5 microseconds/call