name: data-and-structure
description: "Construct, inspect, import, and mutate GemPy input tables and
structural frames; use this for surface points, orientations, elements,
groups, IDs, colors, and input validation."
disable-model-invocation: true
metadata:
disco-role: operating
license: EUPL 1.2
GemPy data and structure
Use this sub-skill when a request concerns input data or geological
organization, before model interpolation. A GemPy model stores input in this
hierarchy:
GeoModel -> StructuralFrame -> StructuralGroup -> StructuralElement -> SurfacePointsTable + OrientationsTable.
A group is an ordered series/stack (or a fault group); an element is one named
surface or fault. The frame's group order is geological structure, not merely a
container order. Keep the same element names/IDs in surface points and
orientations, and validate before computing.
Route and boundaries
- Construct or compute a model, map a stack to surfaces, or run interpolation:
use
modeling.
- Configure regular/custom/section/topography grids or plot input/results: use
grids-and-visualization.
- Save/load JSON or
.gempy, or use advanced mesh/plugin workflows: use
serialization-and-advanced.
- Install GemPy or diagnose missing pandas/viewer/engine dependencies: use
environment-and-troubleshooting.
The public data API is available through gempy as gp and gp.data. The
recipes below use only caller-provided arrays, DataFrames, or file paths; they
do not require GemPy's source checkout or example data.
Core workflow
- Build
SurfacePointsTable and OrientationsTable from equal-length numeric
arrays, usually with from_arrays.
- Use one explicit
name_id_map for both tables when IDs must be controlled;
otherwise let GemPy generate an opaque ID per distinct name.
- Build a frame with
StructuralFrame.from_data_tables(surface_points, orientations) or start with StructuralFrame.initialize_default_structure()
for incremental GeoModel construction.
- Inspect
frame.elements_names, element_id_name_map, counts, and the table
.df/.data before mapping or computing.
- Mutate through
gp.add_*/gp.modify_*, or replace a frame table through the
model setter. Re-run model.validate() and then route computation to
modeling.
A minimal direct construction is:
import numpy as np
import gempy as gp
sp = gp.data.SurfacePointsTable.from_arrays(
x=np.array([0., 1., 0.]), y=np.array([0., 0., 1.]), z=np.array([0., 0., 0.]),
names="Layer",
)
ori = gp.data.OrientationsTable.from_arrays(
x=np.array([0.5]), y=np.array([0.5]), z=np.array([0.]),
G_x=np.array([0.]), G_y=np.array([0.]), G_z=np.array([1.]),
names=["Layer"], name_id_map=sp.name_id_map,
)
frame = gp.data.StructuralFrame.from_data_tables(sp, ori)
from_data_tables creates one default erosional group and an element for each
surface-point ID. An element with no matching orientation receives an empty
orientation table; this is allowed as a data structure, but may be insufficient
for later model validation/computation.
Input/mutation recipes
Add to an existing model
# model must already contain an element named "Layer"
gp.add_surface_points(
model, x=[2., 3.], y=[0., 0.], z=[1., 1.],
elements_names=["Layer", "Layer"],
)
gp.add_orientations(
model, x=[2.], y=[0.], z=[1.], elements_names=["Layer"],
pole_vector=np.array([[0., 0., 1.]]),
)
add_surface_points and add_orientations append rows to the named element
and return the model's StructuralFrame. All coordinate/name/pole/nugget
sequences must have the same length. An unknown element name raises
ValueError; neither add_* creates a new element. Create an element and put
it in a group first when adding a new surface.
add_orientations accepts a representation in one of these forms:
pole_vector: an (n, 3) array of gradient components [G_x, G_y, G_z].
orientation: an (n, 3) array [azimuth_degrees, dip_degrees, polarity].
Pass one representation. If both are supplied, the current implementation
converts orientation and ignores the supplied pole vector. A missing
representation raises ValueError. A missing nugget uses GemPy's
current defaults (0.01 for orientations and 0.00002 for surface points).
Modify existing rows
# Target by element name, or use a global row index/slice.
gp.modify_surface_points(model, elements_names=["Layer"], Z=np.array([1., 1., 2.]))
gp.modify_surface_points(model, slice=0, X=0.25, nugget=0.00002)
gp.modify_orientations(
model, slice=slice(0, 1),
G_x=np.array([0.0]), G_y=np.array([0.0]), G_z=np.array([1.0]),
)
modify_surface_points accepts X, Y, Z, and nugget;
modify_orientations accepts those coordinates plus G_x, G_y, G_z, and
nugget. Scalars broadcast through NumPy structured-array assignment; arrays
must match the selected row count. Surface-point selection cannot specify both
elements_names and slice. These functions update the model through the
frame table setters; returned value is the updated StructuralFrame.
The current inspected GemPy release exposes angular keyword handling in
modify_orientations, but its implementation raises an unpacking ValueError
when azimuth, dip, or polarity is used. Treat angular modification as a
known compatibility gap: convert angles yourself and write G_x/G_y/G_z, or
recreate the orientation rows with add_orientations. Do not claim angular
modification works unless a later package version is verified.
Delete rows or elements
gp.delete_surface_points() and gp.delete_orientations() are exported but
currently are zero-argument stubs that raise NotImplementedError; they do not
provide a selector. For a controlled row deletion, filter a copy and assign it
back, preserving the structured dtype:
sp = model.surface_points_copy
keep = sp.data["Z"] >= 0.0
sp.data = sp.data[keep]
model.surface_points = sp
ori = model.orientations_copy
keep_ori = ori.data["X"] != 2.0
ori.data = ori.data[keep_ori]
model.orientations = ori
model.validate()
This is a low-level workaround: filter by the table's numeric fields or IDs,
never by an assumed row order, and validate immediately. Removing the only
observations can make the model empty or underdetermined.
Validation gate
Before handing off to modeling, check model.validate(). It raises
gempy.data.ModelValidationError, whose useful attributes are field,
reason, message, and context. Current semantic checks include:
- no surface points and no orientations (
empty_model);
- an empty structural group (
empty_non_fault_group or empty_fault_group);
- at most one surface point with no orientation (
underdetermined_input);
- a
BASEMENT group before the final group (basement_relation_on_non_last_group).
For fault groups, accessing the frame's fault descriptor also requires a square
fault-relation matrix with one row/column per structural group. For a failure,
inspect model.structural_frame.structural_groups, elements_names, table
lengths, IDs, and model.structural_frame.fault_relations; repair the structure
before calling compute. A successful data-table construction is not proof that
interpolation is determined.
Verification and recovery
Use the bundled table inspector for a caller-owned CSV pair:
python sub-skills/data-and-structure/scripts/inspect_tables.py \
--surface-points points.csv --orientations orientations.csv
It reports canonical columns, row counts, IDs, names, and finite-value checks
without modifying files. For numeric/shape errors, first convert inputs with
np.asarray(..., dtype=float), assert all row counts agree, and print
table.data.dtype/table.data.shape. For name/ID errors, pass the same
name_id_map to both constructors and compare it with
frame.element_name_id_map. For sparse inputs, add non-collinear surface
points and at least one orientation per interpolated element, then call
model.validate() again.
For more column-level contracts and structural relationships, read
references/data-formats.md and
references/api-reference.md. For failures,
read references/troubleshooting.md. For
persistence, route the already validated model to
serialization-and-advanced; do not serialize ad hoc table internals as a
replacement for model persistence.