(ns emmy.complex
"This namespace provides a number of functions and constructors for working
with [[Complex]] numbers in Clojure and ClojureScript, and
installs [[Complex]] into the Emmy generic arithmetic system.
For other numeric extensions, see [[emmy.ratio]]
and [[emmy.numbers]]."
(:require #?(:cljs ["complex.js$default" :as Complex])
#?(:cljs [goog.object :as obj])
[emmy.generic :as g]
[emmy.util :as u]
[emmy.value :as v])
#?(:clj
(:import (org.apache.commons.math3.complex Complex ComplexFormat))))
(def ZERO
"A [[Complex]] value equal to 0 (south pole on the Riemann Sphere)."
#?(:clj Complex/ZERO
:cljs (obj/get Complex "ZERO")))
#emmy/complex[0 0x0]
(def ONE
"A [[Complex]] value equal to 1."
#?(:clj Complex/ONE
:cljs (obj/get Complex "ONE")))
#emmy/complex[1 0x0]
(def I
"A [[Complex]] value equal to `i`."
#?(:clj Complex/I
:cljs (obj/get Complex "I")))
#emmy/complex[0 0x1]

NOTE that on the JVM this obnoxiously negates the (zero-valued) real component too. So (complex 0 -1) does not equal -I... but (complex -0.0 -1.0) does. Once we get a native complex implementation in this issue will disappear.

