/[escript]/branches/arrayview_from_1695_trunk/escript/src/JoelMods.cpp_
ViewVC logotype

Contents of /branches/arrayview_from_1695_trunk/escript/src/JoelMods.cpp_

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1710 - (show annotations)
Fri Aug 15 06:24:20 2008 UTC (12 years, 2 months ago) by jfenwick
File size: 19077 byte(s)
Branch commit.

Bug fix


1 // Joel's test version of TensorProduct
2
3
4
5
6 DataTypes::ValueType::const_reference
7 Data::getDataAtOffset(DataTypes::ValueType::size_type i) const
8 {
9 return getPointDataView().getData()[i];
10 }
11
12
13 DataTypes::ValueType::reference
14 Data::getDataAtOffset(DataTypes::ValueType::size_type i)
15 {
16 return getPointDataView().getData()[i];
17 }
18
19
20
21 Data
22 escript::C_GeneralTensorProduct_J(Data& arg_0,
23 Data& arg_1,
24 int axis_offset,
25 int transpose)
26 {
27 // General tensor product: res(SL x SR) = arg_0(SL x SM) * arg_1(SM x SR)
28 // SM is the product of the last axis_offset entries in arg_0.getShape().
29
30 // Interpolate if necessary and find an appropriate function space
31 Data arg_0_Z, arg_1_Z;
32 if (arg_0.getFunctionSpace()!=arg_1.getFunctionSpace()) {
33 if (arg_0.probeInterpolation(arg_1.getFunctionSpace())) {
34 arg_0_Z = arg_0.interpolate(arg_1.getFunctionSpace());
35 arg_1_Z = Data(arg_1);
36 }
37 else if (arg_1.probeInterpolation(arg_0.getFunctionSpace())) {
38 arg_1_Z=arg_1.interpolate(arg_0.getFunctionSpace());
39 arg_0_Z =Data(arg_0);
40 }
41 else {
42 throw DataException("Error - C_GeneralTensorProduct: arguments have incompatible function spaces.");
43 }
44 } else {
45 arg_0_Z = Data(arg_0);
46 arg_1_Z = Data(arg_1);
47 }
48 // Get rank and shape of inputs
49 int rank0 = arg_0_Z.getDataPointRank();
50 int rank1 = arg_1_Z.getDataPointRank();
51 const DataTypes::ShapeType& shape0 = arg_0_Z.getDataPointShape();
52 const DataTypes::ShapeType& shape1 = arg_1_Z.getDataPointShape();
53
54 // Prepare for the loops of the product and verify compatibility of shapes
55 int start0=0, start1=0;
56 if (transpose == 0) {}
57 else if (transpose == 1) { start0 = axis_offset; }
58 else if (transpose == 2) { start1 = rank1-axis_offset; }
59 else { throw DataException("C_GeneralTensorProduct: Error - transpose should be 0, 1 or 2"); }
60
61
62 // Adjust the shapes for transpose
63 DataTypes::ShapeType tmpShape0(rank0); // pre-sizing the vectors rather
64 DataTypes::ShapeType tmpShape1(rank1); // than using push_back
65 for (int i=0; i<rank0; i++) { tmpShape0[i]=shape0[(i+start0)%rank0]; }
66 for (int i=0; i<rank1; i++) { tmpShape1[i]=shape1[(i+start1)%rank1]; }
67
68 #if 0
69 // For debugging: show shape after transpose
70 char tmp[100];
71 std::string shapeStr;
72 shapeStr = "(";
73 for (int i=0; i<rank0; i++) { sprintf(tmp, "%d,", tmpShape0[i]); shapeStr += tmp; }
74 shapeStr += ")";
75 cout << "C_GeneralTensorProduct: Shape of arg0 is " << shapeStr << endl;
76 shapeStr = "(";
77 for (int i=0; i<rank1; i++) { sprintf(tmp, "%d,", tmpShape1[i]); shapeStr += tmp; }
78 shapeStr += ")";
79 cout << "C_GeneralTensorProduct: Shape of arg1 is " << shapeStr << endl;
80 #endif
81
82 // Prepare for the loops of the product
83 int SL=1, SM=1, SR=1;
84 for (int i=0; i<rank0-axis_offset; i++) {
85 SL *= tmpShape0[i];
86 }
87 for (int i=rank0-axis_offset; i<rank0; i++) {
88 if (tmpShape0[i] != tmpShape1[i-(rank0-axis_offset)]) {
89 throw DataException("C_GeneralTensorProduct: Error - incompatible shapes");
90 }
91 SM *= tmpShape0[i];
92 }
93 for (int i=axis_offset; i<rank1; i++) {
94 SR *= tmpShape1[i];
95 }
96
97 // Define the shape of the output (rank of shape is the sum of the loop ranges below)
98 DataTypes::ShapeType shape2(rank0+rank1-2*axis_offset);
99 { // block to limit the scope of out_index
100 int out_index=0;
101 for (int i=0; i<rank0-axis_offset; i++, ++out_index) { shape2[out_index]=tmpShape0[i]; } // First part of arg_0_Z
102 for (int i=axis_offset; i<rank1; i++, ++out_index) { shape2[out_index]=tmpShape1[i]; } // Last part of arg_1_Z
103 }
104
105 // Declare output Data object
106 Data res;
107
108 if (arg_0_Z.isConstant() && arg_1_Z.isConstant()) {
109 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace()); // DataConstant output
110 double *ptr_0 = &(arg_0_Z.getDataAtOffset(0));
111 double *ptr_1 = &(arg_1_Z.getDataAtOffset(0));
112 double *ptr_2 = &(res.getDataAtOffset(0));
113 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
114 }
115 else if (arg_0_Z.isConstant() && arg_1_Z.isTagged()) {
116
117 // Prepare the DataConstant input
118 DataConstant* tmp_0=dynamic_cast<DataConstant*>(arg_0_Z.borrowData());
119 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataConstant."); }
120
121 // Borrow DataTagged input from Data object
122 DataTagged* tmp_1=dynamic_cast<DataTagged*>(arg_1_Z.borrowData());
123 if (tmp_1==0) { throw DataException("GTP_1 Programming error - casting to DataTagged."); }
124
125 // Prepare a DataTagged output 2
126 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace()); // DataTagged output
127 res.tag();
128 DataTagged* tmp_2=dynamic_cast<DataTagged*>(res.borrowData());
129 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
130
131 // Prepare offset into DataConstant
132 int offset_0 = tmp_0->getPointOffset(0,0);
133 double *ptr_0 = &(arg_0_Z.getDataAtOffset(offset_0));
134 // Get the views
135 // DataArrayView view_1 = tmp_1->getDefaultValue();
136 // DataArrayView view_2 = tmp_2->getDefaultValue();
137 // // Get the pointers to the actual data
138 // double *ptr_1 = &((view_1.getData())[0]);
139 // double *ptr_2 = &((view_2.getData())[0]);
140
141 double *ptr_1 = &(tmp_1->getDefaultValue(0));
142 double *ptr_2 = &(tmp_2->getDefaultValue(0));
143
144
145 // Compute an MVP for the default
146 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
147 // Compute an MVP for each tag
148 const DataTagged::DataMapType& lookup_1=tmp_1->getTagLookup();
149 DataTagged::DataMapType::const_iterator i; // i->first is a tag, i->second is an offset into memory
150 for (i=lookup_1.begin();i!=lookup_1.end();i++) {
151 tmp_2->addTag(i->first);
152 // DataArrayView view_1 = tmp_1->getDataPointByTag(i->first);
153 // DataArrayView view_2 = tmp_2->getDataPointByTag(i->first);
154 // double *ptr_1 = &view_1.getData(0);
155 // double *ptr_2 = &view_2.getData(0);
156
157 double *ptr_1 = &(tmp_1->getDataByTag(i->first,0));
158 double *ptr_2 = &(tmp_2->getDataByTag(i->first,0));
159
160 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
161 }
162
163 }
164 else if (arg_0_Z.isConstant() && arg_1_Z.isExpanded()) {
165
166 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace(),true); // DataExpanded output
167 DataConstant* tmp_0=dynamic_cast<DataConstant*>(arg_0_Z.borrowData());
168 DataExpanded* tmp_1=dynamic_cast<DataExpanded*>(arg_1_Z.borrowData());
169 DataExpanded* tmp_2=dynamic_cast<DataExpanded*>(res.borrowData());
170 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataConstant."); }
171 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
172 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
173 int sampleNo_1,dataPointNo_1;
174 int numSamples_1 = arg_1_Z.getNumSamples();
175 int numDataPointsPerSample_1 = arg_1_Z.getNumDataPointsPerSample();
176 int offset_0 = tmp_0->getPointOffset(0,0);
177 #pragma omp parallel for private(sampleNo_1,dataPointNo_1) schedule(static)
178 for (sampleNo_1 = 0; sampleNo_1 < numSamples_1; sampleNo_1++) {
179 for (dataPointNo_1 = 0; dataPointNo_1 < numDataPointsPerSample_1; dataPointNo_1++) {
180 int offset_1 = tmp_1->getPointOffset(sampleNo_1,dataPointNo_1);
181 int offset_2 = tmp_2->getPointOffset(sampleNo_1,dataPointNo_1);
182 double *ptr_0 = &(arg_0_Z.getDataAtOffset(offset_0));
183 double *ptr_1 = &(arg_1_Z.getDataAtOffset(offset_1));
184 double *ptr_2 = &(res.getDataAtOffset(offset_2));
185 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
186 }
187 }
188
189 }
190 else if (arg_0_Z.isTagged() && arg_1_Z.isConstant()) {
191
192 // Borrow DataTagged input from Data object
193 DataTagged* tmp_0=dynamic_cast<DataTagged*>(arg_0_Z.borrowData());
194 if (tmp_0==0) { throw DataException("GTP_0 Programming error - casting to DataTagged."); }
195
196 // Prepare the DataConstant input
197 DataConstant* tmp_1=dynamic_cast<DataConstant*>(arg_1_Z.borrowData());
198 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataConstant."); }
199
200 // Prepare a DataTagged output 2
201 res = Data(0.0, shape2, arg_0_Z.getFunctionSpace()); // DataTagged output
202 res.tag();
203 DataTagged* tmp_2=dynamic_cast<DataTagged*>(res.borrowData());
204 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
205
206 // Prepare offset into DataConstant
207 int offset_1 = tmp_1->getPointOffset(0,0);
208 double *ptr_1 = &(arg_1_Z.getDataAtOffset(offset_1));
209 // Get the views
210 // DataArrayView view_0 = tmp_0->getDefaultValue();
211 // DataArrayView view_2 = tmp_2->getDefaultValue();
212 // // Get the pointers to the actual data
213 // double *ptr_0 = &((view_0.getData())[0]);
214 // double *ptr_2 = &((view_2.getData())[0]);
215
216 double *ptr_0 = &(tmp_0->getDefaultValue(0));
217 double *ptr_2 = &(tmp_2->getDefaultValue(0));
218
219 // Compute an MVP for the default
220 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
221 // Compute an MVP for each tag
222 const DataTagged::DataMapType& lookup_0=tmp_0->getTagLookup();
223 DataTagged::DataMapType::const_iterator i; // i->first is a tag, i->second is an offset into memory
224 for (i=lookup_0.begin();i!=lookup_0.end();i++) {
225 // tmp_2->addTaggedValue(i->first,tmp_2->getDefaultValue());
226 // DataArrayView view_0 = tmp_0->getDataPointByTag(i->first);
227 // DataArrayView view_2 = tmp_2->getDataPointByTag(i->first);
228 // double *ptr_0 = &view_0.getData(0);
229 // double *ptr_2 = &view_2.getData(0);
230
231 tmp_2->addTag(i->first);
232 double *ptr_0 = &(tmp_0->getDataByTag(i->first,0));
233 double *ptr_2 = &(tmp_2->getDataByTag(i->first,0));
234 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
235 }
236
237 }
238 else if (arg_0_Z.isTagged() && arg_1_Z.isTagged()) {
239
240 // Borrow DataTagged input from Data object
241 DataTagged* tmp_0=dynamic_cast<DataTagged*>(arg_0_Z.borrowData());
242 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
243
244 // Borrow DataTagged input from Data object
245 DataTagged* tmp_1=dynamic_cast<DataTagged*>(arg_1_Z.borrowData());
246 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
247
248 // Prepare a DataTagged output 2
249 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace());
250 res.tag(); // DataTagged output
251 DataTagged* tmp_2=dynamic_cast<DataTagged*>(res.borrowData());
252 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
253
254 // // Get the views
255 // DataArrayView view_0 = tmp_0->getDefaultValue();
256 // DataArrayView view_1 = tmp_1->getDefaultValue();
257 // DataArrayView view_2 = tmp_2->getDefaultValue();
258 // // Get the pointers to the actual data
259 // double *ptr_0 = &((view_0.getData())[0]);
260 // double *ptr_1 = &((view_1.getData())[0]);
261 // double *ptr_2 = &((view_2.getData())[0]);
262
263 double *ptr_0 = &(tmp_0->getDefaultValue(0));
264 double *ptr_1 = &(tmp_1->getDefaultValue(0));
265 double *ptr_2 = &(tmp_2->getDefaultValue(0));
266
267
268 // Compute an MVP for the default
269 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
270 // Merge the tags
271 DataTagged::DataMapType::const_iterator i; // i->first is a tag, i->second is an offset into memory
272 const DataTagged::DataMapType& lookup_0=tmp_0->getTagLookup();
273 const DataTagged::DataMapType& lookup_1=tmp_1->getTagLookup();
274 for (i=lookup_0.begin();i!=lookup_0.end();i++) {
275 tmp_2->addTag(i->first); // use tmp_2 to get correct shape
276 }
277 for (i=lookup_1.begin();i!=lookup_1.end();i++) {
278 tmp_2->addTag(i->first);
279 }
280 // Compute an MVP for each tag
281 const DataTagged::DataMapType& lookup_2=tmp_2->getTagLookup();
282 for (i=lookup_2.begin();i!=lookup_2.end();i++) {
283 // DataArrayView view_0 = tmp_0->getDataPointByTag(i->first);
284 // DataArrayView view_1 = tmp_1->getDataPointByTag(i->first);
285 // DataArrayView view_2 = tmp_2->getDataPointByTag(i->first);
286 // double *ptr_0 = &view_0.getData(0);
287 // double *ptr_1 = &view_1.getData(0);
288 // double *ptr_2 = &view_2.getData(0);
289
290 double *ptr_0 = &(tmp_0->getDataByTag(i->first,0));
291 double *ptr_1 = &(tmp_1->getDataByTag(i->first,0));
292 double *ptr_2 = &(tmp_2->getDataByTag(i->first,0));
293
294 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
295 }
296
297 }
298 else if (arg_0_Z.isTagged() && arg_1_Z.isExpanded()) {
299
300 // After finding a common function space above the two inputs have the same numSamples and num DPPS
301 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace(),true); // DataExpanded output
302 DataTagged* tmp_0=dynamic_cast<DataTagged*>(arg_0_Z.borrowData());
303 DataExpanded* tmp_1=dynamic_cast<DataExpanded*>(arg_1_Z.borrowData());
304 DataExpanded* tmp_2=dynamic_cast<DataExpanded*>(res.borrowData());
305 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
306 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
307 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
308 int sampleNo_0,dataPointNo_0;
309 int numSamples_0 = arg_0_Z.getNumSamples();
310 int numDataPointsPerSample_0 = arg_0_Z.getNumDataPointsPerSample();
311 #pragma omp parallel for private(sampleNo_0,dataPointNo_0) schedule(static)
312 for (sampleNo_0 = 0; sampleNo_0 < numSamples_0; sampleNo_0++) {
313 int offset_0 = tmp_0->getPointOffset(sampleNo_0,0); // They're all the same, so just use #0
314 double *ptr_0 = &(arg_0_Z.getDataAtOffset(offset_0));
315 for (dataPointNo_0 = 0; dataPointNo_0 < numDataPointsPerSample_0; dataPointNo_0++) {
316 int offset_1 = tmp_1->getPointOffset(sampleNo_0,dataPointNo_0);
317 int offset_2 = tmp_2->getPointOffset(sampleNo_0,dataPointNo_0);
318 double *ptr_1 = &(arg_1_Z.getDataAtOffset(offset_1));
319 double *ptr_2 = &(res.getDataAtOffset(offset_2));
320 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
321 }
322 }
323
324 }
325 else if (arg_0_Z.isExpanded() && arg_1_Z.isConstant()) {
326
327 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace(),true); // DataExpanded output
328 DataExpanded* tmp_0=dynamic_cast<DataExpanded*>(arg_0_Z.borrowData());
329 DataConstant* tmp_1=dynamic_cast<DataConstant*>(arg_1_Z.borrowData());
330 DataExpanded* tmp_2=dynamic_cast<DataExpanded*>(res.borrowData());
331 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
332 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataConstant."); }
333 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
334 int sampleNo_0,dataPointNo_0;
335 int numSamples_0 = arg_0_Z.getNumSamples();
336 int numDataPointsPerSample_0 = arg_0_Z.getNumDataPointsPerSample();
337 int offset_1 = tmp_1->getPointOffset(0,0);
338 #pragma omp parallel for private(sampleNo_0,dataPointNo_0) schedule(static)
339 for (sampleNo_0 = 0; sampleNo_0 < numSamples_0; sampleNo_0++) {
340 for (dataPointNo_0 = 0; dataPointNo_0 < numDataPointsPerSample_0; dataPointNo_0++) {
341 int offset_0 = tmp_0->getPointOffset(sampleNo_0,dataPointNo_0);
342 int offset_2 = tmp_2->getPointOffset(sampleNo_0,dataPointNo_0);
343 double *ptr_0 = &(arg_0_Z.getDataAtOffset(offset_0));
344 double *ptr_1 = &(arg_1_Z.getDataAtOffset(offset_1));
345 double *ptr_2 = &(res.getDataAtOffset(offset_2));
346 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
347 }
348 }
349
350
351 }
352 else if (arg_0_Z.isExpanded() && arg_1_Z.isTagged()) {
353
354 // After finding a common function space above the two inputs have the same numSamples and num DPPS
355 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace(),true); // DataExpanded output
356 DataExpanded* tmp_0=dynamic_cast<DataExpanded*>(arg_0_Z.borrowData());
357 DataTagged* tmp_1=dynamic_cast<DataTagged*>(arg_1_Z.borrowData());
358 DataExpanded* tmp_2=dynamic_cast<DataExpanded*>(res.borrowData());
359 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
360 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataTagged."); }
361 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
362 int sampleNo_0,dataPointNo_0;
363 int numSamples_0 = arg_0_Z.getNumSamples();
364 int numDataPointsPerSample_0 = arg_0_Z.getNumDataPointsPerSample();
365 #pragma omp parallel for private(sampleNo_0,dataPointNo_0) schedule(static)
366 for (sampleNo_0 = 0; sampleNo_0 < numSamples_0; sampleNo_0++) {
367 int offset_1 = tmp_1->getPointOffset(sampleNo_0,0);
368 double *ptr_1 = &(arg_1_Z.getDataAtOffset(offset_1));
369 for (dataPointNo_0 = 0; dataPointNo_0 < numDataPointsPerSample_0; dataPointNo_0++) {
370 int offset_0 = tmp_0->getPointOffset(sampleNo_0,dataPointNo_0);
371 int offset_2 = tmp_2->getPointOffset(sampleNo_0,dataPointNo_0);
372 double *ptr_0 = &(arg_0_Z.getDataAtOffset(offset_0));
373 double *ptr_2 = &(res.getDataAtOffset(offset_2));
374 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
375 }
376 }
377
378 }
379 else if (arg_0_Z.isExpanded() && arg_1_Z.isExpanded()) {
380
381 // After finding a common function space above the two inputs have the same numSamples and num DPPS
382 res = Data(0.0, shape2, arg_1_Z.getFunctionSpace(),true); // DataExpanded output
383 DataExpanded* tmp_0=dynamic_cast<DataExpanded*>(arg_0_Z.borrowData());
384 DataExpanded* tmp_1=dynamic_cast<DataExpanded*>(arg_1_Z.borrowData());
385 DataExpanded* tmp_2=dynamic_cast<DataExpanded*>(res.borrowData());
386 if (tmp_0==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
387 if (tmp_1==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
388 if (tmp_2==0) { throw DataException("GTP Programming error - casting to DataExpanded."); }
389 int sampleNo_0,dataPointNo_0;
390 int numSamples_0 = arg_0_Z.getNumSamples();
391 int numDataPointsPerSample_0 = arg_0_Z.getNumDataPointsPerSample();
392 #pragma omp parallel for private(sampleNo_0,dataPointNo_0) schedule(static)
393 for (sampleNo_0 = 0; sampleNo_0 < numSamples_0; sampleNo_0++) {
394 for (dataPointNo_0 = 0; dataPointNo_0 < numDataPointsPerSample_0; dataPointNo_0++) {
395 int offset_0 = tmp_0->getPointOffset(sampleNo_0,dataPointNo_0);
396 int offset_1 = tmp_1->getPointOffset(sampleNo_0,dataPointNo_0);
397 int offset_2 = tmp_2->getPointOffset(sampleNo_0,dataPointNo_0);
398 double *ptr_0 = &(arg_0_Z.getDataAtOffset(offset_0));
399 double *ptr_1 = &(arg_1_Z.getDataAtOffset(offset_1));
400 double *ptr_2 = &(res.getDataAtOffset(offset_2));
401 matrix_matrix_product(SL, SM, SR, ptr_0, ptr_1, ptr_2, transpose);
402 }
403 }
404
405 }
406 else {
407 throw DataException("Error - C_GeneralTensorProduct: unknown combination of inputs");
408 }
409
410 return res;
411 }

  ViewVC Help
Powered by ViewVC 1.1.26