Hydrodynamics Kernel

Jeffrey Oldham oldham at codesourcery.com
Tue Apr 10 22:10:29 UTC 2001


One of the Pooma deliverables is a hydrodynamics kernel to test the
Field class and to use for timings.  Attached is

1) the main hydrodynamics kernel file (also included in the archive)
2) a uuencoded gzipped tar archive with all the source files

To compile the program,
a. uudecode foo.uu		# produces hydrodynamics.tgz
b. tar xzvf hydrodynamics.tgz	# extracts the five files
c. In Makefile, modify the compiler definitions as necessary.
d. make				# produces hydrodynamics

****************

There are several ways the code could be improved:

1) The two sequential portions of the code could be replaced by
   data-parallel code.  Modifying the initialization code should be
   easy, but I do not know how to produce a data-parallel version of
   the "product" operator.

2) Corner fields could be better supported.  See my previously posted
   proposal.

Please send suggestions, comments, and questions.

Thanks,
Jeffrey D. Oldham
oldham at codesourcery.com
-------------- next part --------------
// Oldham, Jeffrey D.
// 2001Mar27
// Pooma

// Hydrodynamics Kernel Written Using POOMA

#include <iostream>
#include <stdlib.h>
#include <cmath>
#include "Pooma/NewFields.h"
#include "ComputeVolumes.h"
#include "Product.h"
#include "ConstantVectorComponentBC.h"

// This hydrodynamics program implements "The Construction of
// Compatible Hydrodynamics Algorithms Utilizing Conservation of Total
// Energy," by E.J. Caramana, D.E. Burton, M.J. Shashkov, and
// P.P. Whalen, \emph{Journal of Computational Physics}, vol. 146
// (1998), pp. 227-262.

// The code uses fields with two different granularities: coarse and
// fine.  Both grid conceptually cover the same domain, but the
// fine-grain one has twice the number of values for each dimension.
// Currently, Pooma does not nicely support operations between fields
// with different granularities.  The code occurring between
// `CORNER_FIELD_TEMPORARY' preprocessor values substitutes for this
// lack of support.  See the separately distributed proposal for
// details.

// Preprocessor symbols:
// CORNER_FIELD_TEMPORARY: Define this symbol.  Include code to deal
// 			   with different granularity fields.
// PSEUDOCODE: Do not define this symbol.  Surrounds desired code to
//	       deal with different granularity fields.
// DEBUG: If defined, print some information about internal program
//	  values.
// DEBUG2: If defined, print much more information about internal
//	   program values.

// Sample compilation command:
// g++ -g -DCORNER_FIELD_TEMPORARY -I/nfs/oz/home/oldham/pooma/r2/src/ -I/nfs/oz/home/oldham/pooma/r2/lib/LINUXgcc/ -L. hydrodynamics.cc -lpooma-gcc -o hydrodynamics

#ifdef CORNER_FIELD_TEMPORARY

/** FORWARD DECLARATIONS **/

template<class InputGeometry, class OutputGeometry, class T, class EngineTag>
inline
void
sumAroundCell (const Field<InputGeometry, T, EngineTag> & input,
	       Field<OutputGeometry, T, EngineTag> & output);

template<class InputGeometry, class OutputGeometry, class T, class EngineTag>
inline
void
sumAroundVertex(const Field<InputGeometry, T, EngineTag> & input,
		Field<OutputGeometry, T, EngineTag> & output);

template<class InputGeometry1,
         class InputT1, class InputT2,
         class InputEngineTag1, class InputEngineTag2,
         class OutputGeometry, class OutputT, class OutputEngineTag>
inline
void
computeCornerMasses(const Field<InputGeometry1, InputT1, InputEngineTag1> & pressureDensity,
		    const Field<OutputGeometry, InputT2, InputEngineTag2> & fineCoordinates,
		    Field<OutputGeometry, OutputT, OutputEngineTag> & output);

template <class InputGeometry1,
          class InputT1, class InputT2,
          class InputEngineTag1, class InputEngineTag2,
          class OutputGeometry, class OutputT, class OutputEngineTag>
inline
void
computeCornerForces(const Field<InputGeometry1, InputT1, InputEngineTag1> & pressure,
		    const Field<OutputGeometry, InputT2, InputEngineTag2> & coordinates,
		    Field<OutputGeometry, OutputT, OutputEngineTag> & output);

template <class InputGeometry1,
          class InputT1,
          class InputEngineTag1,
          class OutputGeometry, class OutputT, class OutputEngineTag>
inline
void
copyVelocities(const Field<InputGeometry1, InputT1, InputEngineTag1> & input1,
	       const Field<InputGeometry1, InputT1, InputEngineTag1> & input2,
	       Field<OutputGeometry, OutputT, OutputEngineTag> & output);
#endif // CORNER_FIELD_TEMPORARY



/** THE HYDRODYNAMICS PROGRAM **/

