Search code examples
algorithmmatrixjuliafractionsbigint

Issues in computing matrix numerical terms. Is Julia wrong in evaluating the result?


I have troubles in evaluating terms of a matrix nxn, where, starting with the first 3 rows, I compute the rest of the matrix with an algorithm which have a quite complicated loop. I'm trying to write the code for the generation of Number Walls, a pretty nice topic in mathematics Mathologer's youtube channel talked about. The code is wrote here:

using Plotly, PlotlyJS, Plots
using Statistics, ProgressMeter, ImageShow

riempi_matrice_3 = function(lato)
    
    width = lato
    height = width

    data = zeros(Float32, width, height)

    data[data .== 0] .= NaN

    data[1,:] = zeros(Int, width)
    data[2,:] = ones(Int, width)

    seqq = [0, 1, 1, 1, 0]
    w = 0
    while w <13
        seqq2 = .-1 .* seqq .+ 1
        seqq = vcat(seqq,seqq2)
        w += 1
    end

    data[3,:]= seqq[1:width]

    total_steps = (width - 3) * (width - 2)
    p = Progress(total_steps)

    for i in 3 : (height-1)
        for j in 2:(width-1)
            if !isnan(data[i, j-1]) && !isnan(data[i, j+1]) 
                if data[i,j] != 0 && data[i-1, j] != 0 

                    data[i+1, j] = BigInt(BigInt(data[i, j]^2 - data[i, j-1] * data[i, j+1])//BigInt(data[i-1, j]))  # = F1

                elseif data[i,j] != 0 && data[i-1, j] == 0 

                    l_lato_finestra = 0
                    posiz_sul_lato_dal_fondo = 1
                    posiz_sul_lato_dalla_cima = 1

                    for n in 1:(i)
                        if data[i-n,j] == 0
                            l_lato_finestra +=1
                            n += 1
                        else
                            break
                        end
                    end

                    for n in 1:(i)
                        if data[i-1,j+n] == 0 
                            posiz_sul_lato_dal_fondo +=1
                            n += 1
                        else
                            break
                        end
                    end

                    for n in 1:(i)
                        if data[i-1,j-n] == 0
                            posiz_sul_lato_dalla_cima += 1
                            n += 1
                        else
                            break
                        end
                    end

                    lato_sinistro = BigInt(j-posiz_sul_lato_dalla_cima)

                    rapp_geom_up = BigInt(data[i-l_lato_finestra-1,j-posiz_sul_lato_dalla_cima+1])//BigInt(data[i-l_lato_finestra-1,j-posiz_sul_lato_dalla_cima])                        
                    rapp_geom_left = BigInt(data[i-l_lato_finestra, lato_sinistro])//BigInt(data[i-l_lato_finestra-1, lato_sinistro]) #cambiato il valore 1
                    rapp_geom_right = BigInt(data[i-l_lato_finestra-1, j + posiz_sul_lato_dal_fondo])//BigInt(data[i-l_lato_finestra, j + posiz_sul_lato_dal_fondo]) #cambiato il valore 1
                    rapp_geom_down = BigInt(data[i,j-1])//BigInt(data[i,j])

                    A = 0
                    B = 0
                    C = 0
                    if data[i-l_lato_finestra-1,j+posiz_sul_lato_dal_fondo-posiz_sul_lato_dalla_cima] ==0 

                        return["Il denominatore del rapporto superiore è zero", data]
                    else
                        A=rapp_geom_left*BigInt(data[i-l_lato_finestra-2,j+posiz_sul_lato_dal_fondo-posiz_sul_lato_dalla_cima])//BigInt(data[i-l_lato_finestra-1,j+posiz_sul_lato_dal_fondo-posiz_sul_lato_dalla_cima])
                    end

                    if data[i-posiz_sul_lato_dalla_cima,lato_sinistro] == 0 

                        return["Il denominatore del rapporto a sinistra è zero", data]
                    else
                        B=rapp_geom_up*BigInt(data[i-posiz_sul_lato_dalla_cima,lato_sinistro-1])//BigInt(data[i-posiz_sul_lato_dalla_cima,lato_sinistro]*(-1)^posiz_sul_lato_dal_fondo)
                    end

                    if data[i-posiz_sul_lato_dal_fondo,j+posiz_sul_lato_dal_fondo] ==0 

                        retunr("Il denominatore del rapporto a destra è zero")
                    else
                        C=rapp_geom_down*BigInt(data[i-posiz_sul_lato_dal_fondo,j+posiz_sul_lato_dal_fondo+1])//BigInt(data[i-posiz_sul_lato_dal_fondo,j+posiz_sul_lato_dal_fondo]*(-1)^posiz_sul_lato_dal_fondo)
                    end 

                    D=(BigInt(data[i,j])//rapp_geom_right)

                    # A +- B = x/D +- C 

                    data[i+1,j] = BigInt((A+B-C)*D)

                    # F4

                elseif data[i,j] == 0 

                    posiz_dalla_prima_riga = 1

                    for n in 1:(i)
                        if data[i-n,j] == 0
                            posiz_dalla_prima_riga += 1
                            n += 1
                        else
                            break
                        end
                    end

                    posiz_sul_lato_dal_fondo = 1
                    posiz_sul_lato_dalla_cima = 1



                    for n in 1:(i)

                        if j+n < width 
                            if data[i-posiz_dalla_prima_riga+1,j+n] == 0
                                posiz_sul_lato_dal_fondo += 1
                                n += 1
                            else
                                break
                            end
                        end
                    end

                    for n in 1:(i)
                        if data[i-posiz_dalla_prima_riga+1,j-n] == 0
                            posiz_sul_lato_dalla_cima += 1
                            n += 1
                        else
                            break
                        end
                    end

                    l_lato_finestra = BigInt(posiz_sul_lato_dal_fondo + posiz_sul_lato_dalla_cima - 1)
          
                    if posiz_dalla_prima_riga == l_lato_finestra

                        lato_sinistro = BigInt(j+posiz_sul_lato_dalla_cima)  

                        up = BigInt(data[i-l_lato_finestra,j-posiz_sul_lato_dalla_cima+posiz_sul_lato_dal_fondo])
                        left = BigInt(data[i-posiz_sul_lato_dalla_cima+1,j-posiz_sul_lato_dalla_cima])
                        right = BigInt(data[i-l_lato_finestra+posiz_sul_lato_dalla_cima,j+posiz_sul_lato_dal_fondo])

                        data[i+1,j] = BigInt((-1)^(l_lato_finestra*posiz_sul_lato_dal_fondo)*left*right//up)

                    else

                        data[i+1,j] = Int(0)

                    end
                                               
                end
            end
            next!(p)
        end
    end

    color_matrix = [data[Int(i),Int(j)] == 0 ? RGB(1,1,1) : (isnan(data[Int(i),Int(j)]) ? RGB(1,0,0) : RGB(0, 0, 0)) for i in 1:height/2+2, j in 1:width]
end

I converted the most part of the calculations to fractions, so I avoid the problem with floating points. I think I summarized the totality of the cases I can potentially see, but my issue is: This code works for values of 'lato' below 20-21. After that, it starts to give a variety of errors like 'ERROR: InexactError: BigInt(-582176//73)'.

There are two reasons for me that should make this error impossible:

  • the code should adn, the sequence I use to generate the 3rd row of values, makes impossible to have not integer values inside the matrix.
  • These fractions that Julia shows that are problematic, are extremely close to integers.

Is it possible that I forgot something in the code that prevent an eventual wrong evaluation of matrix cells?

Documentation on the formulas used: "The Number-Wall Algorithm: an LFSR Cookbook"


Solution

  • tl;dr

    Your code is converting everything into Float32 values, and your code is demonstrating floating point math errors when lato becomes large. Declare data to be a Matrix{BigInt} or Matrix{Union{BigInt, Nothing}} instead, and adjust your code accordingly.

    Your code is converting everything into Float32 values

    Your first assumption, that your code makes it impossible not to have integer values inside the matrix, is incorrect. In fact, your data matrix cannot contain anything except Float32 values, because that is how you declared it:

    data = zeros(Float32, 2, 2);
    typeof(data) == Matrix{Float32}  # true
    eltype(data) == Float32  # true
    

    You may think that by assigning values in the matrix to Int values will store Int values, but because you declared data to be a matrix of Float32 values, Julia will convert the values into the best possible Float32 representation it can:

    data[1,:] = zeros(Int, 2);
    typeof(data) == Matrix{Float32}  # true
    typeof(data[1,1]) == Float32  # true
    

    data does not store Ints, it stores Float32s. It doesn't matter that you set some elements to Rational{BigInt} values: Julia will just convert those into Float32s as well:

    data[2,1] = BigInt(1)//BigInt(10);
    typeof(data) == Matrix{Float32}  # true
    typeof(data[2,1]) == Float32  # true
    

    Your code is demonstrating floating point math errors

    Now, hopefully, you can see how this becomes a problem in your code. You are summing up a lot of things that you may think are Rational{BigInt} values, but are actually Float32 values. This is a simple example of how things can go wrong:

    data[1,1] = BigInt(1)//BigInt(10)
    data[1,2] = BigInt(0)
    
    for _ in 1:10
        data[1,2] += data[1,1]
    end
    
    data[1,2] == BitInt(1)  # false, because Float32(1)/Float32(10) != 1//10
    

    Declare data differently

    If you want data to be a matrix of BigInt values, just declare it that way:

    data = zeros(BigInt, width, height)
    eltype(data) == BigInt  # true
    

    (If you really want data to contain rational values, you can use Rational{BigInt} instead, but if you inspect your algorithm, I think you will find that you won't need them.)

    Now, you may be falling into a python/pandas gotcha by assuming that, because you need a value to represent something undefined, you need data to be able to contain NaN and therefore require data to be a matrix of floating point values; however, Julia can handle unions of types, and a Union{T, Nothing} is a common way to handle this very case:

    data = Matrix{Union{BigInt, Nothing}}(nothing, width, height)
    

    This declares data as a matrix that can hold values of type BigInt or Nothing and initializes it to a width by height matrix of all nothing values. To check for uninitialized values, use data[i,j] == nothing or isnothing(data[i,j]) instead of checking against NaN.