Search code examples
carraysdata-structuresparaview

Split a tridimensionnal array into smaller "cubes"


I'm currently working on this : I generate a Paraview .vtm file that contains several .vtr files. Each .vtr file contains values, and coordinates, like this, assuming I'm working on a dimension of 8 :

<PointData Scalars="U">
  <DataArray type="Float32" Name="U" format="ascii">
    <!-- 8*8*8 values -->
  </DataArray>
</PointData>
<Coordinates>
  <DataArray type="Float32" Name="x" format="ascii">
    <!-- 8 x values -->
  </DataArray>
  <DataArray type="Float32" Name="y" format="ascii">
    <!-- 8 y values -->
  </DataArray>
  <DataArray type="Float32" Name="z" format="ascii">
    <!-- 8 z values -->
  </DataArray>
</Coordinates>

I use a quadridimensionnal array to store my values : float ****tab, with tab[s][x][y][z], where :

  • s is the current split step. It increments everytime I start working on the next .vtr file.
  • x, y, z the values.

Now is what causes me trouble : the coordinates where I have to place these points can be anything. It can be constant (following a step, like 0, 0.1, 0.2, and so on), or not.

I store the coordinates in three arrays : x[], y[], z[]. My goal is to cut the set of values into smaller cubes. Let's assume I split my values into 8 files (2^3 files), I have to retrieve the correct coordinates for 8 small cubes. And I can't find a way to do that.

I'm pretty sure my data structures choice is terrible, could someone give me some help with that ?

EDIT :

Here is the function generating my four-star array :

float**** fill_array_random4d(int split, int size)
{
  float**** ret;
  ret = malloc(sizeof(float***) * split);
  for (int i = 0; i < split; i++)
  {
    ret[i] = malloc(sizeof (float**) * size);
    for (int j = 0; j < size; j++)
    {
      ret[i][j] = malloc(sizeof (float*) * size);
      for (int k = 0; k < size; k++)
      {
        ret[i][j][k] = malloc(sizeof (float) * size);
        for (int l = 0; l < size; l++)
          ret[i][j][k][l] = rand() % 100;
      }
    }
  }

  return ret;
}

It's a pretty basic stuff. Right now I'm using random values. Here is how I create and fill my x, y, z arrays :

float *x, *y, *z;
  x = malloc(sizeof (float) * size);
  y = malloc(sizeof (float) * size);
  z = malloc(sizeof (float) * size);
  for (int i = 0; i < size * split; i++)
    x[i] = step * i;
  for (int i = 0; i < size * split; i++)
    y[i] = step * i;
  for (int i = 0; i < size * split; i++)
    z[i] = step * i;

It's still very basic, and finally here is the function printing the coordinates in the file, following the vtk legacy format :

void print_Coordinates(FILE *file, float *x, float *y, float *z, int size, int split)
{
  fprintf(file, "      <Coordinates>\n");
  for (int i = 0; i < 3; i++)
  {
    const char *text1 = "        <DataArray type=\"Float32\" Name=\"";
    const char *text2 = "\" format=\"ascii\">\n";
    fprintf(file, "%s%c%s", text1, 'x' + i, text2);
    for (int j = 0; j < size; j++)
    {
      if (i == 0)
        fprintf(file, "          %f\n", x[j]);
      else if (i == 1)
        fprintf(file, "          %f\n", y[j]);
      else
        fprintf(file, "          %f\n", z[j]);
    }
    fprintf(file, "        </DataArray>\n");
  }
  fprintf(file, "      </Coordinates>\n");
}

So, yeah, it doesn't do what I want at all. Here is a screenshot of the result :

All the cubes are on top of each other. With the code I was using earlier, I had several cubes (one per file), but they were aligned on a diagonal (which is not good either).