int main(int argc, char *argv[])
{
  // Set up the Pooma library.
  Pooma::initialize(argc,argv);
#ifdef DEBUG
  std::cout << "Hello, world." << std::endl;
#endif // DEBUG

  /* DECLARATIONS */

  // Values
  const double gamma = 4.0/3;		// gas pressure constant $\gamma$
  const double timeStep = 0.01;		// $\Delta t$
  unsigned nuIterations = 1;		// number of iterations

  // Create a simple layout.
  const unsigned Dim = 2;		// Work in a 2D world.
  const unsigned nHorizVerts = 11;	// number of horizontal vertices
  const unsigned nAngles = 5;		// number of angles
  Interval<Dim> vertexDomain;
  vertexDomain[0] = Interval<1>(nHorizVerts);
  vertexDomain[1] = Interval<1>(nAngles);
  DomainLayout<Dim> layout(vertexDomain, GuardLayers<2>(1));

  // Preparation for Field creation.
  Vector<Dim> origin(0.0);
  Vector<Dim> spacings(1.0,1.0);  // TODO: What does this do?
  typedef UniformRectilinear<Dim, double, Cartesian<Dim> > Geometry_t;
  typedef Field<Geometry_t, double,      Brick> Fields_t;
  typedef Field<Geometry_t, double,      Brick> ConstFields_t; // TODO: Change to ConstField when ConstField is available.
  typedef Field<Geometry_t, Vector<Dim>, Brick> Fieldv_t;
  typedef Field<Geometry_t, Vector<Dim>, Brick> ConstFieldv_t; // TODO: Change to ConstField when ConstField is available.

  // Cell-centered Fields.
  Cell cell;
  Fields_t internalEnergy  (cell, layout, origin, spacings);
  ConstFields_t zoneMass   (cell, layout, origin, spacings);
  Fields_t pressure	   (cell, layout, origin, spacings);
  Fields_t pressureDensity (cell, layout, origin, spacings);
  Fields_t volume	   (cell, layout, origin, spacings);

  // Vertex-centered Fields.
  Vert vert;
  ConstFields_t pointMass  (vert, layout, origin, spacings);
  Fieldv_t coordinates	   (vert, layout, origin, spacings);
  Fieldv_t velocity	   (vert, layout, origin, spacings);
  Fieldv_t velocityChange  (vert, layout, origin, spacings);

  // Corner Fields.
  // TODO: How should I implement corner Fields?
#ifdef CORNER_FIELD_TEMPORARY
  // For now, implement corner Fields using a Field with twice as many
  // entries in each dimension.
  Interval<Dim> cornerVertexDomain;
  for (unsigned d = 0; d < Dim; ++d)
    cornerVertexDomain[d] = Interval<1>(2*vertexDomain[d].min(),2*vertexDomain[d].max());
  DomainLayout<Dim> cornerLayout(cornerVertexDomain, GuardLayers<2>(1));
  Fieldv_t cornerForces	   (cell, cornerLayout, origin, spacings);
  Fields_t cornerMasses    (cell, cornerLayout, origin, spacings);
  Fieldv_t cornerCoordinates(vert, cornerLayout, origin, spacings);
  Fieldv_t cornerVelocities(vert, cornerLayout, origin, spacings);
  Fields_t tmp1		   (cell, cornerLayout, origin, spacings);
  Fields_t tmp2		   (cell, cornerLayout, origin, spacings);
#endif // CORNER_FIELD_TEMPORARY

  /* INITIALIZATION */

  // TODO: Is coordinates initialization necessary?  What is the best way to do this?
  for (unsigned xIndex = 0; xIndex <= vertexDomain[0].last (); ++xIndex)
    for (unsigned yIndex = 0; yIndex <= vertexDomain[1].last (); ++yIndex)
      coordinates(xIndex,yIndex) =
	Vector<2>(static_cast<double>(xIndex)/(nHorizVerts-1) * cos(yIndex*M_PI/(2*(nAngles-1))),
		  static_cast<double>(xIndex)/(nHorizVerts-1) * sin(yIndex*M_PI/(2*(nAngles-1))));
  for (unsigned xIndex = 0; xIndex <= cornerVertexDomain[0].last (); ++xIndex)
    for (unsigned yIndex = 0; yIndex <= cornerVertexDomain[1].last (); ++yIndex)
      cornerCoordinates(xIndex,yIndex) =
	Vector<2>(0.5*xIndex/(nHorizVerts-1) * cos(0.5*yIndex*M_PI/(2*(nAngles-1))),
		  0.5*xIndex/(nHorizVerts-1) * sin(0.5*yIndex*M_PI/(2*(nAngles-1))));
#ifdef DEBUG
  std::cout << "initial coordinates:\n" << coordinates << std::endl;
#endif // DEBUG
  internalEnergy = 1.0;
  pressureDensity = 1.0;
#ifdef PSEUDOCODE
  // This is the desired code:
  cornerMasses = replicate<2>(pressureDensity) *
    computeVolumes(interpolate<2>(coordinates));
  pointMass = total<2>(cornerMasses);
  zoneMass = total<2>(cornerMasses);
  // This is the code that Pooma currently supports:
#endif // PSEUDOCODE
#ifdef CORNER_FIELD_TEMPORARY
  computeCornerMasses(pressureDensity, cornerCoordinates, cornerMasses);
  sumAroundVertex(cornerMasses, pointMass);
  sumAroundCell(cornerMasses, zoneMass);
#endif // CORNER_FIELD_TEMPORARY
  PInsist(min(pointMass) > 0.0, "Zero masses are not supported.");
#ifdef DEBUG2
  std::cout << "pointMass:\n" << pointMass << std::endl;
  std::cout << "zoneMass:\n" << zoneMass << std::endl;
#endif // DEBUG2
  velocity = Vector<Dim>(0.0);
  velocityChange.addUpdaters(AllConstantFaceBC<Vector<Dim> >(Vector<Dim>(0.0), false));
  for (int d = 0; d < Dim; ++d)
    velocityChange.addUpdater(ConstantVectorComponentBC<Vector<Dim>::Element_t>(2*d, d, 0.0, true));
  velocityChange.addUpdater(ConstantVectorComponentBC<Vector<Dim>::Element_t>(3, 0, 0.0, true));

  /* ITERATIONS */

  for (; nuIterations > 0; --nuIterations) {
#ifdef DEBUG
    std::cout << "Begin an iteration." << std::endl;
#endif // DEBUG
    pressure = (gamma - 1.0) * pressureDensity * internalEnergy;
#ifdef DEBUG2
    std::cout << "pressure:\n" << pressure << std::endl;
#endif // DEBUG2
#ifdef PSEUDOCODE
    // This is the desired code.
    forces = replicate<2>(pressure) * addNormals(coordinates);
    velocityChange = (timeStep / pointMass) * total<2>(cornerForces);
    // This is the code that Pooma currently supports:
#endif // PSEUDOCODE
#ifdef CORNER_FIELD_TEMPORARY
    computeCornerForces(pressure, coordinates, cornerForces);
    sumAroundVertex(cornerForces, velocityChange);
    velocityChange *= timeStep / pointMass;
#endif // CORNER_FIELD_TEMPORARY
#ifdef DEBUG2
    std::cout << "corner forces:\n" << cornerForces << std::endl;
    std::cout << "velocity changes:\n" << velocityChange << std::endl;
#endif // DEBUG2
    velocityChange.update();
#ifdef PSEUDOCODE
    // This is the desired code.
    internalEnergy +=
      -(timeStep / pointMass) *
      total<2>(dot(cornerForces, replicate<2>(velocity + 0.5*velocityChange)));
    // This is the code that Pooma currently supports:
#endif // PSEUDOCODE
#ifdef CORNER_FIELD_TEMPORARY
    copyVelocities(velocity, velocityChange, cornerVelocities);
    tmp1 = dot(cornerForces, cornerVelocities);
    sumAroundCell(tmp1, tmp2);
    internalEnergy += -(timeStep / pointMass) * tmp2;
#endif // CORNER_FIELD_TEMPORARY
    coordinates += (velocity + 0.5*velocityChange) * timeStep;
    velocity += velocityChange;
    volume = computeVolumes(coordinates);
    pressureDensity = zoneMass / volume;
#ifdef DEBUG2
    std::cout << "pressure density:\n" << pressureDensity << std::endl;
#endif // DEBUG2
  }

  /* TERMINATION */

  std::cout << "final coordinates:\n" << coordinates << std::endl;
#ifdef DEBUG
  std::cout << "Goodbye, world." << std::endl;
#endif // DEBUG
  Pooma::finalize();
  return EXIT_SUCCESS;
}


