Writing a version of AverageCell for a parallel environment introduces a "problem" that does not arise with the Add module: the implementation of parallelism by dividing Fields into spatially disjoint subsets called partitions. Each partition is stored as a Field inside a Group Object. This Group is a special subclass of Group objects called a "Composite Field."
The AverageCell algorithm requires information about the neighbors of each cell. But for cells on a partition boundary, at least some of those neighbors are in another partition. DXGrow deals with this difficulty and obtains the needed information by "growing" the partition by a specified number of cells. In effect it "restores the old neighborhood." The desired operation can then be performed on the "grown" Field. DXShrink restores the partition to its pre-growth state by removing the extra cells and "cleaning up." (See 14.4 , "Growing and Shrinking Partitioned Data".)
To create a version of Data Explorer that includes the AverageCellParallel module, copy the following files to the directory where you want to work:
/usr/local/dx/samples/program_guide/Makefile_workstation /usr/local/dx/samples/program_guide/averagecell_parallel.c /usr/local/dx/samples/program_guide/averagecellpar.mdfNow rename the makefile to Makefile and enter: make avgcell_par.
To run this version (from the directory to which the files were copied), enter:
dx -mdf ./averagecellpar.mdf -exec ./dxexec
You can now run any visual program that uses the AverageCellParallel module. One such program is averagecell_parallel.net in the directory /usr/local/dx/samples/program_guide.
The example AverageCellParallel code follows:
01 #include <dx/dx.h> 02 03 static Error DoAverageCell(Object); 04 05 06 07 Error m_AverageCellParallel(Object *in, Object *out) 08 { 09 Object o=NULL; 10 11 if (!in[0]) { 12 DXSetError(ERROR_BAD_PARAMETER,"missing input"); 13 goto error; 14 } 15 16 o = DXCopy(in[0], COPY_STRUCTURE);
"Grow" the Fields so that averaging can be performed across partition boundaries. Since it is not necessary to grow a Field beyond the original boundaries of the data, and since only the "data" component is affected, grow the partition by one cell. (The original components--"positions," "data," etc.--are copied into components named "original positions," "original data," and so on.)
17 if (!DXGrow(o, 1, GROW_NONE, "data", NULL)) 18 goto error;
Create the task Group.
19 if (!DXCreateTaskGroup()) 20 goto error;
The add tasks will be added in DoAverageCell().
21 if (!DoAverageCell(o)) { 22 DXAbortTaskGroup(); 23 goto error; 24 } 25 26 if (!DXExecuteTaskGroup()) 27 goto error;
Do not call DXShrink to shrink the grown Field until you have recursively removed any "original data" component(s), assuming that you want to save the newly created one(s). Otherwise the new "data" component(s) will be replaced by the (unprocessed) "original data" components(s). Now you can call DXShrink.
28 if (DXExists(o, "original data")) 29 DXRemove(o,"original data"); 30 if (!DXShrink(o)) 31 goto error; 32 33 out[0] = o; 34 return OK; 35 error: 36 DXDelete((Object)o); 37 return ERROR; 38 } 39 40 struct arg { 41 Field field; 42 }; 43 44 static Error AddCellTask(Pointer p) 45 { 46 struct arg *arg = (struct arg *)p; 47 int i, j, numitems, shape, *neighbors_ptr, sum, neighbor; 48 int dim, counts[3]; 49 char *attribute; 50 float *data_ptr, *newdata_ptr, dataaverage; 51 Array connections, data, newdata=NULL, neighbors; 52 Field field; 53 54 field = arg->field; 55
Get the connections component; determine the number of connections and their element type.
56 57 connections = (Array)DXGetComponentValue(field,"connections"); 58 if (!connections) { 59 DXSetError(ERROR_MISSING_DATA,"input has no connections"); 60 goto error; 61 } 62 if (!DXGetArrayInfo(connections, &numitems, NULL, NULL, NULL, NULL)) { 63 goto error; 64 } 65 if (!(attribute= 66 (char *)DXGetString((String)DXGetComponentAttribute(field, 67 "connections", 68 "element type")))) { 69 DXSetError(ERROR_MISSING_DATA, 70 "missing connection element type attribute"); 71 goto error; 72 } 73 74
Get the data component, and get the data dependency attribute.
75 data = (Array)DXGetComponentValue(field,"data"); 76 if (!data) { 77 DXSetError(ERROR_MISSING_DATA,"input has no data"); 78 goto error; 79 } 80 if (!(attribute= 81 (char *)DXGetString((String)DXGetComponentAttribute(field, 82 "data", 83 "dep")))) { 84 DXSetError(ERROR_MISSING_DATA, 85 "missing data dependency attribute"); 86 goto error; 87 } 88
In this example, the data must be dependent on the connections.
89 if (strcmp(attribute,"connections")) { 90 DXSetError(ERROR_INVALID_DATA, 91 "data must be dependent on connections"); 92 goto error; 93 } 94
For this example, the data must be floating-point scalar.
95 if (!DXTypeCheck(data, TYPE_FLOAT, CATEGORY_REAL, 0, NULL)) { 96 DXSetError(ERROR_INVALID_DATA, "data must be floating point scalar"); 97 goto error; 98 }
Get a pointer to the data.
99 data_ptr = (float *)DXGetArrayData(data); 100
Make a new data component, allocate space in it, and get a pointer to it.
101 newdata = DXNewArray(TYPE_FLOAT,CATEGORY_REAL, 0); 102 if (!DXAddArrayData(newdata, 0, numitems, NULL)) 103 goto error; 104 newdata_ptr = (float *)DXGetArrayData(newdata); 105
If the data is ungridded, use the neighbors component. If it is gridded, use a different method.
106 if (!DXQueryGridConnections(connections, &dim, counts)) { 107
Now the program needs the neighbors of the connections. Note that neighbors can be obtained only for ungridded data: for gridded data there are more efficient ways to determine neighbors.
108 neighbors = DXNeighbors(field); 109 if (!neighbors) 110 goto error; 111 neighbors_ptr = (int *)DXGetArrayData(neighbors); 112 if (!DXGetArrayInfo(neighbors, NULL, NULL, NULL, NULL, &shape)) 113 goto error; 114 115 116 for (i=0; i<numitems; i++) { 117 dataaverage = data_ptr[i]; 118 sum = 1;
shape is the number of neighbors of a connection element.
119 for (j=0; j<shape; j++) { 120 neighbor = neighbors_ptr[shape*i + j]; 121 if (neighbor != -1) { 122 dataaverage = dataaverage + data_ptr[neighbor]; 123 sum++; 124 } 125 } 126 dataaverage = dataaverage/sum; 127 newdata_ptr[i] = dataaverage; 128 } 129 } 130 131 else {
The connections are gridded. This example handles only 2-dimensional connections (quads).
132 133 if (dim != 2) { 134 DXSetError(ERROR_INVALID_DATA,"connections must be 2-dimensional"); 135 goto error; 136 } 137 138 for (i=0; i< numitems; i++) { 139 dataaverage = data_ptr[i]; 140 sum = 1;
There are as many as four (4) neighbors for every quad.
141 if ((i % (counts[1]-1)) > 0) { 142 neighbor = i-1; 143 dataaverage = dataaverage + data_ptr[neighbor]; 144 sum++; 145 }
146 if ((i % (counts[1]-1)) < (counts[1] - 2)) { 147 neighbor = i+1; 148 dataaverage = dataaverage + data_ptr[neighbor]; 149 sum++; 150 }
151 neighbor = i-(counts[1]-1); 152 if (neighbor>=0 && neighbor<numitems) { 153 dataaverage = dataaverage + data_ptr[neighbor]; 154 sum++; 155 } 156 neighbor = i+(counts[1]-1); 157 if (neighbor>=0 && neighbor<numitems) { 158 dataaverage = dataaverage + data_ptr[neighbor]; 159 sum++; 160 } 161 dataaverage = dataaverage/sum; 162 newdata_ptr[i] = dataaverage; 163 } 164 }
Place the new data component in the Field.
165 DXSetComponentValue(field, "data", (Object)newdata); 166 newdata=NULL;
The data component has been changed (lines 162 and 165)
167 if (!DXChangedComponentValues(field,"data")) 168 goto error; 169 170 171 return OK; 172 error: 173 DXDelete((Object)newdata); 174 return ERROR; 175 } 176 177 178 static Error DoAverageCell(Object object) 179 { 180 Object subo; 181 struct arg arg; 182 int i; 183 184 switch (DXGetObjectClass(object)) { 185 case (CLASS_FIELD): 186 187 arg.field = (Field)object; 188 if (!DXAddTask(AddCellTask, &arg, sizeof(arg), 0.0)) 189 goto error; 190 break; 191 192 case (CLASS_GROUP):
If object is a Group, recursively call DoAverageCell().
193 for (i=0; subo=DXGetEnumeratedMember((Group)object, i, NULL); i++) { 194 if (!DoAverageCell(subo)) 195 return ERROR; 196 } 197 break; 198 } 199 return OK; 200 error: 201 return ERROR; 202 }