gnofract4d fails to build with Python 3.14.0a4. _______________________________ Test.test_stdlib _______________________________ self = <fract4d_compiler.tests.test_codegen.Test testMethod=test_stdlib> def test_stdlib(self): '''This is the slowest test, due to how much compilation it does. Calls standard functions with a variety of values, checking that they produce the right answers''' # additions to python math stdlib def myfcotan(x): return math.cos(x) / math.sin(x) def myfcotanh(x): return math.cosh(x) / math.sinh(x) def mycotanh(z): return cmath.cosh(z) / cmath.sinh(z) def myasinh(z): return cmath.log(z + cmath.sqrt(z * z + 1)) def myacosh(z): return cmath.log(z + cmath.sqrt(z - 1) * cmath.sqrt(z + 1)) def myctrunc(z): return complex(int(z.real), int(z.imag)) def mycfloor(z): return complex(math.floor(z.real), math.floor(z.imag)) def mycround(z): return complex(int(z.real + 0.5), int(z.imag + 0.5)) def mycceil(z): x = complex(math.ceil(z.real), math.ceil(z.imag)) return x def mycosxx(z): # python 2.6's cmath cos produces opposite sign from 2.5 & # my implementation. odd but hopefully harmless if z == (0 - 1j): return cmath.cos(z) cosz = cmath.cos(z) i = -cosz.imag return complex(cosz.real, i) def myczero(z): return complex(0, 0) tests = [ # code to run, var to inspect, result ["fm = (3.0 % 2.0, 3.1 % 1.5)", "fm", "(1,0.1)"], ["cj = conj(y)", "cj", "(1,-2)"], ["fl = flip(y)", "fl", "(2,1)"], ["ri = (imag(y),real(y))", "ri", "(2,1)"], ["m = |y|", "m", "(5,0)"], ["t = (4,2) * (2,-1)", "t", "(10,0)"], ["d1 = y/(1,0)", "d1", "(1,2)"], ["d2 = y/y", "d2", "(1,0)"], ["d3 = (4,2)/y", "d3", "(1.6,-1.2)"], ["d4 = (2,1)/2", "d4", "(1,0.5)"], ["recip1 = recip((4,0))/recip(4)", "recip1", "(1,0)"], ["i = ident(y)", "i", "(1,2)"], ["a = (abs(4),abs(-4))", "a", "(4,4)"], ["a2 = abs((4,-4))", "a2", "(4,4)"], ["cab = (cabs((0,0)), cabs((3,4)))", "cab", "(0,5)"], ["sq = (sqrt(4),sqrt(2))", "sq", self.predict(math.sqrt, 4, 2)], ["l = (log(1),log(3))", "l", self.predict(math.log, 1, 3)], ["ex = (exp(1),exp(2))", "ex", self.predict(math.exp, 1, 2)], ["pow0a = (2^2, 9^0.5)", "pow0a", "(4,3)"], ["pow0b = ((-1)^0.5,(-1)^2)", "pow0b", "(nan,1)"], ["pow1 = (1,0)^2", "pow1", "(1,0)"], ["pow2 = (-2,-3)^7.5", "pow2", "(-13320.5,6986.17)"], ["pow3 = (-2,-3)^(1.5,-3.1)", "pow3", "(0.00507248,-0.00681128)"], ["pow4 = (0,0)^(1.5,-3.1)", "pow4", "(0,0)"], ["manh1 = (manhattanish(2.0,-1.0),manhattanish(0.1,-0.1))", "manh1", "(1,0)"], ["manh2 = (manhattan(2.0,-1.5),manhattan(-2,1.7))", "manh2", "(3.5,3.7)"], ["manh3 = (manhattanish2(2.0,-1.0),manhattanish2(0.1,-0.1))", "manh3", "(25,0.0004)"], ["mx2 = (max2(2,-3),max2(-3,0))", "mx2", "(9,9)"], ["mn2 = (min2(-1,-2),min2(7,4))", "mn2", "(1,16)"], ["r2 = (real2(3,1),real2(-2.5,2))", "r2", "(9,6.25)"], ["i2 = (imag2(3,2),imag2(2,-0))", "i2", "(4,0)"], ["ftrunc1 = (trunc(0.5), trunc(0.4))", "ftrunc1", "(0,0)"], ["ftrunc2 = (trunc(-0.5), trunc(-0.4))", "ftrunc2", "(0,0)"], ["frnd1 = (round(0.5), round(0.4))", "frnd1", "(1,0)"], ["frnd2 = (round(-0.5), round(-0.4))", "frnd2", "(0,0)"], ["fceil1 = (ceil(0.5), ceil(0.4))", "fceil1", "(1,1)"], ["fceil2 = (ceil(-0.5), ceil(-0.4))", "fceil2", "(0,0)"], ["ffloor1 = (floor(0.5), floor(0.4))", "ffloor1", "(0,0)"], ["ffloor2 = (floor(-0.5), floor(-0.4))", "ffloor2", "(-1,-1)"], ["fzero = (zero(77),zero(-41.2))", "fzero", "(0,0)"], ["fmin = (min(-2,-3), min(2,3))", "fmin", "(-3,2)"], ["fmax = (max(-2,-3), max(2,3))", "fmax", "(-2,3)"], # trig functions ["t_sin = (sin(0),sin(1))", "t_sin", self.predict(math.sin)], ["t_cos = (cos(0),cos(1))", "t_cos", self.predict(math.cos)], ["t_tan = (tan(0),tan(1))", "t_tan", self.predict(math.tan)], ["t_cotan = (cotan(0),cotan(1))", "t_cotan", self.predict(myfcotan)], ["t_sinh = (sinh(0),sinh(1))", "t_sinh", self.predict(math.sinh)], ["t_cosh = (cosh(0),cosh(1))", "t_cosh", self.predict(math.cosh)], ["t_tanh = (tanh(0),tanh(1))", "t_tanh", self.predict(math.tanh)], ["t_cotanh = (cotanh(0),cotanh(1))", "t_cotanh", self.predict(myfcotanh)], # inverse trig functions ["t_asin = (asin(0),asin(1))", "t_asin", self.predict(math.asin)], ["t_acos = (acos(0),acos(1))", "t_acos", self.predict(math.acos)], ["t_atan = (atan(0),atan(1))", "t_atan", self.predict(math.atan)], ["t_atan2 = (atan2((1,1)),atan2((-1,-1)))", "t_atan2", "(0.785398,-2.35619)"], # these aren't in python stdlib, need to hard-code results ["t_asinh = (asinh(0),asinh(1))", "t_asinh", "(0,0.881374)"], ["t_acosh = (acosh(10),acosh(1))", "t_acosh", "(2.99322,0)"], ["t_atanh = (atanh(0),atanh(0.5))", "t_atanh", "(0,0.549306)"], ] tests += self.manufacture_tests("sin", cmath.sin) tests += self.manufacture_tests("cos", cmath.cos) tests += self.manufacture_tests("cosxx", mycosxx) tests += self.manufacture_tests("tan", cmath.tan) tests += self.manufacture_tests("sinh", cmath.sinh) tests += self.manufacture_tests("cosh", cmath.cosh) tests += self.manufacture_tests("tanh", cmath.tanh) tests += self.manufacture_tests("exp", cmath.exp) tests += self.manufacture_tests("sqrt", cmath.sqrt) tests += self.manufacture_tests("round", mycround) tests += self.manufacture_tests("ceil", mycceil) tests += self.manufacture_tests("floor", mycfloor) tests += self.manufacture_tests("trunc", myctrunc) tests += self.manufacture_tests("zero", myczero) tests += self.cotantests() tests += self.cotanhtests() tests += self.logtests() tests += self.asintests() tests += self.acostests() tests += self.atantests() tests += self.manufacture_tests("asinh", myasinh) tests += self.manufacture_tests("acosh", myacosh) tests += self.atanhtests() # construct a formula calculating all of the above, # run it and compare results with expected values src = 't_c6{\ninit: y = (1,2)\n' + \ "\n".join([x[0] for x in tests]) + "\n}" check = "\n".join([self.inspect_complex(x[1]) for x in tests]) exp = ["%s = %s" % (x[1], x[2]) for x in tests] > self.assertCSays(src, "init", check, exp) fract4d_compiler/tests/test_codegen.py:1995: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ fract4d_compiler/tests/test_codegen.py:2305: in assertCSays self.assertEqual(exp, res) E AssertionError: 'ct_sin6 = (0.841471,-0)' != 'ct_sin6 = (0.841471,0)' E - ct_sin6 = (0.841471,-0) E ? - E + ct_sin6 = (0.841471,0) https://docs.python.org/3.14/whatsnew/3.14.html For the build logs, see: https://copr-be.cloud.fedoraproject.org/results/@python/python3.14/fedora-rawhide-x86_64/08558686-gnofract4d/ For all our attempts to build gnofract4d with Python 3.14, see: https://copr.fedorainfracloud.org/coprs/g/python/python3.14/package/gnofract4d/ Testing and mass rebuild of packages is happening in copr. You can follow these instructions to test locally in mock if your package builds with Python 3.14: https://copr.fedorainfracloud.org/coprs/g/python/python3.14/ Let us know here if you have any questions. Python 3.14 is planned to be included in Fedora 43. To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.14. A build failure prevents us from testing all dependent packages (transitive [Build]Requires), so if this package is required a lot, it's important for us to get it fixed soon. We'd appreciate help from the people who know this package best, but if you don't want to work on this now, let us know so we can try to work around it on our side.
-0 vs. 0, just lovely... Any mathematicians around to explain that? I'm afraid I could use some help with that. FWIW, this just successfully rebuilt for the F42 mass rebuild.
$ python3.13 -c 'from cmath import sin; print(sin(1-0j))' (0.8414709848078965+0j) $ python3.14 -c 'from cmath import sin; print(sin(1-0j))' (0.8414709848078965-0j) I am no math expert, but I think both results are de facto correct. We can bisect Python to see where this happened if it helps.
commit 987311d42e3ec838de8ff27f9f0575aa791a6bde Author: Sergey B Kirpichev <skirpichev> Date: Tue Nov 26 18:57:39 2024 +0300 gh-69639: Add mixed-mode rules for complex arithmetic (C-like) (GH-124829) "Generally, mixed-mode arithmetic combining real and complex variables should be performed directly, not by first coercing the real to complex, lest the sign of zero be rendered uninformative; the same goes for combinations of pure imaginary quantities with complex variables." (c) Kahan, W: Branch cuts for complex elementary functions. This patch implements mixed-mode arithmetic rules, combining real and complex variables as specified by C standards since C99 (in particular, there is no special version for the true division with real lhs operand). Most C compilers implementing C99+ Annex G have only these special rules (without support for imaginary type, which is going to be deprecated in C2y). https://github.com/python/cpython/commit/987311d42e3ec838de8ff27f9f0575aa791a6bde https://github.com/python/cpython/pull/124829 https://github.com/python/cpython/issues/69639 ---- I don't know if the -0 thing is a desired result of this or an accidental one.
I asked in https://github.com/python/cpython/issues/69639#issuecomment-2614336920
This bug appears to have been reported against 'rawhide' during the Fedora Linux 42 development cycle. Changing version to 42.
Created attachment 2093177 [details] Possible patch The relevant code appears to be: ``` def cpredict(self, f, arg=(1 + 0j)): try: z = f(arg) return "(%.6g,%.6g)" % (z.real, z.imag) except OverflowError: return "(inf,inf)" except ZeroDivisionError: return "(nan,nan)" except ValueError: return "(inf,inf)" def make_test(self, myfunc, pyfunc, val, n): codefrag = "ct_%s%d = %s((%d,%d))" % ( myfunc, n, myfunc, val.real, val.imag) lookat = "ct_%s%d" % (myfunc, n) result = self.cpredict(pyfunc, val) return [codefrag, lookat, result] def manufacture_tests(self, myfunc, pyfunc): vals = [0 + 0j, 0 + 1j, 1 + 0j, 1 + 1j, 3 + 2j, 1 - 0j, 0 - 1j, -3 + 2j, -2 - 2j, -1 + 0j] return [self.make_test(myfunc, pyfunc, x_y[0], x_y[1]) for x_y in zip(vals, list(range(1, len(vals))))] ``` It's the `1 - 0j` iteration that's failing. So I think what's happening here is that, while the sign is being preserved by Python initially (as of 3.14), it's not being preserved elsewhere: 1) it is not preserved when passed through the "%d" formatting of `make_test`, e.g.: >>> val = 1 - 0j >>> val (1-0j) >>> val.imag -0.0 >>> print("%d" % val.imag) 0 2) it is not being preserved by C++ and/or gf4d; note other places marked `CONSIDER` in the sources which need to be fixed due to mismatches. The simplest workaround appears to be to skip the 1-0j iteration, per the attached patch which I tested in the side tag: https://koji.fedoraproject.org/koji/taskinfo?taskID=133610276 I'm open to suggestions though.
*** Bug 2371720 has been marked as a duplicate of this bug. ***