Petrel Data Access.book - Ocean - Schlumberger
Petrel Data Access.book - Ocean - Schlumberger
Petrel Data Access.book - Ocean - Schlumberger
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
<strong>Ocean</strong><br />
<strong>Ocean</strong>* application development framework<br />
Version 2007.1<br />
Volume 2 l <strong>Petrel</strong>* <strong>Data</strong> <strong>Access</strong>
Release Notes<br />
Copyright Notice<br />
Copyright © 2007 <strong>Schlumberger</strong>. All rights reserved.<br />
No part of this document may be reproduced, stored in an information retrieval system, or translated or retransmitted<br />
in any form or by any means, electronic or mechanical, including photocopying and recording, without<br />
the prior written permission of the copyright owner.<br />
*<br />
* Mark of <strong>Schlumberger</strong><br />
Other company, product, and service names are the properties of their respective owners.<br />
ii<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
iii<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Contents<br />
1 <strong>Access</strong>ing Domain Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1<br />
General Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2<br />
Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2<br />
Property Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />
Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />
Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />
<strong>Data</strong> <strong>Access</strong> Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
Domain Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
Common Patterns and Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />
Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />
<strong>Data</strong> Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />
Domain Object Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />
<strong>Data</strong> Mining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />
<strong>Data</strong> Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />
Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
Models tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
Results and Cases tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />
<strong>Data</strong> Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
Creation rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
Parent types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
Creating new hierarchies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
Expanding trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29<br />
Creating properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
<strong>Data</strong> Deletion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
Settings Information <strong>Access</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />
General Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />
History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />
Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
Active Object <strong>Access</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40<br />
2 Borehole and Geology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .45<br />
Well Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />
Contents<br />
iv<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Borehole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />
Well Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60<br />
Stratigraphy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />
3 Structural Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .71<br />
Shapes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72<br />
Surface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />
PointSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />
4 Seismic and the Geophysical Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79<br />
Seismic <strong>Data</strong>sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />
Seismic Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />
Seismic <strong>Data</strong>sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />
Seismic Interpretation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108<br />
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .117<br />
5 Reservoir Modeling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .119<br />
The Static Reservoir Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120<br />
Pillar Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .121<br />
Pillar Grid Domain Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .134<br />
3D Property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .149<br />
Reservoir Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .163<br />
Simulation Result Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .163<br />
<strong>Data</strong> Analysis and Simulation Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .164<br />
<strong>Access</strong>ing Simulation <strong>Data</strong> by Time Series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .165<br />
Result Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .167<br />
Streamlines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168<br />
Reading Streamline <strong>Data</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .169<br />
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173<br />
6 Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .175<br />
Extending Import/Export . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .176<br />
Open <strong>Petrel</strong> Binary Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .182<br />
RESCUE Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .183<br />
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .185<br />
v<br />
<strong>Ocean</strong> Developer’s Guide<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
1 <strong>Access</strong>ing Domain Objects<br />
In This Chapter<br />
General Concepts .......................................................................................... 2<br />
<strong>Data</strong> <strong>Access</strong> Patterns ..................................................................................... 9<br />
<strong>Data</strong> Deletion...............................................................................................32<br />
Settings Information <strong>Access</strong> ..........................................................................33<br />
Active Object <strong>Access</strong>.....................................................................................40<br />
1: <strong>Access</strong>ing Domain Objects 1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
General Concepts<br />
<strong>Ocean</strong> exposes most of <strong>Petrel</strong> <strong>Data</strong> Model. The modeling concepts in the<br />
<strong>Ocean</strong> framework are founded on the design of a C# class library. Part of<br />
this library models the various <strong>Data</strong> Domains of <strong>Petrel</strong>. This chapter explains<br />
how to retrieve, modify, and create <strong>Petrel</strong> <strong>Data</strong> Domain objects, also known<br />
as native <strong>Petrel</strong> domain objects.<br />
The term 'domain object' is used in the <strong>Ocean</strong> API to mean a class of data.<br />
The data used in Exploration and Production can be varied. To organize<br />
such a varied amount of data, abstract classes are formed in a taxonomic<br />
hierarchy with the interface IDomainObject at the root. One domain object<br />
may represent a geological horizon, another domain object may be a<br />
completed interval in a Well, and yet another domain object may be a Seismic<br />
cube.<br />
Every domain object representing earth entities have related properties. The<br />
properties, although seen as domain objects themselves, are dependent<br />
instances that cannot exist without a related independent domain object.<br />
For example, a 3D property object in a model is meaningless without the<br />
pillar grid to which it is attached. The 3D property object does not contain<br />
any geometry information and needs the pillar grid to be placed in space.<br />
A domain object may participate in any number of binary relationships with<br />
other domain objects, and these relationships may be exclusive (composition)<br />
or shared (aggregation). For example, a borehole refers to a trajectory object<br />
that is really part of the borehole, and a borehole is contained in a<br />
BoreholeCollection, but it can exist outside that collection.<br />
A domain object is anything, concrete or abstract, that is uniquely identifiable<br />
or observable and being of interest during a period of time or at all times. A<br />
domain object represents some type of object that is of interest to the class<br />
of applications under consideration. It is described by a collection of<br />
characteristics. For example, a borehole is a domain object. In data modeling<br />
terminology, a domain object would be closely associated with an 'Entity.'<br />
A domain object cannot be constructed. Its model representation is either an<br />
interface or a sealed class. Domain objects are created usually by a Create<br />
method residing in the intended container class of the object.<br />
Properties<br />
Properties are used to describe physical characteristics of data model entities.<br />
Properties are modeled as domain objects themselves, but they are dependent<br />
of the entity domain object that they characterize.<br />
For example, a WellLog cannot be placed in 3D space without the Borehole<br />
it is attached to. Likewise a Property (3D property) is meaningless without<br />
the Grid (pillar grid) that contains it.<br />
2 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
General Concepts<br />
As property objects contain values that bear meaning in a certain unit<br />
measurement context, they must define that context. A property always refers<br />
to a unit measurement and a presentation template.<br />
Property Version<br />
This introduces the concept of property version. A property version defines<br />
uniqueness for the entity it characterizes. For the API user, the property<br />
version specifies the unit measurement so that the API consumer may<br />
convert the values of the property to the proper unit.<br />
A property version reference is always needed when creating a property type<br />
object.<br />
The PropertyVersion class and the IUnitMeasurement and ITemplate<br />
interface are related as follows.<br />
IUnitMeasurement<br />
PropertyVersion<br />
ITemplate<br />
Fig. 1-1<br />
Property Version, Unit Measurement, and Template Classes<br />
Defining Property<br />
Versions<br />
Log Type Property<br />
Versions<br />
When creating a Property instance, one needs to specify the property<br />
version that the property object will use. Existing property versions are<br />
found in the IPropertyVersionService. This is accessed via the static<br />
class <strong>Petrel</strong>System with many other <strong>Petrel</strong>-related services.<br />
The IPropertyVersionService is accessed via the static property<br />
<strong>Petrel</strong>System.PropertyVersionService.<br />
<strong>Petrel</strong>System.PropertyVersionService has methods to retrieve, or<br />
create if needed, the PropertyVersion and ITemplate objects.<br />
This is done slightly differently for Log type properties than for other types.<br />
Property versions for logs are treated as a separate case because the data<br />
model allows only one log of a given property version in each individual<br />
borehole. This is exposed in the <strong>Petrel</strong> data tree in the global well log objects<br />
that the user can select. The global well log is referenced in the API with the<br />
1: <strong>Access</strong>ing Domain Objects 3<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
interface ILogTemplate. Having an ILogTemplate instance is enough to<br />
determine the property version.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.UI;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
ILogTemplate globalLog = ...;<br />
IPropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
PropertyVersion pv =<br />
pvs.FindOrCreate(globalLog);<br />
However, the user may not have selected a global well log, so that<br />
ILogTemplate object may not be at hand. Yet the global well log may be<br />
retrieved by mnemonic:<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>;<br />
using Slb.<strong>Ocean</strong>.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
IPropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
ILogTemplate globalLog =<br />
pvs.FindTemplateByMnemonics(“GR”);<br />
PropertyVersion pv =<br />
pvs.FindOrCreate(globalLog);<br />
An alternative is to define the property version from a given well log name<br />
and an ITemplate property template. This is similar to the property version<br />
definition for non-log properties, except that we supply a well log name,<br />
which will be used to create a new global well log.<br />
Instead of ITemplate, we can supply IUnitMeasurement. In that case, the<br />
first template found in the catalog that fits that unit measurement will be<br />
used (several templates are used for the same unit measurement, yet one<br />
4 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
General Concepts<br />
would like to specify the property version as precisely as possible, so using<br />
IUnitMeasurement is not recommended).<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.UI:<br />
using Slb.<strong>Ocean</strong>.Units;<br />
IPropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
// Use IUnitMeasurement to define<br />
PropertyVersion<br />
IUnitServiceSettings uss;<br />
uss =<br />
CoreSystem.GetService(<br />
);<br />
IUnitMeasurement um;<br />
um =<br />
uss.CurrentCatalog.GetUnitMeasurement("Poros<br />
ity");<br />
// Create PropertyVersion from<br />
UnitMeasurement, log name “NewPorosity”<br />
PropertyVersion pv =<br />
pvs.FindOrCreate("NewPorosity", um);<br />
// It is better to find a template for a<br />
precise definition of<br />
// PropertyVersion<br />
ITemplate temp1 =<br />
pvs.FindTemplate("Porosity");<br />
PropertyVersion pv =<br />
pvs.FindOrCreate("NewPorosity", temp1);<br />
// Better yet, use statics to find template<br />
// to avoid typing mistakes on property names<br />
ITemplate temp =<br />
<strong>Petrel</strong>UnitSystem.TemplateGroupPetrophysical.<br />
Porosity;<br />
PropertyVersion pv =<br />
pvs.FindOrCreate("NewPorosity", temp);<br />
Non-Log Type Property<br />
Versions<br />
This is a simpler case; we find an ITemplate or settle for an<br />
IUnitMeasurement and get the PropertyVersion with the FindOrCreate<br />
method.<br />
We only have to specify where to look for property versions to reuse. This is<br />
done by giving a container object where other property versions have been<br />
1: <strong>Access</strong>ing Domain Objects 5<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
instantiated. Property version containers are objects of type Borehole,<br />
BoreholeCollection, MarkerCollection, Surface, and PointSet.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.UI:<br />
using Slb.<strong>Ocean</strong>.Units;<br />
IPropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
// Use IUnitMeasurement to define<br />
PropertyVersion<br />
IUnitServiceSettings uss;<br />
uss = CoreSystem.GetService(<br />
);<br />
IUnitMeasurement um;<br />
um =<br />
uss.CurrentCatalog.GetUnitMeasurement("Poros<br />
ity");<br />
// Create PropertyVersion from a container<br />
and the UnitMeasurement<br />
Borehole bh = ...;<br />
PropertyVersion pv = pvs.FindOrCreate(bh,<br />
um);<br />
// Use a template for a precise definition of<br />
PropertyVersion<br />
ITemplate temp =<br />
<strong>Petrel</strong>UnitSystem.TemplateGroupPetrophysical.<br />
Porosity;<br />
PropertyVersion pv = pvs.FindOrCreate(bh,<br />
temp);<br />
Dictionary Property<br />
Versions<br />
Dictionary property versions are similar to property versions but define<br />
presentation templates for enumerated property types such as facies codes.<br />
Similar methods are used from IDictionaryPropertyVersionService<br />
object referenced by <strong>Petrel</strong>System. We can also create log or non-log<br />
dictionary property versions. The code sample shows versioning on a non-log<br />
property.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.UI:<br />
6 <strong>Ocean</strong> Application Development Framework 2007.1<br />
Borehole bh = ...;<br />
IDictionaryPropertyVersionService dpvs;<br />
dpvs =<br />
<strong>Petrel</strong>System.DictionaryPropertyVersionServic<br />
e;<br />
IDictionaryTemplate dtemp;<br />
dtemp =<br />
<strong>Petrel</strong>UnitSystem.TemplateGroupFacies.Fluvial<br />
Facies;<br />
DictionaryPropertyVersion dpv =<br />
dpvs.FindOrCreate(bh, dtemp);<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
General Concepts<br />
Domain<br />
A log is a possibly sparse collection of samples (property values) along the<br />
wellbore. The point in the well path where the sample has been measured can<br />
be expressed in different ways, in TVDSS (true vertical depth below sea<br />
level), TVD, MD (measured depth), TWT (two-way time), etc. Domain is a<br />
class that exposes the various domains via public static read-only properties.<br />
These static properties are used to switch index on the log. This is done by<br />
the Borehole.Transform method.<br />
public sealed class Domain<br />
{<br />
public static Domain AZIMUTH;<br />
public static Domain CALENDAR_TIME;<br />
public static Domain ELEVATION_DEPTH;<br />
public static Domain ELEVATION_TIME;<br />
public static Domain INCLINATION;<br />
public static Domain INDEX;<br />
public static Domain MD;<br />
public static Domain MD_MSL;<br />
public static Domain OWT;<br />
public static Domain TST;<br />
public static Domain TVD;<br />
public static Domain TVD_KB;<br />
public static Domain TVT;<br />
public static Domain TWT;<br />
public static Domain X;<br />
public static Domain Y;<br />
}<br />
Project<br />
Project is a class that gets instantiated to describe the primary project open<br />
at run time. It contains global settings for the project like elevation time,<br />
depth references, and the default coordinate system.<br />
It also lets the <strong>Ocean</strong> Module create a new Collection (a folder in the<br />
Input data tree residing at the root) or a new ModelCollection (similar<br />
folder in the Models data tree).<br />
public sealed class Project<br />
{<br />
public string Name { get; };<br />
public ICoordSys CoordinateSystem { get; set; }<br />
public double ReferenceElevationDepth { get; };<br />
public IEnumerable Collections { get; };<br />
...<br />
public Collection CreateCollection (string name);<br />
public Collection CreateModelCollection (string name);<br />
}<br />
Extensions<br />
IExtensions is an interface to add or remove custom domain objects to or<br />
from native <strong>Petrel</strong> domain objects. Extensions can only contain objects that<br />
are custom domain objects, that is, those that are not native <strong>Petrel</strong> objects.<br />
Extensions cannot be used to modify the class relationships in the <strong>Petrel</strong> data<br />
model.<br />
1: <strong>Access</strong>ing Domain Objects 7<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
When an object is added with the IExtensions.Add method, it will appear<br />
in the data tree under the IExtensionsSource type object that had the<br />
Extensions property to start with. Once inserted in the data tree, the<br />
custom domain object can be selected, displayed, and have a custom context<br />
menu.<br />
The IExtensions interface can also be used to find the objects inserted to<br />
extend the data model.<br />
The use of extensions is discussed in Volume 3, Chapter 12, "UI and<br />
Visualization" on page 707.<br />
8 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
<strong>Data</strong> <strong>Access</strong> Patterns<br />
This section describes the basic patterns of data access in <strong>Ocean</strong> <strong>Petrel</strong>, covering<br />
the relevant portions of Domain Object Hosting from the perspective of an<br />
application developer using the native <strong>Petrel</strong> domain objects. The native <strong>Petrel</strong><br />
domain objects API is tailored to the individual domain object and discussed in<br />
detail in the following sections, beginning with Well Domain. See Volume 2,<br />
Chapter 6, "<strong>Access</strong>ing Domain Objects" on page 341.<br />
Domain Objects<br />
Domain Objects provide a high-level way to access your data. They are often<br />
called business objects because they represent concepts that are visible to the<br />
end-user. An important point with Domain Objects is that they are<br />
implemented as C# classes. The application code is type safe; object<br />
properties and types are known and checked by the compiler.<br />
Domain objects contain business logic. Typically, they act as intermediaries<br />
between a persistent data store and application code. They map from the<br />
data store representation to a format required (and desired) by applications.<br />
Their object model is often quite different from the model used by the<br />
underlying data store. They hide database implementation details and<br />
differences between database vendors from the application. Domain Objects<br />
do not require persistence; it is possible to have memory objects that contain<br />
business logic but are not able to save their state to a persistent data store.<br />
application<br />
Domain<br />
Object<br />
Domain<br />
Object<br />
database<br />
file<br />
persisten<br />
Domain<br />
Object<br />
in memory<br />
Fig. 1-2<br />
Domain Object<br />
<strong>Data</strong> in <strong>Petrel</strong> is accessed via hosted domain objects; they are hosted domain<br />
objects because their implementations utilize the <strong>Ocean</strong> Domain Object<br />
Hosting (DOH) patterns, interfaces, and classes. All native <strong>Petrel</strong> domain<br />
objects are hosted domain objects, as opposed to custom domain objects,<br />
which may or may not use the DOH hosting model. As a user of the native<br />
<strong>Petrel</strong> domain objects, the complexity of the DOH implementation is hidden<br />
from you. There are only a few general topics of which you should be aware,<br />
and they are as follows:<br />
• Common interfaces<br />
1: <strong>Access</strong>ing Domain Objects 9<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
• Transactions<br />
• <strong>Data</strong> store<br />
• Events<br />
Since <strong>Petrel</strong> is a single-threaded application, all access to the native <strong>Petrel</strong><br />
domain objects must take place on the main thread. Otherwise, you will<br />
receive the following run-time error.<br />
System.InvalidOperationException was unhandled<br />
Message="Cross-thread operation not valid: Application accessed<br />
domain object from a thread other than the main thread."<br />
Source="Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject"<br />
Common Patterns<br />
and Interfaces<br />
IIdentifiable<br />
There is not a general create, read, update, delete API for manipulating native<br />
<strong>Petrel</strong> domain objects, though there are some common patterns and<br />
interfaces. The API depends on the individual native <strong>Petrel</strong> domain object.<br />
The common patterns that native <strong>Petrel</strong> domain objects follow are:<br />
• Native <strong>Petrel</strong> domain objects are created by calling a create method on<br />
the parent container; there are no public constructors on native <strong>Petrel</strong><br />
domain objects.<br />
• Native <strong>Petrel</strong> domain objects are deleted by calling a delete method on<br />
the object; many native <strong>Petrel</strong> domain objects have this functionality.<br />
<strong>Petrel</strong>System.PrimaryProject returns you the Project native <strong>Petrel</strong><br />
domain object, which is the starting point for navigating through the domain<br />
model.<br />
Many native <strong>Petrel</strong> domain objects implement the following interfaces:<br />
• IIdentifiable<br />
• IDescriptionSource<br />
• IDomainObject<br />
• INotifyingOnChanged<br />
• INotifyingOnDeleted<br />
Many native <strong>Petrel</strong> domain objects have the following properties:<br />
• NullObject<br />
• LastModified<br />
Native <strong>Petrel</strong> domain objects that implement<br />
Slb.<strong>Ocean</strong>.Core.IIdentifiable have a durable identity via a Droid. A<br />
Droid is a Durable Runtime Object Identifier, which is a reference to an<br />
actual domain object. A Droid eliminates the need to hold on to the object<br />
itself. See Volume 1, Chapter 2, "The <strong>Ocean</strong> Core" on page 60 for a more<br />
detailed discussion of Droids.<br />
public interface IIdentifiable<br />
{<br />
Droid Droid;<br />
}<br />
10 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
When persisting a custom domain object that has a reference to an<br />
identifiable native <strong>Petrel</strong> domain object, save only the Droid of the native<br />
<strong>Petrel</strong> domain object. When the custom domain object is "re-hydrated," the<br />
native <strong>Petrel</strong> domain object can be recreated from the Droid using the<br />
Resolve functionality of the <strong>Data</strong>Manager or I<strong>Data</strong>SourceManager after<br />
the Workspace has become available.<br />
IDescriptionSource<br />
Native <strong>Petrel</strong> domain objects that implement<br />
Slb.<strong>Ocean</strong>.Core.IDescriptionSource can give out a name, a short<br />
description, and a long description via the IDescription interface. See<br />
Volume 1, Chapter 2, "The <strong>Ocean</strong> Core" on page 67.<br />
public interface IDescriptionSource<br />
{<br />
IDescription Description { get; }<br />
}<br />
public interface IDescription<br />
{<br />
string Name { get; }<br />
string ShortDescription { get; }<br />
string Description { get; }<br />
}<br />
IDomainObject<br />
Many native <strong>Petrel</strong> domain objects implement the<br />
Slb.<strong>Ocean</strong>.<strong>Data</strong>.Hosting.IDomainObject interface.<br />
public interface IDomainObject<br />
{<br />
public I<strong>Data</strong>Source <strong>Data</strong>Source { get; }<br />
public bool IsGood { get; }<br />
}<br />
You can access the data source in which the domain object is persisted.<br />
IsGood gets a value indicating whether or not the domain object is deleted<br />
from its data source. This method can be slow, so use it with care. Note that<br />
a NullObject is never good. Trying to access a property of a "bad" object<br />
will probably throw an exception.<br />
INotifyingOnChanged<br />
Many native <strong>Petrel</strong> domain objects implement the<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.INotifyingOnChanged interface in<br />
order to notify clients when they have changed. The Changed event is raised<br />
when the domain object has changed, but not when children are added or<br />
removed. See Volume 2, Chapter 1, "" on page 15.<br />
public interface INotifyingOnChanged<br />
{<br />
event EventHandler Changed;<br />
}<br />
INotifyingOnDeleted<br />
Many native <strong>Petrel</strong> domain objects implement the<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.INotifyingOnDeleted interface in<br />
order to notify clients when they have changed. The Deleted event is<br />
1: <strong>Access</strong>ing Domain Objects 11<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
raised when the domain object has been deleted, but not when children are<br />
added or removed. See Volume 2, Chapter 1, "" on page 15.<br />
public interface INotifyingOnDeleted<br />
{<br />
event EventHandler Deleted;<br />
}<br />
NullObject<br />
DOH uses the null object design pattern for hosted domain objects. This<br />
means that you will never receive a null when asking for a hosted domain<br />
object; you will receive a special null instance of the same type instead. This<br />
is an example showing the Surface NullObject property.<br />
public class Surface : ...<br />
{<br />
public static Surface NullObject { get; }<br />
...<br />
}<br />
To test if a marker is part of a surface, compare its Surface property with<br />
the Surface.NullObject instead of comparing its Surface property with<br />
a null.<br />
Marker myMarker = ...;<br />
if (myMarker.Surface == Surface.NullObject)<br />
MessageBox.Show(“Marker is not part of a<br />
surface”);<br />
LastModified<br />
All native <strong>Petrel</strong> domain objects have a LastModified property that returns<br />
a LastModificationInfo structure. This structure supplies the time and<br />
date of the last modification as well as the name of the user who caused the<br />
modification.<br />
public struct LastModificationInfo<br />
{<br />
public DateTime Time { get; }<br />
public string UserName { get; }<br />
...<br />
}<br />
To access information about when a native <strong>Petrel</strong> domain object was last<br />
modified, use its LastModified property.<br />
Surface mySurface = ...;<br />
string name =<br />
mySurface.LastModified.UserName;<br />
DateTime t = mySurface.LastModified.Time;<br />
Transactions<br />
A transaction represents a group of edits on some data (domain objects).<br />
Transactions are required for operations on native <strong>Petrel</strong> domain objects that<br />
will change the data: create, update, and delete.<br />
12 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
To use a transaction complete the following steps.<br />
1. Create a transaction.<br />
2. Lock domain objects that are going to be changed.<br />
3. Modify the domain objects.<br />
4. Commit the transaction.<br />
5. Dispose of the created transaction.<br />
In <strong>Petrel</strong>, transactions are used to batch event notifications. <strong>Petrel</strong> does not<br />
use a database, so changes to domain objects made within a transaction<br />
happen immediately.<br />
Transactions use the Slb.<strong>Ocean</strong>.Core.ITransaction interface.<br />
public interface ITransaction: IDisposable<br />
{<br />
void Commit();<br />
void Lock(params object[ ] objects);<br />
void LockCollection(IEnumerable<br />
objectCollection);<br />
...<br />
}<br />
Transactions can be created two ways. The first and most common way is<br />
through static convenience functions on the static convenience class<br />
Slb.<strong>Ocean</strong>.Core.<strong>Data</strong>Manager.<br />
public static class <strong>Data</strong>Manager<br />
{<br />
public static ITransaction<br />
NewTransaction();<br />
public static ITransaction<br />
NewTransaction(object context);<br />
public static ITransactionManager<br />
TransactionManager { get; }<br />
...<br />
}<br />
It is also possible to create a transaction via the<br />
Slb.<strong>Ocean</strong>.Core.ITransactionManager interface, available from the<br />
<strong>Data</strong>Manager.<br />
public interface ITransactionManager<br />
{<br />
ITransaction NewTransaction(object<br />
context);<br />
...<br />
}<br />
1: <strong>Access</strong>ing Domain Objects 13<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Since the transaction must be disposed when it is no longer needed, it is<br />
usually wrapped in a using statement.<br />
ITransaction trans;<br />
using (trans = <strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(...);<br />
}<br />
// create or update data<br />
trans.Commit();<br />
...<br />
The rules for locking domain objects in <strong>Petrel</strong> are as follows.<br />
• Objects that are being updated or deleted must be locked first. If it is<br />
not locked and an attempt is made to modify the object, then a<br />
TransactionException will be thrown.<br />
• When an object is going to be created as the child of another object,<br />
then the parent object must be locked before the creation.<br />
• When an object is created inside a transaction it is implicitly locked. For<br />
example, if you create an object and then want to modify its name, you<br />
do not have to lock it. The lock you placed on its parent before the<br />
creation carries through to the new child.<br />
Note that all of the functionality of ITransaction is not supported in the<br />
<strong>Ocean</strong> 2006 native <strong>Petrel</strong> domain objects. TryLock and<br />
TryLockCollection have no meaning in <strong>Petrel</strong>; all locks will always<br />
succeed. Abandon is not supported; there is no rollback. As a practical<br />
matter, this means that omitting the call to Commit is currently the same as<br />
calling Commit, assuming that you have the transaction in a using block.<br />
Events will still be fired because the changes were already made to the data.<br />
The locked objects will also be unlocked.<br />
<strong>Data</strong> Source<br />
<strong>Petrel</strong> persists its data including hosted domain objects to a file, called the<br />
project file, via serialization. The native <strong>Petrel</strong> domain objects that<br />
implement IDomainObject allow you to access the data source via<br />
Slb.<strong>Ocean</strong>.Core.I<strong>Data</strong>Source. The <strong>Petrel</strong> data source is also available<br />
from the Slb.<strong>Ocean</strong>.Core.IWorkspace interface.<br />
public interface IWorkspace :<br />
IDescriptionSource<br />
{<br />
I<strong>Data</strong>Source DefaultSource { get; }<br />
...<br />
}<br />
14 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
The data source allows the application developer to resolve Droids after the<br />
workspace is available and determine if the data store is dirty.<br />
public interface I<strong>Data</strong>Source: IDroidResolver<br />
{<br />
bool CanResolve(Droid droid);<br />
object Resolve(Droid droid);<br />
}<br />
bool IsDirty { get; }<br />
event EventHandler IsDirtyChanged;<br />
...<br />
IDroidResolver.CanResolve(droid)==true<br />
is equivalent to:<br />
IDroidResolver.Resolve(droid)!=null<br />
There is a known bug that the <strong>Petrel</strong> data source always reports that the data<br />
source is clean: IsDirty always returns false.<br />
As a convenience, Resolve is also available as a static function on the static<br />
convenience class <strong>Data</strong>Manager. Again, Resolve can only be called after<br />
the workspace has become available.<br />
public static class <strong>Data</strong>Manager<br />
{<br />
public static object Resolve(Droid droid);<br />
...<br />
}<br />
Domain Object<br />
Events<br />
Many native <strong>Petrel</strong> domain objects implement INotifyingOnChanged and<br />
INotifyingOnDeleted and thus publish Changed and Deleted events to<br />
allow an application to monitor lifecycle activity. Specific domain objects<br />
may publish more specific events. For example, Logs publishes<br />
DictionaryWellLogsChanged, MultiTraceWellLogsChanged, and<br />
WellLogsChanged events.<br />
Many native <strong>Petrel</strong> domain objects that are collections or that contain<br />
collections also publish specialized events for those collection changes. For<br />
example, BoreholeCollection publishes two collection changed events<br />
1: <strong>Access</strong>ing Domain Objects 15<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
since it is a collection and contains collections: BoreholesChanged and<br />
BoreholesCollectionsChanged.<br />
Domain Object Hosting<br />
Well<br />
name = “C7”<br />
Changed<br />
Well<br />
name = “B4”<br />
Changed<br />
Fig. 1-3<br />
Changed Event<br />
Subscribe to the events on a domain object instance.<br />
BoreholeCollection bhc = ...;<br />
bhc.Changed +=<br />
new EventHandler(bhcChanged);<br />
bhc.Deleted +=<br />
new EventHandler(bhcDeleted);<br />
bhc.BoreholesChanged +=<br />
new EventHandler<br />
(bhcBoreholesChanged);<br />
bhc.BoreholeCollectionsChanged += is<br />
new EventHandler<br />
<br />
(bhcCollectionsChanged);<br />
Unsubscribe from events also in the standard .NET way. This example shows<br />
the shorter form available in .NET 2.0.<br />
bhc.Changed -= bhcChanged;<br />
bhc.Deleted -= bhcDeleted;<br />
bhc.BoreholesChanged -= bhcBoreholesChanged;<br />
bhc.BoreholeCollectionsChanged -= bhcCollectionsChanged;<br />
Event delivery is coordinated with transactions. Events are queued in an<br />
EventService component and broadcast to listeners when the enclosing<br />
transaction is committed or disposed.<br />
Changed Event<br />
The Changed event includes arguments that give the object changed and the<br />
source of the change. The origin of the event source is always <strong>Petrel</strong> itself<br />
(EventOrigin.Internal) since <strong>Petrel</strong> does not support multi-process<br />
16 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
access to the native <strong>Petrel</strong> domain objects. The list of property names that<br />
have been changed is always empty in <strong>Petrel</strong>.<br />
public class DomainObjectChangeEventArgs :<br />
DomainObjectEventArgs<br />
{<br />
public override int GetHashCode();<br />
public string[] GetPropertyNames();<br />
public override string ToString();<br />
public IEnumerable PropertyNames {get; }<br />
public IDomainObject AffectedObject { get; }<br />
public EventSource EventSource { get; }<br />
...<br />
}<br />
The event handler accesses the information it needs from the event<br />
arguments.<br />
void bhChanged(object sender,<br />
DomainObjectChangeEventArgs args)<br />
{<br />
Borehole b = args.AffectedObject as Borehole;<br />
...<br />
}<br />
If a domain object is changed multiple times within the same transaction, for<br />
example more than one property is changed, only one change event will be<br />
raised.<br />
Deleted Event<br />
Similarly, the Deleted event has arguments that give the affected object and<br />
its Droid. The Droid is only valid if the domain object is identifiable<br />
(implements IIdentifiable); otherwise, the Droid is empty and resolves to<br />
null.<br />
public class DomainObjectDeletedEventArgs :<br />
DomainObjectEventArgs<br />
{<br />
public override int GetHashCode();<br />
public Droid Droid { get; }<br />
public IDomainObject AffectedObject { get; }<br />
public EventSource EventSource { get; }<br />
...<br />
}<br />
The event handler accesses the information it needs from the event<br />
arguments.<br />
void bhDeleted(object sender,<br />
DomainObjectDeletedEventArgs args)<br />
{<br />
Borehole b = args.AffectedObject as Borehole;<br />
Droid d = args.Droid;<br />
...<br />
}<br />
1: <strong>Access</strong>ing Domain Objects 17<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
If a domain object is changed and deleted within the same transaction, only<br />
the delete event will be raised; no change events will be raised in this<br />
scenario.<br />
Collection Changed<br />
Events<br />
There could not be a generic collection changed event since the collection<br />
changing applies to both domain objects that are collections and domain<br />
objects that contain collections. While the events are specialized, they all<br />
follow a pattern in their use of the same argument class. The collection<br />
change events all have arguments that give what change happened, the list of<br />
added objects, and the list of removed objects.<br />
public class DomainObjectCollectionChangeEventArgs :<br />
HostingEventArgs where T = class;<br />
{<br />
public override int GetHashCode();<br />
public IEnumerable AddedObjects { get; }<br />
public IEnumerable RemovedObjects { get; }<br />
public DomainObjectCollectionChangeTypes ChangeType { get; }<br />
...<br />
}<br />
public enum DomainObjectCollectionChangeTypes<br />
{<br />
None = 0,<br />
Add = 1,<br />
Remove = 2<br />
}<br />
A change type of None means one of the following:<br />
• No changes.<br />
• Collection was reshuffled.<br />
• Added and removed objects are unknown.<br />
The event handler accesses the information it needs from the event<br />
arguments.<br />
void bhcBoreholesChanged(object sender,<br />
DomainObjectCollectionChangeEventArgs args)<br />
{<br />
if (args.ChangeType == DomainObjectCollectionChangeTypes.Add)<br />
{<br />
foreach (Borehole bh in args.AddedObjects)<br />
...<br />
}<br />
...<br />
}<br />
<strong>Data</strong> Mining<br />
<strong>Ocean</strong> 2007 for <strong>Petrel</strong> carries the concept of <strong>Data</strong> Mining; for example, it<br />
supports browsing through the project to retrieve collections of domain<br />
objects of a given type.<br />
As we have seen in the previous section, there is no general pattern for<br />
object listing or creation. This is done through collections that represent the<br />
natural data organization.<br />
18 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
The <strong>Petrel</strong> project organization is hierarchical. It breaks collections in<br />
meaningful sub-collections so that the user is not faced with long list<br />
searches. There is no SQL-type query support in <strong>Petrel</strong>. When searching for<br />
data in a project, the user has to recursively search through nested<br />
collections. The API follows the same paradigm.<br />
The following example searches the Input data tree for Borehole objects<br />
with a given characteristic (they must contain a gamma ray log). This is done<br />
by traversing the nested hierarchy of BoreholeCollection instances. The<br />
example avoids recursion.<br />
public static void ListGRBoreholes ()<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("List all Boreholes with GR logs");<br />
WellRoot wr = WellRoot.Get(<strong>Petrel</strong>Project.PrimaryProject);<br />
BoreholeCollection col = wr.BoreholeCollection;<br />
if (col == BoreholeCollection.NullObject)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Project has no Borehole");<br />
return;<br />
}<br />
// Traverse the global well logs for "Gamma ray"<br />
PropertyVersion glob = PropertyVersion.NullObject;<br />
ITemplate temp = <strong>Petrel</strong>UnitSystem.TemplateGroupLogTypes.GammaRay;<br />
foreach (PropertyVersion pv in wr.WellLogVersions)<br />
{<br />
if (<strong>Petrel</strong>UnitSystem.FindTemplateForPropertyVersion(pv) == temp)<br />
{<br />
glob = pv;<br />
break;<br />
}<br />
}<br />
if (glob == PropertyVersion.NullObject)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Project contains no GR log");<br />
return;<br />
}<br />
1: <strong>Access</strong>ing Domain Objects 19<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
List bhcCol = new List();<br />
List bhCol = new List();<br />
bhcCol.Add(col);<br />
// Traverse the Borehole folders (simulate tail recursion)<br />
int i = 0;<br />
while (i < bhcCol.Count)<br />
{<br />
col = bhcCol[i];<br />
bhCol.AddRange(col);<br />
bhcCol.AddRange(col.BoreholeCollections);<br />
i++;<br />
}<br />
// Now traverse the Boreholes<br />
foreach (Borehole bh in bhCol)<br />
{<br />
foreach (WellLog log in bh.Logs.WellLogs)<br />
{<br />
IWellLogVersion lpv = log.WellLogVersion;<br />
ITemplate logTemp;<br />
logTemp = <strong>Petrel</strong>UnitSystem.FindTemplateForPropertyVersion(lpv);<br />
if (logTemp == <strong>Petrel</strong>UnitSystem.TemplateGroupLogTypes.GammaRay)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Borehole " + bh.Name);<br />
break;<br />
<strong>Data</strong> Queries<br />
Domain Partitioning<br />
The <strong>Petrel</strong> data domain is organized in different hierarchies; access (and data<br />
mining) is done following the <strong>Petrel</strong> organization.<br />
The data trees found in a <strong>Petrel</strong> project are as follows.<br />
• Input<br />
• Models<br />
• Results<br />
• Cases<br />
In a <strong>Petrel</strong> project, there are no cross-domain relationships, although<br />
domains are crossed by applications. For instance, building a pillar grid may<br />
use a Shape.Surface object, originally computed from a<br />
Seismic.HorizonInterpretation instance to produce a<br />
PillarGrid.Horizon instance. Yet these instances will end up in unrelated<br />
data trees.<br />
The links between different representations of a Horizon are factual.<br />
• They overlap geographically and in geological time.<br />
• They carry the same name (enforced by grid construction).<br />
• The user knows which seismic horizon was used to create a given level<br />
in the pillar grid.<br />
The API, to retrieve such data, has to search data trees individually.<br />
20 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
Navigation<br />
Root objects<br />
Although trees are unrelated, data navigation is allowed in the same domain<br />
in specific cases as when there are data dependencies (deleting a Borehole<br />
will delete all the WellMarker instances that it contains). These cases are<br />
• markers to boreholes<br />
• interpretation subsets to seismic surveys where picks originated<br />
The API properties implementing these relationships are exposed in the<br />
relevant data chapters.<br />
All data queries will be done via root objects. Querying in a <strong>Petrel</strong> project is<br />
done by traversing the tree of containers where this type of object can be<br />
found. The search starts at the root.<br />
The root objects that let the API search the data trees in the project are as<br />
follows.<br />
• WellRoot<br />
• SeismicRoot (SeismicProject)<br />
• PillarGridRoot<br />
• AnalysisRoot and SimulationRoot<br />
The individual data trees are accessible from root objects, directly accessible<br />
from static methods by supplying the Project instance (referenced by<br />
<strong>Petrel</strong>Project.PrimaryProject).<br />
In an empty project, data trees have not been created yet. However, these<br />
root objects exist by default.<br />
<strong>Data</strong> queries start with root objects in the various trees that are displayed in<br />
<strong>Petrel</strong>.<br />
Root objects relate to specific data trees displayed in the <strong>Petrel</strong> project.<br />
1: <strong>Access</strong>ing Domain Objects 21<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Input<br />
Fig. 1-4<br />
Input tree showing seismic data and interpretation objects<br />
Input shows all data imported or generated prior to inclusion in a <strong>Petrel</strong><br />
structural model (pillar grid).<br />
• Boreholes and Trajectories<br />
• Stratigraphy Columns with Markers<br />
• Seismic <strong>Data</strong><br />
• Seismic Interpretation<br />
• Shapes<br />
• Unnested individual data<br />
Well and stratigraphy data in the input tree is accessible from an instance of<br />
the WellRoot class, returned in the class itself by a static Get method.<br />
Boreholes are leaves in a tree of nested collections all rooted under a main<br />
folder. This top-level folder is accessible from the WellRoot object.<br />
Note that the top level folder does not exist in an empty project or in one<br />
that has not been loaded with any well data. In that case the<br />
22 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
BoreholeCollection exposed in WellRoot is a NullObject, but a new<br />
folder is created when the method GetOrCreateBoreholeCollection is<br />
invoked.<br />
Stratigraphy is arranged in non-nested overlapping classifications, showing as<br />
individual stratigraphy folders. All these folders, represented by<br />
MarkerCollection instances can be retrieved from the WellRoot object.<br />
public sealed class WellRoot : ...<br />
{<br />
public static WellRoot Get(Project project);<br />
public BoreholeCollection GetOrCreateBoreholeCollection();<br />
}<br />
}<br />
public BoreholeCollection BoreholeCollection { get; }<br />
public IEnumerable MarkerCollections { get; }<br />
public IEnumerable WellLogVersions { get; }<br />
public IEnumerable<br />
DictionaryWellLogVersions { get; }<br />
public IEnumerable CheckShotVersions { get; }<br />
public IEnumerable PointWellLogVersions { get;<br />
...<br />
Seismic datasets, like boreholes, are arranged in a tree of nested<br />
SeismicCollection instances. The top level collection is found under the<br />
SeismicProject, which is only created if the project is loaded with seismic<br />
data.<br />
public sealed class SeismicRoot : ...<br />
{<br />
public static SeismicRoot Get(Project project);<br />
public SeismicProject CreateSeismicProject();<br />
public SeismicProject GetOrCreateSeismicProject();<br />
}<br />
public SeismicProject SeismicProject { get; }<br />
public IEnumerable<br />
InterpretationCollections { get; }<br />
...<br />
Unlike borehole folders, the SeismicProject object commands both<br />
datasets and interpretation hierarchies, stressing the creation rule for<br />
interpretation sets. Seismic interpretation cannot be created unless the<br />
1: <strong>Access</strong>ing Domain Objects 23<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
SeismicProject has been created (enforcing the concept that a dataset is<br />
required before interpretation can be created).<br />
public sealed class SeismicProject : ...<br />
{<br />
public InterpretationCollection<br />
CreateInterpretationCollection(string name);<br />
public SeismicCollection CreateSeismicCollection(string name);<br />
public Wavelet CreateWavelet(string name);<br />
...<br />
public string Name { get; }<br />
public IEnumerable<br />
InterpretationCollections { get; }<br />
public IEnumerable SeismicCollections { get;<br />
}<br />
public IEnumerable Wavelets { get; }<br />
}<br />
Other objects, such as Shape domain instances are found in simple<br />
Collection folders. These folders are nested and the roots of folder trees<br />
are listed directly under the Project instance.<br />
public sealed class Project : ...<br />
{<br />
public IEnumerable Collections { get; }<br />
...<br />
}<br />
public sealed class Collection : ...<br />
{<br />
public Collection CreateCollection(string name);<br />
public PointSet CreatePointSet(string name);<br />
public PolylineSet CreatePolylineSet(string name);<br />
public RegularHeightFieldSurface<br />
CreateRegularHeightFieldSurface(string name,<br />
LatticeInfo lattice);<br />
public Wavelet CreateWavelet(string name);<br />
}<br />
// indexer for collection members<br />
public IDomainObject this[int index] { get; set; }<br />
public int Count { get; } // number of objects in the collection<br />
// sub-collections<br />
public IEnumerable Collections { get; }<br />
public string Name { get; set; }<br />
// members<br />
public IEnumerable PointSets { get; }<br />
public IEnumerable PolylineSets { get; }<br />
public IEnumerable<br />
RegularHeightFieldSurfaces { get; }<br />
public IEnumerable Wavelets { get; }<br />
...<br />
Details on the tree contents are exposed in the following data chapters.<br />
24 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
Models tree<br />
Fig. 1-5<br />
Model tree showing objects under pillar grid expanded<br />
Models tree displays reservoir models. Models of the same type are gathered<br />
in collections. Models contain a number of structural elements like pillar<br />
grids, their faults and horizon surfaces, and the 3D properties that assign<br />
values to each cell in the grid. Pillar grids contain both property distributions<br />
and fault face properties, all contained in the model.<br />
Collections of models are accessed directly under the project.<br />
Pillar grid models are the only models that are exposed in the <strong>Ocean</strong> API, so<br />
they are treated as a special case. The unique PillarGridRoot object<br />
1: <strong>Access</strong>ing Domain Objects 25<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
returns pillar grid models, filtered or not, by their model collection<br />
containers.<br />
public sealed class Project : ...<br />
{<br />
public IEnumerable ModelCollections { get; }<br />
...<br />
}<br />
public sealed class PillarGridRoot : ...<br />
{<br />
public static PillarGridRoot Get(Project project);<br />
public int GetGridCount(ModelCollection modelCollection);<br />
public IEnumerable GetGrids(ModelCollection<br />
modelCollection);<br />
}<br />
public int GridCount { get; }<br />
public IEnumerable Grids { get; }<br />
Collections of models are represented by ModelCollection instances.<br />
Members of these collections are models of different types (the only type<br />
that is exposed in the <strong>Ocean</strong> API at the moment is the Grid type, which<br />
represents the pillar grid). The model collections are not nested.<br />
public sealed class ModelCollection : ...<br />
{<br />
public int ModelCount { get; }<br />
public IEnumerable Models { get; }<br />
public string Name { get; set; }<br />
...<br />
}<br />
Properties under the pillar grid are organized in a nested collection tree. The<br />
root of that tree is unique and stored in the Grid class member<br />
PropertyCollection. New sub-collections can be created at any level of<br />
the property tree, but the root remains unique.<br />
public sealed class PropertyCollection : ...<br />
{<br />
public Grid Grid { get; }<br />
public int PropertyCount { get; }<br />
public IEnumerable Properties { get; }<br />
public string Name { get; set; }<br />
// nesting<br />
public PropertyCollection ParentPropertyCollection { get; }<br />
public int PropertyCollectionCount { get; }<br />
public IEnumerable PropertyCollections {<br />
get; }<br />
}<br />
// create sub-collection<br />
public PropertyCollection CreatePropertyCollection(string name);<br />
public Property CreateProperty(PropertyVersion propertyVersion);<br />
26 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
Results and Cases<br />
tree<br />
Fig. 1-6<br />
<strong>Petrel</strong> Results and Cases tree data<br />
Cases display data analysis runs and reservoir simulation runs. Cases are<br />
enumerated by the AnalysisRoot instance. This AnalysisRoot object is<br />
unique and retrieved from a class static method by supplying the Project<br />
instance.<br />
Results show simulation result categories and result time series. Results are<br />
also accessed from the AnalysisRoot instance.<br />
public sealed class AnalysisRoot : ...<br />
{<br />
public static AnalysisRoot Get(Project project);<br />
}<br />
public int CaseAnalysisCount { get; }<br />
public IEnumerable CaseAnalyses { get; }<br />
public int CaseCount { get; }<br />
public IEnumerable Cases { get; }<br />
public int ResultCategoryCount { get; }<br />
public IEnumerable ResultCategories { get; }<br />
public int ResultPropertyCount { get; }<br />
public IEnumerable ResultProperties { get; }<br />
1: <strong>Access</strong>ing Domain Objects 27<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Simulation cases are specific case runs loaded in the <strong>Petrel</strong> project from a<br />
simulation run executed with a number of alternative simulator products.<br />
These cases originated in simulators are accessible via the SimulationRoot<br />
instance.<br />
public sealed class SimulationRoot : ...<br />
{<br />
public static SimulationRoot Get(Project project);<br />
}<br />
public int SimulationCount { get; }<br />
public IEnumerable Simulations { get; }<br />
<strong>Data</strong> Creation<br />
Creation of <strong>Petrel</strong> data domain objects is not separated from their placement<br />
in the project data trees. This is intentional and the API does not provide<br />
constructors for domain object classes.<br />
The placement in the project of newly created <strong>Petrel</strong> data domain objects<br />
follows the data tree organization that we have seen for data browsing.<br />
Methods to create specific domain object types are found in the class that<br />
naturally contains that domain object and never in the class that represents<br />
the object itself.<br />
These parent classes can be collection types or entities that possess<br />
collection-type members.<br />
Creation rules<br />
Creation of data domain objects in a <strong>Petrel</strong> project is governed by a number<br />
of unwritten rules.<br />
• Entity types are part of a collection. For instance, a Borehole is found in<br />
a BoreholeCollection.<br />
• Most collections are nested. This is inherent to the purely hierarchical<br />
nature of the <strong>Petrel</strong> data organization. Nesting lets the user organize the<br />
data tree so that a reasonable number of branches appear at any node.<br />
Therefore, creation will often include branching out in the tree by<br />
creating a new sub-collection.<br />
• There is no single data tree organization but a number of root objects<br />
that define the top level of each data tree. As we have seen earlier, data<br />
trees do not cross domains and single domains have several data trees.<br />
• Root folders may be created via the API, if needed.<br />
Parent types<br />
The classes that provide data creation can be characterized as follows.<br />
• The project class, to add elements at the top of the data trees<br />
• Root classes, specifically provided to create new data trees<br />
• Collection classes, allowing member creation and sometimes creation of<br />
sub-collections<br />
28 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
• Entity classes, generally providing methods to create properties<br />
Creating new<br />
hierarchies<br />
This is done with the Project class or root classes.<br />
From the project, we can create generic collections (that will contain newly<br />
created Shapes domain objects) and model collections (that will appear at<br />
the top level of the Models tree).<br />
The project also allows creation of Wavelet and PointSet objects that will<br />
appear outside of all data folders.<br />
public sealed class Project : ...<br />
{<br />
...<br />
public Collection CreateCollection(string name);<br />
public ModelCollection CreateModelCollection(string name);<br />
public PointSet CreatePointSet(string name);<br />
public Wavelet CreateWavelet(string name);<br />
}<br />
WellRoot provides creation methods for marker collections (stratigraphy<br />
columns) and borehole collections. Only the top level borehole collection can<br />
be created at that level, and it will fail if that unique folder object exists. A<br />
safer GetorCreate method is also provided.<br />
public sealed class WellRoot : ...<br />
{<br />
public BoreholeCollection CreateBoreholeCollection();<br />
public BoreholeCollection GetOrCreateBoreholeCollection();<br />
public MarkerCollection CreateMarkerCollection(string name);<br />
...<br />
}<br />
SeismicProject can create seismic collections (2D or 3D seismic surveys)<br />
and interpretation collections.<br />
public sealed class SeismicProject : ...<br />
{<br />
public InterpretationCollection<br />
CreateInterpretationCollection(string name);<br />
public SeismicCollection CreateSeismicCollection(string name);<br />
public Wavelet CreateWavelet(string name);<br />
...<br />
}<br />
Other root objects are used for browsing but do not provide creation<br />
methods.<br />
Expanding trees<br />
Most collection types allow creation of objects and sub-collections, although<br />
not all of them (ModelCollection is read-only as new pillar grids cannot yet<br />
be created). Collections able to create objects consist of the following.<br />
• BoreholeCollection in the well domain<br />
1: <strong>Access</strong>ing Domain Objects 29<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
• SeismicCollection and InterpretationCollection in the<br />
seismic domain<br />
• Collection in the shapes domain<br />
• PropertyCollection in the pillar grid domain<br />
All these collection types have similar creation methods. They are fully<br />
detailed in the online manual. We will just list one here,<br />
BoreholeCollection.<br />
public sealed class BoreholeCollection : ...<br />
{<br />
// children of type collection<br />
public int BoreholeCollectionCount { get; }<br />
public IEnumerable BoreholeCollections {<br />
get; }<br />
// parent<br />
public BoreholeCollection ParentBoreholeCollection { get; }<br />
}<br />
// create a sub-collection<br />
public BoreholeCollection Create(string name);<br />
// children of type element<br />
public int Count { get; }<br />
public IEnumerator GetEnumerator();<br />
// create a new element in the collection<br />
public Borehole CreateBorehole(string name);<br />
Non-collection types also sometimes provide creation methods to add<br />
structural parts to the entity defined. Such classes are as follows:<br />
• MarkerCollection in the well domain<br />
• HorizonInterpretation in the seismic domain<br />
• StreamlineSet and StreamlineSubset in the simulation domain<br />
These methods are specific to the classes concerned. We show one here as an<br />
example, MarkerCollection.<br />
public sealed class MarkerCollection : ...<br />
{<br />
public Fault CreateFault();<br />
public Horizon CreateFirstHorizon();<br />
public Interface CreateInterface();<br />
public void CreateZoneAndHorizonAbove(Horizon horizon,<br />
out Zone newZone, out Horizon newHorizon);<br />
public void CreateZoneAndHorizonAbove(Zone zone,<br />
out Zone newZone, out Horizon newHorizon);<br />
public void CreateZoneAndHorizonBelow(Horizon horizon,<br />
out Zone newZone, out Horizon newHorizon);<br />
public void CreateZoneAndHorizonBelow(Zone zone,<br />
out Zone newZone, out Horizon newHorizon);<br />
public void CreateZoneAndHorizonIn(Zone zone,<br />
out Zone newZone1, out Horizon newHorizon, out Zone newZone2);<br />
...<br />
}<br />
30 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
<strong>Data</strong> <strong>Access</strong> Patterns<br />
Creating properties<br />
In all domains, property classes contain scalar values and are called properties<br />
or index values in a related classification and are then called dictionary<br />
properties. The creation of a property or a dictionary property is similar and<br />
takes either a property version or a dictionary property version as argument.<br />
Entity classes that provide creation methods for contained properties are the<br />
following.<br />
• Logs and MarkerCollection in the well domain<br />
• RegularHeightFieldSurface, Surface, and PointSet in the<br />
shapes domain<br />
• PropertyCollection in the pillar grid domain<br />
• StreamlineSet in the simulation domain<br />
All property owner types have the same type of interface, and we will just<br />
show one here as an example, the Logs class.<br />
public sealed class Logs : ...<br />
{<br />
public WellLog CreateWellLog(PropertyVersion version);<br />
public DictionaryWellLog<br />
CreateDictionaryWellLog(DictionaryPropertyVersion version);<br />
...<br />
}<br />
1: <strong>Access</strong>ing Domain Objects 31<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
<strong>Data</strong> Deletion<br />
Fig. 1-7<br />
Popup menu showing Delete... item<br />
<strong>Data</strong> deletion is invoked from the object to be deleted. Most domain objects<br />
provide a Delete() method. The action performed is equivalent to the<br />
delete menu entry in the corresponding object's context menu in the <strong>Petrel</strong><br />
data tree.<br />
The method signature is the same across domains. We show the Borehole<br />
class here as an example.<br />
public sealed class Borehole : ...<br />
{<br />
public void Delete();<br />
...<br />
}<br />
32 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Settings Information <strong>Access</strong><br />
Settings Information <strong>Access</strong><br />
You can access settings information for native <strong>Petrel</strong> domain objects,<br />
including history and statistics.<br />
Fig. 1-8<br />
Settings Dialog<br />
Each type of settings information has a different set of interfaces to use to<br />
access it.<br />
Table 1-1 Settings Info Interfaces<br />
Information<br />
General<br />
settings<br />
Settings<br />
Dialog Tab<br />
Info<br />
Interfaces<br />
ISettingsInfo,<br />
ISettingsInfoFactory<br />
History Info IHistoryInfo,<br />
IHistoryInfoFactory<br />
Statistics Statistics Statistics, IStatisticsFactory,<br />
StatisticsService<br />
1: <strong>Access</strong>ing Domain Objects 33<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
General Settings<br />
All native <strong>Petrel</strong> domain objects have basic information in the settings dialog<br />
Info tab. These include name, color, object type, and comments.<br />
Fig. 1-9<br />
General Information in Info Tab<br />
You can access these settings via ISettingsInfo.<br />
public interface ISettingsInfo :<br />
IPresentation<br />
{<br />
string Text { get; set; }<br />
Color Color { get; set; }<br />
Bitmap TypeImage { get; }<br />
string TypeName { get; }<br />
string Comment { get; set; }<br />
bool IsReadOnly { get; }<br />
}<br />
public interface IPresentation<br />
{<br />
Bitmap Image { get; }<br />
string Text { get; }<br />
}<br />
34 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Settings Information <strong>Access</strong><br />
Get the settings and presentation interfaces via their factories.<br />
public interface ISettingsInfoFactory<br />
{<br />
ISettingsInfo GetSettingsInfo(object o);<br />
}<br />
public interface IPresentationFactory<br />
{<br />
IPresentation GetPresentation(object o);<br />
}<br />
<strong>Access</strong> the presentation and settings information using the following pattern.<br />
1. Get the appropriate factory service, I*Factory.<br />
2. Get the interface for the specific <strong>Petrel</strong> object, I*.<br />
3. Get information from the interface properties.<br />
SeismicCube c = ...;<br />
ISettingsInfoFactory sif;<br />
sif =<br />
CoreSystem.GetService(<br />
c);<br />
ISettingsInfo info = sif.GetSettingsInfo(c);<br />
Image i = info.Image;<br />
Color color = info.Color;<br />
String comment = info.Comment;<br />
Fig. 1-10 Settings Sample Output<br />
History<br />
Native <strong>Petrel</strong> domain objects typically have a history of actions performed on<br />
them.<br />
Fig. 1-11 Seismic Cube History<br />
1: <strong>Access</strong>ing Domain Objects 35<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Each history entry is an immutable record of a data change event about the<br />
domain object. These entries are part of the audit trail of an object.<br />
public sealed class HistoryEntry<br />
{<br />
HistoryEntry(DateTime begin, DateTime end,<br />
string user,string operation,<br />
string args, string Version);<br />
HistoryEntry(string operation, string args,<br />
string Version);<br />
string AppVersion { get; }<br />
string Arguments { get; }<br />
string Operation { get; }<br />
string UserName { get; }<br />
DateTime BeginDate { get; }<br />
DateTime EndDate { get; }<br />
}<br />
Some domain objects may also give you the ability to add a new history entry<br />
via IHistoryInfoEditor.<br />
public interface IHistoryInfoEditor<br />
{<br />
void AddHistoryEntry(HistoryEntry he);<br />
bool UpdateLastHistoryEntry(string userName, string operation,<br />
string arguments, stringappVersion);<br />
}<br />
This interface also allows you to update end time of the last history entry, in<br />
order to support long-running edit operations.<br />
Use the convenience class HistoryService to get the list of history entries<br />
and the history info editor, if supported.<br />
public interface HistoryService<br />
{<br />
public static IEnumerable GetHistory( object o);<br />
public static IHistoryInfoEditor GetHistoryInfoEditor(object o);<br />
}<br />
Get the list of history entries and then access the needed information from<br />
each history entry.<br />
SeismicCube c = ...;<br />
string h;<br />
foreach (HistoryEntry he in<br />
HistoryService.GetHistory(c))<br />
{<br />
h = h + he.BeginDate.ToString() + "-" +<br />
he.EndDate.ToString() + " "<br />
+ he.UserName + " " + he.Operation +<br />
" "<br />
+ he.Arguments + " " + he.AppVersion<br />
+ "\r\n";<br />
}<br />
36 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Settings Information <strong>Access</strong><br />
Fig. 1-12 Seismic Cube History Sample Output<br />
Typically, the oldest history entry is first in the list.<br />
Statistics<br />
Native <strong>Petrel</strong> domain objects typically have statistics, which are displayed in<br />
the Statistics tab of the settings dialog.<br />
Fig. 1-13 Seismic Cube Statistics<br />
Statistics are a collection of common information about the object. There<br />
are two different collections. One is axis information and the other is a set<br />
1: <strong>Access</strong>ing Domain Objects 37<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
of attribute key value pairs. These are available from the Statistics class.<br />
public sealed class Statistics<br />
{<br />
Statistics(IEnumerable axisInfo,<br />
IEnumerableattributes);<br />
IEnumerable AxisInfo { get; }<br />
IEnumerable Attributes { get; }<br />
}<br />
The contents of Statistics are immutable, and the contents of the class<br />
are a copy at the calling time.<br />
An AxisInfoItem describes spatial information about the domain object<br />
relevant for a particular axis. These are either related to a domain or to a<br />
dictionary/property version.<br />
public abstract class AxisInfoItem<br />
{<br />
AxisInfoItem(string name, double min, double max);<br />
double Max { get; }<br />
double Min { get; }<br />
string Name { get; }<br />
virtual double Delta { get; }<br />
abstract IUnitMeasurement UnitMeasurement { get; }<br />
}<br />
You use the convenience class StatisticsService to get statistics and also<br />
to determine if statistics are available for a given type.<br />
public class StatisticsService<br />
{<br />
static Statistics GetStatistics( object o);<br />
static bool CanGetStatistics(Type type);<br />
}<br />
Get the statistics from StatisticsService and then access the axis and<br />
attribute information needed.<br />
string attrs, ai;<br />
SeismicCube c = ...;<br />
Statistics stat =<br />
StatisticsService.GetStatistics(cube);<br />
foreach (AxisInfoItem item in stat.AxisInfo)<br />
ai = ai + item.Name + " Min " +<br />
item.Min.ToString()<br />
+ " Max " + item.Max.ToString() +<br />
"\r\n";<br />
foreach (KeyValuePair kvp in<br />
stat.Attributes)<br />
attrs = attrs + kvp.Key + " " + kvp.Value +<br />
"\r\n";<br />
38 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Settings Information <strong>Access</strong><br />
Fig. 1-14 Statistics Axis Info Sample Output<br />
Fig. 1-15 Statistics Attributes Sample Output<br />
1: <strong>Access</strong>ing Domain Objects 39<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Active Object <strong>Access</strong><br />
Active objects in <strong>Petrel</strong> set the scope for interactive operations. They are<br />
displayed in boldface in the <strong>Petrel</strong> explorer trees.<br />
Fig. 1-16 Active <strong>Petrel</strong> Objects<br />
Active objects are based on the type of object. At any given time, there can<br />
be multiple active objects of different types, as shown above. There is no<br />
more than one active object of a particular type.<br />
Whether there is an active object of a particular type depends on the type.<br />
Some types must always have an active object, like the Grid. Other types do<br />
not require an active object, like HorizonInterpretation.<br />
40 <strong>Ocean</strong> Application Development Framework 2007.1<br />
Fig. 1-17 No Active Horizon Interpretations<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Active Object <strong>Access</strong><br />
You can access active objects via IActiveObjectService. This service<br />
allows you to query for and retrieve active objects.<br />
public interface IActiveObjectService<br />
{<br />
bool CanGetActiveObject();<br />
IActiveObject GetActiveObject();<br />
bool CanGetActiveObjectInContainer();<br />
IActiveObjectInContainer<br />
GetActiveObjectInContainer();<br />
...<br />
}<br />
You can manipulate active objects using IActiveObject. This interface<br />
allows you to query, check the state, change the state, and retrieve the active<br />
object.<br />
public interface IActiveObject<br />
{<br />
bool IsActive( TDomainObject o);<br />
bool CanActivate( TDomainObject o);<br />
bool CanDeactivate(TDomainObject o);<br />
}<br />
void Activate( TDomainObject o);<br />
void Deactivate(TDomainObject o);<br />
TDomainObject GetObject();<br />
...<br />
The first step is to get an active object for a given type, and then you can<br />
manipulate it. Note that the ActiveObjectService is available from the<br />
<strong>Petrel</strong>System convenience class.<br />
IActiveObject activeGrid;<br />
activeGrid = <strong>Petrel</strong>System.ActiveObjectService.GetActiveObject();<br />
Grid g = activeGrid.GetObject();<br />
Grid g1 = FindFirstGridInProject();<br />
activeGrid.Activate(g1);<br />
// following will throw exception; must always be an active grid<br />
activeGrid.Deactivate(g1);<br />
Grid g2 = FindSecondGridInProject();<br />
// will activate g2 and deactivate g1<br />
activeGrid.Activate(g2);<br />
1: <strong>Access</strong>ing Domain Objects 41<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
You may also be able to find an active object in a particular container via<br />
IActiveObjectInContainer.<br />
public interface<br />
IActiveObjectInContainer<br />
{<br />
bool IsActive( TDomainObject o);<br />
bool CanActivate( TDomainObject o);<br />
bool CanDeactivate(TDomainObject o);<br />
}<br />
void Activate( TDomainObject o);<br />
void Deactivate(TDomainObject o);<br />
TDomainObject GetObject(object container);<br />
...<br />
You will need to check if the “in container” functionality is supported; it is<br />
not available for all types.<br />
IActiveObjectService activeOS = <strong>Petrel</strong>System.ActiveObjectService;<br />
if(activeOS.CanGetActiveObjectInContainer())<br />
{<br />
IActiveObjectInContainer activeGridIC;<br />
activeGridIC = activeOS.GetActiveObjectInContainer();<br />
object container = ...;<br />
Grid g = activeGridIC.GetObject(container);<br />
}<br />
You can also register for events when the active object changes for a specific<br />
object type, either for activation or deactivation. This functionality is<br />
available from IActiveObject.<br />
public interface IActiveObject<br />
{<br />
...<br />
event<br />
EventHandler Activated;<br />
EventHandlerDeactivated;<br />
}<br />
Use the specialized event argument class to get the affected object or the<br />
container.<br />
public class ActivationChangeEventArgs : EventArgs<br />
{<br />
TDomainObject AffectedObject { get; }<br />
object Container { get; }<br />
ActivationChangeEventArgs(TDomainObject affectedObj,object<br />
container);<br />
}<br />
42 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Active Object <strong>Access</strong><br />
You can register for activation or deactivation events or both.<br />
IActiveObject activeGrid;<br />
activeGrid = <strong>Petrel</strong>System.ActiveObjectService.GetActiveObject();<br />
activeGrid.Activated += gridActivated;<br />
...<br />
void gridActivated(object s, ActivationChangeEventArgs args)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Activated: " +<br />
args.AffectedObject.Name);<br />
}<br />
1: <strong>Access</strong>ing Domain Objects 43<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
44 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
2 Borehole and Geology<br />
In This Chapter<br />
Well Domain ................................................................................................46<br />
Stratigraphy .................................................................................................67<br />
2: Borehole and Geology 45<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Well Domain<br />
The well domain deals with all the physical entities that are encountered in<br />
and around the wellbore during the exploration and production activities.<br />
surface location<br />
kelly bushing<br />
(MD reference datum)<br />
mean sea level<br />
(subsea reference datum)<br />
borehole<br />
well log<br />
well marker<br />
trajectory<br />
Fig. 2-1<br />
Well Presentation in <strong>Ocean</strong><br />
Borehole<br />
A borehole is materialized by a trajectory that starts somewhere near the<br />
earth’s surface and traverses the reservoir. It is used to produce reservoir<br />
fluids and also to measure precisely the petrophysical properties of the<br />
reservoir along the borehole path.<br />
46 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
The Borehole class and its associated classes are shown below:<br />
Marker<br />
*<br />
WellLog<br />
1<br />
* 1<br />
*<br />
1<br />
DictionaryWellLog<br />
*<br />
WellRoot<br />
Borehole<br />
1<br />
1<br />
1<br />
1<br />
BoreholeCollection<br />
Logs<br />
1<br />
1<br />
Trajectory<br />
MultitraceWellLog<br />
*<br />
*<br />
PointWellLog<br />
Fig. 2-2<br />
Well Domain Classes<br />
Borehole Collections<br />
Finding Boreholes in<br />
the Project<br />
The data model of <strong>Petrel</strong> is organized in a hierarchical manner. It is therefore<br />
essential for the user that boreholes be grouped in collections. In the <strong>Petrel</strong><br />
data domain, borehole collections do not carry specific semantic meaning;<br />
they can represent a field, but they can also group boreholes by completion<br />
type (injectors vs. producers). The collection is merely a folder and the<br />
organization lies with the user.<br />
The borehole collections themselves represent a complex hierarchy. Borehole<br />
collections can contain other collections. There is only one borehole<br />
collection at the root, the main “Wells” folder. That collection is accessible<br />
from the API as a static property of the BoreholeCollection class.<br />
<strong>Ocean</strong> provides a convenience class, WellRoot, which provides navigation to<br />
domain objects in the well domain including BoreholeCollection and<br />
MarkerCollection objects. BoreholeCollection collections may be<br />
nested while MarkerCollection collections may not. All Borehole objects<br />
exist under a BoreholeCollection, which is an IEnumerable<br />
generic enumerable type. The collection can be traversed with a simple<br />
foreach loop. To retrieve boreholes contained in nested collections, we have<br />
2: Borehole and Geology 47<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
to call the sub-collections recursively. This example traverses the whole<br />
borehole set of a project:<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
{<br />
...<br />
}<br />
// depth-first search traversal<br />
// of the Borehole collection hierarchy<br />
WellRoot wr = WellRoot.Get( <strong>Petrel</strong>Project.PrimaryProject );<br />
BoreholeCollection bhc = wr.BoreholeCollection;<br />
// check that the top Borehole collection is valid<br />
// empty projects will have a top collection set to null<br />
if (bhc.IsGood)<br />
{<br />
PrintBoreholes(bhc);<br />
}<br />
return;<br />
private void PrintBoreholes(BoreholeCollection bhc)<br />
{<br />
int count = 0;<br />
}<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(bhc.Name + " Collection :");<br />
foreach (Borehole bh in bhc)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Borehole " + bh.Name);<br />
count++;<br />
}<br />
if (count == 0)<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Empty");<br />
if (bhc.BoreholeCollectionCount > 0)<br />
{<br />
foreach (BoreholeCollection bc in bhc.BoreholeCollections)<br />
PrintBoreholes(bhc);<br />
}<br />
return;<br />
The Borehole search returns a complete list of Boreholes with their folders.<br />
Fig. 2-3<br />
Listing Boreholes in the Project<br />
48 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
Wellbore Identifier<br />
A well or wellbore requires multiple candidate identifiers, usually referred to<br />
as aliases. While a single identifier for a well and wellbore may be sufficient<br />
within an organization or business function, the general Exploration &<br />
Production community must accommodate alternate names using various<br />
naming conventions. Some examples of the business requirements for<br />
multiple identifiers follow.<br />
With oil companies operating wells in many different regions of the world,<br />
the process of naming wells and keeping track of them in a database requires<br />
more complexity to handle the majority of cases.<br />
The unique names assigned by a company to its well prospects are different<br />
than the names derived for wells on a lease tract or connected to an offshore<br />
platform, which are in turn different from the unique names administered by<br />
a regulatory agency, data vendor, or industry standards organization.<br />
Many companies arrive at their own unique system of naming wells, while<br />
others adhere to some standard like American Petroleum Institute (API).<br />
Many times, multiple identifiers must be kept for each well to deal with the<br />
complexity. For example, an American-based oil company may use the API<br />
designation for wells, but when they begin exploration in Mexico, they will<br />
not be able to store their well data in the database by API number. Likewise,<br />
they may store by well name, so they could drill a well on the Smith lease and<br />
call it the Trilobite #1 Smith, but if they drill another well a few years later<br />
on a different Smith lease, it could also have the name Trilobite #1 Smith.<br />
The <strong>Ocean</strong> Borehole class identifies itself by Name. Naming is free of<br />
constraints, which is common usage in the industry. For keeping standard<br />
naming with the Project, we have a UWI property in the class.<br />
Public sealed class Borehole : IDomainObject,<br />
IDescriptionSource,...<br />
{<br />
public string UWI { get; set; }<br />
}<br />
Creating a New<br />
Borehole<br />
Creation of a new Borehole is done from the BoreholeCollection that<br />
is to contain the resulting borehole. Borehole creation is kept simple. The<br />
borehole does not have a trajectory yet. This is added later. The borehole<br />
needs a surface location, stored in the WellHead property and a<br />
2: Borehole and Geology 49<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
KellyBushing elevation (from sea level) to mark the origin of all measured<br />
depth references in the borehole.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
WellRoot wr = WellRoot.Get( <strong>Petrel</strong>Project.PrimaryProject );<br />
BoreholeCollection bhc = wr.BoreholeCollection;<br />
if (! Bhc.IsGood) return;<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(bhc);<br />
string wellName = "AField1";<br />
Borehole bh = bhc.CreateBorehole(wellName);<br />
bh.UWI = "WELL001";<br />
bh.WellHead = new Point2 (1500.0, 1500.0);<br />
bh.KellyBushing = 200.0;<br />
}<br />
// Add Trajectory<br />
...<br />
Trajectory<br />
Trajectory is a class that contains the definition of a borehole path,<br />
sometimes referred to as a deviation survey. It contains all of the geometrical<br />
elements of the borehole path through the reservoir.<br />
A Trajectory is a collection of trajectory records contained in<br />
TrajectoryRecord structures. The TrajectoryRecord structure has<br />
measured depth, azimuth, and inclination measurements. These are the raw<br />
records measured in the field from which the trajectory is computed. When a<br />
Borehole is created, the array of TrajectoryRecord structures may be<br />
appended after the WellHead and KellyBushing properties of the<br />
Borehole object have been set.<br />
public struct TrajectoryRecord<br />
{<br />
public TrajectoryRecord ( double md, double inclination,<br />
double azimuth );<br />
}<br />
public double MD { get; }<br />
public double Inclination { get; }<br />
public double Azimuth { get; }<br />
An alternative method of creating a borehole trajectory is to use the<br />
Trajectory class AppendPolyline method to derive the<br />
TrajectoryRecord values from a Polyline3 object.<br />
50 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
The following code sample shows both methods of creating a borehole<br />
trajectory for the Borehole object in the previous sample.<br />
Borehole bh = ...;<br />
// Create array of TrajectoryRecords<br />
TrajectoryRecord[] traj = new TrajectoryRecord[numPts];<br />
// Fill array values.<br />
for (int i = 0; i < numPts; i++)<br />
{<br />
double md = ...;<br />
double inclindation = ...;<br />
double azimuth = ...;<br />
traj[i] = new TrajectoryRecord( md, inclination, azimuth );<br />
}<br />
// Append trajectory records to trajectory<br />
bh.Trajectory.Append( traj );<br />
Borehole bh2 = ...;<br />
IPolyline3 polyline = ...;<br />
bh2.Trajectory.AppendPolyline( polyline );<br />
We can access the Trajectory points for a Borehole by processing the<br />
TrajectoryRecord points in the Trajectory.<br />
// Printing the Trajectory to the Message log window<br />
Trajectory traj = bh.Trajectory;<br />
int i = 0;<br />
foreach (TrajectoryRecord tr in traj.Records)<br />
{<br />
i++;<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Record no " + i +<br />
tr.Azimuth + ", " +<br />
tr.Inclination + ", at " +<br />
tr.MD);<br />
}<br />
Domain Transforms<br />
Points along the trajectory have to be retrieved in measured depth or in X, Y,<br />
Z to tie to other objects in the model. The Borehole API provides a<br />
2: Borehole and Geology 51<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
transform method to retrieve trajectory points in all the domains listed under<br />
the Domain class.<br />
double MD = 0.0, X, Y, maxMD =<br />
bh.MDRange.Max;<br />
int index = 0;<br />
while (MD < maxMD)<br />
{<br />
MD = bh.Transform(Domain.INDEX,<br />
(double)index, Domain.MD);<br />
X = bh.Transform(Domain.INDEX,<br />
(double)index, Domain.X);<br />
Y = bh.Transform(Domain.INDEX,<br />
(double)index, Domain.Y);<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("coordinates<br />
at " + MD +<br />
" : X = " + X + " Y = " + Y);<br />
index++;<br />
}<br />
Fig. 2-4<br />
Borehole Transforms<br />
Checkshot Survey<br />
<strong>Ocean</strong> provides access to time-to-depth relationship data through the<br />
CheckShot domain object. A CheckShot object has a set of<br />
52 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
WellLogSample objects that contain measured depth and two-way time<br />
values. The CheckShot class is defined as:<br />
public sealed class CheckShot : FacadeConvenience, IDomainObject,<br />
IDescriptionSource,<br />
IPropertyVersionContainer,<br />
IIdentifiable<br />
{<br />
public IEnumerable BooleanProperties { get; }<br />
public int BooleanPropertyCount { get; }<br />
public Borehole Borehole { get; }<br />
public IDescription Description { get; }<br />
public int DictionaryPropertyVersionCount { get; }<br />
public IEnumerable<br />
DictionaryPropertyVersions { get; }<br />
public Droid Droid { get; }<br />
public override LastModifiedInfo LastModified { get; }<br />
public static CheckShot NullObject { get; }<br />
public int PropertyVersionCount { get; }<br />
public IEnumerable PropertyVersions { get; }<br />
public int SampleCount { get; }<br />
public IEnumerable Samples { get; set; }<br />
public IEnumerable StringProperties { get; }<br />
public int StringPropertyCount { get; }<br />
public static int UndefinedDictionaryPropertyValue { get; }<br />
public PropertyVersion WellLogVersion { get; }<br />
public WellLogSample this[int index] { get; }<br />
public void Append ( IEnumerable stream );<br />
public void CreateBooleanProperty ( string name );<br />
public DictionaryPropertyVersion CreateDictionaryProperty (<br />
DictionaryPropertyVersion property );<br />
public PropertyVersion CreateProperty ( PropertyVersion property<br />
);<br />
public void CreateStringProperty ( string name );<br />
public void Delete ( );<br />
public bool GetBooleanProperty ( string property, double md );<br />
public bool GetBooleanProperty ( string property,<br />
WellLogSample atSample );<br />
2: Borehole and Geology 53<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
public int GetDictionaryProperty (<br />
DictionaryPropertyVersion property, double md );<br />
public int GetDictionaryProperty (<br />
DictionaryPropertyVersion property,<br />
WellLogSample atSample );<br />
public double GetProperty ( PropertyVersion property, double md<br />
);<br />
public double GetProperty ( PropertyVersion property,<br />
WellLogSample atSample );<br />
public string GetStringProperty ( string property, double md );<br />
public string GetStringProperty ( string property,<br />
WellLogSample atSample );<br />
public static bool IsUndefinedDictionaryPropertyValue ( int<br />
value );<br />
public bool IsWritableBooleanProperty ( string property,<br />
double md );<br />
public bool IsWritableBooleanProperty ( string property,<br />
WellLogSample atSample<br />
);<br />
public bool IsWritableDictionaryProperty (<br />
DictionaryPropertyVersion property, double md );<br />
public bool IsWritableDictionaryProperty (<br />
DictionaryPropertyVersion property,<br />
WellLogSample atSample );<br />
public bool IsWritableProperty ( PropertyVersion property,<br />
double md );<br />
public bool IsWritableProperty ( PropertyVersion property,<br />
WellLogSample atSample );<br />
public bool IsWritableStringProperty ( string property, double<br />
md );<br />
public bool IsWritableStringProperty ( string property,<br />
WellLogSample atSample );<br />
public void SetBooleanProperty ( string property, double md,<br />
bool propertyValue );<br />
public void SetBooleanProperty ( string property,<br />
WellLogSample atSample,<br />
bool propertyValue );<br />
public void SetDictionaryProperty (<br />
DictionaryPropertyVersion property,<br />
double md, int propertyValue );<br />
public void SetDictionaryProperty (<br />
DictionaryPropertyVersion property,<br />
WellLogSample atSample, int propertyValue );<br />
public void SetProperty ( PropertyVersion property, double md,<br />
double propertyValue );<br />
public void SetProperty ( PropertyVersion property,<br />
WellLogSample atSample,<br />
double propertyValue );<br />
public void SetStringProperty ( string property, double md,<br />
string propertyValue );<br />
public void SetStringProperty ( string property,<br />
WellLogSample atSample,<br />
string propertyValue );<br />
)<br />
54 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
Creating Checkshot<br />
Surveys<br />
CheckShot domain objects are created by first creating a PropertyVersion<br />
for the checkshot. The property version is created under the root object for<br />
the wells, which must be locked in transaction.<br />
Using (Itransaction tr = <strong>Data</strong>Manager.NewTransaction())<br />
{<br />
// Find root of all wells<br />
WellRoot wr = WellRoot.Get( <strong>Petrel</strong>System.PrimaryProject );<br />
// lock the root object<br />
tr.Lock( wr );<br />
// create the property version for the checkshot<br />
PropertyVersion pv = wr.CreateCheckShotVersion( "CheckShot" );<br />
...<br />
Next the PropertyVersion is used to create the CheckShot using the<br />
CreateCheckShot method under the Logs class. At this point the borehole<br />
must be locked since we are creating a new object under it.<br />
...<br />
// Get borehole from user<br />
Borehole borehole = ...;<br />
// Lock the borehole<br />
tr.Lock( borehole );<br />
// Create the checkshot using the property version created earlier<br />
CheckShot cs = borehole.Logs.CreateCheckShot( pv );<br />
...<br />
The data for the CheckShot object is contained in an array of<br />
WellLogSample structures. The WellLogSample structure is also used to<br />
contain well logs for the borehole; therefore, the checkshot resembles a log<br />
curve in its construction. The WellLogSample struct is defined as:<br />
public struct WellLogSample<br />
{<br />
public WellLogSample ( double md, float val );<br />
}<br />
[UnitMeasurement( "Standard_Depth_Index" )]<br />
public double MD { get; }<br />
public float Value { get; }<br />
WellLogSample uses a UnitMeasurement attribute to define the units for<br />
the MD value. The MD, or Measured Depth, value is the distance from the<br />
surface location of the borehole of the point represented by this instance of<br />
the WellLogSample struct.<br />
The array of WellLogSample structs must have their MD values in ascending<br />
order and may not have duplicate MD values. The entire array of structs must<br />
be created and set for the CheckShot at once. You cannot insert individual<br />
entries into the CheckShot Samples property.<br />
2: Borehole and Geology 55<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Once the Samples property for the CheckShot has been set, the borehole<br />
may activate the CheckShot for use. When the CheckShot is activated, it<br />
will become the preferred depth to time relationship for the borehole.<br />
}<br />
...<br />
// Create array of WellLogSamples to hold data<br />
WellLogSample[] values = new WellLogSamples[ numberOfSamples ];<br />
...<br />
// Add the WellLogSample for this depth<br />
values[i] = new WellLogSample( MD_val, time_val );<br />
...<br />
// Set the Samples property to the wellLogSamples created<br />
cs.Samples = values;<br />
// Make this the active depth/time relationship for the borehole<br />
borehole.ActivateCheckShot( cs );<br />
// Commit the property version and checkshot creation<br />
tr.Commit();<br />
<strong>Access</strong>ing Checkshot Surveys<br />
Programatically CheckShot objects are found with the logs for a borehole<br />
under the Logs class (Borehole.Logs.). The corresponding data for the<br />
CheckShot is found under the Global Well Logs in the <strong>Petrel</strong> Explorer Input<br />
data tab. With regard to CheckShot objects, the Logs class definition is as<br />
follows:<br />
public sealed class Logs : IExtensionSource, ILockObjectProvider,<br />
IEventConsumer<br />
{<br />
public int CheckShotCount { get; }<br />
public IEnumerable CheckShots { get; }<br />
public int CheckShotVersionCount { get; }<br />
public IEnumerable CheckShotVersions { get; }<br />
public event<br />
EventHandler<br />
CheckShotsChanged;<br />
public CheckShot CreateCheckShot(PropertyVersion<br />
CheckShotVersion);<br />
...<br />
}<br />
56 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
Given a borehole, we can find its checkshot surveys and the data they<br />
contain by doing the following:<br />
...<br />
Borehole bh = ...;<br />
// Indicate number of checkshot surveys the borehole contains.<br />
<strong>Petrel</strong>Logger.InfoOutputWindow ("Borehole contains " +<br />
bh.Logs.CheckShotCount +<br />
" checkshot surveys.");<br />
// Process each checkshot<br />
foreach (CheckShot cs in bh.Logs.CheckShots)<br />
{<br />
// Indicate number of data samples in the checkshot<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Checkshot contains " +<br />
cs.SampleCount +<br />
" samples.");<br />
}<br />
// Process each sample.<br />
foreach (WellLogSample wls in cs.Samples)<br />
{<br />
...<br />
}<br />
Adding Properties to Checkshot Surveys<br />
CheckShot objects may be extended by properties. The properties added to<br />
a CheckShot can contain float, int, bool, or string values. The float<br />
and int properties are normal <strong>Ocean</strong> Property or DictionaryProperty<br />
objects that require a PropertyVersion or DictionaryPropertyVersion<br />
to create. The bool and string property types require only a name when<br />
created. In all cases the property values are placed at the MD values defined by<br />
the CheckShot Samples. You cannot place property values at locations<br />
other than the defined MD values.<br />
The data for the CheckShot must be added to the CheckShot before any<br />
property can be added. Adding the WellLogSample data for the CheckShot<br />
is shown in the earlier example code. With the data in place the desired<br />
2: Borehole and Geology 57<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
property can be created. Here is an example adding a Boolean property to a<br />
CheckShot.<br />
CheckShot chk = ...;<br />
Borehole borehole = chk.Borehole;<br />
// Name for our property. Indicates inclinations < 30 degrees<br />
string sIncl = "Inclination < 30";<br />
// some needed constants<br />
const int PI_DEGREES = 180;<br />
const int LIMITING_ANGLE = 30;<br />
using (ITransaction tr = <strong>Data</strong>Manager.NewTransaction() )<br />
{<br />
tr.Lock(chk);<br />
// Create the property using the name.<br />
chk.CreateBooleanProperty( sIncl );<br />
// Process each checkshot sample<br />
foreach ( WellLogSample sample in chk.Samples)<br />
{<br />
}<br />
// Get the inclination of the point at the MD<br />
double incl = borehole.Transform( Domain.MD, sample.MD,<br />
Domain.INCLINATION );<br />
// Compute the value in degrees<br />
double angle = ( PI_DEGREES * incl ) / Math.PI;<br />
// Check the value and set the property at this MD.<br />
if ( angle < LIMITING_ANGLE )<br />
{<br />
chk.SetBooleanProperty( sIncl, sample.MD, true );<br />
}<br />
else<br />
{<br />
chk.SetBooleanProperty( sIncl, sample.MD, false);<br />
}<br />
tr.Commit();<br />
}<br />
There is no limit to the number of properties that may be added to a<br />
CheckShot object. The API provides a count of each type available and an<br />
enumerable property. A set of "Get" methods provides access to the<br />
58 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
property data. Here is a sample that reads the Boolean property created<br />
above.<br />
CheckShot chk = ...;<br />
// Name of property used when it was created.<br />
string sIncl = "Inclination < 30";<br />
// Check the count of boolean properties<br />
if ( chk.BooleanPropertyCount >= 1)<br />
{<br />
// Look for the name of our property<br />
foreach ( string sProp in chk.BooleanProperties )<br />
{<br />
// Use the name to see if we have a match<br />
if ( sProp.Equals( sIncl ) )<br />
{<br />
// Process the samples.<br />
foreach ( WellLogSample sample in chk.Samples )<br />
{<br />
// Check sample. If inclination < 30, it should be true.<br />
if ( chk.GetBooleanProperty( sIncl, sample ) == true )<br />
{<br />
...<br />
}<br />
else<br />
{<br />
...<br />
}<br />
break;<br />
}<br />
}<br />
}<br />
}<br />
2: Borehole and Geology 59<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Borehole Example<br />
Retrieving the Farthest Point along the Path<br />
In this example, a Borehole Trajectory is retrieved and transformed to<br />
the X and Y domains to project the trajectory on the horizontal plane. The<br />
point farthest away from the surface location is returned.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
Borehole bore = ...;<br />
Point2 wHead = bore.WellHead;<br />
Range1 mdr = bore.MDRange;<br />
Domain dIndex = Domain.INDEX;<br />
double maxIndex = bore.Transform(Domain.MD,<br />
mdr.Max, dIndex);<br />
int maxI = (int)maxIndex;<br />
double maxX, maxY;<br />
double dx = 0.0, dy = 0.0, d = 0.0, dist =<br />
0.0;<br />
for (int i = 0; i dist)<br />
{<br />
maxX = Math.Abs(dx);<br />
maxY = Math.Abs(dy);<br />
dist = d;<br />
}<br />
}<br />
Well Completion<br />
Well completions refer to a depth range within a well. They can only be read<br />
via <strong>Ocean</strong>. Each object has a name and a depth range.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
Borehole bh = ...;<br />
int wci = 0;<br />
foreach (WellCompletion wc in bh.Completions)<br />
{<br />
wci++;<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Completion<br />
no " + wci + ": "<br />
+ wc.Name + " from " + wc.StartMD + " to<br />
" + wc.EndMD);<br />
}<br />
Well Log<br />
WellLog contains the property values measured along the borehole path.<br />
They represent the property type object when the borehole is the geometrical<br />
entity, a partial view of the reservoir. WellLogs always refer to a<br />
PropertyVersion.<br />
60 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
DictionaryWellLog<br />
type (facies)<br />
WellLog type<br />
(porosity)<br />
Fig. 2-5<br />
Well Log Types<br />
There are two types of logs (for 1D log arrays):<br />
4. WellLog<br />
5. DictionaryWellLog<br />
WellLog represents a continuous log of Property values that cover a wide<br />
float range. It refers to a PropertyVersion for its template and<br />
classification.<br />
DictionaryWellLog is an array of integer values taken from an enumerated<br />
list of codes. These codes are defined in a DictionaryPropertyVersion.<br />
2: Borehole and Geology 61<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Creating a New WellLog<br />
Creating a new WellLog requires a PropertyVersion, built from an<br />
ILogTemplate (global well log). The global well log must be uniquely<br />
represented in the Borehole where the log is created.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
Borehole bh = ...;<br />
string name = ...;<br />
int numpoints = ...;<br />
double interval = ..., top = ...;<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
IPropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
ILogTemplate glob =<br />
pvs.FindTemplateByMnemonics("Porosity");<br />
PropertyVersion pv =<br />
pvs.FindOrCreate(glob);<br />
trans.Lock(bh);<br />
WellLog log = bh.Logs.CreateWellLog(pv);<br />
// Build a time array to serve as time log<br />
WellLogSample[] tsamples = new<br />
WellLogSample[numpoints];<br />
}<br />
for (int i = 0; i < numpoints; i++)<br />
{<br />
double md = top + i * interval;<br />
float val = ...;<br />
tsamples[i] = new WellLogSample(md, val);<br />
}<br />
log.Samples = tsamples;<br />
trans.Commit();<br />
DictionaryWellLog<br />
DictionaryWellLogs behave exactly like the WellLogs except that they are<br />
filled with integer values and refer to a DictionaryPropertyVersion.<br />
The integer values that fill the log array are entries into a dictionary that<br />
converts these values into codes defined by string values. The codes are<br />
assigned contiguous ordinal numbers so that there is no missing integer value<br />
in the enumeration of codes and the total number of available codes is equal<br />
to the largest valid integer value plus one (counting starts at zero).<br />
New codes may be added by supplying a string value to the AddFacies<br />
method. This new code will be assigned to the next available integer value.<br />
62 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
There is a way to find the Dictionary color table entry and pattern<br />
corresponding to a given code.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
DictionaryWellLog log = ...;<br />
IDictionaryColorTable dct;<br />
DictionaryPropertyVersion pv =<br />
log.DictionaryWellLogVersion;<br />
dct =<br />
<strong>Petrel</strong>System.ColorTableService.GetColorTable<br />
(pv);<br />
foreach (DictionaryWellLogSample dwls in<br />
log.Samples)<br />
{<br />
if (!dwls.IsUndefinedValue())<br />
{<br />
DictionaryColorTableEntry dce =<br />
dct.Transform(dwls.Value);<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(string.Format(<br />
"MD:{0}: {1} [{2}]",<br />
dwls.MD, dce.Name, dce.Color));<br />
}<br />
}<br />
MultiTraceWellLog<br />
MultiTraceWellLog objects are logs where each sample contains an array<br />
of float values. The size of the array is constant throughout the log. Multitrace<br />
logs are used to record borehole electrical images for instance. They are<br />
accessible via the API like any normal log.<br />
Their individual samples are MultiTraceWellLogSample, a struct<br />
composed of a depth member (expressed in measured depth) and a Value<br />
array. It is reasonable to expect that the array of floats has a constant<br />
length throughout the collection of samples; however, this is not required by<br />
the API (or by <strong>Petrel</strong>).<br />
PointWellLog<br />
Fig. 2-6<br />
PointWellLog Domain Object<br />
2: Borehole and Geology 63<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The Well namespace provides the PointWellLog class to allow the user to<br />
place points on a borehole trajectory that are not related to horizons,<br />
surfaces, or faults. The points in a PointWellLog indicate points of interest<br />
for the user. When rendered they are displayed as discrete points not a log<br />
polyline. They may have properties attached to them in the form of string,<br />
Booleans, DictionaryProperty, or Property objects. The PointWellLog<br />
class definition is as follows:<br />
public sealed class PointWellLog : ...<br />
{<br />
...<br />
public Borehole Borehole { get; }<br />
public int SampleCount { get; }<br />
public IEnumerable Samples {<br />
get; set; }<br />
public WellLogSample this[int index] { get;<br />
}<br />
public void Append (<br />
IEnumerable stream );<br />
public int PropertyVersionCount { get; }<br />
public IEnumerable<br />
PropertyVersions { get; }<br />
public PropertyVersion CreateProperty (<br />
PropertyVersion property);<br />
public double GetProperty ( PropertyVersion<br />
property, double md );<br />
public double GetProperty ( PropertyVersion<br />
property,<br />
WellLogSample atSample );<br />
public void SetProperty ( PropertyVersion<br />
property, double md,<br />
double propertyValue );<br />
public void SetProperty ( PropertyVersion<br />
property,<br />
WellLogSample<br />
atSample,<br />
double<br />
propertyValue );<br />
...<br />
}<br />
PointWellLog domain objects are created from the Logs class just like<br />
WellLog and DictionaryWellLog domain objects. They require a property<br />
version, which is created from a string passed to the<br />
64 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Well Domain<br />
WellRoot.CreatePointWellLogVersion method. Here is an example<br />
creating a PointWellLog.<br />
Borehole bh = ...;<br />
Using ITransaction tr =<br />
<strong>Data</strong>Manager.NewTransaction( ))<br />
{<br />
// Lock WellRoot to create the property<br />
version<br />
WellRoot wr = WellRoot.Get(<br />
<strong>Petrel</strong>Project.PrimaryProject );<br />
Tr.Lock( wr );<br />
// Create the property version<br />
PropertyVersion pv =<br />
wr.CreatePointWellLogVersion(“Pt Well Log”);<br />
// Lock the borehole to create the<br />
PointWellLog<br />
Tr.Lock( bh );<br />
// Create the PointWellLog<br />
PointWellLog pwl =<br />
bh.Logs.CreatePointWellLog( pv );<br />
}<br />
...<br />
The data for PointWellLog domain objects is stored in an array of<br />
WellLogSample structures. Each WellLogSample structure contains a<br />
measured depth and a data value. As with WellLog objects, the array of<br />
WellLogSample structures is created and then added in its entirety to the<br />
PointWellLog. The array of structures must have strictly increasing<br />
measured depth values. The code for this might look something like the<br />
following.<br />
...<br />
// Create list of WellLogSample structs<br />
List wls = new<br />
List( );<br />
// Fill in data inside some kind of loop<br />
for (...)<br />
{<br />
md = ...;<br />
// create this entry for list<br />
WellLogSample sample = new<br />
WellLogSample( md, (float)value );<br />
}<br />
// Add sample to list<br />
Wls.Add( sample );<br />
// Add list to PointWellLog<br />
pwl.Samples = wls;<br />
}<br />
...<br />
2: Borehole and Geology 65<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The PointWellLog object, having just been created in the previous sample,<br />
is implicitly locked in this sample.<br />
PointWellLog domain objects may be extended by the addition of<br />
properties. The properties may be in the form of Boolean,<br />
DictionaryPropertyVersion, String, or normal PropertyVersion<br />
types. Multiple properties may be added to a PointWellLog object. Normal<br />
Property and DictionaryProperty additions require a<br />
PropertyVersion or DictionaryPropertyVersion for creation. The<br />
Boolean and String property additions require a name defined by a string.<br />
Here is an example creating a porosity property and a Boolean property for<br />
the PointWellLog.<br />
...<br />
double myValue = ...;<br />
// Get the Property Version service<br />
PropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
// Use the porosity template to create a<br />
property version.<br />
ITemplate pt = pvs.FindTemplate( “Porosity”<br />
);<br />
PropertyVersion pv = pvs.FindOrCreate( null,<br />
pt );<br />
// Create the property under our PointWellLog<br />
PropertyVersion myProperty =<br />
pwl.CreateProperty( pv );<br />
// Assign the property version a name<br />
myProperty.Name = “Porosity”;<br />
// Assign values to the new property<br />
foreach (WellLogSample sample in pwl.Samples)<br />
{<br />
pwl.SetProperty( myProperty, sample,<br />
myValue );<br />
}<br />
...<br />
// Create a Boolean property<br />
pwl.CreateBooleanProperty( “TF_Test” );<br />
// Assign true or false to the property<br />
samples<br />
foreach (WellLogSample sample in pwl.Samples)<br />
{<br />
// Perform some test. If true, then set<br />
property true.<br />
if (...)<br />
{<br />
pwl.SetBooleanProperty( “TF_Test”,<br />
sample.MD, true );<br />
}<br />
else<br />
{<br />
pwl.SetBooleanProperty( “TF_Test”, sample.MD,<br />
false );<br />
}<br />
...<br />
}<br />
66 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Stratigraphy<br />
Stratigraphy<br />
Fig. 2-7<br />
Stratigraphy<br />
The Stratigraphy Domain models all the physical entities used by geologists<br />
to understand the reservoir structure. These objects are related to the<br />
structural domain, but they serve merely as references for well data such as<br />
markers.<br />
Horizons and Zones are named here and tied to the seismic interpretation<br />
later by matching the well markers with the seismic horizons and faults.<br />
The <strong>Ocean</strong> API lets an application build an entire stratigraphic model.<br />
MarkerCollection<br />
Borehole<br />
Marker<br />
Property<br />
Surface<br />
Horizon<br />
Interface<br />
Fault<br />
Fig. 2-8<br />
Stratigraphy Classes<br />
2: Borehole and Geology 67<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Markers<br />
Horizon<br />
A Marker is a point on a well trajectory that is associated with some<br />
recognizable event, such as a geological top pick or a geophysical horizon.<br />
Many different disciplines within the Exploration and Production community<br />
use well markers. We must distinguish between several types of markers,<br />
depending on the type of surface it characterizes.<br />
The well marker is modeled as the intersection of a well trajectory and a<br />
Surface (Horizon, Interface, or Fault). A single surface will have<br />
similar types of well markers across wells. For example, if a fault intersects<br />
multiple well trajectories, it will be characterized with fault markers at the<br />
intersection with the boreholes.<br />
Horizons are part of the stratigraphy folder (MarkerCollection). They can<br />
be created in the Collection and then used to create markers in boreholes.<br />
They only serve as a common reference to all markers, from different<br />
boreholes that observe the same geological event.<br />
Horizons are characterized by type and stored in property<br />
Horizon.HorizonType which returns an enum HorizonType.<br />
public enum HorizonType<br />
{<br />
Unknown,<br />
Conformable,<br />
Erosional,<br />
Base,<br />
Discont<br />
}<br />
Fault<br />
Zone<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well.Fault links together all the<br />
Markers from different Boreholes that mark the presence of the same<br />
Fault.<br />
Fault only carries a Name property. It is created from the<br />
MarkerCollection object, which serves as a container for the stratigraphy<br />
column.<br />
Zone is an element of the stratigraphic layer hierarchy. It has a top and a<br />
bottom Horizon and a Name.<br />
Note that the Name may not be unique and can be changed by the <strong>Petrel</strong> user.<br />
Do not use it as a key.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well<br />
{<br />
public sealed class Zone<br />
{<br />
public Horizon Base { get; }<br />
public string Comments { get;<br />
set; }<br />
public IDescription Description { get; }<br />
public string Name { get; set;<br />
}<br />
public Horizon Top { get; }<br />
}<br />
}<br />
68 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Stratigraphy<br />
Creating a Stratigraphy<br />
Column<br />
The following example creates a new stratigraphy column and corresponding<br />
Markers in a given Borehole.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well;<br />
Borehole bh = ...;<br />
// Create a Stratigraphy column and<br />
corresponding Markers in Borehole<br />
using (ITransaction t =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
// Find root of all wells<br />
WellRoot wr = WellRoot.Get( <strong>Petrel</strong>System.PrimaryProject );<br />
// lock the root object<br />
t.Lock( wr );<br />
// Create a new stratigraphy collection<br />
Project proj =<br />
<strong>Petrel</strong>Project.PrimaryProject;<br />
MarkerCollection mc =<br />
MarkerCollection.Create("Litho", proj);<br />
Zone[] zoneList = new Zone[4];<br />
Horizon[] horList = new Horizon[5];<br />
// Create Base Horizon<br />
horList[1] = mc.CreateFirstHorizon();<br />
horList[1].HorizonType =<br />
HorizonType.Conformable;<br />
horList[1].Name = "Top Pay Zone";<br />
// Create Fault<br />
Fault f = mc.CreateFault();<br />
f.Name = "Main";<br />
// First, create Pay Zone<br />
mc.CreateZoneAndHorizonBelow(horList[1],<br />
out zoneList[0],<br />
out horList[0]);<br />
zoneList[0].Name = "Pay Zone";<br />
horList[0].Name = "Bottom Pay Zone";<br />
horList[0].HorizonType = HorizonType.Base;<br />
// Add 3 Zones on top of Pay Zone<br />
for (int i = 1; i < 4; i++)<br />
{<br />
mc.CreateZoneAndHorizonAbove(horList[i],<br />
out zoneList[i], out horList[i + 1]);<br />
zoneList[i].Name = "Zone " + i;<br />
horList[i].Name = "Top of Zone " + i;<br />
horList[i + 1].HorizonType =<br />
HorizonType.Conformable;<br />
}<br />
2: Borehole and Geology 69<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
// Create Markers in Borehole for Fault<br />
mc.CreateMarker(bh, f, 1850);<br />
// Create Markers in Borehole for Horizons<br />
double[] md = {1980, 1920, 1870, 1848,<br />
1824};<br />
for (int i = 0; i < 5; i++)<br />
{<br />
mc.CreateMarker(bh, horList[i], md[i]);<br />
}<br />
t.Commit();<br />
}<br />
70 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
3 Structural Domain<br />
In This Chapter<br />
Shapes .......................................................................................................72<br />
Surface..................................................................................................73<br />
PointSet.................................................................................................76<br />
3: Structural Domain 71<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The structural model is derived from both geological and geophysical<br />
interpretations around the reservoir. The structural model fills the gap where<br />
data is incomplete and builds the first volumes representing the formations<br />
in the underground. This is an essential step towards reservoir modeling.<br />
Fig. 3-1<br />
Structural Model<br />
Shapes<br />
Shapes are simple structural elements that are available in the <strong>Petrel</strong> domain<br />
model today. They are the input used by the user to build the static reservoir<br />
model. Shapes can be read, created, and updated. Property fields can be<br />
added to Surfaces and PointSets. The shape classes are Surface,<br />
PolylineSet, and PointSet.<br />
All three classes have a Domain property that specifies whether the shape is<br />
specified in two-way seismic travel time or in depth.<br />
72 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Shapes<br />
DictionarySurfaceProperty<br />
Surface<br />
SurfaceProperty<br />
PolylineSet<br />
Polyline<br />
PointPropertyRecord<br />
PointProperty<br />
PointSet<br />
DictionaryPointProperty<br />
Fig. 3-2<br />
Shapes<br />
Surface<br />
A surface is a (usually gridded) height field. If a geologist is working on an<br />
area that has markers for 15 wells, 8 of the wells may be clustered fairly close<br />
together in a field, with the other wells widely scattered. In order to obtain a<br />
visual representation of the surface, the geologist thinks about the type of<br />
depositional environment that is represented and threads contours through<br />
the data points in order to have an idea of the subsurface that is represented.<br />
However, this technique is difficult for computers to emulate. Grids were<br />
developed as a convenient way for computers to turn data that may be<br />
irregularly spaced into a continuous surface model of regularly spaced points.<br />
Gridding algorithms were developed with a great deal of sophistication and<br />
flexibility in order to convert random data into data that is fairly distributed.<br />
If you think of plotting the well data on a piece of graph paper, the<br />
intersections of the x-axis and the y-axis would represent a grid node<br />
location. The third dimension (z) would represent time, depth, porosity, and<br />
so on, depending on the data that is being gridded. A grid will have a data<br />
value calculated for each grid node by using the original data as a starting<br />
point.<br />
Different gridding algorithms are available, with their own particular<br />
parameters, which may be changed in order to have the resulting grid fit the<br />
data in the best manner. Grids are one of the most fundamental building<br />
blocks of data in Exploration and Production work and are particularly<br />
valuable for the creation of contour maps, 3D model visualization of the<br />
surfaces, and display of the surfaces in cross section. In addition, they allow<br />
volumetrics to be calculated.<br />
The most common type of grid used is a rectilinear or Cartesian grid. The X<br />
and Y increments do not need to be the same, but they usually are. For<br />
3: Structural Domain 73<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Creating a New Surface<br />
example, the X grid increment may be 100 and the Y grid increment may be<br />
125.<br />
All gridded shapes in the <strong>Petrel</strong> data domain use Cartesian grids. The points<br />
in the surface object were interpolated from the original set of points to have<br />
a height field value at each node of a 2D lattice.<br />
The difference between a Surface and a seismic HorizonInterpretation<br />
is that the Surface does not have to be consistent with a seismic Survey,<br />
and it has a calculated height for each node in the lattice. There may be<br />
“holes” in the surface (absent values, in the form of float.NaN), but these<br />
holes mean real holes in the surface. However, when in a<br />
HorizonInterpretation, the holes just show points that have not yet been<br />
interpreted.<br />
Surfaces are found under surface collections. These are placed at the top level<br />
of the Input data tree of <strong>Petrel</strong>. New Collections cannot be created, but<br />
Surfaces can be added to existing Collections.<br />
The next version will handle height fields in a more specific manner.<br />
It is possible to create a new surface, given a surface collection. This is done<br />
by creating the Surface object first, then setting the height field.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Shapes;<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
// Create a new Surface collection in the<br />
Input data tree<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
trans.Lock(proj);<br />
Collection col = proj.CreateCollection("Surfaces");<br />
int numi = 100, numj = 100;<br />
double rotation = 0, spacingX = 25,spacingY = 50;<br />
Point2 origin = new Point2(458000,6780000);<br />
RegularHeightFieldSurface surf;<br />
surf = col.CreateRegularHeightFieldSurface("Test",<br />
new LatticeInfo(origin.X, origin.Y,<br />
spacingX, spacingY, rotation, true,<br />
numi, numj, false, 0, 0, numi, numj));<br />
}<br />
for (int j = 0; j < numj; j++)<br />
{<br />
for (int i = 0; i < numi; i++)<br />
{<br />
// give a sinusoidal shape to the Surface in both directions<br />
surf[i, j] = -1800 + 25 *<br />
(1 - Math.Cos(i * 2 * Math.PI / numi)) *<br />
(1 - Math.Cos(j * 2 * Math.PI / numj));<br />
}<br />
surf.Name = "New Surface";<br />
trans.Commit();<br />
}<br />
74 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Shapes<br />
Fig. 3-3<br />
Surface<br />
PolylineSet<br />
A polyline set is another possible representation for a surface that has not<br />
been interpolated using a horizontal grid. It is used to represent fault<br />
surfaces, but the object can represent any polyline collection.<br />
A polyline set differs from a fault interpretation in that it does not have to be<br />
consistent with a seismic survey geometry.<br />
PolylineSet contains an enumeration of polylines via its Polylines<br />
property. There is also an indexer in the class.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Shapes;<br />
PolylineSet pls = ...;<br />
int index = ...;<br />
IPolyline3 pl = pls[index];<br />
Creating a New<br />
PolylineSet<br />
The API allows the creation of a new set of polylines, given a collection in<br />
the <strong>Petrel</strong> Input tree.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Shapes;<br />
double x0 = ..., y0 = ...;<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
Collection scol = ...;<br />
trans.Lock(scol);<br />
3: Structural Domain 75<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
PolylineSet pls = PolylineSet.Create(scol);<br />
IPolyline3 [] pllist = new IPolyline3[5];<br />
for (int i = 0; i < 5; i++)<br />
{<br />
Point3 [] pts = new Point3[10];<br />
double startx = x0 + 10 * i;<br />
double starty = y0 + 10 * i;<br />
for (int j = 0; j < 10; j++)<br />
{<br />
double delta = 100 * Math.Cos(j * Math.PI / 10);<br />
pts[j] = new Point3(startx + delta, starty + delta,<br />
-2000 + 10*j);<br />
}<br />
pllist[i] = new Polyline3 (pts);<br />
}<br />
pls.Polylines = pllist;<br />
trans.Commit();<br />
}<br />
Fig. 3-4<br />
PolylineSet<br />
PointSet<br />
A point set is the simplest structural domain element accessible via the API.<br />
It is simply built from a list of XYZ points and added to a Collection in<br />
the <strong>Petrel</strong> Input tree.<br />
76 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Shapes<br />
Creating a PointSet<br />
PointSet creation is possible by specifying the containing collection. The<br />
PointSet instance is created first, and then the set of points is added to the<br />
object.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Shapes;<br />
double x0 = ..., y0 = ...;<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
Collection scol = ...;<br />
trans.Lock(scol);<br />
PointSet ps = PointSet.Create(scol);<br />
Point3 [] points = new Point3[5];<br />
double originz = -1900;<br />
for (int i = 0; i < 5; i++)<br />
{<br />
points[i] = new Point3 (x0 + 10 * i,<br />
y0 + 10 * i,<br />
originz + 10 * i);<br />
}<br />
ps.Points = new Point3Set(points);<br />
trans.Commit();<br />
}<br />
Fig. 3-5<br />
PointSet<br />
3: Structural Domain 77<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
78 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
4 Seismic and the Geophysical Domain<br />
In This Chapter<br />
Seismic <strong>Data</strong>sets ..........................................................................................80<br />
Seismic Interpretation.................................................................................108<br />
4: Seismic and the Geophysical Domain 79<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Seismic <strong>Data</strong>sets<br />
3D volume<br />
2D lines<br />
Fig. 4-1<br />
Seismic <strong>Data</strong>sets<br />
Geophysicists evaluate the reservoir by interpreting seismic reflections<br />
measured from the surface. This investigation method is less precise but<br />
covers a greater area than measurements done at the well for a far lesser cost.<br />
The user interprets some geologic features such as horizons and faults from<br />
these measurements, which are stored in the <strong>Petrel</strong> project as seismic<br />
datasets. The horizons and faults are built from points picked on reflectors<br />
that the user discovers in the graphical representation of the seismic data.<br />
Seismic Concepts<br />
Seismic data represent amplitude measured on seismic reflectors at given<br />
points in space.<br />
The reflector depth is characterized by the length on the time scale of the<br />
seismic wave from the source to the reflector echo. This distance is measured<br />
in milliseconds and represents two-way time or the time the seismic sound<br />
wave takes to go from the source to the reflector and back. Seismic data is<br />
digitized, and each sample is separated from the next by a sample interval,<br />
usually expressed in milliseconds.<br />
When the interpreter has built a comprehensive velocity model (i.e. when the<br />
sound wave travel time is known throughout the seismic measure range) the<br />
seismic data can be converted to depth. In <strong>Ocean</strong>, we read and create Seismic<br />
data in two-way time or in depth.<br />
For depth seismic to be available through the API, it has to have been either<br />
loaded or realized from depth conversion by the <strong>Petrel</strong> user.<br />
Seismic data is recorded in 2D or 3D surveys. These surveys are materialized<br />
in the Input data tree as Seismic folders (SeismicCollection instances in<br />
the API).<br />
80 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
Seismic Terms<br />
3D Survey<br />
3D Survey map<br />
Fig. 4-2<br />
Map view of 3D seismic survey<br />
3D data is binned, which means that each trace is stacked with all the other<br />
traces that fall inside the same cell of the 3D survey map. 3D data is<br />
therefore perfectly aligned, each node being defined by a lattice with an<br />
origin, spacings for inlines and crosslines, and a rotation angle.<br />
2D Survey<br />
2D Survey map<br />
Fig. 4-3<br />
Map view of 2D seismic survey<br />
2D surveys have exact geographical positions for each shot point. These<br />
positions are recorded while shooting the seismic and later loaded from<br />
navigation files. They are also part of the dataset description in the SEG-Y<br />
format. The particularity of 2D lines is that they are not showing as straight<br />
lines.<br />
4: Seismic and the Geophysical Domain 81<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Seismic Trace<br />
sample<br />
trace<br />
Seismic trace<br />
Fig. 4-4<br />
Color filled representation of seismic trace<br />
Each seismic trace is an array of amplitude (or other property) data values.<br />
Each trace in a survey will hold the same number of samples.<br />
Seismic Section<br />
Fig. 4-5<br />
Seismic line displayed in Interpretation window<br />
Seismic data is presented in an Interpretation window one section at a time.<br />
One section holds one 2D line or a specific section in the 3D cube: an inline,<br />
a crossline, or a time slice.<br />
Seismic Cube<br />
3D seismic cubes hold one amplitude value in each cell of a 3D lattice.<br />
Seismic <strong>Data</strong>sets<br />
9x11x16 amplitude cube<br />
Fig. 4-6<br />
Representation of 3D seismic data<br />
82 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
We find two types of field-recorded datasets in the <strong>Petrel</strong> Input tree:<br />
• 3D cubes<br />
• 2D lines<br />
SeismicCube<br />
In a 3D cube the seismic traces are binned, which means they are gathered,<br />
bin by bin in a lattice. All traces that fall in the same bin will be stacked<br />
together.<br />
lines from 3D<br />
volume<br />
lines from 3D<br />
sub-volume<br />
Fig. 4-7<br />
Seismic Cube<br />
The 3D cube lattice arrangement makes it easy to specify in space where<br />
each piece of seismic data resides. The lattice is regular and all we have to<br />
specify is an origin, an orientation, steps in the inline and the crossline<br />
directions, the number of shot points per inline, and the number of inlines<br />
per crossline.<br />
SeismicLine2D<br />
A 2D line is a dataset where a lesser number of shots are fired. The shots are<br />
placed in a line showing the actual position of the recording equipment.<br />
4: Seismic and the Geophysical Domain 83<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
In a 2D line, the navigation data is loaded on top of the seismic amplitude<br />
data. For this reason, the API allows the user to clone a line but not to create<br />
one from scratch.<br />
Fig. 4-8<br />
2D Seismic <strong>Data</strong>set<br />
SeismicCollection<br />
All seismic data is stored under a Seismic collection instance. All 3D cubes<br />
under the same collection will share the same lattice.<br />
Since <strong>Petrel</strong> 2007, Seismic collections are specialized, as they contain either<br />
3D or 2D datasets. They carry the property MemberType that lets the<br />
application distinguish between 2D and 3D collections. Many 2D surveys can<br />
be stored under the same (2D) collection, as there is no constraint on the<br />
shot locations.<br />
The following code example illustrates determining the type of data<br />
contained in a collection.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
public void Find3D()<br />
{<br />
SeismicProject proj =<br />
SeismicRoot.Get(<strong>Petrel</strong>Project.PrimaryProject<br />
);<br />
if (!proj.IsGood) return;<br />
foreach (SeismicCollection col in proj.SeismicCollections)<br />
{<br />
if (scol.MemberType == typeof(SeismicCube))<br />
{<br />
...<br />
}<br />
}<br />
}<br />
84 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
collection of 3D cubes<br />
and groups of 2D lines<br />
volume of 3D<br />
seismic traces<br />
SeismicCollection<br />
group of 2D lines<br />
SeismicCube<br />
SeismicLine2DCollection<br />
SeismicLine3D<br />
SeismicLine2D<br />
3D<br />
inline<br />
or<br />
Xline<br />
Fig. 4-9<br />
Seismic <strong>Data</strong> Classes<br />
ITrace<br />
2D line of<br />
seismic traces<br />
individual indexed trace<br />
Individual traces are accessed from SeismicCube, SeismicLine3D, or<br />
SeismicLine2D either via an enumerator, Traces, or a function, GetTrace,<br />
which provides random order access. The samples inside the ITrace object<br />
are returned as an array.<br />
The seismic collection not only serves as a lattice definition for the 3D<br />
survey, but also as a container for datasets in the <strong>Petrel</strong> Input data tree.<br />
SeismicCollection<br />
SeismicCube<br />
SeismicLine2DCollection<br />
Fig. 4-10 Seismic <strong>Data</strong> Layout<br />
4: Seismic and the Geophysical Domain 85<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
<strong>Access</strong>ing Seismic <strong>Data</strong><br />
Collection to Cube<br />
A seismic collection may contain many 3D cubes (based on consistent<br />
lattices).<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCollection s<strong>Data</strong> = ...;<br />
IEnumerable all3D = s<strong>Data</strong>.SeismicCubes;<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("All cubes in " + s<strong>Data</strong>.Name);<br />
foreach (SeismicCube one3D in all3D)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(one3D.Name);<br />
}<br />
Cube to Trace<br />
Seismic traces are accessed from the 3D cube via an enumerator.<br />
SeismicCube contains an IEnumerable property, Traces.<br />
The enumerator returns traces in storage order.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCube one3D = ...;<br />
foreach (ITrace trace in one3D.Traces)<br />
{<br />
...<br />
}<br />
access traces in storage order<br />
continue on next line<br />
Fig. 4-11 Seismic Cube Traces <strong>Access</strong> in Storage Order<br />
Traces can also be accessed in random order, by specifying the IJ index of<br />
the trace in the cube. I specifies the inline number and J specifies the<br />
86 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
crossline number. It is necessary to check that the cube can be written to,<br />
using the property IsWritable, before writing data into the cube.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCube one3D = ...;<br />
int numI = one3D.NumSamplesIJK.I;<br />
int numJ = one3D.NumSamplesIJK.J;<br />
for (int i = 0; i < numI; i++)<br />
{<br />
for (int j = 0; j < numJ; j++)<br />
{<br />
ITrace trace = one3D.GetTrace(i, j);<br />
...<br />
if (one3D.IsWriteable)<br />
{<br />
// update trace values<br />
...<br />
}<br />
}<br />
}<br />
Fig. 4-12 Seismic Cube Random Trace <strong>Access</strong><br />
Collection to 2D Survey<br />
A SeismicCollection may also contain many 2D surveys. Each 2D survey<br />
is represented by a SeismicLine2DCollection instance.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCollection s<strong>Data</strong> = ...;<br />
IEnumerable all2D<br />
all2D = s<strong>Data</strong>.SeismicLine2DCollections();<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("All 2D surveys<br />
in " + s<strong>Data</strong>.Name);<br />
foreach (SeismicLine2DCollection one2D in all2D)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(one2D.Name);<br />
}<br />
4: Seismic and the Geophysical Domain 87<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
2D Survey to 2D Line<br />
A 2D survey is a collection of 2D lines. These are enumerated in the<br />
Seismic2DLineCollection instance.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicLine2DCollection one2D = ...;<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("All lines in " + one2D.Name);<br />
foreach (SeismicLine2D oneLine in one2D)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(oneLine.Name);<br />
}<br />
2D Line to Trace<br />
Individual traces from a 2D line are accessible from an enumerator. Class<br />
Seismic2DLine contains an IEnumerable property, Traces.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicLine2D oneLine = ...;<br />
foreach (ITrace trace in oneLine.Traces)<br />
{<br />
...<br />
}<br />
The traces are retrieved in storage order.<br />
access traces in storage order<br />
Fig. 4-13 <strong>Access</strong> 2D Line Traces in Storage Order<br />
88 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
Traces can also be accessed from a 2D line in random order. The GetTrace<br />
method in class SeismicLine2D takes a trace index argument and returns<br />
an individual trace.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicLine2D oneLine = ...;<br />
int index = 3;<br />
ITrace trace = oneLine.GetTrace(index);<br />
...<br />
The traces are indexed by their J position in the line. The trace instance holds<br />
its position in its J property. Its I property, in the case of a 2D seismic line,<br />
is always set to 0.<br />
j index<br />
Fig. 4-14 <strong>Access</strong>ing 2D Line Traces in Random Order<br />
<strong>Access</strong>ing Trace Samples<br />
Traces are arrays of samples. They have an indexer member that allows<br />
individual samples to be accessed by simply indexing the ITrace instance.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic<br />
{<br />
public interface ITrace<br />
{<br />
int I { get; }<br />
int J { get; }<br />
int Length { get; }<br />
}<br />
}<br />
float this[int index] { get; set; }<br />
4: Seismic and the Geophysical Domain 89<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Samples are retrieved as floats. Undefined values are returned as NaN.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
ITrace trace = ...;<br />
...<br />
for (int k = 0; k < trace.Length; k++)<br />
{<br />
// Retrieving the sample value<br />
double sample = trace[k];<br />
}<br />
...<br />
// Setting the sample to a new value<br />
if (one3D.IsWriteable)<br />
{<br />
trace[k] = ...<br />
}<br />
Flushing Individual Traces<br />
Individual traces should be flushed to control their transfer to disk storage.<br />
This is done on the trace container, the SeismicLine2D, or the<br />
SeismicCube instances.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCube one3D = ...;<br />
SeismicLine2D oneLine = ...;<br />
// Browse through the cube<br />
ITrace trace = ...;<br />
...<br />
// Flush the trace in the Seismic cube storage files<br />
One3D.DoneWith(trace);<br />
// Browse through trace in the 2D line<br />
trace = ...;<br />
...<br />
// Flush the trace in the Seismic line storage file<br />
OneLine.DoneWith(trace);<br />
Buffered <strong>Access</strong> to 3D<br />
Seismic Cubes<br />
The <strong>Ocean</strong> API provides an interface to access a block of seismic trace<br />
values, the ISubCube interface.<br />
The buffered block is defined as a sub-cube in all dimensions, so unlike trace<br />
by trace access shown previously, one does not have to retrieve full traces.<br />
This is particularly advantageous when reading a single time slice.<br />
The buffered access of ISubCube can be used when filling a newly created<br />
cube with values since write access, as well as read access, is availabe.<br />
90 <strong>Ocean</strong> Application Development Framework 2007.1<br />
public interface ISubCube : IEnumerable, IEnumerable<br />
{<br />
Index3 MaxIJK { get; }<br />
Index3 MinIJK { get; }<br />
float this[Index3 index] { get; set; }<br />
void CopyFrom(float[, ,] data);<br />
float[, ,] ToArray();<br />
}<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
An ISubCube block is defined by calling a method on the original 3D<br />
seismic cube, and passing six values in arguments, three start indices, and<br />
three stop indices.<br />
public sealed class SeismicCube : ...<br />
{<br />
public ISubCube GetSubCube(Index3 MinIJK, Index3 MaxIJK);<br />
}<br />
Getting a sub-cube buffer does not create an object in the <strong>Petrel</strong> project, and<br />
no new data entity appears on the data tree. The operation only creates a<br />
memory structure used for subsequent data access.<br />
The indexing used to retrieve data from the sub-cube buffer relates to the<br />
full extent of the original cube. See the example below. Note that ISubCube<br />
is a specialization of IEnumerable and can be used in a<br />
foreach loop to create automatically increasing indices.<br />
private void FillCube(SeismicCube orig, SeismicCube cube)<br />
{<br />
int block_size = 100;<br />
for (int i = 0; i < orig.NumSamplesIJK.I; i += block_size)<br />
for (int j = 0; j < orig.NumSamplesIJK.J; j += block_size)<br />
for (int k = 0; k < orig.NumSamplesIJK.K; k += block_size)<br />
{<br />
Index3 start = new Index3(i, j, k);<br />
Index3 stop = new Index3(<br />
Math.Min(i + block_size, orig.NumSamplesIJK.I - 1),<br />
Math.Min(j + block_size, orig.NumSamplesIJK.J - 1),<br />
Math.Min(k + block_size, orig.NumSamplesIJK.K - 1));<br />
ISubCube from = orig.GetSubCube(start, stop);<br />
ISubCube to = cube.GetSubCube(start, stop);<br />
foreach (Index3 idx in from)<br />
{<br />
to[idx] = from[idx];<br />
}<br />
}<br />
}<br />
There are a number of methods in ISubCube to enhance data access<br />
performance.<br />
• A complete array may be pasted onto the sub-cube in one function call<br />
(CopyFrom()).<br />
• The block may be saved to an array for zero-based indexing.<br />
4: Seismic and the Geophysical Domain 91<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
• The previous example is shown here with direct array access and an<br />
improved copy time.<br />
private void FillCube(SeismicCube orig, SeismicCube cube)<br />
{<br />
int block_size = 100;<br />
for (int i = 0; i < orig.NumSamplesIJK.I; i += block_size)<br />
for (int j = 0; j < orig.NumSamplesIJK.J; j += block_size)<br />
for (int k = 0; k < orig.NumSamplesIJK.K; k += block_size)<br />
{<br />
Index3 start = new Index3(i, j, k);<br />
Index3 stop = new Index3(<br />
Math.Min(i + block_size, orig.NumSamplesIJK.I - 1),<br />
Math.Min(j + block_size, orig.NumSamplesIJK.J - 1),<br />
Math.Min(k + block_size, orig.NumSamplesIJK.K - 1));<br />
ISubCube from = orig.GetSubCube(start, stop);<br />
ISubCube to = cube.GetSubCube(start, stop);<br />
to.CopyFrom(from.ToArray());<br />
}<br />
}<br />
Creating a Seismic<br />
Cube<br />
There are three methods used to create a seismic cube including:<br />
• Cloning an existing cube<br />
• Creating a sub-cube of an existing cube<br />
• Specifying the survey lattice<br />
The first method creates a copy of the original cube, leaving all traces empty.<br />
The second method also creates a cube but lets the caller specify decimation<br />
in trace range and in trace and sample spacing. The third method requires<br />
defining the cube origin, line spacing, sample spacing, range, and rotation.<br />
Cloning an Existing Cube<br />
Creating a cube by cloning an existing one is straightforward.<br />
The new cube is added to a SeismicCollection instance. It is not<br />
necessarily the container of the original cube, but it has to be compatible<br />
with the cube to be created. Use the CanBeAdded method to verify lattice<br />
compatibility. Actually, this rule is not enforced in the present version of<br />
<strong>Ocean</strong>, but CanBeAdded should be called for later compatibility.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCollection sc = ...;<br />
SeismicCube oldCube = ...;<br />
if (sc.CanBeAdded(oldCube))<br />
SeismicCube myCube = sc.CreateSeismicCube(oldCube);<br />
else<br />
<strong>Petrel</strong>Logger.ErrorBox(“Selected cube cannot be added to” + sc.Name);<br />
Creating a Decimated Cube<br />
To create a sub-cube of the original cube, we have to specify<br />
• A start offset, translation in IJK space for the cube origin<br />
92 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
• A new spacing step in all dimensions<br />
• A size in IJK, the number of inlines, crosslines, and samples<br />
We also have to specify a PropertyVersion for the newly created cube.<br />
This allows creation of attribute cubes.<br />
Fig. 4-15 Defining a Sub-cube from an Existing Cube<br />
The creation call should be protected by CanBeAdded. As before, we verify<br />
that the cube is compatible with the SeismicCollection.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicCollection sc = ...;<br />
SeismicCube oldCube = ...;<br />
if (sc.CanBeAdded(oldCube))<br />
{<br />
// new origin is expressed in number of inlines and xlines<br />
// from old origin<br />
Index3 startPt = new Index3(20,10, 50);<br />
// new inline, crossline and sample spacing<br />
Index3 ptInc = new Index3(2, 2, 1);<br />
// total number of inlines, crosslines and samples, not to exceed<br />
// cube range<br />
Index3 newSize = new Index3(25, 25, 30);<br />
PropertyVersion pv = ...;<br />
SeismicCube newCube = sc.CreateSeismicCube(oldCube, startPt,<br />
ptInc , newSize,<br />
pv);<br />
}<br />
Creating a New Cube<br />
To create a new cube in a SeismicCollection, one has to specify the<br />
geometry of the cube:<br />
• Cube size in all dimensions<br />
• Origin of the cube in XYZ space<br />
• Unit vector specifying the spacing and the orientation of the inlines<br />
4: Seismic and the Geophysical Domain 93<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
• Unit vector specifying the spacing and the orientation of the crosslines<br />
• Unit vector specifying the spacing of the trace samples<br />
• <strong>Data</strong> storage format (from 8-bit integer to float)<br />
• Vertical axis domain (two-way time or depth)<br />
• Property version<br />
• Clipping range<br />
public class SeismicCollection : ...<br />
{<br />
public SeismicCube CreateSeismicCube(Index3 size,<br />
PropertyVersion propertyVersion,<br />
Range1 clippingRange);<br />
Point3 origin,<br />
Vector3 iUnitVector,<br />
Vector3 jUnitVector,<br />
Vector3 kUnitVector,<br />
Type storageType,<br />
Domain verticalDomain,<br />
}<br />
...<br />
Index3 size defines the number of inlines, crosslines, and samples per trace.<br />
Point3 origin specifies the position in space of the shallowest lower left corner<br />
(meaning inline 0, crossline 0, and sample 0). It is expressed in the project's projection<br />
coordinate system.<br />
The three Vector3 arguments, iUnitVector, jUnitVector, and kUnitVector<br />
specify spacing in all directions and rotation of the lattice in the horizontal<br />
plane from the North aligned projection system.<br />
origin<br />
k index<br />
i index<br />
j index<br />
time<br />
crossline<br />
inline<br />
Fig. 4-16 Seismic Cube Layout<br />
94 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
iUnitVector and jUnitVector specifies the rotation of the cube lattice<br />
from the North-aligned Cartesian projection system. The horizontal<br />
coordinates of the iUnitVector are as follows.<br />
(inline spacing) * cos(rotation angle)<br />
and<br />
(inline spacing) * sin(rotation angle)<br />
The horizontal coordinates of the jUnitVector are as follows.<br />
(crossline spacing) * sin(rotation angle)<br />
and<br />
(crossline spacing) * -cos(rotation angle)<br />
The units for the values for the iUnitVector and jUnitVector are<br />
expressed in the Invariant unit system of the project, or SI. For horizontal<br />
distance the units are meters.<br />
The kUnitVector argument specifies the spacing of data samples in the<br />
vertical domain. The units for kUnitVector are expressed in the Invariant<br />
unit system for the project for the Domain that is defined by the Domain<br />
argument to the Create method. For the ELEVATION_TIME domain, this<br />
is seconds and for ELEVATION_DEPTH, it is meters.<br />
Note that the i , j , k vectors should form an orthogonal system with k<br />
oriented towards the center of the earth. So the i , j , k system must verify<br />
the following rules.<br />
• The X and Y components of the k vector are equal to zero.<br />
• The Z component of I and J is equal to zero.<br />
• I and J are perpendicular to one another, i.e. the scalar product i . j is<br />
equal to zero.<br />
4: Seismic and the Geophysical Domain 95<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
line spacing<br />
trace spacing<br />
Northing (y)<br />
(x j , y j , 0)<br />
25<br />
12.5<br />
origin<br />
(xi, yi, 0)<br />
Easting (x)<br />
Fig. 4-17 IJ Unit Vectors in the Projection Plane<br />
The last four arguments of the CreateSeismicCube method specify the<br />
trace sample domain and range.<br />
• <strong>Data</strong> format is controlled by System.Type argument.<br />
• K axis vertical domain is specified by the Domain argument.<br />
• Type of data is characterized by PropertyVersion.<br />
• <strong>Data</strong> in the cube will be clipped by the Range1 argument if<br />
the data is stored in integer or byte format.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
using (ITransaction t =<br />
<strong>Data</strong>Manager.NewTransaction()<br />
{<br />
SeismicCollection sc = ...;<br />
t.Lock(sc);<br />
Index3 size = new Index3(500, 1000, 2001);<br />
Point3 origin = new Point3(13579.75, 24680.08, 0.0);<br />
Vector3 iSpacing = new Vector3(10.82,6.25, 0.000);<br />
Vector3 jSpacing = new Vector3(-12.50, 21.65, 0.000);<br />
Vector3 kSpacing = new Vector3( 0.00, 0.00, 0.004);<br />
96 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
}<br />
if (sc.CanBeAdded(size, origin, iSpacing, jSpacing, kSpacing)<br />
{<br />
Type dataType = typeof(float);<br />
Domain vDomain = Domain.TWT;<br />
PropertyVersion pv = ...;<br />
Range1 r = new Range1(- 2048.0, 2048.0);<br />
SeismicCube cube = sc.CreateSeismicCube(<br />
size,<br />
origin,<br />
iSpacing,<br />
jSpacing,<br />
kSpacing,<br />
dataType,<br />
vDomain,<br />
pv,<br />
r);<br />
}<br />
t.Commit();<br />
Creating a Seismic Line<br />
Creating a seismic line can only be done by cloning an existing 2D survey.<br />
This method is provided to allow creation of 2D seismic attributes.<br />
A PropertyVersion is specified so that the cloned 2D survey can represent<br />
any seismic attribute Property.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
using (ITransaction t =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
SeismicCollection sCol = ...;<br />
SeismicLine2DCollection lineCol = ...;<br />
PropertyVersion pv = ...;<br />
}<br />
t.Lock(sCol);<br />
SeismicLine2DCollection newCol;<br />
newCol = sCol.CreateSeismicLine2DCollection(lineCol, pv);<br />
foreach (SeismicLine2D line in newCol)<br />
{<br />
foreach (ITrace trace in line.Traces)<br />
{<br />
int index = ...;<br />
trace[index] = ...;<br />
line.DoneWith(trace);<br />
}<br />
}<br />
t.Commit();<br />
The API only provides the capability to create an entire collection of 2D lines.<br />
This is due to the way 2D surveys are stored in the compressed SEG-Y format<br />
used in <strong>Petrel</strong> for seismic data storage. A single file (the data entity that is<br />
cloned) corresponds to the entire set of lines in the collection.<br />
4: Seismic and the Geophysical Domain 97<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Other Seismic <strong>Data</strong>sets:<br />
The Virtual Cube<br />
Original Cube<br />
Filtered<br />
Quality<br />
Fig. 4-18 Section on a Virtual Filtered Cube vs. Original<br />
Creation of virtual cubes in the <strong>Petrel</strong> data tree is done via the IAttribute<br />
interface.<br />
The main purpose of virtual cubes is to define new seismic attributes (filters<br />
on existing cubes), and the creation of virtual cubes is attached to the<br />
"Volume attributes" application in <strong>Petrel</strong> Geophysics.<br />
The IAttribute definition is found in the SeismicAttribute namespace,<br />
outside of the <strong>Petrel</strong> DomainObject namespace hierarchy.<br />
A seismic attribute is designed by providing the following classes:<br />
• A class implementing IAttribute<br />
• A class to hold user-specified attribute arguments<br />
• A UI class for the user to assign values to the attribute arguments<br />
• A class implementing IAttributeGenerator<br />
98 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
The first interface organizes the attribute computation, specifying the<br />
number of input cubes and the number of neighboring traces on which the<br />
attribute is computed.<br />
public interface IAttribute<br />
{<br />
string CategoryName { get; }<br />
string Description { get; }<br />
string[] InputLabels { get; }<br />
string Name { get; }<br />
int NumInputs { get; }<br />
Index3 OperatorSize { get; }<br />
PropertyVersion PropertyVersion { get; }<br />
Range1 ValueLimit { get; }<br />
}<br />
object CreateArgumentPackage();<br />
IAttributeGenerator CreateGenerator(object argumentPackage,<br />
IGeneratorContext context);<br />
Control CreateUI(object argumentPackage);<br />
The InputLabels and NumInputs properties allow the IAttribute<br />
interface implementation to specify several input cubes. However, that<br />
functionality is not implemented yet, and the "Volume attributes" user<br />
interface will only take one input cube.<br />
The interface specifies three methods to instantiate the argument package,<br />
the generator that will compute traces on demand and the custom UI that<br />
will allow the user to specify his or her choice of argument values.<br />
Attribute arguments<br />
The attribute arguments are specified in a class whose public properties will<br />
be the attribute parameters set by the user and used by the generator to<br />
calculate trace values.<br />
The attribute argument implementation must be Serializable and<br />
implement ICloneable. Once instantiated the virtual cube is part of the<br />
project but contains no data in itself. Instead it must keep references to its<br />
input cube and save the argument values.<br />
[Serializable]<br />
internal class MyAttributeArguments : ICloneable<br />
{<br />
private float m_param = 0.1291f;<br />
}<br />
public float MyParameter<br />
{<br />
get { return m_param; }<br />
set { m_param = value; }<br />
}<br />
...<br />
Arguments can hold <strong>Ocean</strong> domain objects like process arguments, as the<br />
attribute computation is run in <strong>Petrel</strong>'s main thread. The following example<br />
takes a second cube to apply a quality filter to the input cube. The input and<br />
the quality cube share the same survey. The attribute arguments are the<br />
secondary cube (<strong>Petrel</strong> domain object) and the two values in the second cube<br />
4: Seismic and the Geophysical Domain 99<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
indicate the high and low quality values. Handling a domain object as an<br />
argument adds the difficulty that the argument value should not be serialized<br />
(the object reference would be invalid at restore time), but its Droid should<br />
be saved and serialized with the argument class and used when loading the<br />
project to re-create the reference.<br />
[Serializable]<br />
internal class AttributeArguments : ICloneable<br />
{<br />
private Droid m_droid;<br />
private float m_highest, m_lowest, m_scale, m_offset;<br />
[NonSerialized]<br />
private SeismicCube m_Other;<br />
public AttributeArguments()<br />
{<br />
}<br />
public SeismicCube OtherSeismicCube<br />
{<br />
get<br />
{<br />
if (m_Other == null)<br />
m_Other = <strong>Data</strong>Manager.Resolve(m_droid) as SeismicCube;<br />
return m_Other;<br />
}<br />
set<br />
{<br />
m_OtherSeismicCube = value;<br />
m_droid = m_OtherSeismicCube.Droid;<br />
}<br />
}<br />
public float Highest<br />
{<br />
get { return m_highest; }<br />
set { m_highest = value; }<br />
}<br />
public float Lowest<br />
{<br />
get { return m_lowest; }<br />
set { m_lowest = value; }<br />
}<br />
public float Scale<br />
{<br />
get { return m_scale; }<br />
set { m_scale = value; }<br />
}<br />
public float Offset<br />
{<br />
get { return m_offset; }<br />
set { m_offset = value; }<br />
}<br />
// ICloneable Members<br />
public object Clone()<br />
{<br />
return MemberwiseClone();<br />
}<br />
}<br />
In the example above, which takes a second seismic cube to indicate quality,<br />
we hold argument values for high and low values for the quality cube (chosen<br />
100 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
by the user) and compute a scale and offset to apply to the input seismic<br />
cube. The output will represent the input cube quality filtered.<br />
Custom UI<br />
As in the case of a Process, the IAttribute provides a method to create a<br />
set of argument values picked by the user. This Control instance will appear<br />
when the attribute is selected by the user in the "Volume attributes"<br />
application.<br />
Fig. 4-19 UI Design for the Virtual Attribute Cube<br />
The same user interface gets displayed when the user double-clicks on a<br />
virtual cube in the data tree and when the virtual cube is built with the<br />
IAttribute implementation.<br />
Attribute generator<br />
The attribute generator provides the method used by <strong>Petrel</strong> to calculate trace<br />
values on demand. This is the last class to be instantiated when creating a<br />
virtual cube instance, after the arguments and the UI. The attribute calls the<br />
CreateGenerator() method after the user has pressed the Apply or OK<br />
button on the attribute editor.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.SeismicAttribute<br />
{<br />
public interface IAttributeGenerator<br />
{<br />
void Calculate(IAttribute<strong>Data</strong>[] input,<br />
IAttribute<strong>Data</strong> output,<br />
Index3 outputOffset);<br />
}<br />
}<br />
4: Seismic and the Geophysical Domain 101<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The main object of the generator class is to provide a Calculate() method<br />
that will be used to generate seismic traces on demand. The following<br />
example continues the implementation of our quality filter.<br />
[Serializable]<br />
class AttributeGenerator : IAttributeGenerator<br />
{<br />
private AttributeArguments m_arguments;<br />
private bool m_equalGeometry = false;<br />
public AttributeGenerator(AttributeArguments arguments,<br />
IGeneratorContext generatorContext,<br />
SeismicAttribute origin)<br />
{<br />
m_arguments = arguments;<br />
LatticeInfo lat = m_Arguments.OtherSeismicCube.Lattice;<br />
m_equalGeometry = (lat.Operations.Equals(<br />
generatorContext.InputCubes[0].Lattice) &&<br />
(m_Arguments.OtherSeismicCube.NumSamplesIJK ==<br />
generatorContext.InputCubes[0].NumSamplesIJK));<br />
}<br />
public void Calculate(ISubCube[] input,<br />
ISubCube output,Index3 outputOffset)<br />
{<br />
if (m_arguments.OtherSeismicCube == SeismicCube.NullObject ||<br />
!m_arguments.OtherSeismicCube.IsGood) return;<br />
if (!m_equalGeometry) return;<br />
ISubCube input0 = input[0];<br />
ISubCube otherSubCube = m_arguments.OtherSeismicCube.GetSubCube(<br />
outputOffset + input[0].MinIJK,<br />
outputOffset + input[0].MaxIJK);<br />
float offset = m_arguments.Offset;<br />
float scale = m_arguments.Scale;<br />
// Now the actual output loop. ISubCube is an Index3 enumerator<br />
foreach (Index3 index in output)<br />
{<br />
// Get the index and value for the Other cube<br />
float otherValue = otherSubCube[outputOffset + index];<br />
}<br />
}<br />
}<br />
// Compute the attribute<br />
float myValue = input[0][index];<br />
output[index] = myValue * (offset + scale * otherValue);<br />
Attribute example<br />
With the classes designed above, we build an IAttribute implementation<br />
that will be instantiated at load time by the <strong>Ocean</strong> module that defines it.<br />
Note that the attribute instance will be serialized when the project is saved,<br />
102 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
with its argument package and generator instance, and should include only<br />
basic types in its private members.<br />
[Serializable]<br />
public class SeismicAttribute : IAttribute<br />
{<br />
private Index3 m_operatorSize;<br />
private string[] m_inputLabels;<br />
public SeismicAttribute()<br />
{<br />
m_inputLabels = new string[2];<br />
m_inputLabels[0] = "CubeOne";<br />
m_operatorSize = new Index3(1, 1, 1);<br />
}<br />
public object CreateArgumentPackage()<br />
{<br />
return new AttributeArguments();<br />
}<br />
public IAttributeGenerator CreateGenerator(object argumentPackage,<br />
IGeneratorContext generatorContext)<br />
{<br />
AttributeArguments args = argumentPackage as AttributeArguments;<br />
return new AttributeGenerator(args, generatorContext, this);<br />
}<br />
public Control CreateUI(object argumentPackage)<br />
{<br />
AttributeArguments args = argumentPackage as AttributeArguments;<br />
return new SeismicAttributeForm(args);<br />
}<br />
public string Description<br />
{<br />
get { return "quality multiplier from secondary cube"; }<br />
4: Seismic and the Geophysical Domain 103<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
}<br />
}<br />
public string[] InputLabels<br />
{<br />
get { return m_inputLabels; }<br />
}<br />
public string Name<br />
{<br />
get { return "Quality Filtering"; }<br />
}<br />
public int NumInputs<br />
{<br />
get { return 1; }<br />
}<br />
public Index3 OperatorSize<br />
{<br />
get { return m_operatorSize; }<br />
}<br />
public PropertyVersion PropertyVersion<br />
{<br />
get<br />
{<br />
IPropertyVersionService pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
return pvs.FindOrCreate(<br />
pvs.GetGlobalPropertyVersionContainer(),<br />
<strong>Petrel</strong>UnitSystem.TemplateGroupSeismicColor.SeismicDefault);<br />
}<br />
}<br />
public Range1 ValueLimit<br />
{<br />
get { return new Range1(-8000.0f, 8000.0f); }<br />
}<br />
Attribute registration<br />
Having instantiated the attribute class, the module must register the new<br />
instance with the core service of type IAttributeService. This is done<br />
with Slb.<strong>Ocean</strong>.Core.CoreSystem, so that the new attribute is available<br />
when running the Volume attributes application.<br />
Public class MyAttributeModule : IModule<br />
{<br />
...<br />
public void Integrate()<br />
{<br />
IAttributeService service;<br />
service = CoreSystem.GetService();<br />
if (service == null)<br />
{<br />
throw new Slb.<strong>Ocean</strong>.Core.LifecycleExcption("Service missing");<br />
}<br />
service.InstallAttribute(new MyAttribute());<br />
}<br />
}<br />
104 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
Once the attribute is registered, it can be used to instantiate a virtual cube<br />
programmatically without running the "Volume attributes" application in<br />
<strong>Petrel</strong>.<br />
IAttributeService service;<br />
service = CoreSystem.GetService();<br />
IAttribute attr = new MyAttribute();<br />
SeismicCube original = ...;<br />
string name = "My Virtual Cube";<br />
SeismicCube virtual = service.CreateVirtualCube(attr, original, name);<br />
To save the virtual cube with the project, three classes, the IAttribute and<br />
IAttributeGenerator implementations, and the arguments package class<br />
must be serializable.<br />
Other Seismic <strong>Data</strong>sets:<br />
Wavelet <strong>Data</strong><br />
Fig. 4-20 Wavelet Display in <strong>Petrel</strong><br />
A seismic wavelet is a regularly sampled sequence of dimensionless amplitude<br />
values. Such data is typically defined in the time domain and is convolved<br />
with a set of correlation coefficients to form synthetic seismic data. It can<br />
also represent a spatial filter, in which case its domain is a length.<br />
In <strong>Ocean</strong>, a wavelet is represented by the Wavelet class. The class defines<br />
and implements several rules to accommodate wavelets such that they will<br />
follow typical processing guidelines for convolution and filtering. These<br />
include<br />
6. rounding the number of samples up to the next power of two when the<br />
wavelet is created<br />
4: Seismic and the Geophysical Domain 105<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
7. placing the original center sample of the wavelet at the rounded up<br />
sample count divided by 2<br />
8. padding the ends of the wavelet with values of 0<br />
The definition for the Wavelet class is<br />
public sealed class Wavelet : FacadeConvenience, IDomainObject,<br />
IDescriptionSource,<br />
IContainerMember,<br />
IContainerMember<br />
{<br />
public IEnumerable Amplitudes { get; set; }<br />
public Domain Domain { get; set; }<br />
public bool IsWritable { get; }<br />
public int SampleCount { get; }<br />
public double SamplingInterval { get; set; }<br />
public double SamplingStart { get; }<br />
...<br />
public double this[int index] { get; }<br />
}<br />
Wavelets are preferably created in SeismicCollection or SeismicProject<br />
domain objects. However, they may also be created under Project and<br />
Collection domain objects. Here is an example creating a wavelet.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
...<br />
// Get the project and find the root of all seismic data<br />
Project project = <strong>Petrel</strong>Project.PrimaryProject;<br />
SeismicProject sp = SeismicRoot.Get( project ).SeismicProject;<br />
// Start a transaction since we will create data.<br />
using (ITransaction tr = <strong>Data</strong>Manager.newTransaction( ))<br />
{<br />
// Lock the seismic project so the wavelet can be created<br />
tr.Lock( sp );<br />
}<br />
// Create the wavelet using a provided name.<br />
Wavelet wavelet = sp.CreateWavelet( "MyWavelet" );<br />
...<br />
When amplitude data is added to the wavelet it must be added in the form of<br />
an entire array of type double. Along with the amplitude data the sample<br />
interval and Domain of the wavelet must be provided. The following<br />
106 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic <strong>Data</strong>sets<br />
example illustrates creating and setting the data for the wavelet created above.<br />
// Get number of amplitudes in wavelet<br />
int numSamples = ...;<br />
...<br />
// Create array for wavelet amplitudes<br />
double[] samples = new double[numSamples];<br />
// Fill array with data<br />
for (int i = 0; i < numSamples; i++)<br />
{<br />
Samples[i] = ...;<br />
}<br />
// Set the wavelet amplitudes<br />
wavelet.Amplitudes = samples;<br />
}<br />
// Set the sample interval and domain<br />
wavelet.SampingInterval = 0.004;<br />
wavelet.Domain = Domain.ELEVATION_TIME;<br />
As mentioned in the rules listed earlier, the number of samples in the wavelet<br />
will be rounded up to the next power of two and the ends of the wavelet will<br />
be padded with zeros. In the following figure you can see a 13 sample<br />
wavelet provided by the user. The length of the wavelet is rounded up to 16<br />
samples, and the center of the original data is placed at the sample in the<br />
middle of the 16 sample (16 / 2 = 8).<br />
Fig. 4-21 Wavelet Storage<br />
When the SampleCount property for the wavelet is checked, the value will<br />
be the power of two value to which the wavelet was rounded.<br />
4: Seismic and the Geophysical Domain 107<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Seismic Interpretation<br />
Seismic interpretation produces references in the seiesmic cube space to<br />
seismic reflectors recognized by the geophysicist. These reflectors mark<br />
geological or rock property changes that the interpreter will tie with its<br />
structural model.<br />
Each pick in the seismic domain is represented by a point in XYZ space, the<br />
Z dimension being generally two-way time. The interpretation objects are<br />
collections of such points.<br />
Fig. 4-22 Set of Points Contributing to a Horizon Interpretation<br />
Grid Usage<br />
3D horizon interpretation is based on grids defined by the 3D seismic survey.<br />
The underlying lattice defines the X/Y geometry pattern (that is, the origin,<br />
rotation angle, and axis extents of the grid). To access points defined along<br />
the 3D grid of a given survey, we have to distinguish between parts of the<br />
interpretation that have been defined on different 3D surveys.<br />
2D horizon interpretation is just a collection of points following seismic<br />
lines. The API collects 2D interpretation point sets per 2D survey to ease<br />
navigation.<br />
In the case of 3D horizon interpretation, the lattice will be conformant with<br />
the seismic dataset on which the interpretation is built. The interpretation<br />
instances give access to lattice information, so that specific horizon points<br />
may be accessed for a particular grid I, J, K reference. Furthermore, the API<br />
will return the coordinates of each interpreted point.<br />
108 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic Interpretation<br />
This lattice navigation for 3D interpretation is new in <strong>Ocean</strong> 2007.1, as<br />
lattice conformance is only implemented in <strong>Petrel</strong> 2007.1 and following<br />
versions. Legacy interpretation instances cannot be accessed from I, J, K<br />
references, although the user has the choice to convert old interpretation data<br />
to <strong>Petrel</strong> 2007.1 conformant interpretation instances (as long as the points<br />
coincide with some seismic survey definition).<br />
Seismic Interpretation<br />
Classes<br />
Seismic interpretation is grouped under folders exposed in the <strong>Ocean</strong> API as<br />
InterpretationCollection instances.<br />
The interpretation instances are then separated in FaultInterpretation<br />
and HorizonInterpretation. Below these we find groups of points<br />
belonging to different seismic surveys (only for horizons, faults are<br />
undifferentiated) and property instances.<br />
InterpretationCollection<br />
FaultInterpretation<br />
HorizonInterpretation<br />
HorizonInterpretation3D<br />
HorizonInterpretation2D<br />
filtering<br />
SeismicCollection<br />
IRegularHeightField<br />
navigation<br />
LineGeometry<br />
LatticeInfo<br />
Fig. 4-23 Seismic Interpretation Classes<br />
InterpretationCollectio<br />
n<br />
All interpretation instances must belong to a folder in the <strong>Petrel</strong> Input data<br />
tree of the type InterpretationCollection.<br />
The InterpretationCollection is the containment folder of all seismic<br />
interpretation instances.<br />
These folders are nested but not rooted under a single folder. This is why an<br />
IEnumerable is retrieved from the<br />
SeismicProject.<br />
4: Seismic and the Geophysical Domain 109<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Fig. 4-24 Nesting of seismic data and interpretation folders in <strong>Petrel</strong> Input<br />
tree<br />
InterpretationCollection folders are retrieved from the <strong>Petrel</strong> project<br />
in one of two ways.<br />
• From the SeismicRoot static instance, for legacy interpretation folders<br />
(containing interpretation that is not linked to seismic surveys).<br />
• From the SeismicProject unique instance, if it exists in the project,<br />
for all seismic interpretation that is related to seismic datasets (via<br />
SeismicCollection instances).<br />
The top folder in the interpretation collection hierarchy, in both cases, is held<br />
in the InterpretationCollections property in SeismicRoot or<br />
SeismicProject. The following example browses through<br />
110 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic Interpretation<br />
InterpretationCollection instances in the project, looking through<br />
folders and down each sub-folder for FaultInterpretation instances.<br />
using System.Collections.Generic;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
SeismicRoot sr = SeismicRoot.Get(<strong>Petrel</strong>Project.PrimaryProject);<br />
List listcol;<br />
IEnumerable icolcol;<br />
InterpretationCollection icol;<br />
// legacy interpretation collections<br />
icolcol = sr.InterpretationCollections;<br />
listcol = new List(icolcol);<br />
for (int i = 0; i < listcol.Count; i++)<br />
{<br />
icol = listcol[i];<br />
<strong>Petrel</strong>Logger.Info("Legacy interpretation " + icol.Name);<br />
if (icol.InterpretationCollectionCount > 0)<br />
{<br />
listcol.AddRange(icol.InterpretationCollections);<br />
}<br />
foreach (FaultInterpretation f in icol.FaultInterpretations)<br />
{<br />
<strong>Petrel</strong>Logger.Info("Fault " + f.Name);<br />
}<br />
}<br />
// <strong>Petrel</strong> 2007 interpretation collections<br />
SeismicProject sp = sr.SeismicProject;<br />
if (sp == SeismicProject.NullObject)<br />
return;<br />
listcol.Clear();<br />
listcol.AddRange(sp.InterpretationCollections);<br />
for (int i = 0; i < listcol.Count; i++)<br />
{<br />
icol = listcol[i];<br />
<strong>Petrel</strong>Logger.Info("Seismic datasets interpretation " + icol.Name);<br />
if (icol.InterpretationCollectionCount > 0)<br />
{<br />
listcol.AddRange(icol.InterpretationCollections);<br />
}<br />
foreach (FaultInterpretation f in icol.FaultInterpretations)<br />
{<br />
<strong>Petrel</strong>Logger.Info("Fault " + f.Name);<br />
}<br />
}<br />
<strong>Access</strong>ing Horizon<br />
Points<br />
Under the seismic data folder (exposed by the <strong>Ocean</strong> API as the<br />
SeismicProject instance), horizon interpretations are grouped per the<br />
seismic surveys (2D or 3D) whose datasets have been used when picking the<br />
horizon points.<br />
4: Seismic and the Geophysical Domain 111<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
However, all these points are recognized as being part of the same structural<br />
boundary and may be retrieved as a single enumerable.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
HorizonInterpretation hor = ...<br />
IPoint3Set points = hor.Points;<br />
// report the number of points<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(<br />
String.Format("horizon {0} has {1} points",<br />
hor.Name, hor.PointCount));<br />
foreach (Point3 xyz in points)<br />
{<br />
...<br />
}<br />
But the points coming from a seismic 3D survey may be distinguished from<br />
points picked on 2D lines or on other 3D surveys. This is done by using<br />
SeismicCollection instances (also located under the SeismicProject) to<br />
filter them out.<br />
Note that if several 3D surveys belong to the same hierarchy of<br />
SeismicCollection instances and therefore share the same lattice<br />
definition or use decimated lattices defined from the same original lattice in a<br />
parent collection, it is not necessary to have any distinction between their<br />
origin datasets. Therefore, when a SeismicCollection is passed as an<br />
argument to select a subset of interpretation points, the topmost parent of<br />
that collection is used.<br />
Given a SeismicCollection (2D or 3D), one can retrieve points by<br />
effectively applying the lattice filtering to the HorizonInterpretation<br />
instance. Note that the points are returned by a method and the point count<br />
is not accessible directly at this level. However, it will be accessible from the<br />
specialized instance HorizonInterpretation3D or<br />
HorizonInterpretation2D.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
SeismicCollection scol = ...<br />
// verify that the seismic collection corresponds to a 3D survey<br />
if (scol.MemberType == typeof(SeismicCube))<br />
{<br />
HorizonInterpretation hor = ...<br />
IPoint3Set points = hor.GetPoints(scol);<br />
// count the points<br />
int count = 0;<br />
foreach (Point3 pt in points) count++;<br />
<strong>Petrel</strong>Logger .InfoOutputWindow(<br />
String.Format("horizon {0} has {1} points in {2}",<br />
h.Name, count, scol.Name));<br />
foreach (Point3 xyz in points)<br />
{<br />
...<br />
}<br />
}<br />
112 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic Interpretation<br />
Each interpretation class contains a list of Property instances and a list of<br />
DictionaryProperty instances, each instance having a reference to the<br />
appropriate property version type.<br />
InterpretationCollection folders are retrieved from the project with the<br />
static method GetInterpretationCollections. The following example<br />
browses through InterpretationCollection instances in the project,<br />
looking through folders and one-level down sub-folders for<br />
FaultInterpretation instances.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
FaultInterpretation fault =<br />
FaultInterpretation.NullObject;<br />
Project p = <strong>Petrel</strong>Project.PrimaryProject;<br />
IEnumerable<br />
colcol;<br />
colcol =<br />
InterpretationCollection.GetRootInterpretationCollections(p);<br />
foreach (InterpretationCollection icx in colcol)<br />
{<br />
if (icx.FaultInterpretationCount > 0)<br />
{<br />
foreach (FaultInterpretation ifx in icx.<br />
FaultInterpretations)<br />
{<br />
fault = ifx;<br />
if (fault.IsGood)<br />
break;<br />
}<br />
break;<br />
}<br />
else if (icx.InterpretationCollectionCount > 0)<br />
{<br />
foreach (InterpretationCollection subicx in<br />
icx.InterpretationCollections)<br />
{<br />
if (subicx.FaultInterpretationCount > 0)<br />
{<br />
foreach (FaultInterpretation subifx<br />
in subicx.FaultInterpretations)<br />
{<br />
fault = subifx;<br />
if (fault.IsGood) break;<br />
}<br />
break;<br />
}<br />
}<br />
break;<br />
}<br />
}<br />
if (fault == FaultInterpretation.NullObject)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Cannot find Fault Interpretations");<br />
return;<br />
}<br />
4: Seismic and the Geophysical Domain 113<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Creating Interpretation<br />
Objects<br />
On top of the containment hierarchy, the InterpretationCollection<br />
serves also as folder data container in the Input data tree. All interpretation<br />
creation has to be done from an existing folder.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
HorizonInterpretation hor = ...;<br />
// Print the Horizon points<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(“Listing all<br />
points in Horizon “ +<br />
hor.Name);<br />
foreach (Point3 p in hor.GetPoints())<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("X= "+ p.X +<br />
“Y= "+ p.Y +" Z= "+ p.Z);<br />
}<br />
InterpretationCollection folder = hor.InterpretationCollection;<br />
hor = folder.CreateHorizonInterpretation("New horizon Interp");<br />
HorizonInterpretation<br />
Once created, the HorizonInterpretation is updated by replacing its set<br />
of points. The points are associated with the interpretation object in one<br />
function call, SetPoints. The old points are garbage collected.<br />
A HorizonInterpretation can also be modified by adding a Property<br />
object to it. The HorizonProperty instance contains a set of<br />
PointPropertyRecords. Only the value in each PointPropertyRecord<br />
can be set. The point geometry definition is inherited from the Horizon<br />
object.<br />
114 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic Interpretation<br />
HorizonProperty or DictionaryHorizonProperty instances can be<br />
added to the HorizonInterpretation object by supplying the appropriate<br />
PropertyVersion or DictionaryPropertyVersion.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
HorizonInterpretation hor = ...;<br />
IPoint3Set pts = hor.GetPoints();<br />
double x0 = ..., y0 = ...;<br />
using (ITransaction t = <strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(hor);<br />
}<br />
// Update the Horizon point set<br />
foreach (Point3 pt in pts)<br />
{<br />
if (pt.X > x0 && pt.Y > y0) pt.Z = Double.NaN;<br />
}<br />
hor.SetPoints(pts);<br />
// Create a Property on the Horizon<br />
PropertyVersion pv = ...<br />
HorizonProperty prop = hor.CreateProperty(pv);<br />
prop.Name = “New Property”;<br />
...<br />
trans.Commit();<br />
FaultInterpretation<br />
A FaultInterpretation object differs slightly from a<br />
HorizonInterpretation in that its points are arranged in a collection of<br />
polylines. This reflects the fact that the interpretation picks are chosen on<br />
Seismic sections, one polyline corresponding to one section.<br />
Note that the (Fault) Interpretation is not grid-based. Some of the<br />
points in the same section pick (same polyline) could be vertically placed on<br />
top of each other.<br />
The Fault polylines are retrieved in an enumeration. All polylines can be<br />
retrieved at once, or only the polylines picked on a given SeismicCube, or<br />
just the polylines picked on a given SeismicLine2D. The method<br />
GetPolylines is overloaded with three signatures.<br />
FaultInterpretation instances are updated by replacing their polylines.<br />
All polylines are set in one call to method SetPolylines. Like<br />
GetPolylines, SetPolylines is overloaded to replace<br />
• All polylines<br />
• Polylines picked in a SeismicCube<br />
• Polylines picked in a SeismicLine2D<br />
The three method signatures are provided for forward compatibility. In the<br />
present version, they are all equivalent.<br />
4: Seismic and the Geophysical Domain 115<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Like HorizonInterpretation, the FaultInterpretation can be<br />
modified by adding a Property object to it. In the Input data tree, the<br />
Property object will show under the containment of the FaultProperty<br />
instance. FaultProperty or FaultDictionaryProperty has to be<br />
supplied with a PropertyVersion or a DictionaryPropertyVersion<br />
instance.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
FaultInterpretation fault = ...;<br />
double x0 = ..., y0 = ...;<br />
// Update the Fault polylines<br />
using (ITransaction trans = <strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(fault);<br />
IEnumerable lineSet = fault.Polylines();<br />
Polyline3 [] newSet = new Polyline3[fault.PolylineCount];<br />
int iline = 0;<br />
foreach (Polyline3 pline in lineSet)<br />
{<br />
int count = 0;<br />
foreach (Point3 p in pline)<br />
if (p.X > x0 && p.Y > y0) count++;<br />
Point3 [] newPoints = new Point3[count];<br />
int jpoint = 0;<br />
foreach (Point3 pt in pline)<br />
{<br />
if (pt.X > x0 && pt.Y > y0)<br />
{<br />
newPoints[jpoint] = pt;<br />
jpoint++;<br />
}<br />
}<br />
newSet[iline] = new Polyline3(newPoints);<br />
iline++;<br />
}<br />
fault.SetPolylines(newSet);<br />
trans.commit();<br />
}<br />
116 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Seismic Interpretation<br />
FaultInterpretation can also be modified by adding a FaultProperty<br />
or a FaultDictionaryProperty to it. This is done by supplying the<br />
appropriate PropertyVersion or DictionaryPropertyVersion.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
FaultInterpretation fault = ...;<br />
...<br />
PropertyVersion pversion = ...;<br />
using (ITransaction trans = <strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(fault);<br />
FaultProperty prop = fault.CreateProperty(pversion);<br />
prop.Name = “New Property”;<br />
...;<br />
}<br />
PointPropertyRecord<br />
Each point-value pair in FaultProperty or HorizonProperty instances is<br />
retrieved as a PointPropertyRecord instance.<br />
PointPropertyRecord has a Geometry and Value property. Geometry is<br />
read-only and inherited from the interpretation object,<br />
HorizonInterpretation or FaultInterpretation.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Seismic;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
HorizonProperty hprop = ...;<br />
double x0 = ..., y0 = ...;<br />
foreach (PointPropertyRecord ptr in hprop)<br />
{<br />
double newValue = ...;<br />
}<br />
if (ptr.Geometry.X > x0 && ptr.Geometry.Y > y0)<br />
{<br />
ptr.Value = newValue;<br />
}<br />
DictionaryFaultProperty and DictionaryHorizonProperty objects<br />
contain DictionaryPropertyRecord instances, which have similar<br />
definitions but with a Value property that takes integer values.<br />
4: Seismic and the Geophysical Domain 117<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
118 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
5 Reservoir Modeling<br />
In This Chapter<br />
The Static Reservoir Model .........................................................................120<br />
Pillar Grid...................................................................................................121<br />
Reservoir Simulation ...................................................................................163<br />
5: Reservoir Modeling 119<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The Static Reservoir Model<br />
The static reservoir model puts together the structural model, the geological<br />
interpretation, and the properties measured in several points in the reservoir.<br />
The final result is a set of Property objects distributed throughout the pillar<br />
grid mesh.<br />
Fig. 5-1<br />
Reservoir Model<br />
These 3D properties are to the pillar grid what well logs are to the borehole<br />
trajectory. They are static properties and do not represent the evolution of<br />
fluid movement through time.<br />
120 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Pillar Grid<br />
Fig. 5-2<br />
Pillar Grid Mesh and Pillars<br />
The pillar grid is built by <strong>Petrel</strong> using private gridding algorithms and user<br />
interaction. It makes the link between the structural work, defined by<br />
surfaces and their intersections, and the static reservoir model. The pillar grid<br />
implementation has a set of 3D properties that are earth model properties<br />
distributed in the cells of the grid.<br />
The <strong>Ocean</strong> API offers an access to the pillar grid and all its structural<br />
elements (read, with limited update) and to all its dependent 3D properties<br />
(full access).<br />
The pillar grid based static model in <strong>Petrel</strong> is based on a description of the<br />
reservoir separated horizontally by horizontal surfaces and divided into<br />
segments by faults modeled as pseudo-vertical fences.<br />
The Grid object defines the geometry of the static reservoir model. The<br />
gridding algorithm produces a mesh that tries to divide the segments<br />
horizontally into square cells. Vertically the cells are divided according to the<br />
stratigraphic model and further subdivided into layers.<br />
The Pillar Grid<br />
Structure<br />
The structural elements of the pillar grid are as follows:<br />
• Horizons<br />
• Zones<br />
• Faults<br />
• Segments<br />
The geometrical elements of the pillar grid are as follows:<br />
• Cell<br />
5: Reservoir Modeling 121<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
• Pillar<br />
• Node<br />
The Cell<br />
The cells are delimited by eight points and four pillar sections, making six<br />
faces like those of a die. However, none of the faces are planar. Each face<br />
will be viewed as two triangles. The triangles are not defined by the pillar grid<br />
geometry, just the corner points.<br />
The Pillar<br />
A pillar is a set of nodes that traverses the stratigraphy column. Each node in<br />
the pillar is at the same { I, J } place in the mesh built by the gridding<br />
algorithm. A particular type of pillar is the fault pillar, which is used in<br />
designing the pillar grid.<br />
The Node<br />
A node is the most basic element of the Pillar Grid. It represents a point in<br />
space and an {i, j, K} reference in the grid.<br />
Abstract <strong>Data</strong> Types<br />
The abstract data types used to describe the geometrical elements are:<br />
• Index3 {int I, int J, int K}<br />
• Point3 {double X, double Y, double Z}<br />
Index3 serves as a 3-dimensional index for nodes and cells. It is supported<br />
by a class in the <strong>Ocean</strong> services hierarchy.<br />
namespace Slb.<strong>Ocean</strong>.Basics<br />
{<br />
class Index3<br />
{<br />
public Index3 (int I, int J, int K);<br />
int I { get; }<br />
int J { get; }<br />
int K { get; }<br />
...<br />
}<br />
}<br />
122 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Point3 is used to contain any point reference in the Pillar Grid geometry. It<br />
is supported by a class in the <strong>Ocean</strong> Services class hierarchy like all generic<br />
geometry objects and services.<br />
namespace Slb.<strong>Ocean</strong>.Geometry<br />
{<br />
class Point3<br />
{<br />
public Point3 (double X, double Y, double<br />
Z);<br />
double X { get; }<br />
double Y { get; }<br />
double Z { get; }<br />
...<br />
}<br />
}<br />
<strong>Access</strong>ing the Cells<br />
The cell is the smallest volume element in the pillar grid. Each cell is<br />
referenced by index I, J, K.<br />
The index origin (cell {0, 0, 0}) is at the shallowest lower left corner of the<br />
grid. The grid dimensions are expressed in number of I indexes, number of J<br />
indexes, and number of K indexes.<br />
Note that the <strong>Petrel</strong> user can swap the display indexes. The API always<br />
returns the native index. The next version will support transformations to<br />
the UI domain.<br />
j<br />
k<br />
i<br />
Fig. 5-3<br />
Pillar Grid Mesh in i, j, k Space<br />
5: Reservoir Modeling 123<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The Grid domain object, in<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid namespace, contains grid<br />
dimension information.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public class Grid<br />
{<br />
Index3 NumCellsIJK { get; }<br />
}<br />
}<br />
Point3 [] GetCellCorners(Index3);<br />
double GetCellVolume (Index3);<br />
...<br />
The total number of cells is the product of the number of cell indexes in all<br />
three dimensions. The numCellsIJK property in class Grid returns an<br />
integer for each axis dimension.<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
Index3 idx = g.NumCellsIJK;<br />
long numCells = idx.I * idx.J * idx.K;<br />
As cell face triangulation is not explicit, there is an API function to return<br />
the volume of an individual cell.<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
Index3 index = new Index3(2,6,1);<br />
if (g.HasCellVolume(index))<br />
{<br />
double vol = g.GetCellVolume(index);<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Cell Vol: "<br />
+ vol.ToString());<br />
}<br />
The <strong>Petrel</strong> user sees the reservoir model in X, Y, Z space. The API on the<br />
contrary, only sees a 3-dimensional array of cells. The X, Y, Z points are<br />
merely cells’ attributes. On the picture below, we see that that array is sparse.<br />
124 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Fig. 5-4<br />
3D Property in XYZ Space and IJK Space<br />
Collapsed Cells<br />
Not all cells are defined. Some cells within the IJK bounds are undefined<br />
because memory is not allocated all the way to the pillar grid corners to avoid<br />
waste. Cells can be outside the IJK range, but even if they are within range,<br />
they can be undefined. Then if they are within range and defined, they could<br />
be collapsed.<br />
Fig. 5-5<br />
Cells Outside, Cells Undefined, Cells without Volume<br />
Not all cells have volume. Cells have no volume if they have either:<br />
• Coincident top and base. The cell’s K level is close to an erosional<br />
Horizon. The layer has no thickness at that point.<br />
5: Reservoir Modeling 125<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
XYZ View<br />
IJK View<br />
Fig. 5-6<br />
Cells Collapsed Vertically<br />
• Coincident sides. The cell is next to a Fault and the mesh row or<br />
column is collapsing at that point. Since the cell array is 3-dimensional,<br />
it contains the same number of cells for each row (or column). The<br />
number of cells has to be equal to the maximum number in all rows (or<br />
columns). Thinner rows (or shorter columns) have cells with collapsed<br />
sides.<br />
XYZ View<br />
IJK View<br />
Fig. 5-7<br />
Cells Collapsed in the Horizontal Plane<br />
126 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Individual Cell Points<br />
Northwest<br />
Top<br />
Northeast<br />
Southwest<br />
Southeast<br />
Cell<br />
Base<br />
Fig. 5-8<br />
Cell and Cell Corners<br />
From individual cells we can retrieve geometrical information for all cell<br />
corners. Since the cell faces are non-planar, there are eight independent<br />
corners to each cell. The API gives access to nine points: the eight cell<br />
corners and the cell center, which is the equally weighted barycenter of these<br />
eight points.<br />
Individual points will be retrieved according to their relative position in the<br />
cell. The position is defined by elevation (Top or Base) and compass<br />
direction (Northwest, Northeast, Southwest, or Southeast). Directions to<br />
access cell corners are relative to grid IJK organization, not to geographical<br />
5: Reservoir Modeling 127<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
direction. Cell corner locations and relative directions are defined by a set of<br />
enumerations and a class.<br />
namespace SLb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject<br />
{<br />
public enum Direction<br />
{<br />
SouthWest = 0,<br />
NorthWest = 1,<br />
NorthEast = 2,<br />
SouthEast = 3,<br />
}<br />
}<br />
public enum TopOrBase<br />
{<br />
Top = 0,<br />
Base = 1,<br />
}<br />
public enum CellCorner<br />
{<br />
BaseSouthWest = 0,<br />
BaseNorthWest = 1,<br />
BaseNorthEast = 2,<br />
BaseSouthEast = 3,<br />
TopSouthWest = 4,<br />
TopNorthWest = 5,<br />
TopNorthEast = 6,<br />
TopSouthEast = 7,<br />
}<br />
public enum CellSide<br />
{<br />
None = 0,<br />
Up = 1,<br />
Down = 2,<br />
North = 3,<br />
South = 4,<br />
West = 5,<br />
East = 6,<br />
}<br />
public static class CellCornerSet<br />
{<br />
public static readonly CellCorner[] All;<br />
public static readonly CellCorner[] Base;<br />
public static readonly CellCorner[] Top;<br />
}<br />
128 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
cell viewed from above<br />
Compass North<br />
NorthEast corner node<br />
i<br />
SouthWest corner node<br />
j<br />
Fig. 5-9<br />
Cell IJK Convention<br />
The direction Southwest is towards grid origin, or IJ index {0, 0}.<br />
Northeast is towards high indexes, or IJ index { numCellsIJK.I,<br />
numCellsIJK.J }.<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
Index3 index = new Index3(2,6,1);<br />
if (g.IsCellDefined(index))<br />
{<br />
Point3 p = g.GetPointAtCell(index,<br />
Corner.NorthEast,<br />
}<br />
...<br />
TopOrBase.Top);<br />
Cell enumerations<br />
Cell corners and sides are defined by enumerations.<br />
Fig. 5-10 Cell Corners and Sides<br />
5: Reservoir Modeling 129<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The CellCorner enumeration is used to define the position of a point<br />
relative to a cell. Points occur at the corners of cells. Top is the shallowest<br />
location of the cell and Base is the deepest location of the cell.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject<br />
{<br />
public enum CellCorner<br />
{<br />
BaseSouthWest = 0,<br />
BaseNorthWest = 1,<br />
BaseNorthEast = 2,<br />
BaseSouthEast = 3,<br />
TopSouthWest = 4,<br />
TopNorthWest = 5,<br />
TopNorthEast = 6,<br />
TopSouthEast = 7<br />
}<br />
}<br />
The CellSide enumeration is used to define the sides of the cell.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject<br />
{<br />
public enum CellSide<br />
{<br />
None = 0,<br />
Up = 1,<br />
Down = 2,<br />
North = 3,<br />
South = 4,<br />
West = 5,<br />
East = 6<br />
}<br />
}<br />
The Corner enumeration is used to define the corners of a cell without<br />
considering the Top or Base.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject<br />
{<br />
public enum Corner<br />
{<br />
NorthEast = 0,<br />
SouthEast = 1,<br />
SouthWest = 2,<br />
NorthWest = 3<br />
}<br />
}<br />
130 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
The TopOrBase enumeration is used to define the vertical position relative to<br />
the grid. Top is the shallowest location of the cell and Base is the deepest<br />
location of the cell.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject<br />
{<br />
public enum TopOrBase<br />
{<br />
Top = 0,<br />
Base = 1<br />
}<br />
}<br />
The CellCornerSet class defines the CellCorners for a cell.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject<br />
{<br />
public static class CellCornerSet<br />
{<br />
public static readonly CellCorner[] All;<br />
public static readonly CellCorner[] Base;<br />
public static readonly CellCorner[] Top;<br />
}<br />
}<br />
All represents all cell corners, Base represents cell base corners, and Top<br />
represents cell top corners.<br />
The CellCornerSet class is used by the Grid.GetCellCorners method to<br />
get the positions of the corners of a cell as a Point3 array.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
Index3 index = new Index3(10, 20, 30);<br />
Point3 c[] = g.GetCellCorners(index, CellCornerSet.Top);<br />
...<br />
Node <strong>Access</strong><br />
Nodes also can be accessed by IJK index. A node can be seen as a level in a<br />
pillar of the Pillar Grid. It is also placed at a corner of a cell. Nodes and cells<br />
can be outside the range or undefined.<br />
We will see later in the description of a fault in the pillar grid the special case<br />
of nodes on a faulted pillar.<br />
5: Reservoir Modeling 131<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Nodes viewed from above.<br />
j<br />
(i=0, j=0)<br />
i<br />
Some node IJK’s<br />
can point to<br />
to undefined areas.<br />
Fig. 5-11 Node <strong>Access</strong><br />
Node Index versus Cell Index<br />
We have one more index per dimension for nodes, when compared with cell<br />
indexes. The cell IJK index corresponds to the IJK index of its shallowest<br />
southwest corner.<br />
132 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Node<br />
(0,2,0)<br />
Node<br />
(0,1,0)<br />
Node<br />
(0,0,0)<br />
Node<br />
(0,0,1)<br />
Cell<br />
(0,0,0)<br />
Cell<br />
(1,0,0)<br />
Cell<br />
(1,1,0)<br />
Node<br />
(2,2,0)<br />
k axis<br />
Cell<br />
(1,1,1)<br />
Node<br />
(0,0,2)<br />
Cell<br />
(0,0,1)<br />
i axis<br />
Cell<br />
(1,0,1)<br />
j axis<br />
Fig. 5-12 Node Indexes Relative to Cells<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
Index3 index = new Index3(2,6,1);<br />
if (!g.IsNodeInside(index)) return;<br />
if (g.IsNodeDefined(index,<br />
Direction.NorthEast))<br />
{<br />
Point3 p = g.GetPointAtNode(index,<br />
Direction.NorthEast);<br />
...<br />
}<br />
The example shows that we need to specify a Direction on the<br />
GetPointAtNode method. We will see why this is important when we look at<br />
Fault pillars.<br />
Pillar <strong>Access</strong><br />
Grid geometry can also be accessed by IJ, meaning by two-dimensional<br />
index. Each IJ tuple within range will potentially correspond to a grid pillar.<br />
The IJ reference can be inside or not and can correspond to a pillar or not.<br />
The method IsNodeInside is overridden with Index2 as a node index to<br />
5: Reservoir Modeling 133<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
indicate a pillar index. A pillar is defined when method HasNodePillar<br />
returns true, again supplying an Index2 to specify the pillar.<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
Index2 index = new Index2 (2,6);<br />
if (g.IsNodeInside(index))<br />
{<br />
if (g.HasNodePillar(index))<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(“Pillar<br />
exists at Index {2, 6}”);<br />
}<br />
...<br />
}<br />
Pillar Grid Domain<br />
Objects<br />
*<br />
1<br />
*<br />
*<br />
*<br />
Grid<br />
Fault<br />
Segment<br />
Horizon<br />
Zone<br />
*<br />
*<br />
Property<br />
DictionaryProperty<br />
* *<br />
FaultProperty<br />
*<br />
FaultPropertyRecord<br />
Fig. 5-13 Pillar Domain Classes<br />
The Grid domain object carries reference to other domain objects that<br />
describe the Pillar Grid:<br />
Structural elements:<br />
• Fault<br />
• Segment<br />
• Horizon<br />
• Zone<br />
These structural elements describe earth model entities that compose the<br />
pillar grid. To these entities are attached properties, carried by other domain<br />
objects. In the <strong>Petrel</strong> domain model, geometrically defined entities and earth<br />
134 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Browsing through Pillar<br />
Grids in Models Tree<br />
properties are described in different objects. The borehole object, for<br />
instance, carries the well geographical description and the definition of its<br />
trajectory, while the well log object carries earth properties measured along<br />
the wellbore.<br />
Similarly, we have lists of domain objects describing earth properties, which<br />
are attached to the Grid object:<br />
• Property<br />
• DictionaryProperty<br />
• FaultProperty<br />
When browsing through the “Models” tree of a <strong>Petrel</strong> project, one can access<br />
all Grids in the project. This is done by accessing first the root list of model<br />
collections in the tree. The Project class ModelCollections property<br />
contains a collection of ModelCollecion objects in the project.<br />
One can retrieve the list of Pillar Grids attached to a model collection with<br />
the PillarGridRoot class method GetGrids, which takes a<br />
ModelCollection instance as argument.<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
using System.Collections.Generic;<br />
// Get all model collections from the project.<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
IEnumerable mclist = proj.ModelCollections;<br />
// Process each model collection in the list<br />
foreach ( ModelCollection mc in mclist )<br />
{<br />
// Tell us which collection it is.<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Found Model Collection " + mc.Name);<br />
}<br />
// List the name of each grid in the collection.<br />
int i = 0;<br />
foreach ( Grid g in PillarGridRoot.GetGrids( mc ) )<br />
{<br />
i++;<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Grid no "+ i +" "+ g.Name);<br />
}<br />
5: Reservoir Modeling 135<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Fault<br />
Fig. 5-14 Fault Planes, Pillar Based<br />
A fault is a surface that traverses the reservoir volume. The fault surface is<br />
modeled by a set of pillars which traverse the pillar grid at a single IJ index<br />
reference in <strong>Petrel</strong> static reservoir modeling.<br />
Pillars can be vertical, straight, or curved, but they always join nodes of same<br />
IJ index. We have to retrieve the position of each node along the pillar for<br />
the exact geometry of the pillars.<br />
The Fault object only carries a list of pillar references. The points along the<br />
fault are retrieved by accessing the node points from the Grid object.<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid grid = ...;<br />
foreach (Fault fault in grid.Faults)<br />
{<br />
foreach (Index2 i in fault.Nodes)<br />
{<br />
...<br />
}<br />
}<br />
Fault nodes have the particularity of cutting horizons at more than one point.<br />
Along a Fault we thus retrieve more points than there are levels in the pillar<br />
grid.<br />
136 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
The blue pillar<br />
has 3 different<br />
depth levels for<br />
the same level<br />
Fig. 5-15 Multiple Nodes for One IJK<br />
SE and NE cells connect to the same point<br />
Each node defined from its IJK index corresponds to a unique point in<br />
space, but when the node is on a Fault pillar, the point is unique only when<br />
viewed from the quadrant in which the observer places him or herself.<br />
In the picture below, the pillar traverses four levels but encounters eight<br />
nodes. For each IJK index one has to specify which side of the Fault it is<br />
placed in to retrieve a unique point in space.<br />
Fig. 5-16 Why a Single IJK Corresponds to Multiple Nodes<br />
5: Reservoir Modeling 137<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The API will return a depth for an IJK node when the quadrant is specified.<br />
This is why the GetPointAtNode method requires a Direction argument;<br />
the node could sit on a pillar.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
Using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Fault f = ...;<br />
Grid g = f.Grid;<br />
foreach (Index2 pillar in f.Nodes)<br />
{<br />
int K = g.NumCellsIJK.K / 2;<br />
Index3 index = new Index3(pillar.I,<br />
pillar.J, K);<br />
Point3 p = g.GetPointAtNode(index,<br />
Direction.NorthEast);<br />
...<br />
}<br />
Fault Direction<br />
The API returns a set of Fault directions at each pillar. The Grid method<br />
GetFaultedDirections returns an array of up to four directions at each<br />
pillar. When the pillar is faulted, there are two, three, or four directions<br />
across the fault, depending on whether the fault is simple, branching, or<br />
crossing.<br />
The following code will return the maximum throw observed at a given<br />
Horizon on the pillars following a given Fault.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject;<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Fault f = ...;<br />
Horizon h = ...;<br />
Grid g = f.Grid;<br />
if (h.Grid != g)<br />
{<br />
<strong>Petrel</strong>Logger.ErrorBox("Pick Horizon/Fault<br />
from same Pillar Grid");<br />
return;<br />
}<br />
138 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
foreach (Index2 pillar in f.Nodes)<br />
{<br />
Direction[] a =<br />
g.GetFaultedDirections(pillar);<br />
if (a.Length >= 2)<br />
{<br />
Index3 node = new Index3(pillar.I,<br />
pillar.J, h.K);<br />
double max_depth = 0, min_depth =<br />
double.MaxValue;<br />
foreach (Direction dir in a)<br />
{<br />
double depth = - g.GetPointAtNode(node,<br />
dir).Z;<br />
if (depth < min_depth) min_depth =<br />
depth;<br />
if (depth > max_depth) max_depth =<br />
depth;<br />
}<br />
double fault_throw = max_depth -<br />
min_depth;<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Fault<br />
throw at node (" +<br />
pillar.I + ", " + pillar.J + ") = " +<br />
fault_throw);<br />
}<br />
}<br />
Segment<br />
A segment is a contiguous block of cells bounded by fault surfaces.<br />
Fig. 5-17 Segments in a Pillar Grid<br />
The set of segments in the reservoir model creates a partition of the<br />
reservoir volume.<br />
• Each cell belongs to only one segment.<br />
• Two cells in the same segment can be joined by a path that does not<br />
cross any fault surface.<br />
• Any path that joins two cells in different segments will cross at least one<br />
fault surface.<br />
5: Reservoir Modeling 139<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The Segment class offers a number of methods to check whether a cell or<br />
node is part of that Segment.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public class Segment<br />
{<br />
public Grid Grid { get {};}<br />
public bool IsCellInside(Index2);<br />
public bool IsNodeInside(Index2,<br />
Direction);<br />
}<br />
}<br />
public IDescription Description { get {}; }<br />
...<br />
The method IsCellInside takes an Index2 as an argument, which<br />
specifies a cell column rather than a cell. This is because if a cell is inside a<br />
Segment, all the cells of that same IJ column are also part of the same<br />
Segment. Faults that separate segments are built with pillars that traverse the<br />
grid at a constant IJ index. Truncated faults join other faults by collapsing<br />
cells between their respective IJ indexes from the truncation point onward.<br />
Therefore two cells with the same IJ index cannot be in different segments.<br />
This is exposed by specifying only an IJ index in the<br />
Segment.IsCellInside method.<br />
The method Segment.IsNodeInside takes a pillar argument (IJ node index)<br />
and a Direction. The Direction is needed for pillars which are on the<br />
edge of the segment, as one direction may show the node inside and another<br />
direction may show the node outside the segment.<br />
<strong>Access</strong>ing Segment from Grid<br />
Segments can be accessed from the Grid by specifying a starting cell. The<br />
method GetSegmentAtCell is found in the Grid class.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
140 <strong>Ocean</strong> Application Development Framework 2007.1<br />
Grid g = ...;<br />
Index2 idx = new Index2(28, 89)<br />
Segment s = g.GetSegmentAtCell(idx)<br />
for (int i = 0; i < g.NumCellsIJK.I; i++)<br />
{<br />
for (int j = 0; j < g.NumCellsIJK.J; j++)<br />
{<br />
Index2 c = new Index2(i, j);<br />
if (s.IsCellInside(c))<br />
{<br />
...<br />
}<br />
}<br />
}<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Horizon<br />
A horizon marks the place in the reservoir model where a geological event<br />
occurred.<br />
Fig. 5-18 Horizon in a Pillar Grid<br />
Horizon is a surface, a volume boundary. It does not represent a layer in the<br />
reservoir but an interface between two layers.<br />
In the pillar grid, a horizon is fully characterized by a single K level across<br />
the entire model.<br />
From the Grid object, one can list the Horizons created in the model and<br />
retrieve their K indexes.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
Using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
foreach (Horizon h in g.Horizons)<br />
{<br />
string msg = String.Format("Name: {0}, K:<br />
{1}”,<br />
h.Name, h.K);<br />
...<br />
}<br />
Zone<br />
A zone is an interval between two Horizons.<br />
Zones are organized in a two-level hierarchy. The top level zones separate the<br />
reservoir between two horizons. Sub-zones further subdivide zones into finer<br />
intervals. The last refinement is done at the cell level.<br />
5: Reservoir Modeling 141<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Fig. 5-19 Zones and Horizons in the <strong>Petrel</strong> <strong>Data</strong> Domain<br />
A zone corresponds to two levels in the pillar grid, a top and a base. The<br />
Zone class returns both levels and allows navigation in the zone hierarchy.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public class Zone<br />
{<br />
public Grid Grid { get; }<br />
public IDescription Description {<br />
get; }<br />
public Zone<br />
ParentZone { get;<br />
}<br />
public IEnumerable Zones {<br />
get; }<br />
public int ZoneCount { get;<br />
}<br />
}<br />
}<br />
}<br />
}<br />
public int TopK { get;<br />
public int BaseK { get;<br />
...<br />
142 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Zones can be accessed directly from the Grid object.<br />
Pillar Grid Fault Tracing<br />
Example<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
Grid g = ...;<br />
IEnumerable zones = g.Zones;<br />
foreach (Zone zone in zones)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(String.Format(<br />
"(Top={0}, Base={1})",<br />
zone.TopK, zone.BaseK));<br />
}<br />
if (zone.ZoneCount > 0)<br />
{<br />
// process sub zones<br />
}<br />
In this coding example, we follow the nodes of a fault and create a fault<br />
displacement property where we store the horizon gaps measured across the<br />
fault pillars.<br />
This deals with cell geometry and orientation. The cell whose property value<br />
gets filled is always to the right of the fault pillar path while traversing the<br />
fault pillars in order.<br />
using Slb.<strong>Ocean</strong>.Basics;<br />
using Slb.<strong>Ocean</strong>.Geometry;<br />
using<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid;<br />
protected override void<br />
InvokeSimpleCore(FaultTrace.Arguments args)<br />
{<br />
if (args == null)<br />
throw new<br />
ArgumentNullException("argumentPackage is<br />
null");<br />
// Input argument: PillarGrid.Fault f<br />
// Output argument: PillarGrid.Property<br />
outProp<br />
Fault f = args.PillarGridFault;<br />
Grid g = f.Grid;<br />
Property outProp = args.OutputProperty;<br />
5: Reservoir Modeling 143<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The first thing is to create a “Fault Displacement” PropertyVersion and<br />
use it to create the 3D property. Get the template from the<br />
<strong>Petrel</strong>UnitSystem template group for FaultProperty objects.<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
if (outProp == null)<br />
{<br />
trans.Lock(g);<br />
ITemplate ptemp =<br />
<strong>Petrel</strong>UnitSystem.TemplateGroupFaultProperty.<br />
FaultDisplacement;<br />
IPropertyVersionService pvs;<br />
pvs =<br />
<strong>Petrel</strong>System.PropertyVersionService;<br />
IPropertyVersionContainer pvc = null;<br />
PropertyVersion pv =<br />
pvs.FindOrCreate(pvc, ptemp);<br />
outProp = g.CreateProperty(pv);<br />
outProp.Name = "Fault Displacement";<br />
g.AddProperty(outProp);<br />
}<br />
else trans.Lock(outProp);<br />
We are going to loop through the Fault nodes, keeping the last one in<br />
variable node and the current loop variable in next_node. We need two<br />
nodes to find a fault “cell.” Skip over the first node to have an interval in<br />
each loop.<br />
In the figure below you can see that the fault cell will be defined as the cell<br />
to the right of two concurrent nodes going from one node to the next in the<br />
fault.<br />
Node n + 1<br />
Node n<br />
Fig. 5-20<br />
144 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
float undef = float.NaN;<br />
float displacement = undef;<br />
int K;<br />
Vector3 gap = new Vector3(0, 0, 0);<br />
IEnumerable nodes = f.Nodes;<br />
IEnumerable horizons =<br />
g.Horizons;<br />
Index2 node = null;<br />
Index3 cell;<br />
foreach (Index2 next_node in nodes)<br />
{<br />
// skip over first node,<br />
// need node and next_node to frame<br />
interval<br />
if (node == null) { node = next_node;<br />
continue; }<br />
Inside the loop, check whether the Fault runs along the I axis (abcissa) or<br />
the J axis (ordinate). Also check whether the Fault is “increasing” or<br />
“decreasing” in IJ; this will determine which cell is “to the right of ” the edge<br />
from node to next_node.<br />
K = 0;<br />
// check whether the fault is along<br />
ordinates or abcissae<br />
bool fault_in_ord = node.I ==<br />
next_node.I;<br />
// check whether the nodes increase in<br />
I,J or not<br />
bool fault_increasing;<br />
fault_increasing =<br />
(node.I+node.J)
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Determining which cell is “right of ” the Fault edge depends on the axis<br />
alignment (ordinal or abscissa) and direction (increasing or decreasing).<br />
Fig. 5-21<br />
Cell next to Fault<br />
is (I-1,J)<br />
Fault going West<br />
Cell next to Fault<br />
is (I-1,J-1)<br />
Node n to Node n+1<br />
Fault going North<br />
Cell next to Fault is (I,J)<br />
Node n (I,J)<br />
Fault going East<br />
Fault going South<br />
Cell next to Fault is (I,J-1)<br />
Fault can be increasing or decreasing and moving along the I or the J axis.<br />
// determine in which cell (immediately<br />
right of fault<br />
// plane going from node to next_node)<br />
to set the property<br />
// going north, cell right of fault is<br />
node.I,node.J,horizon.K<br />
// going east, cell right of fault is<br />
I, J-1, K<br />
// going south, cell right of fault is<br />
I-1, J-1, K<br />
// going west, cell right of fault is<br />
I-1, J, K<br />
cell = (fault_increasing) ?<br />
((fault_in_ord) ? new Index3<br />
(node.I, node.J, horizon.K) :<br />
new Index3 (node.I, node.J - 1,<br />
horizon.K))<br />
:<br />
((fault_in_ord) ? new Index3<br />
(node.I-1,node.J-1,horizon.K):<br />
new Index3 (node.I - 1, node.J,<br />
horizon.K));<br />
Skip the last Horizon; we will compute displacement in horizon intervals.<br />
When we skip the last horizon, we still have to fill all the layers between the<br />
last Horizon and the current one. This is done with the last value of<br />
displacement.<br />
// Do not write below the deepest K<br />
level<br />
if (horizon.K >= g.NumCellsIJK.K)<br />
{<br />
// Fill intermediate zones with top<br />
Horizon displacement<br />
while (K < horizon.K)<br />
{<br />
outProp[cell.I, cell.J, K] =<br />
displacement;<br />
K++;<br />
}<br />
// bottom Horizon, exit loop and go<br />
to next fault node<br />
continue;<br />
}<br />
146 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Now we have to select the node with which we will compute the<br />
displacement.<br />
Fig. 5-22<br />
We need a node IJK and a direction to point to each side of the fault.<br />
// level left of fault<br />
// fault going north: north west of<br />
node<br />
// fault going south: south east of<br />
node<br />
// fault going east: north east of<br />
node<br />
// fault going west: south west of<br />
node<br />
Point3 level_before =<br />
g.GetPointAtNode(nodeIJK,<br />
(fault_in_ord) ?<br />
((fault_increasing) ?<br />
Direction.NorthWest :<br />
Direction.SouthEast)<br />
:<br />
((fault_increasing) ?<br />
Direction.NorthEast :<br />
Direction.SouthWest));<br />
// From node, the cell right of fault<br />
is in the direction:<br />
// if fault is going north: north east<br />
// if fault is going south: south west<br />
// if fault is going east: south east<br />
// if fault is going west: north west<br />
Direction direction = (fault_in_ord) ?<br />
((fault_increasing) ?<br />
Direction.NorthEast :<br />
Direction.SouthWest)<br />
:<br />
((fault_increasing) ?<br />
Direction.SouthEast :<br />
Direction.NorthWest);<br />
We now need to skip collapsed cells, by going to the next cell “right of ” the<br />
cell next to the node edge, if the cell immediately next to the Fault has<br />
coincident sides. Note that the cell could be collapsed vertically and have no<br />
volume, but still have a non-null vertical projection. We will skip vertically<br />
collapsed cells, but our process will traverse all layers between the current<br />
horizon and the next.<br />
5: Reservoir Modeling 147<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Skipping collapsed cells is done by adding the proper “step” to the currently<br />
selected cell.<br />
// if cell is empty, look for one with<br />
thickness in I or J<br />
// start from node<br />
nodeIJK = new Index3 (node.I, node.J,<br />
horizon.K);<br />
if (!g.HasCellVolume(cell))<br />
{<br />
// which way to go to skip over<br />
empty cells?<br />
// fault going north: increase I<br />
// fault going south: decrease I<br />
// fault going east: decrease J<br />
// fault going west: increase J<br />
Index3 step = (fault_in_ord) ?<br />
((fault_increasing) ? new Index3<br />
(1, 0, 0) :<br />
new Index3(-1, 0, 0))<br />
:<br />
((fault_increasing) ? new Index3<br />
(0, -1, 0) :<br />
new Index3(0, 1, 0));<br />
Point3 x1 =<br />
g.GetPointAtNode(nodeIJK, direction);<br />
Point3 x2 = g.GetPointAtNode(nodeIJK +<br />
step, direction);<br />
while (x1 == x2)<br />
{<br />
cell = new Index3 (cell + step);<br />
nodeIJK = new Index3 (nodeIJK +<br />
step);<br />
x1 = x2;<br />
x2 = g.GetPointAtNode(nodeIJK +<br />
step, direction);<br />
}<br />
}<br />
148 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Now compute displacement between cells on each side of the Fault.<br />
// level right of fault, after<br />
skipping collapsed cells<br />
Point3 level_after =<br />
g.GetPointAtNode(nodeIJK, direction);<br />
gap = level_before - level_after;<br />
displacement =<br />
(float)System.Math.Abs(gap.Z);<br />
// Fill the intermediate zones with<br />
top Horizon displacement<br />
while (K < horizon.K)<br />
{<br />
outProp[cell.I, cell.J, K] =<br />
displacement;<br />
K++;<br />
}<br />
K = horizon.K;<br />
}<br />
// loop to next node in Fault<br />
node = next_node;<br />
}<br />
args.OutputProperty = outProp;<br />
trans.Commit();<br />
}<br />
return;<br />
}<br />
The figure below shows the result of running the fault tracing algorithm<br />
explained above.<br />
Fig. 5-23 Pillar Grid Fault tracing<br />
3D Property<br />
In the reservoir model, we call property, or 3D property, the distribution of<br />
an earth property in the Pillar Grid cells.<br />
5: Reservoir Modeling 149<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Fig. 5-24 3D Property<br />
Each cell will return an individual value for that Property object.<br />
Property can contain a measurement quantity, like porosity, permeability of<br />
fluid content, or an index into an enumerated table of symbols, such as facies<br />
distributions.<br />
There are two classes of Property to hold grid data.<br />
• Property for measurement quantities, stored in double precision<br />
floating point numbers.<br />
• DictionaryProperty, stored as integer.<br />
The DictonaryProperty class is named that way because the integer values<br />
it stores are entries in a dictionary of symbols represented by a string of<br />
characters. The integer values have to be continuous and represent the index<br />
into the symbol enumeration. The DictionaryProperty values correspond<br />
to a DictionaryPropertyVersion classification.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public class Grid<br />
{<br />
Property CreateProperty(PropertyVersion<br />
pv);<br />
DictionaryProperty<br />
CreateDictionaryProperty(DictionaryPropertyV<br />
ersion v);<br />
...<br />
void AddProperty(Property);<br />
void AddDictionaryProperty(DictionaryProperty);<br />
...<br />
}<br />
}<br />
150 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
<strong>Access</strong>ing Property Values<br />
<strong>Access</strong>ing Property values is done by indexing directly the class instance,<br />
using the C# indexer member type. Indexer declarations are similar to<br />
property declarations, with the main differences being that indexers are<br />
nameless (the “name” used in the declaration is this, since this is being<br />
indexed) and that indexers include indexing parameters. The indexing<br />
parameters are provided between square brackets.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public class Property<br />
{<br />
public float this [Index3] { get; set; }<br />
public float Min { get; }<br />
public float Max { get; }<br />
public IEnumerable<br />
GetAllUpscaledCells (bool);<br />
...<br />
}<br />
}<br />
The same definition is provided for the DictionaryProperty class. The<br />
DictionaryProperty class also includes a method to add a symbol to the<br />
enumerated symbol table. This is done by adding a string-valued symbol, as<br />
the index will automatically get the next available one, augmenting the<br />
enumeration by one.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
}<br />
}<br />
public class DictionaryProperty<br />
{<br />
public int this [Index3] { get; set; }<br />
public int Min { get; }<br />
public int Max { get; }<br />
public int AddFaciesCode (string);<br />
...<br />
5: Reservoir Modeling 151<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
The following example shows how to create an upscaled copy of a<br />
Property. This is using a Transaction object to allow data creation in the<br />
<strong>Petrel</strong> project.<br />
Grid grid = ...;<br />
Property myProperty = ...;<br />
using (ITransaction t =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
t.Lock(grid);<br />
Property outProp =<br />
grid.CreateProperty(myProperty.PropertyVersi<br />
on);<br />
grid.AddProperty(outProp);<br />
IEnumerable upCells =<br />
myProperty.GetAllUpscaledCells(true);<br />
foreach(Index3 index3 in upCells)<br />
{<br />
outProp[index3] = myProperty[index3];<br />
}<br />
t.Commit();<br />
}<br />
FaultProperty<br />
Fault properties are used in <strong>Petrel</strong> to characterize fault transmissibility before<br />
the static reservoir model is fed to the simulation applications. They are<br />
contained in FaultProperty objects which are found in the <strong>Petrel</strong> Model<br />
tree in the pillar grid Fault Properties folder. FaultProperty objects have a<br />
PropertyVersion controlling their data type, and there can be only one<br />
FaultProperty of any given type in the folder. FaultProperty objects are<br />
different from general Property distribution in that they assign values to<br />
cells along the Fault pillars.<br />
public sealed class FaultProperty : FacadeConvenience,<br />
IDomainObject,<br />
IDescriptionSource<br />
{ ...<br />
public Fault Fault { get; }<br />
public IEnumerable FaultFaceProperties { get;<br />
}<br />
public int FaultFacePropertyCount { get; }<br />
public float Max { get; }<br />
public float Min { get; }<br />
public string Name { get; }<br />
public PropertyVersion PropertyVersion { get; }<br />
public void Delete ( );<br />
}<br />
152 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
Fault properties are enumerations of FaultPropertyRecord instances. Each<br />
FaultPropertyRecord contains a cell IJK index and a Property value.<br />
public sealed class FaultFaceProperty : ILockObjectProvider<br />
{<br />
public FaultFace Face { get; }<br />
public float Value { get; set; }<br />
}<br />
public struct FaultFace<br />
{ ...<br />
public Index3 Cell1 { get; }<br />
public Index3 Cell2 { get; }<br />
public Point3 FaceCenter { get; }<br />
public Point3[] FaceCorners { get; }<br />
}<br />
Here is an example that creates a FaultProperty that contains the data<br />
from a seismic cube that intersects a fault. The input arguments to the<br />
workstep include a Fault and a SeismicCube.<br />
protected override void InvokeSimpleCore(Arguments argumentPackage)<br />
{<br />
Fault f = argumentPackage.Fault;<br />
SeismicCube c = argumentPackage.Cube;<br />
Grid g = fault.Grid;<br />
if (f == Fault.NullObject || c == SeismicCube.NullObject)<br />
{<br />
<strong>Petrel</strong>Logger.ErrorBox("Fault and seismic cube required.");<br />
return;<br />
}<br />
// If the user didn't specify a PropertyVersion<br />
// then we'll create one...<br />
PropertyVersion pv = argumentPackage.FaultPropertyVersion;<br />
if (null == pv)<br />
{<br />
PropertyVersionService pvs = <strong>Petrel</strong>System.PropertyVersionService;<br />
pv = pvs.FindOrCreate(g.ModelCollection.FaultPropertyVersions,<br />
c.PropertyVersion.UnitMeasurement);<br />
argumentPackage.FaultPropertyVersion = pv;<br />
}<br />
// Look for an existing FaultProperty based on the propertyVersion.<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.FaultProperty fp;<br />
fp = f.GetProperty(pv);<br />
using (ITransaction tr = <strong>Data</strong>Manager.NewTransaction( ))<br />
{<br />
if (fp ==<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.FaultProperty.NullObject)<br />
{<br />
tr.Lock(f);<br />
try<br />
{<br />
5: Reservoir Modeling 153<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
fp = f.CreateProperty(pv);<br />
}<br />
catch (FaultPropertyException e)<br />
{<br />
<strong>Petrel</strong>Logger.ErrorBox("Unable to create Fault Property.\n",<br />
e);<br />
tr.Abandon();<br />
return;<br />
}<br />
}<br />
tr.Lock( fp );<br />
// Create indices for accessing the seismic cube...<br />
Index3 traceTop = new Index3();<br />
Index3 traceBot = new Index3();<br />
Index3 valIndex = new Index3();<br />
// Initialize variables for the max i,j,k. Not using<br />
// NumSamplesIJK inside loop can improve the performance<br />
// by an order of magnitude...<br />
int traceMaxI = c.NumSamplesIJK.I - 1;<br />
int traceMaxJ = c.NumSamplesIJK.J - 1;<br />
int traceMaxK = c.NumSamplesIJK.K - 1;<br />
foreach (FaultFaceProperty ffp in fp.FaultFaceProperties)<br />
{<br />
Point3 facePoint = ffp.Face.FaceCenter;<br />
IndexDouble3 idx = c.IndexAtPosition( facePoint );<br />
// Set indices for accesiing seismic trace at the facePoint...<br />
traceTop.I = (int)Math.Round(idx.I);<br />
traceTop.J = (int)Math.Round(idx.J);<br />
traceTop.K = 0;<br />
}<br />
traceBot.I = traceTop.I;<br />
traceBot.J = traceTop.J;<br />
traceBot.K = traceMaxK;<br />
if ((double.IsNaN(idx.I) || double.IsNaN(idx.J) ||<br />
double.IsNaN(idx.K)) || (traceTop.I > traceMaxI) ||<br />
(traceTop.J > traceMaxJ))<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Point " +<br />
facePoint.ToString() +<br />
" outside cube.");<br />
continue;<br />
}<br />
// Get the seismic trace at this i,j...<br />
ISubCube sc = c.GetSubCube(traceTop, traceBot);<br />
// Get the seismic value at the location on the fault face...<br />
valIndex.I = traceTop.I;<br />
valIndex.J = traceTop.J;<br />
valIndex.K = (int)Math.Round(idx.K);<br />
float sampleVal = sc[valIndex];<br />
// Set the fault face property...<br />
ffp.Value = sampleVal;<br />
}<br />
}<br />
tr.Commit( );<br />
154 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
When reading the FaultProperty, the geometry information is contained<br />
with the FaultFace and the property value is contained in the<br />
FaultFaceProperty object. Here is an example reading the<br />
FaultProperty objects for a fault.<br />
Fault f = ...;<br />
double avgVal = 0;<br />
int count = 0;<br />
// Process fault properties in fault<br />
foreach (FaultProperty fp in f)<br />
{<br />
// Process fault face properties in fault property<br />
foreach (FaultFaceProperty ffp in fp)<br />
{<br />
if (!double.IsNan( ffp.Value))<br />
{<br />
// Get the value<br />
avgVal += ffp.Value;<br />
count++;<br />
}<br />
}<br />
}<br />
// Get the position information for the face.<br />
Point3 fCenter = ffp.FaceCenter;<br />
Point3[] corners = ffp.FaceCorners;<br />
if (count > 0)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Avg value: " + avgVal.ToString());<br />
}<br />
Pillar Grid Intersection<br />
Service<br />
The IPillarGridIntersectionService is basically an engine that<br />
computes the intersections between a polyline (better a polyline implied by a<br />
sequence of TrajectoryRecord samples) and cells in a given Grid.<br />
This service replaces the earlier WellLog.GetLogInCells API, which only<br />
allowed the user to find the first and last log point within a cell.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public interface IPillarGridIntersectionService<br />
{<br />
IEnumerable<br />
GetPillarGridPolylineIntersections(Grid grid, IPolyline3<br />
line);<br />
}<br />
}<br />
The GetPillarGridPolylineIntersections method computes the<br />
intersections between a polyline and the cells of a given Grid. It returns a list<br />
of SegmentCellIntersections describing the intersections or an empty<br />
list if no intersections exist.<br />
The intersections, if any exist, occur on cell faces (on single faces when<br />
entering or leaving or on a pair of cell faces inside the Grid). Cells without<br />
5: Reservoir Modeling 155<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
volume are also traversed. It is important that the Domain of the polyline<br />
argument is the same as that of the Grid.<br />
Fig. 5-25 Polyline Intersecting Grid Cells<br />
The resulting array of SegmentCellIntersections describes the instances<br />
of intersections with references to the leaving cell and/or entering cell. The<br />
position of each intersection is located on one or two cell faces, depending<br />
on whether the polyline enters the pillar grid (1 face), intersects a pair of cell<br />
faces internally (2 faces), or leaves the pillar grid (1 face).<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public struct SegmentCellIntersection<br />
{<br />
public SegmentCellIntersection(double<br />
intersectionInPolylineIndex,<br />
Index3 leavingCell,<br />
Index3 enteringCell,<br />
Point3 intersectionPoint,<br />
Vector3 surfaceNormal);<br />
public SegmentCellIntersection(double<br />
intersectionInPolylineIndex,<br />
Index3 leavingCell,<br />
Index3 enteringCell,<br />
Point3 intersectionPoint,<br />
Vector3 surfaceNormal,<br />
CellSide leavingCellSide,<br />
CellSide enteringCellSide);<br />
}<br />
}<br />
public Index3 EnteringCell { get; }<br />
public CellSide EnteringCellSide { get; }<br />
public double IntersectionInPolylineIndex { get; }<br />
public Point3 IntersectionPoint { get; }<br />
public bool IsNotEntering { get; }<br />
public bool IsNotLeaving { get; }<br />
public Index3 LeavingCell { get; }<br />
public CellSide LeavingCellSide { get; }<br />
public Vector3 SurfaceNormal { get; }<br />
156 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
EnteringCell provides the cell index if the polyline segment enters a cell. A<br />
null is returned if the polyline leaves the Grid coming from the inside.<br />
EnteringCellSide provides the CellSide of the EnteringCell.<br />
IntersectionInPolylineIndex provides the fractional index into the<br />
polyline (array of points) used in the intersection computation. This implicitly<br />
defines a single segment (a pair of points) and the fractional index describes<br />
the relative distance between the intersection point and the points of the<br />
segment.<br />
IntersectionPoint provides the actual intersection point<br />
(Slb.<strong>Ocean</strong>.Geometry.Point3) in 3D. The point is defined in the context<br />
of Domain of the Grid.<br />
IsNotEntering returns true if the polyline segment leaves the Grid<br />
coming from the inside and false if this is an internal cell intersection.<br />
IsNotLeaving returns true if the polyline segment enters the Grid from<br />
the outside; false if this is an internal cell intersection.<br />
LeavingCell provides the cell index if the polyline segment leaves a cell. A<br />
null is returned if the polyline enters the Grid from the outside.<br />
LeavingCellSide provides the CellSide of the LeavingCell.<br />
SurfaceNormal provides the normal vector<br />
(Slb.<strong>Ocean</strong>.Geometry.Vector3) at the intersection.<br />
Let us look at a simple example to understand the information contained in<br />
SegmentCellIntersection.<br />
Fig. 5-26 Polyline Cell Intersection Scenarios<br />
5: Reservoir Modeling 157<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
For our example we consider the intersections of four different polylines<br />
with a simple grid composed of two cells. Polyline A starts and ends outside<br />
the grid, polyline B starts inside Cell1 but ends outside the grid, polyline C<br />
starts outside but ends in Cell2, and polyline D starts inside Cell1 and ends<br />
inside Cell2. This allows us to cover all possible scenarios.<br />
In the case of Polyline A, we get three SegmentCelllntersections<br />
corresponding to the three intersections at A1, A2, and A3. Polyline A enters<br />
the grid at A1, hence the intersection is on one cell face only (CellSide.Up<br />
of Cell1). A2 is an internal intersection located on two cell faces<br />
(CellSide.Down of Cell1 and CellSide.Up of Cell2). The polyline leaves<br />
the pillar grid at A3, hence the intersection is again on one cell face only<br />
(CellSide.Down of Cell2). For the first SegmentCellIntersection,<br />
IsNotLeaving is true because the polyline segment enters the grid from<br />
the outside. For the third SegmentCellIntersection, IsNotEntering is<br />
true because the polyline segment leaves the grid here coming from the<br />
inside.<br />
For each intersection, the IntersectionInPolylineIndex defines the<br />
relative distance between the intersection point and the end points of the<br />
polyline segment. A1 has fractional index 1.3 and lies between index 1 and 2<br />
of the polyline, A2 has fractional index 2.6 and lies between index 2 and 3 of<br />
the polyline. Similarly A3 has fractional index 4.4.<br />
The following table describes the three SegmentCelllntersections for<br />
polyline A.<br />
Table 5-1 Polyline A Intersections<br />
Polyline A Intersections<br />
SegmentCelllntersection 1 2 3<br />
EnteringCell<br />
Cell1 Cell2 null<br />
(0,0,0) (0,0,1)<br />
EnteringCellSide Up Up None<br />
LeavingCell null Cell1<br />
(0,0,0)<br />
Cell2<br />
(0,0,1)<br />
LeavingCellSide None Down Down<br />
IntersectionPoint A1 A2 A3<br />
IntersectionInPolyLineIndex 1.3 2.6 4.4<br />
IsNotEntering false false true<br />
IsNotLeaving true false false<br />
In the case of polyline B we get two SegmentCelllntersections<br />
corresponding to the two intersections at B1 and B2. The following table<br />
describes the two SegmentCelllntersections for polyline B. Since the<br />
158 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
polyline starts within the grid, there is no SegmentCelllntersection with<br />
IsNotLeaving set to true.<br />
Table 5-2 Polyline B Intersections<br />
Polyline B Intersections<br />
SegmentCellIntersection 1 2<br />
EnteringCell Cell2 (0,0,1) null<br />
EnteringCellSide Up None<br />
LeavingCell Cell1 (0,0,0) Cell2 (0,0,1)<br />
LeavingCellSide Down Down<br />
IntersectionPoint B1 B2<br />
IsNotEntering false true<br />
IsNotLeaving false false<br />
In the case of polyline C we get two SegmentCelllntersections<br />
corresponding to the two intersections at C1 and C2. The following table<br />
describes the two SegmentCelllntersections for polyline C. Since the<br />
polyline ends within the grid, there is no SegmentCelllntersection with<br />
IsNotEntering set to true.<br />
Table 5-3 Polyline C Intersections<br />
Polyline C Intersections<br />
SegmentCellIntersection 1 2<br />
EnteringCell Cell1 (0,0,0) Cell2 (0,0,1)<br />
EnteringCellSide Up Up<br />
LeavingCell null Cell1 (0,0,0)<br />
LeavingCellSide None Down<br />
IntersectionPoint C1 C2<br />
IsNotEntering false false<br />
IsNotLeaving true false<br />
In the case of polyline D we get only one SegmentCelllntersection<br />
corresponding to the intersection at D1. The following table describes the<br />
only SegmentCelllntersection for polyline D. Since the polyline starts<br />
and ends within the grid, both IsNotLeaving and IsNotEntering are set<br />
to false.<br />
Table 5-4 Polyline D Intersections<br />
Polyline D Intersections<br />
SegmentCellIntersection 1<br />
EnteringCell Cell2 (1,1,2)<br />
EnteringCellSide<br />
Up<br />
LeavingCell Cell1 (1,1,1)<br />
5: Reservoir Modeling 159<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Table 5-4 Polyline D Intersections<br />
Polyline D Intersections<br />
LeavingCellSide<br />
IntersectionPoint<br />
IsNotEntering<br />
IsNotLeaving<br />
Down<br />
D1<br />
false<br />
false<br />
Here is how you can get the IPillarGridIntersectionService service to<br />
find grid polyline intersections.<br />
...<br />
Grid grid;<br />
IPolyline3 line;<br />
...<br />
IPillarGridIntersectionService pgiservice;<br />
pgiservice = CoreSystem.GetService();<br />
...<br />
IEnumerable intersectionsegments =<br />
pgiservice.GetPillarGridPolylineIntersections(grid, line);<br />
foreach(SegmentCellIntersection sci in intersectionsegments)<br />
{<br />
Point3 pt = sci.IntersectionPoint;<br />
Index3 cell = sci.EnteringCell;<br />
CellSide enteringSide = sci.EnteringCellSide;<br />
...<br />
}<br />
...<br />
<strong>Data</strong> Analysis<br />
<strong>Data</strong> analysis is a <strong>Petrel</strong> process that computes statistics on a<br />
DictionaryProperty.<br />
Fig. 5-27 <strong>Data</strong> Analysis in <strong>Petrel</strong><br />
160 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Pillar Grid<br />
The results are stored with the DictionaryProperty but are not exposed<br />
as a class member as they are only present if the data analysis module has<br />
been run.<br />
The <strong>Ocean</strong> API exposes these statistical results as a virtual 3D property that<br />
can be accessed in memory. The virtual Property cannot be added to the<br />
Grid object, but values can be retrieved and copied in a newly created<br />
Property.<br />
Two types of statistics are retrieved: “Vertical Proportion,” the depth<br />
percentage of a given cell where a facies code (or any DictionaryProperty<br />
symbol) is found and “Attribute Probability,” the probability of finding a<br />
given facies code or DictionaryProperty symbol in a cell.<br />
<strong>Data</strong> analysis results are obtained from a fetcher interface,<br />
I<strong>Data</strong>AnalysisFetcher, whose implementation instance is returned by the<br />
static class <strong>Petrel</strong>Project.Simulation<strong>Data</strong>.<br />
namespace<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid<br />
{<br />
public enum Discrete<strong>Data</strong>AnalysisType<br />
{<br />
VerticalProportion,<br />
AttributeProbability<br />
}<br />
public interface I<strong>Data</strong>AnalysisFetcher<br />
{<br />
Property GetDiscrete<strong>Data</strong>Analysis(<br />
DictionaryProperty property,<br />
Discrete<strong>Data</strong>AnalysisType analysisType,<br />
int<br />
faciesCode<br />
);<br />
}<br />
}<br />
5: Reservoir Modeling 161<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
This example shows how to retrieve statistics in a Property that will be stored<br />
in the <strong>Petrel</strong> project for later use.<br />
DictionaryProperty prop = ...;<br />
Grid g = ...;<br />
PropertyVersion pv = ...;<br />
I<strong>Data</strong>AnalysisFetcher f;<br />
f =<br />
<strong>Petrel</strong>Project.Simulation<strong>Data</strong>.Get<strong>Data</strong>Analysis<br />
Fetcher(prop);<br />
Discrete<strong>Data</strong>AnalysisType t;<br />
t =<br />
Discrete<strong>Data</strong>AnalysisType.VerticalProportion;<br />
Property stat =<br />
f.GetDiscrete<strong>Data</strong>Analysis(prop, t, 0);<br />
using (ITransaction trans =<br />
<strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(g);<br />
Property proportion = g.CreateProperty(pv);<br />
g.AddProperty(proportion);<br />
for (int i = 0; i <<br />
proportion.NumCellsIJK.I; i++)<br />
for (int j = 0; j <<br />
proportion.NumCellsIJK.J; j++)<br />
for (int k = 0; k <<br />
proportion.NumCellsIJK.K; k++)<br />
proportion[i, j, k] = stat[i, j, k];<br />
trans.Commit();<br />
}<br />
162 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Reservoir Simulation<br />
Reservoir Simulation<br />
The dynamic reservoir model focuses on production estimation over long<br />
periods of time. As the reservoir starts losing formation pressure, the flow of<br />
borehole fluid is modified. Simulation predicts the variations of fluid flow<br />
characteristics over time. It is an essential component in petroleum reservoir<br />
management.<br />
Case Run<br />
Summary Category Summary Template Summary Result<br />
Fig. 5-28 Simulation Domain<br />
Simulation Result<br />
Classes<br />
Simulation results, the list of production data as a function of time, are not<br />
listed as a domain object on the <strong>Petrel</strong> "Case" and "Results" trees.<br />
In the "Case" tree, we have the following objects:<br />
• Case objects that represent the input to a case analyzer or simulator.<br />
• Simulation objects that represent the simulation cases.<br />
In the "Results" tree we have the following object types:<br />
• Properties defined by ResultProperty objects that represent<br />
properties for which there are TimeSeries objects available.<br />
• Categories defined by ResultCategory objects that provide a filter,<br />
typically a well or field, when navigating to a TimeSeries.<br />
All these objects will determine the specific Simulation results.<br />
5: Reservoir Modeling 163<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
CaseDefinition<br />
CaseRun SummaryTemplate SummaryCategory<br />
SummaryResult<br />
PropertyVersion<br />
TimeVector<br />
<strong>Data</strong>Vector<br />
Fig. 5-29 Objects That Determine Specific Simulation Results<br />
<strong>Data</strong> Analysis and<br />
Simulation Cases<br />
The Cases tree lists data analysis and simulation case runs. <strong>Data</strong> analysis case<br />
runs are CaseAnalysis objects and simulation case runs are Simulation<br />
objects. Simulation objects derive from CaseAnalysis objects and<br />
include methods and properties to get time series and streamline sets for the<br />
simulation.<br />
It is important to note that Case, CaseAnalysis, and Simulation objects<br />
are read-only objects. You cannot create or modify these objects through the<br />
API if they have been created by the <strong>Petrel</strong> user.<br />
There are two root classes for accessing cases and simulations.<br />
AnalysisRoot provides navigation to Case and CaseAnalysis objects.<br />
164 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Reservoir Simulation<br />
SimulationRoot provides navigation to Simulation objects. Here is an<br />
example accessing each type of object.<br />
using Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Analysis;<br />
// Get the project<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
// Get the root object for analysis for the project.<br />
AnalysisRoot aRoot = AnalysisRoot.Get( proj );<br />
// Process each case<br />
foreach ( Case c in aRoot.Cases )<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow( "Case " + c.Name );<br />
}<br />
// Process each case analysis<br />
foreach ( CaseAnalysis ca in c.CaseAnalyses )<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow( "Case run " + ca.Name );<br />
}<br />
// Get the root simulation object<br />
SimulationRoot sRoot = SimulationRoot.Get( proj );<br />
// Process each simulation object<br />
foreach ( Simulation sim in sRoot.Simulations )<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow( "Simulation " + sim.Name );<br />
}<br />
The Simulation class provides access to time series and streamline data<br />
through its properties and methods.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Simulation<br />
{<br />
public sealed class Simulation : CaseAnalysis<br />
{ ...<br />
public IEnumerable TimeSeries { get; }<br />
public StreamlineSet StreamlineSet { get; }<br />
public IEnumerable GetTimeSeries(<br />
ResultProperty property );<br />
public IEnumerable GetTimeSeries(<br />
ResultProperty property,<br />
ResultCategory category );<br />
}<br />
<strong>Access</strong>ing<br />
Simulation <strong>Data</strong> by<br />
Time Series<br />
Time series contain simulation result values versus time. The simulation<br />
property data is typically displayed in the <strong>Petrel</strong> Function window with the<br />
time series as the X axis. A time series is composed of time samples and<br />
corresponding data samples. You can only read time series data through the<br />
5: Reservoir Modeling 165<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
API; writing time series is not possible in the current release. The<br />
TimeSeries class defines this type.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Analysis<br />
{<br />
public sealed class TimeSeries<br />
{<br />
public IEnumerable <strong>Data</strong>Samples { get; }<br />
public string Name { get; }<br />
public PropertyVersion PropertyVersion { get; }<br />
public IDictionary Samples { get; }<br />
public IEnumerable TimeSamples { get; }<br />
}<br />
}<br />
The <strong>Data</strong>Samples property enumerates the result values for the series. The<br />
TimeSamples property enumerates date references parallel to the result<br />
values. The Samples property returns values based on a date key.<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
SimulationRoot sr = SimulationRoot.Get( Proj );<br />
ResultCategory rc = ...;<br />
ResultProperty rp = ...;<br />
string dateToTry = "08/01/2007";<br />
DateTime keyDate = DateTime.Parse( dateToTry );<br />
// Process each simulation<br />
foreach (Simulation sim in sr.Simulations)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Simulation: " + sim.Name);<br />
}<br />
}<br />
// Process each time series in the simulation<br />
// for the provided ResultProperty and ResultCategory<br />
foreach (TimeSeries ts in sim.GetTimeSeries( rp, rc ))<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Time Series: " + ts.Name);<br />
// Print out the sample values<br />
foreach (double sample in ts.<strong>Data</strong>Samples)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(sample.ToString());<br />
}<br />
// Print the value for each key in the series<br />
foreach (DateTime key in ts.Samples.Keys)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow("Key: " + key.ToString() +<br />
" Sample: " + ts.Samples[key].ToString());<br />
}<br />
// Print the value at the user provided date<br />
// Note: the users input DateTime must be an exact match.<br />
<strong>Petrel</strong>Logger.InfoOutputWindow( "Sample at " + Date + ": " +<br />
ts.Samples[keyDate].ToString( ) );<br />
166 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Reservoir Simulation<br />
Result Filtering<br />
The TimeSeries data is read through the API and is filtered by simulation<br />
result properties and categories.<br />
A result property type, defined by the class ResultProperty, is a link to the<br />
PropertyVersion for the simulation data. It provides a filter for one<br />
measurement type.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Analysis<br />
{<br />
public sealed class ResultProperty : FacadeConvenience,<br />
IDomainObject<br />
{<br />
public override LastModifiedInfo LastModified { get; }<br />
public string Name { get; }<br />
public static ResultProperty NullObject { get; }<br />
public PropertyVersion PropertyVersion { get; }<br />
}<br />
}<br />
ResultProperty objects are available as well as an enumerable property,<br />
ResultProperties, of the AnalysisRoot class.<br />
// Get the project<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
// Get the root object for analysis for the project.<br />
AnalysisRoot aRoot = AnalysisRoot.Get( proj );<br />
// Get a list of result properties<br />
List props;<br />
props = new List(aRoot.ResultProperties);<br />
The time series data may also be filtered by category. A category is<br />
represented by a Field, Group, or Well and is defined by the<br />
ResultCategory class.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Analysis<br />
{<br />
public sealed class ResultCategory : FacadeConvenience,<br />
IDomainObject<br />
{<br />
public override LastModifiedInfo LastModified { get; }<br />
public string Name { get; }<br />
public static ResultCategory NullObject { get; }<br />
}<br />
}<br />
5: Reservoir Modeling 167<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
ResultCategory objects are also available as an enumerable property of<br />
AnalysisRoot.<br />
// Get the project<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
// Get the root object for analysis for the project.<br />
AnalysisRoot aRoot = AnalysisRoot.Get( proj );<br />
// Get a list of result categories<br />
List cats;<br />
cats = new List(aRoot.ResultCategories);<br />
Filtering TimeSeries objects is done by using the ResultProperty and/or<br />
the ResultCategory objects to retrieve the TimeSeries object from the<br />
Simulation. This is done through the GetTimeSeries method. You may<br />
choose to filter only by ResultProperty or by both ResultProperty and<br />
ResultCategory. Here is an example that gets the TimeSeries for each<br />
ResultProperty and ResultCategory combination.<br />
// Get the project<br />
Project proj = <strong>Petrel</strong>Project.PrimaryProject;<br />
// Get the root objects for analysis and simulation.<br />
AnalysisRoot aRoot = AnalysisRoot.Get( proj );<br />
SimulationRoot sRoot = SimulationRoot.Get( proj );<br />
// Get a list of result properties and categories<br />
List props;<br />
props = new List(aRoot.ResultProperties);<br />
List cats;<br />
cats = new List(aRoot.ResultCategories);<br />
for (int i = 0; i < aRoot.ResultPropertyCount; i++)<br />
{<br />
for (int j = 0; j < aRoot.ResultCategoryCount; j++)<br />
{<br />
foreach (Simulation sim in sRoot.Simulations)<br />
{<br />
foreach (TimeSeries ts in sim.GetTimeSeries(props[i],<br />
cats[j]))<br />
{<br />
...<br />
}<br />
}<br />
}<br />
}<br />
Streamlines<br />
Streamlines define fluid paths in a reservoir. They have start and stop<br />
positions typically related to a completion interval in a borehole and are<br />
168 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Reservoir Simulation<br />
represented graphically by 3D polyline objects. They are created with respect<br />
to a DateTime so they may be grouped in time stamped collections.<br />
Fig. 5-30 Streamlines Between Two Wells<br />
Reading Streamline<br />
<strong>Data</strong><br />
The Simulation class provides read access to streamlines through the<br />
StreamlineSet property referencing the StreamlineSet class.<br />
5: Reservoir Modeling 169<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
StreamlineSet allows the creation and reading of streamline boundaries,<br />
properties, and subsets.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Simulation.Streamline<br />
{<br />
public sealed class StreamlineSet : FacadeConvenience,<br />
IDomainObject<br />
{<br />
public override LastModifiedInfo LastModified { get; }<br />
public static StreamlineSet NullObject { get; }<br />
public IEnumerable StreamlineBoundaries<br />
{ get; }<br />
public IEnumerable StreamlineProperties<br />
{ get; }<br />
public int StreamlineSubsetCount { get; }<br />
public IEnumerable StreamlineSubsets { get;<br />
}<br />
public PropertyVersion CreateNodeProperty( PropertyVersion<br />
pv);<br />
public PropertyVersion CreateSegmentProperty (<br />
PropertyVersion pv );<br />
public StreamlineSubset CreateStreamlineSubset (<br />
DateTime dateTime );<br />
public PropertyType GetPropertyType ( PropertyVersion pv );<br />
public StreamlineSubset GetStreamlineSubset (<br />
DateTime dateTime );<br />
public bool HasProperty ( PropertyVersion pv );<br />
}<br />
}<br />
A subset represents streamline related data for a given time step. Streamline<br />
subsets are defined by the StreamlineSubset class. You can navigate to a<br />
subset through the StreamlineSubsets property.<br />
Simulation sim = ...;<br />
DateTime keyDate = ...;<br />
StreamlineSet sls = sim.StreamlineSet;<br />
// Process each subset<br />
foreach ( StreamlineSubset subset in sls.StreamlineSubsets )<br />
{<br />
}<br />
// See if the date matches our requested date.<br />
if (sub.DateTime.Equals(keyDate))<br />
{<br />
...<br />
}<br />
A StreamlineSubset contains a set of Streamline objects for a given time<br />
as defined by its DateTime property. You can read all of them through the<br />
170 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Reservoir Simulation<br />
Streamlines property or a group for a particular boundary using the<br />
GetStreamlines method.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Simulation.Streamline<br />
{<br />
public sealed class StreamlineSubset : FacadeConvenience,<br />
IDomainObject<br />
{<br />
public DateTime DateTime { get; }<br />
public override LastModifiedInfo LastModified { get; }<br />
public string Name { get; }<br />
public static StreamlineSubset NullObject { get; }<br />
public int StreamlineCount { get; }<br />
public IEnumerable Streamlines { get; }<br />
public StreamlineSet StreamlineSet { get; }<br />
public event<br />
EventHandler<br />
StreamlinesChanged;<br />
public Streamline CreateStreamline ( );<br />
public void Delete ( );<br />
public IEnumerable GetStreamlines (<br />
StreamlineBoundary streamlineBoundary );<br />
}<br />
}<br />
Here is an example accessing all streamlines in a subset and then only those<br />
for a particular boundary. We will use the start boundary from the first<br />
streamline to later query for all streamlines with the same boundary.<br />
StreamlineSubset sub = ...;<br />
StreamlineBoundary bound = null;<br />
bool first = false;<br />
// Process all streamlines in subset<br />
foreach ( Streamline sline in sub.Streamlines )<br />
{<br />
// capture the boundary for the first one to use later<br />
if (first)<br />
bound = sline.StartBoundary;<br />
}<br />
...;<br />
// Process only those streamlines with the captured boundary<br />
foreach ( Streamline sline in sub.GetStreamlines( bound ))<br />
{<br />
...;<br />
}\<br />
Streamlines are defined by the Streamline class. The streamline Geometry<br />
property is a polyline (IPolyline3) defining the flow path of the streamline.<br />
5: Reservoir Modeling 171<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Corresponding to this there may be node and segment properties for<br />
different PropertyVersion defined types of data.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Simulation.Streamline<br />
{<br />
public sealed class Streamline<br />
{<br />
public IPolyline3 Geometry { get; set; }<br />
public int GeometryCount { get; }<br />
public bool IsValid { get; }<br />
public bool IsWritable { get; }<br />
public int PropertyCount { get; }<br />
public StreamlineBoundary StartBoundary { get; set; }<br />
public StreamlineBoundary StopBoundary { get; set; }<br />
public StreamlineSubset StreamlineSubset { get; }<br />
public IEnumerable GetNodeProperty (<br />
PropertyVersion pv );<br />
public IEnumerable GetSegmentProperty (<br />
PropertyVersion pv );<br />
public bool HasGeometry ( );<br />
public bool HasProperty ( PropertyVersion pv );<br />
public void SetNodeProperty ( PropertyVersion pv,<br />
IEnumerable data );<br />
public void SetSegmentProperty ( PropertyVersion pv,<br />
IEnumerable data );<br />
}<br />
}<br />
Streamlines have boundaries that define their beginning and ending points.<br />
These boundaries may be defined at Borehole locations or they may be<br />
somewhere in the reservoir not related to a Borehole. The<br />
StreamlineBoundary class defines the type for a streamline boundary.<br />
namespace Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Simulation.Streamline<br />
{<br />
public sealed class StreamlineBoundary : FacadeConvenience,<br />
IDomainObject<br />
{<br />
public Borehole Borehole { get; }<br />
public bool IsBorehole { get; }<br />
public override LastModifiedInfo LastModified { get; }<br />
public static StreamlineBoundary NullObject { get; }<br />
}<br />
}<br />
When processing streamlines, you should check that the streamline is well<br />
defined by checking its IsValid property. IsValid is true if the<br />
streamline has at least three points, both start and stop boundaries, and all<br />
172 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Reservoir Simulation<br />
properties are valid as nodes or segments. Here is an extension of the<br />
previous example that processes streamlines for a boundary.<br />
// Process only those streamlines with the captured boundary<br />
foreach ( Streamline sline in sub.GetStreamlines( bound ))<br />
{<br />
// Check for valid streamline<br />
if ( sline.ISValid )<br />
{<br />
// Process streamline points.<br />
foreach ( Point3 pt in sline.Geometry )<br />
{<br />
...;<br />
}<br />
}<br />
}<br />
Creating Streamlines<br />
Streamlines may be created under a StreamlineSubset. Once created it is<br />
advised that you set the Geometry and either node or segment properties<br />
immediately. Here is an example.<br />
StreamlineSubset sub = ...;<br />
PropertyVersion pv = ...;<br />
IPolyline3 streamlinePoints = ...;<br />
double[] nodeValues = ...;<br />
using ( ITransaction tr = <strong>Data</strong>Manager.NEwTransaction())<br />
{<br />
tr.Lock( sub );<br />
Streamline myStreamline = sub.CreateStreamline( );<br />
myStreamline.Geometry = streamlinePoints;<br />
myStreamline.SetNodeProperty( pv, nodeValues );<br />
}<br />
tr.Commit( );<br />
If you have boundaries you may set them as well using:<br />
myStreamline.StartBoundary = startBound;<br />
myStreamline.StopBoundary = stopBound;<br />
This must be inside the transaction. Currently the API does not support the<br />
creation of boundaries so you would have to use existing<br />
StreamlineBoundary objects.<br />
5: Reservoir Modeling 173<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
174 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
6 Input and Output<br />
In This Chapter<br />
Extending Import/Export.............................................................................176<br />
Open <strong>Petrel</strong> Binary Format ..........................................................................182<br />
RESCUE Format..........................................................................................183<br />
6: Input and Output 175<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Extending Import/Export<br />
<strong>Ocean</strong> allows three types of programmatic import and export:<br />
1. Extending the <strong>Petrel</strong> import/export capabilities with new file types and<br />
corresponding implementations.<br />
2. Using the Open <strong>Petrel</strong> Binary (OPB) format to import and export OPB<br />
files for a pillar grid instance.<br />
3. Using the RESCUE format to import and export a pillar grid instance.<br />
<strong>Petrel</strong> allows the end user to import and export <strong>Petrel</strong> project data.<br />
Fig. 6-1<br />
<strong>Petrel</strong> Import File Dialog<br />
The data import and export capabilities of <strong>Petrel</strong> can be extended to support<br />
new file types. These new file types will be added to the standard <strong>Petrel</strong><br />
import/export dialog as if they were native to the system. The steps to add a<br />
new file type are:<br />
1. Derive from the abstract FileFormat class.<br />
2. Add the new file format to the IFileFormatCollection.<br />
The FileFormat class contains the information necessary to populate the<br />
standard <strong>Petrel</strong> import and export dialogs. It also determines if a particular<br />
object can be imported or exported, and it actually does the import and/or<br />
export work for the file type.<br />
public abstract class FileFormat<br />
{<br />
public abstract ImportExportCapabilities Capabilities { get; }<br />
public abstract string Description { get; }<br />
public abstract string Extension { get; }<br />
public abstract string Name { get; }<br />
public abstract string TypeOfSubject { get; }<br />
176 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Extending Import/Export<br />
abstract bool CanExport(object o);<br />
abstract bool CanImport(object parent);<br />
}<br />
abstract void Export(string filename, object o);<br />
abstract void Import(string filename, object parent);<br />
The ImportExportCapabilities enumeration describes what the file can<br />
do with the file: nothing, import, export, or both import and export.<br />
public enum ImportExportCapabilities<br />
{<br />
None = 0,<br />
ImportOnly = 1,<br />
ExportOnly = 2,<br />
ImportExport = 3<br />
}<br />
This will determine if the file type is displayed in either of the standard <strong>Petrel</strong><br />
import or export dialogs.<br />
The sample file format implementation will export a continuous well log to<br />
an ASCII file of MD and data values and import it back to a well.<br />
public class ImportExportWellLog : FileFormat<br />
{<br />
public override ImportExportCapabilities Capabilities<br />
{ get { return ImportExportCapabilities.ImportExport; } }<br />
The values of the Description, Extension, and Name properties will be<br />
displayed in the standard <strong>Petrel</strong> import or export dialogs. The Extension<br />
should not return or include the ‘.’ in the file name; it should only return the<br />
file extension after the dot. The Name will also be displayed in the Format<br />
name column of the <strong>Petrel</strong> Import data table (Help->List of available<br />
formats).<br />
public override string Description<br />
{ get { return "Well log data with header in ASCII format"; } }<br />
public override string Extension<br />
{ get { return "asc"; } }<br />
public override string Name<br />
{ get { return "AsciiWellLog"; } }<br />
6: Input and Output 177<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Fig. 6-2<br />
Standard <strong>Petrel</strong> Import Dialog with New File Name, Extension, and<br />
Description<br />
The TypeOfSubject property value will be displayed in the <strong>Data</strong> type<br />
column of the <strong>Petrel</strong> Import data table (Help->List of available formats). It<br />
gives the name of the type of object data that is imported or exported using<br />
the format.<br />
public override string TypeOfSubject<br />
{ get { return "WellLog"; } }<br />
Fig. 6-3<br />
Import <strong>Data</strong> Table with New FileFormat<br />
178 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Extending Import/Export<br />
CanImport determines if the file format can be imported under the given<br />
parent; the parent can either be the root of the tree represented by null or a<br />
domain object within the tree. CanExport reports whether the specified<br />
domain object can be exported to the file format.<br />
public override bool CanImport(object parent)<br />
{<br />
return (parent is Borehole);<br />
}<br />
public override bool CanExport(object obj)<br />
{<br />
return (obj is WellLog);<br />
}<br />
Well logs are contained by boreholes, so the file format can only be imported<br />
if the parent is a Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.Well.Borehole. The<br />
sample only exports well logs.<br />
The Import method performs the import of the specified filename on disk<br />
into a given parent object. If the parent is the root level of a tree, then its<br />
argument value will be null. If the parent is not a native <strong>Petrel</strong> domain<br />
object, it is the responsibility of the import implementation to add the child<br />
to the parent "Implement INotifyingEnumerable" in Volume 3, Chapter 13,<br />
"Custom Domain Objects" on page 770. The import implementation needs<br />
to place any new domain objects at the proper location in the <strong>Petrel</strong> tree<br />
hierarchy.<br />
Note that the implementation of Import depends on what type of data is<br />
being imported, what object is its container, and how the file is formatted.<br />
When dealing with native <strong>Petrel</strong> domain objects, it will use transactions when<br />
creating new data and follow the patterns and practices defined by the native<br />
<strong>Petrel</strong> domain object API. The data given to the <strong>Ocean</strong> API is always<br />
expected to be in the Invariant unit system; any unit conversions necessary<br />
must be done by the Import function.<br />
public override void Import(string f, object parent)<br />
{<br />
if (!File.Exists(f)) return;<br />
Borehole b = (Borehole)parent;<br />
PropertyVersion pv = this.PropVersionFromHeader(f, b);<br />
using (ITransaction trans = <strong>Data</strong>Manager.NewTransaction())<br />
{<br />
trans.Lock(b);<br />
WellLog newWellLog = b.Logs.CreateWellLog(pv);<br />
List sampleList = new List();<br />
StreamReader sr = new StreamReader(f, Encoding.ASCII);<br />
try<br />
{<br />
// Read the file stream till end of stream<br />
...<br />
6: Input and Output 179<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
string md<strong>Data</strong> = data[0];<br />
string val<strong>Data</strong> = data[1];<br />
double mdVal = Double.Parse(md<strong>Data</strong>);<br />
float val = float.Parse(val<strong>Data</strong>);<br />
sampleList.Add(new WellLogSample(mdVal, val));<br />
...<br />
newWellLog.Append(sampleList);<br />
}<br />
finally<br />
{<br />
sr.Close();<br />
}<br />
trans.Commit();<br />
}<br />
}<br />
The Export method performs the export of the specified object into a specified<br />
filename on disk. Transactions are not needed since the export reads the<br />
data and is not creating data. The data from the <strong>Ocean</strong> API is always returned in<br />
the Invariant unit system.<br />
public override void Export(string f, object obj)<br />
{<br />
if (File.Exists(f))<br />
File.Delete(f);<br />
StreamWriter sw = new StreamWriter(f, true, Encoding.ASCII);<br />
WellLog wellLog = obj as WellLog;<br />
try<br />
{<br />
this.WriteHeader(sw, wellLog);<br />
float val = 0.0f;<br />
double md = 0.0f;<br />
foreach (WellLogSample s in wellLog.Samples)<br />
{<br />
md = s.MD;<br />
val = s.Value;<br />
...<br />
sw.WriteLine(s.MD + " " + s.Value);<br />
}<br />
sw.AutoFlush = true;<br />
}<br />
finally<br />
{<br />
sw.Close();<br />
}<br />
}<br />
}<br />
At this point, we have a FileFormat, but <strong>Petrel</strong> does not know that we have<br />
this new functionality to offer. The IFileFormatCollection service is<br />
available from the static convenience class <strong>Petrel</strong>System.<br />
public interface IFileFormatCollection<br />
{<br />
void Add (FileFormat f);<br />
void Remove (FileFormat f);<br />
}<br />
180 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Extending Import/Export<br />
The new FileFormat needs to be added to the file format collection; this is<br />
typically done in the module’s Integrate method.<br />
public void Integrate ()<br />
{<br />
<strong>Petrel</strong>System.FileFormats.Add(new ImportExportWellLog());<br />
}<br />
6: Input and Output 181<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
Open <strong>Petrel</strong> Binary Format<br />
The IOpen<strong>Petrel</strong>BinaryFormat service imports and exports Open <strong>Petrel</strong><br />
Binary (OPB) files for a<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid instance. The<br />
limitations of this format are:<br />
1. User defined continuous templates are not exported; they are replaced<br />
with the ‘General’ template.<br />
2. User defined discrete templates are not exported; they are replaced with<br />
the ‘General discrete’ template.<br />
3. Property folders are not exported. Properties in these folders are<br />
exported to the parent Properties folder in the Models pane.<br />
4. Contact Sets are not exported.<br />
The Export method exports a specified pillar grid into a specified file on<br />
disk in the Open <strong>Petrel</strong> Binary format. The Import method performs the<br />
import of a grid in OPB format on disk into a<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid instance. It will be<br />
placed into the given parent ModelCollection; if the parent is null, the new<br />
grid will be imported into the root of the Models tree.<br />
public interface IOpen<strong>Petrel</strong>BinaryFormat<br />
{<br />
void Export(string filename, Grid pillarGrid);<br />
Grid Import(string filename, ModelCollection parent);<br />
}<br />
The Export method exports a specified pillar grid into a specified file on<br />
disk in the Open <strong>Petrel</strong> Binary format. The Import method performs the<br />
import of a grid in OPB format on disk into a<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid instance. It will be<br />
placed into the given parent ModelCollection; if the parent is null, the new<br />
grid will be imported into the root of the Models tree.<br />
IOpen<strong>Petrel</strong>BinaryFormat opb;<br />
obp = CoreSystem.GetService();<br />
182 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
RESCUE Format<br />
RESCUE Format<br />
The RESCUE (REServoir Characterization Using Epicenter) format is an<br />
open POSC standard for exchange of geomodels. <strong>Ocean</strong> supports import<br />
and export of both binary and ASCII RESCUE formats.<br />
The IRescueFormat service imports and exports Rescue files for a<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid instance. The<br />
Rescue data model is not fully compatible with <strong>Petrel</strong> data model i.e. export<br />
and re-import may not be fully lossless. Some data, such as zone hierarchies,<br />
will be lost.<br />
public interface IRescueFormat<br />
{<br />
void Export(string directory, Grid pillarGrid, bool isBinary);<br />
Grid Import(string filename);<br />
Grid Import(string filename, ModelCollection parent);<br />
}<br />
The Export method performs the export of the specified<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid into a specified<br />
directory on disk. The data is stored in files within the specified directory<br />
which is created if it does not exist. Transactions are not needed since the<br />
export reads the data and is not creating data. The isBinary argument<br />
specifies the type (binary or ASCII) of Rescue file. The exported file names<br />
will have extension "bin" to indicate binary format and "txt" for ASCII<br />
format. All filenames will have a numeric extension following the "bin" or<br />
"txt" except the main Rescue file.<br />
Local disk (D:)<br />
provided directory<br />
main rescue file<br />
Rescue<br />
Rescue.bin<br />
Rescue.bin.31<br />
Rescue.bin.37<br />
...<br />
Fig. 6-4<br />
Exported Rescue Format File Names<br />
The Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid domain object<br />
needs to satisfy the following criteria for allowing export to Rescue format.<br />
1. Zones and horizons must have unique names.<br />
2. Grid should have at least two horizons and one zone.<br />
6: Input and Output 183<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
3. Grid should have a limit (definition of area it encompasses).<br />
string directory = ...;<br />
Grid pillarGrid = ...;<br />
IRescueFormat rescueService = null;<br />
rescueService = CoreSystem.GetService();<br />
try<br />
{<br />
// Export as a Binary rescue file<br />
rescueService.Export(directory , pillarGrid, true);<br />
}<br />
catch(Exception e)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(e.Message);<br />
}<br />
The Import method performs the import of a Rescue file on disk into a<br />
Slb.<strong>Ocean</strong>.<strong>Petrel</strong>.DomainObject.PillarGrid.Grid instance. The<br />
Import method requires the main Rescue file name (that does not have a<br />
numeric extension).<br />
The imported grid will be placed into the first instance of ModelCollection<br />
under the models root. If the parent model collection is provided as an<br />
argument, the new grid will be imported into the specified<br />
ModelCollection.<br />
string filename = "Rescue.bin";<br />
Grid pillarGrid = Grid.NullObject;<br />
IRescueFormat rescueService = null;<br />
rescueService = CoreSystem.GetService();<br />
try<br />
{<br />
pillarGrid = rescueService.Import(filename);<br />
}<br />
catch(Exception e)<br />
{<br />
<strong>Petrel</strong>Logger.InfoOutputWindow(e.Message);<br />
}<br />
184 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
RESCUE Format<br />
The name of the new pillar grid is fixed by the system. It may be renamed by<br />
the user from the GUI.<br />
imported grid<br />
Fig. 6-5<br />
Imported Grid<br />
Since this is a service, it can be accessed via the general<br />
CoreSystem.GetService functionality.<br />
IRescueFormat rescueFormat;<br />
rescueFormat = CoreSystem.GetService();<br />
6: Input and Output 185<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
186 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
Index<br />
D<br />
<strong>Data</strong> Analysis ...................................................................................................................................160<br />
DictionaryFaultProperty .................................................................................................................117<br />
DictionaryHorizonProperty ............................................................................................115, 117, 135<br />
DictionaryProperty ..................................................................................................................113, 150<br />
DictionaryPropertyRecord .............................................................................................................117<br />
DictionaryPropertyVersion ..............................................................................................................62<br />
DictionaryWellLog ......................................................................................................................61, 62<br />
Domain ...........................................................................................................................................7, 72<br />
Droid<br />
E<br />
IIdentifiable .....................................................................................................................................10<br />
export ...............................................................................................................................................176<br />
FileFormat .............................................................................................................................176, 180<br />
IFileFormatCollection .............................................................................................................176, 180<br />
Open<strong>Petrel</strong>Binary Format ...............................................................................................................182<br />
I<br />
IDomainObject ...................................................................................................................................11<br />
IDroidResolver ..................................................................................................................................15<br />
IIdentifiable ........................................................................................................................................10<br />
import ...............................................................................................................................................176<br />
FileFormat .............................................................................................................................176, 180<br />
IFileFormatCollection .............................................................................................................176, 180<br />
Open<strong>Petrel</strong>Binary Format ...............................................................................................................182<br />
Index classes<br />
Index2 ..................................................................................................................................133, 140<br />
Index3 ..........................................................................................................................................122<br />
INotifyingOnDeleted .........................................................................................................................11<br />
M<br />
ModelCollection ......................................................................................................................135, 182<br />
MultiTraceWellLog ............................................................................................................................63<br />
MultiTraceWellLogSample ...............................................................................................................63<br />
N<br />
NullObject ..........................................................................................................................................12<br />
O<br />
Open <strong>Petrel</strong> Binary format<br />
IOpen<strong>Petrel</strong>BinaryFormat ...............................................................................................................182<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.<br />
Index 187
Volume 2: <strong>Petrel</strong> <strong>Data</strong> <strong>Access</strong><br />
P<br />
<strong>Petrel</strong> Trees<br />
Input .............................................................................................................................................116<br />
<strong>Petrel</strong>Project ....................................................................................................................................161<br />
<strong>Petrel</strong>UnitSystem ............................................................................................................................144<br />
Pillar ..................................................................................................................................121, 122, 133<br />
Point classes<br />
Point3 ...................................................................................................................................122, 123<br />
PointPropertyRecord ..............................................................................................................114, 117<br />
PolylineSet .........................................................................................................................................75<br />
Property .................................................................61, 72, 97, 113, 114, 116, 120, 135, 150, 161, 162<br />
Property versions<br />
S<br />
DictionaryPropertyVersion ................................................................................61, 115, 116, 117, 150<br />
PropertyVersion ...............................................................................................62, 115, 116, 117, 144<br />
Shapes ...............................................................................................................................................72<br />
Simulation<strong>Data</strong> ................................................................................................................................161<br />
T<br />
Templates<br />
ILogTemplate ..................................................................................................................................62<br />
Transactions ......................................................................................................................................12<br />
ITransactionManager .......................................................................................................................13<br />
188 <strong>Ocean</strong> Application Development Framework 2007.1<br />
© 2007 <strong>Schlumberger</strong>. All rights reserved.
www.ocean.slb.com<br />
*Mark of <strong>Schlumberger</strong><br />
Copyright © 2007 <strong>Schlumberger</strong>. All rights reserved. 07-IS-475