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