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