google-research / dex-lang

Research language for array processing in the Haskell/ML family
BSD 3-Clause "New" or "Revised" License
1.59k stars 107 forks source link

mandelbrot.dx demo is currently broken #121

Closed lancelet closed 4 years ago

lancelet commented 4 years ago

Somewhere between 9877efb233e7fc37db8cc516e1dbcf91eade049b and fd2c8acd70c28c6189fd92a1d7373bf0a0c57d4a, the mandelbrot.dx demo became broken somehow. It currently looks like this for me: broken-mandelbrot If I reduce the number of iterations to 50, it starts looking more correct: mandelbrot-50

Unfortunately quite a lot changed between the last commit where it worked and the first commit where it failed, so I haven't been able to track down what's causing the problem. Frustratingly, the individual operations in the example all seem to work as expected. I've been wondering if there's some small floating-point error or similar that might be causing it.

btw: It might be convenient to validate this example's output in the tests if, after displaying the 300x200 matrix, it was evaluated over a smaller number of elements (say 7x5) and then dumped using :p.

lancelet commented 4 years ago

Update: I think I've figured out what's going on. It does look like a floating-point issue of some kind:

First, I replaced escapeTime with a version that returned the complex value contained in the fold state:

escapeTime : Int -> Complex -> (Real & Complex) =
  \j c. fold (0.0, czero) $ \i:(Fin j) (n, z).
     z' = update c z
     (n + b2r (inBounds z'), z')

This reveals that as NaNs are introduced, the function starts increasing the escape time:

:p escapeTime 1 pt
> (1.0, (1.0, 0.0))
:p escapeTime 2 pt
> (1.0, (2.0, 0.0))
:p escapeTime 3 pt
> (1.0, (5.0, 0.0))
:p escapeTime 4 pt
> (1.0, (26.0, 0.0))
:p escapeTime 5 pt
> (1.0, (677.0, 0.0))
:p escapeTime 6 pt
> (1.0, (458330.0, 0.0))
:p escapeTime 7 pt
> (1.0, (2.1006639e11, 0.0))
:p escapeTime 8 pt
> (1.0, (4.4127886e22, 0.0))
:p escapeTime 9 pt
> (1.0, (Infinity, 0.0))
:p escapeTime 10 pt
> (1.0, (Infinity, 0.0))
:p escapeTime 11 pt
> (1.0, (Infinity, 0.0))
:p escapeTime 12 pt
> (1.0, (Infinity, 0.0))
:p escapeTime 13 pt
> (2.0, (Infinity, NaN))
:p escapeTime 14 pt
> (3.0, (NaN, NaN))
:p escapeTime 15 pt
> (4.0, (NaN, NaN))

(Side note: interestingly, here Infinity is clearly not behaving as a singleton value, because the escapeTime function returns different values for Infinity on different evaluations. Does Infinity have some kind of underlying representation that is actually participating in the calculations here? - I'll have to look that up!)

And indeed the NaNs are interacting with < in the inBounds function in all kinds of hilarious ways:

>=> infty = 1./0.
>=> infty
Infinity
>=> nan = 0./0.
>=> nan
NaN
>=> infty < 2.0
False
>=> nan < 2.0
True

which explains why the escape time starts increasing.

I'm not really sure how to handle this though, or why the behavior changed. I'll take a look at what was happening when it was still working correctly.

dougalm commented 4 years ago

Good digging! I'm afk right now and can't check, but my guess is that it's from the dictionary synthesis stuff, where we put Ord in userspace. I (sloppily) made the Ord typeclass dictionary just a single > function, with < defined, in the prelude, using > and ==. Not a good thing to do for NaNs! Replacing < with <= might fix the example, but we should really do Ord properly.

lancelet commented 4 years ago

Thanks for the confirmation. I expanded Ord a bit in PR #122.

I'm not 100% sure if that's what you had in mind. I've noticed there have been some developments toward sum types. Do you have any plans for labelled products / records / (I'm not 100% sure what to call them - like data in Haskell 😄) that could replace the current tupling approach for type classes?

dougalm commented 4 years ago

Thanks for the PR!

Do you have any plans for labelled products / records

Yes, definitely. Building everything out of nested pairs and accessing them with compositions of fst and snd gets old quickly. One of the goals with Dex is to explore typed relational/dataframe programming, and labeled fields will be essential there. The plan is to have some sort of extensible record system. I really like Daan Leijen's take on row polymorphism so I think we'll start there. Any ideas or suggestions would very welcome.