Skip to content
This repository has been archived by the owner on May 31, 2020. It is now read-only.

Added powers of complex numbers #753

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
168 changes: 153 additions & 15 deletions batavia/types/Complex.js
Expand Up @@ -3,14 +3,18 @@ var exceptions = require('../core').exceptions
var version = require('../core').version
var type_name = require('../core').type_name
var create_pyclass = require('../core').create_pyclass
var Int = require('./Int.js')

// Helper function defined in Float.js
var scientific_notation_exponent_fix = require('./Float').scientific_notation_exponent_fix

/*************************************************************************
* A Python complex type
*************************************************************************/

var MAX_INT = new Int('9223372036854775807')
var MAX_FLOAT = new Int('179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497791')
var MIN_FLOAT = new Int('-179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497791')

function part_from_str(s) {
var types = require('../types')

Expand Down Expand Up @@ -60,9 +64,7 @@ function under_js_precision(complex) {

function Complex(re, im) {
var types = require('../types')

PyObject.call(this)

// console.log(100000, re, im);
if (types.isinstance(re, types.Str)) {
// console.log(1000, re, im);
Expand Down Expand Up @@ -271,28 +273,164 @@ Complex.prototype.__abs__ = function() {
/**************************************************
* Binary operators
**************************************************/

Complex.prototype.__pow__ = function(exponent) {
function hyp(x, y) {
x = Math.abs(x)
y = Math.abs(y)
if (x < y) {
var temp = x
x = y
y = temp
}
if (x === 0) {
return 0
} else {
var yx = y / x
return x * Math.sqrt(1 + yx * yx)
}
}
function quot(a, b) {
var r = new Complex(0, 0)
var abs_bimag = 0
var abs_breal = 0
if (b.real < 0) {
abs_breal = -b.real
} else {
abs_breal = b.real
}
if (b.imag < 0) {
abs_bimag = -b.imag
} else {
abs_bimag = b.imag
}
if (abs_breal >= abs_bimag) {
if (abs_breal === 0) {
r = new Complex(0, 0)
} else {
const ratio = b.imag / b.real
const denom = b.real + b.imag * ratio
r = new Complex((a.real + a.imag * ratio) / denom, (a.imag - a.real * ratio) / denom)
}
} else if (abs_bimag >= abs_breal) {
const ratio = b.real / b.imag
const denom = b.real * ratio + b.imag
r = new Complex((a.real * ratio + a.imag) / denom, (a.imag * ratio - a.real) / denom)
} else {
r = new Complex(NaN, NaN)
}
return r
}
function powu(x, y) {
var mask = 1
var r = new Complex(1, 0)
var p = x
while (mask > 0 && y >= mask) {
if (y & mask) {
r = __mul__(r, p)
}
mask <<= 1
p = __mul__(p, p)
}
return r
}
function powc(x, y) {
if (y.real === 0 && y.imag === 0) {
return new Complex(1, 0)
} else if (x.real === 0 && x.imag === 0) {
if (y.imag !== 0 || y.real < 0) {
throw new exceptions.ZeroDivisionError.$pyclass(
'0.0 to a negative or complex power'
)
}
return new Complex(0, 0)
}
var vabs = hyp(x.real, x.imag)
var l = Math.pow(vabs, y.real)
var at = Math.atan2(x.imag, x.real)
var phase = at * y.real
if (y.imag !== 0) {
l /= Math.exp(at * y.imag)
phase += y.imag * Math.log(vabs)
}
var r = l * Math.cos(phase)
var im = l * Math.sin(phase)
return new Complex(r, im)
}
function powi(x, y) {
var cn
if (Number(y) == 0) {
return new Complex(1, 0)
}
if (Number(y) >= MAX_INT) {
if (Number(y) <= MAX_FLOAT) {
if (x.real*x.real + x.imag*x.imag == 1) {
return powc(x, new Complex(Number(y), 0))
}
else if (x.real === 0 && x.imag === 0) {
return new Complex(0, 0)
}
throw new exceptions.OverflowError.$pyclass(
'complex exponentiation'
)
} else {
throw new exceptions.OverflowError.$pyclass(
'int too large to convert to float'
)
}
}
else if (Number(y) <= MIN_FLOAT) {
throw new exceptions.OverflowError.$pyclass(
'int too large to convert to float'
)
}
else if (x.real === 0 && x.imag === 0) {
if (Number(y) < 0) {
throw new exceptions.ZeroDivisionError.$pyclass(
'0.0 to a negative or complex power'
)
}
return new Complex(0, 0)
} else {
if (y > 100 || y < -100) {
cn = new Complex(Number(y), 0)
return powc(x, cn)
} else if (y > 0) {
return powu(x, y)
} else {
return quot(new Complex(1, 0), powu(x, -y))
}
}
}
function __pow__(x, y, inplace) {
var types = require('../types')

// types.Bool?? Yes, really; under the hood cpython checks to see if the
// exponent is a numeric type, and bool subclasses int.
// See cpython/Objects/abstract.c.
if (types.isinstance(exponent, types.Bool)) {
if (exponent.valueOf()) {
return this
if (types.isinstance(y, types.Int)) {
return powi(x, y.val)
} else if (types.isinstance(y, types.Bool)) {
if (y.valueOf()) {
return new Complex(x.real, x.imag)
} else {
return new Complex(1, 0)
}
// else if (types.isinstance(exponent, [types.Float, types.Int, types.Complex]) {
// { do some stuff }
} else if (types.isinstance(y, types.Complex)) {
return powc(x, y)
} else if (types.isinstance(y, types.Float)) {
return powc(x, new Complex(y.valueOf(), 0))
} else {
var prefix
if (inplace) {
prefix = '='
} else {
prefix = ''
}
throw new exceptions.TypeError.$pyclass(
"unsupported operand type(s) for ** or pow(): 'complex' and '" + type_name(exponent) + "'"
'unsupported operand type(s) for ** or pow()' + prefix + ": 'complex' and '" + type_name(y) + "'"
)
}
}

Complex.prototype.__pow__ = function(other) {
return __pow__(this, other)
}

function __div__(x, y, inplace) {
var types = require('../types')

Expand Down
4 changes: 0 additions & 4 deletions tests/builtins/test_pow.py
Expand Up @@ -155,10 +155,6 @@ class BuiltinTwoargPowFunctionTests(BuiltinTwoargFunctionTestCase, TranspileTest
'test_class_str',
'test_class_tuple',

'test_complex_complex',
'test_complex_float',
'test_complex_int',

'test_float_complex',
'test_float_float',
'test_float_int',
Expand Down
6 changes: 0 additions & 6 deletions tests/datatypes/test_complex.py
Expand Up @@ -31,13 +31,7 @@ class BinaryComplexOperationTests(BinaryOperationTestCase, TranspileTestCase):
data_type = 'complex'

not_implemented = [
# These two work, but print floats not *quite* right due to JS
# Python differences
# TODO: re-implement the Python float printing function.

'test_power_complex',
'test_power_float',
'test_power_int',
]


Expand Down
8 changes: 8 additions & 0 deletions tests/utils.py
Expand Up @@ -430,6 +430,13 @@ def _normalize_outputs(code1, code2, transform_output=None):
line2, val2 = _normalize(line2)
if transform_output(val1) == transform_output(val2):
line2 = line1
elif (
type(val1) == type(val2) and
type(val1) in (float, complex) and
val1+val2 != 0 and
abs(val1-val2)/abs(val1+val2) < 0.0001
):
line2 = line1

if line1 is not None:
processed_code1.append(line1)
Expand Down Expand Up @@ -820,6 +827,7 @@ def wrapper(*args, **kwargs):
'complex': [
'0j',
'1j',
'0.7071067811865476+0.7071067811865475j', # sqrt(0.5)*(1+1j) -- magnitude 1, but not pure
'3.14159265j',
'-5j',
'1+2j',
Expand Down