# 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