OpenGeoRock
5.3

Python API

Describes OGR Slip2D 0.1.235

OGR Slip2D is a Python program, and its calculations can be driven from a script. There are two levels: the engine, whose objects (Project, the searches and the methods) give full control and leave the checking to you, and the operations layer ogr_api, the named, validated and undoable operations that the MCP server publishes as tools. The program's window also has a Python terminal connected to the live project. A short example of each follows, all checked against v0.1.235.

5.3.1Two levels#

Table 5.3.1 The two Python levels of OGR Slip2D.
EngineOperations layer
Packagesogr_core (model, settings, files), ogr_slip2d (methods, searches), ogr_fem2d (seepage)ogr_api
You holdPython objects: a Project, a search, its resulthandles: project_id, job_id, result_id
Validationwhat each object checks; a misspelt setting is accepted in silenceevery argument and setting checked, with the closest valid names
Undonoevery edit is one undo step
Analysesrun in your processbackground jobs in a child process that can be cancelled
Answersfull result objects (every surface, every slice)JSON-safe summaries; full results on request
Use it forresearch, parametric studies, new post-processingscripts that should behave as the program does; what an agent does

Both need the package installed (Section 1.1); neither needs the window. The repository's examples/quick_slope.py uses the engine and examples/api_acads.py the operations layer; from the root of the repository they run as python -m examples.quick_slope and python -m examples.api_acads.

5.3.2The engine#

Projects#

A model is a Project (ogr_core.project). Project.load(path) reads a .ogr file and project.save(path) writes one (Section 5.4); Project(name) starts an empty one, to which boundaries and materials are added with add_boundary and add_material, as examples/quick_slope.py does. ogr_core.project.demo.build_demo_project() returns the demo slope.

The analysis the project describes#

run_analysis(project, method_ids=None, progress_cb=None, *, allow_unconfigured=False), in ogr_slip2d.analysis_runner, is the analysis that Analysis → Compute, the command line and the operations layer run: one search per method, built from the project's settings. It applies the design-standard partial factors to a copy (the project passed in is not modified) and takes into account rapid drawdown, slope limits, surface filters, focus objects, the seismic mode, the convergence settings, the seed and the admissibility checks.

from ogr_core.project import Project
from ogr_slip2d.analysis_runner import run_analysis


def main():
    project = Project.load("slope.ogr")
    outcome = run_analysis(project)        # the methods enabled in the project
    for note in outcome.warnings:
        print("Note:", note)
    for method_id, result in outcome.results.items():
        critical = result.critical
        if critical is None:
            print(f"{method_id}: no valid surface")
        else:
            print(f"{method_id}: {critical.fos:.4f} "
                  f"({result.valid_count} valid of {result.attempts})")


if __name__ == "__main__":
    main()
  • It returns an AnalysisOutcome with results (method id → search result), factor_report (the design-standard report), warnings (the notes the window shows) and project (the factored copy that was analysed). results, report = run_analysis(project) also works.
  • If the project cannot be analysed as configured, it raises AnalysisNotConfigured, a ValueError whose problems lists every reason; allow_unconfigured=True runs anyway, for a caller that has read them.
  • With a design standard active, critical.fos is an over-design factor, which must exceed 1, not a factor of safety.
  • progress_cb(done, total) is called across the whole run, all methods included.

A search built by hand#

The searches (GridSearch, SlopeSearch, AutoRefineSearch, BlockSearch, PathSearch, SimulatedAnnealingSearch; the particle swarm is in ogr_slip2d.particle_swarm) and the nine methods (BishopSimplified, JanbuSimplified, JanbuCorrected, OrdinaryFellenius, Spencer, GLEMorgensternPrice, LoweKarafiath, CorpsOfEngineers1, CorpsOfEngineers2) can be built directly; method_registry() maps each method id to its class.

from ogr_core.project import Project, save_results
from ogr_slip2d import BishopSimplified, GridSearch

project = Project.load("slope.ogr")
search = GridSearch(method=BishopSimplified(), grid_nx=6, grid_ny=6,
                    radius_increment=5, num_slices=25)
