Monday, January 22, 2024

HASKELL LOGARITHMS

HASKELL LOGARITHMS

REVISED: Saturday, Febuary 3, 2024


1. INTRODUCTION

As mentioned in previous tutorials, Haskell has three exponentiation operators: (^), (^^), and (**). ^ is non-negative integral exponentiation, ^^ is integer exponentiation, and ** is floating-point exponentiation.

Use the ^ exponent operator when you need a non-negative integer as the exponent.

Prelude>  2 ^ 5
32
it :: Num a => a
Prelude>

Prelude>  :type (^)
(^) :: (Num a, Integral b) => a -> b -> a
Prelude>

Prelude>  :info (^)
(^) :: (Num a, Integral b) => a -> b -> a -- Defined in `GHC.Real'
infixr 8 ^
Prelude>    

Prelude>  2 ^ (-5)
*** Exception: Negative exponent
Prelude>

Use the ^^ exponent operator when you need a positive or negative integer as the exponent.

Prelude>  2 ^^ 5
32.0
Prelude>

Prelude>  :type (^^)
(^^) :: (Integral b, Fractional a) => a -> b -> a
Prelude>  

Prelude>  :info (^^)
(^^) :: (Fractional a, Integral b) => a -> b -> a
  -- Defined in ‘GHC.Real’
infixr 8 ^^
Prelude>  

Prelude>  2 ^^ (-5)
3.125e-2
Prelude>

Use the ** exponent operator when you need a positive or negative floating point number as the exponent.

Prelude> 2 ** 5.0
32.0
it :: Floating a => a
Prelude> 

Prelude>  :type (**)
(**) :: Floating a => a -> a -> a
Prelude>

Prelude>  :info (**)
class Fractional a => Floating a where
  ...
  (**) :: a -> a -> a
  ...
         -- Defined in `GHC.Float'
infixr 8 **
Prelude>  

Prelude>  2 ** (-5.0)
3.125e-2
Prelude>

Use the exp exponent operator to use Euler's number e,  the base of the Natural Logarithms.

Logarithms are another way of thinking about exponents. For example, in exponential form 2 3 = 8 and in logarithmic form log 2 8 = 3. In other words if b = y then log b y = x where the base is b and the exponent is x.

Furthermore:

log b (xy) = log b x + log b y

log b (x/y) = log b x - log b y

log b (x y) = y (log b x)

Prelude>  exp 1
2.718281828459045
it :: Floating a => a
Prelude> 

Prelude>  :type exp
exp :: Floating a => a -> a
Prelude>

