0
$\begingroup$

In the paper, The use of bit and byte manipulation in computing summation sequences, by M.C. Wunderlich (link here) the author calculates the Ulam sequence using the following algorithm.

Initialize $V = [1,1,0,0,....0]$ and $k = 2$. The length of V depends on how many Ulam numbers one would want to compute/the storage capacity of the machine. The notation $V(a,b)$ stands for $[V_a, V_{a+1},...,V_b]$ and $\oplus$ is not modulo 2 addition (see latest edit for more details about the definition).

  1. $V(k+1, 2k-1) = V(k+1,2k-1) \oplus V(1, k-1)$
  2. $k = min(L)$ where $L = [j | j > k, V_j = 1]$
  3. If $L$ is empty then compute $M = [i | V_i = 1]$ else go to 1.

$M$ represents the Ulam sequence so generated. It turns out that the sequence is generated properly (for the initial two Ulam numbers being 1, 2) upto a certain point and then gives wrong results.

Actual sequence: $1, 2, 3, 4, 6, 8, 11, 13, 16, 18, 26,..$

Computed sequence: $1,2,3,4,6,8,11,13,14,16,21,24,...$

Is there any fault in the order of the operations or is there an inherent flaw in the algorithm?

CONCLUSIVE EDIT

After carefully re-reading the paper I have come to the conclusion that the operator $\oplus$ is not modulo 2 but is actually defined the following way :

$V \oplus W = [v_i + w_i]$ if $w_i = 1$ and $ = [v_i]$ otherwise.

This question is closed.

  • 0
    In step 2, what is $n$? Also, is the first element of $V$ denoted as $V_0$ or $V_1$?2017-02-28
  • 0
    I have edited. $n=1$. The first element is $V_1$.2017-02-28
  • 0
    I seem to have gotten $1, 2, 3, 4, 6, 8, 11, 13, 14, 15, 16, 17, 19, 22, 24, 25, 27, 32, 35, 36, 38, 45, \dots$ as the first few after executing a quick script in Python. Not sure if my code is wrong or if my interpretation of the algorithm is incorrect, or if there is a flaw in the algorithm like you suggested. I'll try to do it by hand when I have time to see if I can arrive at the same computed sequence that you have.2017-02-28
  • 0
    The author "proves" that this procedure works using induction. However, I guess inductive proofs don't work if there is a counter-example sitting at some point in between.2017-02-28

2 Answers 2

0

So I made a modification to the algorithm which appears to make it work. So the way the algorithm seems to work is that initially all the numbers (with the exception of $1$ and $2$) are initially set to being not Ulam (which we denote with the bit $0$). For each integer $n$, every time we findsome combination of two distinct Ulam numbers that add to $n$, we flip the bit.

The first time the $n$th bit is flipped (from $0$ to $1$) marks the first time we find a combination of two distinct Ulam numbers that add to $n$. Assuming this bit is never flipped again, then we know that $n$ is indeed an Ulam number since the combination of distinct Ulam numbers that add up to $n$ is unique. The second time we flip the bit (from $1$ to $0$) that is also fine since that means we've found two combinations of distinct Ulam numbers that add to $n$, and thus $n$ is not an Ulam number (which is denoted by $0$). The issue arises if we flip the bit a third time (from 0 to 1) since that means that we've found three pairs of distinct Ulam numbers that add to $n$ and thus $n$ is not Ulam, yet the $n$th bit is $1$ which represents that it is.

If you run through the algorithm, you'll find that $14$ is the first number whose bit is flipped three times which is why it's the first discrepancy between the actual and computed sequence. After this point, the algorithm then returns incorrect results because it believes that $14$ is an Ulam number.

I've remedied this by keeping track of a list of numbers called $Not\_Ulam$. Before we flip any bits, we see if the number is in the $Not\_Ulam$ list, if it is, we ignore it. If it isn't and if we want to flip the bit from $0$ to $1$, we do so. If we want to flip the bit from $1$ to $0$, we do so and then include that number in the $Not\_Ulam$ list.

