0

I'm getting an error from GHCi that I can't explain. I'm working with the following code (the vast majority of which is seemingly-irrelevant to the issue, but I can't replicate the issue with less code; commented-out lines are ones that I would like to add to replace the dummy in 0 lines)

import Linear
apply x f = f x
pos xs = -- smallest i where xs!!i > 0, else length xs
    let aux xs n = case xs of
                   x:t -> if x > 0 then n
                          else aux t (n+1)
                   [] -> n
    in aux xs 0
optimize d opt d_opt funs d_funs x0 p0 eps =
    let n = length funs in
    let aux x p f_best = let feas = map (apply x) funs in
                         let i = pos feas in
                         let (g,a,f_best) =
                                 if i == n then
                                    let g = d_opt x in
                                    let g' = p !* g in
                                    let prod = g `dot` g' in
                                    let g = g / (sqrt prod) in
                                    let f_best = min (opt x) f_best in
                                    let a = (opt x - f_best) / (sqrt prod) in
                                    (g,a,f_best)
                                 else
                                    let g = (d_funs!!i) x in
                                    let g' = p !* g in
                                    let prod = g `dot` g' in
                                    let g = g / (sqrt prod) in
                                    let a = ((funs!!i) x) / (sqrt prod) in
                                    (g,a,f_best)
                         in
                         let b = (1+d*a)/(d+1) in
                         let b' = 2/(1+a) in
                         let b'' = (1-a^2)*(d^2)/(d^2-1) in
                         let h = p !* g in
                         let y = x - b*g in
                         -- let q = (p - g'*(transpose g')*b*b')*b'' in
                         -- aux y q f_best
                         0
    -- in aux x0 p0 (1/0)
    in 0 

This code causes GHCi to throw six errors, including highlighting the p in let h = p !* g in; however, when I change that line to let g = p !* g in it goes through. Unfortunately, doing this and then uncommenting the next line (let x = x - b*g in) causes the same errors to be thrown (including highlighting the p in the same spot).

p and p0 are supposed to be (n-by-n) square matrices using the Linear package, while g, x and x0 are supposed to be (n-by-1) vectors; d is an integer, opt is a linear function on n-space, funs is a list of convex functions on n-space, d_opt and d_funs are the respective gradients, eps is real.

Any help with getting this to compile would be greatly appreciated. Thanks!

Edit: here is one of the error messages. There are similar ones for let g = d_opt x, let f_best = min (opt x) f_best, let g = (d_funs!!i) x, let a = ((funs!!i) x) / (sqrt prod), and let b = (1+d*a)/(d+1).

Lenstra.hs:57:34: error:
    • Occurs check: cannot construct the infinite type: a1 ~ m a1
      Expected type: m (m a1)
        Actual type: m (m (m a1))
    • In the first argument of ‘(!*)’, namely ‘p’
      In the expression: p !* g
      In an equation for ‘h’: h = p !* g
    • Relevant bindings include
        h :: m a1 (bound at Lenstra.hs:57:30)
        b'' :: m a1 (bound at Lenstra.hs:56:30)
        b' :: m a1 (bound at Lenstra.hs:55:30)
        b :: m a1 (bound at Lenstra.hs:54:30)
        g :: m a1 (bound at Lenstra.hs:37:31)
        a :: m a1 (bound at Lenstra.hs:37:33)
        aux :: m a1 -> m (m (m a1)) -> p8 -> p9 (bound at Lenstra.hs:35:9)
        (Some bindings suppressed; use -fmax-relevant-binds=N or -fno-max-relevant-binds)
   |
57 |                          let h = p !* g in
   |                                  ^
Failed, no modules loaded.
zjs
  • 327
  • 1
  • 9
  • Well that probably means the type of `p` and `q` are different. – Willem Van Onsem Aug 03 '20 at 16:59
  • 1
    Without even reading the question: `let x = x - b*g` defines `x` in terms of itself, not in terms of some previously-available `x` as you probably intended. Pick a new name for the new `x` and modify your code accordingly. Same for `p`. 95% confidence your problems will disappear. – Daniel Wagner Aug 03 '20 at 17:00
  • But this algorithm is, imho too chaotic to effectively debug. Normally you split the logic into small parts that perform specific tasks. The amount of `let ... in ...` chaining is gigantic. While it is not unusual to have 2-3 `let` statements, the above does not look very Haskellish. In fact it also looks very procedural. – Willem Van Onsem Aug 03 '20 at 17:01
  • 1
    @DanielWagner Unfortunately, I tried changing the names to `let h = p !* g` and `let y = x - b*h` (keeping the redefinition of `p` commented out) and seem to have the same compilation problem – zjs Aug 03 '20 at 17:09
  • @WillemVanOnsem yes I wish I had less chaining, but having everything substituted in was making debugging even more difficult. Is it better to e.g. stick each branch of the `if`/`else` into its own function outside of `optimize`? – zjs Aug 03 '20 at 17:10
  • What is the error message? – Fyodor Soikin Aug 03 '20 at 17:14
  • @FyodorSoikin i have edited in one of the error messages to the bottom of the post. I could add the others if it would be additionally useful. – zjs Aug 03 '20 at 17:22
  • Try adding type annotations to the bindings. That will narrow down where the infinite type is being inferred. – pat Aug 03 '20 at 17:31
  • How did you come up with this algorithm? Are there distinct ideas in it that you could factor out into independently meaningful functions? – dfeuer Aug 03 '20 at 21:42
  • @dfeuer I am implementing the constrained ellipsoid method, https://stanford.edu/class/ee364b/lectures/ellipsoid_method_notes.pdf. Unfortunately I haven't identified a good way to factor the code other than the branches of the `if`/`else`. – zjs Aug 03 '20 at 22:40

