packages feed

brillo-examples-2.0.0: picture/Visibility/Geometry/Randomish.hs

{-# LANGUAGE BangPatterns #-}

module Geometry.Randomish (
  randomishPoints,
  randomishInts,
  randomishDoubles,
)
where

import Data.Vector.Generic qualified as G
import Data.Vector.Unboxed qualified as V
import Data.Vector.Unboxed.Mutable qualified as MV
import Data.Word (Word64)


-- | Some uniformly distributed points
randomishPoints ::
  -- | seed
  Int ->
  -- | number of points
  Int ->
  -- | minimum coordinate
  Float ->
  -- | maximum coordinate
  Float ->
  V.Vector (Float, Float)
randomishPoints seed' n pointMin pointMax =
  let pts = randomishFloats (n * 2) pointMin pointMax seed'
      xs = G.slice 0 n pts
      ys = G.slice n n pts
  in  V.zip xs ys


{-| Use the "minimal standard" Lehmer generator to quickly generate some random
  numbers with reasonable statistical properties. By "reasonable" we mean good
  enough for games and test data, but not cryptography or anything where the
  quality of the randomness really matters.

  From "Random Number Generators: Good ones are hard to find"
  Stephen K. Park and Keith W. Miller.
  Communications of the ACM, Oct 1988, Volume 31, Number 10.
-}
randomishInts ::
  Int -> -- Length of vector.
  Int -> -- Minumum value in output.
  Int -> -- Maximum value in output.
  Int -> -- Random seed.
  V.Vector Int -- Vector of random numbers.
randomishInts !len !valMin' !valMax' !seed' =
  let
    -- a magic number (don't change it)
    multiplier :: Word64
    multiplier = 16807

    -- a merzenne prime (don't change it)
    modulus :: Word64
    modulus = 2 ^ (31 :: Integer) - 1

    -- if the seed is 0 all the numbers in the sequence are also 0.
    seed
      | seed' == 0 = 1
      | otherwise = seed'

    !valMin = fromIntegral valMin'
    !valMax = fromIntegral valMax' + 1
    !range = valMax - valMin

    {-# INLINE f #-}
    f x = multiplier * x `mod` modulus
  in
    G.create $
      do
        vec <- MV.new len

        let go !ix !x
              | ix == len = return ()
              | otherwise =
                  do
                    let x' = f x
                    MV.write vec ix $ fromIntegral $ (x `mod` range) + valMin
                    go (ix + 1) x'

        go 0 (f $ f $ f $ fromIntegral seed)
        return vec


{-| Generate some randomish doubles with terrible statistical properties.
  This is good enough for test data, but not much else.
-}
randomishDoubles ::
  Int -> -- Length of vector
  Double -> -- Minimum value in output
  Double -> -- Maximum value in output
  Int -> -- Random seed.
  V.Vector Double -- Vector of randomish doubles.
randomishDoubles !len !valMin !valMax !seed =
  let range = valMax - valMin

      mx = 2 ^ (30 :: Integer) - 1
      mxf = fromIntegral mx
      ints = randomishInts len 0 mx seed
  in  V.map (\n -> valMin + (fromIntegral n / mxf) * range) ints


{-| Generate some randomish doubles with terrible statistical properties.
  This is good enough for test data, but not much else.
-}
randomishFloats ::
  Int -> -- Length of vector
  Float -> -- Minimum value in output
  Float -> -- Maximum value in output
  Int -> -- Random seed.
  V.Vector Float -- Vector of randomish doubles.
randomishFloats !len !valMin !valMax !seed =
  let range = valMax - valMin

      mx = 2 ^ (30 :: Integer) - 1
      mxf = fromIntegral mx
      ints = randomishInts len 0 mx seed
  in  V.map (\n -> valMin + (fromIntegral n / mxf) * range) ints