result = search.run(project)               # 7 × 7 centres × 6 radii = 294 circles
print(result.critical.fos, result.attempts)
save_results("slope_bishop.h5", result, project_id=project.id)
A hand-built search differs from Compute

An object built by hand uses its own defaults and reads from the project only the model itself. A method takes a tolerance of 0.001, 75 iterations and no Steffensen acceleration (the project defaults are 0.005, 50 and Steffensen on); GridSearch takes 10 × 10 intervals, a radius increment of 10, 30 slices and a minimum area of 0.5 m², and places the grid itself when grid_x and grid_y are not given. The project's slope limits, surface filters, focus objects and seismic Ky mode reach a hand-built search only if they are passed to it, and rapid drawdown and the design standard are applied only by the analysis runner. To get what the window computes, use run_analysis. build_search(project, method_id) returns the search the project configures for one method, rapid drawdown included; the design standard is applied by run_analysis, to a copy of the project.

Table 5.3.2 What a search returns (SearchResult) and what each evaluated surface carries (LEMResult).
AttributeMeaning
result.criticalThe admissible surface with the lowest factor of safety (the lowest critical seismic coefficient in a Ky run), or None when no surface is valid. If every valid surface failed the admissibility checks, the checks are ignored rather than returning nothing, and inadmissible_count says so.
result.evaluationsEvery surface that was sliced and computed, valid or not, in the order generated.
valid_count, invalid_countSurfaces with and without a factor of safety; their sum is total_count.
attempts, focus_rejectedCandidates formed (a Grid Search attempts (nx + 1)(ny + 1)(radius increment + 1)) and candidates removed by focus objects.
top_n(n), min_fosThe n best valid surfaces; the factor of the critical surface.
minima, optimized, objectiveDistinct local minima (multimodal particle swarm); the surface produced by Optimize Surfaces when it improved on the search; "fos" or "ky".
fosFactor of safety of one surface, or None when there is none (never NaN).
converged, iterations, method_idConvergence of the iteration, iterations used and method.
surface, slicesThe slip surface (surface.to_dict() gives its geometry) and its slices.
is_validConverged, with a positive factor and no error.
admissible, admissibility_note, admissibility_reasonVerdict of the post-analysis checks (tensile stress, m-alpha).
error_message, reasonWhy there is no factor, in words and as a code (Section 5.5.2).
detailsExtras such as lambda, ky or newmark_displacement.

One surface#

evaluate_surfaces(project, surface, method_ids=None) computes the factor of safety of one given surface with each method, through the same checks, factored copy and configured search as run_analysis. Its results map each method id to a result, or to None when the surface does not cut the model.

from ogr_core.project import Project
from ogr_slip2d import SlipCircle
from ogr_slip2d.analysis_runner import evaluate_surfaces

project = Project.load("slope.ogr")
circle = SlipCircle(centre_x=37.5, centre_y=31.7, radius=16.5)
outcome = evaluate_surfaces(project, circle, ["bishop_simplified", "spencer"])
for method_id, res in outcome.results.items():
    print(method_id, None if res is None else res.fos)

Results files#

save_results(path, search_result, project_id="") writes one search result to HDF5 and load_summary(path) reads back its metadata and summary arrays, without the slices; both are in ogr_core.project. The layout is in Section 5.4.5.

Scripts and worker processes#

A Grid Search of 400 circles or more is evaluated in a pool of worker processes (at most 8, using by default 50 % of the logical processors; Project Settings → Advanced can switch it off). On Windows each worker imports the script again, so a script that runs a search should keep its top-level code under if __name__ == "__main__":, as the examples on this page and in the repository do. A pool that cannot start does not stop the analysis: the search falls back to one process.

5.3.3The operations layer#

ogr_api holds what the program can do to a model as named operations with typed arguments and JSON-safe answers, without Qt and without the MCP SDK. There are 73 operations: the MCP tools except server_info, under the same names, and every parameter of a tool is a parameter of its operation, so the tool tables of Section 5.1.7 describe them too. ogr_api.OPERATIONS is the registry.

from pathlib import Path

from ogr_api import OgrApiError, Workspace, call


