A striplog depends on a hierarchy of objects. This notebook shows the objects and their basic functionality.
Striplogs (a set of Intervals) are described in a separate notebook.
Decors and Legends are also described in another notebook.
In [1]:
import striplog
striplog.__version__
# If you get a lot of warnings here, just run it again.
Out[1]:
In [2]:
from striplog import Lexicon
print(Lexicon.__doc__)
In [3]:
lexicon = Lexicon.default()
lexicon
Out[3]:
In [4]:
lexicon.synonyms
Out[4]:
Most of the lexicon works 'behind the scenes' when processing descriptions into Rock components.
In [5]:
lexicon.find_synonym('Halite')
Out[5]:
In [6]:
s = "grysh gn ss w/ sp gy sh"
lexicon.expand_abbreviations(s)
Out[6]:
A set of attributes. All are optional.
In [7]:
from striplog import Component
In [8]:
print(Component.__doc__)
We define a new rock with a Python dict object:
In [9]:
r = {'colour': 'grey',
'grainsize': 'vf-f',
'lithology': 'sand'}
rock = Component(r)
rock
Out[9]:
The Rock has a colour:
In [10]:
rock['colour']
Out[10]:
And it has a summary, which is generated from its attributes.
In [11]:
rock.summary()
Out[11]:
We can format the summary if we wish:
In [12]:
rock.summary(fmt="My rock: {lithology} ({colour}, {grainsize!u})")
Out[12]:
The formatting supports the usual s, r, and a:
s: strr: repra: asciiAlso some string functions:
u: str.upperl: str.lowerc: str.capitalizet: str.titleAnd some numerical ones, for arrays of numbers:
+ or ∑: np.summ or µ: np.meanv: np.vard: np.stdx: np.product
In [15]:
x = {'colour': ['Grey', 'Brown'],
'bogosity': [0.45, 0.51, 0.66],
'porosity': [0.2003, 0.1998, 0.2112, 0.2013, 0.1990],
'grainsize': 'VF-F',
'lithology': 'Sand',
}
X = Component(x)
# This is not working at the moment.
#fmt = 'The {colour[0]!u} {lithology!u} has a total of {bogosity!∑:.2f} bogons'
#fmt += 'and a mean porosity of {porosity!µ:2.0%}.'
fmt = 'The {lithology!u} is {colour[0]!u}.'
X.summary(fmt)
Out[15]:
In [16]:
X.json()
Out[16]:
We can compare rocks with the usual == operator:
In [17]:
rock2 = Component({'grainsize': 'VF-F',
'colour': 'Grey',
'lithology': 'Sand'})
rock == rock2
Out[17]:
In [18]:
rock
Out[18]:
In order to create a Component object from text, we need a lexicon to compare the text against. The lexicon describes the language we want to extract, and what it means.
In [19]:
rock3 = Component.from_text('Grey fine sandstone.', lexicon)
rock3
Out[19]:
Components support double-star-unpacking:
In [20]:
"My rock: {lithology} ({colour}, {grainsize})".format(**rock3)
Out[20]:
Positions define points in the earth, like a top, but with uncertainty. You can define:
upper — the highest possible locationmiddle — the most likely locationlower — the lowest possible locationunits — the units of measurementx and y — the x and y location (these don't have uncertainty, sorry)meta — a Python dictionary containing anything you wantPositions don't have a 'way up'.
In [21]:
from striplog import Position
print(Position.__doc__)
In [22]:
params = {'upper': 95,
'middle': 100,
'lower': 110,
'meta': {'kind': 'erosive', 'source': 'DOE'}
}
p = Position(**params)
p
Out[22]:
Even if you don't give a middle, you can always get z: the central, most likely position:
In [23]:
params = {'upper': 75, 'lower': 85}
p = Position(**params)
p
Out[23]:
In [24]:
p.z
Out[24]:
In [25]:
from striplog import Interval
print(Interval.__doc__)
I might make an Interval explicitly from a Component...
In [26]:
Interval(10, 20, components=[rock])
Out[26]:
... or I might pass a description and a lexicon and Striplog will parse the description and attempt to extract structured Component objects from it.
In [27]:
Interval(20, 40, "Grey sandstone with shale flakes.", lexicon=lexicon).__repr__()
Out[27]:
Notice I only got one Component, even though the description contains a subordinate lithology. This is the default behaviour, we have to ask for more components:
In [28]:
interval = Interval(20, 40, "Grey sandstone with black shale flakes.", lexicon=lexicon, max_component=2)
print(interval)
Intervals have a primary attribute, which holds the first component, no matter how many components there are.
In [29]:
interval.primary
Out[29]:
Ask for the summary to see the thickness and a Rock summary of the primary component. Note that the format code only applies to the Rock part of the summary.
In [30]:
interval.summary(fmt="{colour} {lithology}")
Out[30]:
We can change an interval's properties:
In [31]:
interval.top = 18
In [32]:
interval
Out[32]:
In [33]:
interval.top
Out[33]:
In [34]:
# Depth ordered
i1 = Interval(top=61, base=62.5, components=[Component({'lithology': 'limestone'})])
i2 = Interval(top=62, base=63, components=[Component({'lithology': 'sandstone'})])
i3 = Interval(top=62.5, base=63.5, components=[Component({'lithology': 'siltstone'})])
i4 = Interval(top=63, base=64, components=[Component({'lithology': 'shale'})])
i5 = Interval(top=63.1, base=63.4, components=[Component({'lithology': 'dolomite'})])
# Elevation ordered
i8 = Interval(top=200, base=100, components=[Component({'lithology': 'sandstone'})])
i7 = Interval(top=150, base=50, components=[Component({'lithology': 'limestone'})])
i6 = Interval(top=100, base=0, components=[Component({'lithology': 'siltstone'})])
In [35]:
i2.order
Out[35]:
Technical aside: The Interval class is a functools.total_ordering, so providing __eq__ and one other comparison (such as __lt__) in the class definition means that instances of the class have implicit order. So you can use sorted on a Striplog, for example.
It wasn't clear to me whether this should compare tops (say), so that '>' might mean 'above', or if it should be keyed on thickness. I chose the former, and implemented other comparisons instead.
In [36]:
print(i3 == i2) # False, they don't have the same top
print(i1 > i4) # True, i1 is above i4
print(min(i1, i2, i5).summary()) # 0.3 m of dolomite
In [37]:
i2 > i4 > i5 # True
Out[37]:
We can combine intervals with the + operator. (However, you cannot subtract intervals.)
In [38]:
i2 + i3
Out[38]:
Adding a rock adds a (minor) component and adds to the description.
In [39]:
interval + rock3
Out[39]:
In [40]:
i6.relationship(i7), i5.relationship(i4)
Out[40]:
In [41]:
print(i1.partially_overlaps(i2)) # True
print(i2.partially_overlaps(i3)) # True
print(i2.partially_overlaps(i4)) # False
print()
print(i6.partially_overlaps(i7)) # True
print(i7.partially_overlaps(i6)) # True
print(i6.partially_overlaps(i8)) # False
print()
print(i5.is_contained_by(i3)) # True
print(i5.is_contained_by(i4)) # True
print(i5.is_contained_by(i2)) # False
In [42]:
x = i4.merge(i5)
x[-1].base = 65
x
Out[42]:
In [43]:
i1.intersect(i2, blend=False)
Out[43]:
In [44]:
i1.intersect(i2)
Out[44]:
In [45]:
i1.union(i3)
Out[45]:
In [46]:
i3.difference(i5)
Out[46]:
©2015 Agile Geoscience. Licensed CC-BY. striplog.py