Wells, well surveys and well logs#

This section offers an overview of the different methods and attributes available for the Petrel well object subcategories.

Wells#

Check out the API documentation to view a detailed description of all the functions and properties available for working with well headers.

Let’s look at an example on how to retrieve all the wells from our Petrel project by using the .wells property:

[39]:
from cegalprizm.pythontool import PetrelConnection
petrel = PetrelConnection()

all_wells = petrel.wells
print(all_wells)
{'Input/Wells/Producers/B9': Well(petrel_name="B9"), 'Input/Wells/Injectors/C5': Well(petrel_name="C5"), 'Input/Wells/Injectors/C6': Well(petrel_name="C6"), 'Input/Wells/Injectors/C2': Well(petrel_name="C2"), 'Input/Wells/Producers/B8': Well(petrel_name="B8"), 'Input/Wells/Injectors/C4': Well(petrel_name="C4"), 'Input/Wells/Producers/A16': Well(petrel_name="A16"), 'Input/Wells/Injectors/C3': Well(petrel_name="C3"), 'Input/Wells/Producers/A15': Well(petrel_name="A15"), 'Input/Wells/Producers/A10': Well(petrel_name="A10")}

The cell above returned all the wells from the project in a dictionary where the keys represent the path of the well within the Petrel input tree (i.e Input/Wells/Injectors/C6 ) and the value represents the name of the well ( Well(petrel_name=“C6”) ). We can also save all the well paths to a list and then use slicing to get the name of a particular well:

[40]:
# Get all the well paths from the dictionary keys
all_wells_paths=petrel.wells.keys()
# Save them in a list
paths = list(all_wells_paths)
#Slice the list to get the first well
well = petrel.wells[paths[2]]
print(well)
Well(petrel_name="C6")

Similarly, we can iterate through the dictionary and return all the values:

[41]:
for obj in petrel.wells:
    print(obj)
Well(petrel_name="B9")
Well(petrel_name="C5")
Well(petrel_name="C6")
Well(petrel_name="C2")
Well(petrel_name="B8")
Well(petrel_name="C4")
Well(petrel_name="A16")
Well(petrel_name="C3")
Well(petrel_name="A15")
Well(petrel_name="A10")

Using the .logs property we obtain a readonly iterable collection of the logs available for the selected well:

[42]:
for obj in well.logs:
    print (obj)
WellLog(petrel_name="NetGross")
WellLog(petrel_name="Perm")
WellLog(petrel_name="Porosity")
WellLog(petrel_name="One-way time 1")
DiscreteWellLog(petrel_name="Facies")
DiscreteWellLog(petrel_name="Facies2")
DiscreteWellLog(petrel_name="LogShape")

To access the well global observed data we can use the .observed_data_sets property which returns a readonly iterable collection of the observed data sets for the well :

[43]:
for obs in well.observed_data_sets:
    print (obs)
ObservedDataSet(petrel_name="Oil production rate")

To access the well survey we can use the .surveys property which returns a readonly iterable collection of the well surveys for the selected well :

[6]:
for sur in well.surveys:
    print(sur)
WellSurvey(petrel_name="Explicit survey 1")

Working with well surveys#

Check out the API documentation to view a detailed description of all the functions and properties available for working with well surveys.

To find all surveys as a dictionary with names and paths we can use the .well_surveys property:

