Julia: abstract complex?

Created on 13 Sep 2019  ·  18Comments  ·  Source: JuliaLang/julia

I'm wondering if it is good to have AbstractComplex, I think this is pretty useful since there can be different representation format for complex number as well, and they should share some similar interface and be treated both as complex number. e.g

  1. complex number can be represented as radius and angle
  2. or in my case I would like to define a symbolic type for complex number, which will directly inherit most of the complex number interfaces and make a lot function "just work"
  3. in some cases we only use exp(theta*im), it only need one floating point or number.
  4. I believe there can be more...

on the other hand, currently use some traits might help, but this will make complex numbers different from other numbers...

PS. integers have Integer, real numbers have Real, but complex number have none... it is not fair :-)

complex

Most helpful comment

I'm not necessarily opposed to an abstract supertype per se, but polar representations are a poor motivation.

The possibility of internally representing complex numbers in polar or cartesian form is one of those shibboleths that OOP textbooks like to give as examples of abstraction, but which are never used in practice. The basic issue is that practically the only thing you would ever want to do with the polar representation is multiplication/division, but for these restricted operations a Number abstraction is not useful — you can and should just work with (r,φ) directly. You don't need or want an "internal" polar representation that is hidden by abstraction: you have to work with the polar form explicitly to ensure that the only operations you do on this format are multiplication and similar.

or in my case I would like to define a symbolic type for complex number, which will directly inherit most of the complex number interfaces and make a lot function "just work"

If you have a symbolic type for a real number that is a <: Real, then you can use it with the existing Complex type already. Or you can make your SymbolicComplex a subtype of Number (or Any), since virtually nothing will "just work" on a complex type that doesn't share the structure of Complex and doesn't represent a specific numeric value anyway.

I'd like to see a useful example of an alternative AbstractComplex subtype where nontrivial existing Complex code would actually work if it were changed to use the new type…

All 18 comments

Seems good. Would allow e.g. PolarComplex <: AbstractComplex to inherit most generic code.

I'm not necessarily opposed to an abstract supertype per se, but polar representations are a poor motivation.

The possibility of internally representing complex numbers in polar or cartesian form is one of those shibboleths that OOP textbooks like to give as examples of abstraction, but which are never used in practice. The basic issue is that practically the only thing you would ever want to do with the polar representation is multiplication/division, but for these restricted operations a Number abstraction is not useful — you can and should just work with (r,φ) directly. You don't need or want an "internal" polar representation that is hidden by abstraction: you have to work with the polar form explicitly to ensure that the only operations you do on this format are multiplication and similar.

or in my case I would like to define a symbolic type for complex number, which will directly inherit most of the complex number interfaces and make a lot function "just work"

If you have a symbolic type for a real number that is a <: Real, then you can use it with the existing Complex type already. Or you can make your SymbolicComplex a subtype of Number (or Any), since virtually nothing will "just work" on a complex type that doesn't share the structure of Complex and doesn't represent a specific numeric value anyway.

I'd like to see a useful example of an alternative AbstractComplex subtype where nontrivial existing Complex code would actually work if it were changed to use the new type…

Thanks for the comment @stevengj , the main motivation for me is the symbolic calculations. I just think other cases can also benefit from this.

If you have a symbolic type for a real number that is a <: Real, then you can use it with the existing Complex type already.

I actually tried this solution at first, but it seems not working so well, this is because expressions can be complex as well, and it might not be
able to be divided into 2 real symbolic expression easily (and not necessarily), it will be much easier to define the imaginary unit and merge similar terms when possible then tag the expression's domain. Handling the imaginary numbers with correct printing also seems to be painful with this solution.

Or you can make your SymbolicComplex a subtype of Number (or Any), since virtually nothing will "just work" on a complex type that doesn't share the structure of Complex and doesn't represent a specific numeric value anyway.

This is basically what I'm doing now, I made SymComplex and SymReal to be a symbolic tag of expressions, they will tag the expression's domain via type inference. so they will be treated correctly in practice, however since I cannot define SymComplex <: AbstractComplex, this means in practice other packages (like our own) which contains boundary constrains on Complex won't work but it should work in principal, a simplified version:

struct ArrayReg{B, T<:Complex, MT <: AbstractMatrix{T}} <: AbstractRegister{B}
   # blabla
end

