14.02.2014 Views

Petrel Data Access.book - Ocean - Schlumberger

Petrel Data Access.book - Ocean - Schlumberger

Petrel Data Access.book - Ocean - Schlumberger

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!