/** HELPER FUNCTIONS **/

#ifdef CORNER_FIELD_TEMPORARY
// Following is temporary code to permit compilation using the current
// Pooma code.  Adding new Pooma abstractions may eliminate the need
// for this code.
static Loc<2> offset[] = { Loc<2>(0,0), Loc<2>(0,1),
			     Loc<2>(1,0), Loc<2>(1,1) };

// Given a Field with twice the refinement, form a cell-centered Field
// by summing the 1<<Dim corresponding corners.
template<class InputGeometry, class OutputGeometry, class T, class EngineTag>
inline
void
sumAroundCell(const Field<InputGeometry, T, EngineTag> & input,
	      Field<OutputGeometry, T, EngineTag> & output)
{
  const int dim = Field<OutputGeometry, T, EngineTag>::dimensions;
  Field<OutputGeometry,T,EngineTag>::Domain_t range = output.physicalDomain();
  output(range) = 0;
  for (unsigned counter = 0; counter < (1<<dim); ++counter)
    output(range) += input(2*range+offset[counter]);
  return;
}


// Given a Field with twice the refinement, form a vertex-centered Field
// by summing the 1<<Dim corresponding corners.
template<class InputGeometry, class OutputGeometry, class T, class EngineTag>
inline
void
sumAroundVertex(const Field<InputGeometry, T, EngineTag> & input,
		Field<OutputGeometry, T, EngineTag> & output)
{
  const int dim = Field<OutputGeometry, T, EngineTag>::dimensions;
  Field<OutputGeometry,T,EngineTag>::Domain_t range = output.physicalDomain();
  output(range) = 0;
  for (unsigned counter = 0; counter < (1<<dim); ++counter)
    output(range) += input(2*range-offset[counter]);
  return;
}


// Given a quadrilateral's two diagonals as vectors, return the
// quadrilateral's volume.
template <int Dim, class T, class E>
inline
double
quadrilateralVolume (const Vector<Dim,T,E> &v, const Vector<Dim,T,E> &w)
{
  return 0.5 * std::abs (sum (product (v, w)));
}


// Given a pressure density Field and a fine-mesh coordinate Field, compute
// corner masses, placing in "output".
// TODO: Extend to multiple dimensions.
template<class InputGeometry1,
         class InputT1, class InputT2,
         class InputEngineTag1, class InputEngineTag2,
         class OutputGeometry, class OutputT, class OutputEngineTag>
inline
void
computeCornerMasses(const Field<InputGeometry1, InputT1, InputEngineTag1> & pressureDensity,
		    const Field<OutputGeometry, InputT2, InputEngineTag2> & fineCoordinates,
		    Field<OutputGeometry, OutputT, OutputEngineTag> & output)
{
  // Assume the temporary finer coordinates Field fineCoordinates
  // already has values.

  // Compute the volumes in fineCoordinates.
  output = computeVolumes(fineCoordinates);

  // Multiply each volume by the pressure density.
  Field<InputGeometry1,InputT1,InputEngineTag1>::Domain_t range = pressureDensity.physicalDomain();
  const unsigned corners =
    (1 << (Field<InputGeometry1, InputT1, InputEngineTag1>::dimensions));;
  for (unsigned c = 0; c < corners; ++c)
    output(2*range+offset[c]) *= pressureDensity(range);

  return;
}


// Given a pressure Field and a coordinate Field, compute the corner
// forces in a fine-mesh Field.
template <class InputGeometry1,
          class InputT1, class InputT2,
          class InputEngineTag1, class InputEngineTag2,
          class OutputGeometry, class OutputT, class OutputEngineTag>
