Skip to content

Commit ecaa933

Browse files
committed
[GR-23281] Implementation of cmath functions isinf, isnan, isfinite, polar, phase and sqrt
PullRequest: graalpython/962
2 parents 039b87b + 9a98137 commit ecaa933

File tree

5 files changed

+452
-1
lines changed

5 files changed

+452
-1
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.oracle.graal.python.builtins.modules.BinasciiModuleBuiltins;
4949
import com.oracle.graal.python.builtins.modules.BuiltinConstructors;
5050
import com.oracle.graal.python.builtins.modules.BuiltinFunctions;
51+
import com.oracle.graal.python.builtins.modules.CmathModuleBuiltins;
5152
import com.oracle.graal.python.builtins.modules.CodecsModuleBuiltins;
5253
import com.oracle.graal.python.builtins.modules.CollectionsModuleBuiltins;
5354
import com.oracle.graal.python.builtins.modules.ContextvarsModuleBuiltins;
@@ -356,6 +357,7 @@ private static final PythonBuiltins[] initializeBuiltins() {
356357
new TimeModuleBuiltins(),
357358
new ModuleBuiltins(),
358359
new MathModuleBuiltins(),
360+
new CmathModuleBuiltins(),
359361
new MarshalModuleBuiltins(),
360362
new RandomModuleBuiltins(),
361363
new RandomBuiltins(),
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/* Copyright (c) 2020, Oracle and/or its affiliates.
2+
* Copyright (C) 1996-2020 Python Software Foundation
3+
*
4+
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5+
*/
6+
package com.oracle.graal.python.builtins.modules;
7+
8+
import com.oracle.graal.python.builtins.Builtin;
9+
import com.oracle.graal.python.builtins.CoreFunctions;
10+
import com.oracle.graal.python.builtins.PythonBuiltins;
11+
import com.oracle.graal.python.builtins.objects.complex.PComplex;
12+
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
13+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
14+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
15+
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
16+
import com.oracle.graal.python.nodes.util.CoerceToComplexNode;
17+
import com.oracle.graal.python.runtime.PythonCore;
18+
import com.oracle.truffle.api.CompilerDirectives;
19+
import com.oracle.truffle.api.dsl.Cached;
20+
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
21+
import com.oracle.truffle.api.dsl.ImportStatic;
22+
import com.oracle.truffle.api.dsl.NodeFactory;
23+
import com.oracle.truffle.api.dsl.Specialization;
24+
import com.oracle.truffle.api.dsl.TypeSystemReference;
25+
import com.oracle.truffle.api.frame.VirtualFrame;
26+
27+
import java.util.List;
28+
29+
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OverflowError;
30+
31+
@CoreFunctions(defineModule = "cmath")
32+
public class CmathModuleBuiltins extends PythonBuiltins {
33+
34+
@Override
35+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
36+
return CmathModuleBuiltinsFactory.getFactories();
37+
}
38+
39+
@Override
40+
public void initialize(PythonCore core) {
41+
// Add constant values
42+
builtinConstants.put("pi", Math.PI);
43+
builtinConstants.put("e", Math.E);
44+
builtinConstants.put("tau", 2 * Math.PI);
45+
builtinConstants.put("inf", Double.POSITIVE_INFINITY);
46+
builtinConstants.put("nan", Double.NaN);
47+
builtinConstants.put("infj", core.factory().createComplex(0, Double.POSITIVE_INFINITY));
48+
builtinConstants.put("nanj", core.factory().createComplex(0, Double.NaN));
49+
super.initialize(core);
50+
}
51+
52+
@TypeSystemReference(PythonArithmeticTypes.class)
53+
@ImportStatic(MathGuards.class)
54+
abstract static class CmathComplexUnaryBuiltinNode extends PythonUnaryBuiltinNode {
55+
56+
// Constants used for the definition of special values tables in subclassess
57+
static final double INF = Double.POSITIVE_INFINITY;
58+
static final double NAN = Double.NaN;
59+
60+
protected static class ComplexConstant {
61+
final double real;
62+
final double imag;
63+
64+
ComplexConstant(double real, double imag) {
65+
this.real = real;
66+
this.imag = imag;
67+
}
68+
}
69+
70+
private enum SpecialType {
71+
NINF, // 0, negative infinity
72+
NEG, // 1, negative finite number (nonzero)
73+
NZERO, // 2, -0.0
74+
PZERO, // 3, +0.0
75+
POS, // 4, positive finite number (nonzero)
76+
PINF, // 5, positive infinity
77+
NAN; // 6, Not a Number
78+
79+
static SpecialType ofDouble(double d) {
80+
if (Double.isFinite(d)) {
81+
if (d != 0) {
82+
if (Math.copySign(1.0, d) == 1.0) {
83+
return POS;
84+
} else {
85+
return NEG;
86+
}
87+
} else {
88+
if (Math.copySign(1.0, d) == 1.0) {
89+
return PZERO;
90+
} else {
91+
return NZERO;
92+
}
93+
}
94+
}
95+
if (Double.isNaN(d)) {
96+
return NAN;
97+
}
98+
if (Math.copySign(1.0, d) == 1.0) {
99+
return PINF;
100+
} else {
101+
return NINF;
102+
}
103+
}
104+
}
105+
106+
protected PComplex specialValue(ComplexConstant[][] table, double real, double imag) {
107+
if (!Double.isFinite(real) || !Double.isFinite(imag)) {
108+
ComplexConstant c = table[SpecialType.ofDouble(real).ordinal()][SpecialType.ofDouble(imag).ordinal()];
109+
if (c == null) {
110+
CompilerDirectives.transferToInterpreterAndInvalidate();
111+
throw new IllegalStateException("should not be reached");
112+
}
113+
return factory().createComplex(c.real, c.imag);
114+
}
115+
return null;
116+
}
117+
118+
/**
119+
* Creates an instance of ComplexConstant. The name of this factory method is intentionally
120+
* short to allow subclassess compact definition of their tables of special values.
121+
*
122+
* @param real the real part of the complex constant
123+
* @param imag the imaginary part of the complex constant
124+
* @return a new instance of ComplexConstant representing the complex number real + i * imag
125+
*/
126+
protected static ComplexConstant C(double real, double imag) {
127+
return new ComplexConstant(real, imag);
128+
}
129+
130+
PComplex compute(@SuppressWarnings("unused") double real, @SuppressWarnings("unused") double imag) {
131+
CompilerDirectives.transferToInterpreterAndInvalidate();
132+
throw new IllegalStateException("should not be reached");
133+
}
134+
135+
@Specialization
136+
PComplex doL(long value) {
137+
return compute(value, 0);
138+
}
139+
140+
@Specialization
141+
PComplex doD(double value) {
142+
return compute(value, 0);
143+
}
144+
145+
@Specialization
146+
PComplex doC(PComplex value) {
147+
return compute(value.getReal(), value.getImag());
148+
}
149+
150+
@Specialization
151+
PComplex doGeneral(VirtualFrame frame, Object value, @Cached CoerceToComplexNode coerceToComplex) {
152+
return doC(coerceToComplex.execute(frame, value));
153+
}
154+
}
155+
156+
@TypeSystemReference(PythonArithmeticTypes.class)
157+
@ImportStatic(MathGuards.class)
158+
abstract static class CmathBooleanUnaryBuiltinNode extends PythonUnaryBuiltinNode {
159+
160+
boolean compute(@SuppressWarnings("unused") double real, @SuppressWarnings("unused") double imag) {
161+
CompilerDirectives.transferToInterpreterAndInvalidate();
162+
throw new IllegalStateException("should not be reached");
163+
}
164+
165+
@Specialization
166+
boolean doL(long value) {
167+
return compute(value, 0);
168+
}
169+
170+
@Specialization
171+
boolean doD(double value) {
172+
return compute(value, 0);
173+
}
174+
175+
@Specialization
176+
boolean doC(PComplex value) {
177+
return compute(value.getReal(), value.getImag());
178+
}
179+
180+
@Specialization
181+
boolean doGeneral(VirtualFrame frame, Object value, @Cached CoerceToComplexNode coerceToComplex) {
182+
return doC(coerceToComplex.execute(frame, value));
183+
}
184+
}
185+
186+
@Builtin(name = "isnan", minNumOfPositionalArgs = 1)
187+
@GenerateNodeFactory
188+
abstract static class IsNanNode extends CmathBooleanUnaryBuiltinNode {
189+
@Override
190+
boolean compute(double real, double imag) {
191+
return Double.isNaN(real) || Double.isNaN(imag);
192+
}
193+
}
194+
195+
@Builtin(name = "isinf", minNumOfPositionalArgs = 1)
196+
@GenerateNodeFactory
197+
abstract static class IsInfNode extends CmathBooleanUnaryBuiltinNode {
198+
@Override
199+
boolean compute(double real, double imag) {
200+
return Double.isInfinite(real) || Double.isInfinite(imag);
201+
}
202+
}
203+
204+
@Builtin(name = "isfinite", minNumOfPositionalArgs = 1)
205+
@GenerateNodeFactory
206+
abstract static class IsFiniteNode extends CmathBooleanUnaryBuiltinNode {
207+
@Override
208+
boolean compute(double real, double imag) {
209+
return Double.isFinite(real) && Double.isFinite(imag);
210+
}
211+
}
212+
213+
@Builtin(name = "phase", minNumOfPositionalArgs = 1)
214+
@TypeSystemReference(PythonArithmeticTypes.class)
215+
@ImportStatic(MathGuards.class)
216+
@GenerateNodeFactory
217+
abstract static class PhaseNode extends PythonUnaryBuiltinNode {
218+
219+
@Specialization
220+
double doL(long value) {
221+
return value < 0 ? Math.PI : 0;
222+
}
223+
224+
@Specialization
225+
double doD(double value) {
226+
return value < 0 ? Math.PI : 0;
227+
}
228+
229+
@Specialization
230+
double doC(PComplex value) {
231+
return Math.atan2(value.getImag(), value.getReal());
232+
}
233+
234+
@Specialization
235+
double doGeneral(VirtualFrame frame, Object value, @Cached CoerceToComplexNode coerceToComplex) {
236+
return doC(coerceToComplex.execute(frame, value));
237+
}
238+
}
239+
240+
@Builtin(name = "polar", minNumOfPositionalArgs = 1)
241+
@TypeSystemReference(PythonArithmeticTypes.class)
242+
@ImportStatic(MathGuards.class)
243+
@GenerateNodeFactory
244+
abstract static class PolarNode extends PythonUnaryBuiltinNode {
245+
246+
@Specialization
247+
PTuple doL(long value) {
248+
return doD(value);
249+
}
250+
251+
@Specialization
252+
PTuple doD(double value) {
253+
return factory().createTuple(new Object[]{Math.abs(value), value < 0 ? Math.PI : 0});
254+
}
255+
256+
@Specialization
257+
PTuple doC(PComplex value) {
258+
// TODO: the implementation of abs(z) should be shared with ComplexBuiltins.AbsNode, but
259+
// it currently does not pass the overflow test
260+
double r;
261+
if (!Double.isFinite(value.getReal()) || !Double.isFinite(value.getImag())) {
262+
if (Double.isInfinite(value.getReal())) {
263+
r = Math.abs(value.getReal());
264+
} else if (Double.isInfinite(value.getImag())) {
265+
r = Math.abs(value.getImag());
266+
} else {
267+
r = Double.NaN;
268+
}
269+
} else {
270+
r = Math.hypot(value.getReal(), value.getImag());
271+
if (Double.isInfinite(r)) {
272+
throw raise(OverflowError, "absolute value too large");
273+
}
274+
}
275+
return factory().createTuple(new Object[]{r, Math.atan2(value.getImag(), value.getReal())});
276+
}
277+
278+
@Specialization
279+
PTuple doGeneral(VirtualFrame frame, Object value, @Cached CoerceToComplexNode coerceToComplex) {
280+
return doC(coerceToComplex.execute(frame, value));
281+
}
282+
}
283+
284+
@Builtin(name = "sqrt", minNumOfPositionalArgs = 1)
285+
@GenerateNodeFactory
286+
abstract static class SqrtNode extends CmathComplexUnaryBuiltinNode {
287+
288+
// @formatter:off
289+
@CompilerDirectives.CompilationFinal(dimensions = 2)
290+
private static final ComplexConstant[][] SPECIAL_VALUES = {
291+
{C(INF, -INF), C(0.0, -INF), C(0.0, -INF), C(0.0, INF), C(0.0, INF), C(INF, INF), C(NAN, INF)},
292+
{C(INF, -INF), null, null, null, null, C(INF, INF), C(NAN, NAN)},
293+
{C(INF, -INF), null, C(0.0, -0.0), C(0.0, 0.0), null, C(INF, INF), C(NAN, NAN)},
294+
{C(INF, -INF), null, C(0.0, -0.0), C(0.0, 0.0), null, C(INF, INF), C(NAN, NAN)},
295+
{C(INF, -INF), null, null, null, null, C(INF, INF), C(NAN, NAN)},
296+
{C(INF, -INF), C(INF, -0.0), C(INF, -0.0), C(INF, 0.0), C(INF, 0.0), C(INF, INF), C(INF, NAN)},
297+
{C(INF, -INF), C(NAN, NAN), C(NAN, NAN), C(NAN, NAN), C(NAN, NAN), C(INF, INF), C(NAN, NAN)},
298+
};
299+
// @formatter:on
300+
301+
@Override
302+
PComplex compute(double real, double imag) {
303+
PComplex result = specialValue(SPECIAL_VALUES, real, imag);
304+
if (result != null) {
305+
return result;
306+
}
307+
if (real == 0.0 && imag == 0.0) {
308+
return factory().createComplex(0.0, imag);
309+
}
310+
311+
double ax = Math.abs(real);
312+
double ay = Math.abs(imag);
313+
314+
double s;
315+
if (ax < Double.MIN_NORMAL && ay < Double.MIN_NORMAL && (ax > 0.0 || ay > 0.0)) {
316+
final double scaleUp = 0x1.0p53;
317+
final double scaleDown = 0x1.0p-27;
318+
ax *= scaleUp;
319+
s = Math.sqrt(ax + Math.hypot(ax, ay * scaleUp)) * scaleDown;
320+
} else {
321+
ax /= 8.0;
322+
s = 2.0 * Math.sqrt(ax + Math.hypot(ax, ay / 8.0));
323+
}
324+
double d = ay / (2.0 * s);
325+
326+
if (real >= 0.0) {
327+
return factory().createComplex(s, Math.copySign(d, imag));
328+
}
329+
return factory().createComplex(d, Math.copySign(s, imag));
330+
}
331+
}
332+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathGuards.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,6 +40,7 @@
4040
*/
4141
package com.oracle.graal.python.builtins.modules;
4242

43+
import com.oracle.graal.python.builtins.objects.complex.PComplex;
4344
import com.oracle.graal.python.builtins.objects.floats.PFloat;
4445
import com.oracle.graal.python.builtins.objects.ints.PInt;
4546

@@ -60,6 +61,10 @@ public static boolean isNumber(Object value) {
6061
return isInteger(value) || value instanceof Float || value instanceof Double || value instanceof PFloat;
6162
}
6263

64+
public static boolean isComplexNumber(Object value) {
65+
return isNumber(value) || value instanceof PComplex;
66+
}
67+
6368
public static boolean isInteger(Object value) {
6469
return value instanceof Integer || value instanceof Long || value instanceof PInt || value instanceof Boolean;
6570
}

0 commit comments

Comments
 (0)