[44]:
petrel.well_surveys
[44]:
WellSurveys({'Input/Wells/Producers/A10/Survey_A10_demo': WellSurvey(petrel_name="Survey_A10_demo"), 'Input/Wells/Producers/A10/NewWellSurveyName': WellSurvey(petrel_name="NewWellSurveyName"), 'Input/Wells/Producers/B8/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Injectors/C5/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Producers/A15/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Producers/B9/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Injectors/C4/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Injectors/C2/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Producers/A10/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Injectors/C3/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Producers/A16/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1"), 'Input/Wells/Injectors/C6/Explicit survey 1': WellSurvey(petrel_name="Explicit survey 1")})

Similarly, we can iterate trough the dictionary and return all the values of it:

[45]:
for w in petrel.well_surveys:
    print(w)
WellSurvey(petrel_name="Survey_A10_demo")
WellSurvey(petrel_name="NewWellSurveyName")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")
WellSurvey(petrel_name="Explicit survey 1")

Let’s select the Survey_A10_demo:

[46]:
all_well_surveys = petrel.well_surveys
paths = list(all_well_surveys.keys())
well_survey = petrel.well_surveys[paths[0]]
print(well_survey)
WellSurvey(petrel_name="Survey_A10_demo")

Using the .azimuth_reference we can obtain the azimuth reference for well survey types of MD inclination azimuth survey and DX DY TVD survey:

[47]:
well_survey.azimuth_reference
[47]:
'Grid north'

The .record_count will return the amount of trajectory points as defined in Petrel trajectory spreadsheet for selected well survey:

recordcount2.png

[48]:
well_survey.record_count
[48]:
2

We can load the trajectory spreadsheet into a DataFrame using the .as_dataframe() method:

[49]:
well_survey_df = well_survey.as_dataframe()
well_survey_df
[49]:
X Y Z MD Inclination Azimuth GN
0 456979.0637 6782712.412 -0.0 0.0 0.0 0.0
1 456979.0637 6782712.412 -1000.0 1000.0 0.0 0.0

Petrel well surveys can be of 5 different types. They are: * ‘X Y Z survey’ * ‘X Y TVD survey’ * ‘DX DY TVD survey’ * ‘MD inclination azimuth survey’ * ‘Explicit survey’

Python Tool Pro does not differentiate different well surveys type when retrieving the list of well surveys. But once you retrieve a specific well survey, the type is checked. You can find out the survey type by using the method well_survey_type:

wellsurveytype.png

[50]:
well_survey.well_survey_type
[50]:
'MD inclination azimuth survey'

Since a well can have several surveys available, users can select which one to use with the .set_survey_as_definitive() function:

[26]:
well_survey.set_survey_as_definitive()

Lateral well surveys will have a tie in point with another well survey. This can be printed by using the tie_in_md property:

[10]:
PetrelPathToLateralWellSurvey=""
lateral_well_survey=petrel.well_surveys[PetrelPathToLateralWellSurvey]
lateral_well_survey.tie_in_md

You can create a new well survey with the clone function. Using the flag copy_values=False Python Tool Pro will create a new well survey with no trajectory:

[27]:
well_survey.clone('NewWellSurveyName', copy_values=False)
[27]:
WellSurvey(petrel_name="NewWellSurveyName")

The results are written back to Petrel in real time. Notice that the newly created survey has no values:

cloneSurvey.png

Well Logs#

Python Tool Pro has 4 classes which allow users to work with well logs:

  • cegalprizm.pythontool.WellLog -> a class holding information about continuous well logs

  • cegalprizm.pythontool.DiscreteWellLog -> a class holding information about discrete well logs

  • cegalprizm.pythontool.GlobalWellLog -> a class holding information about global continuous well logs

  • cegalprizm.pythontool.DiscreteGlobalWellLog -> a class holding information about global discrete well logs

Check out the API documentation to view a detailed description of all the functions and properties available for working with well logs:

We can retrieve all the continuous logs belonging to all the wells using the .well_logs property which will return a dictionary where the key represents the path of the well log within the Petrel input tree and the value represents the well log name:

[51]:
petrel.well_logs
[51]:
WellLogs({'Input/Wells/Injectors/C6/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Injectors/C3/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Producers/A10/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Injectors/C5/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Injectors/C3/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Producers/A10/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Injectors/C3/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/B9/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Injectors/C6/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Injectors/C4/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Producers/B8/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Producers/B8/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/B8/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Producers/A15/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Injectors/C2/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Producers/A16/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Producers/A16/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Injectors/C3/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Injectors/C5/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Producers/A16/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Producers/B9/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/B8/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Injectors/C6/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Producers/A16/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Producers/B9/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Producers/A15/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Injectors/C4/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Producers/B8/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Injectors/C4/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Producers/A15/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Injectors/C5/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/A16/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/A10/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/A15/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Producers/B9/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Injectors/C6/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Injectors/C4/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Injectors/C5/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Injectors/C2/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Producers/B9/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Producers/A10/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Producers/A10/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Injectors/C2/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Injectors/C5/Well logs/Demo logs/Perm': WellLog(petrel_name="Perm"), 'Input/Wells/Injectors/C2/Well logs/Demo logs/Gamma': WellLog(petrel_name="Gamma"), 'Input/Wells/Injectors/C4/Well logs/One-way time 1': WellLog(petrel_name="One-way time 1"), 'Input/Wells/Injectors/C2/Well logs/Porosity': WellLog(petrel_name="Porosity"), 'Input/Wells/Producers/A15/Well logs/Demo logs/NetGross': WellLog(petrel_name="NetGross"), 'Input/Wells/Injectors/C3/Well logs/Porosity': WellLog(petrel_name="Porosity")})

We can use widgets to navigate through different wells and explore the available logs:

[12]:
import ipywidgets as widgets
well_names = sorted(list(petrel.wells.keys()))
well_name_widget = widgets.Dropdown(
    options=well_names,
    value=well_names[0],
    description='Select well:',
    disabled=False,
)
display(well_name_widget)

Alternatively, we can just assign a particular well to a variable:

[13]:
well_list=sorted(list(petrel.wells.keys()))
well_A10= well_list[5]
print(well_A10)
Input/Wells/Producers/A10

We can assign all the logs available for well A10 to a variable and by using a nested list we can print out their name:

[14]:
well=petrel.wells[well_A10]
logs = well.logs
print(', '.join([log.petrel_name for log in logs]))
Gamma, NetGross, Perm, Porosity, One-way time 1, Facies

We can also assign all the well logs to a list:

[15]:
from cegalprizm.pythontool.welllog import WellLog
A10cont_logs = [log for log in well.logs if type(log) is WellLog]
print(A10cont_logs)
[WellLog(petrel_name="Gamma"), WellLog(petrel_name="NetGross"), WellLog(petrel_name="Perm"), WellLog(petrel_name="Porosity"), WellLog(petrel_name="One-way time 1")]

To determine which global well log the selected log belongs to, we can use the .global_well_log property:

[17]:
A10cont_logs[1].global_well_log
[17]:
GlobalWellLog(petrel_name="NetGross")

The .well property allows users to check which well the selected log belongs to:

[18]:
A10cont_logs[2].well
[18]:
Well(petrel_name="A10")

To check the Petrel template of a particular log, we can use the .template property:

[19]:
A10cont_logs[2].template
[19]:
'Permeability'

Similarly, using the .unit_symbol attribute, we can access the unit for any object associated with a certain template:

[20]:
symbol=A10cont_logs[2].unit_symbol
"The unit_symbol for the log {} is {}".format(A10cont_logs[2].petrel_name,symbol)
[20]:
'The unit_symbol for the log Perm is mD'

Different projects use different standard values to mark missing values. To check the value interpreted by Petrel as a ‘missing’ one we can use the .missing_value property:

[21]:
A10cont_logs[2].missing_value
[21]:
nan

To get all the continuous wells logs associated to well A10, interpolated at the same depth in a dataframe we can use the .logs_dataframe() function:

[35]:
A10logs_df = well.logs_dataframe(A10cont_logs)

# Sets MD as index
A10logs_df = A10logs_df.set_index('MD')
A10logs_df
[35]:
Gamma NetGross Perm Porosity One-way time 1 TWT TVD
MD
-527.621008 NaN NaN NaN NaN NaN -510.664881 -527.621008
-527.121008 NaN NaN NaN NaN 0.0 -510.180839 -527.121008
-526.621008 NaN NaN NaN NaN 0.0 -509.696796 -526.621008
-526.121008 NaN NaN NaN NaN 0.0 -509.212754 -526.121008
-525.621008 NaN NaN NaN NaN 0.0 -508.728711 -525.621008
... ... ... ... ... ... ... ...
2413.878992 75.862561 0.0 105.309330 0.173314 NaN 2037.715005 2413.878992
2414.378992 75.399295 0.0 103.075364 0.177510 NaN 2038.055903 2414.378992
2414.878992 NaN 0.0 NaN NaN NaN 2038.396801 2414.878992
2415.378992 NaN 0.0 NaN NaN NaN 2038.737699 2415.378992
2415.878992 NaN 0.0 NaN NaN NaN 2039.078597 2415.878992

5888 rows × 7 columns

The .create_well_log() function creates a well log for a particular well and assigns this log to a specific global well log. The function takes in one parmeter which represents the well object for which the well log is to be created. The function will output an empty well log with no values assigned to it. The difference between this function and the .clone() one is that when using the .clone() function to create a new well log for a well it will also generate a new global well log which is not always ideal .

[54]:
#Assign the log we want to copy to a variable

new_log=A10cont_logs[3].global_well_log
print(new_log)
GlobalWellLog(petrel_name="Porosity")
[58]:
#Assign the well we want to generate the new log for to a variable

all_wells_paths=petrel.wells.keys()
paths = list(all_wells_paths)
well15 = petrel.wells[paths[8]]
print(well15)
Well(petrel_name="A15")
[60]:
#Create a porosity log for well 15 and assign it to the porosity global well log

new_log.create_well_log(well15)
[60]:
WellLog(petrel_name="Porosity")

The image bellow shows the output of the previous cell. A new porosity log has been created for well 15. Note that the log has no depth or porosity values and it is assigned to the porosity global well log:

createwelllog.png

All the previous examples can be reproduced with discrete logs as most of the attributes and methods are common to all the classes. Let’s select well C2:

[29]:
from cegalprizm.pythontool.welllog import DiscreteWellLog


well_list=sorted(list(petrel.wells.keys()))
well_C2= well_list[0]
print(well_C2)
Input/Wells/Injectors/C2

Print out the associated discrete logs:

[30]:
well=petrel.wells[well_C2]
logs = well.logs
discrete_logs = [log for log in logs if type(log) is DiscreteWellLog]
print("Well C2 has", len(discrete_logs), "discrete logs:", "\n")
print(', '.join([log.petrel_name for log in logs if type(log) is DiscreteWellLog]))

Well C2 has 1 discrete logs:

Facies

Create a DataFrame of the discrete logs in well C2:

[31]:
C2Facies = well.logs_dataframe(discrete_logs[0])

# Sets MD as index
C2Facies = C2Facies.set_index('MD')
C2Facies
[31]:
Facies TWT TVD
MD
1923.907229 Silt 1824.370861 1924.172392
1928.714834 Sand 1827.628075 1927.607736
1932.181133 Fine Silt 1829.594166 1930.084630
1934.049920 Silt 1830.615324 1931.419999
1935.391227 Sand 1831.348252 1932.378449
... ... ... ...
2448.439087 Clay 2099.553700 2270.344903
2450.193287 Sand 2100.467339 2271.430009
2457.978592 Clay 2104.503249 2276.222562
2473.757075 UNDEF 2112.599674 2285.837806
2476.379880 UNDEF 2113.944556 2287.435320

89 rows × 3 columns

The .discrete_codes property returns a dictionary of discrete codes as keys and the associated facies as values. Changes to this dictionary will not be persisted or affect any Petrel objects:

facies_codes.png

[32]:
discrete_logs[0].discrete_codes
[32]:
{0: 'Clay', 1: 'Sand', 2: 'Silt', 3: 'Fine Silt'}

We can retrieve the statics of the Facies log associate with well C2 by using the .retrieve_stats() function:

Faciesstatistic.png

[33]:
discrete_logs[0].retrieve_stats()
[33]:
{'X Min': '454635',
 'X Max': '455033.33',
 'X Delta': '398.330000000016',
 'Y Min': '6787607.12',
 'Y Max': '6787607.12',
 'Y Delta': '0',
 'Z Min': '-2283.72',
 'Z Max': '-1924.84',
 'Z Delta': '358.88',
 'Facies Min': '0',
 'Facies Max': '3',
 'Facies Delta': 'NaN',
 'Is synthetic': 'No',
 'Start md': '1923.90722924',
 'Base md': '2476.37988030',
 'Average md increment': '6.27809831',
 'Number of defined samples': '87',
 '-----------------': '-----------------',
 'Type of data': 'Discrete',
 'Min': '0',
 'Max': '3',
 'Delta': '3',
 'Number of defined values': '1100',
 'Mean': '1',
 'Std. dev.': '1',
 'Variance': '1',
 'Sum': '970'}

To create a copy of the Facies log associated to well C2, we can use the .clone() function. The clone is placed in the same collection as the source object. A clone cannot be created with the same name as an existing Petrel object in the same collection.

[35]:
discrete_logs[0].clone('newFaciesLog', copy_values=True)
[35]:
DiscreteWellLog(petrel_name="newFaciesLog")