inline
void
computeCornerForces(const Field<InputGeometry1, InputT1, InputEngineTag1> & pressure,
		    const Field<OutputGeometry, InputT2, InputEngineTag2> & coordinates,
		    Field<OutputGeometry, OutputT, OutputEngineTag> & output)
{
  Field<InputGeometry1,InputT1,InputEngineTag1>::Domain_t range = pressure.physicalDomain();
  for (unsigned xIndex = 0; xIndex <= range.unwrap()[0].last(); ++xIndex)
    for (unsigned yIndex = 0; yIndex <= range.unwrap()[1].last(); ++yIndex) {
      Loc<2> range (xIndex, yIndex);
      output(2*range + offset[0]) =
	pressure(range) *
	0.5 * product(InputT2(1.0),
		      coordinates(range+offset[2])
		      - coordinates(range+offset[1]));
      output(2*range+offset[1]) =
	pressure(range) *
	0.5 * product(InputT2(1.0),
		      coordinates(range+offset[0])
		      - coordinates(range+offset[3]));
      output(2*range+offset[2]) =
	pressure(range) *
	0.5 * product(InputT2(1.0),
		      coordinates(range+offset[3])
		      - coordinates(range+offset[0]));
      output(2*range+offset[3]) =
	pressure(range) *
	0.5 * product(InputT2(1.0),
		      coordinates(range+offset[1])
		      - coordinates(range+offset[2]));
    }
  return;

#ifdef PSEUDOCODE
  // TODO: I really wanted to use "range" in a data-parallel
  // statement, but I do not know how to extend "product" to work
  // on Fields for such a statement.
  Field<InputGeometry1,InputT1,InputEngineTag1>::Domain_t range = pressure.physicalDomain();
  output(2*range+offset[0]) =
    pressure(range) *
    0.5 * product(InputT2(1.0),
		  coordinates(range+offset[2])
		  - coordinates(range+offset[1]));
  output(2*range+offset[1]) =
    pressure(range) *
    0.5 * product(InputT2(1.0),
		  coordinates(range+offset[0])
		  - coordinates(range+offset[3]));
  output(2*range+offset[2]) =
    pressure(range) *
    0.5 * product(InputT2(1.0),
		  coordinates(range+offset[3])
		  - coordinates(range+offset[0]));
  output(2*range+offset[3]) =
    pressure(range) *
    0.5 * product(InputT2(1.0),
		  coordinates(range+offset[1])
		  - coordinates(range+offset[2]));
#endif // PSEUDOCODE
}


template <class InputGeometry, class InputT, class InputEngineTag,
          class OutputGeometry, class OutputT, class OutputEngineTag>
inline
void
copyVelocities(const Field<InputGeometry, InputT, InputEngineTag> & input1,
	       const Field<InputGeometry, InputT, InputEngineTag> & input2,
	       Field<OutputGeometry, OutputT, OutputEngineTag> & output)
{
  Field<InputGeometry,InputT,InputEngineTag>::Domain_t range =
    input1.physicalDomain();
  output(2*range-offset[0]) =
  output(2*range-offset[1]) =
  output(2*range-offset[2]) =
  output(2*range-offset[3]) = input1(range) + 0.5*input2(range);
  return;
}

