AtpClient.Isabelle (AtpClient v0.5.0)

Copy Markdown View Source

Client for Isabelle servers, built on top of IsabelleClient from the :isabelle_elixir package.

The Isabelle server cannot accept theory text over the wire — theories have to live as .thy files on a filesystem that both the BEAM node running AtpClient and the Isabelle server can see. This module handles the file bookkeeping for you: you pass the theory body as a string, and it is written into a configured shared directory before use_theories is invoked.

Configuration

config :atp_client, :isabelle,
  host: "isabelle.example.org",
  port: 9999,
  password: System.get_env("ISABELLE_PASSWORD"),
  session: "HOL"

When the BEAM and the Isabelle server run on the same host (the usual dev setup) no further configuration is required — :local_dir defaults to a subdirectory of System.tmp_dir!/0 and the library writes .thy files there before asking Isabelle to load them. Pass theory text in, get results back; the file bookkeeping is hidden.

Paths: local_dir vs. isabelle_dir

Only relevant when the BEAM node and the Isabelle server see the shared directory under different paths. In that case set both explicitly:

config :atp_client, :isabelle,
  local_dir: "/shared/problems",
  isabelle_dir: "/data/problems"

The two common cases:

  • Containers: BEAM writes to /shared/problems inside its container; the Isabelle server container has the same volume mounted at /data/problems.
  • Windows + Cygwin: BEAM on Windows sees C:/Users/you/thy; Isabelle started from a Cygwin shell sees the same directory as /cygdrive/c/Users/you/thy.

Paths given via isabelle_dir are passed through verbatim — the library does not try to translate Windows paths to POSIX or vice-versa. When unset, :isabelle_dir defaults to :local_dir.

Example

Pass a full theory or just the body — bare text is automatically wrapped in theory <name> imports Main begin ... end:

# Full theory
theory = ~S"""
theory Example imports Main begin
lemma "P \<or> \<not> P" by auto
end
"""

# Or just the body (equivalent)
body = ~S"""
lemma "P \<or> \<not> P" by auto
"""

{:ok, :theorem} = AtpClient.Isabelle.query(theory, "Example", [])
{:ok, :theorem} = AtpClient.Isabelle.query(body, "Example", [])

For fine-grained workflows, open a session once and reuse it:

{:ok, session} = AtpClient.Isabelle.open_session()
{:ok, result1} = AtpClient.Isabelle.prove_theory(session, theory1, "T1")
{:ok, result2} = AtpClient.Isabelle.prove_theory(session, theory2, "T2")
:ok = AtpClient.Isabelle.close_session(session)

Submitting TPTP/THF problems

query_tptp/2 and prove_tptp/3 accept a TPTP/THF problem string and route it through IsabelleClient.TPTP.isabellize_theory/1 before handing it to the per-lemma path. The bundled TPTP.thy support theory is copied into :local_dir on first use, and the generated theory imports it with unbundle from_TPTP active:

problem = ~S"""
thf(p_type, type, p: $i > $o).
thf(g, conjecture, ! [X: $i]: (p @ X | ~ (p @ X))).
"""

{:ok, lemma_results} = AtpClient.Isabelle.query_tptp(problem)

Lemma names propagate from TPTP formula names. Line numbers are not exposed — they would refer to the generated theory, not the input TPTP source. Match on :name for UI attribution.

Cancellation

Death of the process calling query/2, query_tptp/2, or any of the session helpers is the abort signal. The session is owned by an internal GenServer that monitors the caller; on caller :DOWN it stops the underlying IsabelleClient.Shared, which closes the TCP socket to the Isabelle server. Closing the socket while a use_theories task is in flight aborts that task on the server side — per the Isabelle protocol, the running check is dropped along with the session.

IsabelleClient does not currently expose a way to cancel a single in-flight task while keeping the session open, so cancelled calls pay the session-start cost again on the next request (typically a few seconds for HOL, longer for larger sessions). Callers that need to cancel cheaply should keep the volume of cancelled work small or open multiple sessions in parallel.

Summary

Types

The result of a call to prove_theory/4 or query/3

Functions

Stops the active Isabelle session and shuts down the underlying IsabelleClient.Shared GenServer, closing its TCP socket.

Extracts the lemma specs (name + body-line range) from a theory body.

Connects to the Isabelle server, starts a session (typically HOL or Main), and returns a Session handle.

Like prove_theory/4, but returns one result per lemma instead of a single consolidated result.

