# C Integration

This example demonstrates how to use fluids from C.

## Source Code

      1#define PY_SSIZE_T_CLEAN
      2#include <Python.h>
      3#include <stdio.h>
      4#include <time.h>
      5#include <assert.h>
      6#include <math.h>
      7
      8// Helper function to initialize Python and import fluids
      9PyObject* init_fluids(void) {
     10    Py_Initialize();
     11    PyObject* module = PyImport_ImportModule("fluids");
     12    if (!module) {
     13        PyErr_Print();
     14        fprintf(stderr, "Failed to import fluids module\n");
     15        return NULL;
     16    }
     17    return module;
     18}
     19
     20// Helper to measure time in microseconds
     21long long microseconds_now(void) {
     22    struct timespec ts;
     23    clock_gettime(CLOCK_MONOTONIC, &ts);
     24    return (long long)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL;
     25}
     26
     27void test_atmosphere(PyObject* fluids) {
     28    printf("\nTesting atmosphere at 5000m elevation:\n");
     29
     30    // Create ATMOSPHERE_1976 instance
     31    PyObject* atm_class = PyObject_GetAttrString(fluids, "ATMOSPHERE_1976");
     32    if (!atm_class) {
     33        PyErr_Print();
     34        return;
     35    }
     36
     37    PyObject* args = PyTuple_New(0);
     38    PyObject* kwargs = PyDict_New();
     39    PyDict_SetItemString(kwargs, "Z", PyFloat_FromDouble(5000));
     40
     41    PyObject* atm = PyObject_Call(atm_class, args, kwargs);
     42    if (!atm) {
     43        PyErr_Print();
     44        Py_DECREF(args);
     45        Py_DECREF(kwargs);
     46        Py_DECREF(atm_class);
     47        return;
     48    }
     49
     50    // Test various properties
     51    PyObject* temp = PyObject_GetAttrString(atm, "T");
     52    PyObject* pressure = PyObject_GetAttrString(atm, "P");
     53    PyObject* density = PyObject_GetAttrString(atm, "rho");
     54    PyObject* gravity = PyObject_GetAttrString(atm, "g");
     55    PyObject* viscosity = PyObject_GetAttrString(atm, "mu");
     56    PyObject* conductivity = PyObject_GetAttrString(atm, "k");
     57    PyObject* sonic = PyObject_GetAttrString(atm, "v_sonic");
     58
     59    printf("✓ Temperature: %.4f\n", PyFloat_AsDouble(temp));
     60    printf("✓ Pressure: %.4f\n", PyFloat_AsDouble(pressure));
     61    printf("✓ Density: %.6f\n", PyFloat_AsDouble(density));
     62    printf("✓ Gravity: %.6f\n", PyFloat_AsDouble(gravity));
     63    printf("✓ Viscosity: %.6e\n", PyFloat_AsDouble(viscosity));
     64    printf("✓ Thermal conductivity: %.4f\n", PyFloat_AsDouble(conductivity));
     65    printf("✓ Sonic velocity: %.4f\n", PyFloat_AsDouble(sonic));
     66
     67    // Test static gravity method
     68    PyObject* gravity_method = PyObject_GetAttrString(atm_class, "gravity");
     69    PyObject* gravity_args = PyTuple_New(0);
     70    PyObject* gravity_kwargs = PyDict_New();
     71    PyDict_SetItemString(gravity_kwargs, "Z", PyFloat_FromDouble(1E5));
     72
     73    PyObject* high_gravity = PyObject_Call(gravity_method, gravity_args, gravity_kwargs);
     74    printf("✓ High altitude gravity: %.6f\n", PyFloat_AsDouble(high_gravity));
     75
     76    // Cleanup
     77    Py_DECREF(temp);
     78    Py_DECREF(pressure);
     79    Py_DECREF(density);
     80    Py_DECREF(gravity);
     81    Py_DECREF(viscosity);
     82    Py_DECREF(conductivity);
     83    Py_DECREF(sonic);
     84    Py_DECREF(gravity_method);
     85    Py_DECREF(gravity_args);
     86    Py_DECREF(gravity_kwargs);
     87    Py_DECREF(high_gravity);
     88    Py_DECREF(atm);
     89    Py_DECREF(args);
     90    Py_DECREF(kwargs);
     91    Py_DECREF(atm_class);
     92}
     93
     94void test_expanded_reynolds(PyObject* fluids) {
     95    printf("\nTesting Reynolds number calculations:\n");
     96
     97    // Get Reynolds function
     98    PyObject* reynolds_func = PyObject_GetAttrString(fluids, "Reynolds");
     99    if (!reynolds_func) {
    100        PyErr_Print();
    101        return;
    102    }
    103
    104    // Test with density and viscosity
    105    PyObject* args1 = PyTuple_New(0);
    106    PyObject* kwargs1 = PyDict_New();
    107    PyDict_SetItemString(kwargs1, "V", PyFloat_FromDouble(2.5));
    108    PyDict_SetItemString(kwargs1, "D", PyFloat_FromDouble(0.25));
    109    PyDict_SetItemString(kwargs1, "rho", PyFloat_FromDouble(1.1613));
    110    PyDict_SetItemString(kwargs1, "mu", PyFloat_FromDouble(1.9E-5));
    111
    112    PyObject* re1 = PyObject_Call(reynolds_func, args1, kwargs1);
    113    double re1_val = PyFloat_AsDouble(re1);
    114    printf("✓ Re (with rho, mu): %.4f\n", re1_val);
    115    assert(fabs(re1_val - 38200.6579) < 0.1);
    116
    117    // Test with kinematic viscosity
    118    PyObject* args2 = PyTuple_New(0);
    119    PyObject* kwargs2 = PyDict_New();
    120    PyDict_SetItemString(kwargs2, "V", PyFloat_FromDouble(2.5));
    121    PyDict_SetItemString(kwargs2, "D", PyFloat_FromDouble(0.25));
    122    PyDict_SetItemString(kwargs2, "nu", PyFloat_FromDouble(1.636e-05));
    123
    124    PyObject* re2 = PyObject_Call(reynolds_func, args2, kwargs2);
    125    double re2_val = PyFloat_AsDouble(re2);
    126    printf("✓ Re (with nu): %.4f\n", re2_val);
    127    assert(fabs(re2_val - 38202.934) < 0.1);
    128
    129    // Cleanup
    130    Py_DECREF(reynolds_func);
    131    Py_DECREF(args1);
    132    Py_DECREF(kwargs1);
    133    Py_DECREF(re1);
    134    Py_DECREF(args2);
    135    Py_DECREF(kwargs2);
    136    Py_DECREF(re2);
    137}
    138
    139void test_psd(PyObject* fluids) {
    140    printf("\nTesting particle size distribution functionality:\n");
    141
    142    // Get particle_size_distribution module
    143    PyObject* psd_module = PyObject_GetAttrString(fluids, "particle_size_distribution");
    144    if (!psd_module) {
    145        PyErr_Print();
    146        return;
    147    }
    148
    149    // Create discrete PSD
    150    PyObject* psd_class = PyObject_GetAttrString(psd_module, "ParticleSizeDistribution");
    151    if (!psd_class) {
    152        PyErr_Print();
    153        Py_DECREF(psd_module);
    154        return;
    155    }
    156
    157    // Create lists for ds and numbers
    158    PyObject* ds_list = PyList_New(15);
    159    double ds[] = {240, 360, 450, 562.5, 703, 878, 1097, 1371, 1713,
    160                   2141, 2676, 3345, 4181, 5226, 6532};
    161    for (int i = 0; i < 15; i++) {
    162        PyList_SetItem(ds_list, i, PyFloat_FromDouble(ds[i]));
    163    }
    164
    165    PyObject* numbers_list = PyList_New(14);
    166    double numbers[] = {65, 119, 232, 410, 629, 849, 990, 981, 825,
    167                       579, 297, 111, 21, 1};
    168    for (int i = 0; i < 14; i++) {
    169        PyList_SetItem(numbers_list, i, PyFloat_FromDouble(numbers[i]));
    170    }
    171
    172    PyObject* args = PyTuple_New(0);
    173    PyObject* kwargs = PyDict_New();
    174    PyDict_SetItemString(kwargs, "ds", ds_list);
    175    PyDict_SetItemString(kwargs, "fractions", numbers_list);
    176    PyDict_SetItemString(kwargs, "order", PyLong_FromLong(0));
    177
    178    PyObject* psd = PyObject_Call(psd_class, args, kwargs);
    179    printf("✓ Created discrete PSD\n");
    180
    181    // Test mean sizes
    182    PyObject* mean_size_method = PyObject_GetAttrString(psd, "mean_size");
    183    PyObject* mean_args = PyTuple_Pack(2, PyLong_FromLong(2), PyLong_FromLong(1));
    184    PyObject* d21 = PyObject_Call(mean_size_method, mean_args, NULL);
    185    printf("✓ Size-weighted mean diameter: %.4f\n", PyFloat_AsDouble(d21));
    186
    187    PyObject* mean_args2 = PyTuple_Pack(2, PyLong_FromLong(1), PyLong_FromLong(0));
    188    PyObject* d10 = PyObject_Call(mean_size_method, mean_args2, NULL);
    189    printf("✓ Arithmetic mean diameter: %.4f\n", PyFloat_AsDouble(d10));
    190
    191    // Test percentile calculations
    192    PyObject* dn_method = PyObject_GetAttrString(psd, "dn");
    193    PyObject* d10_args = PyTuple_Pack(1, PyFloat_FromDouble(0.1));
    194    PyObject* d90_args = PyTuple_Pack(1, PyFloat_FromDouble(0.9));
    195
    196    PyObject* d10_percentile = PyObject_Call(dn_method, d10_args, NULL);
    197    PyObject* d90_percentile = PyObject_Call(dn_method, d90_args, NULL);
    198
    199    printf("✓ D10: %.4f\n", PyFloat_AsDouble(d10_percentile));
    200    printf("✓ D90: %.4f\n", PyFloat_AsDouble(d90_percentile));
    201
    202    // Cleanup
    203    Py_DECREF(psd_module);
    204    Py_DECREF(psd_class);
    205    Py_DECREF(ds_list);
    206    Py_DECREF(numbers_list);
    207    Py_DECREF(args);
    208    Py_DECREF(kwargs);
    209    Py_DECREF(psd);
    210    Py_DECREF(mean_size_method);
    211    Py_DECREF(mean_args);
    212    Py_DECREF(d21);
    213    Py_DECREF(mean_args2);
    214    Py_DECREF(d10);
    215    Py_DECREF(dn_method);
    216    Py_DECREF(d10_args);
    217    Py_DECREF(d90_args);
    218    Py_DECREF(d10_percentile);
    219    Py_DECREF(d90_percentile);
    220}
    221
    222void test_fluids(PyObject* fluids) {
    223    printf("Running fluids tests from C...\n");
    224
    225    // Get version
    226    PyObject* version = PyObject_GetAttrString(fluids, "__version__");
    227    if (version) {
    228        const char* version_str = PyUnicode_AsUTF8(version);
    229        printf("✓ Successfully imported fluids\n");
    230        printf("✓ Fluids version: %s\n", version_str);
    231        Py_DECREF(version);
    232    }
    233
    234    // Test Reynolds number calculation
    235    PyObject* reynolds_func = PyObject_GetAttrString(fluids, "Reynolds");
    236    if (reynolds_func) {
    237        PyObject* args = PyTuple_New(0);
    238        PyObject* kwargs = PyDict_New();
    239        PyDict_SetItemString(kwargs, "V", PyFloat_FromDouble(2.5));
    240        PyDict_SetItemString(kwargs, "D", PyFloat_FromDouble(0.1));
    241        PyDict_SetItemString(kwargs, "rho", PyFloat_FromDouble(1000));
    242        PyDict_SetItemString(kwargs, "mu", PyFloat_FromDouble(0.001));
    243
    244        PyObject* result = PyObject_Call(reynolds_func, args, kwargs);
    245        if (result) {
    246            double re = PyFloat_AsDouble(result);
    247            printf("✓ Reynolds number calculation successful: %f\n", re);
    248            assert(re > 0);
    249            Py_DECREF(result);
    250        }
    251
    252        Py_DECREF(args);
    253        Py_DECREF(kwargs);
    254        Py_DECREF(reynolds_func);
    255    }
    256}
    257
    258void benchmark_fluids(PyObject* fluids) {
    259    printf("\nRunning benchmarks:\n");
    260
    261    // Get friction_factor function
    262    PyObject* friction_func = PyObject_GetAttrString(fluids, "friction_factor");
    263    if (!friction_func) {
    264        PyErr_Print();
    265        return;
    266    }
    267
    268    // Get TANK class
    269    PyObject* tank_class = PyObject_GetAttrString(fluids, "TANK");
    270    if (!tank_class) {
    271        PyErr_Print();
    272        Py_DECREF(friction_func);
    273        return;
    274    }
    275
    276    // Benchmark friction_factor
    277    printf("\nBenchmarking friction_factor:\n");
    278    long long start = microseconds_now();
    279
    280    for(int i = 0; i < 1000000; i++) {
    281        PyObject* args = PyTuple_New(0);
    282        PyObject* kwargs = PyDict_New();
    283        PyDict_SetItemString(kwargs, "Re", PyFloat_FromDouble(1e5));
    284        PyDict_SetItemString(kwargs, "eD", PyFloat_FromDouble(0.0001));
    285
    286        PyObject* result = PyObject_Call(friction_func, args, kwargs);
    287
    288        Py_DECREF(result);
    289        Py_DECREF(args);
    290        Py_DECREF(kwargs);
    291    }
    292
    293    long long duration = microseconds_now() - start;
    294    printf("Time for 1e6 friction_factor calls: %lld microseconds\n", duration);
    295    printf("Average time per call: %.6f microseconds\n", duration / 1000000.0);
    296
    297    // Benchmark TANK creation
    298    printf("\nBenchmarking TANK creation:\n");
    299    start = microseconds_now();
    300
    301    for(int i = 0; i < 1000; i++) {
    302        PyObject* args = PyTuple_New(0);
    303        PyObject* kwargs = PyDict_New();
    304        PyDict_SetItemString(kwargs, "L", PyFloat_FromDouble(3));
    305        PyDict_SetItemString(kwargs, "D", PyFloat_FromDouble(5));
    306        PyDict_SetItemString(kwargs, "horizontal", Py_False);
    307        PyDict_SetItemString(kwargs, "sideA", PyUnicode_FromString("torispherical"));
    308        PyDict_SetItemString(kwargs, "sideB", PyUnicode_FromString("torispherical"));
    309        PyDict_SetItemString(kwargs, "sideA_f", PyFloat_FromDouble(1));
    310        PyDict_SetItemString(kwargs, "sideA_k", PyFloat_FromDouble(0.1));
    311        PyDict_SetItemString(kwargs, "sideB_f", PyFloat_FromDouble(1));
    312        PyDict_SetItemString(kwargs, "sideB_k", PyFloat_FromDouble(0.1));
    313
    314        PyObject* tank = PyObject_Call(tank_class, args, kwargs);
    315
    316        Py_DECREF(tank);
    317        Py_DECREF(args);
    318        Py_DECREF(kwargs);
    319    }
    320
    321    duration = microseconds_now() - start;
    322    printf("Average time per tank creation: %.6f microseconds\n", duration / 1000.0);
    323
    324    Py_DECREF(friction_func);
    325    Py_DECREF(tank_class);
    326}
    327
    328void test_tank(PyObject* fluids) {
    329    printf("\nTesting tank calculations:\n");
    330
    331    // Get TANK class
    332    PyObject* tank_class = PyObject_GetAttrString(fluids, "TANK");
    333    if (!tank_class) {
    334        PyErr_Print();
    335        return;
    336    }
    337
    338    // Test basic tank creation
    339    PyObject* args1 = PyTuple_New(0);
    340    PyObject* kwargs1 = PyDict_New();
    341    PyDict_SetItemString(kwargs1, "V", PyFloat_FromDouble(10));
    342    PyDict_SetItemString(kwargs1, "L_over_D", PyFloat_FromDouble(0.7));
    343    PyDict_SetItemString(kwargs1, "sideB", PyUnicode_FromString("conical"));
    344    PyDict_SetItemString(kwargs1, "horizontal", Py_False);
    345
    346    PyObject* T1 = PyObject_Call(tank_class, args1, kwargs1);
    347    if (!T1) {
    348        PyErr_Print();
    349        goto cleanup1;
    350    }
    351
    352    PyObject* length = PyObject_GetAttrString(T1, "L");
    353    PyObject* diameter = PyObject_GetAttrString(T1, "D");
    354    printf("✓ Tank length: %.6f\n", PyFloat_AsDouble(length));
    355    printf("✓ Tank diameter: %.6f\n", PyFloat_AsDouble(diameter));
    356
    357    // Test torispherical tank
    358    PyObject* args2 = PyTuple_New(0);
    359    PyObject* kwargs2 = PyDict_New();
    360    PyDict_SetItemString(kwargs2, "L", PyFloat_FromDouble(3));
    361    PyDict_SetItemString(kwargs2, "D", PyFloat_FromDouble(5));
    362    PyDict_SetItemString(kwargs2, "horizontal", Py_False);
    363    PyDict_SetItemString(kwargs2, "sideA", PyUnicode_FromString("torispherical"));
    364    PyDict_SetItemString(kwargs2, "sideB", PyUnicode_FromString("torispherical"));
    365    PyDict_SetItemString(kwargs2, "sideA_f", PyFloat_FromDouble(1));
    366    PyDict_SetItemString(kwargs2, "sideA_k", PyFloat_FromDouble(0.1));
    367    PyDict_SetItemString(kwargs2, "sideB_f", PyFloat_FromDouble(1));
    368    PyDict_SetItemString(kwargs2, "sideB_k", PyFloat_FromDouble(0.1));
    369
    370    PyObject* DIN = PyObject_Call(tank_class, args2, kwargs2);
    371    if (!DIN) {
    372        PyErr_Print();
    373        goto cleanup2;
    374    }
    375
    376    // Get max height
    377    PyObject* h_max = PyObject_GetAttrString(DIN, "h_max");
    378    printf("✓ Tank max height: %.6f\n", PyFloat_AsDouble(h_max));
    379
    380    // Test h_from_V method
    381    PyObject* h_from_V = PyObject_GetAttrString(DIN, "h_from_V");
    382    PyObject* h_args = PyTuple_Pack(1, PyFloat_FromDouble(40));
    383    PyObject* h_result = PyObject_CallObject(h_from_V, h_args);
    384    printf("✓ Height at V=40: %.6f\n", PyFloat_AsDouble(h_result));
    385
    386    // Test V_from_h method
    387    PyObject* V_from_h = PyObject_GetAttrString(DIN, "V_from_h");
    388    PyObject* v_args = PyTuple_Pack(1, PyFloat_FromDouble(4.1));
    389    PyObject* v_result = PyObject_CallObject(V_from_h, v_args);
    390    printf("✓ Volume at h=4.1: %.5f\n", PyFloat_AsDouble(v_result));
    391
    392    // Cleanup
    393    Py_DECREF(v_result);
    394    Py_DECREF(v_args);
    395    Py_DECREF(V_from_h);
    396    Py_DECREF(h_result);
    397    Py_DECREF(h_args);
    398    Py_DECREF(h_from_V);
    399    Py_DECREF(h_max);
    400    Py_DECREF(DIN);
    401cleanup2:
    402    Py_DECREF(args2);
    403    Py_DECREF(kwargs2);
    404    Py_DECREF(diameter);
    405    Py_DECREF(length);
    406    Py_DECREF(T1);
    407cleanup1:
    408    Py_DECREF(args1);
    409    Py_DECREF(kwargs1);
    410    Py_DECREF(tank_class);
    411}
    412
    413int main() {
    414    PyObject* fluids = init_fluids();
    415    if (!fluids) {
    416        return 1;
    417    }
    418
    419    test_fluids(fluids);
    420    test_atmosphere(fluids);
    421    test_tank(fluids);
    422    test_expanded_reynolds(fluids);
    423    test_psd(fluids);
    424    benchmark_fluids(fluids);
    425
    426    Py_DECREF(fluids);
    427    Py_Finalize();
    428    printf("\nAll tests completed!\n");
    429    return 0;
    430}

## Requirements

- Python with fluids installed

- Development python libraries

## Usage Notes

- The example demonstrates basic integration with fluids

- No overhead (1.3 microsecond friction factor, 7 microsecond tank creation)