# 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

- Python with fluids installed

- PyD: https://pyd.readthedocs.io/en/latest/

## 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