Exercise 3.9
Formation Tops Database
Geologists track formation tops: the depth at which each formation is first encountered in each well. The natural structure is a dictionary of dictionaries: each well maps to {formation: top_depth_ft}. The starter gives you four OML 58 wells. Note that OD-007 never reached the E3000 sand; its dict simply omits that key.
Write three functions:
wells_with_formation(db, formation): the names of all wells that
penetrated a given formation.
average_top_depth(db, formation): the mean top depth of a formation
across the wells that have it (skip wells that don't).
formation_at_depth(db, well, depth_ft): which formation you are in at
a given depth in a given well: the formation with the deepest top that is still at or above depth_ft. Return None if depth_ft is shallower than the first formation top.
Then draw a correlation chart: formation top depth on the y-axis (with depth increasing downward, invert the axis), wells along the x-axis, one line connecting each formation across the wells. This side-by-side top correlation is one of the most common pictures in subsurface geology.
Stuck? Reveal hints one at a time — they progress from nudge to near-solution.
visibilityReveal reference solutionexpand_more
Try solving it yourself first — the hints walk you through it. The solution below is one valid approach; yours may differ and still be correct.
import matplotlib.pyplot as plt
formation_tops = {
"OD-001": {"Benin": 200, "Agbada": 3800, "Akata": 8200, "E3000": 9400},
"OD-003": {"Benin": 180, "Agbada": 3650, "Akata": 8050, "E3000": 9150},
"OD-005": {"Benin": 220, "Agbada": 3900, "Akata": 8400, "E3000": 9600},
"OD-007": {"Benin": 160, "Agbada": 3600, "Akata": 7950},
}
def wells_with_formation(db, formation):
return [well for well, tops in db.items() if formation in tops]
def average_top_depth(db, formation):
depths = [tops[formation] for tops in db.values() if formation in tops]
return sum(depths) / len(depths) if depths else None
def formation_at_depth(db, well, depth_ft):
candidates = [(fm, top) for fm, top in db[well].items() if top <= depth_ft]
if not candidates:
return None
return max(candidates, key=lambda pair: pair[1])[0]
# Correlation chart - depth downward, one line per formation across wells.
well_names = list(formation_tops.keys())
x = list(range(len(well_names)))
fig, ax = plt.subplots(figsize=(10, 6))
for formation in ["Benin", "Agbada", "Akata", "E3000"]:
xs = [i for i, w in enumerate(well_names) if formation in formation_tops[w]]
ys = [formation_tops[w][formation] for w in well_names if formation in formation_tops[w]]
ax.plot(xs, ys, marker="o", linewidth=2, label=formation)
ax.set_xticks(x)
ax.set_xticklabels(well_names)
ax.set_xlabel("Well")
ax.set_ylabel("Depth (ft)")
ax.invert_yaxis()
ax.legend()
ax.set_title("Formation tops correlation - OML 58")
plt.tight_layout()
plt.show()
lockCopying code is a Full Access feature.