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.
Zero-config: isabelle on $PATH
If the isabelle executable is discoverable (either on $PATH or via the
ISABELLE_TOOL environment variable), the bundled :isabelle_elixir
package can spin up a local resident server on demand. This removes any
need to configure host/port/password by hand:
# Start a throwaway server (uses `isabelle` from $PATH).
{:ok, server} = IsabelleClient.start_server()
opts = [host: server.host, port: server.port, password: server.password]
{:ok, session} = AtpClient.Isabelle.open_session(opts)
{:ok, %AtpClient.Result{status: :theorem}} =
AtpClient.Isabelle.prove_theory(session, ~S"""
lemma "P \<or> \<not> P" by auto
""", "Example")
:ok = AtpClient.Isabelle.close_session(session)
# When you're done, stop the server:
IsabelleClient.Server.kill(server.name)A minimal config/config.exs reduces the per-call opts to nothing —
after the one-time start_server/0 call, put the returned coordinates
into the application env once and forget about them.
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/problemsinside 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.
Results
Every proving call returns {:ok, %AtpClient.Result{}} or {:error, reason}.
Isabelle prints no SZS lines; the status is read from the server's messages by
AtpClient.Isabelle.Verdict, and the result's evidence is
{:backend, AtpClient.Isabelle}. output is the text of those messages, and
details carries what the call knows beyond the status:
prove_theory/4—%{payload: map()}, theuse_theoriespayload as the server returned it.prove_lemmas/4,query_lemmas/3,prove_tptp/3andquery/2—%{lemmas: [lemma_result], payload: map()}, one entry per lemma in the order of thelemmadeclarations, each%{name: name, status: status}. The result's ownstatusis the weakest link::theoremonly when every lemma was discharged, otherwise the first other status in lemma order. A body with no lemmas is:gave_up.
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, session} = AtpClient.Isabelle.open_session()
{:ok, %{status: :theorem}} = AtpClient.Isabelle.prove_theory(session, theory, "Example")
{:ok, %{status: :theorem}} = AtpClient.Isabelle.prove_theory(session, body, "Example")
:ok = AtpClient.Isabelle.close_session(session)Submitting TPTP/THF problems
query/2 and prove_tptp/3 take an AtpClient.Problem 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, result} = AtpClient.Isabelle.query(problem)
result.status # :theorem
result.details.lemmas # [%{name: "g", status: :theorem}]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 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
Functions
Stops the active Isabelle session and shuts down the underlying
IsabelleClient.Shared GenServer, closing its TCP socket.
Connects to the Isabelle server, starts a session (typically HOL or Main),
and returns a Session handle.
Like prove_theory/4, but reads one status per lemma.
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 an AtpClient.Problem in TPTP/THF instead
of an Isabelle theory.
Behaviour entry point: opens a session, runs prove_tptp/3, and 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).
Types
@type result() :: {:ok, AtpClient.Result.t()} | {:error, term()}
What a proving call returns.
{:ok, %AtpClient.Result{}}— the task finished; see "Results" in the module documentation for what the result carries.{:error, {:isabelle_failed, payload, notes}}— the server reported aFAILEDmessage for the task, such as a theory file it could not load.notesis the list of intermediateNOTEpayloadsIsabelleClient.Taskaccumulated before the failure. When the message is "Cannot load theory file",notesis replaced by hints carrying thelocal_dirandisabelle_diractually used.- other
{:error, reason}for connection, session-start, and I/O issues.
Functions
@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.
@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 AtpClient.Isabelle.Session.client/1. 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");:dirs— list of directories containingROOTfiles, forwarded asdirstosession_startso that:sessionmay name a session defined outside the Isabelle distribution. Paths are as seen by the Isabelle server and are passed through verbatim (default from config, else none);:session_start_timeout_ms— how long to wait for the initialsession_startto complete (default120_000).
@spec prove_lemmas(AtpClient.Isabelle.Session.t(), String.t(), String.t(), keyword()) :: result()
Like prove_theory/4, but reads one status per lemma.
The result's details.lemmas holds one entry per lemma <name>: declaration
in the body, in that order, each %{name: name, status: status}; the result's
own status is the weakest link over them. Unnamed lemma "…" by …
declarations are not represented — the classifier buckets messages by the
lemma names it finds in the body.
Options
Same as prove_theory/4.
@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>
endThe status is read over every message of the task by
AtpClient.Isabelle.Verdict.classify/1; the use_theories payload is in
result.details.payload.
Options
:imports— session/theory to import when auto-wrapping (default"Main");:use_theories_timeout_ms— overall deadline for the task (default from config,120_000).
Examples
{:ok, %AtpClient.Result{status: :theorem, details: %{payload: %{"ok" => true}}}} =
AtpClient.Isabelle.prove_theory(session, "lemma p: True by auto\n", "P")
@spec prove_tptp(AtpClient.Isabelle.Session.t(), AtpClient.Problem.t(), keyword()) :: result()
Like prove_lemmas/4, but takes an AtpClient.Problem in TPTP/THF 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
result.details.lemmas follows the order of the thf(…, conjecture, …)
declarations in the input problem and carries 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-derivedAtpClient_<hash>string). Useful for stable filenames across runs.:proof_method— Isabelle tactic appended to each generatedlemma(default"by auto"). The isabellizer emitslemmadeclarations 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, sooopsis fine —:theorem/:counter_satisfiable/:satisfiablecome from the tool messages, not from atheorem name:completion.
Errors
{:error, {:tptp_thy_copy_failed, reason}}— could not placeTPTP.thynext to the generated theory in:local_dir;{:error, {:tptp_parse, message}}—isabellize_theory/1raised (the parser is tolerant by design, so this should be rare);- everything
prove_lemmas/4can return.
@spec query(AtpClient.Problem.t(), keyword()) :: result()
Behaviour entry point: opens a session, runs prove_tptp/3, and closes the
session afterwards, even on error.
The result's status is the weakest link over the isabellized lemmas and
details.lemmas has each lemma's own status.
THF-only
IsabelleClient.TPTP.isabellize_theory/1 in the currently vendored
:isabelle_elixir (0.4.1) only recognises thf(…) annotated formulae.
FOF/CNF input is silently dropped by the isabellizer, so the generated
body has no lemmas and the result is :gave_up — indistinguishable from
an honest failed proof. If you need FOF/CNF today, route through the
SystemOnTPTP, StarExec, or LocalExec backends instead.
Convenience wrapper: opens a session, calls prove_lemmas/4, and
unconditionally closes the session afterwards (even on error).