Installs into .claude/skills of the current project.
Are you the author of Data And Structure?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/vectorspacelab-data-and-structure)
---
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`](../modeling/SKILL.md).
- Configure regular/custom/section/topography grids or plot input/results: use
[`grids-and-visualization`](../grids-and-visualization/SKILL.md).
- 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
1. Build `SurfacePointsTable` and `OrientationsTable` from equal-length numeric
arrays, usually with `from_arrays`.
2. Use one explicit `name_id_map` for both tables when IDs must be controlled;
otherwise let GemPy generate an opaque ID per distinct name.
3. Build a frame with `StructuralFrame.from_data_tables(surface_points,
orientations)` or start with `StructuralFrame.initialize_default_structure()`
for incremental `GeoModel` construction.
4. Inspect `frame.elements_names`, `element_id_name_map`, counts, and the table
`.df`/`.data` before mapping or computing.
5. 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`](../modeling/SKILL.md).
A minimal direct construction is:
```python
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
```python
# 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
```python
# 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:
```python
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:
```bash
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`](references/data-formats.md) and
[`references/api-reference.md`](references/api-reference.md). For failures,
read [`references/troubleshooting.md`](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.