1 Answers1

5

There are several errors:

  • Where the integer d is used as a float, you need to use fromIntegral, so for example: b = (1 + fromIntegral d * a)/(fromIntegral d + 1)
  • Scalar multiplication/division of vectors and matrices can't use * and /. You must use the *^, ^*, and ^/ operators for vectors and !!* and !!/ for matrices.
  • Pointwise sums and differences of vectors and matrices can't use + and -. You must use ^+^ and ^-^ for vectors and !+! and !-! for matrices.
  • For a vector g', transpose g' isn't going to work, so g' * transpose g' doesn't have a prayer. Use outer g' g' to get the matrix resulting from the product of g' as a column vector with g' as a row vector, if that's what you're trying to do.
  • There was no g' in scope where q is defined. Maybe you wanted to return g' from your if statement?
  • let g = some expression with g is not going to work, as you'll create a recursive definition that loops forever. You'll need to use a fresh variable -- you've done this correctly in some places but not in others.

There's also a significant logic error, at least in the version with your commented statements uncommented. The function aux never returns anything other than a tail call to aux, so it will necessarily loop forever. I don't even know what type it's supposed to return. You need some stopping condition (probably returning f_best or something).

You will find it helpful to add type signatures to optimize and its aux function to keep these errors under control. The following type-checks but still contains several bugs (infinite loops, etc.):

import Linear
import Data.Vector (Vector)

apply x f = f x

pos :: (Ord a, Num a) => [a] -> Int
pos xs = -- smallest i where xs!!i > 0, else length xs
    let aux xs n = case xs of
                   x:t -> if x > 0 then n
                          else aux t (n+1)
                   [] -> n
    in aux xs 0

type Matrix a = Vector (Vector a)

optimize
  :: Integer
  -> (Vector Double -> Double)
  -> (Vector Double -> Vector Double)
  -> [Vector Double -> Double]
  -> [Vector Double -> Vector Double]
  -> Vector Double
  -> Matrix Double
  -> Double
  -> a
optimize d opt d_opt funs d_funs x0 p0 eps =
    let n = length funs in
    let aux
          :: Vector Double
          -> Matrix Double
          -> Double
          -> a
        aux x p f_best = let feas = map (apply x) funs in
                         let i = pos feas in
                         let g :: Vector Double
                             (g,g',a,f_best) =
                                 if i == n then
                                    let g = d_opt x in
                                    let g' = p !* g in
                                    let prod = g `dot` g' in
                                    let g = g ^/ (sqrt prod) in  -- **LOOP**
                                    let f_best = min (opt x) f_best in
                                    let a = (opt x - f_best) / (sqrt prod) in
                                    (g,g',a,f_best)
                                 else
                                    let g = (d_funs!!i) x in
                                    let g' = p !* g in
                                    let prod = g `dot` g' in
                                    let g = g ^/ (sqrt prod) in  -- **LOOP**
                                    let a = ((funs!!i) x) / (sqrt prod) in
                                    (g,g',a,f_best)
                         in
                         let b = (1+fromIntegral d*a)/(fromIntegral d+1) in
                         let b' = 2/(1+a) in
                         let b'' = (1-a^2)*(fromIntegral d^2)/(fromIntegral d^2-1) in
                         let h = p !* g in
                         let y = x ^-^ b*^g in
                         let q = (p !-! outer g' g' !!* (b*b')) !!* b'' in
                         aux y q f_best
    in aux x0 p0 (1/0)

Finally, when you get this running, you might want to submit it to the Code Review Stack Exchange, along with an explanation of the algorithm and some running examples. There are lots of stylistic improvements that I think could make it make it more idiomatic.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • Thank you very much for looking at the code so thoroughly. Yes, `outer` was exactly what I was trying to do. The definition of `q` should have referenced `g`, not `g'`; this was an artifact from a previous debug attempt. The only part of the method that I didn't get around to writing before attempting/flailing at debugging was the halt condition, which I plan to place after the definition of `i`, and which will return `x` (or go to another iteration). – zjs Aug 03 '20 at 22:50