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:
[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:
[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:
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:
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:
[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:
[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")