The language currently supports casting from a tuple to
a complex, e.g.
var b = (1.0, 2.0):complex;
However the inverse operation is not implemented:
var tup = (1.0+2.0i):(real, real); // doesn't compile today
error: illegal cast from complex(128) to 2*real(64)
This issue requests that this second pattern be supported.
Implementing it should be pretty easy; it amounts to adding one or two _cast overloads in ChapelTuple.
@damianmoz fyi - I created this issue on your behalf.
This sounds good.
How about also a method on complex that does the same? Ex.
var tup = myComplex.toTuple();
I find Vass's proposal a little easier to understand when reading the code. Do we have other examples of casts to tuple?
Diving into my first sentence a little more, I think having the additional set of parenthesis around the right side of the cast kinda clutters the whole thing for me. That said, I don't think I'd feel the same way if we obtained the type through a type call, say : getTupleType(), so I don't think I'm arguing against being able to cast to tuple types? Just that I'd probably prefer to use the .toTuple method myself.
For the original proposal, I'd want to make sure that both : (real, real) and : 2*real work.
To elaborate on my proposal, in turn, it would be natural to complement it with a .toComplex() method that applies to 2-tuples of real.
Just like we have casts going both ways, we would have methods going both ways.
Last but not least, these methods would improve the visibility of this feature to users. Because they would be documented on the tuple page in the spec and so more likely to be encounters by users learning about tuples. //Of course we also could document the cast on that same page.
So summarizing, given
var x : real, y : real, p : real, q : real;
var c : complex;
then the following are possible:
c = (x, y):complex;
c = (x, y).toComplex();
(a, b) = (p + q i):(real, real);
(a, b) = (p + q i).toTuple();
There is a subtle difference in that the methods look to the object to determine the precision whereas in the pure cast case, the precision is mandated.
I think that @lydia-duncan is also saying that
(a, b) = c:2*real
should also be legal.
are possible
Correction: would be possible with the proposal
There are more corrections than just a change conditional tense. But at least I am not replying at 4am this time.
The following would be possible with the proposal:
c = (x, y):complex;
c = (x, y).toComplex;
(a, b) = (p + q i):(real, real);
(a, b) = (p + q i).toTuple;
I have to get out of the C-habit of always adding () to a proc without parameters.
Do we have a precedent where we chose between paren-ful vs. paren-less function in a situation similar to toComplex and toTuple ?
// It is the definition of the function that determines whether it is to be called with or without parens.
How far back do you want to go for precedents? If you go back to Algol60, Pascal, Modula, no parameters means no parentheses. Not sure about ZPL. The long dead CPL language in 1963 demanded parentheses in a function with no parameters but needed no parentheses in a parameterless routine which struck me as strange. I do not know why and the primary author Christopher Strachey is no longer around to ask. Martin Richards retained the empty parameterlist in BCPL but that was because BCPL had no concept of signatures, and then Ken Thompson perpetuated that in C which at the time also had no signatures. I cannot see the point of redundant parentheses in a much higher level language like Chapel where you can rely on the signature to tell you that no parameters are needed. I grew up using BCPL but that is not to say I would use its concepts in a much high level language like Chapel.
In some linear algebra work I am doing, I have an implementation of part of the above proposal and they now have a name toTuple and toComplex. Their definitions have no brackets. I did have different names, tupelize and complexify respectively, but I prefer yours as they are more consistent.
I have been using an IEEE 754 feature module for over a year and any method that needs no parameters has no parentheses.,
If the above toTuple and toComplex allows an optional precision _w_, then we would need parentheses.
Just like we have casts going both ways, we would have methods going both ways.
I'm not sure I'm in favor of having both casts and methods going both ways. (But I might be convinced later). I'd prefer either only having casts or only having methods.
Having methods only would presumably be more clear to readers of the code, but it is a change that could break existing programs.
I'm not sure why, but the cast from complex to tuple isn't all that appealing to me. The tuple-to-complex cast was added as a convenience in the early days of Chapel and isn't something I'm deeply wed to either, though I'm not crazy about breaking existing code by removing it. I'm trying to think why the complex-to-tuple direction gives me pause. Maybe in part because it gets into questions about whether we should also support : (real, imag) (or : (imag, imag), or : (int, int) or ...).
Tagging @npadmana who works pretty heavily with complex numbers to see what his reaction is to this general issue.
I don't have particularly strong feelings either way -- I haven't missed the feature personally, but I also probably might use it if it were there.
A few more thoughts/questions --
proc hypot2(x,y) {
return x*x+y*y;
}
var c = 1+2i;
hypot2((c...));
Of course, I could just as easily overload this - hence, my initial reaction.
var ctup = (c.re, c.im);
var cmplx = ctup(0) + ctup(1) i;
As I write these, I partially answered this -- I think I'd prefer the cast/method form to
var ComplexTuple = (ComplexNumber.re, ComplexNumber.im);
var ComplexTup = ComplexTuple(0) + ComplexTuple(1) i;
I'll also add that typographically, while the 1+2i form works nicely for a literal, it's a little harder to read in code for x+y i, and I do prefer (x,y):complex.
While we're discussing this, it would be useful to consider how these changes might interact with #15620
@npadmana -
Will this automatically cast, or would one need to explicitly cast between the tuple and complex forms?
As far as I know, nobody is proposing automatic conversions between tuples and complexes.
@mppf -- sorry, yes, that was me just thinking aloud. I agree that an automatic conversion, if desirable, should be a separate issue.
In hindsight, which is always 20/20, my original request can be cleanly resolved by defining a method on a complex type which could be named .tuple and be defined as
proc (complex(?w)).tuple return (this.re, this.im);
It could have other names like reim, or pair because a pair is a 2-tuple, just as a triplet, quartet, and quintet are 3-tuples, 4-tuples and 5-tuples. And yes, it needs neither parameters nor parentheses. I am sure if people think that my idea is a worthwhile addition to the language, the name may be an interesting discussion.
With this method, my original request for cast from a complex to a tuple is satisfied. Cleanly. With very little code. Sadly, my brain just needed lots of prompting by others to eventually come up with the simple, clean, answer. I am regularly pleased that useful tweaks to the language are mostly so easy to do in Chapel. It obviously was a very good design in the first place!!!!
Anyway, back to the conversation ...
The cast from a tuple to a complex can be similarly avoided to reduce compiler complexity as shown below with the complexify routine that I provide below. I apologize in advance for the name but I do want a name which is a single English word, even if that word is not correct English. It takes 2 real(?w) arguments and creates a complex(w) number, relieving the programmer of the need to choose the precision as they would be doing when using a cast. However, as noted, removing the standard cast-to-complex would break lots of code, including my own. Maybe the cast could be semi-deprecated, i.e. off by default but able to be re-enabled with a compiler switch.
Anyway, as a discussion topic if indeed it is needed, I show the following generically typed code which highlights the various approaches. My apologies if this has not been suitably enough distilled by my old brain but other things are occupying it at the moment. My comments are embedded. Your comments are welcome. My response time might be slow this week - sorry. Apologies if this file should have been attached
// pre-amble A : apply a Givens rotation defined by (c, s) stored as
// the complex number c + s * I (in C-speak) on the vectors u * v
// this is a translation of the BLAS-1 code with the variation that
// the Givens matrix is stored compactly within a complex number.
// this is a generic routine with C a complex(w) and R a real(w).
inline proc rot(cs : ?C, ref u : [?uD] ?R , ref v : [?vD] R)
{
// the tasking overhead of a 'forall' is probably excessive
// this routine could be tiled if parallelism is desired(?)
// with each tile being vectorized for further performance.
for (vi, ui) in zip(v, u) do
{
// pray that Chapel optimizes this multiplication
const t = (vi, ui):C * cs;
(vi, ui) = (t.re, t.im);
}
}
// pre-amble B : create a complex(w) number from two real(w) numbers
// this code avoid casts altogether - what is performance like VS casting?
// the nomenclature used for this routine might leave a lot to be desired
// ** this uses the concept of an implied return type
inline proc complexify(p : real(?w), q : real(w))
{
var c : complex(w << 1);
(c.re, c.im) = (p, q);
return c;
}
// this really should be implement as the method '.tuple' on the
// actual record which Chapel uses to store a complex(w) number!!
// see the exmple below 'rotWithCasts' for an example of its use.
// the prior comment on nomenclature probably is still relevant
// ** again using the concept of an implied return type
inline proc tupelize(c : complex(?w))
{
return (c.re, c.im);
}
proc (complex(?w)).tuple return (this.re, this.im);
// end preamble
// the next 3 routines show Chapel's expressiveness but .. BUT ..
// they following suck in performance because they parallelize so
// they probably should be implemented as just simple 'for' loops
// they are meant as alternative approaches to the same problem
inline proc rotWithCasts(cs : ?C, ref u : [?uD] ?R , ref v : [?vD] R)
{
[i in uD] (v[i], u[i]) = (cs * (v[i], u[i]):C):(R, R);
}
inline proc rotNoCasts(cs : ?C, ref u : [?uD] ?R , ref v : [?vD] R)
{
[i in uD] (v[i], u[i]) = tupelize(cs * complexify(v[i], u[i]));
}
inline proc rotMethod(cs : ?C, ref u : [?uD] ?R , ref v : [?vD] R)
{
[i in uD] (v[i], u[i]) = (cs * complexify(v[i], u[i])).tuple;
}
// ... for completeness (but adding nothing to the basic discussion):
//
// the following vectorizes well but does Chapel vectorize well? but
// it has a massive performance hit because it has to allocate two 1D
// arrays at the start to act as temporaries to allow vectorization
inline proc rotV(cs : ?C, ref u : [?uD] ?R , ref v : [?vD] R)
{
const p = u, q = v;
const (c, s) = tupelize(cs);
u = c * p + s * q;
v = c * q - s * p;
}
proc main
{
type Real = real(32);
var uu = [ 1:Real, 2:Real, 3:Real, 4:Real, 5:Real ];
var vv = [ 17:Real, 23:Real, 29:Real, 31:Real, 37:Real ];
var c = 0.6:Real;
var s = 0.8:Real;
var cs = complexify(c, s);
writeln(cs.tuple);
// basic case
{
var u = uu, v = vv;
writeln("\n... rot");
writeln(u);
writeln(v);
rot(cs, u, v);
writeln(u);
writeln(v);
}
// the vectorizable case
{
var u = uu, v = vv;
writeln("\n... rotV[ectorizable]");
writeln(u);
writeln(v);
rotV(cs, u, v);
writeln(u);
}
if 0 != 0 then // removed at compile time - smart little compiler
{
var u = uu, v = vv;
writeln("... rot With Casts"); // a cast to a tuple are NOT in the language
writeln(u);
writeln(v);
rotWithCasts(cs, u, v);
writeln(u);
writeln(v);
}
// avoid casts - use complexify
{
var u = uu, v = vv;
writeln("\n... No Casts");
writeln(u);
writeln(v);
rotNoCasts(cs, u, v);
writeln(u);
writeln(v);
}
// use the method on the complex number
{
var u = uu, v = vv;
writeln("\n... Method");
writeln(u);
writeln(v);
rotMethod(cs, u, v);
writeln(u);
writeln(v);
}
}
If people want to know where complexify comes from, the word reify is a genuine English word and means to _make real_. I originally thought of using coify but that did not seem clear enough. Anyway, the name is not my decision in the end. English etymology is fascinating but probably not what Chapel programmers need to worry much about.
I got told in no uncertain terms in a review meeting that complexify is for the birds and why I did not chase scoring a point with the Fortran users and just use cmplx. Even C++ is using it now. At least there are multiple precedents. I cannot argue with that, Comments.
Most helpful comment
So summarizing, given
then the following are possible:
There is a subtle difference in that the methods look to the object to determine the precision whereas in the pure cast case, the precision is mandated.
I think that @lydia-duncan is also saying that
should also be legal.