Expand the rank of a domain

While slicing one dimension effectively reduces the rank of a domain by one, what is the opposite, growing a domain by one. I looked up the documentation but I maybe I am not looking in the right place.

If I have an n-dimensional domain, say D below, how do I create an (n+1)-dimensional domain, say E, below. The example below is done in a manual fashion:

const n = 2;
const m = 10;
const D : domain(2) = {1..n, 1..n};
const E : domain(3) = {1..m, 1..n, 1..n};
var p : [1..10][D] real(64); // this data structure is a fall back
var q : [E] real(64); // this data structure is what I really want
var z = 1.0;

for k in 1..m do
{
    for r in 1..n do
    {
        for c in 1..2 do
        {
            p[k][r, c] = z;
            q[k, r, c] = z;
            z += 1.0;
        }
    }
    writeln(p[k]);
    writeln("..");
}
writeln(D);
writeln(p); // this has a very weird format (but not one I am worried about).
writeln(q);

What I really want is something like

E = D.grow({1..n})

While I can only think of examples right now where I would want to expand left-wise by a single axis, that might need to grow by two axes. For example,

const P = domain(2) = {1..n, 1..n};
const Q = domain(2) = {1..m, 1..m};
const R = domain(4) = {1..m, 1..m, 1..n, 1..n};
const S = domain(4) = {1..n, 1..n, 1..m, 1..m};

assert(R == P.grow(Q));
assert(S == Q.grow(P));

Or is is more like some sort of multiplication operator on a domain:

R = Q * P
S = P * Q;

One can handle growing left or right by just amending the order one does things. I think.

I apologise if the above is not clear.

A solution to a greatly simplified problem is

const p = [ 1, 2, 3 ];

proc newaxis(X : [?D] ?T, R : range = 1..1)
{
    var A : [R, D.dim(0)] T;

    for i in R do
    {
        A[i, ..] = X;
    }
    return A;
}

writeln(newaxis(p));

i.e. expand a 1D vector into a 2D matrix. I want something more general.

I think what I am asking is impossible because the rank is a param. Oops.

Hi Damian,

I think you're correct that it's not currently supported today. However,
there is one domain method that I think could be adjusted to support
this functionality in the future, and I think it would be reasonable to
request that it does:

The expand method
(Domains — Chapel Documentation 2.3)
is defined with two overloads and returns a new domain. I think it
would be perfectly reasonable to add an overload to this method that
allows creating a new domain with a different rank than the currently
existing rank. There's probably some more details to consider before
finalizing that addition but I think that would be the most natural
addition.

Thanks,
Lydia

Hi Damian,

Brad pointed out that a very similar question came up on stack overflow
recently and he had a way to solve the issue there:

So hopefully that will allow you to accomplish what you're trying to
do! I do think it would still be reasonable to request that expand
get an overload that can do this, since there are cases where it may be
nicer to just provide information about the transformation you want to
make rather than having to do it yourself. But there would still be
interface questions about how to accomplish that in a way that is most
useful and this solution avoids those.

Thanks,
Lydia

Thanks Brad. Thanks Lydia. That solves it. A bit like a Cartesian product or outer product. The famous ... expansion of a tuple. Fascinating.

Given that the the concept/operation is so different to expanding the range of an existing dimension, to try and overload the name extract is probably unwise. I wonder if I can define an operator * on two domains to do the job for my esoteric application. I had a clunky way to do it previously which was a bit like Brad's a priori solution but with a select statement. My explanation of that within some documentation I was writing for somebody else was just awful so I knew it had to be a bad idea. Brad's clean solution is something that I can explain clearly.

Brad's neat idea needs to go into the spec:

[Domain Specs](https://chapel-lang.org/docs/language/spec/domains.html)

Thanks again.

1 Like

Hi Damian —

I wonder if I can define an operator * on two domains to do the job for my esoteric application.

Interesting point. At times, we've definitely considered treating * or ** as a way to turn ranges into domains, e.g.:

(1..m) * (1..n) => {1..m, 1..n}
(1..n)**3 => {1..n, 1..n, 1..n}

The latter, in particular, can be nice for doing rank-independent programming (e.g., imagine making '3' into a config param, debugging in 1D, and then running your "production" code in 2D, 3D, or higher without code changes).

I still feel warmly about this idea, but we'd need to introduce it gingerly as a language feature in a post-2.0 world because (1..n) * (1..n) today results in the sequence of squares '1, 4, 9, 16, ...', as a promotion of the scalar * operator over two ranges. Similarly (1..n)**3 results in the sequence of cubes, so code that relied on either of these idioms would break.

I can see a world in which one might imagine treating * between domains similarly as a composition of the domains, and if you're able to write it, could imagine a library module providing such an operator since it would be reasonable for a library to provide custom operator definitions.

An obvious challenge to supporting this operator in the language is that it's not clear how the 4D result of applying * to two 2D block-distributed domains would be distributed across locales. But again, in a module context, the operator could limit itself to default-distributed domains, which don't present such challenges.

-Brad

I was thinking more about an operation on domains (rather than ranges) like

{ 1..np } * { 1..m, 1..n } -> { 1..np, 1..m, 1..n }

and not so much as becoming a language feature but being a way of me/us getting experience with messing with operators. Just a idea thrown into the air at this stage. Needs more thought.

Also, a lot of what might be good for the language would use 0-based indexing whereas all our work uses 1-based indexing. Early days.

For now, if what I do makes it easier for me to craft words to explain code, I will be happy.

Fascinating idea, those triple dots ... prefixing an object.

Thanks again.

1 Like