Prelude>  :info exp
class Fractional a => Floating a where
  ...
  exp :: a -> a
  ...
  -- Defined in `GHC.Float'
Prelude>  

Prelude>  let e = exp 1
e :: Floating a => a
Prelude>

= denotes definition, like it does in mathematics; e.g., e "is defined as" exp 1.

Prelude>  e
2.718281828459045
it :: Floating a => a
Prelude>

Haskell provides various approaches to solving logarithmic equations, depending on the complexity and desired accuracy.

2. OVERVIEW
 
The following are three common methods which can be used to solve logarithmic equations.

2.1. Using Logarithmic Properties:

This method exploits the logarithmic properties, such as product rule, quotient rule, and power rule, to manipulate the equation into a form where the unknown variable can be isolated.

2.2. Using Numerical Methods:

For more complex equations or when analytical solutions are difficult, numerical methods like bisection method or Newton-Raphson method can be applied. These methods iteratively refine an initial guess until the desired accuracy is reached.

2.3. Using Symbolic Libraries:

Libraries like "symbolic" or "polyglot" allow symbolic manipulation of expressions, enabling you to solve equations for exact solutions without numerical approximations.

3. EXAMPLES

3.1. Using Logarithmic Properties:

import Prelude hiding ((**))

-- Function to apply a logarithmic property
applyProperty :: (Double -> Double) -> Double -> Double -> Double
applyProperty f x y = f (x * y)

-- Example equation: 3^x = 81
solveEquation :: Double -> Double
solveEquation x =
  let
    leftSide = exp (x * logBase 10 3)  -- Apply power rule
    rightSide = 81
  in
    logBase 10 rightSide / logBase 10 3  -- Apply quotient rule

main :: IO ()
main = do
  let solution = solveEquation 1.0  -- Starting with a guess of 1.0
  print solution

Explanation:

Import Prelude: This module provides basic functions and types, including logBase and exp for logarithms and exponents.

applyProperty: This function demonstrates how to apply a logarithmic property to two numbers. It's not used directly in this example, but it illustrates the concept.

solveEquation: Takes a guess for x as input. Calculates the left side of the equation using exp (x * logBase 10 3), applying the power rule of logarithms. Compares the left side to the right side (81). Applies the quotient rule of logarithms to isolate x and return its value.

main: Calls solveEquation with an initial guess of 1.0. Prints the solution, which should be 4.0 (since 3^4 = 81).

Key Points:

The program uses logBase 10 for base-10 logarithms. You can use log for natural logarithms (base e).

It assumes a simple equation with known values, but this approach can be applied to more complex equations as well.

In more sophisticated cases, you might need to use numerical methods for solving equations or finding roots. 

3.2. Using the Bisection Method:

import Prelude hiding ((**))

-- Function representing the logarithmic equation
eqn :: Double -> Double
eqn x = log x - 2  -- Example: log(x) = 2

-- Bisection method
bisection :: Double -> Double -> Double -> Double -> Double
bisection a b tolerance iterations =
  let mid = (a + b) / 2
      fmid = eqn mid
      error = abs (b - a)
  in
    if error < tolerance || iterations == 0
      then mid
      else if fmid * eqn a < 0
          then bisection a mid tolerance (iterations - 1)
          else bisection mid b tolerance (iterations - 1)

main :: IO ()
main = do
  let solution = bisection 1.0 10.0 1e-6 100  -- Initial interval, tolerance, max iterations
  print solution

Explanation:

Import Prelude: Provides basic functions and types, including log for logarithms.

eqn: Represents the logarithmic equation to be solved.

bisection: Implements the bisection algorithm: Takes the initial interval (a, b), tolerance, and maximum iterations as input. Recursively halves the interval based on the sign of fmid * eqn a. Checks for convergence based on error or maximum iterations.

main: Calls bisection with an initial interval of (1.0, 10.0), tolerance of 1e-6, and maximum iterations of 100. Prints the solution, which should be approximately 7.389065 (since log(7.389065) ≈ 2).

Key Points:

The bisection method is generally more robust than the Newton-Raphson method, as it doesn't require calculating a derivative and can handle a wider range of equations. It converges slower than Newton-Raphson but is guaranteed to converge if the equation has a root within the initial interval. You can adjust the initial interval, tolerance, and maximum iterations to suit your specific problem.

3.3. Newton-Raphson Method:

import Prelude hiding ((**))

-- Function representing the logarithmic equation
eqn :: Double -> Double
eqn x = log x - 2  -- Example: log(x) = 2

-- Derivative of the equation
eqnDeriv :: Double -> Double
eqnDeriv x = 1 / x

-- Newton-Raphson method
newtonRaphson :: Double -> Double -> Double -> Double
newtonRaphson x0 tolerance iterations =
  let x1 = x0 - eqn x0 / eqnDeriv x0
      error = abs (x1 - x0)
  in
    if error < tolerance || iterations == 0
      then x1
      else newtonRaphson x1 tolerance (iterations - 1)

main :: IO ()
main = do
  let solution = newtonRaphson 1.0 1e-6 100  -- Initial guess, tolerance, max iterations
  print solution

Explanation:

Import Prelude: Provides basic functions and types, including log for logarithms.

eqn: Represents the logarithmic equation to be solved.

eqnDeriv: Calculates the derivative of the equation, needed for the Newton-Raphson method.

newtonRaphson: Implements the Newton-Raphson algorithm: Takes the initial guess, tolerance, and maximum iterations as input. Recursively updates the guess using the formula x1 = x0 - eqn x0 / eqnDeriv x0. Checks for convergence based on error or maximum iterations.

main: Calls newtonRaphson with an initial guess of 1.0, tolerance of 1e-6, and maximum iterations of 100. Prints the solution, which should be approximately 7.389056 (since log(7.389056) ≈ 2).

Key Points:

You can adjust the equation (eqn), derivative (eqnDeriv), tolerance, and maximum iterations to solve different logarithmic equations.

The method might not converge for all equations or initial guesses.

Other numerical methods, like the bisection method, can also be implemented in Haskell using similar techniques.

3.4. Symbolic Libraries:

While Haskell doesn't have a library named "symbolic" specifically for symbolic computation, it does have several libraries that offer symbolic capabilities:

3.4.1. SBV (Satisfiability Modulo Theories):

Provides symbolic reasoning for expressions and constraints. Can be used to solve equations, prove theorems, and model systems.

Example:

import Data.SBV

solveEquation :: SymExpr Solver -> IO ()
solveEquation expr = do
    model <- getModel expr
    print (eval model expr)

main :: IO ()
main = do
    let x = free "x" :: SymExpr Solver
    solveEquation (log x .== 2)

3.4.2. Grisette:

Offers symbolic evaluation of Haskell expressions. Useful for constraint solving, testing, and analysis.

Example:

import Data.Grisette.Crucible.Core

solveEquation :: Expr -> IO ()
solveEquation expr = do
    result <- evaluate expr
    print result

main :: IO ()
main = do
    let x = mkVar "x" :: Expr
    solveEquation (log x .== 2)

3.4.3. Polyglot:

A frontend for various symbolic backends (e.g., Mathematica, SymPy).  Allows interaction with these systems from Haskell.

Example:

import Language.Polyglot

solveEquation :: String -> IO ()
solveEquation expr = do
    result <- runSession $ runMathematica $ expr
    print result

main :: IO ()
main = do
    solveEquation "Solve[Log[x] == 2, x]"

Choosing the right library depends on your specific requirements and the complexity of the symbolic tasks you need to perform.

4. CONCLUSION

Choose the method based on the equation's complexity and desired accuracy.

Numerical methods may require fine-tuning initial guess and tolerance parameters.

Symbolic libraries offer exact solutions but might not be efficient for all cases.

5. REFERENCES

Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.

Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.

Goerzen, J. & O'Sullivan, B. &  Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.

Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.

Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.

Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.