D Integration

This example demonstrates how to use fluids from D.

Source Code

  1import std.stdio;
  2import pyd.pyd;
  3import pyd.embedded;
  4import std.format;
  5import std.math;
  6import std.array;
  7import core.time;
  8import std.datetime.stopwatch;
  9
 10// Initialize Python before using it
 11shared static this() {
 12    py_init();
 13}
 14
 15// Helper to run Python expressions and handle errors
 16auto pyEval(T)(string expr, string namespace = "") {
 17    try {
 18        return py_eval!T(expr, namespace);
 19    } catch (Exception e) {
 20        writeln("Error evaluating: ", expr);
 21        writeln("Error: ", e.msg);
 22        throw e;
 23    }
 24}
 25
 26void testFluids() {
 27    writeln("\nTesting basic fluids functionality...");
 28    
 29    // Import fluids and create a PydObject reference
 30    PydObject fluidsModule = py_import("fluids");
 31    writeln("✓ Successfully imported fluids");
 32    
 33    // Get version using attribute access
 34    string fluidsVersion = fluidsModule.__version__.to_d!string();
 35    writeln("✓ Fluids version: ", fluidsVersion);
 36    
 37    double Re = pyEval!double("Reynolds(V=2.5, D=0.1, rho=1000, mu=0.001)", "fluids");
 38    writeln("✓ Reynolds number calculation: ", Re);
 39    assert(Re > 0);
 40    
 41    // Test friction factor
 42    double fd = pyEval!double("friction_factor(Re=1e5, eD=0.0001)", "fluids");
 43    writeln("✓ Friction factor: ", fd);
 44    assert(0 < fd && fd < 1);
 45}
 46
 47void testAtmosphere() {
 48    writeln("\nTesting atmosphere calculations...");
 49    PydObject fluidsModule = py_import("fluids");    
 50    PydObject atm = py_eval("ATMOSPHERE_1976(5000.0)", "fluids");
 51    
 52    // Get properties
 53    double temp = atm.T.to_d!double();
 54    double pressure = atm.P.to_d!double();
 55    double density = atm.rho.to_d!double();
 56    double gravity = atm.g.to_d!double();
 57    double viscosity = atm.mu.to_d!double();
 58    double thermalConductivity = atm.k.to_d!double();
 59    double sonicVelocity = atm.v_sonic.to_d!double();
 60    
 61    writeln("At 5000m elevation:");
 62    writefln("✓ Temperature: %.4f", temp);
 63    writefln("✓ Pressure: %.4f", pressure);
 64    writefln("✓ Density: %.6f", density);
 65    writefln("✓ Gravity: %.6f", gravity);
 66    writefln("✓ Viscosity: %.6e", viscosity);
 67    writefln("✓ Thermal conductivity: %.6f", thermalConductivity);
 68    writefln("✓ Sonic velocity: %.4f", sonicVelocity);
 69    
 70    // Test static methods
 71    double gHigh = pyEval!double("ATMOSPHERE_1976.gravity(1E5)", "fluids");
 72    writefln("✓ High altitude gravity: %.6f", gHigh);
 73    
 74    double vSonic = pyEval!double("ATMOSPHERE_1976.sonic_velocity(300)", "fluids");
 75    writefln("✓ Sonic velocity at 300K: %.4f", vSonic);
 76    
 77    double mu400 = pyEval!double("ATMOSPHERE_1976.viscosity(400)", "fluids");
 78    writefln("✓ Viscosity at 400K: %.6e", mu400);
 79    
 80    double k400 = pyEval!double("ATMOSPHERE_1976.thermal_conductivity(400)", "fluids");
 81    writefln("✓ Thermal conductivity at 400K: %.6f", k400);
 82}
 83
 84void testTank() {
 85    writeln("\nTesting tank calculations...");
 86    PydObject fluidsModule = py_import("fluids");
 87    
 88    // Test basic tank creation
 89    PydObject T1 = py_eval("TANK(V=10, L_over_D=0.7, sideB='conical', horizontal=False)", "fluids");
 90    writefln("✓ Tank length: %.6f", T1.L.to_d!double());
 91    writefln("✓ Tank diameter: %.6f", T1.D.to_d!double());
 92    
 93    // Test ellipsoidal tank
 94    PydObject tankEllip = py_eval(
 95        "TANK(D=10, V=500, horizontal=False, sideA='ellipsoidal', sideB='ellipsoidal', sideA_a=1, sideB_a=1)",
 96        "fluids"
 97    );
 98    writefln("✓ Ellipsoidal tank L: %.6f", tankEllip.L.to_d!double());
 99    
100    // Test torispherical tank
101    PydObject DIN = py_eval(
102        "TANK(L=3, D=5, horizontal=False, sideA='torispherical', sideB='torispherical', " ~
103        "sideA_f=1, sideA_k=0.1, sideB_f=1, sideB_k=0.1)",
104        "fluids"
105    );
106    
107    writeln("✓ Tank representation: ", DIN.toString());
108    writefln("✓ Height at V=40: %.6f", DIN.method("h_from_V", 40.0).to_d!double());
109    writefln("✓ Volume at h=4.1: %.5f", DIN.method("V_from_h", 4.1).to_d!double());
110    writefln("✓ Surface area at h=2.1: %.5f", DIN.method("SA_from_h", 2.1).to_d!double());
111    
112}
113
114void testReynolds() {
115    writeln("\nTesting Reynolds number calculations:");
116    
117    // Test with density and viscosity
118    double Re1 = pyEval!double("Reynolds(V=2.5, D=0.25, rho=1.1613, mu=1.9E-5)", "fluids");
119    writefln("✓ Re (with rho, mu): %.4f", Re1);
120    assert(abs(Re1 - 38200.6579) < 0.1);
121    
122    // Test with kinematic viscosity
123    double Re2 = pyEval!double("Reynolds(V=2.5, D=0.25, nu=1.636e-05)", "fluids");
124    writefln("✓ Re (with nu): %.4f", Re2);
125    assert(abs(Re2 - 38202.934) < 0.1);
126}
127
128void testPSD() {
129    writeln("\nTesting particle size distribution functionality:");
130    
131    // Create arrays for discrete PSD
132    double[] ds = [240, 360, 450, 562.5, 703, 878, 1097, 1371, 1713, 2141, 2676, 3345, 4181, 5226, 6532];
133    double[] numbers = [65, 119, 232, 410, 629, 849, 990, 981, 825, 579, 297, 111, 21, 1];
134    
135    // Create Python lists from D arrays
136    string dsStr = format("[%(%s,%)]", ds);
137    string numbersStr = format("[%(%s,%)]", numbers);
138    
139    // Create the PSD object
140    PydObject particleDist = py_eval("particle_size_distribution", "fluids");
141    PydObject psd = particleDist.ParticleSizeDistribution(ds, numbers, 0);
142    writeln("✓ Created discrete PSD");
143    
144    // Test mean sizes
145    double d21 = psd.method("mean_size", 2, 1).to_d!double();
146    writefln("✓ Size-weighted mean diameter: %.4f", d21);
147    
148    double d10 = psd.method("mean_size", 1, 0).to_d!double();
149    writefln("✓ Arithmetic mean diameter: %.4f", d10);
150    
151    // Test percentile calculations
152    double d10Percentile = psd.method("dn", 0.1).to_d!double();
153    double d90Percentile = psd.method("dn", 0.9).to_d!double();
154    writefln("✓ D10: %.4f", d10Percentile);
155    writefln("✓ D90: %.4f", d90Percentile);
156    
157    // Test probability functions
158    double pdfVal = psd.method("pdf", 1000.0).to_d!double();
159    double cdfVal = psd.method("cdf", 5000.0).to_d!double();
160    writefln("✓ PDF at 1000: %.4e", pdfVal);
161    writefln("✓ CDF at 5000: %.6f", cdfVal);
162    
163    // Test lognormal distribution
164    PydObject psdLog = particleDist.PSDLognormal(0.5, 5e-6);
165    writeln("✓ Created lognormal PSD");
166    
167    double vssa = psdLog.vssa.to_d!double();
168    writefln("✓ Volume specific surface area: %.2f", vssa);
169    
170    // Calculate span using individual method calls
171    double dn90 = psdLog.method("dn", 0.9).to_d!double();
172    double dn10 = psdLog.method("dn", 0.1).to_d!double();
173    double span = dn90 - dn10;
174    writefln("✓ Span: %.4e", span);
175    
176    // Calculate ratio using individual method calls
177    double dn75 = psdLog.method("dn", 0.75).to_d!double();
178    double dn25 = psdLog.method("dn", 0.25).to_d!double();
179    double ratio7525 = dn75 / dn25;
180    writefln("✓ D75/D25 ratio: %.6f", ratio7525);
181}
182
183void benchmarkFluids() {
184    writeln("\nRunning benchmarks:");
185    
186    // Benchmark friction factor calculation
187    writeln("\nBenchmarking friction_factor:");
188    auto sw = StopWatch(AutoStart.yes);
189    
190    foreach (i; 0..10000) {
191        pyEval!double("friction_factor(Re=1e5, eD=0.0001)", "fluids");
192    }
193    
194    sw.stop();
195    double elapsed = sw.peek().total!"usecs" / 1_000_000.0;
196    writefln("Time for 1e4 friction_factor calls: %.6f seconds", elapsed);
197    writefln("Average time per call: %.6f seconds", elapsed/10000);
198    
199    // Benchmark tank creation
200    writeln("\nBenchmarking TANK creation:");
201    sw.reset();
202    sw.start();
203    
204    foreach (i; 0..1_000) {
205        py_eval(
206            "TANK(L=3, D=5, horizontal=False, sideA='torispherical', sideB='torispherical', " ~
207            "sideA_f=1, sideA_k=0.1, sideB_f=1, sideB_k=0.1)",
208            "fluids"
209        );
210    }
211    
212    sw.stop();
213    elapsed = sw.peek().total!"usecs" / 1_000_000.0;
214    writefln("Average time per creation: %.6f seconds", elapsed/1_000);
215}
216
217void main() {
218    try {
219        writeln("Running fluids tests from D...");
220        testFluids();
221        testAtmosphere();
222        testTank();
223        testReynolds();
224        testPSD();
225        benchmarkFluids();
226        writeln("\nAll tests completed!");
227    } catch (Exception e) {
228        writeln("Test failed with error: ", e.msg);
229    }
230}

Requirements

Usage Notes

  • The example demonstrates basic integration with fluids

  • 15 microsecond friction factor, 36 microsecond tank creation observed by author

  • The script was easy to develop