Writes theory_text to <local_dir>/<theory_name>.thy, asks the Isabelle server to process it in the given session, and blocks until the task finishes or fails.

Like prove_lemmas/4, but takes a TPTP/THF problem string instead of an Isabelle theory.

Behaviour entry point: proves problem (a TPTP-format string) and collapses the per-lemma results from query_tptp/2 into a single AtpClient.ResultNormalization.atp_result/0.

Convenience wrapper: opens a session, calls prove_theory/4, and unconditionally closes the session afterwards (even on error).

Convenience wrapper: opens a session, calls prove_lemmas/4, and unconditionally closes the session afterwards (even on error).

Convenience wrapper: opens a session, calls prove_tptp/3, and unconditionally closes the session afterwards (even on error).

Types

result()

@type result() ::
  {:ok, AtpClient.ResultNormalization.atp_result()}
  | {:ok, map()}
  | {:error, term()}

The result of a call to prove_theory/4 or query/3:

  • {:ok, result} where result is either the normalized ATP result or the raw use_theories payload (a map with "nodes", "errors", "ok", …), depending on the :raw option;
  • {:error, {:isabelle_failed, payload, notes}} when the server reports a FAILED message for the task (e.g. a theory file that Isabelle could not load). notes is the list of intermediate NOTE payloads accumulated by IsabelleClient.Task before the failure;
  • {:error, {:isabelle_failed, payload, hints}} — same shape, but with hints carrying diagnostic suggestions and the local_dir / isabelle_dir actually used. Currently emitted when the server reports "Cannot load theory file";
  • other {:error, reason} for connection, session-start, and I/O issues.

Functions

close_session(session)

@spec close_session(AtpClient.Isabelle.Session.t()) :: :ok

Stops the active Isabelle session and shuts down the underlying IsabelleClient.Shared GenServer, closing its TCP socket.

lemma_specs(theory_text)

Extracts the lemma specs (name + body-line range) from a theory body.

Walks the body line-by-line, picking out lemma <name>: or lemma <name>[…]: declarations and computing each lemma's range as [its line .. (line before the next lemma)]. Used by prove_lemmas/4 and prove_tptp/3 to feed ResultNormalization.per_lemma_results/3 without having to re-parse the body inside the classifier.

open_session(opts \\ [])

@spec open_session(keyword()) ::
  {:ok, AtpClient.Isabelle.Session.t()} | {:error, term()}

Connects to the Isabelle server, starts a session (typically HOL or Main), and returns a Session handle.

The handle is backed by a private owner process that holds the link to IsabelleClient.Shared for you, so callers do not need to trap exits: a failed connection surfaces as {:error, reason} and a later crash of the Shared process surfaces as a :DOWN if the caller monitors session.client. The owner is also caller-monitored, so if the caller dies the session is shut down cleanly instead of orphaning a remote Isabelle session.

The caller is still responsible for eventually passing the handle to close_session/1 under normal control flow.

Options

  • :host, :port, :password — override config;
  • :session — name of the Isabelle session to start (default from config, typically "HOL");
  • :session_start_timeout_ms — how long to wait for the initial session_start to complete (default 120_000).

prove_lemmas(session, theory_text, theory_name, opts \\ [])

@spec prove_lemmas(AtpClient.Isabelle.Session.t(), String.t(), String.t(), keyword()) ::
  {:ok, [AtpClient.ResultNormalization.lemma_result()]} | {:error, term()}

Like prove_theory/4, but returns one result per lemma instead of a single consolidated result.

Calls prove_theory/4 internally with raw: true and parses the payload via AtpClient.ResultNormalization.per_lemma_results/3. Returns {:ok, [lemma_result]} for a finished task, where entries follow the order of the lemma <name>: declarations in the input body:

%{
  name:   String.t(),                # lemma name from the body
  result: {:ok, :theorem} | {:ok, :counter_satisfiable} | {:ok, :satisfiable}
        | {:ok, :gave_up} | {:ok, :timeout} | {:ok, :memory_out}
}

Unnamed lemma "…" by … declarations are not represented in the result — the classifier buckets messages by lemma name extracted from the body.

Returns {:error, term()} for connection failures and for tasks that fail before producing any messages (e.g. unreadable theory files).

Options

Same as prove_theory/4 except :raw, which is always overridden.

prove_theory(session, theory_text, theory_name, opts \\ [])

@spec prove_theory(AtpClient.Isabelle.Session.t(), String.t(), String.t(), keyword()) ::
  result()

Writes theory_text to <local_dir>/<theory_name>.thy, asks the Isabelle server to process it in the given session, and blocks until the task finishes or fails.

