The purpose of this document is to explain how a RESCUE model is created. This document is a working draft in an early stage. Please send any comments concerning document directions to rhanks@benchbuilt.com.
Although several coordinate systems may be used in the description of a RESCUE model, all are closely related. A single coordinate system is used to describe the points in the wireframe model. The first step in creating a model is to create this coordinate system (which we will refer to as the model coordinate system). The model coordinate system is then declared as an argument to the constructor for RescueModel.
To save space, almost all coordinates declared in RESCUE are single precision. Unfortunately, this is not enough precision to place the model in a global context. Therefore, the usual practice is to use a local coordinate system. The local coordinate system is placed in a global context with a RescueVertex. The RescueVertex is a point described in a well-known coordinate system (such as a UTM, State Plane, or Geographic coordinate system) which is used as the origin of the local coordinate system.
All coordinate systems and grids in RESCUE have a declared orientation. The orientation describes the direction in which coordinates are increasing. There are eight possible orientations.
If you visualize a cube, it has eight corners. Each corner has three lines running away from it, representing three directions in space. If any corner were the origin, then the coordinates would all be increasing as they moved away from that corner. The orientation enumeration denotes which corner is the origin of the space.
The following code illustrates the sequence of creation. First, a well-known coordinate system (the ultimateCS) is created. This coordinate system is fully described, including information needed for projection. Next, a vertex is created by specifying a point in ultimateCS. Finally, a local coordinate system (the penUltimateCS) is created using the vertex as its origin. penUltimateCS is then used to create a RescueModel.
RescueCoordinateSystem *ultimateCs = new RescueCoordinateSystem("UTM 14N m", RescueCoordinateSystem::LDF, 0, "northing", "m", "easting", "m", "elevation", "m", 1, 14, 2, 8); RescueVertex *modelVertex = new RescueVertex("model vertex", ultimateCs, 960172, 1239471, 0); RescueCoordinateSystem *penUltimateCs = new RescueCoordinateSystem("penultimate cs", RescueCoordinateSystem::LDF, modelVertex, "northing", "m", "easting", "m", "elevation", "m"); RescueModel *model = new RescueModel("test model 1", penUltimateCs);
A RESCUE model must have at least one RescueBlockUnit, and may have any number of them.
RESCUE models may be devided into blocks by vertical surfaces which cut thru the entire model. They may also be devided into units by horizontal surfaces which similarly cut thru the entire model. Because internal surfaces and sections are supported, the modeler is not obliged to devide the model into multiple blocks or multiple units.
At root, a RescueBlockUnit is a business object, and the model builder may decide how many to have.
RescueBlocks, RescueUnits, and RescueHorizons, being business objects, are created simply by naming them. They are arranged in order within the model by specifying which is above and below which others. This assists readers in traversing the model.
RescueBlock *block1 = new RescueBlock("Block 1", model); RescueBlock *block2 = new RescueBlock("Block 2", model); RescueBlock *block3 = new RescueBlock("Block 3", model); RescueUnit *unit1 = new RescueUnit("Unit 1", model); RescueUnit *unit2 = new RescueUnit("Unit 2", model); RescueHorizon *horizon1 = new RescueHorizon("Horizon 1", model); RescueHorizon *horizon2 = new RescueHorizon("Horizon 2", model); RescueHorizon *horizon3 = new RescueHorizon("Horizon 3", model); horizon1->SetUnitBelowMe(unit1); horizon2->SetUnitAboveMe(unit1); horizon2->SetUnitBelowMe(unit2); horizon3->SetUnitAboveMe(unit2); unit1->SetHorizonAboveMe(horizon1); unit1->SetHorizonBelowMe(horizon2); unit2->SetHorizonAboveMe(horizon2); unit2->SetHorizonBelowMe(horizon3);
A RescueSection is a bit more complicated. It includes a description of the surface as a three dimensional object. All surfaces in RESCUE models, whether horizontal or vertical, are described by a two dimensional grid, with an xyz triplet for each node of the grid.
The following code constructs an array of triplets for the fault shown above, then creates the section and passes the array of triplets to the constructor. The x,y,z values are in the model coordinate system (the one passed in the constructor to RescueModel).
The array has three dimensions, i, j, k. "i" defines the i node, "j" defines the j node. "k" has one of three values 0 = x, 1 = y, 2 = z.
All arrays passed back and forth between the RESCUE library and a user program are in Fortran order, so when coding in c the proper indexing would be array[k][j][i]. Because this is confusing the RCH3DArray control class is used. The order of arguments in RCH3DArray methods is (i, j, k) but internally the class reverses the normal array order.
RCH3DArray *section1Face = new RCH3DArray(6, 6, 3, new float[6*6*3]); section1Face->Ndx(0, 0, 0) = 0.0; section1Face->Ndx(0, 0, 1) = 9.0; section1Face->Ndx(0, 0, 2) = 0.0; section1Face->Ndx(1, 0, 0) = 4.0; section1Face->Ndx(1, 0, 1) = 9.0; section1Face->Ndx(1, 0, 2) = 0.0; section1Face->Ndx(2, 0, 0) = 8.0; section1Face->Ndx(2, 0, 1) = 9.0; section1Face->Ndx(2, 0, 2) = 0.6; section1Face->Ndx(3, 0, 0) = 9.0; section1Face->Ndx(3, 0, 1) = 9.0; section1Face->Ndx(3, 0, 2) = 1.0; section1Face->Ndx(4, 0, 0) = 14.0; section1Face->Ndx(4, 0, 1) = 9.0; section1Face->Ndx(4, 0, 2) = 1.0; section1Face->Ndx(5, 0, 0) = 18.0; section1Face->Ndx(5, 0, 1) = 9.0; section1Face->Ndx(5, 0, 2) = 0.8; /* bottom row. */ section1Face->Ndx(0, 5, 0) = 0.0; section1Face->Ndx(0, 5, 1) = 11.0; section1Face->Ndx(0, 5, 2) = 6.0; section1Face->Ndx(1, 5, 0) = 4.0; section1Face->Ndx(1, 5, 1) = 11.0; section1Face->Ndx(1, 5, 2) = 6.0; section1Face->Ndx(2, 5, 0) = 7.0; section1Face->Ndx(2, 5, 1) = 11.0; section1Face->Ndx(2, 5, 2) = 6.0; section1Face->Ndx(3, 5, 0) = 8.0; section1Face->Ndx(3, 5, 1) = 11.0; section1Face->Ndx(3, 5, 2) = 6.0; section1Face->Ndx(4, 5, 0) = 14.0; section1Face->Ndx(4, 5, 1) = 11.0; section1Face->Ndx(4, 5, 2) = 6.0; section1Face->Ndx(5, 5, 0) = 18.0; section1Face->Ndx(5, 5, 1) = 11.0; section1Face->Ndx(5, 5, 2) = 6.0; /* top row. */ int iLoop, jLoop, cLoop; for (iLoop = 0; iLoop < 6; iLoop++) { for (jLoop = 1; jLoop < 5; jLoop++) { float incr[] = {0.1, 0.6, 0.8, 0.9}; for (cLoop = 0; cLoop < 3; cLoop++) { float left = section1Face->Value(iLoop, 0, cLoop); float right = section1Face->Value(iLoop, 5, cLoop); float temp = left + ((right - left) * incr[jLoop - 1]); if (cLoop == 2 || iLoop == 2 || iLoop == 3) { temp = left + ((right - left) * (0.2 * (float) jLoop)); } section1Face->Ndx(iLoop, jLoop, cLoop) = temp; } } } /* Extrapolate in between. */ RescueSection *section1 = new RescueSection(RescueCoordinateSystem::RDF, "main fault", model, RescueSection::FAULT, 0, 6, 0, 6, -9999.99, section1Face->Array());
A RescueBlockUnit describes the volume at the intersection of one RescueBlock with one RescueUnit. The RescueBlockUnit is gridded with a three dimensional logical grid and an x,y,z triplet can be derived for each i,j,k node of the grid.
The volume x,y,z triplets may be in the model coordinate system, or in a related coordinate system which is derived by rotating the model coordinate system about a point in space. Rotation is used to save coordinate space. When the x or y element of a triplet can be derived by the simple formula
x = origin + step * i or y = origin + step * j
then the x and/or y triplet does not have to be stored in the model.
RescueVertex *rotationVertex = new RescueVertex("rotation vertex", ultimateCs, 0.0, 0.0, 0.0); RescueBlockUnit *topU = new RescueBlockUnit(RescueCoordinateSystem::LDF, model->NthRescueBlock(blockLoop), unit1, 0.0 + (300.0 * blockLoop), 100.0, 0, 4, 0.0, 100.0, 0, 4, 0, 4, -9999.99, 90.0, rotationVertex);
A block unit grid (BUG) described this way, with an origin and step, has a complete description for x and y at each i,j,k node, but the Z values are zero. This is appropriate for pinched-out areas of the BUG, but other areas must have a z value defined for each node.
topU->GridGeometry()->SetZValue((float *)blockUnitGeometry);
The array has three dimensions, i, j, and k.
There are alternate ways of describing a BUG which describe Z values by offsetting from one or more surfaces. If top and bottom reference surfaces are available, the individual k layers can be determined by placing them proportionally between the surfaces.
This can be seen more clearly in 3d if your browser is enabled. This model was built by the "propModel.cpp" example program.
Similarly, with one surface the z values may be calculated by offsetting from that surface, placed either above or below the BUG, as seen below.
Again, the effect is more clear in 3d. This model was built by the "refModel2.cpp" example program.
If conformal grids are desired, the BUG can be selectively modified to add additional complexity to the model. As described under RescueGeometry, each i,j node of the grid which requires further refinement may be defined as one of four alternate types, RescueZStack, RescueCoordinateLine, RescueCoordinatePolyLine, or RescueSplitLine. The figure below shows a grid, built by the "modelB.cpp" example program, which uses all four types in a single grid.
The figure (without annotation) is more clearly viewed in 3d.
The block unit grid is termed the "micro model" because it assigns properties at a small scale. A RescueBlockUnit has a bag of RescueBlockUnitPropertyGroups. These collect a set of properties under a user-defined name. The property group contains a set of RescueProperty objects, which assign some property to each cell. The user can assign a floating point value or, using a lookup table, a string or a two-dimensional floating point table. The table can be used to describe properties which are in the form y=f(x). Examples of using each of the property assignment schemes are shown in the makeModel2.cpp example program.
The Macro Model defines the shape of structures within the model. Elements of the macro model include the RescueSection surface descriptions and the volume descriptions around each RescueMacroVolume.
Each RescueBlockUnit has at least one, and may have several RescueMacroVolume descriptions. Each of these descriptions defines a single contiguous volume. Consider the following block unit grid, which shows two distinct volumes...
3d.
For each RescueMacroVolume there is a set of RescueEdgeSets. There is one for the top, one for the bottom, and one for for each k layer of the grid. These edge sets define trim loops around the k layer to further define the exact edge of the volume (useful for non-conformal grids) and interior edges to show where sections cut thru the grid. The boundary grids only are shown in this illustration.
3d.
The next step in defining the macro model is to define the surfaces which lie above and below the volumes. Note that in this example the area between the two volumes is pinched out, so that the top and bottom surfaces come into contact in the middle of the block unit.
3d.
Next we define side sections to enclose the space. We want to have a set of RescueSections such that both volumes are completely enclosed by the sections. On the ends where the two volumes do not touch we used a single surface one end of which covers one volume and the other end of which covers the other volume. This is okay. The RescueBlockUnitSide for each RescueMacroVolume will have a trim loop which describes how that RescueBlockUnitSide relates to the RescueSection as a whole.
The other interesting point is the RescueSections which describe the pinched out side. In the middle of the RescueBlockUnit each RescueMacroVolume has a side which has a zero height. Nevertheless, we still give a complete description of the side. The same thing can happen with RescueBlockUnitHorizonSurfaces. For example, a y faulted block might have a bottom surface with zero width.
3d.
After that we build RescueTrimLoops to delimit the area of the sides against the RescueBlockUnitSides we define for each volume.
In building RescueTrimLoops, we first define RescueTrimVertexes for all of the common points. Any point which appears in more than one RescueTrimEdge is defined as a RescueTrimVertex. The wireframe model is held together by common points. RescueTrimEdges which share a RescueTrimVertex are known to be connected.
The next step after defining RescueTrimVertexes is to define RescuePolyLines. RescuePolyLines have a RescueTrimVertex at each end, and zero or more intermediate points defined. Just as with RescueTrimVertexes, RescuePolyLines are conserved. For example, if two RescueBlockUnitSides lie against each other they probably share trim edges. Each of these blocks would use the same RescuePolyLine in their trim loop.
Once RescuePolyLines are constructed, we can build RescueTrimEdges. A RescueTrimEdge consists of a reference to a RescuePolyLine and a direction. The direction is necessary because connected RescueTrimEdges (those which share a RescueTrimVertex) are bundled int RescueTrimLoops. RescueTrimLoops proceed as nearly as possible in a counter-clockwise direction around the face of the volume, as seen from outside the volume.
Rescue tracks the position of wellbores that traverse or TD in the model. Each wellbore is a polyline defined as a directed set of x,y,z triplets.
In addition to the geometry, Rescue can store a variety of properties for each wellbore. The property types are the same as those supported for block unit grids. Floating point values can be sampled at intervals like a normal log. A table of floating point values can store functions in the form y = f(x) like an acoustic log. The string type can record picks and shows.
The modeler may also define which cells of block unit grids each wellbore traverses, and which faces of which surfaces the wellbore penetrates.