Python API

A geotechnical finite element analysis is a Python script.

katai is the Python package of KATAI 2D. A script reads like the engineering problem — materials, geometry, water, loads, structures, phases — and every object is created in one call that hands back a handle. Phases switch handles on and off. That is the whole of staged construction, and it makes a parametric study a for loop.

A footing, its settlement and its factor of safety

No input file, no project folder, no session to keep alive. The script on the right builds a strip footing on clay, loads it in a second phase, and asks for the factor of safety of the loaded state. The three comment lines at the bottom are what the published 0.9.0 wheel prints, in about forty seconds: no displacement in the K0 state, 24.7 mm under the footing, a factor of safety of 1.78. (A fos of −1 means the phase did not compute one; the displacement of a Safety phase is the failure mechanism and has no physical magnitude.)

  • Fixed units, stated in every docstring: kN, m, day (seconds in dynamic phases).
  • Handles, not names. What a call returns is what a phase activates.
  • One contract. prj.save("job.k2d") writes the same project the command line and the desktop application read — byte-identical to a hand-written file, pinned by the test suite.
  • Arrays out. Results are NumPy data, not screenshots.
import katai

# Units everywhere: kN, m, day.
prj = katai.Project("Strip footing", mesh_size=1.0, auto_refine=False)

clay = prj.materials.mohr_coulomb("Stiff clay", E=2.0e4, nu=0.3,
                                  c=10.0, phi=25.0, gamma=18.0)

prj.geometry.rectangle(0, 0, 20, 10, material=clay)
load = prj.loads.line_load((8, 10), (12, 10), qy=-100.0)   # 100 kPa over 4 m

prj.initial(procedure="k0", exclude=[load])                # ground first, load later
prj.phases.plastic("Load the footing", activate=[load])
prj.phases.safety("Factor of safety under load")

job = prj.run()
for res in job.results():
    print(res.ok, f"max|u| = {res.max_disp:.4f} m", f"FoS = {res.fos:.3f}")

# True max|u| = 0.0000 m FoS = -1.000     <- K0 state: no displacement
# True max|u| = 0.0247 m FoS = -1.000     <- 24.7 mm under the footing
# True max|u| = 18.8336 m FoS = 1.780     <- factor of safety 1.78

The surface

Ten namespaces, at a glance.

NamespaceWhat it makes
prj.materialslinear_elastic · mohr_coulomb · hardening_soil · hs_small · soft_soil · soft_soil_creep · hoek_brown
prj.geometrypolygon(points, material=, fix=[…], flow=[…]) · rectangle(x0, y0, x1, y1, material=)
prj.watertable(y) · phreatic_line(points)
prj.loadsline_load(a, b, qx=, qy=) · point_load(at, qx=, qy=)
prj.structuresplate · anchor · geogrid · pile · interface
prj.displacementsline(a, b, ux=, uy=) — prescribed displacement
prj.dewateringwell(a, b, q=, h_min=) · drain(a, b, head=)
prj.phasesplastic · consolidation · safety · transient_flow · fully_coupled · dynamic
prjinitial(procedure=) · validate() · build() · save(path) · run()
kataisummary(job, prj) · extremes(result) · Refusal · run(project)

Stiffnesses are per metre out of plane, as everywhere in plane strain. Anchors and pile rows are the exception and say so: they are discrete members, so they carry a spacing in metres and the engine divides by it.

Reference

Materials, structures, phases, results.

Signatures checked against the source of release 0.9.0. Parameter values are illustrative.

Materials

prj.materials.linear_elastic("Concrete", E=3.0e7, nu=0.2, gamma=24.0,
                             drainage="non_porous")

prj.materials.mohr_coulomb("Sand", E=3.0e4, nu=0.3, c=1.0, phi=33.0, psi=3.0,
                           gamma=18.0, gamma_sat=20.0, k=1.0)

prj.materials.hardening_soil("Dense sand", E50=4.0e4, Eoed=4.0e4, Eur=1.2e5,
                             m=0.5, c=1.0, phi=35.0, psi=5.0, gamma=19.0)

prj.materials.hs_small("Stiff clay", E50=3.0e4, Eoed=3.0e4, Eur=9.0e4,
                       G0=1.875e5, gamma07=1.5e-4, c=10.0, phi=28.0, gamma=20.0)

prj.materials.soft_soil("Soft clay", lam_star=0.10, kap_star=0.02,
                        c=1.0, phi=25.0, gamma=16.0, drainage="undrained_a", k=1.0e-4)

prj.materials.soft_soil_creep("Peat", lam_star=0.15, kap_star=0.03, mu_star=0.006,
                              c=2.0, phi=20.0, gamma=12.0, drainage="undrained_a")

prj.materials.hoek_brown("Phyllite", E=7.5e6, nu=0.25, sigma_ci=50.0e3,
                         mi=7.0, gsi=48.0, D=0.0)

# drainage: "drained" | "undrained_a" | "undrained_b" | "undrained_c" | "non_porous"

What each model computes, with its equations: constitutive models.

Structures

wall = prj.structures.plate((12, 20), (12, 6), EA=1.2e7, EI=1.0e5, w=5.0,
                            Mp=0.0, Np=0.0, interfaces="both", name="Wall")
anch = prj.structures.anchor((12, 18), (20, 14), EA=2.0e5, spacing=2.5,
                             prestress=300.0, Fmax_tens=0.0, name="Anchor")
grid = prj.structures.geogrid((4, 6), (20, 6), EA=1.0e4, Np=50.0)
pile = prj.structures.pile((12, 20), (12, 6), E=3.0e7, diameter=0.6, spacing=2.5,
                           Tskin_max=0.0, Fmax_base=0.0, connection="hinged")
