diff --git a/doc/Petrophysics_Handbook.pdf b/doc/Petrophysics_Handbook.pdf new file mode 100644 index 0000000..f54443d Binary files /dev/null and b/doc/Petrophysics_Handbook.pdf differ diff --git a/tools/gen_petrolib_handbook.py b/tools/gen_petrolib_handbook.py index 44dae79..61a7026 100644 --- a/tools/gen_petrolib_handbook.py +++ b/tools/gen_petrolib_handbook.py @@ -7,16 +7,19 @@ "Code for Petrophysics Jan/Feb 2014 - Jun/Jul 2026", and "https://github.com/ingehap/Petrophysics_Code"; * one blank page; +* a table of acronyms (CBL, NMR, ...) and a table of non-SI units + (MRayl, mD, ...) used in the manual; * an alphabetical table of contents of every public petrolib function; -* one page per function: Python path, purpose, input parameters (each with - its meaning), output parameters, and the sources as full SPWLA - *Petrophysics* journal citations resolved from the module References - sections. +* one page per function: general call format, Python path, purpose, input + parameters (each with its meaning), output parameters, and the sources + as full SPWLA *Petrophysics* journal citations resolved from the module + References sections. Parameter meanings and output descriptions live in tools/handbook_descriptions.json (keyed module -> function -> params / -returns); keep it in sync when the petrolib API changes — missing entries -are reported on stderr. Requires reportlab: +returns); the acronym and unit tables live in tools/handbook_glossary.json. +Keep both in sync when the petrolib API changes — missing entries are +reported on stderr. Requires reportlab: pip install reportlab python tools/gen_petrolib_handbook.py @@ -34,6 +37,7 @@ ROOT = pathlib.Path(__file__).resolve().parents[1] OUT = ROOT / "doc" / "Petrophysics_Handbook.pdf" DESCRIPTIONS = pathlib.Path(__file__).with_name("handbook_descriptions.json") +GLOSSARY = pathlib.Path(__file__).with_name("handbook_glossary.json") RETURN_ALIASES = { "_Float": "numpy.ndarray of float64", @@ -79,6 +83,20 @@ class Entry: returns_meaning: str = "" sources: list[str] = field(default_factory=list) + @property + def signature(self) -> str: + """General call format, e.g. ``f(x, y, *, clip=None)``.""" + bits: list[str] = [] + star_done = False + for p in self.params: + if p.kw_only and not star_done: + bits.append("*") + star_done = True + if p.name.startswith("*"): + star_done = True + bits.append(p.name + (f"={p.default}" if p.default is not None else "")) + return f"{self.name}({', '.join(bits)})" + def parse_references(module_doc: str) -> dict[str, str]: """Map srcYYYY_MM tags to full citations from a References section.""" @@ -278,8 +296,8 @@ def build_pdf(entries: list[Entry]) -> None: func_heading = ParagraphStyle( "FuncHeading", fontName="DejaVuSansMono", - fontSize=13, - leading=16, + fontSize=11.5, + leading=14.5, spaceAfter=0, textColor=navy, ) @@ -353,19 +371,46 @@ def on_page(canvas: object, _doc: object) -> None: story.append(NextPageTemplate("normal")) story.append(PageBreak()) - # ---- table of contents - story.append( - Paragraph( - "Table of Contents", - ParagraphStyle( - "TocTitle", - fontName="DejaVuSans-Bold", - fontSize=18, - textColor=navy, - spaceAfter=10, - ), - ) + section_title = ParagraphStyle( + "SectionTitle", + fontName="DejaVuSans-Bold", + fontSize=18, + textColor=navy, + spaceAfter=10, ) + glossary_style = TableStyle( + [ + ("FONTNAME", (0, 0), (-1, 0), "DejaVuSans-Bold"), + ("FONTSIZE", (0, 0), (-1, 0), 8.6), + ("FONTSIZE", (0, 1), (-1, -1), 8.6), + ("TEXTCOLOR", (0, 0), (-1, 0), navy), + ("LINEBELOW", (0, 0), (-1, 0), 0.6, navy), + ("LINEBELOW", (0, 1), (-1, -2), 0.25, colors.Color(0.85, 0.85, 0.88)), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("TOPPADDING", (0, 0), (-1, -1), 2), + ("BOTTOMPADDING", (0, 0), (-1, -1), 2), + ("LEFTPADDING", (0, 0), (-1, -1), 2), + ] + ) + term = ParagraphStyle("Term", parent=body, fontName="DejaVuSans-Bold", fontSize=8.8) + + def glossary_table(title: str, col0: str, items: dict[str, str]) -> None: + story.append(Paragraph(title, section_title)) + rows: list[list[object]] = [[col0, "Meaning"]] + for k in sorted(items, key=str.lower): + rows.append([Paragraph(esc(k), term), Paragraph(rich(items[k]), body)]) + t = Table(rows, colWidths=[38 * mm, 128 * mm], repeatRows=1) + t.setStyle(glossary_style) + story.append(t) + story.append(PageBreak()) + + # ---- acronyms, then non-SI units + glossary = json.loads(GLOSSARY.read_text()) + glossary_table("Acronyms", "Acronym", glossary["acronyms"]) + glossary_table("Non-SI Units", "Unit", glossary["units"]) + + # ---- table of contents + story.append(Paragraph("Table of Contents", section_title)) toc = TableOfContents() toc.levelStyles = [ ParagraphStyle( @@ -382,15 +427,23 @@ def on_page(canvas: object, _doc: object) -> None: # ---- one page per function blank_line = Spacer(1, 12.4) # one body-height line with no text + mono_line = ParagraphStyle( + "MonoLine", parent=body, fontName="DejaVuSansMono", fontSize=9.4, leading=12.4 + ) for i, e in enumerate(entries): key = f"e{i}" path = f"{e.module}.{e.name}" - head = Paragraph(f'{esc(path)}', func_heading) + story.append(Paragraph(f'Call format', label)) + head = Paragraph(esc(e.signature), func_heading) head._toc_key = key head._toc_text = f"{e.name} — {e.module}" story.append(head) block: list[object] = [blank_line] + block.append(Paragraph("Python path", label)) + block.append(Paragraph(esc(path), mono_line)) + + block.append(blank_line) block.append(Paragraph("Purpose", label)) if e.purpose: for para in e.purpose.split("\n\n"): @@ -455,7 +508,8 @@ def on_page(canvas: object, _doc: object) -> None: block.append(Paragraph("• " + rich(s), src_style)) # shrink-to-fit so every function occupies exactly one page - story.append(KeepInFrame(frame_w, frame_h - 14 * mm, block, mode="shrink", hAlign="LEFT")) + # (28 mm reserved for the call-format heading, which can wrap) + story.append(KeepInFrame(frame_w, frame_h - 28 * mm, block, mode="shrink", hAlign="LEFT")) story.append(PageBreak()) doc.multiBuild(story) diff --git a/tools/handbook_glossary.json b/tools/handbook_glossary.json new file mode 100644 index 0000000..791f03a --- /dev/null +++ b/tools/handbook_glossary.json @@ -0,0 +1,124 @@ +{ + "acronyms": { + "AAPE": "Average absolute percentage error, a regression fit-quality metric.", + "ANNIE": "The Schoenberg-Muir-Sayers (1996) simplified three-parameter transversely isotropic velocity model for shales, behind the annie_c11/annie_c13 stiffness approximations.", + "API": "American Petroleum Institute; API units calibrate gamma-ray logs (see the non-SI units table) and API gravity grades crude oils.", + "BET": "Brunauer-Emmett-Teller multilayer gas-adsorption isotherm, the basis of specific-surface-area measurement.", + "BI": "Bond index: cement-bond quality normalized to [0, 1] between the free-pipe (0) and well-bonded (1) anchors.", + "BPP": "Bloembergen-Purcell-Pound theory of NMR relaxation by molecular motion.", + "BVI": "Bulk volume irreducible: the NMR porosity fraction below the T2 cutoff, held as capillary-bound water.", + "BVW": "Bulk volume water: porosity multiplied by water saturation.", + "C/O": "Carbon-oxygen ratio from inelastic neutron spectroscopy, an oil-saturation indicator independent of water salinity.", + "C1-C5": "Light-hydrocarbon components in mud-gas analysis: C1 methane, C2 ethane, C3 propane, C4 butanes, C5 pentanes.", + "CBL": "Cement bond log: the cased-hole sonic amplitude/attenuation measurement of casing-to-cement bond quality.", + "CBW": "Clay-bound water: the shortest-T2 fraction of the NMR porosity spectrum.", + "CEC": "Cation exchange capacity of clay (typically meq/g), the source of excess conductivity in shaly-sand models.", + "CFL": "Courant-Friedrichs-Lewy stability number limiting the time step of explicit finite-difference schemes.", + "CGR": "Computed (uranium-free) gamma ray built from the potassium and thorium contributions only.", + "Cij": "Elastic stiffness-tensor components in Voigt notation (C11, C33, C44, C66, C13, C12).", + "CODATA": "Committee on Data for Science and Technology; source of the recommended values of the physical constants used in petrolib.constants.", + "CPMG": "Carr-Purcell-Meiboom-Gill multi-echo pulse sequence used to measure NMR T2.", + "CRIM": "Complex refractive index method, a volumetric mixing law for dielectric permittivity.", + "CT": "(X-ray) computed tomography of core; its attenuation contrast maps to porosity and saturation.", + "DC": "Direct current, i.e. the zero-frequency limit (e.g. the DC resistivity rho0 of Cole-Cole dispersion).", + "DLIS": "Digital Log Interchange Standard wireline data format.", + "DOI": "Digital object identifier of a journal article.", + "DT": "Sonic transit-time (slowness) log mnemonic.", + "DTW": "Dynamic time warping, the elastic depth-alignment algorithm for log curves.", + "ECD": "Equivalent circulating density: effective mud density including annular friction losses while circulating.", + "EM": "Electromagnetic.", + "FFI": "Free fluid index: the NMR porosity fraction above the T2 cutoff, i.e. producible fluid.", + "FHZ": "Flory-Huggins-Zuo equation of state for asphaltene concentration gradients.", + "FZI": "Flow zone indicator: RQI/phi_z, the Amaefule-Tiab hydraulic rock-typing variable.", + "GLCM": "Gray-level co-occurrence matrix, a texture descriptor for borehole images.", + "GOC": "Gas-oil contact.", + "GOR": "Gas-oil ratio.", + "GR": "(Natural) gamma-ray log.", + "HC": "Hydrocarbon.", + "HCPV": "Hydrocarbon pore volume.", + "HI": "Hydrogen index: hydrogen density relative to fresh water.", + "IEEE": "Institute of Electrical and Electronics Engineers; its standard 754 defines the binary floating-point arithmetic assumed by the regression tests.", + "IGR": "Gamma-ray index (GR - GR_clean)/(GR_shale - GR_clean), the linear input to shale-volume transforms.", + "IQR": "Interquartile range (Q3 - Q1), a robust spread measure.", + "ISTA": "Iterative shrinkage-thresholding algorithm for L1-regularized (sparse) inversion.", + "L1 / L2": "Sum-of-absolute-values and Euclidean (sum-of-squares) vector norms used in misfits and regularization.", + "LAS": "Log ASCII Standard wireline data format.", + "LET": "Lomeland-Ebeltoft-Thomas three-parameter relative-permeability correlation.", + "LM": "Levenberg-Marquardt damped least-squares algorithm for nonlinear fitting.", + "LOM": "Level of organic metamorphism: the thermal-maturity scale in Passey's Delta-log-R TOC method.", + "MAD": "Median absolute deviation, a robust scale estimate used for despiking.", + "MALA": "Metropolis-adjusted Langevin algorithm, a gradient-informed MCMC sampler.", + "MAP": "Maximum a posteriori: the mode of a Bayesian posterior distribution.", + "MCMC": "Markov chain Monte Carlo posterior sampling.", + "MICP": "Mercury injection capillary pressure measurement.", + "ML": "Machine learning.", + "MW": "Mud weight (in the drilling gas-normalization formulas).", + "N_A": "Avogadro constant, 6.022e23 per mole.", + "NCS": "Net confining stress on a core sample.", + "ND": "Neutron-density shale/clay indicator built from the neutron-porosity and density logs.", + "NMR": "Nuclear magnetic resonance.", + "NPHI": "Neutron-porosity log mnemonic.", + "OB": "Overburden (vertical total) stress or pressure, as in the Eaton pore-pressure relation.", + "OD": "Optical density (asphaltene gradients); also outer diameter (casing condition).", + "ODE": "Ordinary differential equation.", + "OM": "Organic matter.", + "OSI": "Oil saturation index: Rock-Eval S1 divided by TOC, in mg hydrocarbon per g TOC.", + "OWC": "Oil-water contact.", + "P-wave": "Compressional (primary) elastic wave.", + "PEF": "Photoelectric factor log.", + "PR": "Peng-Robinson cubic equation of state.", + "RD": "Deep-resistivity log mnemonic.", + "RDDTW": "Regularized derivative dynamic time warping.", + "RF": "Ratio factor of the minimum-curvature well-trajectory method.", + "RFN": "Rock fabric number in Lucia's carbonate permeability classification.", + "RHOB": "Bulk-density log mnemonic.", + "RI": "Resistivity index Rt/R0 in Archie's second law.", + "RMS": "Root mean square.", + "ROP": "Rate of penetration while drilling.", + "RQI": "Rock quality index 0.0314*sqrt(k/phi).", + "S-wave": "Shear (secondary) elastic wave.", + "S/V": "Pore surface-to-volume ratio, which controls NMR surface relaxation.", + "SCAL": "Special core analysis.", + "SDR": "Schlumberger-Doll Research NMR permeability transform k = C*phi^4*T2LM^2.", + "SEM": "Scanning electron microscopy.", + "SG": "Specific gravity: density relative to fresh water (liquids) or to air (gases).", + "SGR": "Spectral (total) gamma ray reconstructed from the K, U, and Th concentrations.", + "SI": "The International System of Units; petrolib's default unit convention.", + "SNR": "Signal-to-noise ratio.", + "SPWLA": "Society of Petrophysicists and Well Log Analysts, publisher of the journal Petrophysics.", + "SSA": "Specific surface area.", + "STP": "Standard temperature and pressure.", + "SVD": "Singular value decomposition.", + "T1": "Longitudinal (spin-lattice) NMR relaxation time.", + "T2": "Transverse (spin-spin) NMR relaxation time; T2LM is its amplitude-weighted logarithmic mean.", + "TE": "Inter-echo spacing of the CPMG pulse sequence.", + "TMALI": "Thermal Maturity-Adjusted Log Interpretation, the Craddock et al. (2019) organic-shale workflow.", + "TOC": "Total organic carbon.", + "TP / FP / TN / FN": "True/false positive/negative counts of a binary classification.", + "TVD": "True vertical depth.", + "ULP": "Unit in the last place: the spacing between adjacent floating-point numbers.", + "USBM": "U.S. Bureau of Mines wettability index.", + "VMD": "Variational mode decomposition, a signal-denoising method.", + "VTI": "Vertical transverse isotropy: a layered medium with a vertical axis of rotational symmetry." + }, + "units": { + "API unit (gAPI)": "Gamma-ray calibration unit of the American Petroleum Institute, defined so the contrast of the API test pit in Houston reads 200 units; gamma-ray logs are recorded in gAPI.", + "bar": "Pressure unit: 1 bar = 100 kPa = 14.504 psi.", + "c.u.": "Capture unit of the thermal-neutron capture cross-section Sigma: 1 c.u. = 1e-3 per cm.", + "darcy (D), millidarcy (mD)": "Permeability: 1 darcy = 9.869233e-13 m2 (about 1 um2); 1 mD = 1e-3 D.", + "degF": "Degree Fahrenheit: T[degF] = 1.8*T[degC] + 32.", + "ft": "Foot: 1 ft = 0.3048 m exactly (also ft/s velocities, ft/hr penetration rates, and psi/ft gradients).", + "g/cc": "Gram per cubic centimetre (CGS density): 1 g/cc = 1000 kg/m3.", + "in.": "Inch: 1 in. = 25.4 mm exactly (bit and casing diameters).", + "M (molar)": "Mole per litre of solution: 1 M = 1000 mol/m3.", + "meq/g, meq/ml": "Milliequivalent (1e-3 mol of unit charge) per gram or per millilitre; the units of CEC and of Qv in shaly-sand conductivity models.", + "MRayl": "Megarayl, specific acoustic impedance: 1 MRayl = 1e6 rayl, where 1 rayl = 1 Pa*s/m = 1 kg/(m2*s).", + "p.u.": "Porosity unit: one percentage point of porosity (0.01 v/v).", + "ppg": "Pound per US gallon (mud weight): 1 ppg = 119.83 kg/m3, equivalent to a hydrostatic gradient of 0.052 psi/ft.", + "ppm": "Part per million (fraction 1e-6), e.g. NaCl salinity or trace-element concentration.", + "psi": "Pound-force per square inch: 1 psi = 6894.757 Pa; the gradient 1 psi/ft = 22.62 kPa/m.", + "us/ft": "Microsecond per foot, the sonic slowness convention: 1 us/ft = 3.2808 us/m; velocity in ft/s is 1e6 divided by the slowness.", + "v/v": "Volume fraction (dimensionless); the porosity and saturation convention.", + "wt%": "Weight (mass) percent." + } +}