Below is my Python code that computes using the modified algorithm described. Please excuse the auxiliary functions and some of the inefficiencies in the program; I was trying to have the program stay true to the original algorithm as close as possible. (The auxiliary functions are to take care of the fact that Python has 0-indexed arrays and I didn't want to deal with constant off-by-one errors throughout).

global V
upper = 1000
V = [0]*upper

def getV(i):
  return V[i-1]

def setV(i,a):
  V[i-1] = a

setV(1,1)
setV(2,1)
k=2
L = []
notUlam = []

while True and k < upper/2:
  i = k+1
  while i <=2*k-1:
    if i not in notUlam:
      if getV(i) == 1 and getV(i-k) == 1:
        setV(i,(getV(i)+getV(i-k))%2)
        notUlam.append(i)
      else:
        setV(i,(getV(i)+getV(i-k))%2)

    i += 1
  L = [j for j in range(k+1,upper) if getV(j) == 1]
  if L == []:
    break
  else:
    k = min(L)


print([i+1 for i in range(0, len(V)) if V[i]==1])

EDIT: I've changed the code to make it a bit more "bitwisey" by introducing a list $W$ which serves a similar function to $Not\_Ulam$. I couldn't get the bitwise operations to be "nice" though, although maybe it could be cleaned up using a bit of Boolean algebra to simplify the Boolean expression.

upper = 100
V = [0]*upper
W = [1]*upper
V[0] = 1
V[1] = 1
W[0] = 0
W[1] = 0
k=2

while True and k < upper/2:
  V[k:2*k-1] = [((not a) and b and c) or (a and (not b) and (not c)) or (a and (not b) and c) for a,b,c in zip(V[k:2*k-1], V[0:k-1], W[k:2*k-1])]
  W[k:2*k-1] = [(not a) and b for a,b in zip(V[0:k-1], W[k:2*k-1])]
  print(V)
  L = [j for j in range(k+1,upper) if V[j-1] == 1]
  if L == []:
    break
  else:
    k = min(L)
print([i+1 for i in range(0, len(V)) if V[i]==1])
  • 0
    So you agree that the proof by induction is wrong? Like the induction going wrong at Ulam number 14? I can post pictures of the paper if that is legal.2017-02-28
  • 0
    I would not go as far as saying that the proof by induction is necessarily wrong; given my academic standing (a prospective grad student in math in the fall) I feel that I neither have the experience, credibility, nor confidence required to say something that would discredit the author of the paper, those that peer-reviewed the paper, and the journal to which the paper was published.2017-02-28
  • 0
    At most, I can say that at least one of the following is true: (1) the proof is incorrect, (2) the algorithm is incorrect (or contains a typo), (3) your transcription of the algorithm is incorrect, (4) my interpretation of your transcription of the algorithm is incorrect, or (5) my program is incorrect (based on my interpretation).2017-02-28
  • 0
    I guess it's (3) but I have checked it many times and it was exactly the same as that in the paper. My guess was on the fact that the induction worked till a certain point (until 14 happened in the list of possible Ulam numbers). I will go thru the paper once again and check. If (4) or (5) is right then isn't this answer wrong?2017-02-28
  • 0
    Yes, if (4) or (5) is correct, then yes, this answer is wrong. Of course, whenever I do write an answer on Math Stackexchange, I do like to believe that I am answering both correctly and to the best of my ability. Being a human being, I am prone to making errors myself, so I never entirely rule out the possibility of being incorrect (however any doubts I have of being wrong does decrease towards zero if I become increasingly convinced my answer is correct for whatever reason. For example, I can say with extremely strong confidence that $1+1=2$ assuming I haven't been lied to my whole life).2017-02-28
  • 0
    I would be interested if perhaps someone else could provide a second opinion (in particular, someone that has access to the paper). Perhaps you could show the paper and this webpage to some other Math or CS professors you are in regular contact with and see what they think.2017-02-28
0

If it may be of any use, I have provided the Haskell implementation of the algorithm below. The mistake was in the definition of the way the summation of the sub-arrays took place and it is clarified in the $sumL$ function. Thanks to benguin for the alternative implementation in Python.

v = [1,1] ++ [0 | x <- [1..25]] :: [Int]


vsel :: [Int] -> Int -> Int -> [Int]
vsel v a b = take (b-a+1) (drop (a-1) v)

sumL :: [Int] -> [Int] -> [Int]
sumL [] a = a
sumL a [] = a
sumL (x:xs) (y:ys) | y == 1    = (x+y):(sumL xs ys)
                   | otherwise = x:(sumL xs ys)

updatev :: [Int] -> Int -> [Int]
--update the bit vector with this function
--updatev vsofar k = vnewsofar
--v(k+1, 2k-1) = v(k+1, 2k-1) + v(1, k-1)
updatev vec k =  (vsel vec 1 (k)) ++ (sumL (vsel vec (k+1) (2*k - 1)) (vsel vec 1 (k-1))) ++ (vsel vec (2*k) (length vec))

updatek :: Int -> [Int] -> Int -> (Int, Bool)
updatek k vec n | aleph == [] = (k, True)
                | k     > 50  = (k, True)
                | otherwise   = (minimum aleph, False) where aleph = [j | j <- [(k+1)..(length vec)], vec!!(j-1) == n]


ulamBit :: [Int] -> Bool -> Int -> [Int]
ulamBit usoFar isDone k | isDone == True     = [j | j <- [1..(length usoFar)], usoFar !! (j-1) == 1]
                        | otherwise          = ulamBit upv (snd $ updatek k upv 1) (fst $ updatek k upv 1) where upv = (updatev usoFar k)