slip = prj.structures.interface((4, 6), (20, 6))

# interfaces: "both" | "positive" | "negative" | "none"
# Mp, Np, Fmax_*, Tskin_max = 0 means UNLIMITED, not zero capacity.

Phases

prj.initial(procedure="k0", exclude=[anch])       # "k0" | "gravity" | "safety"

prj.phases.plastic("Excavate", activate=[anch], deactivate=[pit])
prj.phases.plastic("Half the stage", apply_fraction=0.5)        # Sum-Mstage
prj.phases.plastic("Design check", design="ec7_da3")            # partial factors
prj.phases.plastic("Dewater", water=8.0)                        # phase water level

prj.phases.consolidation("Wait a month", duration=30.0, steps=100)
prj.phases.consolidation("Until 90 %", until_degree=90)         # reports its time
prj.phases.consolidation("Until 1 kPa", until_excess_pore=1.0)

prj.phases.safety("FoS")
prj.phases.transient_flow("Drawdown", duration=10.0, steps=10)
prj.phases.fully_coupled("Rapid drawdown", duration=10.0, steps=10)
prj.phases.dynamic("Quake", duration=20.0, steps=2000, wave="record",
                   record=accel, record_dt=0.02, damping=0.05,
                   rayleigh=(1.0, 10.0), compliant_base=True, free_field=True)

# every phase also takes: tolerance=, load_steps=, max_iterations=,
#   ignore_undrained=, reset_small_strain=
# design: "none" | "ec7_da1_c1" | "ec7_da1_c2" | "ec7_da2" | "ec7_da3"
#         | "tbdy2018_static" | "tbdy2018_seismic"

Activation is inherited: a phase lists only what changes. until_degree is a pressure ratio — the largest excess pore pressure left over the largest the stage generated — not the settlement ratio; the two reach 90% at different times.

Running and reading results

job = prj.run(on_phase=lambda i, n, name: print(f"  [{i}/{n}] {name}"))

print(katai.summary(job, prj))        # phases, extremes WITH locations, forces, diagnostics

res = job.results()[-1]               # one result object per phase
res.ok, res.fos, res.max_disp, res.diagnostics
res.node_x, res.displacement, res.reaction      # NumPy arrays

wall_f = next(s for s in res.struct_forces if s.name == "Wall")
wall_f.max_N, wall_f.max_Q, wall_f.max_M, wall_f.yielded
for st in wall_f.stations:            # along the member
    print(st.s, st.x, st.y, st.N, st.Q, st.M)

A summary that says where

“The largest settlement is 43 mm” and “…and it is three metres from the wall” are two different findings. katai.summary() gives both; a field that is uniform says so instead of inventing a location. The block on the right is the output of the published 0.9.0 wheel for the anchored excavation example (some rows omitted, every value verbatim).

A refusal is an exception

When the engine will not solve a model — an input contract is broken, an object would contribute nothing, an analysis is not defined for a material — it raises katai.Refusal, carrying the message verbatim and the stable K2D-* codes. A script branches on the reason instead of parsing prose.

try:
    job = prj.run()
except katai.Refusal as e:
    if any(d.code == "K2D-G003" for d in e.diagnostics):
        ...            # a distributed load was drawn off the soil
    print(e.report)    # field-path issues, when the refusal came from validation
=== Excavate to formation =======================================
  status        converged
  max |u|       4.329029e-02 m
  diagnostics
    NOTE  K2D-A004
          A structural element ends on a supported node. The reported reactions
          are the soil's contribution only; the element's own end force at that
          support is not included in this build.
  extremes                    min          at                 max          at
    |u|       [  m]             0  (0, 16)                 0.04329  (3, 12)
    u_x       [  m]    -0.0067762  (11.3, 5.9)           0.0065545  (1, 11)
    u_y       [  m]   -0.00089196  (30, 18)               0.043216  (3, 12)
    sig'_yy   [kPa]       -376.15  (30, 0)                   5.888  (10, 12)
    tau_max   [kPa]             0  (0, 16)                  120.34  (0, 11)
  structural forces (envelope |max|)
    Anchor row 1    anchor    N   195.4 kN/m
    Diaphragm wall  plate     N   325.7 kN/m   Q   84.65 kN/m   M   58.33 kNm/m

Examples

Four complete programs, each checked by the test suite.

They ship under python/examples/ and run in CTest against their verification bands — an example that stops telling the truth fails the build rather than the first reader who tries it.

ExampleWhat it doesWhat it prints
slope_stability.py Griffiths & Lane (1999) slope by strength reduction FoS 1.010; published ≈ 0.99
consolidation_terzaghi.py Terzaghi column, by time and by target degree |ΔU| ≤ 0.0061 at the sampled times
staged_excavation.py Staged excavation against 1D elastic unloading by hand heave 30.31 mm vs 30.31 mm · +0.00%
anchored_excavation.py Diaphragm wall, interfaces, prestressed anchor wall |M| 58.3 kNm/m; anchor 195.4 kN/m

Requirements

Python
CPython 3.12 or newer — one abi3 wheel serves every later version.
Platform
Windows x64.
Dependencies
NumPy, installed automatically.
Install
pip install katai2d-0.9.0-cp312-abi3-win_amd64.whl, downloaded from the releases page. See install.
Version check
python -c "import katai; print(katai.__version__)" → 0.9.0

Writing code with an AI assistant? /llms.txt carries this API surface in a form written for language models, including which names need which release.

pip install, run an example, compare with the closed form.

KATAI 2D is free under Apache‑2.0, installs in one line and ships the input files of the benchmarks it is verified against. The fastest way to judge it is to rerun one.