Solution

  • As you have admitted, there are some problems with your data structure:

    • The first dimension s seems incongruent: Should the data structure include the original and the smaller cube? That's not easy to do, because the smaller cubes have other dimensions.
    • You have many separate data: The (random) data, the coordinates and the array dimensions. In order to represent the cube, you need to keep track of all of these. I recommend to create a structure to keep the relevant data together.
    • There isn't anything per se wrong with your approach to represent the three-dimensional array with a triple pointer, but the design leads to many fragmented allocations. A multi-dimensional array with constant dimensions is probably better represented as one "flat" memory block.

    I suggest two structures:

    typedef struct Cube Cube;
    typedef struct Axis Axis;
    
    struct Axis {
        int n;              /* number of values */
        float *data;        /* graduation values */
    };
    
    struct Cube {
        Axis *x, *y, *z;    /* Axes of the cube */
        float *data;        /* x-major data */
    };
    

    An "axis" stores the values along one of the axes. The cube itself doesn't worry about the axis-related code and just delegates it to its three member axes. A "cube" is your data object. (In the implementation below, the data representation is x-major, meaning the x loop is the outermost, the z loop is the innermost. You can chnage that by swapping the loops.)

    If you have a populated cube object, you can the extract sub-cubes by creating a cube of a smaller dimension and copying the relevant data ranges from the axes and from the cube data. If you want to cover the whole cube, you can either extract and write the cubes as you go or store them in an array of cubes, e.g. Cube *small[8] for splitting in half for each direction. (This would be like your original s index, only that each cube may have its own dimension.)

    An implementation of this behaviour with an (addmittedly simple) test main is below:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    typedef struct Cube Cube;
    typedef struct Axis Axis;
    
    struct Axis {
        int n;              /* number of values */
        float *data;        /* graduation values */
    };
    
    struct Cube {
        Axis *x, *y, *z;    /* Axes of the cube */
        float *data;        /* x-major data */
    };
    
    /*
     *      Create a new axis with a constant step.
     */
    Axis *axis_new(int n, float start, float step)
    {
        Axis *axis = malloc(sizeof(*axis));
        float *p;
    
        axis->n = n;
        axis->data = malloc(n * sizeof(*axis->data));
    
        p = axis->data;
        while (n--) {
            *p = start;
            start += step;
            p++;
        }
    
        return axis;
    }
    
    /*
     *      Destroy and clean up axis
     */
    void axis_delete(Axis *axis)
    {
        if (axis) {
            free(axis->data);
            free(axis);
        }
    }
    
    /*
     *      Write axis in XML format to given file
     */
    void axis_write(const Axis *axis, FILE *f, const char *name)
    {
        float *p = axis->data;
        int n = axis->n;
    
        fprintf(f, "  <DataArray type=\"Float32\" "
            "Name=\"%s\" format=\"ascii\">\n", name);
    
        fprintf(f, "    ");
        while (n--) {
            fprintf(f, " %g", *p++);
        }
        fprintf(f, "\n");
        fprintf(f, "  </DataArray>\n");
    }
    
    /*
     *      Create a new axis that is a sub-axis of orig.
     */
    Axis *axis_slice(const Axis *orig, int start, int len)
    {
        Axis *axis = axis_new(len, 0, 0);
        memcpy(axis->data, orig->data + start, len * sizeof(*axis->data));
    
        return axis;
    }
    
    
    
    /*
     *      Create a cube of zero values for the given axes
     */
    Cube *cube_new(Axis *x, Axis *y, Axis *z)
    {
        Cube *cube = malloc(sizeof(*cube));
        int dim = x->n * y->n * z->n;
    
        cube->x = x;
        cube->y = y;
        cube->z = z;
    
        cube->data = malloc(dim * sizeof(*cube->data));
    
        return cube;
    }
    
    /*
     *      Destroy and clean up cube
     */
    void cube_delete(Cube *cube)
    {
        if (cube) {
            axis_delete(cube->x);
            axis_delete(cube->y);
            axis_delete(cube->z);
    
            free(cube->data);
            free(cube);
        }
    }
    
    float *cube_at(const Cube *cube, int x, int y, int z)
    {
        int pos = (x * cube->y->n + y) * cube->z->n + z;
        return cube->data + pos;
    }
    
    /*
     *      Populate all x, y, z values according to the function func.
     */
    void cube_populate(Cube *cube, float (*func)(float x, float y, float z))
    {
        int i, j, k;
        float *p = cube->data;
    
        for (i = 0; i < cube->x->n; i++) {
            float x = cube->x->data[i];
    
            for (j = 0; j < cube->y->n; j++) {
                float y = cube->y->data[j];
    
                for (k = 0; k < cube->z->n; k++) {
                    float z = cube->z->data[k];
    
                    *p++ = func(x, y, z);
                }
            }
        }    
    }
    
    /*
     *      Write cube to given file.
     */
    void cube_write(const Cube *cube, FILE *f)
    {
        float *p = cube->data;
        int n = cube->x->n * cube->y->n * cube->z->n;
    
        fprintf(f, "<PointData Scalars=\"U\">\n");
        fprintf(f, "  <DataArray type=\"Float32\" Name=\"U\" format=\"ascii\">\n");
    
        while (n--) {
            fprintf(f, " %g", *p++);
        }
        fprintf(f, "\n");
    
        fprintf(f, "  </DataArray>\n");
        fprintf(f, "</PointData>\n");
    
        fprintf(f, "<Coordinates>\n");
        axis_write(cube->x, f, "x");
        axis_write(cube->y, f, "y");
        axis_write(cube->z, f, "z");
        fprintf(f, "</Coordinates>\n");
    }
    
    /*
     *      Create a new cube that is a sub-cube of orig.
     */
    Cube *cube_slice(const Cube *orig,
        int x, int dx, int y, int dy, int z, int dz)
    {
        Cube *cube;
        float *p;
        int i, j, k;
    
        if (x + dx > orig->x->n) return NULL;
        if (y + dy > orig->y->n) return NULL;
        if (z + dz > orig->z->n) return NULL;
    
        cube = cube_new(
            axis_slice(orig->x, x, dx),
            axis_slice(orig->y, y, dy),
            axis_slice(orig->z, z, dz));
    
        p = cube->data;
    
        for (i = 0; i < dx; i++) {
            for (j = 0; j < dy; j++) {
                for (k = 0; k < dz; k++) {
                    *p++ = *cube_at(orig, x + i, y + j, z + k);
                }
            }
        }    
    
        return cube;
    }
    
    /*
     *      Example appliaction
     */
    float dist2(float x, float y, float z)
    {
        return x*x + y*y + z*z;
    }
    
    int main()
    {
        Cube *cube = cube_new(
            axis_new(4, 0, 0.1),
            axis_new(4, 0, 0.1),
            axis_new(4, 0, 0.1));
        int i, j, k;
    
        cube_populate(cube, dist2);
    
        for (i = 0; i < 2; i++) {
            for (j = 0; j < 2; j++) {
                for (k = 0; k < 2; k++) {
                    Cube *sub = cube_slice(cube, 2*i, 2, 2*j, 2, 2*k, 2);
    
                    cube_write(sub, stdout);
                    printf("--\n");
                    cube_delete(sub);
                }
            }
        }
    
        cube_delete(cube);
    
        return 0;
    }