def main():
    ws = Workspace(workdir=Path.cwd())
    try:
        pid = call(ws, "project_open", path="slope.ogr")["project_id"]
        call(ws, "analysis_configure", project_id=pid,
             methods=["bishop_simplified", "spencer"],
             grid={"nx": 6, "ny": 6}, radius_increment=5)
        run = call(ws, "analysis_run", project_id=pid, wait_seconds=300)
        if run["state"] != "done":
            print("still running:", run["job_id"])
            return
        for m in run["summary"]["methods"]:
            print(m["method_id"], m["caption"], m["value"], m["counts"]["valid"])
        try:
            call(ws, "settings_set", project_id=pid,
                 changes={"search.search_methd": "grid"})
        except OgrApiError as exc:
            print(exc.code, exc.hint)     # E_INVALID_ARGUMENT Did you mean 'search_method'?
    finally:
        ws.shutdown()


if __name__ == "__main__":
    main()
  • Workspace(workdir=None, jobs_root=None, max_concurrent_jobs=1, max_results_per_project=5) holds the open models, their undo history, their results and the job manager. workdir anchors relative paths; without it a relative path is an error. It keeps the last five results of each model.
  • call(ws, "operation", **kwargs) runs one operation. The model is named by project_id, which may be omitted while only one model is open.
  • Edits are atomic and each is one undo step (call(ws, "project_history", action="undo")).
  • Analyses (analysis_run, groundwater_run, statistics_run, back_analysis_run, optimize_run, drawdown_sweep_run) run as jobs in a child process, on a detached copy of the model. A call waits up to wait_seconds and otherwise answers state: "running" with a job_id for job_get; job_cancel kills the job's process tree. The answer of a finished run carries result_id, summary and stale (whether the model has changed since). ws.shutdown() kills the jobs still running and removes their files.
  • Errors are OgrApiError subclasses with a stable code, a message, a hint and details (Section 5.5.4). An argument name that the operation does not take is not an OgrApiError: Python raises TypeError.
  • python_exec runs a script with project, api (every operation as api.<name>(...), with project_id filled in), np, ogr_core, ogr_slip2d and result(result_id) (a stored result in full) as one undo step; it returns ok, stdout, stderr, value (the representation of the last expression), error and rolled_back.
In OGR Slip2D

The operations layer exists because the engine's settings objects accept a misspelt setting in silence and an unknown search id becomes a Grid Search; here both are refused, with a suggestion. It never imports Qt or the MCP SDK, which a test checks, so the same registry serves the MCP server, the window's agent bridge and a plain script.

Source: ogr_api/__init__.py · ogr_api/workspace.py · examples/api_acads.py · examples/quick_slope.py

5.3.4The embedded terminal#

Window → Terminal (Ctrl+`) opens a Python console inside the program, connected to the live project. It runs in the program's own process and acts directly on the model on screen.

Table 5.3.3 Names available in the embedded terminal.
NameWhat it is
projectThe current Project; it is re-attached when a project is created or opened.
canvasThe drawing canvas of the main window.
mainwindowThe main window.
npNumPy.
ogr_core, ogr_slip2dThe model and solver packages.
help_ogr()Prints a short list of tips.
>>> print(len(project.materials), project.bounding_box())
>>> from ogr_slip2d.analysis_runner import run_analysis
>>> outcome = run_analysis(project, ["spencer"])
>>> print(outcome.results["spencer"].critical.fos)
  • Enter runs the input, Shift+Enter starts a new line, Ctrl+↑ and Ctrl+↓ walk the history and Ctrl+L clears the console.
  • The value of a bare expression is not echoed: use print(), as above.
  • The console runs on the window's own thread, so a long calculation started there holds the window until it ends.
  • Changes made to project from the console act on the model directly and are not steps of Edit → Undo. For undoable edits from a script, use the operations layer or an agent's python_exec.
In OGR Slip2D

The console executes each input as a block of statements, which is why expressions are not echoed. Its tips (help_ogr()) still show bare expressions such as r.critical.fos, which print nothing as written.

Source: ogr_gui/widgets/terminal.py