(we don't actually do type checks here, but in constructors) and if I feed my symbolic complex number, it will give me warnings saying this is not a complex number currently, which is not very elegant.

or another case here: https://github.com/QuantumBFS/YaoBlocks.jl/blob/cc3344559ae7ac8bde7d293ddadb054ac8a4e0f3/src/primitive/reflect_gate.jl

the point is existing Complex do not actually allow users to define a generic upper bound of the desired complex type if we consider there are things like symbolic complex numbers. It limits the possibility of extending this object.

If a function specifies T <: Complex, and cannot handle T <: Number, then it is hard for me to imagine that any nontrivial code would actually support a totally different symbolic complex-number type. If the code does support your symbolic complex type, then it would probably support Number as well and should be widened accordingly.

e.g. in the quantum code you linked, if it is generic enough to handle your symbolic type, then arguably it is also generic enough to handle e.g. quaternion quantum states and should be widened to Number.

So do you suggest we could use Number as some sort of AbstractComplex here? if

If a function specifies T <: Complex, and cannot handle T <: Number, then it is hard for me to imagine that any nontrivial code would actually support a totally different symbolic complex-number type.

I think we add that <:Complex bound mainly because we wanted a complex domain upper bound just for readability it work for Number indeed.

Triage is ok with this. Adding the type seems harmless enough. Adding a pure imaginary type is one possible application.

The tricky part of this is going through and figuring out which methods in Base could apply to AbstractComplex. It's nontrivial, since presumably every AbstractComplex type can implement reim but that doesn't necessarily mean the method is useful (a loose analogy perhaps is calling dense matrix fallback implementations on sparse arrays).

I don't think Number is a good substitute for AbstractComplex because one can imagine wanting to write a f(::AbstractComplex) that doesn't work on say Quaternions or CliffordNumbers.

This is tentatively approved but there is work to do to move it forward: someone needs to go through the various method definitions for Complex and decide which ones are specific to the concrete Complex type and which ones are applicable to AbstractComplex.

I would like to implement a type ComplexParticles <: AbstractComplex that internally represents the number as a collection of samples from a distribution. This already works very well for real numbers in MonteCarloMeasurements.jl, but all the code generation there assumes that
Particles{T} <: Real and T<: Real such that every function that accepts a particle type also accepts one of its internal samples. If I represent this as Complex{Particles{T<: Real}} I need to reimplement a lot of functions, and the reverse Particles{Complex{T}} does not work as it violates the assumption that Particles and its samples are the "same" type.

That's an interesting use case. The path here is open:

someone needs to go through the various method definitions for Complex and decide which ones are specific to the concrete Complex type and which ones are applicable to AbstractComplex.

To be clear, I'm not aware of anyone who is currently planning on doing this, so if someone wants an AbstractComplex type they should perhaps work on this.

I'm interested in working on this. I'll try to open a PR sometime in the next week.

Why can't the package accept Number?

Why can't the package accept Number?

I don't know about @baggepinnen's usecase, but I'm working with symbolic number-types. In particular, I'm quite interested in non-commutative number systems, so I want to reserve subtypes of Number for types where I can't make any assumptions about commutativity. Complex numbers do commute under multiplication so I don't want something like SymbolicComplex <: Number.

Why can't the package accept Number?

It can, it's just a matter of how to implement the functionality. If I go for Complex{Particles}, which is currently the case, the number dispatches as a Complex and I would have to implement a lot of methods for Complex{<:AbstractParticles} instead of getting that behavior for free by dispatching as AbstractParticles.

However, is type Particles supposed to be Real or I mean mathematically it is a complex number or something else? @baggepinnen I think if mathematically if a type follows the same structure as Complex, that's where AbstractComplex is useful.

Yeah, I opened this issue originally for symbolic systems. It'd be great if @MasonProtter could work on this, or I could work on this in summer when I have more free time. Also let me know if I could help when you open the PR!

I would like to implement a type ComplexParticles <: AbstractComplex

I have Particles <: Real and would like to have the new ComplexParticles <: AbstractComplex

would like to have the new ComplexParticles <: AbstractComplex

Why? What methods do you want to use that are not defined for Number?

Just to add one more motivation for AbstractComplex: In https://github.com/kalmarek/Cyclotomics.jl I deal with (semi-symbolic?) sums of roots of unity, which are definitely complex with non-trivial arithmetic:

julia> E(45)^9 # 45-th root of unity raised to power 9
 ζ₅

julia> E(45)^5 #45-th root of unity raised to power 5
 -ζ₉⁴ -ζ₉⁷ # since vector space of 9-th root of unity does not contain ζ₉

julia> E(45)^5 + E(45)^9
 ζ₄₅² +ζ₄₅⁸ +ζ₄₅¹¹ +ζ₄₅¹⁷ -ζ₄₅²⁴ +ζ₄₅²⁶ +ζ₄₅²⁹ +ζ₄₅³⁸ -ζ₄₅³⁹ +ζ₄₅⁴⁴ # no reduction to smaller cyclotomic field is possible

julia> x = -E(5)^2 - E(5)^3
 -ζ₅² -ζ₅³

julia> isreal(x)
true

julia> float(x) == (1+√5)/2
true

It'd be much easier for me to make it into AbstractComplex

Was this page helpful?
0 / 5 - 0 ratings

Related issues

m-j-w picture m-j-w  ·  3Comments

tkoolen picture tkoolen  ·  3Comments

i-apellaniz picture i-apellaniz  ·  3Comments

omus picture omus  ·  3Comments

wilburtownsend picture wilburtownsend  ·  3Comments