# go Integration

This example demonstrates how to use fluids from go.

## Source Code

      1package main
      2
      3/*
      4#cgo pkg-config: python3
      5#cgo LDFLAGS: -lpython3.11
      6#define PY_SSIZE_T_CLEAN
      7#include <Python.h>
      8
      9// Helper function to convert C string to Go string without memory leaks
     10static char* getPyUnicodeAsString(PyObject* unicode) {
     11    return (char*)PyUnicode_AsUTF8(unicode);
     12}
     13
     14// Helper to get double from PyFloat
     15static double getPyFloatAsDouble(PyObject* float_obj) {
     16    return PyFloat_AsDouble(float_obj);
     17}
     18*/
     19import "C"
     20import (
     21	"fmt"
     22	"time"
     23	"unsafe"
     24)
     25
     26// Helper functions to convert between Go and Python types
     27func goStringToPyUnicode(s string) *C.PyObject {
     28	cs := C.CString(s)
     29	defer C.free(unsafe.Pointer(cs))
     30	return C.PyUnicode_FromString(cs)
     31}
     32
     33func goFloat64ToPyFloat(v float64) *C.PyObject {
     34	return C.PyFloat_FromDouble(C.double(v))
     35}
     36
     37func goIntToPyLong(v int64) *C.PyObject {
     38	return C.PyLong_FromLongLong(C.longlong(v))
     39}
     40
     41func pyObjectToGoString(obj *C.PyObject) string {
     42	cstr := C.getPyUnicodeAsString(obj)
     43	return C.GoString(cstr)
     44}
     45
     46func pyObjectToGoFloat64(obj *C.PyObject) float64 {
     47	return float64(C.getPyFloatAsDouble(obj))
     48}
     49
     50// Initialize Python interpreter and import fluids module
     51func initFluids() *C.PyObject {
     52	C.Py_Initialize()
     53
     54	moduleName := C.CString("fluids")
     55	defer C.free(unsafe.Pointer(moduleName))
     56
     57	module := C.PyImport_ImportModule(moduleName)
     58	if module == nil {
     59		C.PyErr_Print()
     60		panic("Failed to import fluids module")
     61	}
     62
     63	return module
     64}
     65
     66func testAtmosphere(fluids *C.PyObject) {
     67	fmt.Println("\nTesting atmosphere at 5000m elevation:")
     68
     69	// Get ATMOSPHERE_1976 class
     70	atmClass := C.PyObject_GetAttrString(fluids, C.CString("ATMOSPHERE_1976"))
     71	defer C.free(unsafe.Pointer(C.CString("ATMOSPHERE_1976")))
     72	if atmClass == nil {
     73		C.PyErr_Print()
     74		return
     75	}
     76	defer C.Py_DecRef(atmClass)
     77
     78	// Create instance with Z=5000
     79	args := C.PyTuple_New(0)
     80	kwargs := C.PyDict_New()
     81	C.PyDict_SetItemString(kwargs, C.CString("Z"), goFloat64ToPyFloat(5000))
     82	defer C.free(unsafe.Pointer(C.CString("Z")))
     83
     84	atm := C.PyObject_Call(atmClass, args, kwargs)
     85	if atm == nil {
     86		C.PyErr_Print()
     87		C.Py_DecRef(args)
     88		C.Py_DecRef(kwargs)
     89		return
     90	}
     91	defer C.Py_DecRef(atm)
     92	defer C.Py_DecRef(args)
     93	defer C.Py_DecRef(kwargs)
     94
     95	// Get and print properties
     96	properties := []struct {
     97		name string
     98		format string
     99	}{
    100		{"T", "Temperature: %.4f"},
    101		{"P", "Pressure: %.4f"},
    102		{"rho", "Density: %.6f"},
    103		{"g", "Gravity: %.6f"},
    104		{"mu", "Viscosity: %.6e"},
    105		{"k", "Thermal conductivity: %.4f"},
    106		{"v_sonic", "Sonic velocity: %.4f"},
    107	}
    108
    109	for _, prop := range properties {
    110		cname := C.CString(prop.name)
    111		value := C.PyObject_GetAttrString(atm, cname)
    112		C.free(unsafe.Pointer(cname))
    113		if value == nil {
    114			C.PyErr_Print()
    115			continue
    116		}
    117		fmt.Printf("✓ "+prop.format+"\n", pyObjectToGoFloat64(value))
    118		C.Py_DecRef(value)
    119	}
    120
    121	// Test static gravity method
    122	gravityMethod := C.PyObject_GetAttrString(atmClass, C.CString("gravity"))
    123	defer C.free(unsafe.Pointer(C.CString("gravity")))
    124	if gravityMethod == nil {
    125		C.PyErr_Print()
    126		return
    127	}
    128	defer C.Py_DecRef(gravityMethod)
    129
    130	gravityArgs := C.PyTuple_New(0)
    131	gravityKwargs := C.PyDict_New()
    132	C.PyDict_SetItemString(gravityKwargs, C.CString("Z"), goFloat64ToPyFloat(1E5))
    133	defer C.free(unsafe.Pointer(C.CString("Z")))
    134
    135	highGravity := C.PyObject_Call(gravityMethod, gravityArgs, gravityKwargs)
    136	if highGravity == nil {
    137		C.PyErr_Print()
    138		C.Py_DecRef(gravityArgs)
    139		C.Py_DecRef(gravityKwargs)
    140		return
    141	}
    142
    143	fmt.Printf("✓ High altitude gravity: %.6f\n", pyObjectToGoFloat64(highGravity))
    144
    145	C.Py_DecRef(highGravity)
    146	C.Py_DecRef(gravityArgs)
    147	C.Py_DecRef(gravityKwargs)
    148}
    149
    150func testExpandedReynolds(fluids *C.PyObject) {
    151	fmt.Println("\nTesting Reynolds number calculations:")
    152
    153	reynoldsFunc := C.PyObject_GetAttrString(fluids, C.CString("Reynolds"))
    154	defer C.free(unsafe.Pointer(C.CString("Reynolds")))
    155	if reynoldsFunc == nil {
    156		C.PyErr_Print()
    157		return
    158	}
    159	defer C.Py_DecRef(reynoldsFunc)
    160
    161	// Test with density and viscosity
    162	args1 := C.PyTuple_New(0)
    163	kwargs1 := C.PyDict_New()
    164
    165	params1 := map[string]float64{
    166		"V": 2.5,
    167		"D": 0.25,
    168		"rho": 1.1613,
    169		"mu": 1.9E-5,
    170	}
    171
    172	for k, v := range params1 {
    173		ckey := C.CString(k)
    174		C.PyDict_SetItemString(kwargs1, ckey, goFloat64ToPyFloat(v))
    175		C.free(unsafe.Pointer(ckey))
    176	}
    177
    178	re1 := C.PyObject_Call(reynoldsFunc, args1, kwargs1)
    179	if re1 == nil {
    180		C.PyErr_Print()
    181		C.Py_DecRef(args1)
    182		C.Py_DecRef(kwargs1)
    183		return
    184	}
    185
    186	re1Val := pyObjectToGoFloat64(re1)
    187	fmt.Printf("✓ Re (with rho, mu): %.4f\n", re1Val)
    188
    189	C.Py_DecRef(re1)
    190	C.Py_DecRef(args1)
    191	C.Py_DecRef(kwargs1)
    192
    193	// Test with kinematic viscosity
    194	args2 := C.PyTuple_New(0)
    195	kwargs2 := C.PyDict_New()
    196
    197	params2 := map[string]float64{
    198		"V": 2.5,
    199		"D": 0.25,
    200		"nu": 1.636e-05,
    201	}
    202
    203	for k, v := range params2 {
    204		ckey := C.CString(k)
    205		C.PyDict_SetItemString(kwargs2, ckey, goFloat64ToPyFloat(v))
    206		C.free(unsafe.Pointer(ckey))
    207	}
    208
    209	re2 := C.PyObject_Call(reynoldsFunc, args2, kwargs2)
    210	if re2 == nil {
    211		C.PyErr_Print()
    212		C.Py_DecRef(args2)
    213		C.Py_DecRef(kwargs2)
    214		return
    215	}
    216
    217	re2Val := pyObjectToGoFloat64(re2)
    218	fmt.Printf("✓ Re (with nu): %.4f\n", re2Val)
    219
    220	C.Py_DecRef(re2)
    221	C.Py_DecRef(args2)
    222	C.Py_DecRef(kwargs2)
    223}
    224
    225
    226func testTank(fluids *C.PyObject) {
    227    fmt.Println("\nTesting tank calculations:")
    228
    229    // Get TANK class
    230    tankClass := C.PyObject_GetAttrString(fluids, C.CString("TANK"))
    231    if tankClass == nil {
    232        C.PyErr_Print()
    233        return
    234    }
    235
    236    // Test basic tank creation
    237    args1 := C.PyTuple_New(0)
    238    kwargs1 := C.PyDict_New()
    239
    240    // Set basic tank parameters
    241    C.PyDict_SetItemString(kwargs1, C.CString("V"), goFloat64ToPyFloat(10))
    242    C.PyDict_SetItemString(kwargs1, C.CString("L_over_D"), goFloat64ToPyFloat(0.7))
    243    C.PyDict_SetItemString(kwargs1, C.CString("sideB"), goStringToPyUnicode("conical"))
    244    C.PyDict_SetItemString(kwargs1, C.CString("horizontal"), C.Py_False)
    245
    246    tank1 := C.PyObject_Call(tankClass, args1, kwargs1)
    247    if tank1 == nil {
    248        C.PyErr_Print()
    249        return
    250    }
    251
    252    // Get and print dimensions
    253    length := C.PyObject_GetAttrString(tank1, C.CString("L"))
    254    diameter := C.PyObject_GetAttrString(tank1, C.CString("D"))
    255    fmt.Printf("✓ Tank length: %.6f\n", pyObjectToGoFloat64(length))
    256    fmt.Printf("✓ Tank diameter: %.6f\n", pyObjectToGoFloat64(diameter))
    257
    258    // Test ellipsoidal tank
    259    argsEllip := C.PyTuple_New(0)
    260    kwargsEllip := C.PyDict_New()
    261
    262    ellipParams := map[string]interface{}{
    263        "D": 10.0,
    264        "V": 500.0,
    265        "horizontal": false,
    266        "sideA": "ellipsoidal",
    267        "sideB": "ellipsoidal",
    268        "sideA_a": 1.0,
    269        "sideB_a": 1.0,
    270    }
    271
    272    for k, v := range ellipParams {
    273        switch val := v.(type) {
    274        case float64:
    275            C.PyDict_SetItemString(kwargsEllip, C.CString(k), goFloat64ToPyFloat(val))
    276        case string:
    277            C.PyDict_SetItemString(kwargsEllip, C.CString(k), goStringToPyUnicode(val))
    278        case bool:
    279            if val {
    280                C.PyDict_SetItemString(kwargsEllip, C.CString(k), C.Py_True)
    281            } else {
    282                C.PyDict_SetItemString(kwargsEllip, C.CString(k), C.Py_False)
    283            }
    284        }
    285    }
    286
    287    tankEllip := C.PyObject_Call(tankClass, argsEllip, kwargsEllip)
    288    if tankEllip == nil {
    289        C.PyErr_Print()
    290        return
    291    }
    292
    293    ellipL := C.PyObject_GetAttrString(tankEllip, C.CString("L"))
    294    fmt.Printf("✓ Ellipsoidal tank L: %.6f\n", pyObjectToGoFloat64(ellipL))
    295
    296    // Test torispherical tank
    297    argsTori := C.PyTuple_New(0)
    298    kwargsTori := C.PyDict_New()
    299
    300    toriParams := map[string]interface{}{
    301        "L": 3.0,
    302        "D": 5.0,
    303        "horizontal": false,
    304        "sideA": "torispherical",
    305        "sideB": "torispherical",
    306        "sideA_f": 1.0,
    307        "sideA_k": 0.1,
    308        "sideB_f": 1.0,
    309        "sideB_k": 0.1,
    310    }
    311
    312    for k, v := range toriParams {
    313        switch val := v.(type) {
    314        case float64:
    315            C.PyDict_SetItemString(kwargsTori, C.CString(k), goFloat64ToPyFloat(val))
    316        case string:
    317            C.PyDict_SetItemString(kwargsTori, C.CString(k), goStringToPyUnicode(val))
    318        case bool:
    319            if val {
    320                C.PyDict_SetItemString(kwargsTori, C.CString(k), C.Py_True)
    321            } else {
    322                C.PyDict_SetItemString(kwargsTori, C.CString(k), C.Py_False)
    323            }
    324        }
    325    }
    326
    327    DIN := C.PyObject_Call(tankClass, argsTori, kwargsTori)
    328    if DIN == nil {
    329        C.PyErr_Print()
    330        return
    331    }
    332
    333    // Get tank string representation
    334    strMethod := C.PyObject_Str(DIN)
    335    fmt.Printf("✓ Tank representation: %s\n", pyObjectToGoString(strMethod))
    336
    337    // Test various methods
    338    hMax := C.PyObject_GetAttrString(DIN, C.CString("h_max"))
    339    fmt.Printf("✓ Tank max height: %.6f\n", pyObjectToGoFloat64(hMax))
    340
    341    // Test h_from_V method
    342    hFromV := C.PyObject_GetAttrString(DIN, C.CString("h_from_V"))
    343    hArgs := C.PyTuple_New(1)
    344    C.PyTuple_SetItem(hArgs, 0, goFloat64ToPyFloat(40))
    345    hResult := C.PyObject_CallObject(hFromV, hArgs)
    346    fmt.Printf("✓ Height at V=40: %.6f\n", pyObjectToGoFloat64(hResult))
    347
    348    // Test V_from_h method
    349    vFromH := C.PyObject_GetAttrString(DIN, C.CString("V_from_h"))
    350    vArgs := C.PyTuple_New(1)
    351    C.PyTuple_SetItem(vArgs, 0, goFloat64ToPyFloat(4.1))
    352    vResult := C.PyObject_CallObject(vFromH, vArgs)
    353    fmt.Printf("✓ Volume at h=4.1: %.5f\n", pyObjectToGoFloat64(vResult))
    354
    355    // Test SA_from_h method
    356    saFromH := C.PyObject_GetAttrString(DIN, C.CString("SA_from_h"))
    357    saArgs := C.PyTuple_New(1)
    358    C.PyTuple_SetItem(saArgs, 0, goFloat64ToPyFloat(2.1))
    359    saResult := C.PyObject_CallObject(saFromH, saArgs)
    360    fmt.Printf("✓ Surface area at h=2.1: %.5f\n", pyObjectToGoFloat64(saResult))
    361}
    362
    363func benchmarkFluids(fluids *C.PyObject) {
    364    fmt.Println("\nRunning benchmarks:")
    365
    366    // Benchmark friction_factor
    367    frictionFunc := C.PyObject_GetAttrString(fluids, C.CString("friction_factor"))
    368    if frictionFunc == nil {
    369        C.PyErr_Print()
    370        return
    371    }
    372
    373    fmt.Println("\nBenchmarking friction_factor:")
    374    start := time.Now()
    375
    376    for i := 0; i < 1000000; i++ {
    377        args := C.PyTuple_New(0)
    378        kwargs := C.PyDict_New()
    379
    380        C.PyDict_SetItemString(kwargs, C.CString("Re"), goFloat64ToPyFloat(1e5))
    381        C.PyDict_SetItemString(kwargs, C.CString("eD"), goFloat64ToPyFloat(0.0001))
    382
    383        result := C.PyObject_Call(frictionFunc, args, kwargs)
    384        C.Py_DecRef(result)
    385    }
    386
    387    duration := time.Since(start)
    388    fmt.Printf("Time for 1e6 friction_factor calls: %v\n", duration)
    389    fmt.Printf("Average time per call: %.6f microseconds\n", float64(duration.Microseconds())/1000000.0)
    390
    391    // Benchmark TANK creation
    392    tankClass := C.PyObject_GetAttrString(fluids, C.CString("TANK"))
    393    if tankClass == nil {
    394        C.PyErr_Print()
    395        return
    396    }
    397
    398    fmt.Println("\nBenchmarking TANK creation and methods:")
    399    start = time.Now()
    400
    401    for i := 0; i < 1000; i++ {
    402        args := C.PyTuple_New(0)
    403        kwargs := C.PyDict_New()
    404
    405        // Create tank
    406        C.PyDict_SetItemString(kwargs, C.CString("L"), goFloat64ToPyFloat(3))
    407        C.PyDict_SetItemString(kwargs, C.CString("D"), goFloat64ToPyFloat(5))
    408        C.PyDict_SetItemString(kwargs, C.CString("horizontal"), C.Py_False)
    409        C.PyDict_SetItemString(kwargs, C.CString("sideA"), goStringToPyUnicode("torispherical"))
    410        C.PyDict_SetItemString(kwargs, C.CString("sideB"), goStringToPyUnicode("torispherical"))
    411        C.PyDict_SetItemString(kwargs, C.CString("sideA_f"), goFloat64ToPyFloat(1))
    412        C.PyDict_SetItemString(kwargs, C.CString("sideA_k"), goFloat64ToPyFloat(0.1))
    413        C.PyDict_SetItemString(kwargs, C.CString("sideB_f"), goFloat64ToPyFloat(1))
    414        C.PyDict_SetItemString(kwargs, C.CString("sideB_k"), goFloat64ToPyFloat(0.1))
    415
    416        tank := C.PyObject_Call(tankClass, args, kwargs)
    417
    418        // Test some methods while we have the tank
    419        vFromH := C.PyObject_GetAttrString(tank, C.CString("V_from_h"))
    420        vArgs := C.PyTuple_New(1)
    421        C.PyTuple_SetItem(vArgs, 0, goFloat64ToPyFloat(2.5))
    422        vResult := C.PyObject_CallObject(vFromH, vArgs)
    423        C.Py_DecRef(vResult)
    424    }
    425
    426    duration = time.Since(start)
    427    fmt.Printf("Time for 1000 tank operations: %v\n", duration)
    428    fmt.Printf("Average time per tank operation: %.6f microseconds\n", float64(duration.Microseconds())/1000.0)
    429}
    430
    431func main() {
    432	fluids := initFluids()
    433	if fluids == nil {
    434		return
    435	}
    436	defer C.Py_DecRef(fluids)
    437
    438	// Get version
    439	version := C.PyObject_GetAttrString(fluids, C.CString("__version__"))
    440	defer C.free(unsafe.Pointer(C.CString("__version__")))
    441	if version != nil {
    442		fmt.Printf("✓ Successfully imported fluids\n")
    443		fmt.Printf("✓ Fluids version: %s\n", pyObjectToGoString(version))
    444		C.Py_DecRef(version)
    445	}
    446
    447	testAtmosphere(fluids)
    448	testExpandedReynolds(fluids)
    449	testTank(fluids)
    450	benchmarkFluids(fluids)
    451
    452	C.Py_Finalize()
    453	fmt.Println("\nAll tests completed!")
    454}

## Requirements

- Python with fluids installed

- cgo

## Usage Notes

- cgo and low-level python primitives are used to interface the two languages

- 3.5 microsecond friction factor, 25 microsecond tank creation observed by author

- There is some memory issue in the example that causes it to crash at the end