(def -I
"A [[Complex]] value equal to `-i`."
#?(:clj (.negate Complex/I)
:cljs (.neg ^js (obj/get Complex "I"))))
#emmy/complex[0 0x-1]
(def ^:no-doc complextype Complex)
org.apache.commons.math3.complex.Complex
(derive ::complex ::v/number)
nil
#?(:clj
(def complex-format (ComplexFormat.)))
#object[org.apache.commons.math3.complex.ComplexFormat 0x51f2982 "
org.apache.commons.math3.complex.ComplexFormat@51f2982"
]
(defn complex
"Returns a [[Complex]] number with the supplied real part `re` and imaginary
part `im`. `im` defaults to 0."
([re]
#?(:clj (if (string? re)
(.parse ^ComplexFormat complex-format re)
(Complex. (u/double re)))
:cljs (Complex.
(if (string? re)
re
(u/double re)))))
([re im]
(Complex. (u/double re)
(u/double im))))
#object[emmy.complex$complex 0x22baffd "
emmy.complex$complex@22baffd"
]
(defn complex?
"Returns true if `a` is an instance of [[Complex]], false otherwise."
[a]
(instance? Complex a))
#object[emmy.complex$complex_QMARK_ 0x35b8f40e "
emmy.complex$complex_QMARK_@35b8f40e"
]
(defn ^:no-doc real [^Complex a]
#?(:clj (.getReal a)
:cljs (obj/get a "re")))
#object[emmy.complex$real 0x3fc55969 "
emmy.complex$real@3fc55969"
]
(defn ^:no-doc imaginary [^Complex a]
#?(:clj (.getImaginary a)
:cljs (obj/get a "im")))
#object[emmy.complex$imaginary 0x43ea342a "
emmy.complex$imaginary@43ea342a"
]
(defn ^:no-doc parse-complex
"Parser that converts a string, vector or numeric representation of a complex
number, like
- `1 + 3i`
- [1 3]
- 1
into a [[Complex]] number object in clj or cljs."
[x]
(cond (string? x)
#?(:clj
(let [v (.parse ^ComplexFormat complex-format x)]
`(complex ~(real v) ~(imaginary v)))
:cljs `(complex ~x))
(vector? x)
(let [[re im] x]
(if (nil? im)
`(complex ~re)
`(complex ~re ~im)))
(number? x) `(complex ~x)
:else (u/illegal
(str
"#emmy/complex takes a string, 2-vector or a number. Received: "
x))))
#object[emmy.complex$parse_complex 0x1c612fed "
emmy.complex$parse_complex@1c612fed"
]

Type Extensions

#?(:clj
(defmethod print-method Complex [^Complex v ^java.io.Writer w]
(.write w (str "#emmy/complex "
[(.getReal v)
(.getImaginary v)]))))
#object[clojure.lang.MultiFn 0x5877451f "
clojure.lang.MultiFn@5877451f"
]
(extend-type Complex
v/Numerical
(numerical? [_] true)
v/IKind
(kind [_] ::complex))
nil

Gaussian Integers

(defn round
"Generates a [Gaussian integer](https://en.wikipedia.org/wiki/Gaussian_integer)
from the complex number `z` by rounding the real and imaginary components of
`z` to their nearest integral values."
[z]
(cond (complex? z)
(complex
(Math/round ^Float (real z))
(Math/round ^Float (imaginary z)))
(v/native-integral? z) z
:else (Math/round (double z))))
#object[emmy.complex$round 0x6e0ba0eb "
emmy.complex$round@6e0ba0eb"
]
(defn gaussian-integer?
"Returns true if `z` is a [Gaussian
integer](https://en.wikipedia.org/wiki/Gaussian_integer), i.e., a complex entry
with integral real and imaginary components.
[[gaussian-integer?]] will return true if the real and imaginary components
are within `epsilon` of integral values. See [[value/almost-integral?]] for
details."
[z]
(if (complex? z)
(and (g/almost-integral? (real z))
(g/almost-integral? (imaginary z)))
(and (v/real? z)
(g/almost-integral? z))))
#object[emmy.complex$gaussian_integer_QMARK_ 0x7b10a252 "
emmy.complex$gaussian_integer_QMARK_@7b10a252"
]

Complex GCD

(defn ^:no-doc abs-real
"Returns a complex or real number with a positive real component. (i.e., either z
or (* -1 z)), whichever number has a positive real component."
[z]
(cond (complex? z)
(if (neg? (real z))
(g/negate z)
z)
(v/real? z)
(Math/abs ^double (u/double z))
:else (u/illegal "not supported!")))
#object[emmy.complex$abs_real 0x6949927e "
emmy.complex$abs_real@6949927e"
]
(defn ^:no-doc gcd
"Returns the complex gcd of two complex numbers using the euclidean algorithm.
For more details on the algorithm, see [this post on Ask Dr
Math](https://web.archive.org/web/20190720160400/http://mathforum.org/library/drmath/view/67068.html).
NOTE that the GCD of two complex numbers is determined up to a factor of ±1
and ±i."
[l r]
(cond (g/zero? l) r
(g/zero? r) l
(v/= l r) (abs-real l)
(not (or (gaussian-integer? l)
(gaussian-integer? r)))
(u/illegal "gcd can only be computed for gaussian integers, but
both arguments were not.")
(not (gaussian-integer? l))
(u/illegal "gcd can only be computed for gaussian integers, but first
argument was not.")
(not (gaussian-integer? r))
(u/illegal "gcd can only be computed for gaussian integers, but second
argument was not.")
:else (let [[l r] (if (< (g/magnitude l)
(g/magnitude r))
[l r] [r l])]
(loop [a (round l)
b (round r)]
(if (g/zero? b)
(abs-real a)
(recur b (g/sub a (g/mul (round (g/div a b)) b))))))))
#object[emmy.complex$gcd 0x7cc2d6fd "
emmy.complex$gcd@7cc2d6fd"
]

Generic Method Installation

(defmethod g/zero? [::complex] [c] #?(:clj (and (zero? (real c))
(zero? (imaginary c)))
:cljs (.isZero ^js c)))
#object[clojure.lang.MultiFn 0x78e2923f "
clojure.lang.MultiFn@78e2923f"
]
(defmethod g/one? [::complex] [c] (and (g/one? (real c))
(zero? (imaginary c))))
#object[clojure.lang.MultiFn 0x710fa3bd "
clojure.lang.MultiFn@710fa3bd"
]
(defmethod g/identity? [::complex] [c] (g/one? c))
#object[clojure.lang.MultiFn 0x3e853a24 "
clojure.lang.MultiFn@3e853a24"
]
(defmethod g/zero-like [::complex] [_] ZERO)
#object[clojure.lang.MultiFn 0x79337717 "
clojure.lang.MultiFn@79337717"
]
(defmethod g/one-like [::complex] [_] ONE)
#object[clojure.lang.MultiFn 0x46a24adf "
clojure.lang.MultiFn@46a24adf"
]
(defmethod g/identity-like [::complex] [_] ONE)
#object[clojure.lang.MultiFn 0x787144b0 "
clojure.lang.MultiFn@787144b0"
]
(defmethod g/freeze [::complex] [c]
(let [re (real c)
im (imaginary c)]
(if (g/zero? im)
re
(list 'complex re im))))
#object[clojure.lang.MultiFn 0x7a0db79a "
clojure.lang.MultiFn@7a0db79a"
]
(defmethod g/exact? [::complex] [c]
(and (g/exact? (real c)) (g/exact? (imaginary c))))
#object[clojure.lang.MultiFn 0x1864e04e "
clojure.lang.MultiFn@1864e04e"
]
(defmethod g/gcd [::complex ::complex] [a b] (gcd a b))
#object[clojure.lang.MultiFn 0x724dd6e2 "
clojure.lang.MultiFn@724dd6e2"
]
(defmethod g/gcd [::complex ::v/real] [a b] (gcd a b))
#object[clojure.lang.MultiFn 0x724dd6e2 "
clojure.lang.MultiFn@724dd6e2"
]
(defmethod g/gcd [::v/real ::complex] [a b] (gcd a b))
#object[clojure.lang.MultiFn 0x724dd6e2 "
clojure.lang.MultiFn@724dd6e2"
]
(defmethod g/make-rectangular [::v/real ::v/real] [re im]
(if (g/zero? im)
re
(complex re im)))
#object[clojure.lang.MultiFn 0x41c0c051 "
clojure.lang.MultiFn@41c0c051"
]
(defmethod g/make-polar [::v/real ::v/real] [radius angle]
(cond (g/zero? radius) radius
(g/zero? angle) radius
:else
#?(:cljs (Complex. #js {:abs (js/Number radius)
:arg (js/Number angle)})
:clj (let [angle (u/double angle)]
(Complex. (* radius (Math/cos angle))
(* radius (Math/sin angle)))))))
#object[clojure.lang.MultiFn 0x73349281 "
clojure.lang.MultiFn@73349281"
]
(defmethod g/real-part [::complex] [a] (real a))
#object[clojure.lang.MultiFn 0x4ed57a82 "
clojure.lang.MultiFn@4ed57a82"
]
(defmethod g/imag-part [::complex] [a] (imaginary a))
#object[clojure.lang.MultiFn 0x41ca178e "
clojure.lang.MultiFn@41ca178e"
]
(defmethod g/magnitude [::complex] [a]
#?(:clj (.abs ^Complex a)
:cljs (.abs ^js a)))
#object[clojure.lang.MultiFn 0x2df77656 "
clojure.lang.MultiFn@2df77656"
]
(defmethod g/angle [::complex] [a]
#?(:clj (.getArgument ^Complex a)
:cljs (.arg ^js a)))
#object[clojure.lang.MultiFn 0x1ab27ac4 "
clojure.lang.MultiFn@1ab27ac4"
]
(defmethod g/conjugate [::complex] [a]
#?(:clj (.conjugate ^Complex a)
:cljs (.conjugate ^js a)))
#object[clojure.lang.MultiFn 0x7fd6127a "
clojure.lang.MultiFn@7fd6127a"
]
(defmethod g/dot-product [::complex ::complex] [a b]
(+ (* (real a) (real b))
(* (imaginary a) (imaginary b))))
#object[clojure.lang.MultiFn 0x4f7d2d76 "
clojure.lang.MultiFn@4f7d2d76"
]
(defmethod g/dot-product [::complex ::v/real] [a b] (* (real a) b))
#object[clojure.lang.MultiFn 0x4f7d2d76 "
clojure.lang.MultiFn@4f7d2d76"
]
(defmethod g/dot-product [::v/real ::complex] [a b] (* a (real b)))
#object[clojure.lang.MultiFn 0x4f7d2d76 "
clojure.lang.MultiFn@4f7d2d76"
]
(defmethod v/= [::complex ::complex] [a b]
#?(:clj (.equals ^Complex a ^Complex b)
:cljs (.equals ^js a b)))
#object[clojure.lang.MultiFn 0x744f207b "
clojure.lang.MultiFn@744f207b"
]
(defmethod v/= [::complex ::v/real] [^Complex a n]
(and (zero? (imaginary a))
(v/= (real a) n)))
#object[clojure.lang.MultiFn 0x744f207b "
clojure.lang.MultiFn@744f207b"
]
(defmethod v/= [::v/real ::complex] [n ^Complex a]
(and (zero? (imaginary a))
(v/= n (real a))))
#object[clojure.lang.MultiFn 0x744f207b "
clojure.lang.MultiFn@744f207b"
]
(defmethod g/add [::complex ::complex] [a b]
#?(:clj (.add ^Complex a ^Complex b)
:cljs (.add ^js a b)))
#object[clojure.lang.MultiFn 0x7e1fb1be "
clojure.lang.MultiFn@7e1fb1be"
]
(defmethod g/add [::complex ::v/real] [a n]
#?(:clj (.add ^Complex a ^double (u/double n))
:cljs (.add ^js a (u/double n))))
#object[clojure.lang.MultiFn 0x7e1fb1be "
clojure.lang.MultiFn@7e1fb1be"
]
(defmethod g/add [::v/real ::complex] [n a]
#?(:clj (.add ^Complex a ^double (u/double n))
:cljs (.add ^js a (u/double n))))
#object[clojure.lang.MultiFn 0x7e1fb1be "
clojure.lang.MultiFn@7e1fb1be"
]
(defmethod g/expt [::complex ::complex] [a b]
#?(:clj (.pow ^Complex a ^Complex b)
:cljs (.pow ^js a b)))
#object[clojure.lang.MultiFn 0x310104f9 "
clojure.lang.MultiFn@310104f9"
]
(let [choices [1 I -1 -I]]
(defmethod g/expt [::complex ::v/real] [a n]
(if (= a I)
(choices (mod n 4))
#?(:clj (.pow ^Complex a ^double (u/double n))
:cljs (.pow ^js a ^double (u/double n))))))
#object[clojure.lang.MultiFn 0x310104f9 "
clojure.lang.MultiFn@310104f9"
]
(defmethod g/expt [::v/real ::complex] [n a]
#?(:clj (.pow ^Complex (complex n) ^Complex a)
:cljs (.pow ^js (complex n) a)))
#object[clojure.lang.MultiFn 0x310104f9 "
clojure.lang.MultiFn@310104f9"
]

Take advantage of the expt optimizations above for I.

(defmethod g/square [::complex] [z] (g/expt z 2))
#object[clojure.lang.MultiFn 0x147eaac5 "
clojure.lang.MultiFn@147eaac5"
]
(defmethod g/cube [::complex] [z] (g/expt z 3))
#object[clojure.lang.MultiFn 0x7e61e389 "
clojure.lang.MultiFn@7e61e389"
]
(defmethod g/abs [::complex] [a]
#?(:clj (.abs ^Complex a)
:cljs (.abs ^js a)))
#object[clojure.lang.MultiFn 0x721e14f0 "
clojure.lang.MultiFn@721e14f0"
]
(defmethod g/exp [::complex] [a]
#?(:clj (.exp ^Complex a)
:cljs (.exp ^js a)))
#object[clojure.lang.MultiFn 0x7168c357 "
clojure.lang.MultiFn@7168c357"
]
(defmethod g/log [::complex] [a]
#?(:clj (.log ^Complex a)
:cljs (.log ^js a)))
#object[clojure.lang.MultiFn 0x20b5f4b2 "
clojure.lang.MultiFn@20b5f4b2"
]
(defmethod g/sqrt [::complex] [a]
#?(:clj (.sqrt ^Complex a)
:cljs (.sqrt ^js a)))
#object[clojure.lang.MultiFn 0x79e8dd4d "
clojure.lang.MultiFn@79e8dd4d"
]
(defmethod g/sin [::complex] [a]
#?(:clj (.sin ^Complex a)
:cljs (.sin ^js a)))
#object[clojure.lang.MultiFn 0x163caf8e "
clojure.lang.MultiFn@163caf8e"
]
(defmethod g/cos [::complex] [a]
#?(:clj (.cos ^Complex a)
:cljs (.cos ^js a)))
#object[clojure.lang.MultiFn 0x3be6105f "
clojure.lang.MultiFn@3be6105f"
]
(defmethod g/tan [::complex] [a]
#?(:clj (.tan ^Complex a)
:cljs (.tan ^js a)))
#object[clojure.lang.MultiFn 0x428252da "
clojure.lang.MultiFn@428252da"
]
(defmethod g/asin [::complex] [a]
#?(:clj (.asin ^Complex a)
:cljs (.asin ^js a)))
#object[clojure.lang.MultiFn 0x43c45b34 "
clojure.lang.MultiFn@43c45b34"
]
(defmethod g/acos [::complex] [a]
#?(:clj (.acos ^Complex a)
:cljs (.acos ^js a)))
#object[clojure.lang.MultiFn 0x1380d514 "
clojure.lang.MultiFn@1380d514"
]
(defmethod g/atan [::complex] [a]
#?(:clj (.atan ^Complex a)
:cljs (.atan ^js a)))
#object[clojure.lang.MultiFn 0x6a540c36 "
clojure.lang.MultiFn@6a540c36"
]
(defmethod g/cosh [::complex] [a]
#?(:clj (.cosh ^Complex a)
:cljs (.cosh ^js a)))
#object[clojure.lang.MultiFn 0x8dc9785 "
clojure.lang.MultiFn@8dc9785"
]
(defmethod g/sinh [::complex] [a]
#?(:clj (.sinh ^Complex a)
:cljs (.sinh ^js a)))
#object[clojure.lang.MultiFn 0x1d61d948 "
clojure.lang.MultiFn@1d61d948"
]
(defmethod g/tanh [::complex] [a]
#?(:clj (.tanh ^Complex a)
:cljs (.tanh ^js a)))
#object[clojure.lang.MultiFn 0x42231f14 "
clojure.lang.MultiFn@42231f14"
]
(defmethod g/integer-part [::complex] [a]
(let [re (g/integer-part (real a))
im (g/integer-part (imaginary a))]
(if (g/zero? im)
re
(complex re im))))
#object[clojure.lang.MultiFn 0x12b60d1 "
clojure.lang.MultiFn@12b60d1"
]
(defmethod g/fractional-part [::complex] [a]
(let [re (g/fractional-part (real a))
im (g/fractional-part (imaginary a))]
(if (g/zero? im)
re
(complex re im))))
#object[clojure.lang.MultiFn 0x4430be99 "
clojure.lang.MultiFn@4430be99"
]
(defmethod g/negative? [::complex] [a]
(and (g/zero? (imaginary a))
(g/negative? (real a))))
#object[clojure.lang.MultiFn 0x6e310c69 "
clojure.lang.MultiFn@6e310c69"
]
(defmethod g/infinite? [::complex] [a]
(or (g/infinite? (real a))
(g/infinite? (imaginary a))))
#object[clojure.lang.MultiFn 0x585167ae "
clojure.lang.MultiFn@585167ae"
]

The remaining methods have different names in the Clojure vs JS implementations.

#?(:clj
(do
(defmethod g/floor [::complex] [^Complex a]
(let [re (g/floor (.getReal a))
im (g/floor (.getImaginary a))]
(if (g/zero? im)
re
(complex re im))))
(defmethod g/ceiling [::complex] [^Complex a]
(let [re (g/ceiling (.getReal a))
im (g/ceiling (.getImaginary a))]
(if (g/zero? im)
re
(complex re im))))
(defmethod g/sub [::complex ::complex] [^Complex a ^Complex b] (.subtract a b))
(defmethod g/sub [::complex ::v/real] [^Complex a n] (.subtract a (double n)))
(defmethod g/sub [::v/real ::complex] [n ^Complex a] (.add (.negate a) (double n)))
(defmethod g/mul [::complex ::complex] [^Complex a ^Complex b] (.multiply a b))
(defmethod g/mul [::complex ::v/real] [^Complex a n] (.multiply a (double n)))
(defmethod g/mul [::v/real ::complex] [n ^Complex a] (.multiply a (double n)))
(defmethod g/div [::complex ::complex] [^Complex a ^Complex b] (.divide a b))
(defmethod g/div [::complex ::v/real] [^Complex a n] (.divide a (double n)))
(defmethod g/div [::v/real ::complex] [n ^Complex a] (.multiply (.reciprocal a) (double n)))
(defmethod g/negate [::complex] [^Complex a] (.negate a))
(defmethod g/invert [::complex] [^Complex a] (.reciprocal a)))
:cljs
(do
(defmethod g/floor [::complex] [a] (.floor ^js a))
(defmethod g/ceiling [::complex] [a] (.ceil ^js a))
(defmethod g/sub [::complex ::complex] [a b] (.sub ^js a b))
(defmethod g/sub [::complex ::v/real] [a n] (.sub ^js a (u/double n)))
(defmethod g/sub [::v/real ::complex] [n a] (.add ^js (.neg ^js a) (u/double n)))
(defmethod g/mul [::complex ::complex] [a b] (.mul ^js a b))
(defmethod g/mul [::complex ::v/real] [a n] (.mul ^js a (u/double n)))
(defmethod g/mul [::v/real ::complex] [n a] (.mul ^js a (u/double n)))
(defmethod g/div [::complex ::complex] [a b] (.div ^js a b))
(defmethod g/div [::complex ::v/real] [a n] (.div ^js a (u/double n)))
(defmethod g/div [::v/real ::complex] [n a]
(.mul ^js (.inverse ^js a) (u/double n)))
(defmethod g/negate [::complex] [a] (.neg ^js a))
(defmethod g/invert [::complex] [a] (.inverse ^js a))))
#object[clojure.lang.MultiFn 0x165df7c1 "
clojure.lang.MultiFn@165df7c1"
]