If theory_text does not begin with theory, it is automatically wrapped:

theory <theory_name> imports <imports> begin
<theory_text>
end

By default the result is interpreted by AtpClient.ResultNormalization.interpret_isabelle_result/1. Pass raw: true to get back the full use_theories payload (a map with "nodes", "ok", "errors", …) returned by the Isabelle server.

Options

  • :raw — return the raw payload map (default false);
  • :imports — session/theory to import when auto-wrapping (default "Main");
  • :use_theories_timeout_ms — overall deadline for the task (default from config, 120_000).

prove_tptp(session, problem, opts \\ [])

@spec prove_tptp(AtpClient.Isabelle.Session.t(), String.t(), keyword()) ::
  {:ok, [AtpClient.ResultNormalization.lemma_result()]} | {:error, term()}

Like prove_lemmas/4, but takes a TPTP/THF problem string instead of an Isabelle theory.

The problem is converted to an Isabelle theory body via IsabelleClient.TPTP.isabellize_theory/1, which maps thf(name, axiom, …) to axiomatization, thf(name, type, …) to consts/typedecl/ type_synonym, and thf(name, theorem|conjecture, …) to lemma. The generated body is preceded by unbundle from_TPTP so that the TPTP/THF notation parses inside Isabelle, then handed to prove_lemmas/4 with imports: "TPTP".

The bundled TPTP.thy support theory is copied into the configured :local_dir on first use so Isabelle's loader can resolve imports "TPTP" from the generated theory file. Existing files are not overwritten.

Result attribution

Entries in the returned lemma_result() list follow the order of the thf(…, conjecture, …) declarations in the input problem and carry the TPTP formula name as :name. Line numbers are intentionally not exposed: they would refer to the generated theory body rather than the caller's TPTP source.

Options

Same as prove_lemmas/4, plus:

  • :theory_name — name to use for the generated Isabelle theory (default: a content-derived AtpClient_<hash> string). Useful for stable filenames across runs.
  • :proof_method — Isabelle tactic appended to each generated lemma (default "by auto"). The isabellizer emits lemma declarations without proofs, which Isabelle treats as unfinished goals; a tactic must be supplied for the goal to close (or be withdrawn). Pass "by metis" for first-order portfolio reasoning, or "sledgehammer nitpick oops" to probe both proof and countersatisfiability; the latter relies on the per-lemma classifier recognising sledgehammer's "found a proof" and nitpick's verdicts directly, so oops is fine — {:ok, :theorem} / {:ok, :counter_satisfiable} / {:ok, :satisfiable} come from the tool messages, not from a theorem name: completion.

Errors

  • {:error, {:tptp_thy_copy_failed, reason}} — could not place TPTP.thy next to the generated theory in :local_dir;
  • {:error, {:tptp_parse, message}}isabellize_theory/1 raised (the parser is tolerant by design, so this should be rare);
  • everything prove_lemmas/4 can return.

query(problem, opts \\ [])

@spec query(
  String.t(),
  keyword()
) :: AtpClient.ResultNormalization.atp_result() | {:error, term()}

Behaviour entry point: proves problem (a TPTP-format string) and collapses the per-lemma results from query_tptp/2 into a single AtpClient.ResultNormalization.atp_result/0.

Aggregation is weakest-link: any non-{:ok, :theorem} entry wins, so the unified result only reads {:ok, :theorem} when every isabellized lemma was discharged. Callers that need per-lemma detail should use query_tptp/2 directly.

query(theory_text, theory_name, opts)

@spec query(String.t(), String.t(), keyword()) :: result()

Convenience wrapper: opens a session, calls prove_theory/4, and unconditionally closes the session afterwards (even on error).

opts must be passed explicitly (use [] for none) — a default value would shadow query/2 from AtpClient.Backend.

query_lemmas(theory_text, theory_name, opts \\ [])

@spec query_lemmas(String.t(), String.t(), keyword()) ::
  {:ok, [AtpClient.ResultNormalization.lemma_result()]} | {:error, term()}

Convenience wrapper: opens a session, calls prove_lemmas/4, and unconditionally closes the session afterwards (even on error).

query_tptp(problem, opts \\ [])

@spec query_tptp(
  String.t(),
  keyword()
) :: {:ok, [AtpClient.ResultNormalization.lemma_result()]} | {:error, term()}

Convenience wrapper: opens a session, calls prove_tptp/3, and unconditionally closes the session afterwards (even on error).