haskell Integration

This example demonstrates how to use fluids from haskell.

Source Code

  1{-# LANGUAGE OverloadedStrings #-}
  2module Main where
  3
  4import qualified CPython as Py
  5import qualified CPython.Types.Module as Py
  6import qualified CPython.Protocols.Object as Py
  7import qualified CPython.Protocols.Number as PyNum
  8import qualified CPython.Types.Dictionary as PyDict
  9import qualified CPython.Types.Tuple as PyTuple
 10import qualified CPython.Types.Unicode as PyUnicode
 11import qualified CPython.Types.Float as PyFloat
 12import qualified CPython.Types.Exception as PyExc
 13import Control.Exception (handle)
 14import Text.Printf (printf)
 15import Data.Maybe (fromMaybe)
 16import qualified Data.Text as T
 17import System.Clock
 18
 19testBasics :: IO ()
 20testBasics = do
 21    putStrLn "Testing basic fluids functionality..."
 22    
 23    -- Import fluids module
 24    fluidsModule <- Py.importModule (T.pack "fluids")
 25    
 26    -- Get version
 27    versionName <- PyUnicode.toUnicode (T.pack "__version__")
 28    versionObj <- Py.getAttribute fluidsModule versionName 
 29    Just versionStr <- Py.cast versionObj
 30    version <- PyUnicode.fromUnicode versionStr
 31    putStrLn $ "✓ Fluids version: " ++ T.unpack version
 32    
 33    -- Test Reynolds number calculation
 34    let calcReynolds = do
 35            -- Get Reynolds function
 36            reynoldsName <- PyUnicode.toUnicode (T.pack "Reynolds")
 37            reynolds <- Py.getAttribute fluidsModule reynoldsName
 38            
 39            -- Create arguments
 40            v <- PyFloat.toFloat 2.5
 41            d <- PyFloat.toFloat 0.1
 42            rho <- PyFloat.toFloat 1000.0
 43            mu <- PyFloat.toFloat 0.001
 44            args <- PyTuple.toTuple [Py.toObject v, Py.toObject d, Py.toObject rho, Py.toObject mu]
 45            kwargs <- PyDict.new
 46            
 47            -- Call function
 48            result <- Py.call reynolds args kwargs
 49            x <- PyNum.castToNumber result
 50            let num = fromMaybe (error "Could not convert Reynolds number") x
 51            PyFloat.fromFloat =<< PyNum.toFloat num
 52    
 53    re <- calcReynolds
 54    putStrLn $ printf "✓ Reynolds number calculation: %.1f" re
 55    
 56    -- Test friction factor
 57    let calcFriction = do
 58            -- Get friction_factor function
 59            frictionName <- PyUnicode.toUnicode (T.pack "friction_factor")
 60            friction <- Py.getAttribute fluidsModule frictionName
 61            
 62            -- Create arguments
 63            re <- PyFloat.toFloat 1e5
 64            ed <- PyFloat.toFloat 0.0001
 65            args <- PyTuple.toTuple [Py.toObject re, Py.toObject ed]
 66            kwargs <- PyDict.new
 67            
 68            -- Call function
 69            result <- Py.call friction args kwargs
 70            x <- PyNum.castToNumber result
 71            let num = fromMaybe (error "Could not convert friction factor") x
 72            PyFloat.fromFloat =<< PyNum.toFloat num
 73    
 74    fd <- calcFriction
 75    putStrLn $ printf "✓ Friction factor calculation: %.6f" fd
 76    putStrLn "Basic tests completed successfully!\n"
 77
 78testAtmosphere :: IO ()
 79testAtmosphere = do
 80    putStrLn "\nTesting atmosphere at 5000m elevation:"
 81    
 82    -- Import fluids module
 83    fluidsModule <- Py.importModule (T.pack "fluids")
 84    
 85    -- Create argument for constructor
 86    zArg <- PyFloat.toFloat 5000.0
 87    args <- PyTuple.toTuple [Py.toObject zArg]
 88    kwargs <- PyDict.new
 89    
 90    -- Get ATMOSPHERE_1976 class and create instance
 91    atmosClass <- PyUnicode.toUnicode (T.pack "ATMOSPHERE_1976") >>= Py.getAttribute fluidsModule
 92    atm <- Py.call atmosClass args kwargs
 93    
 94    -- Get and print properties
 95    let getProperty name = do
 96            nameObj <- PyUnicode.toUnicode name
 97            prop <- Py.getAttribute atm nameObj
 98            x <- PyNum.castToNumber prop
 99            let num = fromMaybe (error $ "Could not get " ++ T.unpack name ++ " as number") x
100            PyFloat.fromFloat =<< PyNum.toFloat num
101    
102    temp <- getProperty (T.pack "T") 
103    pressure <- getProperty (T.pack "P")
104    density <- getProperty (T.pack "rho")
105    gravity <- getProperty (T.pack "g")
106    viscosity <- getProperty (T.pack "mu")
107    conductivity <- getProperty (T.pack "k")
108    sonicVel <- getProperty (T.pack "v_sonic")
109    
110    putStrLn $ printf "✓ Temperature: %.4f" temp
111    putStrLn $ printf "✓ Pressure: %.4f" pressure
112    putStrLn $ printf "✓ Density: %.6f" density
113    putStrLn $ printf "✓ Gravity: %.6f" gravity
114    putStrLn $ printf "✓ Viscosity: %.6e" viscosity
115    putStrLn $ printf "✓ Thermal conductivity: %.6f" conductivity
116    putStrLn $ printf "✓ Sonic velocity: %.4f" sonicVel
117
118benchmarkFluids :: IO ()
119benchmarkFluids = do
120    putStrLn "\nRunning benchmarks:"
121    
122    -- Import fluids module
123    fluidsModule <- Py.importModule (T.pack "fluids")
124    
125    -- Get friction_factor function
126    frictionName <- PyUnicode.toUnicode (T.pack "friction_factor")
127    friction <- Py.getAttribute fluidsModule frictionName
128    
129    -- Prepare arguments that will be reused
130    re <- PyFloat.toFloat 1e5
131    ed <- PyFloat.toFloat 0.0001
132    args <- PyTuple.toTuple [Py.toObject re, Py.toObject ed]
133    kwargs <- PyDict.new
134    
135    -- Time the operations
136    putStrLn "\nBenchmarking friction_factor:"
137    start <- getTime Monotonic
138    
139    -- Do 1,000,000 iterations like in Julia version
140    sequence_ $ replicate 1000000 $ do
141        result <- Py.call friction args kwargs
142        x <- PyNum.castToNumber result
143        let num = fromMaybe (error "Could not convert friction factor") x
144        PyFloat.fromFloat =<< PyNum.toFloat num
145        
146    end <- getTime Monotonic
147    let diff = (fromIntegral (toNanoSecs (diffTimeSpec end start)) :: Double) / 1000000000
148    
149    putStrLn $ printf "Time for 1e6 friction_factor calls: %.6f seconds" diff
150    putStrLn $ printf "Average time per call: %.6f seconds" (diff / 1000000)    
151
152main :: IO ()
153main = handle pyExceptionHandler $ do
154    putStrLn "Running fluids tests from Haskell..."
155    Py.initialize
156    testBasics
157    testAtmosphere
158    benchmarkFluids
159    Py.finalize
160    putStrLn "\nAll tests completed!"
161  where
162    pyExceptionHandler :: PyExc.Exception -> IO ()
163    pyExceptionHandler e = do
164        putStrLn $ "Python error occurred: " ++ show e
165        Py.finalize

Requirements

  • Python with fluids installed

  • cabal

  • haskell cpython package

Usage Notes

  • The example is incomplete and crashes on termination

  • Performance is 2 microsecond for friction factor, indicating there is very little overhead