# The Mordell-Schinzel conjecture for cubic surfaces # János Kollár, Jennifer Li # Last updated: Saturday, November 30, 2024 # This program, cycles.py, computes all 4-tuples (a1, a2, b1, b2) where a1, a2, b1, b2 are integers ranging from -6 to +6, such that, # for the four functions A(x), A_bar(x), B(y), and B_bar(y) defined using a given 4-tuple, for every pair (x0, y0) such that each x0 and y0 is # an integer either -1 or +1, a 4-cycle of functions defined using A(x), A_bar(x), B(y), and B_bar(y) returns (x0, y0) back to itself (after # some number of iterations of the 4-cycle of functions). # bad_cycles is an array listing of all 4-tuples we want to keep track of. bad_cycles = [] def print_if_true(str, should_print = False): if should_print == True: print(str) # First, we define the functions A(x), A_bar(x), B(y), and B_bar(y). def A(a1, a2, x): return x**3 + a2*x**2 + a1*x + 1 def A_bar(a1, a2, x): return x**3 + a1*x**2 + a2*x + 1 def B(b1, b2, y): return y**3 + b2*y**2 + b1*y + 1 def B_bar(b1, b2, y): return y**3 + b1*y**2 + b2*y + 1 # show_as_int displays the entries in the 4-tuples are integers (otherwise, by default Python will write the integer 1 as 1.0, for example) def show_as_int(coordinate): return tuple(int(num) for num in coordinate) # zero_check defines a function for the first (out of two) early exit checks. Here, we are checking to see if either coordinate is zero. # The function returns True if either coordinate is zero. def zero_check(why, x, y, a1, a2, b1, b2): if x == 0: # print_if_true(f"{why} ({a1},{a2}) ({b1}, {b2}) ({x}, {y}) [zero]") return True elif y == 0: # print_if_true(f"{why} ({a1},{a2}) ({b1}, {b2}) ({x}, {y}) [zero]") return True else: return False # coord_check defines a function for the second early exit check. # Here, we are checking if either coordinate exceeds a certain value that depends on the 4-tuple. # The function returns True if any coordinate exceeds this value. def coord_check(why, x, y, a1, a2, b1, b2): m = max(abs(a2) + abs(a1), abs(b2) + abs(b1)) + 2 if x > m or y > m: # print_if_true(f"{why} ({a1},{a2}) ({b1}, {b2}) ({x}, {y}) [coord]") return True else: return False # The main goal of the cycle function is to calculate the four functions in the 4-cycle. # The input is a single 4-tuple (a1, a2, b1, b2) and a single pair (x0, y0). # The function returns the output of the last function in the 4-cycle (of the particular 4-tuple inputed). # This function also does both early exit checks (see above for those functions). If we need to exit early, then this function returns False. def cycle(a1, a2, b1, b2, x0, y0, print_all = False): if coord_check("input", x0, y0, a1, a2, b1, b2) or zero_check("input", x0, y0, a1, a2, b1, b2): return False c1 = (B(b1, b2, y0) / x0, y0) print_if_true(f" (B(y)/x, y) : {show_as_int(c1)}", print_all) if coord_check("f1", c1[0], c1[1], a1, a2, b1, b2) or zero_check("input", c1[0], c1[1], a1, a2, b1, b2): return False c2 = (c1[0], A(a1, a2, c1[0]) / c1[1]) print_if_true(f" (x, A(x)/y) : {show_as_int(c2)}", print_all) if coord_check("f2", c2[0], c2[1], a1, a2, b1, b2) or zero_check("input", c2[0], c2[1], a1, a2, b1, b2): return False c3 = (B_bar(b1, b2, c2[1]) / c2[0], c2[1]) print_if_true(f" (B_bar(y)/x, y): {show_as_int(c3)}", print_all) if coord_check("f3", c3[0], c3[1], a1, a2, b1, b2) or zero_check("input", c3[0], c3[1], a1, a2, b1, b2): return False c4 = (c3[0], A_bar(a1, a2, c3[0]) / c3[1]) if coord_check("f4", c4[0], c4[1], a1, a2, b1, b2) or zero_check("input", c4[0], c4[1], a1, a2, b1, b2): return False print_if_true(f" (x, A_bar(x)/y): {show_as_int(c4)}", print_all) return c4 # The process_cycles function calls the cycles function (see above) and keeps track of which 4-tuples we are interested in # (these are the 4-tuples that will possibly be added to the "bad cycles list"). # This function takes as input a single 4-tuple along with an (x0, y0) pair. # This function returns 3 different values: # 1) False means we stop because of an early exit check, i.e., a coordinate is 0 or a coordinate is too large. # 2) True means that after processing the cycle, we found that the output pair (the output of the last function of the 4-cycle) matches # the (x0, y0) we started with. That is, this (x0, y0) looped back for this particular (a1, a2, b1, b2). # 3) the output (x, y) means we will run the function cycle again; # this is because we haven't encountered an early exit nor have we cycled back to the original (x0, y0) yet. def process_cycles(a1, a2, b1, b2, initial_x0, initial_y0, x0, y0, print_all): result = cycle(a1, a2, b1, b2, x0, y0, print_all) if result == False: # print_if_true("not interested (e2)") return False elif zero_check("result", result[0], result[1], -1, 0, 2, 3): # print_if_true("not interested") return False elif result[0] == initial_x0 and result[1] == initial_y0: # print_if_true(f"interested: {a1} {a2} {b1} {b2} ({x0}, {y0})") return True else: return result # This is where the computation begins. # In the series of six four loops, we traverse through all possible values of a1, a2, b1, and b2 (each an integer ranging from -6 to +6). # For every possible 4-tuple, we check all four possible pairs (x0, y0) where each x0 and y0 are integers that are either -1 or +1. # We count the number of pairs (x0, y0) that loop back to itself. The maximum number is 4. # If total_x0y0_looped_back = 4, then we know that all four pairs (x0, y0) looped back to itself. Then, we add the particular 4-tuple to the "bad cycles list". # At the end, we go through all 4-tuples in the "bad cycles list" and print the following additional information: # 1) for each 4-tuple of interest: # 2) for each of the four possible pairs (x0, y0): # a) the output of each cycle iteration (these are the outputs of the four functions in the 4-cycle); # b) the total number of iterations (or powers) it takes for the (x0, y0) to loop back to itself. for a1 in range(-6, 7): for a2 in range(-6, 7): for b1 in range(-6, 7): for b2 in range(-6, 7): total_x0y0_looped_back = 0 # This is a counter to determine how many (x0, y0) pairs looped back for a particular 4-tuple. for x_0 in (-1, 1): for y_0 in (-1, 1): result = (x_0, y_0) # returns set when should run again while result != False and result != True: result = process_cycles(a1, a2, b1, b2, x_0, y_0, result[0], result[1], False) if result == True: total_x0y0_looped_back = total_x0y0_looped_back + 1 if total_x0y0_looped_back == 4: # When this line is true, it means that all four possible (x0, y0) pairs looped back to itself, so the particular 4-tuple we are at must be added to the "bad list". bad_cycles.append((a1, a2, b1, b2)) # Adding the 4-tuple to the bad list # At this point, we know all of the (x0, y0) pairs that cycle back to itself # Next, we call process_cycles again, but only for the "bad 4-tuples", and this time we print all of the information print(f"") print(f"") print(f"Start testing: (a1, a2, b1, b2) = ({a1}, {a2}, {b1}, {b2})") for x0 in (-1, 1): for y0 in (-1, 1): print(f" Start testing: (x0, y0) = ({x0}, {y0})") result = (x0, y0) # returns a set when should run again cycle_iteration = 1 while result != False and result != True: print(f" Cycle iteration #{cycle_iteration} for (x0, y0) = ({x0}, {y0})") result = process_cycles(a1, a2, b1, b2, x0, y0, result[0], result[1], True) cycle_iteration = cycle_iteration + 1 print(f" After {cycle_iteration-1} power(s) of the 4-cycle, for (a1, a2, b1, b2) = ({a1}, {a2}, {b1}, {b2}), the (x0, y0) pair ({x0}, {y0}) loops back to itself.") # This prints a list of all elements in the "bad cycles list". print() print("For these (a1, a2, b1, b2), every combination of pairs of (x0, y0) returns to (x0, y0):") for bad_cycle in bad_cycles: print(f" (a1, a2, b1, b2) = ({bad_cycle[0]}, {bad_cycle[1]}, {bad_cycle[2]}, {bad_cycle[3]})")