#endif // CORNER_FIELD_TEMPORARY
-------------- next part --------------
begin 660 hydrodynamics.tgz
M'XL(`&Z"TSH``^P]:W/;MK+]:OT*C-LYE1R*>CBQ3V7'O8ZL).JU)8\M)Z>G
MS?A")"3QAB)U2$J*FLE_O[L+\"GJX51.TSM1)V.2`!:+W<5B=[%`1PO3<\V%
MP\>6X>N&\=TC_*JU:O7HJ/I=M5JM'3][BG^KQT?']+=:/3P^KD+9<?WH*3P\
M?78$]0]KU:??L>IC()/]3?V`>XQ]Y]KFB(]7US-\^TN@\Z5_E0KKTL at U]HL8
M##RQ8!=Z`;[6 at 5E7W*L?X\NUZXYY`9]>)^6%_;?P'&&SMYX5!,)A=[[E#-EU
MMWMU7BA\;SF&/34%.[5</_`$'Y\EOOF!:5M]?93\9HQYD/RP3]U6.F+^TA*V
MZ>NC_41ATQU/IH%XX]K3L<B470..4R/(-G"`UT[P1AB!ZV%SUQ%.\**)U7!L
MO9'ELU%J@!//'7I\S*SQQ!9CJ.VS_=Y(,(+E01>6ZS!W@*T1(`^LOBTR1#JW
MARX0:#3VV5U at V=8?2"0$(+P95P!8SPVXC6!:CO"&"VV?]1>LI?^BLR8'!+C#
M-6!,2V<OIE[@.AJ[PK+;$?='[]V9QKAC$J/T:YV]'7%;0)7?Q7 at R^OB+._4<
M;F,GDF34)WRX'BU\P.^3QF:NK;/:TR.$4*S]]-,_2QJ;3'16KQ^7ZT=U75%'
M,,,%0DY]X;,!<83-85PLF+O,M`8#X0&!&-#+F=H<1FP)OP%-N.>+$+^!Y0B=
ML1<N-!MZE at G%CB$FP93;]@)>9L)C`73D\[%@)G#?@G'TIP%^#-N7H0<+B.8(
M!L.'WBU#4"-G.NY#>QCHC-M31-+UF.#&"+`#WODP;!+MYM1#3.V%)@4;.H+*
MCALP!T`!'OYT,G&]@+D3X1&U?-87P5R`B,MQ(Q0:^HIAPQ`C<KF&`?TASQ4,
M;/P_S>Y-IW5S_[+=NKRX[[6NKKLWYS>__@@")T#F#.'[@+L:AC_M^X$5 at +#+
M(04 at IPC$YL9['*U"%SJ]%9(2OIB`U`0X%M,"0;6`A,)$:9ZX/K`>H"``4P3<
MLGW)W^MDS_YBW'=MOT'DRD6UP2X$<H.P4?4!@[::;33T`.1"2+'>V]MC;#7-
M%HJPQ)_KV];=1;?9O6A!)RXQQLSKZQ;HZDX=$$-3^)8GS+!7`(*]X0_[W[+;
MB]:+NU<-UAZHWDR8!,"W@/DN"*/E`-'&<K[RO at LB"46"9I92$K)7R;,88#T/
MXG@*0CEVO75 at U2!"#13"1<"W'-41C'8\L6S9%IY!29C$L.&3)ZP\9.6+?,ZQ
M<KOB#/R*^T=E!".KR)6O,B%EZ]4KOF=4-M4!W5VY;'?N_C4TL/*EGM:;8$BP
MLDVURT-\=M/EN#H,@"8K9`L&>7#`7G9OWI[?7``9FY?G-^>]=K=SRPX.*H5"
M`(H-QBU.#9O[/L@<:+57`O`,/)C3\F-W&N1\[84/+6<(#.GQX5G!<FQX+,Q<
MRRSXT_$YR513V#8K&JCF&:T^IYE>`%(, at _T#V`;%6B&4.]DFBT2VD4OEI9,O
M,J0WP at O$A\\9T]XN1U/3"BS\)8I[-2WU6L^O%G68KAY]7FZ63S7YM9=^74%!
M0UH:31>,'>\*J at M_-14!KV@\&8R11J#??7_JB0M<CX(%$I=038#+(AS2(SM2
M!(=:I>FZGFDY0&X_!)</*!IS=K2YO&.;F+<M]SZ7?8_#OY>N9^R`?W^6<<;7
MP;2-7'H4=DP6;X3M&F0J?38G2#G58HW[I^#4-VGNK=CPO7#`RF`K;:9"0:YL
MO=<M]OK7BYONQ:^=\ZMV\Y9=WW1?W9Q?R>6-+`0P<8OXP+VA`60=@9-Z`,^S
MW]Z5"A^!*V@&B(!-)V3P23L65F6/>PL=BNE#HV$Y0&0./H<H$B"$@(C*U9<,
M%*@,SEBC8:#Q<7K*]E_#TN=J;.YZMJGOXR<JA['9R2'*QHC)06:)KA0D?F_(
M9"F$K#'=*3I'0PZ6"GO.GNK5RN')WAX:+-R/)I:L#&X:^^%WJOI#%D``QOQM
M("8`HZI7:Q+$#[]?"#O at +,#J4]"M0S"WP"-H!Y$)_YRINK&?8$6E"N4F^*DP
MBSCSR>,#$WL!9-$C%"+(%]88`-8EP+>N]Q[D")K5+Q3=EELXK\$+_`,784(%
M<$FA,L)2UP$OD($3%(`CXN?`.'>&ML#VS[)#X5120`L\0,?2/@44SPB6^'!!
MGM0)E";??ZN^`TA1_=I9,8%C::EV;:FVQ(9JRCJ71"[9LR1=,0E"8Z^FW#.A
MEO#\T_I9L59"Y4641_>#2V:0DT/3D!G(#_+;&)-^NP0.:,($+`+_J?=DD3_A
M!GA;?K&F5[4:5B#XO>Y%MX&N<2#=/7(E3/=G:!TL)@*GPYUCH3%^`\`L5%><
M(&I*[C3TQ$%=6]R1'9VQ4#W<!R<),%*!Q&4Q`/J]\"SC_9FLY7].2XH]1,WC
ML35'(`/D=,4UV'P$3FOB'0;-9^#T<0"KK^TZ05,MA?1L$])Y+6,49G\6:353
M04N5#8'B"!-#!8B at B`QW\.)MQ#&D4N14R>@*`\L>:FA*1C4E3EHD.R14*3HS
MF)L";3^V7>.H7:C7]CZWH;(4']9X1E&Q[?I4RIKF:1Y%L80TP3)1)BX05E*%
M)OHVZ`'_D]8/X?B0MC-I.BP^NZ$2N2T:*TDCNS%!D$AX7[MSYH_<*8AH.PX1
MPN at 2#7[>X.H2.#!*F>/.M55`V)0BJUQI115UP[@7K)S@]R\D&&CG at 4F%2U$V
M[)5=%R3X-YG5`15O,5IM3%QA3^#/*2YW)^S)$[-4D*96MO%O9G9QJ!_,TN7Z
M&!1V2<OYSC\42RL6$=F3_%)<[C9_04D)6FSS)V9#$NRFJ60DW#[V<!`Q%@D_
M38G>PV$D[.:'@<"1!.-);>\SJ0!MZP]JN]D6)LNQW6GWVN>7[7^3\1C;CG*"
MM?VDJF"1-2O-!$=@M!(,WI^97- at MGXSAO@";:<X7%']T::7_>4FX/[0=4WR0
M$JZ>3Y]GS2,=O)F`%4LH^[*2G`!I4(L$J$4^J%H*U"(!BB5'6)2=:*H">U[8
M4XLIR+:/D7OCW@`XI](N.%/U2Y6DY5:NE=@!0/6+$LS!U?UUNP(3,C38H$*I
M)+W.A\$$);069FE9A^23.4>!_#EBYP!<3_+LA%Q'^*K^[$"6KZ`S5MA,Z[5@
M?+)FUX/9Y+BIZ9$4J,;O#GEPR5FTWJ%C65L)?!6]BHS-&B2J0*$4!^W5!$;[
M6DW(9'B^46!IE?J<@>5O6P:&"X'8F5Z`-FK)2>[V%0G'B6NK1HGA22&,#9/G
MH`7`J9*UXFZI5F33K:N4&8O<8D!M(WUN(]Q,"K=A_$:"I at FJ;#(#\J*,V6CA
MLN!J;`GAY8!O7$&+*9.NBV9SIF9(G6V4.6/7;4#1#XJXRL=]@),$/IK&]O\M
M/!<L%>(X!R\?-W44P82I[V=$N[XDVQ'(4*)C#J?E.=LP'$78+N+YVFE0)^=7
MVHL@'0F/)O(YT^:DSDWS;F("2SR_>&[;X6;S2VZ(%\W3I(-Z5LR"T]B`V[Y(
MZ$^,_*PTOU;V7%RYQ9U$H-%H21OS/D`SS00O4Y-<"KRI0F*771P"]$P':NWO
MM=(Q(QKZ23IJ<X8T*)>3WTKL8U819KG^0 at PQ'N/$$9Z-D2P$$@6AGK.BC%.5
M4<FA at LYJOX.,GEP6X"415A`B"0X[VR")>0IVK8K5PV736*U><4C`U0YN/MI^
M2H&>Y`@9$B2*NU422 at 3`9%2G-+85E"^E/#/J4P7YHV!]*MS.<O#,UYFRBI:A
M13Z!#IZS/`)MH3LWR8WR!"4_XP4]1G!)`V8A1)K,(%PC()DA;-2(2WIA2DJA
M6,JW`K80THRM\>2Y,M#*JZ1-E4<R9[I!AEDI<8]&_H3,KPPC2W^!E*;V/D)\
MLB*F+;E["E-TX6`N+H]Z1?WT^HZM-7+E5/$2^5<3GIIM90FD/!J$N8$+"%OU
MF9Y9V#9=5Y63$<B>9XW"926V;+)&RW]%@=E>;X/X$I2L_ at Z!;YP^G]2Z!\O>
M5;N3='C3'0XLY\%6_#J_X)7KFOV%V'9+)]H\(CQPZXBHZ8E at ZCFL]:]V[_[V
MKMELW=Z>%#ZI':W7K<OKU at U[>==I)O(TUL\'BGO9MCO'T!9./0&V!.Y?1<E#
M$^&-K2"5YR(#831+Y:R,TA.E5F'LW#2QBB/FZCOO^X''#6E.C/F""=L:$Q5E
MUI at 0,C5-958I[20]8W;I&J!'F#L8^"+X#8-<']6W8E5#RRUZJ9&;)W<1U<=:
MLD8-:K!/)Y3`\\J:"2<OG(<(>90KA-:3ADB-H9ZQ'.Q&,'W42.-Q2)':*1I?
MJ`I`,L$L(SI(Q>#K7RIIYO-S9AZ49$)[H+(KLI5I.VX+"(U&%!3UHP!7MDE/
M2S:0\83[@'G*$I(HZ!/*G^2V+)>31!85/:G8T'Y?BHC`M$0^2N,^?#EE1>`>
MH$;!"O556OMID*`2B6I at N-.7)THR59-WB:FJ9N?#I6V6MQ7P-<K;E\IH^B9L
M4MC*#Q"V_TRYZ:'6!O_+_M%72<)\B*G'/FY;S,A=)(.-%A:5X9MM)U=I/9'A
M at BR@?=FLM$12(B.9A10H:22$*7VQKXKT!U;/-+:B9"[9K[`$^P6C=;AZPKK"
MBB"+#/P,2C<'0P?66+(J,\3(FA!J*G('_LF<9K!?1HGU75;00 at L'82DW8!S&
M<6P*M>->S[[DVCXEG<K`>>M#`*LZKJ'CJ1U8F,H0B^+ZN?DM/^\KSL\+4W_.
M`<FQ5."QV82=>2DC48I9!@D)@=N>X.:"4NBCW&*UWTGD).!R]M&.8@:*'BF@
M94,\4S7:2;V2LKB0NY/*C(=%!7O*3A$]TI<9]H7<RS(O1W=FF)FK1#,9-FKY
M8M(1+=;05BX^4(R2:A^408Y65OJ8A7Z\3XHXI8*SZ_N[$H88,B-2BIKHNT(/
M1W1-JIR5BD;YOXB3LHD-R?VDFJ(F^K=$T;]=HBAICUU-JMS9M,WFGR=#1\[<
MXY-B*=SX^[Q]OPRL6A)6N)?W44F*<N7D.,(-/P5+Q0NRLX\]"3V_ZCO:%`P'
M'UI(!X4]:1$H$Z"H6(G)9Z607^DMWM2TKK\K197*JZO5WI568)BH\1CX5;?#
M[W`C?O7'P>]P._RJ&_$[?!S\:MOA5X_P^Y30Y"NW5F5N!-2C4W-S[N#A+K#U
MIKY@^P1X7^ILDP>\C&F5MBULV1CC&LK=PW-U;<R0P,VX]XX[9R/X!W"$M![W
MU:#W\=O<]=Y+"*X39B7A#/7Q*!./P>YRW5[G]63X)^=G,N87LQ"_;N+BQAFZ
MQ>Q<-S,?`;/J9LP.UV)6?RS,#C=C5EV+V>%C85;;C)F<B[GQ?32MUEH]:=LF
MWW+Y*X]4:!%F:9P>=)YB(Y!='*989:HH-9+1(CE*1.UOX)BV4"/EC!K)+ZVM
M+:VO+2615 at A%\17:#I$TBTSYE"6_<;OESY[_SYZBW\6=`MG?^OL?:O7Z45W>
M_W!T?/P4Z]4.JT??[G_X(K]*I;S+'YT7MRD$0T>1&4L+&'FFMMOG-GLY=>3U
M"3VE4<,61K;%;C%$J\HALPKOJ;A/G<.ZO^VU.LWVY?WKPO?JR/GZ6H#=+GZ%
MG8\2[_68"6]FB7F#P5N&,84XSJ/HS%B#,?E<-N)[(M0=".B*#9!AKE="`U!M
M$>`9]=`$SAX<D)V4=_@?PMLL0#)6FHI'E6!L;\%#G&`:1=B*TQ9"8 at 2$^"U8
MOH9EGZ:)`^M+3(>2O-TAT3)#244HGW%#76P!AKF):6^)-F"54[*F/Q%&G-,L
M+PY(_'8O_H\@:3UY%(CHOUO at CX&MNJ+B,;"-+YL)+ZVIR&!9ZB*:=%D[.J+I
M^2OJ75B#07?B5Y(2NO+"F\<@V4O7FW//9!<"C%&5A?>W8#8A/[5MP-PW/&N"
MF$=:8JT^7*$E\2A:.,&598[W^-#I')D]M/]2ENZST%5`,.B$@P$K\[C2JD;5
MUUH?R-D!#,\T-A]9X%<GKAWB=&&+!:,`*&'L/%1*\MJ4?(V2.%.4W=@ERS.\
MFB>=8Z"V';\V+1Y&M7.WJ*.M1)[2S?&Q0MSY3 at _3!Z\`S#]U<"K13/P'-7>4
M:P*-_!_#W1!%E=W**I%YEP"1Q,+!K4]YN51*''?:4W8[$2/9\BGTVLX*\CT]
MF4X*CS+J5?, at N@`K:8SNM//'&$U&_\CMZ0J=R2R?L5N2WPHFWGQ-;,UIF=VU
MOPJ?FK?Y8!+YY!I``)VXZGPVP@(H[*SPL3"9]FW+:."&6'A`N5>'^:PB#6%6
M.NV8T5%AJ>B:I!!@NH.6H<`'%,M]`Z52*+6)O'/*XL.]!$KF)Q7:)1??7VH+
M')$JU at C!X_J!O&LD88?`BK6X`U2!:C=6W33G>N'A4U4.8^-3F[0Z+AH(V at .U
M11FV_454[,ME!G0<!DLDAA(>;?@VLTJ6?9089'CXLG6V7%O%B%KL'W2/EHS'
ME$((H"7EMJ\(1FYX=!8+4!9L=RX\2E,(Y&D'13QH'*9:G%`T'`NG:+JOK5R3
ME<ED7L;]C."@S&0%`>,U:A$MEL+QP'"DP%JU%>(P*%JU)U!8ED_5F'&[ZUK]
MK:]`85TF"B8B[A4EDII5!TS+$D]\D4%26.CB3V%Y39:KQ.A'&H_Z>Y at S6^Y\
MP6`EQG05M3*'J36 at O,-*$:8:P-#HE<:0>H47&)6>()B2CE3K8C5.*6?%%"28
MC`<I6$5D=V$O7:F^7"D\8UC.[;"VNL-J%E9]FPZK48=/<CNL/Z##O!$ND:$:
M"L>G1UJ_-QJ'C[[2;9.0B*N+`R9](6G+WY*MG0D>G$+S:,74HK#VJJS#LT8#
M_6F83TOI[7%`?F7*XD!&SL.U;PTFTBK,N<LC/1952]N$-;L1 at 02F]!.]-QIC
M_EX4%9#B0$^L$\423M=2*LY]L#[>ANGR?W7$]/_7;\VEN#OK8WW\OUY_^JQ&
M\?_Z<>WI\3'&_Y_5#K_%_[_(#U7I09DU\<K0`U*_Y\W+!IC0PO%WZ"E&URO[
M[B"8XP%@2D1#/6-CZF+R&M3B"%QSRV$&9BV8;/^V^[+W]ORFM5^BH(#\2FJ"
M%:]5;@/K]O\7)+C<!3>>LB&4S4G=G$_DB3!Y=E3>G3OFIH at O%6)3QU0W#X.=
M/J8+90W/Z@, at 1$9=YQLB0CF3?763+Y^`B3*#BAC7\80M.- at P%&F`!<,%!Q2Q
MN3PO-YNL(R_KHI?R3_\L'SW3P_7NSK$%K"\N8.#-+1_OA37I$)NIR4,I2WTS
M/H41*EN?$RJXB+D+(1A>>ZR>R>W%<8'S1(Y"L"!'&/QB0-BQN!9'^&5-A'3I
MXK75?`Q_.N%UT9>\[U*]A:(6J`XZ5\,ZKL[>EH^?5I^56YU7Y<,C&C_"H8[U
M6YU=X.5>`06AH!=YW$Q1E<I?X<W/#I7C`#UK.`I\E=*B(22ZG!@L04%772=N
M-4Z31R>0T at FDLSZX,ZXEJA.P"=TUAJFFGH57<,\$I;=(:9&-Y<W)8!0L,O3'
MH>'!*I+%$??PL!Y*@&5B#@X>&J3Z'3>@ZWE(RA=Q>@Q=U4:<\T?6A(["IX9G
MXNX*QRNLW(F\2+HC+)0*HF6"3([K9?DJY?H]GK!W,"W(\T"QP^B%#"RB6.":
M;J%0P3.G'&)9V;9X'WQJ%`Y/(80G.GQ+?1VHWC##B,0D2?-0B-N#F$Q0/G9A
M2;=D9I(:W1+%-9D_%%:ERXU#$.IFI3[XK#"K/.#(F'OO$7G?Q6`K9BT!:#"+
M!HB6%:BC+5*$0Z\XGN,#SQW#Y.M<1 at CCM4LK;F$F_0(]81Z4*(\!!@W#=<?\
MO[AAZS9W;'WHSDB>\*IN"U@@KV(DS307?3;A0Y``D.@@F#0JE?E\KB>;JMN4
M]=UJV;3ZW at W<!'B*F4=;N\D[#AY[H[;3>BN3'^ZN+\Y[K9O[9K=SVSOO]-ZT
MFKWN3;-[==WMM#J]%\VEW=L'-7WTK5BVTN9JL!=09C9!5PQ1SX;["T&@`M%A
M@)I"FS2WY"D:"I43$,K[`_4QQ'NQ\&XS3&;OBX6+BBBSMTB_&%31=H>8)6,O
M2C#]HQ@;HYSO`3?H2":/[^?, at T8G"?2_P\[0MTU+!3RQBTC_>PK,(*MT7-R6
M3.\QQJ77Y[XO/+G;F+-=J:X*"?^NV-3,U'H!=M/??/MR110!<_Y6!Q+4!Z*)
M#*CL%JNU&Z"K%)'<YDQOH."ZKW at 5*JC_:^_8?]HVPC_'?\6G0C.[8`>2;I6@
MJ08):ZF at 5".L_#"MRL-)/$*"XM*)H>YOW_>X.Y_CQ`%$F#KYQ,/QO;_O<O>]
M+SGYOS`%$\:BJH0.F5:()S?2L5*;U'9FA[$W&%9LLF4>!?Z<*L<CVDBH2[KF
M([/)3<9":O&N%"GEJ-Z8A`;$75-/S_42JP]%JM$@+\/VF&G,81O)$6Z"XA#X
MO?`*3WWJ!KE@@#U;9`XQTAAD\$Q^ZZ+O16B1R?28?66$Z&T+)4`;IV]`(3/"
MP?X=3B>[0D*U^WV<&E)FX]&-(EJR:DWEGE4QTZ<-O=LFFH>CW>D.A+%1-TW0
MF#I2@*. at 2<L&802AEI'M6MII%0*#!$*:<S`;_JHV]UF3VE:B$%JP/M.*GD<=
MT!RM"TF>'5@\&E9)T%(4R?;7=+Z)"B#RNQ:4-4!-3F<R&>D5I'O9UVBMJ^!0
M-(0=[N;SI4O_/+$DGND.,V?>J'*Z5RR@'[U-$:HOZ!I++LCQ0"K>*J5++G1D
MWHOWFC)2_^$H/3]^%<@'/<KL1*54YGUVPE(P>;%\XE)C8;YGIK^B!8 at G;#00
M3L\8>4"3`E?WPO"*>#>5'2Q9G%#6]>O+,3$=QI[1QPCT*8#4,%:HV)V/"%4F
M\WYW!@^J7/)B-Q\+JOS"?-;B&H'S"]I2E=YJ14CIRO5(<44QK%/Q at 2$W&]SM
M2;H3*WSH+<'-:"H5)(UN<P9JV0I9L'+=E@%LMHH%82Z;M\%D:R^&M]%-G\7L
MBB[.1\R%JSC>UGGQ at W(LWH0P&`2;(D.P#]BN!/,F#X;4<=VBX"WZ$VF34RO(
MRI/)T<\5BQ7"'<<"O2RO.2"6C!:DUV$>C*C$JM1=>V-E)SJ/:I)SD""W^"Q\
M*FU8/AVKCFQ%(;Y.:XNX:DIA9'O!`.3L7Z^UH at U:=E1!>`-D0Z$D?18/<>^>
M'SR`^=8F#P7``X'`K9!D/:W(>P2`/'@TU&G>%0'9$<#I-8OOI9;\K)JH(YSI
MF):R_YE!0#EF#Z8EQ^4B,)0[[,F_HP#AQIN`;P3*PN<@?:%";`F?[2H=OO94
MULH28 at N41#<F at 1Y$0I'',E(6E8\G]-'7]]@IW*G_BE%W5077"\;7ESA/L=''
MG;]>ARWC&9OT;MTL(R,.TD7T-'`[3IKFF'RVBU6ZS;>A<$6]B'D_"DJO,G6P
MTTXW4$=F!:J[C at V,4%0C2>688HV/;6A0C!FV<U0CAE3]:8B;*(5`"^'/:\1J
M=SBY(MLO;L$6EFW"E_8%!S(BV3R>;$DS>,3U1NJ,8RT/<ZO8(I64DT.B]!.'
ME^SF20,N*XOPK")FAY%+.SNQ9KZQ$/-F0,UW`/5HS!JDN`A<!-7BX[Q<!O.5
M3M"3TKP'$VVRQI'[Z[#M)76D.QU0B%*(#$@V>UM]6\VB&=&B)/`,H\&0I.+(
M)/-1G*!:QJ[07$YU>VN>N+EWU(94AG2.7DG)Y3<I:6<DRX)MQN:L- at W4,5\=
M$*>6<!I&@^1N`?S$[;D]L[(M'*EQ[0E at LM^:5++>Z:]1]O8$*T?N1P#?#'A#
M80!Y&!UO;D[*M&`-_!M8>%V(@".#O/G3O1L:V,[O$='`[3T"&DKWQP/?7P%B
M.S<+8<I#S.C9^`I'-M`=^;LBWJ09ILPZ_]%GF[=*+M58=BK^Z(J"^Z2$4H:4
MU8P14_LZ;FSJU#/X-U#W`I)YN5TE;D3L9Y at ECMI@%4AXHB2>`O:;8A#,N6U.
M?,M]6/=M"/Y'!)HB^)/HX`L$KVG_$I8GTF;-BAW2'PM!A0?>?B-6 at KY<<>X*
M97:S(KN\R:4)YKR2[CPY6HX`#>XH0V.R[`X",^DJ)2D3246WDR<4N]7\Z'(.
MRF*<U'?"LIU=2B^7^XGQ;/(%>ABY;6V@)O1\)'%V(G at -57AA?T^L@(80;6QX
M9GOKWRU(?.N-&VUF42`6R25-=.,+ at 742%B7#XN<Q]39'3\W(E]F*Z7!_M?!_
M;:BUHF0<!U?81[[]WU:M]E/-^/_77E6Q_/:/U>W"_N\IT at K.MV7^_=I'X4D=
M^S_B\UFCM<2Q/RGE?$^._<M=*76H'`_8S_^RC40:V3MU^;I1)C;D_,'J,W8=
MWX5Y1&',(8U;AA:M:'Q3D0-X1785N;8%]UF3._`QG*K at T[E+,X!TP#)J)D+"
MF*AYB6<6\2UPUW'8OQ[)/:+B1:$]>;7C=)^$-NUX):9*(KS[;6;L;'H0ZK'-
MB[[&S'2;1Z>-9].QM5.P28#"7<[<ICICNT&473.ZK%?O&$AY)@JRHW'UT.C)
M^C(U>=L>Q\C$$XDF3^2&58>O[(T%?RDW2)6U35G^5W:<PKPM^RX"W<Y2!Q*S
MKQ<.)#/IN'V!Q^$H7&4?3/]M+:3_ME_6Q/_CY2NB_,C_8[M6JQ;TWU.DM;4U
M..&I;\+[L-^?AC?0#!QZ7474'+>GU5?\B146],2?&GPE1\A^(\A"#*;M2\?Y
MU/CE:._M:;WD?R))A?]I//')3('C"5'T&YU?\@>P[JKB'OR.S)_?Y,M'P&_.
M#W0FA0[7;]^='!]\4Z;3TVHEGG8K,.?]*.I4C at X_G)T/NMV*TVA at I[H,OO$C
MXD]'(WJN53K1F!Z<QOFYGD#S[/3@\]'A_MOS\\^''["A at U,<L1HP%5S:WL:&
M<]34S1T%I378.ST].SX0&W$:)N`8IV*XI86EZDX3)2YE"SL<!$-LQ%/S:9PX
MG=.3LU\;.*8Z#&]ZN#??X+D1=>.`<D_VWR/_2GDX8EURYSGFU9\'$\]QG.<[
M@$]."?//SSW@?PH7ZZX:-3[^09^P>P_\":S_K.IA%_>N2`;\2!J4\(3W^_`B
MF.!O%.&?V'&&8;L73A.C@(A(A!+.$_SCXP3H]F3X at W[&O1]AVVR"-`2TFX'=
M&N4WZ!YY5MU<JBMDG at U(L0;^!<P9P+. at ."6*5*0B%:E(12I2D8I4I"(5J4C_
+I_0O9"OC<`"@````
`
end


More information about the pooma-dev mailing list