<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Imandra Inc. - Medium]]></title>
        <description><![CDATA[Reasoning as a Service @ www.imandra.ai - Medium]]></description>
        <link>https://medium.com/imandra?source=rss----aa39f4f76a9f---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Imandra Inc. - Medium</title>
            <link>https://medium.com/imandra?source=rss----aa39f4f76a9f---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Apr 2026 19:31:26 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/imandra" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Managing Complexity with Math and Logic: Changing Stripe Payment Flow with Claude and CodeLogician]]></title>
            <link>https://medium.com/imandra/managing-complexity-with-math-and-logic-changing-stripe-payment-flow-with-claude-and-codelogician-cdd386001e40?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/cdd386001e40</guid>
            <category><![CDATA[llm-reasoning]]></category>
            <category><![CDATA[vibe-coding]]></category>
            <category><![CDATA[imandra]]></category>
            <category><![CDATA[reasoning]]></category>
            <category><![CDATA[claude-code]]></category>
            <dc:creator><![CDATA[Denis Ignatovich]]></dc:creator>
            <pubDate>Fri, 13 Mar 2026 20:50:37 GMT</pubDate>
            <atom:updated>2026-03-26T20:33:33.733Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AB-USO2Tas6Kbo0kAJDkgw.png" /></figure><p><em>There’s a growing belief that AI coding agents can handle complex business logic — payment flows, approval workflows, compliance rules. And they can, sort of. They produce code that *</em><strong><em>looks</em></strong><em>* correct, *</em><strong><em>reads</em></strong><em>* correctly, and passes the tests they write for themselves.</em></p><p><em>But what happens when the AI is both the author and the reviewer? It misses the same edge cases in both roles. I recently ran an experiment that made this concrete: I used Claude (via Claude Code) to formalize a Stripe payment flow, add new features, and verify correctness. Claude wrote the IML (Imandra Modeling Language) models, wrote the verification goals, </em><strong><em>and</em></strong><em> called the CodeLogician CLI itself — invoking `codelogician eval check`, `check-vg`, `check-decomp`, and `gen-test` as part of its own reasoning loop. When a verification goal was refuted, Claude read the counterexample, diagnosed the issue, and fixed it — all within the same conversation.</em></p><p><em>The difference between Claude working alone and Claude working with </em><a href="https://codelogician.dev"><em>CodeLogician</em></a><em>/</em><a href="https://www.imandrax.dev"><em>ImandraX</em></a><em> was not subtle. This post walks through exactly what happened. Here’s the </em><a href="https://github.com/imandra-ai/codelogician-demos"><em>repo with complete code</em></a><em>.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rlLdEYMLOE-1gcEqfzwQZA.png" /></figure><h4>The Process</h4><p>Before diving into the details, here’s the overall workflow. The key insight is that this isn’t a linear pipeline — it’s an iterative loop where the formal verifier acts as an adversary to Claude, catching mistakes it can’t find on its own:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ih-s6JluIeNSRL-qlPScOg.png" /><figcaption>Claude + CodeLogician/ImandraX process</figcaption></figure><p>The refinement loop in the center is where the real value lives. Crucially, Claude drives this entire loop — it invokes the CodeLogician CLI commands, reads the output, and acts on the results. Each time the verifier refutes a goal, it returns a <strong><em>concrete counterexample </em></strong>— not a vague warning, but an exact input that breaks the claim. Claude then decides: is this a bug in the model, or a flaw in the property? Either way, the fix is precise.</p><h4>The Starting Point: A Production Stripe Payment Flow</h4><p>The system under test is a real payment processing flow built on the Stripe API: create order, attach payment method, create and confirm a PaymentIntent with manual capture, approve, capture, refund, cancel, and handle disputes via webhooks. It’s a Flask application with ~700 lines of Python — the kind of code Claude generates confidently and well.</p><p>The goal: formalize this as a pure state machine, add two new features (high-risk approval policy and 3DS/SCA challenge flow), verify the result is correct, and generate exhaustive tests.</p><p>As <a href="https://codelogician.dev/docs/getting-started/tackle-complexity-with-math-and-logic/">CodeLogician’s documentation puts it</a>: there are two categories of programming. The first — UI rendering, CRUD endpoints, data serialization — is where AI coding agents excel. The second — financial transactions, approval workflows, risk assessment — is where “complexity grows combinatorially rather than linearly.” This payment flow is squarely in the second category.</p><h4>Step 1: Formalize in IML</h4><p>I had Claude translate the Python payment flow into IML (Imandra Modeling Language), a pure functional language with formal semantics used by CodeLogician’s automated reasoning engine. The result is a state machine with 10 order statuses, 8 actions, and 7 transition functions:</p><pre>type order = {<br>  amount                : int;<br>  amount_captured       : int;<br>  amount_refunded       : int;<br>  status                : order_status;<br>  requires_review       : bool;<br>  approved_for_capture  : bool;<br>  payment_intent_exists : bool;<br>  latest_charge_exists  : bool;<br>}</pre><p>Each transition function is pure — takes an order, returns a new order:</p><pre>let capture_payment (amt: int) (o: order) : order =<br>  if may_capture o &amp;&amp; 0 &lt; amt &amp;&amp; amt &lt;= o.amount then<br>    { o with<br>      amount_captured = amt;<br>      status = Captured<br>    }<br>  else o</pre><p>This was admitted cleanly — ImandraX accepted all definitions. Claude was confident. I was confident. We were both wrong.</p><h4>Step 2: The Verifier Finds What Claude Can’t</h4><p>Claude then verification goals — properties that should hold for <em>*every possible input*</em> — and ran them through ImandraX using <em>`codelogician eval check-vg`</em>.</p><p>The first property: every transition should preserve the amount invariant (<em>`0 &lt;= amount_refunded &lt;= amount_captured &lt;= amount`</em>).</p><p><strong>Result: REFUTED.</strong> <strong><em>ImandraX returned this counterexample for `capture_payment`</em></strong>:</p><pre>amt = 1<br>o = {amount = 8957; amount_captured = 8957; amount_refunded = 1238;<br>     status = ApprovedForCapture; ...}</pre><p>The bug: <em>`capture_payment`</em> sets <em>`amount_captured = amt`</em> but <em>*doesn’t reset*</em> <em>`amount_refunded`</em>. After capture, the order has <em>`amount_captured = 1`</em> but <em>`amount_refunded = 1238`</em> — violating <em>`amount_refunded &lt;= amount_captured`</em>.</p><p>This isn’t an exotic edge case. It’s a straightforward bug that occurs whenever an order is re-captured after a partial refund. But Claude didn’t think about re-capture sequences when writing the code, and it wouldn’t think about them when writing tests either.</p><p>The fix is one line:</p><pre>{ o with<br>  amount_captured = amt;<br>  amount_refunded = 0;    (* &lt;-- this was missing *)<br>  status = Captured<br>}</pre><h4>Step 3: The Verifier Finds What Claude <em>*Thinks*</em> It Knows</h4><p>Second class of failures was more subtle. Claude wrote this goal:</p><pre>let goal_no_capture_without_approval (amt: int) (o: order) =<br>  not o.approved_for_capture ==&gt;<br>  (capture_payment amt o).status &lt;&gt; Captured</pre><p>The order is <em>*already*</em> Captured. <em>`capture_payment`</em> returns it unchanged. The output status is Captured — not because capture succeeded, but because it was already there. The property was logically wrong. Claude confused “capture shouldn’t <em>*change*</em> the status to Captured” with “the output shouldn’t <em>*be*</em> Captured.”</p><p>The corrected goal:</p><pre>let goal_no_capture_without_approval (amt: int) (o: order) =<br>  inv_amounts o &amp;&amp;<br>  not o.approved_for_capture &amp;&amp;<br>  o.status &lt;&gt; Captured ==&gt;<br>  (capture_payment amt o).status &lt;&gt; Captured</pre><p>This is exactly what CodeLogician’s documentation describes: “LLMs explore likely paths. Logic explores all reachable paths.” Claude stated what it <em>*meant*</em> informally, but the formal statement didn’t match. The verifier showed exactly where the gap was.</p><h4>Step 4: Adding New Features with Confidence</h4><p>With the base model verified, Claude added two new features to the IML model:</p><ul><li><strong><em>High-risk flag</em></strong>: orders marked <em>`high_risk`</em> require 2 approvals before capture (replacing the simple boolean <em>`approved_for_capture`</em> with an integer <em>`approval_count`</em>)</li><li><strong><em>3DS/SCA challenge</em></strong>: orders with <em>`three_ds_required`</em> must complete a 3DS authentication step before authorization</li></ul><p>The <em>`may_capture`</em> predicate grew from a simple boolean check to a multi-condition gate:</p><pre>let may_capture (o: order) =<br>  (o.status = Authorized || o.status = ApprovedForCapture) &amp;&amp;<br>  o.latest_charge_exists &amp;&amp;<br>  ((not o.three_ds_required) || o.three_ds_completed) &amp;&amp;<br>  o.approval_count &gt;= required_approvals o</pre><p>Claude wrote 5 new verification goals for the new features:</p><pre>(* 3DS blocks authorization until completed *)<br>let goal_3ds_blocks_authorization (o: order) =<br>  o.status = PaymentIntentCreated &amp;&amp;<br>  o.payment_intent_exists &amp;&amp;<br>  o.three_ds_required &amp;&amp;<br>  not o.three_ds_completed ==&gt;<br>  (confirm_payment_intent o).status = RequiresAction<br><br>(* High-risk needs 2 approvals *)<br>let goal_high_risk_needs_two_approvals (amt: int) (o: order) =<br>  inv_amounts o &amp;&amp;<br>  o.high_risk &amp;&amp;<br>  o.approval_count &lt; 2 &amp;&amp;<br>  o.status &lt;&gt; Captured ==&gt;<br>  (capture_payment amt o).status &lt;&gt; Captured<br><br>(* Combined: high-risk + 3DS requires BOTH conditions *)<br>let goal_high_risk_3ds_requires_both (amt: int) (o: order) =<br>  inv_amounts o &amp;&amp;<br>  o.high_risk &amp;&amp;<br>  o.three_ds_required &amp;&amp;<br>  (not o.three_ds_completed || o.approval_count &lt; 2) &amp;&amp;<br>  o.status &lt;&gt; Captured ==&gt;<br>  (capture_payment amt o).status &lt;&gt; Captured</pre><p>Note that these goals only proved on the first attempt <em>*because*</em> the refinement loop in Steps 2–3 had already taught us how to write precise preconditions. Without that loop, Claude would have written the same class of flawed properties again.</p><p><strong>All 14 verification goals proved</strong> — 7 amount preservation properties, 2 base safety goals, and 5 new feature goals. Every property holds for every possible input, not just the inputs Claude happened to think of.</p><h4>Step 5: 84 Regions, 84 Tests</h4><p>Here’s where CodeLogician’s region decomposition delivers something Claude simply cannot produce on its own.</p><p>Claude annotated the <em>`step`</em> function with <em>`[@@decomp top ()]`</em> and ran:</p><pre>codelogician eval check-decomp --check-all stripe_flow_updated.iml</pre><p>ImandraX partitioned the entire input space of <em>`step`</em> into <strong>84 disjoint regions</strong> — 84 distinct execution paths, each with unique constraints and a simplified output expression.</p><p><em>`capture_payment`</em> alone produced <strong>**53 regions**</strong>. Each represents a unique combination of:</p><ul><li><em>`high_risk`</em> (true/false)</li><li><em>`requires_review`</em> (true/false)</li><li><em>`three_ds_required`</em> (true/false)</li><li><em>`three_ds_completed`</em> (true/false)</li><li><em>`approval_count`</em> threshold (0, 1, or 2)</li><li>Amount validity (amt &lt;= 0, amt &gt; amount, valid)</li><li>Status (Authorized vs ApprovedForCapture vs other)</li></ul><p>If Claude were to enumerate all capture scenarios, it would have produced maybe 8–10. The decomposition found 53 — and proved they are exhaustive and disjoint.</p><p>Claude then generated concrete Python test cases:</p><pre>codelogician eval gen-test stripe_flow_updated.iml -f step -l python</pre><p>This produced 84 test functions, each with a concrete input satisfying that region’s constraints and the expected output. For example, region 67 — high-risk capture with 2 approvals, no 3DS, valid amount:</p><pre>def test_67():<br>    &quot;&quot;&quot;High-risk capture succeeds with 2 approvals, no 3DS&quot;&quot;&quot;<br>    result = step(<br>        Action(ActionKind.CAPTURE, 39),<br>        Order(<br>            amount=39, amount_captured=1, amount_refunded=3,<br>            status=OrderStatus.AUTHORIZED,<br>            requires_review=False, high_risk=True,<br>            three_ds_required=False, three_ds_completed=False,<br>            approval_count=2,<br>            payment_intent_exists=False, latest_charge_exists=True,<br>        )<br>    )<br>    expected = Order(<br>        amount=39, amount_captured=39, amount_refunded=0,<br>        status=OrderStatus.CAPTURED,<br>        requires_review=False, high_risk=True,<br>        three_ds_required=False, three_ds_completed=False,<br>        approval_count=2,<br>        payment_intent_exists=False, latest_charge_exists=True,<br>    )<br>    assert result == expected</pre><p>After adapting the generated tests to import from the actual Python module: <strong>84/84 passed.</strong> The Python implementation matches the verified IML model across every execution path.</p><h4>What This Demonstrates</h4><p>The article <a href="https://codelogician.dev/docs/getting-started/tackle-complexity-with-math-and-logic/"><em>“Tackle Complexity with Math and Logic”</em></a><em> </em>frames this clearly: when software encodes decisions — rules, states, money, risk — complexity grows combinatorially, and statistical reasoning systematically misses the gaps that logical reasoning finds.</p><p>In this experiment:</p><p><strong>Claude wrote a real bug</strong> in <em>`capture_payment`</em> that it would never have caught through self-review or self-generated tests. The bug requires reasoning about field interactions across a specific multi-step sequence (capture, refund, re-capture) — exactly the kind of combinatorial path that AI coding agents don’t explore.</p><p><strong>Claude wrote logically flawed verification goals</strong> with full confidence. It confused informal intent with formal meaning. The verifier’s counterexamples were immediate and unambiguous — not “this might be wrong” but “here is the exact input that breaks your claim.”</p><p><strong>Claude would have written ~10 tests</strong>. The formal decomposition found 84 distinct execution regions. Not 84 random inputs — 84 mathematically guaranteed partitions covering every path through the state machine. The difference between “I tested the cases I thought of” and “I tested every case that exists.”</p><h4>The Workflow, Revisited</h4><p>Referring back to the process diagram at the top, the practical steps are:</p><ol><li><strong>Claude writes the IML model</strong> — translating Python logic to pure functional form</li><li><strong>Claude runs </strong><em>`codelogician eval check`</em>— validates the model is admitted by ImandraX</li><li><strong>Claude writes verification goals</strong> — it knows what <em>*should*</em> hold, even if it states it imprecisely</li><li><strong>Claude runs </strong><em>`codelogician eval check-vg`</em> — ImandraX proves or refutes each goal with counterexamples</li><li><strong>Claude fixes based on counterexamples</strong>— the refinement loop (Steps 3–5 repeat until all goals prove)</li><li><strong>Claude runs </strong><em>`codelogician eval check-decomp`</em>— ImandraX enumerates all execution regions</li><li><strong>Claude runs </strong><em>`codelogician eval gen-test`</em>— ImandraX generates concrete test cases from regions</li><li><strong>Claude adapts tests to production code</strong> — mechanical translation</li></ol><p>Claude orchestrates the entire pipeline. CodeLogician provides the formal reasoning that Claude cannot do on its own.</p><p>Claude is the author. ImandraX is the auditor. Neither works as well alone. The refinement loop — where the verifier returns concrete counterexamples and Claude decides whether to fix the model or fix the property — is where the real value is created. Each iteration produces either a bug fix or a more precise specification. Both are valuable.</p><h4>Conclusion</h4><p>AI-generated code that passes AI-generated tests is the model agreeing with itself. For business logic with real consequences — payment flows, approval policies, compliance rules — that’s not enough.</p><p><a href="https://www.codelogician.dev">CodeLogician</a>/<a href="https://www.imandrax.dev">ImandraX</a> doesn’t replace Claude. It breaks the self-validation loop by providing something an AI coding agent fundamentally cannot: exhaustive logical reasoning over every possible state. The result isn’t “code that looks correct” — it’s code with a proof.</p><p>The full source code (Python implementation, IML models, and generated tests) is available at <a href="https://github.com/imandra-ai/codelogician-demos">https://github.com/imandra-ai/codelogician-demos</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cdd386001e40" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/managing-complexity-with-math-and-logic-changing-stripe-payment-flow-with-claude-and-codelogician-cdd386001e40">Managing Complexity with Math and Logic: Changing Stripe Payment Flow with Claude and CodeLogician</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vibe Coding was phase 1. Logic-first AI is phase 2. It is here now.]]></title>
            <link>https://medium.com/imandra/vibe-coding-was-phase-1-logic-first-ai-is-phase-2-it-is-here-now-7523beff4188?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/7523beff4188</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[vibe-coding]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[llm-reasoning]]></category>
            <dc:creator><![CDATA[Denis Ignatovich]]></dc:creator>
            <pubDate>Sun, 15 Feb 2026 18:36:16 GMT</pubDate>
            <atom:updated>2026-02-15T18:36:15.808Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Imandra CodeLogician (www.codelogician.dev) is an end-user tool that makes AI coding agents reason explicitly instead of guessing. It forces the LLM to structure its understanding of your code into logic, invokes a dedicated reasoning engine to explore the consequences, and returns concrete artifacts — edge cases, behavioral boundaries, and impact analyses — alongside every decision-critical result.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gQRkxEEK832jKNvZ4veTMg.png" /><figcaption>Claude Code working with CodeLogician (Full demo below)</figcaption></figure><h3><strong>AI Can Generate Code. You Still Have to Trust It.</strong></h3><p>Vibe coding is incredible right up to the moment you have to trust it. The agent generates a clean refactor, a new feature, or a “simple” change to some business logic — and now you’re rereading everything, mentally simulating edge cases, and writing tests just to figure out what it actually did. We didn’t remove the cost of being correct; we just moved it onto the human. CodeLogician flips that workflow. It forces the coding agent to model what the code does, runs automated reasoning on the decision-critical parts, and gives you artifacts that expose real behavior — edge cases, boundaries, and impact — before you merge. This is the shift from AI that writes code to AI that has to show its work.</p><h3>From Token-Space “Reasoning” to State-Space Exploration</h3><p><a href="https://www.codelogician.dev"><strong><em>CodeLogician</em></strong></a> is the developer-facing workflow, but the actual reasoning is performed by <a href="https://www.imandra.ai/core"><strong><em>ImandraX</em></strong></a>. When decision-critical logic appears, the LLM (or <strong><em>CodeLogician</em></strong> on its behalf) translates that logic into a structured model and sends it to the reasoning engine. From there the process is no longer “generate and hope.” <strong><em>ImandraX</em></strong> systematically explores the behavior of the system: it searches for boundary conditions, finds counterexamples to incorrect assumptions, partitions the input space into distinct behavioral regions, and proves when certain bad outcomes are impossible. The output is not more explanation — it’s concrete evidence about what the code can and cannot do.</p><p>This is fundamentally different from the kind of reasoning LLMs perform. When an LLM “reasons,” it produces a convincing chain of text by sampling from patterns in its training data. That statistical process is extremely good at generating typical cases and plausible logic, but it only sees what is likely.<strong><em> The bugs that ship to production live in what is possible but unlikely.</em></strong> An automated reasoning engine doesn’t sample; it constrains the system with logic and explores the full space of behaviors defined by that logic. It answers questions like: <em>Is there an input that breaks this invariant? Under exactly what conditions does this state transition occur? Which combinations of rules interact in unexpected ways?</em></p><p>To measure this precisely, we conducted a study (available at <a href="https://github.com/imandra-ai/code-logic-bench">https://github.com/imandra-ai/code-logic-bench</a>) which measured LLMs’ ability to reason about software logic compared with formally augmented analysis based on region decomposition and verification. The results demonstrated a significant improvements in performance among several key metrics. Sometimes improving performance by over 50%!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mz8pln_NJNTlhKdzXIDAqw.png" /></figure><p>The metrics assess complementary aspects of reasoning quality: <strong>State Space Estimation Accuracy</strong> measures quantification of behavioral regions; <strong>Outcome Precision</strong> evaluates correctness of exact constraints; <strong>Direction Accuracy</strong> checks whether conclusions about safety or correctness are right; <strong>Coverage Completeness</strong> measures how fully scenarios are enumerated; <strong>Control Flow Understanding</strong> evaluates grasp of branching and override logic; <strong>Edge Case Detection</strong> captures identification of rare or boundary behaviors; and <strong>Decision Boundary Clarity</strong> assesses extraction of precise Boolean conditions. See the paper link at <a href="https://www.codelogician.dev">www.codelogician.dev</a>.</p><p>With <strong><em>CodeLogician</em></strong> in the loop, the LLM can generate code, build a formal model of the behavior it just described, query the reasoning engine, and incorporate the returned artifacts into its response. That moves reasoning out of the developer’s head and into the system itself. LLMs explore token space. <strong><em>ImandraX</em></strong> explores state space. One produces plausible outputs; the other produces behavioral guarantees and counterexamples. CodeLogician is the bridge that lets them work together.</p><h3>How the process is changing: the before and after</h3><h4>Before — The Human Is the Reasoning Engine</h4><p>In a typical vibe-coding workflow, the loop looks fast — but the correctness work is still manual:</p><ul><li>You prompt the agent for a change.</li><li>It generates code that looks reasonable.</li><li>You reread the diff and try to simulate the behavior in your head.</li><li>You write tests to search for the edge cases you <em>think</em> might exist.</li><li>You merge with a bit of uncertainty — because you can’t exhaust the state space mentally.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/794/1*lQq-lZ_iClwOk5e7GUsWHA.png" /></figure><p>The LLM helped you write the code, but it didn’t actually <strong>analyze what the code can do</strong>.</p><p>So the responsibility for understanding behavior — all the weird inputs, boundary conditions, and cross-rule interactions — stays with you.</p><p>You’re not reviewing syntax.</p><p>You’re reverse-engineering semantics.</p><p>That’s why non-trivial AI-generated changes still feel slower than they should.</p><p>Generation is instant. Trust is expensive.</p><h4>After — The Agent Has to Show Its Work</h4><p>With CodeLogician in the loop, the workflow changes at the point where correctness matters:</p><ul><li>You prompt the agent.</li><li>Decision-critical logic is detected.</li><li>The agent structures its understanding of that logic into a model.</li><li>The reasoning engine explores the behavior of that model.</li></ul><p>The result comes back with artifacts:</p><ul><li><em>edge cases</em></li><li><em>behavioral boundaries</em></li><li><em>state-transition constraints</em></li><li><em>change impact</em></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wa4PyUDXFEfGl0cjPIIVoQ.png" /></figure><p>Now you review <strong>evidence about behavior</strong>, not just code that “looks right.”</p><p>The key shift is simple but profound:</p><p>Before, the LLM generated and the human reasoned.</p><p>After, the LLM generates and the system reasons.</p><p>You stop asking:</p><blockquote><em>“Did the model get this right?”</em></blockquote><p>and start seeing:</p><blockquote><em>“Here is the set of cases where this logic behaves differently.”</em></blockquote><h3>The Practical Effect</h3><p>This doesn’t slow down the flow — it moves the expensive part to where machines are better than humans:</p><ul><li><strong><em>Humans are good at intent and design.</em></strong></li><li><strong><em>LLMs are good at synthesis.</em></strong></li><li><strong><em>Reasoning engines are good at exhaustive behavioral exploration.</em></strong></li></ul><p>So instead of spending your time mentally executing code paths and inventing tests to catch surprises, you get the surprises surfaced automatically — before the merge, not after the incident.</p><p>Vibe coding made writing code cheap.</p><p>This makes <strong>understanding code</strong> cheap.</p><h3>Demo: A Complex Payment Gateway with Claude Code</h3><p>To get started (<em>and after installing CodeLogician and setting IMANDRA_UNI_KEY as per instructions on codelogician.dev</em>) I asked Claude to learn how to use CodeLogician:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hV4o0x2Cykpp171yLBplyA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yh9_VUzN_sDBkhE37sK3bg.png" /></figure><p>Then, I asked Claude Code to do something that looks straightforward on the surface:</p><blockquote><em>“Come up with a complex payments system example and use CodeLogician to make sure you understand all the edge cases.”</em></blockquote><p>Claude generated a full payment gateway model: payment methods, a transaction state machine, tiered fees, fraud checks, refunds, and merchant balance tracking. Then it ran the CodeLogician CLI to analyze it.</p><h4>Step 1: Generate + Type-check</h4><p>Claude wrote a model file and ran:</p><p>codelogician eval check payment_gateway.iml</p><p>The model type-checked cleanly. That’s the easy part.</p><h4>Step 2: Ask the System to Explore the Logic</h4><p>Next it ran the analysis pass:</p><ul><li><em>codelogician eval check-vg — check-all payment_gateway.iml</em></li><li><em>codelogician eval check-decomp — check-all payment_gateway.iml</em></li></ul><p>This is where the workflow becomes different from “prompt and hope.” The analysis doesn’t just rubber-stamp the code — it tries to break the assumptions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JQ2I0rEYkFdSUiku8Ow9yA.png" /></figure><h4>Step 3: The Engine Found a Real Edge Case</h4><p>One of the intended properties was:</p><blockquote><em>“Fees never exceed the transaction amount.”</em></blockquote><p>Sounds reasonable, right? Claude expected it to hold.</p><p>The reasoning engine produced a concrete counterexample:</p><ul><li><strong>BankTransfer</strong>, amount ≈ <strong>$1.51</strong></li><li>Fee = <strong>$1.50 fixed</strong> + <strong>0.8% × amount</strong></li><li>Result: the fee equals (or exceeds) the transaction amount.</li></ul><p>This is the exact kind of cross-cutting boundary condition that’s obvious in hindsight, but easy to miss when you design the components separately (“$1.50 fixed seems fine” + “0.8% seems fine”).</p><p>And it’s not a toy bug. In a real system, this means a merchant can accept a small bank transfer and <strong>lose money</strong>.</p><p>At that point, we had a choice:</p><ul><li>change the fee policy (e.g., minimum transaction amount)</li><li>or explicitly document the boundary where the rule holds</li></ul><p>Claude fixed the property by adding a minimum-amount constraint (e.g., “for amounts &gt; $2”), reran the analysis, and the updated logic passed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e79klwdkxQ7W4pIGfy68gw.png" /></figure><h4>Step 4: The Second Insight Was Even More Interesting</h4><p>The exploration also revealed a subtler weakness: some functions implicitly rely on callers maintaining invariants (e.g., never passing negative refunded amounts). The engine didn’t “invent” that issue — it made it visible by showing that the input space includes cases you might not defensively guard against.</p><p>This is a common failure mode in real systems:</p><ul><li>you <em>assume</em> upstream validation</li><li>then an integration path violates it</li><li>and you ship a bug that only appears in production</li></ul><p>CodeLogician doesn’t just check “happy path correctness.” It forces you to confront what your logic is actually permitting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O_rs0AL5Em271Wo9nA0yzg.png" /></figure><h3>The Takeaway: This Is Phase 2 of Vibe Coding</h3><p><strong><em>Vibe coding is about speed.</em></strong></p><p><strong><em>Logic-First AI is about turning that speed into something you can trust.</em></strong></p><p>The moment you stop treating the agent as a code generator and start treating it as a system that must <strong>model and explain behavior</strong>, you unlock a different level of reliability — without slowing down everything, and without trying to “prompt harder.” Check out <a href="https://www.codelogician.dev"><strong><em>www.codelogician.dev</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7523beff4188" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/vibe-coding-was-phase-1-logic-first-ai-is-phase-2-it-is-here-now-7523beff4188">Vibe Coding was phase 1. Logic-first AI is phase 2. It is here now.</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[First steps with ImandraX]]></title>
            <link>https://medium.com/imandra/first-steps-with-imandrax-5c3e3a028857?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/5c3e3a028857</guid>
            <category><![CDATA[reasoning]]></category>
            <category><![CDATA[neurosymbolic-ai]]></category>
            <category><![CDATA[mathematics]]></category>
            <category><![CDATA[automated-reasoning]]></category>
            <category><![CDATA[imandra]]></category>
            <dc:creator><![CDATA[Samer Abdallah]]></dc:creator>
            <pubDate>Thu, 08 May 2025 17:53:57 GMT</pubDate>
            <atom:updated>2025-05-12T15:21:07.471Z</atom:updated>
            <content:encoded><![CDATA[<p>This blog entry describes my experience of getting started with <strong>ImandraX</strong> using the VS Code<strong> ImandraX</strong> extension. I decided to take some functions and proofs I made when I was learning <strong>Agda</strong> a couple of years ago, and see<br>how they translate into <strong>ImandraX</strong>.</p><figure><img alt="First steps with ImandraX" src="https://cdn-images-1.medium.com/max/1024/1*quznXSr8FHh6dfr1z12q5w.png" /></figure><h3>Quick background on ImandraX</h3><p><strong>ImandraX</strong> supports many approaches to formal verification, including a seamless integration of bounded and unbounded verification. Every goal can be attacked first with bounded verification (via the `[@@upto n]` annotation), and then subjected to unbounded verification (e.g., proof by induction with `auto` or via more nuanced tactics) only once all counterexamples found have been eliminated.</p><p>We always suggest beginning with bounded verification. Why? (1) Most conjectures one has about code are actually false, and finding counterexamples quickly is much more useful than wasting time trying to prove false goals. In <strong>ImandraX</strong>, all counterexamples are computable objects which are directly available for inspection, computation and experimentation. (2) In many practical engineering applications, bounded verification is sufficient, and with <strong>ImandraX’s</strong> `[@@upto n]` these bounded results can be made “first class” verification results which form a key part of safety cases.</p><p>In this article, I will explore unbounded verification via `auto` and other nuanced tactics. Here’s<a href="https://gist.github.com/samer--/e11bded6f828d27668c1479c8ea6784e"> the complete model</a> if you’d like to follow along.</p><h3>Prequisites</h3><ol><li>It will help if you are familiar with the functional programming language OCaml, upon which <strong>Imandra Modelling Language (IML)</strong> is closely based.</li><li>Install <strong>ImandraX</strong> by running the install script in the Github repository. You can download and run it, or pipe it directly from the repo like this:<br><a href="https://raw.githubusercontent.com/imandra-ai/imandrax-api/refs/heads/main/scripts/install.sh">curl https://raw.githubusercontent.com/imandra-ai/imandrax-api/refs/heads/main/scripts/install.sh</a> | bash</li><li>Get an <strong>ImandraX</strong> API key, e.g. by signing up to Imandra Universe at [<a href="https://universe.imandra.ai/">https://universe.imandra.ai/</a>]. Once you have it, save it in file called $HOME/.config/imandrax/api_key, where $HOME is your home directory.</li><li>Install Microsoft VS Code if you don’t have it already.</li><li>Install the VS Code <strong>ImandraX</strong> extension. When installed, it should look like this:</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*VVPvYoJVoWj60pR2R_jyfA.png" /><figcaption>The ImandraX VS Code extension</figcaption></figure><h3>Some code to reason about</h3><p>First we’ll create ourselves a new file in VS Code, and call it, for example, test.iml . Let’s then introduce a function to play with. We’re going to look at reversing lists. The first list reversing function, rev, is based on a simple structural recursion, where we pattern match on the two possible list constructors. It is sometimes called ‘naive’ reverse because it is (a) easily understood just by looking at it and (b) much slower : O(N²) in the length of the list , rather than the optimal O(N) method we will look at later.</p><pre>let rec rev = function<br> | [] -&gt; []<br> | x::xs -&gt; rev xs @ [x]</pre><h3>Proving some properties of reverse</h3><p>We would like to prove that reversing a list and then reversing the result reproduces the original list, that is, reverse is its own inverse. More formally, we want to prove that for any list x, rev (rev x) = x . From my experiences with <strong>Agda</strong>, I suspected that it would be useful to have a couple of lemmas available which prove that concatenation (the @ operator in OCaml and IML) is associative and reversal is ‘distributive’ in the sense that the reverse of the concatenation of two lists is the reverse concatenation of the reverses of the lists. However, before introducing the lemmas and exploring how <strong>ImandraX</strong> can make use of them, let’s see if <strong>ImandraX</strong> can prove the self-inverse theorem on its own. We encode it like this:</p><pre>theorem rev_self_inverse x =<br>   rev (rev x) = x<br>[@@by auto]</pre><p>When entered into VS Code, the <strong>ImandraX</strong> extension should decorate the function with ‘Check’ and ‘Browse’ clickable links. Clicking on ‘Check’ will tell <strong>ImandraX</strong> to try to prove the theorem below. Alternatively, from the menu, ‘File &gt; Save’ (or Cmd-S) will save the file and check all the lemmas and theorems it contains. The result should look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2a0yy1RTZlP_f43AqWenlw.png" /><figcaption>Successfully proven theorem in the VS Code editor</figcaption></figure><p>The little smiley face to the left indicates that the proof was successful. Clicking on the ‘Browse’ link should open another tab in VS Code containing a description of how <strong>ImandraX</strong> went about proving the theorem and how long it took.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7OZ4jF-pQD5DvH2zFjggzA.png" /><figcaption>Report on the proof of `rev_self_inverse`</figcaption></figure><p>We will examine this proof (by clicking on the triangle next to ‘view’) later, but for now let’s go back and introduce the two lemmas we mentioned earlier, before the main theorem, again relying initially on the automatic proof method that is built in to Imandra.</p><pre>lemma append_assoc x y z =<br>   (x @ y) @ z = x @ (y @ z)<br>[@@by auto]<br><br>lemma rev_distrib x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by auto]<br><br>theorem rev_self_inverse x =<br>   rev (rev x) = x<br>[@@by auto]</pre><p>Let’s browse the report on the proof of rev_distrib. We won’t go through the description in detail, but notice how long it took <strong>ImandraX</strong> to prove rev_distrib using the auto method (or ‘tactic’, as it’s called in <strong>ImandraX</strong> — more on tactics later). When I ran it, it took about 1.5 seconds, which is quite slow in the scheme of things. We will see what we can do to speed that up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tUeVuRdtfqPURYL0D_9B1g.png" /><figcaption>Report on the proof of `rev_distrib`</figcaption></figure><h3>Speeding up the proof using lemmas</h3><p>If you expand the ‘view’ triangle in the ‘Report’ section of the proof browser for rev_distrib, which lets you view a report of the reasoning done by Imandra’s inductive waterfall (auto) tactic, you might notice something which looks like the associativity proposition for list concatenation (represented by the function List.append here — in ImandraX, the @ operator is an infix macro that expands to an application of the List.append function). Buried in the description there is this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9kCkgyY36LSytlg4AAc4ZA.png" /><figcaption>Part of the proof of `rev_distrib`</figcaption></figure><p>which is a particular case of associativity for the three lists gen_2 , gen_1 and [x1] . ImandraX then goes on to prove this without using the lemma we carefully prepared above. If we can get it to use the already-proven general form of the lemma, we might save some time, and moreover, effectively ‘teach’ ImandraX how to reason more efficiently in this domain.</p><p>The way to do this is to tag the lemma as a ‘rewrite’ rule, which makes it available for use in the auto tactic. We do this by adding [@@rw] to the definition, like this:</p><pre>lemma append_assoc x y z =<br>   (x @ y) @ z = x @ (y @ z)<br>[@@by auto] [@@rw]</pre><p>We can then click ‘Check’ to refresh the definition and then ‘Check’ and ‘Browse’ again on rev_distrib to see what the effect is. For me, the proof time was reduced to about 450 ms, a distinct improvement!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rrJhvjqsvXXS6-Ohqf6Myw.png" /><figcaption>Report on the sped-up proof of `rev_distrib` after making `append_assoc` a rewrite rule.</figcaption></figure><p>Examining the proof, we find this, showing that the automatic tactic did indeed find and use the lemma.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gsnfZstgK-ZOtSaGA8q7ww.png" /><figcaption>Part of the proof of `rev_distrib`, showing application of `append_assoc` rewrite rule</figcaption></figure><p>Looking further up the proof, we can also find this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ewwZMA7xC7Qi2tcLZs_tZw.png" /><figcaption>Part of the proof of `rev_distrib`, showing use of built-in `List.append_to_nil` rule</figcaption></figure><p>It turns out that the proof was already using a lemma defined in the ImandraX List module. This lemma, List.append_to_nil, states that, for any list x, List.append x [] = x . The snippet says that, assuming list x is not of the form _ :: _ , i.e. it’s an empty list [], we can conclude that right hand side (RHS) of the goal rev y @ rev x = rev y @ [] = rev y . Since the LHS, x @ y = [] @ y = y is true from the definition of List.append, ImandraX is able to reduce the goal to rev y = rev y, which is trivially true.</p><p>Let’s have a look at the proof of rev_self_inverse— as we noticed before introducing the lemmas, it goes through in about 740 ms without using rev_distrib, not surprisingly because we have not marked rev_distrib as available for rewriting. There is a point where rev_distrib <em>could</em> be applied, here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G3JCtfZ0VgpIWJfzpt1F4w.png" /><figcaption>Part of the proof of `rev_self_inverse` without using `rev_distrib` lemma</figcaption></figure><p>On the LHS of the goal, we have rev (List.append _ _). What happens if we make rev_distrib available for use? We add [@@rw] to the definition:</p><pre>lemma rev_distrib x y =<br>   rev1 (x @ y) = rev y @ rev x<br>[@@by auto] [@@rw]</pre><p>Saving and reviewing the proof of rev_self_inverse, we find:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0SqrhXevzH7Wub5zVZroXg.png" /><figcaption>Report on sped-up proof of `rev_self_inverse` after enabling use of `rev_distrib` lemma</figcaption></figure><p>Success! The proof time is down to about 250 ms, and expanding the report, we find exactly what we were hoping for:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S8T77Ym2ncssDSPqK21BEg.png" /><figcaption>Part of proof of `rev_self_inverse` showing use of `rev_distrib` rewrite rule</figcaption></figure><h3>Weaning off the ‘auto’ proof tactic</h3><p>When looking through the proof reports, you may get the impression (correctly) that <strong>ImandraX</strong> is somehow arriving at a long sequence of steps, often including nontrivial inductions and simplifications, each of which transforms the goals of the proof, until magically, at the end, the goal reduces to something which is trivially true and the proof is done. At each stage, there might be several transformations which could be applied, and ImandraX has to search through or use heuristics to find a sequence of steps which results in success. In this section, we will gradually reduce our reliance on the auto tactic, not because there’s anything wrong with using auto — it is an extremely powerful mechanism that is robust to semantics-preserving changes in the code. Rather, our purpose here pedagogical, and by learning how the various tactics work, we can use them judiciously to improve or direct harder proofs.</p><p>When building a proof manually, its very useful to be able to see the ‘state’ of a partial proof, i.e. goals that remain to be proved. To do this, you can either click on the search box at the top of the VS Code window and click on ‘Show and Run Commands’:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Fz_iKbYpHin6UGorWw52Ig.png" /></figure><p>or press Shift-Cmd-P. Then type ‘ImandraX’ and choose ‘Open goal state’.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L4E2Joq9tB_q77cAGwD1Dw.png" /></figure><p>This should open a new panel called goal-state.md:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a_9GYRrD-81LjvhYzsnuNQ.png" /></figure><p>Now let’s start a new lemma called rev_distrib_manual , but instead of using the auto tactic, we’ll use skip. <strong>Important</strong>: we must also remove the [@@rw] annotation from our previous version of rev_distrib to prevent ImandraX from using rev_distrib to prove rev_distrib_manual, as this would rather defeat the point of the exercise!</p><pre>lemma rev_distrib x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by auto] (* REMOVE [@@rw] *)<br><br>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by skip]</pre><p>Since skip doesn’t advance the proof at all, the proof fails:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DAW-vWYpuEZgg1O0ubnhBQ.png" /></figure><p>However, if we look at the goal state, we see</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WxEeRlp0yg1B949484O2Yg.png" /></figure><p>We have goal to prove below the line and no hypotheses above it. We can reproduce the first step of the automatic proof by using the induction tactic. This will produce two subgoals, one for each case of a structural induction on one of the list arguments. The induction tactic must be composed with two further tactics to handle each of the cases. We are free to use auto for both of these, which results in a sucessful proof:</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>      @&gt;| [auto; auto]]</pre><p>For reference, this proof took about 425 ms for me.</p><p>When we invoke induction () on its own, ImandraX uses its induction heuristics (the same that are used by auto) to analyze the goal and synthesize an appropriate induction scheme. Unlike auto, however, induction () then simply returns the base and inductive cases as subgoals and lets us continue proving the goal manually. We can give induction instructions on which specific induction schemes we want, but in this case (and usually) we are happy to let it synthesize the appropriate induction scheme itself automatically.</p><p>By winding this back to just the induction step, we can get to see the two subgoals:</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by induction ()]</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jbbd5vak3CxscNxNer36Tg.png" /></figure><p>The first subgoal, the base case, assumes the list x is empty, while the second assumes it’s not empty <em>and </em>an inductive hypothesis, which we can write more clearly without type decorations and using the @ operator instead of List.append:</p><pre>rev (List.tl x @ y) = rev y @ rev (List.tl x)</pre><p>Crucially, this is just the rev_distrib_manual lemma with x replaced with List.tl x. From now on I’ll illustrate the goal state not as a screenshot, but as text below the lemma code, without type decorations and using the @ operator.</p><p>The base case can be dealt with using the tactic simplify (). This can exploit function definitions and rewrite rules, but at this point, using it is no more enlightening than using auto, so let’s see we can do at a lower level. The normalize tactic can expand and simplify a target term using function definitions and hypotheses. We apply it to rev x in the first subgoal: since x = [] and rev [] is <em>defined</em> as [], the term reduces to [].</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>      @&gt;| [ [%normalize rev x]<br>          ; auto<br>          ]]<br><br> H0. x = []<br>|----------------------------------------------------------------------<br> rev (x @ y) = rev y @ []</pre><p>Similarly we can normalize x @ y using x = [] and the definition of List.append, to get x @ y = [] @ y = y . The two normalize steps are combined using the @&gt; operator.</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>      @&gt;| [    [%normalize rev x]<br>            @&gt; [%normalize x @ y]<br>          ; auto<br>          ]]<br><br> H0. x = []<br>|----------------------------------------------------------------------<br> rev y = rev y @ []</pre><p>We are quite close now. The built in List.append_to_nil lemma we found earlier can be applied with the use tactic to simplify the RHS:</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>     @&gt;| [    [%normalize rev x]<br>           @&gt; [%normalize x @ y]<br>           @&gt; [%use List.append_to_nil (rev y)]<br>         ; auto<br>         ]]<br><br> H0. x = []<br> H1. rev y @ [] = rev1 y<br>|----------------------------------------------------------------------<br> rev1 y = rev1 y @ []</pre><p>Notice that this does not immediately simplify the goal — rather it inserts the hypothesis associated with the lemma above the line (<em>H1</em> above). We can use the replace tactic to simplify a term using the hypotheses:</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>     @&gt;| [    [%normalize rev x]<br>           @&gt; [%normalize x @ y]<br>           @&gt; [%use List.append_to_nil (rev y)]<br>           @&gt; [%replace rev y @ []]<br>         ; auto<br>         ]]<br><br> H0. x = []<br>|----------------------------------------------------------------------<br> rev y = rev y</pre><p>Finally, the trivial tactic deals with the final remaining tautology in the goal. If we add trivial to the chain and replace auto with skip for the second subgoal, we’ll complete the first subgoal and see what needs doing for the second.</p><pre>lemma rev_distrib_manual x y =<br>   rev (x @ y) = rev y @ rev x<br>   [@@by induction ()<br>     @&gt;| [    [%normalize rev x]<br>           @&gt; [%normalize x @ y]<br>           @&gt; [%use List.append_to_nil (rev y)]<br>           @&gt; [%replace rev y @ []]<br>           @&gt; trivial<br>         ; skip<br>         ]]<br><br> H0. x &lt;&gt; []<br> H1. rev (List.tl x @ y) = rev y @ (rev (List.tl x))<br>|----------------------------------------------------------------------<br> rev (x @ y) = rev y @ rev x</pre><p>We’ll start by using normalize on the LHS of the goal. From here on, for brevity, I’ll omit the List. qualifier when referring to the list head and tail functions hd and tl respectively.</p><pre>lemma rev_distrib_manual x y =<br>    rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>    @&gt;| [    [%normalize rev x]<br>          @&gt; [%normalize x @ y]<br>          @&gt; [%use List.append_to_nil (rev y)]<br>          @&gt; [%replace (rev y) @ []]<br>          @&gt; trivial <br>        ;    [%normalize rev (x @ y)]<br>        ]]<br><br> H0. x &lt;&gt; []<br> H1. rev1 (tl x @ y) = rev y @ rev (tl x)<br>|----------------------------------------------------------------------<br> rev (tl x @ y) @ [hd x] = rev y @ rev x</pre><p>Next let’s normalize rev x in the RHS too:</p><pre>lemma rev_distrib_manual x y =<br>    rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>    @&gt;| [    [%normalize rev x]<br>          @&gt; [%normalize x @ y]<br>          @&gt; [%use List.append_to_nil (rev y)]<br>          @&gt; [%replace (rev y) @ []]<br>          @&gt; trivial <br>        ;    [%normalize rev (x @ y)]<br>          @&gt; [%normalize rev x]<br>        ]]<br><br> H0. x &lt;&gt; []<br> H1. rev (tl x @ y) = rev y @ rev (tl x)<br>|----------------------------------------------------------------------<br> rev1 (tl x @ y) @ [hd x] = rev y @ (rev (tl x) @ [hd x])</pre><p>Notice that we have a term rev (tl x @ y) in the LHS of the goal, and a hypothesis, the induction hypothesis in fact, about this term in <em>H1</em> above the line. We can use <em>H1 </em>to rewrite the term in the goal using the replace tactic:</p><pre>lemma rev_distrib_manual x y =<br>    rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>    @&gt;| [    [%normalize rev x]<br>          @&gt; [%normalize x @ y]<br>          @&gt; [%use List.append_to_nil (rev y)]<br>          @&gt; [%replace (rev y) @ []]<br>          @&gt; trivial <br>        ;    [%normalize rev (x @ y)]<br>          @&gt; [%normalize rev x]<br>          @&gt; [%replace rev (List.tl x @ y)]<br>        ]]<br><br> H0. x &lt;&gt; []<br>|----------------------------------------------------------------------<br> (rev y @ rev (tl x)) @ [hd x] = rev y @ (rev (tl x) @ [hd x])</pre><p>Nearly there. Looks like all we need to do now is apply the append_assoc lemma to rewrite the LHS of the goal to match the RHS.</p><pre>lemma rev_distrib_manual x y =<br>    rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>    @&gt;| [    [%normalize rev x]<br>          @&gt; [%normalize x @ y]<br>          @&gt; [%use List.append_to_nil (rev y)]<br>          @&gt; [%replace (rev y) @ []]<br>          @&gt; trivial <br>        ;    [%normalize rev (x @ y)]<br>          @&gt; [%normalize rev x]<br>          @&gt; [%replace rev (List.tl x @ y)]<br>          @&gt; [%use append_assoc (rev y) (rev (List.tl x)) [List.hd x]]<br>        ]]<br><br> H0. x &lt;&gt; []<br> H1. (rev y @ rev (tl x)) @ [hd x] = rev y @ (rev (tl x) @ [hd x])<br>|----------------------------------------------------------------------<br> (rev y @ rev (tl x)) @ [hd x] = rev y @ (rev (tl x) @ [hd x])</pre><p>The goal is now the same as the hypothesis, so we can finish the proof by adding the trivial tactic:</p><pre>lemma rev_distrib_manual x y =<br>    rev (x @ y) = rev y @ rev x<br>[@@by induction ()<br>    @&gt;| [    [%normalize rev x]<br>          @&gt; [%normalize x @ y]<br>          @&gt; [%use List.append_to_nil (rev y)]<br>          @&gt; [%replace (rev y) @ []]<br>          @&gt; trivial <br>        ;    [%normalize rev (x @ y)]<br>          @&gt; [%normalize rev x]<br>          @&gt; [%replace rev (List.tl x @ y)]<br>          @&gt; [%use append_assoc (rev y) (rev (List.tl x)) [List.hd x]]<br>          @&gt; trivial<br>        ]]</pre><p>This time the proof succeeds, taking about 190 ms for me.</p><p>There are more things we can do to break down this proof into still smaller steps — for example, the normalize tactic is quite clever in how it exploits function definitions and hypotheses, and can be replaced with a more explicit sequence of operations — but we’ll leave that for now. It is also possible to explore how some of the proof steps can be reordered when they operate independently on different parts of the goal.</p><h3>A more efficient list reversal method</h3><p>In this section, we’ll introduce a new function rev_fast, which reverses lists more efficiently than rev. It is defined with the help of a tail recursive function shunt with an accumulator argument that simultanously tears down the input list while building up the reverse list, all in one pass through the elements of the list:</p><pre>let rec shunt tail = function<br>    | [] -&gt; tail<br>    | x::xs -&gt; shunt (x::tail) xs<br><br>let rev_fast x = shunt [] x</pre><p>Let’s see what we can prove about this function. Again, from my previous exploration of this topic in Agda, I knew that there is a useful lemma associated with shunt , which we can encode in ImandraX like this:</p><pre>lemma shunt_lemma acc items =<br>   shunt acc items = rev1 items @ acc<br>[@@by auto] [@@rw]</pre><p>It states that shunt reverses (according to our now verified and trusted definition of reverse rev) the given items and appends the list in the accumulator. Clearly, this is very close to proving that rev_fast produces the same result as rev. As we’ve already enabled shunt_lemma as a rewrite rule, let immediately try to prove that rev_fast x = rev x for all lists:</p><pre>lemma rev_fast_eq_rev x =<br>   rev_fast x = rev x<br>[@@by auto]</pre><p>This proof succeeds in about 30 ms, making use of both shunt_lemma and List.append_to_nil . Intuitively, if rev_fast produces the same result as rev , and rev is self-inverse, then rev_fast must be self-inverse. ImandraX can prove this by itself, in about 700 ms if we <em>don’t</em> make rev_self_inverse and rev_fast_eq_rev available as rewrite rules, or about 40 ms if we do. Let’s instead prove it manually:</p><pre>lemma rev_fast_self_inverse x =<br>   rev_fast (rev_fast x) = x<br>[@@by skip]<br><br>|----------------------------------------------------------------------<br> rev_fast (rev_fast x) = x</pre><p>Instead of our previous approaches where we start by expanding terms using function definitions, let’s immediately use our rev_fast_eq_rev lemma:</p><pre>lemma rev_fast_self_inverse x =<br>   rev_fast (rev_fast x) = x<br>[@@by [%use rev_fast_eq_rev x]]<br><br> H0. rev_fast x = rev x<br>|----------------------------------------------------------------------<br> rev_fast (rev_fast x) = x</pre><p>Like when we used the List.append_to_nil lemma in an earlier proof, the goal is left unchanged but the lemma is inserted above the line. We can use replace to consume and apply it below the line:</p><pre>lemma rev_fast_self_inverse x =<br>   rev_fast (rev_fast x) = x<br>[@@by [%use rev_fast_eq_rev x]<br>   @&gt; [%replace rev_fast x]]<br> <br>|----------------------------------------------------------------------<br> rev_fast (rev x) = x</pre><p>We’ll repeat the process to rewrite rev_fast (rev x):</p><pre>lemma rev_fast_self_inverse x =<br>   rev_fast (rev_fast x) = x<br>[@@by [%use rev_fast_eq_rev x]        <br>   @&gt; [%replace rev_fast x]<br>   @&gt; [%use rev_fast_eq_rev (rev x)] <br>   @&gt; [%replace rev_fast (rev x)]<br><br>|----------------------------------------------------------------------<br> rev (rev x) = x</pre><p>The remaining goal is none other than the proposition of rev_self_inverse, so the proof is completed with a use and a trivial, and completes in under 1 ms:</p><pre>lemma rev_fast_self_inverse x =<br>   rev_fast (rev_fast x) = x<br>[@@by [%use rev_fast_eq_rev x]       @&gt; [%replace rev_fast x]<br>   @&gt; [%use rev_fast_eq_rev (rev x)] @&gt; [%replace rev_fast (rev x)]<br>   @&gt; [%use rev_self_inverse x]<br>   @&gt; trivial]</pre><p>Note that there are many other ways to do this — for example, if we’d wanted to let ImandraX’s rewriter apply rev_fast_eq_rev automatically, we could have used the simplify or normalize tactics. Nevertheless, it’s useful to see how to do these various steps manually!</p><h3>Going further</h3><p>As mentioned at the end of the section on doing manual proofs, there are many ways to break down proofs into more elementary steps, and there are many more tactics that can be used. The best place to find an authoritative list of available tactics is in the ImandraX source code: if you right click on any of the tactics in the code in VS Code, and click on ‘Go to Definition’, VS Code will bring up prelude.iml in a new panel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sDb9x0Dagr4zPmTNS5lDsg.png" /></figure><p>Finally, a summary of the code in this article is available in this <a href="https://gist.github.com/samer--/e11bded6f828d27668c1479c8ea6784e">Github gist</a>, and a short ‘cheat sheet’ of available tactics is available <a href="https://gist.github.com/samer--/25f6198ec190220e7bb74dd32113b73c">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c3e3a028857" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/first-steps-with-imandrax-5c3e3a028857">First steps with ImandraX</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Connecting Digital Twins and Systems]]></title>
            <link>https://medium.com/imandra/connecting-digital-twins-and-systems-170a3164c005?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/170a3164c005</guid>
            <category><![CDATA[imandra]]></category>
            <category><![CDATA[financial-markets]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[software-verification]]></category>
            <category><![CDATA[automated-reasoning]]></category>
            <dc:creator><![CDATA[Filippo Sestini]]></dc:creator>
            <pubDate>Mon, 14 Oct 2024 21:06:06 GMT</pubDate>
            <atom:updated>2024-10-14T21:06:06.461Z</atom:updated>
            <content:encoded><![CDATA[<p><em>In the world of financial trading system APIs, precision and accuracy are paramount. Imandra Protocol Language (IPL) models serve as formal digital twin counterparts of real-world trading systems’s APIs (e.g. FIX). However, for these models to be truly useful and valuable, we need rigorous methods to ensure they are functionally equivalent with the actual systems.</em></p><p><em>Such verification process becomes particularly challenging when: 1) the systems being modeled are not directly accessible (via source code, for example), and 2) the systems “downstream” implement incredibly complex logic (e.g. stock exchange). This is where Imandra’s automated reasoning comes into play.</em></p><figure><img alt="Connection between FIX Gateway and its formal Digital Twin via Automated Reasoning" src="https://cdn-images-1.medium.com/max/1024/1*y88VV90pp1c53uYBFgFqGA.png" /></figure><h4>Modeling FIX Gateways as Digital Twins</h4><p>The description of how IPL models trading system APIs (e.g. FIX gateways) is beyond the scope of this text, but we urge the reader to view IPL <a href="https://www.imandra.ai/imandra-protocol-language">product</a> and <a href="https://docs.imandra.ai/ipl/">documentation</a> pages. At a high-level — the problem IPL solves is modeling system interfaces that may be in virtually infinite number of possible states as formal digital twins. IPL has many built-in powerful techniques and features for doing so.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TwThPX9O8SVNol__ODVrFw.png" /></figure><p>For example, a stock exchange may respond to a customer order with 0 fills (successful trades), 1 fill or 100,000 fills. IPL tackles all such cases with symbolic description of possible events conditioned on the current state of the session. With IPL — fill events are modeled as symbolic actions that are described with 1) data they contain (e.g. fill price), 2) constraints describing when such actions might occur (e.g. if order is active) and 3) how the system reacts to such events. Reasoning about such actions and reconciling them with logs requires powerful reasoning tools like Imandra. Note that the underlying interaction of the order with other orders (e.g. exchange rule book) is beyond the scope of the model.</p><h4>Connecting Digital Twins to Systems</h4><p>Imandra’s <strong><em>Log Analysis</em></strong> tool is a high-level, behavior analysis tool that consumes trading logs produced by the trading system interface being modeled. It then checks these logs for coherence with the IPL model. In other words, it verifies whether the sequence of messages from the real-world logs fits a trading scenario described by the model/specification.</p><p>The performed tests range from basic checks of <em>well-formedness</em> of individual messages to complex verification of the entire log sequence against the model’s message flow and logic. This type of testing is crucial for two main reasons:</p><ol><li>It helps identify potential issues in the model itself (for example, when the model doesn’t implement a valid state transition).</li><li>It flags behaviors in the real system that deviate from the intended specification.</li></ol><p>In essence, these tests can be used to exercise both the model and the deployed system simultaneously, providing a comprehensive verification approach.</p><h4>Checking Log Consistency Against Fields/Messages</h4><p>The first stage of testing in our Log Analysis tool checks the <em>well-formedness</em> of messages (our examples use FIX protocol) contained in the input logs. This phase looks at aspects such as proper formatting, checksum validity, and correct encoding of fields with respect to the field’s data types as specified in the IPL model.</p><p>For example, consider this (excerpt of a) NewOrderSingle message:</p><pre>8=FIX.4.4|9=615|35=D|38=SOMEQTY|...</pre><p>Running this FIX log through our tool produces an error message similar to the following:</p><pre>correct traces: 0% (0/1)<br>reason: cannot convert fix message to model message<br>attempting to match: 8=FIX.4.4|9=615|35=D|38=SOMEQTY|...<br>details:<br>Parsing app data : WrongValueFormat:38</pre><p>This error occurs because the OrderQty field (38) is declared as a numeric field, but the quantity in the FIX log contains a string payload.</p><h4>Conditional Validations</h4><p>IPL allows for the addition of validation statements to message declarations. For instance, we can specify that Limit orders must have a Price field defined:</p><pre>message NewOrderSingle {<br>  req ClOrdID<br>  req OrdType<br>  opt Price<br>  ...<br>  validate {this.OrdType==OrdType.Limit == present(this.Price)}<br>  ...<br>}</pre><p>Our Log Analysis tool uses these validation statements from the source IPL model to check the consistency of a message’s fields with each other.</p><p>Consider this FIX message as an example:</p><pre>8=FIX.4.1|9=103|35=D|34=3|49=SENDER|52=20121105-23:24:42|56=EXEC|11=1352157882577|21=1|38=10000|40=2|54=1|55=RECV|59=0|10=062</pre><p>Here we have a limit order (tag 40 = 2) that does not contain a price (field 44). Predictably, this message fails our tool’s validation checks:</p><pre>correct traces: 0% (0/1)<br>reason: message failed model validations<br><br>details:<br>invalid message: 8=FIX.4.1|9=103|35=D|34=3|49=SENDER|52=20121105-23:24:42|56=EXEC|11=1352157882577|21=1|38=10000|40=1|54=1|55=RECV|59=0|10=062<br>model reject reason: Message NewOrderSingle violates the constraint<br>                                        &quot;this.OrdType==OrdType.Limit == present(this.Price)&quot;</pre><h4>Modeling Downstream Systems with Actions</h4><p>As already discussed, actions in IPL represent events that may occur asynchronously and affect the model’s state. These events can be triggered by things like external actors (e.g., an incoming message from another trader), or other components within the same system (e.g., an instrument being suspended by market operations).</p><p>A common example of an action in trading systems is a fill event. Fills are particularly suitable for modelling as actions because they occur when a counterparty sends a matching order, which is inherently asynchronous.</p><p>Here’s an example of how we might model a fill action in IPL:</p><pre>action fill {<br>  qty   : Qty<br>  price : Price<br>  <br>  validate {<br>    state.OrdStatus in [ OrdStatus.New, PartiallyFilled ]<br>  }<br>  validate {<br>    this.qty &gt; 0.0 &amp;&amp; this.qty &lt;= state.LeavesQty<br>  }<br>  validate {<br>    this.price &gt; 0.0<br>  }<br>  ...<br>}</pre><p>We use “receive” blocks to model how the system should react when an action occurs. In the case of a fill, this typically involves updating the model’s state according to the traded quantity, and issuing an execution report to acknowledge the trade.</p><pre>receive (f:fill) {<br>  state.LeavesQty = state.LeavesQty - f.qty<br>  state.CumQty = state.CumQty + f.qty<br>  ...<br>  send ExecutionReport {<br>    ...<br>  }<br>}</pre><p>Actions also allow us to define more complex message flows. For example, we can model a simple flow of a New Order Single followed by a fill Execution Report:</p><pre>messageFlows {<br>  newOrderFill {<br>    template [ NewOrderSingle, fill ]<br>  }<br>}</pre><h4>Symbolic Reasoning About Systems and Logs</h4><p>At a more advanced level, our tool performs symbolic reasoning to verify that sequences of logs are coherent with the message flows defined in the model. Consider a simple model including NewOrderSingle and ExecutionReport messages, as well as a fill action and a newOrderFill flow as defined above.</p><p>Now, let’s examine some FIX logs:</p><pre>8=FIX.4.1|9=103|35=D|34=3|49=SENDER|52=20121105-23:24:42|56=EXEC|11=1352157882577|21=1|38=10000|40=1|54=1|55=RECV|59=0|10=062<br>8=FIX.4.1|9=139|35=8|34=3|49=EXEC|52=20121105-23:24:42|56=SENDER|6=0|11=1352157882577|14=0|17=1|20=0|31=0|32=0|37=1|38=10000|39=0|54=1|55=RECV|150=2|151=0|10=059</pre><p>These logs show a NewOrderSingle (NOS) followed by an ExecutionReport (ER) with execution type “Fill” (field 150 = 2), acknowledging a fill involving the submitted order.</p><p>Our goal is to verify that this sequence of logs is coherent with the message flow defined in the model. To do so, our Log Analysis tool employs Imandra’s symbolic reasoning to devise a sequence of IPL state transitions that align with the logs. More specifically, it attempts to instantiate a fill action such that running the “receive” block on that action produces an ER message matching the one from the FIX logs. We call this operation “action synthesis”.</p><p>When we run our tool on the FIX logs above, we get a positive result, confirming that the logs are consistent with our model. However, let’s see what happens if we modify the execution type of the ER to something other than 2:</p><pre>8=FIX.4.1|9=103|35=D|34=3|49=SENDER|52=20121105-23:24:42|56=EXEC|11=1352157882577|21=1|38=10000|40=1|54=1|55=RECV|59=0|10=062<br>8=FIX.4.1|9=139|35=8|34=3|49=EXEC|52=20121105-23:24:42|56=SENDER|6=0|11=1352157882577|14=0|17=1|20=0|31=0|32=0|37=1|38=10000|39=0|54=1|55=RECV|150=1|151=0|10=059</pre><p>Now we get a different feedback:</p><pre>correct traces: 0% (0/1)<br>reason: attempting to match message with an action failed<br>attempting to match: 8=FIX.4.1|9=139|35=8|34=3|49=EXEC|52=20121105-23:24:42|56=SENDER|6=0|11=1352157882577|14=0|17=1|20=0|31=0|32=0|37=1|38=10000|39=0|54=1|55=RECV|150=1|151=0|10=059</pre><p>This output indicates that the tool encountered an inbound message (the ER) that should correspond to an asynchronous event (i.e., an action), but failed to find a suitable action in the IPL model. This outcome is expected, as the ER in the logs doesn’t fit the NOS+Fill scenario we defined in the model.</p><h3>Conclusion</h3><p>Verifying models against real-world data is a critical step in ensuring the accuracy and reliability. Imandra’s Log Analysis tool provides a powerful solution to this complex problem, bridging the gap between formal specifications and real-world behavior and offering multiple levels of verification from basic syntax checking to reasoning about complex trading logic. By making use of powerful tools like IPL and Imandra, Imandra’s Log Analysis allows to verify that observed system API behavior (as captured in FIX logs) aligns with the formal model specification, and provide detailed feedback on why certain log sequences fail to match the model, facilitating debugging and refinement of both the model and the actual system. Learn more about this tool and many other features of our Imandra Connectivity product on our <a href="https://www.imandra.ai/connectivity">website</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=170a3164c005" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/connecting-digital-twins-and-systems-170a3164c005">Connecting Digital Twins and Systems</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Validating FIX digital twins with automated reasoning]]></title>
            <link>https://medium.com/imandra/validating-fix-digital-twins-with-automated-reasoning-d944ee3db294?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/d944ee3db294</guid>
            <category><![CDATA[imandra]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[automated-reasoning]]></category>
            <category><![CDATA[fix-protocol]]></category>
            <category><![CDATA[financial-markets]]></category>
            <dc:creator><![CDATA[Nicola Mometto]]></dc:creator>
            <pubDate>Tue, 01 Oct 2024 17:03:06 GMT</pubDate>
            <atom:updated>2024-10-01T17:03:06.828Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Imandra Connectivity is a cloud-native AI-powered platform for designing, enabling, auditing and certifying financial APIs (e.g. FIX Protocol). One of its core features is Imandra Protocol Language — a formal programming language for modeling APIs in a way that makes them susceptible to hallucination-free application of automated reasoning (symbolic AI). Most often (in fact, 100% in our experience!), API designs expressed via lengthy PDFs contain errors that are virtually impossible to detect by humans. Below we showcase a recently released feature (a tool) for detecting subtle inconsistencies in the design before they make their way into production environments and cause embarrassment and/or regulatory fines for the financial institutions providing these APIs to their customers.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tHrEkBRXquG1dhSR5Wm9Bg.png" /></figure><h4><strong>The need for digital twins</strong></h4><p>Typical APIs used in financial markets are very complex — they cover many messages, fields with validation logic, workflows, etc. Traditionally, such APIs were designed in and communicated via Word or PDF documents. In two prior posts listed below (and on our website) we describe the numerous problems this entails. To make API design precise and automate (with AI) many tasks involved in the lifecycle of the API, we created a domain-specific language — Imandra Protocol Language-for making it easy to encode such models in a formal (mathematically-precise) format so we can bring to bear the latest advances in AI to ensure their correctness, generate certification packs, test suites, documentation, rigorously integrate with LLMs and much more.</p><p>For further information on the need for IPL please check out earlier articles:</p><ul><li><a href="https://medium.com/imandra/formalising-the-fix-protocol-in-imandra-c976ed390159"><strong><em>“Formalising the FIX protocol in Imandra</em></strong></a><strong><em>”</em></strong></li><li><a href="https://medium.com/imandra/machine-reasonable-apis-and-regulations-20f29e1bd4cf"><strong><em>“Machine Reasonable APIs and Regulations</em></strong></a><strong><em>”</em></strong></li><li><a href="https://www.imandra.ai/imandra-protocol-language">IPL website</a></li></ul><p>IPL models (programs) are functional digital twins of the API endpoint — they model the infinitely many ways a typical customer (e.g. a hedge fund) can use the system behind the API (e.g. exchange order book). In the article below we demonstrate a new tool (feature of our platform) we created to help our users ensure their designs are correct (before they’re implemented in the actual systems!).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B6I9UGdRfWO1rv05lLRBjQ.png" /><figcaption>Formal digital twins connect all of the stakeholders involved over a “golden source of truth” and enable application of neuro-symbolic AI</figcaption></figure><h4><strong>The problem: logical inconsistencies in IPL models</strong></h4><p>In formal specifications, particularly those modeled using Imandra’s IPL, ensuring logical consistency is critical. Our suite of AI tools analyzes IPL models, generates test cases, and can even aid in onboarding conformance. However, when logical inconsistencies exist in the model — whether due to coding errors or deeper logical issues in the specification itself — our tools may exclude certain behaviors from test case generation. Detecting and resolving these inconsistencies can be challenging, especially when the absence of a test case points to an underlying issue that is hard to identify.</p><p>To address this challenge, we developed a tool that shifts the detection of logical inconsistencies earlier in the process, allowing users to catch and resolve these issues before generating test cases. This saves significant time and effort ensuring that models are thoroughly vetted for logical soundness and completeness.</p><h4><strong>Understanding message flows in IPL</strong></h4><p>In IPL, message flows are used to guide our tools when analyzing and decomposing models. A message flow represents a specific sequence of events or messages that the system processes. For example, a simple message flow might analyze the possible behaviors when a <em>NewOrderSingle </em>message is followed by a <em>fill </em>action.</p><p>A more advanced message flow might look like this:</p><pre>messageFlows {<br>  NewOrderNoStopLimit {<br>        template [<br>            NewOrderSingle when { it.OrdType != OrdType.StopLimit } expanding { OrdType } { TimeInForce }<br>        ]<br>    }<br>}</pre><p>This flow instructs the system to analyze the behaviors for a <em>NewOrderSingle </em>message where the <em>OrdType </em>is not <em>StopLimit</em>. It also tells our system to explicitly enumerate all valid combinations of <em>OrdType </em>and <em>TimeInForce </em>in separate test cases.</p><p>See <a href="https://docs.imandra.ai/ipl/messageFlows/">https://docs.imandra.ai/ipl/messageFlows/</a> for more info.</p><h4><strong>An example of a logical inconsistency</strong></h4><p>Consider the following IPL model, which simulates a simplified financial trading system. In this example, we define two key messages: <em>NewOrderSingle </em>and <em>OrderCancelReplaceRequest</em>, and several validation rules around order types and time conditions.</p><pre>import FIX_4_4<br><br>using OrdType { Limit Market StopLimit Stop }<br>      TimeInForce { Day GoodTillCancel }<br><br>messageFlows {<br>    Flow1 {<br>        template [<br>            NewOrderSingle expanding { OrdType, fun pricePresent } { TimeInForce },<br>            OrderCancelReplaceRequest<br>        ]<br>    }<br>}<br>function pricePresent (m : NewOrderSingle) : bool {<br>    return present (m.Price)<br>}<br>internal state {<br>    ClOrdID :? string<br>    is_live : bool = false;<br>}<br>message NewOrderSingle {<br>    req ClOrdID<br>    req OrderQtyData.OrderQty valid when it &gt; 0.0<br>    req OrdType<br>    opt Price valid when (case (it) { Some x : x &gt; 0.0 } {None: true })<br>    req TimeInForce<br>    validate {<br>        this.OrdType == OrdType.Limit ==&gt; present (this.Price)<br>    }<br>    validate {<br>        present(this.Price) &lt;==&gt; this.TimeInForce != TimeInForce.Day<br>    }<br>    validate {<br>        this.OrdType != OrdType.Market ==&gt; this.TimeInForce == TimeInForce.Day<br>    }<br>    validate {<br>        !state.is_live<br>  }<br>}<br>message OrderCancelReplaceRequest {<br>    req ClOrdID valid when (Some it) == state.ClOrdID<br>    validate { state.is_live }<br>}<br>receive (m : NewOrderSingle) {<br>    state.ClOrdID = Some m.ClOrdID<br>}</pre><p>There are two critical logical inconsistencies in this model:</p><ul><li><em>OrderCancelReplaceRequest </em>can never be valid<br>The <em>OrderCancelReplaceRequest </em>message requires <em>state.is_live </em>to be true for the message to be valid, but in the <em>NewOrderSingle </em>message’s receive block, <em>state.is_live </em>is never updated. As a result, the <em>OrderCancelReplaceRequest </em>can never be accepted, making the model logically inconsistent.</li><li>Contradictory validation rules for <strong><em>NewOrderSingle</em></strong><br>The validation rules for the <strong><em>NewOrderSingle</em></strong><em> </em>message introduce a conflict that prevents any <strong><em>Limit</em></strong><em> </em>order from being valid:<br>- Rule A: If <em>OrdType </em>is <em>Limit</em>, the <em>Price </em>must be present.<br>- Rule B: If the <strong><em>Price</em></strong><em> </em>is present, <strong><em>TimeInForce</em></strong><em> </em>must be something other than Day.<br>- Rule C: If the <strong><em>OrdType</em></strong><em> </em>is not <strong><em>Market</em></strong>, <strong><em>TimeInForce</em></strong><em> </em>must be <strong><em>Day</em></strong>.<br>These rules form a cycle of contradictions, making it impossible for a Limit order to satisfy all the conditions at once, rendering the model logically invalid.</li></ul><h4><strong>How our IPL Logic Validator tool solves this</strong></h4><p>Let’s assume we are analyzing the model under the <em>Flow1 </em>message flow. Our new <strong><em>IPL Logic Validator</em></strong> tool can automatically detect these inconsistencies. For example, it might highlight that the sequence <strong><em>NewOrderSingle</em></strong><em> -&gt; </em><strong><em>OrderCancelReplaceRequest</em></strong><em> </em>is unreachable due to the failure to update <em>state.is_live</em>, as well as pointing out the specific contradictions in the validation rules for the <em>NewOrderSingle </em>message preventing a Limit order from ever being valid.</p><p>Here’s a simplified output from the tool showing the unsatisfiable conditions:</p><pre>For message flow `Flow1`, unsat cores:<br>  step 0: [OrdType(Flow1,NewOrderSingle) = Limit +<br>           this.OrdType == OrdType.Limit ==&gt; present (this.Price) +<br>           present(this.Price) &lt;==&gt; this.TimeInForce != TimeInForce.Day +<br>           this.OrdType != OrdType.Market ==&gt; this.TimeInForce == TimeInForce.Day];<br>  step 1: [state.is_live]</pre><p>This output shows exactly where the model’s logic breaks down. It highlights the specific combination of validation rules for <em>NewOrderSingle</em> that leads to a logical contradiction, and it also points out that <em>state.is_live</em> is never updated, preventing the <em>OrderCancelReplaceRequest </em>from being valid.</p><h4><strong>(Coming soon!) Seamless integration with IPL VS Code Plugin</strong></h4><p>To make this process even more seamless, our <strong><em>IPL Logic Validator </em></strong>tool<strong><em> </em></strong>will soon be fully integrated into our Visual Studio Code extension. When browsing an IPL model, users can simply right-click on the relevant section and select <strong><em>“Validate IPL Logic”</em></strong> from the contextual menu. This triggers an immediate analysis of the model’s logical consistency, pinpointing any unsatisfiable conditions directly within the VSCode environment. This integration allows for quick, in-editor feedback, helping developers identify and resolve issues faster without leaving their workflow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VHJV8ayEPH6aTTKBQfyZZg.png" /></figure><p>Download our IPL VS Code Plug-in in the <a href="https://marketplace.visualstudio.com/items?itemName=aestheticintegration.ipl-vscode">Visual Studio Marketplace</a>.</p><h4><strong>Conclusion</strong></h4><p>Our new tool streamlines the process of detecting and diagnosing logical inconsistencies in IPL models. By catching these issues early — before generating test cases or conducting onboarding sessions — it saves developers and spec designers from the frustrating task of manually hunting down the root causes of missing or unreachable behaviors.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d944ee3db294" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/validating-fix-digital-twins-with-automated-reasoning-d944ee3db294">Validating FIX digital twins with automated reasoning</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automated Reasoning for SysML v2 Part 3]]></title>
            <link>https://medium.com/imandra/automated-reasoning-for-sysml-v2-part-3-cfc8fc60c8af?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/cfc8fc60c8af</guid>
            <category><![CDATA[automated-reasoning]]></category>
            <category><![CDATA[safety-critical]]></category>
            <category><![CDATA[formal-verification]]></category>
            <category><![CDATA[imandra]]></category>
            <dc:creator><![CDATA[Jamie Smith]]></dc:creator>
            <pubDate>Tue, 20 Feb 2024 16:14:16 GMT</pubDate>
            <atom:updated>2024-04-25T21:04:54.932Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0WPza523n9IeBx4oMeP47Q.png" /></figure><p>Using Imandra Region Decomposition to Gain Insights of SysML v2 Models.</p><p><em>The team at Imandra is continuing to develop techniques and explore how Imandra Automated Reasoning can provide insights and prove properties of SysML v2 models. This is the third article in a series on how to use Imandra with SysML v2. In the prior two articles, we covered how Imandra Automated Reasoning is used to verify and prove the correctness of systems. We will expand the Automated Reasoning discussion in this article by looking deeper at proving determinism using formal methods.</em></p><p><em>Specifically, in the prior two articles, we covered formally verifying two different types of verification goals for a traffic light. In article one, we proved that the system properly handled errors and placed the systems in an operational “stop_sign” mode if an error was detected. We proved that the system would fail-operational and notify the central office of the error via an SMS message. In the second article, we used Imandra Automated Reasoning to prove SysML v2 constraints and requirements are always met.</em></p><p><em>I recommend that if you have not read those two articles, please do so; they are linked in medium.com at the bottom of this article. For those of you who have read them, thank you. I am very interested in your thoughts and feedback.</em></p><h3>Using Imandra Automated Reasoning with SysML v2.</h3><p>SysML v2 is a general-purpose modeling language for complex system design. SysML v2 is an open standard developed by the Object Management Group (OMG). Our work at Imandra is based on the SysML v2 pilot implementation and beta release from July 2023. The OMG working groups continue progressing toward a final release, which should come in mid-2024. For more information on the SysML v2 release schedule, go to omg.org.</p><p>We have spent several months learning SysML v2 and mapping the semantics to Imandra Modeling Language (IML). The more we learn, develop, and experiment, the more excited we get about the potential. The SysML v2 formal semantics will allow us to use Imandra Automated Reasoning in a wide range of applications to help systems engineers design safer systems faster.</p><p>At Imandra, we are executing against a roadmap that includes the following:</p><p>· Formal verification of properties, requirements, and constraints.</p><p>· Conversion from SysML v1 to SysML v2</p><p>· SysML v2 to IML transpiler</p><p>· Hallucination-free natural language interfaces (ChatBots) will enable all stakeholders to explore and reason about SysML v2 models.</p><p>· Integration with popular MBSE development tools</p><p>In this article, we will continue to use the traffic light model discussed in the previous two articles. We will show how to use Imandra Automated Reasoning to prove the determinism of the state machine. We will also introduce you to region decomposition to explore all the behavioral regions of the SysML v2 model and to create a semantic diff.</p><h3>SysML v2 State Machines, States, and Transitions</h3><p>SysML v2 system behavioral flow is done primarily in two ways. Systems engineers use SysML v2 to model behavior with action nodes and control nodes, such as forks, joins, merge, and decision. The other approach is state-based behavior using a state machine. Both approaches model complex behaviors, and of course, these two approaches can be combined side-by-side or in a hierarchy. This article will focus solely on state-based behavior and state machines.</p><p>SysML v2 state machines have three basic elements: states, transitions, and actions. States invoke actions, and transitions determine when you change states. SysML v2 states are a specialized type of action that calls up to three sub-actions: an Entry-action, a Do-action, and an Exit-action. Of course, actions can call additional actions, so you are not limited to only three actions, but there are three events when the actions are invoked. The Entry-action is executed when you enter the state, and then the Do-action is executed after the Entry-action is complete. The Exit-action is executed when the state transitions to another state.</p><p>The SysML v2 transitions determine when the state machine changes from one state to another. The SysML v2 transitions may have guards that are expressions that return a Boolean value. If the Boolean value is true, then the transition criteria are met, and the state changes to another state dictated by the transition. Here is a simple diagram of the order of execution of states and actions determined by SysML v2 transitions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UrOig6df7Ngr1YoIJXWIdw.png" /></figure><p>One case that can be confusing is what happens if a transition criterion is met before the Do-action is complete. If that occurs, the Do-action is terminated, and the Exit-action is executed.</p><p>To help us better understand SysML v2 state machines, I will go through the traffic light example in some detail. The traffic light state machine has 12 states and 18 transitions. The states are blocks in the state diagram below, and the transitions are arrows. While this state machine is not trivial, it is pretty simple. In the next section, let’s look at a few states and the transitions, first in SysML v2 textual notation and then in Imandra Modeling Language (IML).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P7-2cO7DbEAaIFZXJm-R_w.png" /><figcaption>Traffic Light State Diagram</figcaption></figure><h3>Explaining the Traffic Light State Machine</h3><p>Let’s look at a section of the state machine to help explain SysML v2 state machine concepts and lay the foundation for our discussion of determinism later in the article. We will detail a few states, transitions, and actions to help us understand SysML v2 state machines. This will lead to our explanation of how Imandra Automated Reasoning proves determinism and isolates design errors in the state machine.</p><p>The path from “traffic_flow” to “yellow_on” of the state machine has four states (traffic_flow, red_on, green_on, and yellow_on) and five transitions and two additional transitions between “traffic_flow” and “operation_selection.” We will start with the transitions to show how the state machine determines when to change from one state to another. Below is the SysML v2 textual notation for the transitions “traffic_flow_to_red_on” and “traffic_flow_to_operation_selection.”</p><pre>transition traffic_flow_to_red_on<br>  first traffic_flow<br>  accept when (traffic_light_controller.error == false)<br>  then red_on;<br><br>transition traffic_flow_to_operation_selection<br>  first traffic_flow<br>  accept when ((traffic_light_controller.message_queue != &quot;&quot;)<br>  or (traffic_light_controller.error == true))<br>  then operation_selection;</pre><p>These two transitions are simple and self-explanatory. If the state machine is in the “traffic_flow” state and an error has not been detected, the system will transition to “red_on.” If the state machine is in “traffic_flow” and there is an error, OR if there is an external message, the system will transition to “operation_selection.” In “operation_selection” the system may change its operating mode or if an error occurred continue toward the “diagnostic” state.</p><p>Now let’s look at the “traffic_flow” state.</p><pre>state traffic_flow {<br>  entry traffic_light_controller.check_status;<br>  exit;<br>}</pre><p>The “traffic_flow” state has an entry function “check_status,” which updates both the “Error” Boolean and the “message_queue.” “Error” and “message_queue” are both used to evaluate the “traffic_flow” transitions. To summarize “traffic_flow,” we have an action that updates the attributes the transitions use to trigger transitions to one of two different states. This example is about as simple as it gets. Now let’s move on to the “red_on” state.</p><p>The “red_on” state also has two transitions. They are more complex but still a series of Boolean expressions. We will go over the two transitions one at a time. Below is the “red_on_to_green_on” transition.</p><pre>transition red_on_to_green_on<br>  first red_on<br>  accept when ((traffic_light_controller.wait_duration &gt;traffic_light_controller.red_on_time)<br>      and (vehicle_sensor.vehicle_at_light == true)<br>      and (traffic_light_controller.message_queue == &quot;Safe_all_red&quot;))<br>then green_on;</pre><p>This transition is easy to read from top to bottom. The state machine will change from the “red_on” state to the “green_on” state if the system has been red longer than the “red_on_time” AND there is a vehicle waiting at the red light, AND we have received the safety message that all the other lights at the intersection are red.</p><p>The other transition usage, red_on_to_traffic_flow, is below.</p><pre>transition red_on_to_traffic_flow<br>  first red_on<br>  accept when ((((traffic_light_controller.message_queue != &quot;&quot;)<br>      and (traffic_light_controller.message_queue != &quot;Safe_all_red&quot;))<br>      or (traffic_light_controller.error == true)))<br>then traffic_flow;</pre><p>The state machine transitions from the “red_on” state to the “traffic_flow” state when there is an external message AND that is not “Safe_all_red” OR if an error has been detected. This is similar to the conditions that change the state machine from the “traffic_flow” state to the “operation_selection” state.</p><p>The “red_on” state has actions that impact the traffic light beyond the execution of the state machine. The “red_on” state has an entry function, “turn_red_on,” that handles turning lights off, resetting the wait clock, and changing the state of the red_light to “lit.”</p><p>[NOTE: The :&gt;&gt; is redefine syntax in SysML v2.]</p><pre>state red_on {<br>  entry traffic_light_controller.turn_red_on;<br>  do traffic_light_controller.check_status;<br>  exit;<br>}<br><br>action turn_red_on {<br>  perform action turn_lights_off;<br>  perform action reset_wait_clock;<br>  attribute :&gt;&gt; traffic_light_controller.red_light_state = Light_state::lit;<br>}</pre><p>The states and transitions usages for the states “green_on” and “yellow_on” are similar to “red_on.” The SysML v2 textual representation is included below, but we will not walk through them. One difference is that the “green_on” and the “yellow_on” states have only one transition.</p><pre>transition green_on_to_yellow_on<br>  first green_on<br>  accept when (traffic_light_controller.wait_duration &gt; traffic_light_controller.green_on_time<br>  or (traffic_light_controller.error == true))<br>  then yellow_on;<br><br>transition yellow_on_to_red_on<br>  first yellow_on<br>  accept when ((traffic_light_controller.wait_duration &gt; traffic_light_controller.yellow_on_time))<br>  then red_on;<br><br>state green_on {<br>  entry traffic_light_controller.turn_green_on;<br>  do traffic_light_controller.check_status;<br>  exit;<br>  }<br><br>state yellow_on {<br>  entry traffic_light_controller.turn_yellow_on;<br>  do traffic_light_controller.check_status;<br>  exit;<br>  }</pre><p>For those of you newer to SysML v2, I hope you better understand SysML v2 state machines, including their states and transitions. In the next section, we will use Imandra Automated Reasoning to prove that the state machine is deterministic, which means that each state has either zero or one legal transition at any time.</p><h3>State Machine Determinism</h3><p>We will go over how Imandra Automated Reasoning is used to prove the determinism of a SysML v2 state machine. We can use similar techniques to prove other properties, including but not limited to “reachability” and “no-dead-lock.” Reachability is proving that each state in the state machine can be reached. “No-dead-lock” proves that the state machine will not become stuck in a state and unable to change states.</p><p>To use Imandra Automated Reasoning, we must convert the SysML v2 model to IML. We have been doing these conversions manually but are actively developing a transpiler to convert from SysML v2 to IML automatically.</p><p>The traffic light SysML v2 model has 12 states, 18 transition usages, and 45 attributes. Some of the attributes are discrete, like “error,” and some are continuous, like “wait_duration,” and some are enumerated types, like the “external_messages.” To prove determinism of the state machine, we will focus on the 18 transitions. Let’s take a look a two of the transition converted to IML. The transitions “traffic_flow_to_red_on” and “red_on_to_green_on” in IML are similar to their SysML v2 textual representation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9e8544008418e66c9234af53fb78d3d7/href">https://medium.com/media/9e8544008418e66c9234af53fb78d3d7/href</a></iframe><p>IML is based on the open-source language OCaml, and IML is nearly identical to OCaml. IML is a slightly scoped version of OCaml to ensure we can use all the Imandra Automated Reasoning tools. It is trivial for developers to include OCaml and OCaml libraries in Imandra if needed for visualization or data manipulation.</p><p>In IML, we represent the two transition usages as functions with one parameter, “sa.” “sa” is a variable containing the current SysML state and all 45 system attributes. When we convert the model to IML, we define a type “sysml_state_attributes” and then declare a variable “sa” of type “sysml_state_attributes.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/374510c54679f3e47fc9e8d41270e0d7/href">https://medium.com/media/374510c54679f3e47fc9e8d41270e0d7/href</a></iframe><p>When we evaluate a transition in IML we pass in “sa” and resolve the Boolean logic. If the Boolean logic resolves as “true,” we change the SysML state; if not, we return “sa” unchanged.</p><p>We have converted all 18 SysML v2 transition usages to IML functions. To prove that the state machine is deterministic, we will create a helper function called “check_state_transition_functions” that takes a single parameter, “sa.” “check_state_transition_functions” checks each transition function, in turn, using the same “sa.” Every time a transition evaluates to true, we increment a variable “i” by 1. If no transition functions are satisfied, “check_state_transition_functions” will return “0”; if one and only one is satisfied, it will return “1”; and if more than one transition function is satisfied, it will return “&gt;=2”. For a state machine to be deterministic, this function must return “0” or “1” for all SysML States and all attribute values.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9541f19f355a2ca9df0eec81de9011af/href">https://medium.com/media/9541f19f355a2ca9df0eec81de9011af/href</a></iframe><p>We will use “check_state_transition_functions” in an Imandra verify function to prove that either 0 OR 1 transition usage satisfies all values of “sa.” Imandra Automated Reasoning will evaluate all 18 transition usages, all 12 SysML states, and all 45 attributes simultaneously and mathematically prove that “check_state_transition_functions” returns either “0” OR “1”.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e8f83281c3a30f89c7ef5ecc664fb2b6/href">https://medium.com/media/e8f83281c3a30f89c7ef5ecc664fb2b6/href</a></iframe><p>Those who were paying close attention in the prior sections may have noticed that this verification cannot be proved due to a lack of constraints in the transitions. Two issues prevent the proof. When “verify” is called, Imandra returns Refuted and a Counter Example showing an explicit “sa” that caused the proof to fail. I trimmed down each counter-example to the relevant attributes. I will go through them one at a time.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/57534a1ad4cc950360fb517fb4c938d3/href">https://medium.com/media/57534a1ad4cc950360fb517fb4c938d3/href</a></iframe><p>When the state machine is in the “Traffic_flow” state AND the “error = false” AND there is a message in the message queue, it is ambiguous whether the state machine should transition to the state “Operation_selection” or “Red_on.” We can correct this issue by modifying “traffic_light_to_red_on” by adding a specific check that no messages are in the message queue.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a1d2500a460eeaa48120915c8ed1cc8/href">https://medium.com/media/3a1d2500a460eeaa48120915c8ed1cc8/href</a></iframe><p>Now, let’s look at the second issue, which is a significant safety concern.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a78c44f42b621bd24028ba6dbedea5b0/href">https://medium.com/media/a78c44f42b621bd24028ba6dbedea5b0/href</a></iframe><p>When the system is in the SysML state “Red_on,” and there is an error, and we have received the message “Safe_all_red,” indicating that all of the other lights at the intersection are red, it is unclear whether the system should change to the “Green_on” state or to the “Traffic_flow” state, as both transition functions are satisfied. This is an issue and potentially a significant safety concern because we do not want to turn the light green if we detect an error in the system.</p><p>The ambiguity can quickly be addressed by adding “error = false” to the “red_on_to_green_on” transition usage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/41f60fada88494f5f8b39fa651d871dc/href">https://medium.com/media/41f60fada88494f5f8b39fa651d871dc/href</a></iframe><p>Once we correct both issues in the transition functions, we can re-run the verify function, and it returns “Proved” and provides formal proof for review and confirmation by third-party proof checkers.</p><p>If you already saw these issues when reading the previous section. Good for you! The two bugs were in my SysML v2 model of the traffic light when I wrote it and converted it to IML. One of the reasons mistakes and bugs are so common is that systems are getting increasingly complex. Even this simple model of a traffic light has a surprising number of behavioral regions.</p><h3>Region Decomposition</h3><p>We can use Imandra Region Decomposition to create a map of any function in our IML model. The result is a collection of invariant regions that collectively represent the entire behavior of the function or system. IML has tools for executing the SysML v2 state machine, including a function for evaluating transitions, “transition sa.” “Transition sa” evaluates transition in every iteration of our state machine engine, checking if any transition expression returns true and, if so, changing the SysML v2 state. Below, we use Imandra Region Decomposition to provide a complete map of the function “transition sa.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MJgTOQHLtnm1nt2NJtqGGA.png" /></figure><p>For the traffic light state machine, the transitions represent 175 unique behavioral regions. Each one of these regions represents a collection of constraints that determine an invariant result. We represent the regions in an interactive Voronoi diagram to allow users to explore the state space. The diagram above highlights the region where the power is off, and the invariant result is that we are in the SysML Off_state. The other 174 regions are when the power is ON. Let’s look at two of them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rCFohlt9o_ExXT-xMsRWZQ.png" /></figure><p>This is one of the two regions we corrected. It is when the conditions for the transition “red_on_to_traffic_flow” are met. In the constraints, you will see that the power is “on” AND message is “Safe_all_red” AND there is an error. So, the SysML State is changed to “Traffic_flow.” It will then progress toward the “diagnostic” state.</p><p>Below is the other corrected transition, “traffic_flow_to_red_on,” where power is on AND no error AND no external message. In this region, the SysML State changes to “Red_on.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CR5O3DHvGCt_YgYufqeibw.png" /></figure><h3>Constraining Region Decomposition and Semantic Diffs</h3><p>Region decomposition is a powerful tool, but sometimes, you want to reduce the number of results by a specific outcome or condition. We can add helper functions to set conditions for the region decomposition. Here are three sets of constraints and the corresponding results.</p><p>The first function we use to set a condition is “sysml_state_red_on sa.” It returns a Boolean true if the SysML v2 State starts in “Red_on” and transitions to any other SysML v2 State.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/89d37eea4bf15d51cc5e55abe5ff2680/href">https://medium.com/media/89d37eea4bf15d51cc5e55abe5ff2680/href</a></iframe><p>The “sysml_state_red_on sa” function is applied as a condition using the syntax ~assuming:”sysml_state_red_on.” The result is a collection of 14 regions, where one region is a transition to “Off_state,” another region is a transition to “Green_on,” and the other 12 are different sets of constraints that change from “Red_on” to “Traffic_flow.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5s3jeCf6Xlx7QrVgHlWPDA.png" /></figure><p>Sometimes, you may want to see the path to a specific state, not the destinations from a starting state. This is called a preimage. We can easily generate a preimage using region decomposition. To do so, we will create another condition function. The “sysml_end_green_on sa” function returns a Boolean true if the system changes from any SysML v2 State to the SysML v2 State “Green_on.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d398a04388cce03a8345d2ca178789e2/href">https://medium.com/media/d398a04388cce03a8345d2ca178789e2/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l1RYplmHNNMLYE3aPpuStg.png" /></figure><p>This Region Decomposition returns only one region, which means that we have one, and only one, path that ends with the SysML v2 State “Green_on.” This result is exciting because we show no unexpected edge cases will lead to a “Green_on” state.</p><p>The final thing to look at is the Semantic Diff between the transitions before and after we corrected them. Here is the third and final function we will use as conditions for the Region Decomposition.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f32d198bb2df70b650343cd1f6755200/href">https://medium.com/media/f32d198bb2df70b650343cd1f6755200/href</a></iframe><p>“diff_t” returns a Boolean true if the transition functions have different results for a given “sa.” The notation of M3 and M2 refer to different modules, or namespaces, in IML. By declaring the function transition in different modules, they can have different definitions and behavior but the same name. We then use Region Decomposition on the function “M3.transition” with the condition that “M2.transition &lt;&gt; M3.transition”. The “M2.transition” is the collection of transition usages before we corrected the mistakes we found in the earlier section, and the “M3.transition” is after we made the corrections.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gp7PZ8lnBGXWdVLIn7lnfw.png" /></figure><p>This is a semantic diff between the two instances of the transition function. We see only two regions; one reflects the changes we made in the transition usage “red_on_to_traffic_flow,” and the other reflects the changes we made to “traffic_flow_to_operation_selection.”</p><h3>Conclusion</h3><p>We continue to make progress and expose more ways to leverage Imandra to reason about SysML v2 models. Determinism, reachability, dead-lock preventions, and semantic diffs are just examples of what is possible. I am excited to learn from you what type of analysis and reasoning would help your teams and customers be more successful systems engineers and stakeholders. We should have the first version of the SysML v2 to IML transpiler done in early 2024 and we would like to get access to your examples to test it. We also want to speak to SysML v1 end-users interested in automatic conversion to SysML v2. Please get in touch with me at <a href="mailto:Jamie@imandra.ai">Jamie@imandra.ai</a> or look for me at the next OMG members meeting.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cfc8fc60c8af" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/automated-reasoning-for-sysml-v2-part-3-cfc8fc60c8af">Automated Reasoning for SysML v2 Part 3</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[An Introduction to Imandra Markets]]></title>
            <link>https://medium.com/imandra/an-introduction-to-imandra-markets-d9b7419916f2?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/d9b7419916f2</guid>
            <category><![CDATA[financial-markets]]></category>
            <category><![CDATA[imandra]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[automated-reasoning]]></category>
            <dc:creator><![CDATA[Paul Brennan]]></dc:creator>
            <pubDate>Tue, 20 Feb 2024 16:11:33 GMT</pubDate>
            <atom:updated>2024-02-20T17:15:31.827Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p1phgAwmsertZ7HUruYDYg.jpeg" /></figure><p><em>First published on </em><a href="https://tabbforum.com/opinions/ai-and-digital-twins-improving-operational-resiliency-in-financial-markets/"><em>Tabb Forum</em></a><em>.</em></p><h3>Can AI and digital twins be used to solve problems with operational resiliency and unlock innovation in financial markets?</h3><p>Global financial markets are under pressure to unlock new revenue streams through innovation and, as highlighted in a recent <a href="https://www.iosco.org/library/pubdocs/pdf/IOSCOPD751.pdf">IOSCO consultation</a> on market outages, improve operational stability. Active innovation trends include ESG, digital assets, new data products, and the use of AI. However, challenges with system complexity, automation and organisational inefficiencies can drag innovation and profitability.</p><p>Is there a way that financial markets can leverage innovative ideas from other industries to accelerate their growth? For example, a recent <a href="https://www.mckinsey.com/capabilities/operations/our-insights/digital-twins-the-next-frontier-of-factory-optimization?utm_medium=DSMN8&amp;utm_source=LinkedIn&amp;utm_user=14419233765393582">McKinsey article</a> highlighted that “digital twins are revolutionising how decisions are made within factories, and forward-thinking manufacturers are getting ahead of the technology curve to drive efficiency”.</p><p>We discuss how the digital twin approach combined with AI can unlock innovation and increase operational resiliency within financial markets.</p><h3>Industry challenges with operational resilience</h3><p>As seen over recent years, major exchange outages have a market-wide impact. Industry participants still ask,<em> “Why does this still happen</em>?”. Regulators and market participants demand a greater level of operational resiliency.</p><p>Despite industrywide initiatives to improve financial market operational resilience, including attempts to agree on alternative closing price procedures, many root causes persist. The problem is that there is no silver bullet solution.</p><p>A recent IOSCO survey shared in their “<a href="https://www.iosco.org/library/pubdocs/pdf/IOSCOPD751.pdf">Consultation Report Market Outages</a>” published in December 2023 shows that software-related issues caused most market outages. Examples include failed software releases and invalid instructions. Reading the paper highlights the breadth of causes in the chart below, which indicates the scale of the challenge that exchanges face.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rq2LcPZkjf5j2Utocm3l2w.png" /><figcaption><em>Source: IOSCO consultation on market outages. “</em>Root causes of market outages on listing trading venues reported in the surveyed IOSCO jurisdictions between 2018 and 2022”.</figcaption></figure><h3>Innovation headwinds</h3><p>With operational resiliency in mind, one can imagine the challenges in delivering swift innovation. New products and features must be designed, specified, built and tested by different internal groups and communicated clearly to external stakeholders such as trading firms and regulators. The coordination required to align all interested parties is enormous, and the industry-wide expectation is to get it right the first time.</p><p>Consider the following examples of exchange upgrades:</p><ul><li>A new order type to accommodate a new asset class.</li><li>A system transformation project to replace a legacy platform.</li><li>A new fee type based on complex order interactions.</li><li>A matching logic change to introduce an anti-gaming mechanism.</li></ul><p>All market participants feel the impact of these upgrades, many of whom have to alter their trading systems to accommodate the change. The exchange has to change its public and private documentation, adjacent systems (post-trade, surveillance, regulatory reporting, etc.), coordinate data providers, have trading firms conformance test, the list goes on. In essence, the entire ecosystem has to understand and implement the change precisely.</p><p>It prompts the following questions for the exchange:</p><ul><li>How much due diligence is required?</li><li>Is the design correct?</li><li>How can we predict the impact throughout the entire platform?</li><li>How can we ensure the correct understanding across all stakeholders?</li><li>How is this tested, and have we identified defects before launch?</li></ul><p>From the project’s inception to final deployment and customer adoption, one perceived conflict is present: <strong><em>What is the trade-off between risk management and cost?</em></strong></p><p>There are also non-functional exchange upgrades, which are mandatory for numerous reasons, including information security, hardware replacements, firmware upgrades, and system performance. They each pose comparable challenges for the exchange.</p><h3>Roots of the problem</h3><p>Traditional product and software development practices can’t keep pace with the algorithm complexity we see in modern trading and exchange systems.</p><p><strong>Knowledge</strong></p><p>The knowledge and understanding of the exchange system are distributed across multiple teams and specialists. Documentation such as business requirements, rulebooks, and technical user guides are written manually using prose, tables, and worked examples, often under-specified and quickly outdated, leading to the following challenges with the system build:</p><ul><li>There is no way of ensuring that new requirements are logically consistent across the ecosystem.</li><li>Developers hope the business requirements have been accurately understood and captured.</li><li>Test programs lack formal measures against which they can assess their success.</li><li>Accountable stakeholders lack transparency, which exposes them to unbounded risks.</li></ul><p>In other words, there is no formal synchronisation between stakeholder understanding &amp; documentation and the exchange system.</p><p><strong>Identification of defects</strong></p><p>We know from Boehm’s Law and real-world experience that fixing bugs and glitches in production is exponentially more expensive than in the design phase. Yet, there is a lack of scientific techniques used within financial markets to identify defects and design flaws. Given the complexity of modern financial systems, is it possible to verify the impact of the new or upgraded system design before it is even built? And if not, identify defects earlier in the development lifecycle.</p><p>An interesting analogy is when a new bridge is built. The architects and civil and structural engineers communicate over an exact blueprint that describes the details of the bridge. Structural engineers apply laws of physics to the blueprint to see if it is structurally sound, even before the ground is broken. This up-front analysis at the design stage saves time, money and lives. This model-based design verification approach is commonplace in safety-critical industries and microprocessor design.</p><h3>The alternative solution — a digital twin</h3><p><strong>What is a digital twin?</strong></p><p><a href="https://www.ibm.com/topics/what-is-a-digital-twin">IBM</a> defines a digital twin as “<em>a virtual representation of an object or system that spans its lifecycle, is updated from real-time data, and uses simulation, machine learning and reasoning to help decision making</em>”.</p><p><strong>Digital twins for financial markets</strong></p><p>With so many stakeholders, the degree of complexity and the demand for rapid innovation, financial systems should transition from age-old, prose-based specifications to a precise design. Representing the financial system’s business logic in a digital executable model that one can interrogate allows exchanges to:</p><ul><li>Verify properties about the exchange system behaviour</li><li>Generate test cases to ensure the correctness of the exchange system</li><li>Run an audit against production trading data</li><li>Generate English-precise, prose documentation</li></ul><p>And in a more advanced state, machine learning and generative AI can be harnessed to:</p><ul><li>Create new revenue streams with innovative data products</li><li>Integrate large language models for ease of access</li></ul><p>Imandra is leading the way with this technology in financial markets. The following section describes how the “Imandra Markets” digital twin combined with Automated Reasoning tackles the abovementioned challenges by replacing age-old analogue processes with a digital approach.</p><h3>Imandra Markets — a digital twin powered by Automated Reasoning</h3><p><strong>The Imandra Markets Digital Twin</strong></p><p>Imandra’s patented technology and scientific advances of automated logical reasoning empower the creation of a fully functional and logically precise “digital twin” of any complex financial software system.</p><p>The Imandra Markets digital twin is positioned at the heart of the software and product development lifecycle. The golden source specification aligns and deepens stakeholders’ understanding digitally. It is dynamically connected to the test and production exchange environments through data synchronisation to ensure compliance and correct implementation. Imandra Markets offers unique “what-if” scenario analysis and new feature innovations, paving the way for predictive data products for trading firms’ systematic strategy calibration.</p><p>Trading venues benefit from the next generation of design verification, rigorous testing, resiliency, business intelligence and growth opportunities.</p><p><strong>Solving the knowledge gap</strong></p><p>Instead of relying on English-prose documents and long email chains, the exchange stakeholders can digitise the business logic design of the venue, allowing them to have one central and precise blueprint, just like the architects and structural engineers do when designing and building a new bridge.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_wdntrW_28WnBCV6Zz1ZAg.jpeg" /><figcaption><em>The Imandra Effect.</em></figcaption></figure><p>The Imandra Markets digital twin is more than just code. It is a living digital specification that systematically aligns all stakeholders to one golden source of exact truth. No time is wasted in decision-making and figuring out how the exchange system works. Knowledge is preserved across the entire team, and no information is lost.</p><p>A core tenet of the product is to ensure that all stakeholders can use it, not just those who can read the code. Imandra Markets has tools catering to all users and their specific areas of interest. Here are two examples:</p><ul><li>A compliance officer can directly tie the precise prose description of a system feature and usage statistics to the underlying system via the digital twin. Imandra Markets can generate comprehensive governance reports, making regulatory compliance and approvals simple and quick.</li><li>A product manager and business analyst can take a cut of the model and ‘play’ by simulating the impact of new designs. Imandra Markets will systematically assess the impact of new designs, which are displayed through powerful side-by-side orderbook replay tools. This “<em>what-if</em>” scenario analysis and impact assessment accelerates the creative thought process and unlocks innovation.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BiUKBOn86sDO-iGJ" /><figcaption><em>The Imandra System Auditor displays the precise prose description aligned with production usage statistics/ test results (Functional Predicates) and proven properties of the design (Verification Goals).</em></figcaption></figure><p><strong>Identifying defects and improving operational resilience</strong></p><p>Financial market systems are so complex humans struggle to understand the entire system’s behaviour, especially when relying on analogue specifications. There are simply too many edge cases to consider, keep track of and test. Breakthroughs in AI and mathematics allow us to model exchange rules and regulations precisely and apply rigorous logical AI to automate regulatory analysis and testing, all while providing logical audit trails.</p><p>Based on the digital design of the exchange or venue, Imandra Markets uses highly automated logical reasoning AI to ensure its compliance and correct implementation. There are two critical pillars to this:</p><ol><li><strong>System Verification — A Verified Design. </strong>By using AI-powered logical reasoning to verify system behaviour, Imandra Markets will identify exchange design defects upfront before the software development process begins, which isn’t possible in the analogue world of prose-based requirements and specifications.</li><li><strong>Systematic Validation — Automated Test Plan. </strong>Imandra can systematically analyse the digital twin to identify all possible behaviours and edge cases. This analysis, “symbolic reasoning”, is a form of generative AI underpinned by logical reasoning, which yields accurate results.</li></ol><p>This leads to high-coverage automated test generation, used to test the exchange system and identify functional and non-functional issues.</p><p><strong>The result is that exchanges benefit from a design and system defect identification step-change. The perceived trade-off between risk management and cost is broken.</strong></p><p>Imandra Markets unlocks the pace of software development, gives financial institutions confidence in their operational resilience, and proves that their system design will behave as intended.</p><p><strong>Business intelligence</strong></p><p>Trading firms are well versed in using order, trade and market data to help inform order routing decision-making, for example, TCA. They aim to understand better the intersection between (i) the exchange features and (ii) the statistical liquidity profile to calibrate their strategy to meet their trading objectives.</p><p>Imandra Markets gives the exchange operators a unique tool to analyse their customers’ interaction with the trading system by running many ‘what-if’ scenarios to create an actionable intelligence data product to help customers optimise the whole potential of the exchange system and its features.</p><p>Timing a racing car on a race track offers an interesting analogy. The crucial performance measurement visible to the outside world is the one against the stopwatch, the lap time. However, the car’s potential is only visible to the racing team. The team’s engineers can analyse the driver using internal telemetry to see if they fully exploit the car’s capability to make it go faster.</p><p>Imandra Markets gives exchanges the tools to help trading firms realise the unused potential of the exchange system.</p><h3>The takeaway</h3><p><strong><em>If there is one takeaway from reading this article, it is that a better way of managing complex financial systems exists.</em></strong></p><p>By taking inspiration from safety-critical industries, companies can move away from traditional analogue processes by using digital twins to manage their systems and reap the benefits.</p><p>The Imandra Markets digital twins offer a leap forward in exchange system resiliency, accelerate system development and open up avenues for business intelligence and new revenue streams.</p><p><strong>Exchanges have already begun using this approach. Platforms in Europe collectively responsible for one-quarter of the equity market volume actively use this method.</strong></p><p>With industry-wide focus and mandates to explore the use of AI, Automated Reasoning is under the spotlight, and when combined with the digital twin approach, it can be game-changing.</p><p><strong>About the author</strong></p><p><em>Paul Brennan is the Chief Strategy Officer and Head of Growth for Financial Markets at Imandra, Inc.</em></p><p><em>Before working at Imandra, Paul spent 15 years at Goldman Sachs in numerous roles, most notably as the Chief Operating Officer of the European trading venue SIGMA X MTF.</em></p><p><em>Paul has deep expertise in managing regulated businesses based on complex mission-critical technology, including steering them through business and technology transformations.</em></p><p><em>As an early adopter, Paul became the first customer of Imandra and demonstrated the value of Automated Reasoning AI within Financial Markets.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d9b7419916f2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/an-introduction-to-imandra-markets-d9b7419916f2">An Introduction to Imandra Markets</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automated Reasoning for SysML v2 Part 2]]></title>
            <link>https://medium.com/imandra/automated-reasoning-for-sysml-v2-part-2-7afd4fbd549c?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/7afd4fbd549c</guid>
            <category><![CDATA[imandra]]></category>
            <category><![CDATA[automated-reasoning]]></category>
            <category><![CDATA[sysml]]></category>
            <category><![CDATA[formal-verification]]></category>
            <category><![CDATA[safety-critical]]></category>
            <dc:creator><![CDATA[Jamie Smith]]></dc:creator>
            <pubDate>Thu, 02 Nov 2023 21:51:19 GMT</pubDate>
            <atom:updated>2023-11-10T18:48:17.591Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*rUX9uSi387856BDY.png" /></figure><h3>Formally Verifying SysML v2 Constraints with Imandra</h3><p><em>I previously published an article entitled </em><a href="https://medium.com/imandra/automated-reasoning-for-sysml-v2-ad7e87addba8"><em>“Automated Reasoning for SysML v2.”</em></a><em> The article outlined how the Imandra Automated Reasoning enables complex reasoning about the design and formal verification of properties and behaviors. The example we shared in that article demonstrated formal verification of three verification goals for a traffic light designed using the pilot implementation of Systems Modeling Language version 2 (SysML v2.):</em></p><p><em>1.</em> <em>The behavior is deterministic, regardless of state or parameter values.</em></p><p><em>2.</em> <em>Errors are handled properly, irrespective of state or parameter values.</em></p><p><em>3.</em> <em>The light only switches to green when it is safe (all other lights in the system are red), irrespective of state or parameter values.</em></p><p><em>The article focused on the second verification goal, “Errors are handled properly.” Several systems engineers and developers working on SysML v2 read the article or reviewed my example and provided encouragement and positive feedback. They also had some great ideas of what I should do next. More than one person recommended that we synthesize verification goals directly from SysML v2 requirements and constraints. Again, I am excited to share my progress over the past couple of weeks.</em></p><h3>Using Imandra Automated Reasoning with SysML v2</h3><p>SysML v2 is a general-purpose modeling language for complex system design developed by the Object Management Group (OMG) as an open standard. The OMG released the beta implementation in July of 2023, and I expect the final release to be next summer. For more information on the SysML v2 release schedule, go to <a href="http://www.omg.org">omg.org</a>.</p><p>I am excited about SysML v2 for several reasons. After working with SysML v2 for the last few months, I have found it easy to learn. Unlike SysML v1, the language is consistent across all the modeling elements, such as parts, ports, connections, states, and so on. I am perhaps most excited about SysML v2’s formal semantics and the opportunities this creates. Formal semantics will ensure that there is a single representation of a SysML v2 model that contains all of its elements and relationships. The SysML v2 formal semantics will allow us to cleanly <em>transpile</em> SysML v2 models to the Imandra Modeling Language (IML). Once we have converted a SysML v2 design, we can formally verify the design against requirements, constraints and other verification goals. I am confident with the approach we are taking that system engineers can define verification goals natively in SysML v2 and still take advantage of Imandra’s automated reasoning tools without having to learn a second modeling language.</p><p>To demonstrate how Imandra can verify a system designed using SysML v2 we modeled a traffic light using SysML v2 and then converted it to IML. This manual conversion has helped create a guide and roadmap for the automatic SysML v2 to IML conversion tool (a “<em>transpiler</em>”). The SysML v2 traffic light model is based on the standard green, yellow and red traffic light used in the United States. We chose the traffic light because it is complicated enough to demonstrate some key capabilities of both SysML v2 and Imandra, while not being so complex that it would be difficult to understand. In the last article we focused on the behavior of the traffic light. This article will focus on two SysML v2 constraints. We will convert both constraints to formal verification goals in Imandra. Reviewing my first article, <a href="https://medium.com/imandra/automated-reasoning-for-sysml-v2-ad7e87addba8">“Automated Reasoning for SysML v2,”</a> may be useful before continuing.</p><h3>Constraints and Verification Goals</h3><p>SysML v2 models consist of elements and their relationships. One specific type of SysML v2 element is a constraint. A constraint has inputs and always returns a Boolean value. If the Boolean value returned is true, then the constraint is met, if it is false, then the constraint is not met. The input values to the constraint are not limited, except that they must be resolved through calculations and logic to a single Boolean result.</p><p>Formal requirements in SysML v2 are a special type of constraint, so for this article and example, you can think of constraints and requirements as interchangeable terms. The techniques and approaches we cover here will apply to both constraints and formal requirements.</p><p>Imandra Automated Reasoning has many capabilities, one of which is formal verification. With Imandra, we can mathematically prove the properties of a system using the Imandra verify function. The verify function leverages a variety of solvers and techniques behind the scenes to verify (or prove) that any function or expression is true. If Imandra is unable to prove that an expression or function is true for all parameters and states of the system, Imandra will provide a counterexample illustrating a case where the expression or function does not hold as true.</p><p>Constraints in SysML v2 map directly to Imandra verification goals. Both SysML v2 constraints and Imandra verification goals are very general and are defined as a function or expression that takes any number of parameters as inputs and returns a Boolean result. I am going to share two examples of SysML v2 constraints mapped to Imandra verification goals. One is trivial, and the second is significantly more complex.</p><h3>Mapping Traffic Light Constraints to Verification Goals</h3><p>In my prior article, we verified three behavioral properties of the traffic light:</p><p>1. The behavior is deterministic, regardless of state or parameter values.</p><p>2. Errors are handled properly, irrespective of state or parameter values.</p><p>3. The light only switches to green when it is safe (all other lights in the system are red), irrespective of state or parameter value.</p><p>In this article, we are going to verify weight and power usage against a limit. Verifying weight for a single system design without variation parts or parts that have fluctuating weight is a trivial task, and there are tools and examples in the SysML v2 language of how to create and resolve these types of constraints. Evaluating power consumption is significantly more complicated, automated reasoning is required to ensure that the power consumption requirement is met across all operational states and parameters.</p><p>Let us start with the weight constraint and verification goal. Below we have a SysML v2 constraint definition and constraint usage. The constraint definition for the traffic-light weight is as follows.</p><pre>   constraint def MassConstraint {<br>        in partMasses : MassValue[0..*];<br>        in massLimit : MassValue;<br>            sum(partMasses) &lt;= massLimit<br>            }</pre><p>This constraint definition takes two inputs: a list of MassValues and a massLimit. The constraint definition returns a Boolean result based on a predicate defined as the sum of all the MassValues to be less than or equal to the massLimit. We then create an Assert Constraint usage based on this definition.</p><pre>Assert constraint massConstraint : MassConstraint {        <br>        in partMasses = <br>            (light_housing.totalMass, <br>            tl_controller.mass,<br>            tl_counter.mass, <br>            tl_radio.mass);<br>         in massLimit = 25.0 [kg];<br>  }</pre><p>There are several other ways to define this list of masses. In the SysML v2 examples, there is a concept of a “mass rollup.” We used that approach here, and the whole system mass includes the “<em>totalMass</em>” for the light_housing that includes: the mass of the three lights (green, yellow, red), 3 inserts, the electronics housing, and light housing itself. The light_housing.totalMass plus the mass for the controller, counter, and radio give us the whole system mass tied to this constraint. The controller, counter, and radio are not in the <em>light_housing&gt;totalMass</em> because they are not part of the light_housing part hierarchy. This example is an asserted constraint. In SysML v2 the difference between a constraint and an asserted constraint is that asserted constraints always must return true and often have a stakeholder (like the customer) tied to them.</p><p>Resolving this constraint is trivial. All that is needed is to sum the 4 mass values and compare them to the mass limit. We are going to use this simple example to illustrate how SysML v2 constraints map to Imandra’s verification goals. Here is the simple mass verification goal in Imandra.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d87c84bc60450f59a699172bcf7cc9c8/href">https://medium.com/media/d87c84bc60450f59a699172bcf7cc9c8/href</a></iframe><p>We synthesized the part_masses function by crawling the part hierarchy and identifying all the massed parts. We include the part masses and the multiplicity. The multiplicity is the number of parts of a specific kind, often, it is 1, but not always. In the case of the “traffic_light” model the light_insert has a multiplicity of 3, one light_insert for each light. We need to multiply the part mass by the multiplicity to get the total mass.</p><p>The “sa” variable includes all the attributes for the system and the SysML v2 state. The “sa” variable provides a complete view of the entire system at a point in time.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/59e53679b202002b32c44b91f10c4e01/href">https://medium.com/media/59e53679b202002b32c44b91f10c4e01/href</a></iframe><p>This trivial example shows the mapping from SysML v2 to IML. The verify function explores the entire state space of the system and proves that the goal is met for all undefined variables and values. This verification goal returns Proved.</p><h3>Formally Verifying a Power Consumption Constraint</h3><p>We now will apply these techniques to a much more complicated example: power consumption. Power consumption, or power usage, is more complex because power consumption changes during operation. The power constraint seems simple enough and is very similar to the mass constraint definition.</p><pre>    constraint def PowerConstraint {<br>        in partPowers : PowerValue[0..*];<br>        in powerLimit : PowerValue;<br>            sum(partPowers) &lt;= powerLimit<br>            }<br><br>    assert constraint powerConstraint : PowerConstraint {<br>            in partPower = (light_housing.light_inserts.green_light.power_usage,<br>                            light_housing.light_inserts.yellow_light.power_usage,<br>                            light_housing.light_inserts.red_light.power_usage,<br>                            light_housing.light_electronic_housing.power_usage,<br>                            traffic_light_controller.power_usage,<br>                            traffic_light_counter.power_usage,<br>                            traffic_light_radio.power_usage);                                       <br>            in powerLimit = 19.0 [W];</pre><p>The lights and the radio have a variable power determined by their individual states. In SysML v2 we represented these variable power values as a calculation.</p><pre>    calc def Light_power { in light_state : Light_state;<br>           return : PowerValue = <br>                     if light_state == Light_state::off ? 0 [W]<br>                        else if light_state == Light_state::lit ? 12 [W]<br>                        else if light_state == Light_state::blinking ? 8 [W]<br>                        else 0 [W];<br>           }<br>           <br>    calc def Radio_power { in radio_on : Boolean;<br>           return : PowerValue = <br>                     if radio_on ? 4 [W]<br>                     else 1 [W];<br>           }</pre><p>When a light is Lit, it consumes 12 Watts, when it is Blinking, it consumes 8 Watts, and when it is Off, it consumes 0 Watts. The radio consumes 4 Watts when it is transmitting and 1 Watt when it is just listening. Summing up the power of all the components “on” you would get ~43W, well above the constraint.</p><p>To verify PowerConstraint, we need to calculate power during operation across all states, all paths, and all possible situations. There are 12 SysML states for the traffic_light system and 45 attributes; you can see the state map below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*en4EuFZUB6_PPm_rDb3ttA.png" /></figure><p>It is a more complex process, so we intend to automate most steps and provide high-level configuration tools native to or similar in style and workflow to popular MBSE tools to enable Systems Engineers to efficiently perform these types of inductive formal proofs.</p><p>Here are the six steps we follow to formally prove that the power never exceeds 19W:</p><p>1. Create a definition for “Good States” for the system.</p><p>2. Create a lemma that verifies, that if you start in a “Good State” you will end up in a “Good State” after executing the state machine once.</p><p>3. Use the lemma from step 2 to create another lemma that verifies that if you start in a “Good State” you will remain in a “Good State” after executing the state machine an arbitrary number of times using any parameters or values.</p><p>4. Define an initial starting point for the system and verify that it is a “Good State”. We used the “Off_state” just after the power was turned on. Now we know that if the system starts from the “Off_state” it remains in a “Good State regardless of how long the state machine runs and over any values and parameters.</p><p>5. Create a lemma that proves the power is &lt;=19 Watts for all “Good States”.</p><p>6. Use lemmas created in steps 2–5 to verify that if we start from the “Off_state” the system will never exceed 19W regardless of how long it runs and over any values and parameters. Steps 2–6 will be completely automated, so the user will just need to define the “Good States” for the system, which we will make easy through tooling.</p><h3>Step 1. Define “Good States”</h3><p>The complete system state is determined by the “SysML_State” and a collection of SysML attributes. The SysML states are shown in the diagram above, and here is the list of the attributes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cfafe43c1facf66dc4b3904fec1cb240/href">https://medium.com/media/cfafe43c1facf66dc4b3904fec1cb240/href</a></iframe><p>The system has 45 attributes in all.</p><p>We combine the SysML State and the Attributes into a single variable “sa” of type “sysml_state_attributes”. We then define 12 “Good States” by constraining 8 of these 45 attribute values.</p><p>In the “Good States” we confirm the following:</p><p>· Power is ON</p><p>· The lights are on and off during the appropriate SysML states. Ex. Green Light is “Lit” in the “Green_on” SysML_state and the other lights are “Off”</p><p>You will see below that each of the lights is represented twice, once in the controller and once as the light itself. We also define a “Good State” when every light is “Off”, but power is ON.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b94976f69043ec6236834ce641094eea/href">https://medium.com/media/b94976f69043ec6236834ce641094eea/href</a></iframe><h3>Step 2. Prove if you start in a “Good State” you remain in a “Good State” after one step of the state machine.</h3><p>We used this function “good_tl_states” along with the function we developed to execute the state machine to create our first lemma. The “my_step” function executes the state_machine once. It takes a “sa” and returns a new “sa” based on external messages and values, passed as the variable “ex_m”.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8e954df9d980fcab1626f68451f3235e/href">https://medium.com/media/8e954df9d980fcab1626f68451f3235e/href</a></iframe><p>The external message “ex_m” is made up of these six variables.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7502937d7567ba83e0bbde7e9a96ce24/href">https://medium.com/media/7502937d7567ba83e0bbde7e9a96ce24/href</a></iframe><p>The first lemma “step_good_invariant” proves that if we start in one of our 12 “Good States” we will be in one of the 12 “Good States” after the state machine executes once. We will then use this lemma “step_good_invariant” in the creation of future lemmas.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/942/1*KfnJyjZMisKSkkXl1TbAoA.png" /><figcaption>lemma number 1</figcaption></figure><h3>Step 3. Prove if you start in a “Good State” the system remains in a “Good State” after n steps of the state machine.</h3><p>Now we create a second lemma that uses the first lemma to verify that if we start in one of our 12 “Good States” we will be in one of the 12 “Good States” after executing an arbitrary list of “external_messages” of an arbitrary length. So no matter how long the state machine runs, we will be in a “Good State”.</p><p>The function below “run_my_step” is a recursive function that runs “my_step” once for every element in the list of “external_messages” “ms”. For the lemma, “run_good_invariant” we do not specify the values or the length of the list of “external_messages” to instruct Imandra to prove that if we start in a “Good State” we end in a “Good State” for an arbitrary list of “external_messages” of an arbitrary length.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2eea91d97d7d524ca065d22dad018043/href">https://medium.com/media/2eea91d97d7d524ca065d22dad018043/href</a></iframe><p>This lemma “run_good_invariant” also comes back as proved, and we can use it in future proofs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/703/1*gDzODPWD3ObggXakdb2TxQ.png" /><figcaption>lemma number 2, built using lemma number 1</figcaption></figure><h3>Step 4. Define an initial state and prove the system remains in a “Good State” after n steps in the state machine.</h3><p>Next, we defined an initial system state that is in the “SysML_state” = “Off_state” and all the lights are OFF and the Power is ON.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/937/1*FmZ4Xx8vW2naetT_Zr4U4Q.png" /><figcaption>Initial System State, SysML_state = Off_state, Power = true, and everything else is Off.</figcaption></figure><p>Then we create a third lemma that proves that if we start from the initial SysML state Off_state we will remain in a “Good State” for an arbitrary list of “external_messages” of an arbitrary length.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/936/1*zAIiUWgq1QZgh4JyESunqg.png" /><figcaption>Lemma 3 proves that the system is always in a “Good State” from power on.</figcaption></figure><h3>Step 5. Prove the power constraint is met for all “Good States”</h3><p>Now we bring in the power calculations. We created a function based on the SysML v2 constraint and used it in our next lemma. The function is “sum_total_power_states” and can be seen below.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d69c96070127a2517a92ea93abbbc018/href">https://medium.com/media/d69c96070127a2517a92ea93abbbc018/href</a></iframe><p>We use the above function to prove that the power is &lt;19W for all “Good States”.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f5c6b455e0fffc698696959381a569d4/href">https://medium.com/media/f5c6b455e0fffc698696959381a569d4/href</a></iframe><h3>Step 6. Prove the power constraint is always met.</h3><p>It returns proved, and now we are ready for the final step. We use all four of the previously proved lemmas to prove that the power consumption is &lt;19W for an arbitrary list of “external_messages” of an arbitrary length starting from turning the power on.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d1129059023900fd720567477d3b7867/href">https://medium.com/media/d1129059023900fd720567477d3b7867/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/936/1*l9zLQJWxalQM3MB4yfcR2Q.png" /></figure><p>Theorem proved! The SysML v2 power constraint is true for every possible system state and attribute value.</p><h3>Conclusion</h3><p>I am excited about our progress, but we are just getting started. The approach outlined in this article can be done manually today to verify your own SysML v2 models, but we are working to make that process much easier. Here is a short list of work underway:</p><p>· Creating a <em>transpiler</em> to use the SysML v2 API to export SysML v2 models as JSON and automatically convert them to IML.</p><p>· Developing tooling and automation to accelerate formal verification of SysML v2 constraints.</p><p>· Integrating Imandra Automated Reasoning Engine with Large Language Models to enable a hallucination-free natural language interface for reasoning about your SysML v2 models, creating verification goals, and synthesizing SysML v2.</p><p>To discuss this work, please contact me at <a href="mailto:Jamie@imandra.ai">Jamie@imandra.ai</a>, or look for me at the next OMG members meeting.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7afd4fbd549c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/automated-reasoning-for-sysml-v2-part-2-7afd4fbd549c">Automated Reasoning for SysML v2 Part 2</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automated Reasoning for SysML v2]]></title>
            <link>https://medium.com/imandra/automated-reasoning-for-sysml-v2-ad7e87addba8?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/ad7e87addba8</guid>
            <category><![CDATA[formal-verification]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[automated-reasoning]]></category>
            <category><![CDATA[sysml]]></category>
            <dc:creator><![CDATA[Jamie Smith]]></dc:creator>
            <pubDate>Mon, 07 Aug 2023 03:55:34 GMT</pubDate>
            <atom:updated>2024-09-30T18:54:33.750Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0WPza523n9IeBx4oMeP47Q.png" /></figure><p><em>Version 2 of SysML represents a significant advance over version 1 and is aimed to address numerous shortcomings and limitations of applicability to many types of modern systems engineering challenges (</em><a href="https://www.knowgravity.com/the-limits-of-sysml-v1-and-how-sysml-v2-addresses-them"><em>see this for example</em></a><em>). Unlike v1, which was based on UML, v2 is based on KerML, a modeling language with formal semantics. It is this foundation of v2 that paves way to applying the latest advances in automated reasoning to empower SysML v2 users with rigorous analysis (including formal verification) of their models, AI-powered testing, integration with LLMs (e.g. ChatGPT) and much more.</em></p><p><em>I am excited to share some of the work we have been doing over the last several weeks on integrating our reasoning engine, Imandra, with the SysML v2 pilot implementation. Our initial focus is on creating an automated translation process between SysML v2 and Imandra Modeling Language (IML), and then using Imandra to reason about the resulting IML models.</em></p><p><em>In this article, we’ll cover an example of a traffic light model and verify some of its properties:</em></p><p><em>1. The behavior is deterministic, regardless of state or parameter values.</em></p><p><em>2. Errors are handled properly, irrespective of state or parameter values.</em></p><p><em>3. The light only switches to green when it is safe (all other lights in the system are red), irrespective of state or parameter values.</em></p><h3>SysML v2 will empower the next step in MBSE evolution</h3><p>Increasing system complexity is driving a fundamental shift from document-based systems engineering to model-based systems engineering (MBSE) and sophisticated model-based systems engineering tools. While MBSE has its roots in the aerospace and defense industries, we see MBSE gaining popularity across all sectors. Systems Modeling Language or SysML has been closely linked with MBSE since the first version of the standard was published by the Object Management Group (OMG) in 2006. SysML provides a standard graphical format to model complex systems’ requirements, architecture, and design and has been adopted by most systems design tools. SysML has helped teams share and communicate their designs with collaborators and customers during the design process.</p><p>The OMG is taking MBSE to the next stage in its evolution. At the Q2 2023 OMG members’ meeting, the SysML steering committee shared their plans for SysML v2 during a hybrid webinar hosted in Orlando, FL. Check out their <a href="https://www.youtube.com/watch?v=A6pEpNj6qkg">video</a> for a great overview.</p><p>The team, comprised of representatives from over 50 organizations, has leveraged 15 years of learning to define SysML v2. SysML v2 will enable increased precision, usability, and interoperability and allow the next generation of systems engineers and the next generation of systems.</p><p>While SysML v1 was based on UML, SysML v2 was built on Kernel Modeling Language (KerML). KerML is designed to be extensible and with formal semantics. Formal semantics brings mathematical precision to enable rigorous analysis and verification. We have begun leveraging the formalism of SysML v2 to analyze designs with Imandra. With Imandra, we can reason about the design to determine whether it will meet the desired requirements. We can also mathematically prove that its behavior is correct and safe by using formal verification techniques. To illustrate what we can accomplish, let’s review a system design we recently created for a simple traffic light.</p><p>We created this traffic light design with the SysML v2 pilot implementation in a Jupyter Labs development environment. The SysML v2 pilot implementation is freely available and can be found here. <a href="https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation">https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation</a></p><h3>The SysML v2 Traffic Light Model</h3><p>The SysML v2 traffic light model is based on standard green, yellow, and red traffic lights used in the United States. We chose the traffic light because it is complicated enough to demonstrate some key capabilities of both SysML v2 and Imandra while not being so complex that it would be difficult to understand. The complete SysML v2 model is available at the end of this blog post in Appendix A.</p><p>The model is contained in one package and includes 7 parts, 5 ports, 2 interfaces, 16 actions, and 12 states. Review the SysML v2 overview video linked above if these terms are new to you. We have also created an equivalent version of this model in IML to allow us to analyze the system’s behavior and verify properties.</p><p>We use Imandra to verify the behavior of the Traffic Light and uncover errors in the states and state transitions. Below is the SysML v2 state diagram, which is a graphical representation of the behavior of the traffic light. Let’s walk through it.</p><p>The traffic light starts in the “off” state, and once it receives power, it will enter the “init” state. During the “init” state, a power-up test is run, and if the outcome of that test returns an error; the traffic light will enter “diagnostic,” where additional tests will be run, and the error status will be communicated wirelessly to operators. Then the traffic light advances to “red_blinking” and operates as a stop sign until a technician can correct the error. If the power test returns no error, the light is ready to enter one of its three operational modes: “traffic_flow,” “caution,” or “stop_sign.” A collection of states, actions, and transitions dictates the behavior of the light.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*en4EuFZUB6_PPm_rDb3ttA.png" /></figure><p>Here are some examples of the SysML v2 for states and their equivalents in IML. Each state has a name and perhaps a series of actions. The actions are classified as entry, then, do, or exit. When the system enters the state, you perform the “entry action,” followed by the “then actions” and then the “do action.” The “do action” is executed until the state is exited. When the system exits a state, it performs the “exit action.”</p><p>The code snippets below show that both the SysML v2 and the IML contain the same state names and corresponding actions.</p><p>SysML v2 states</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8ff8e84b1e5fe8b35e2b8e9f891721ec/href">https://medium.com/media/8ff8e84b1e5fe8b35e2b8e9f891721ec/href</a></iframe><p>IML equivalent states</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ec532687c0e51b31f0d85d919db63ab2/href">https://medium.com/media/ec532687c0e51b31f0d85d919db63ab2/href</a></iframe><p>We have created equivalent data types of all the elements in SysML v2 in IML. Let’s take a look at transitions.</p><p>SysML v2 Transition Usages:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d394b79ecead8c1e741b94325277a754/href">https://medium.com/media/d394b79ecead8c1e741b94325277a754/href</a></iframe><p>IML equivalent transitions:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b34e592ae742a75da7a5e528ce42a152/href">https://medium.com/media/b34e592ae742a75da7a5e528ce42a152/href</a></iframe><p>Each transition has an initial state and a subsequent state, along with a Boolean expression to determine if the transition is valid. For example, look at the transition from “init_to_operation_selection.”</p><p>first state = “init”</p><p>Boolean expression “error = false”</p><p>Next state = “operation_selection”</p><p>So, if we are in the “init” state and the error is false, we move to the “operation_selection” state.</p><p>Finally, look at the actions in both SysML v2 and IML syntax.</p><p>SysML v2 actions</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff7fd827b24a1d8c62345df685eff441/href">https://medium.com/media/ff7fd827b24a1d8c62345df685eff441/href</a></iframe><p>IML equivalent actions</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5c36245026bd614f29f4b64ba8bf5047/href">https://medium.com/media/5c36245026bd614f29f4b64ba8bf5047/href</a></iframe><p>These action functions, include, but are not limited to, turning the lights on and off, managing errors, and reading sensors and clocks.</p><h3>Verification of the Traffic Light</h3><p>As I mentioned above, we used Imandra to verify three properties of the traffic light.</p><p>1. The behavior is deterministic, regardless of state or parameter values.</p><p>2. Errors are handled properly, irrespective of state or parameter values.</p><p>3. The light only switches to green when it is safe (all other lights in the system are red), regardless of state or parameter values.</p><p>We will take a deeper look at verification goal number two, verifying that errors are correctly handled. You can explore the notebook to learn more about the other two verification goals.</p><p>The collection of the state, actions, and transition functions dictate the behavior of the traffic light. We used the IML data types for each of those, along with attributes, to explore and verify the behavior of the traffic light. In practice, we created a data type that included the SysML_states AND the relevant attributes.</p><p>The attributes are listed here:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f67b37cfc1632f2a95e87bd40fa66cf5/href">https://medium.com/media/f67b37cfc1632f2a95e87bd40fa66cf5/href</a></iframe><p>We combined those attributes with the SysML_state. The SysML_states are the 12 states shown in the state diagram above: off, init, diagnostic, operation_selection, stop_sign, caution, traffic_flow, red_blinking, yellow_blinking, green_on, yellow_on, and red_on. Into a single type called `sysml_state_attributes’:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/36d3efe8dd9d95ca9b4036bdd42081d8/href">https://medium.com/media/36d3efe8dd9d95ca9b4036bdd42081d8/href</a></iframe><p>In our IML model, we declared a variable called ‘sa’ of type <strong><em>sysml_state_attributes</em></strong> to represent the larger state of the traffic light. We use this data type along with the transition functions and actions to determine the behavior of the traffic light. We define a function called “my_step” that, when called, does five things.</p><p>1. Increments the system clock by 1 second.</p><p>2. Runs any “do action” for the current SysML_state</p><p>3. Checks the transition functions to see if the SysML_state should change</p><p>4. If the SysML_state changes (a. it runs the “exit action” of the current SysML_state, b. it runs the “entry action,” “do action,” and “then actions” of the new SysML_state)</p><p>5. Returns a variable of type “sysml_state_attributes.”</p><p>Here is “my_step” written in IML.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/00185ad224500f3dc709cf5d0d1a6f1b/href">https://medium.com/media/00185ad224500f3dc709cf5d0d1a6f1b/href</a></iframe><p>We use the function “my_step” and the variable “sa” along with Imandra Automated Reasoning to verify the behavior of the traffic light. You may have noticed another parameter called “ex_m:external_messages.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b3f259fd51413e5230aa1cdda98591a/href">https://medium.com/media/5b3f259fd51413e5230aa1cdda98591a/href</a></iframe><p>External_messages include the incoming control messages and health information about the system. You can think of this as the information external to the traffic light controller that must be received, measured, and evaluated to determine SysML_state transitions.</p><p>Imandra has an internal function called “verify” that evaluates a Boolean function and either returns <strong>Proved</strong>.</p><p>Confirming that the Boolean function is always true for all variables and parameters or it returns <strong>Refuted</strong>.</p><p>If “verify” returns refuted, it includes a counter-examples that provides a set of parameters resulting in the Boolean function being false. This is incredibly powerful and saves hours or days of debugging and attempting to find the conditions that caused your verification goal to fail.</p><p>For our traffic light example, we designed our system such that if an error occurs, the system will transition SysML_states moving SysML_state to SysML_state until it reaches “diagnostic” and then “red_blinking.” We are going to define a verification goal to prove that the system exhibits this behavior. To help us, we defined a couple of “helper functions” to create this verification goal.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bc80139ababa02786271d4c846d94bd2/href">https://medium.com/media/bc80139ababa02786271d4c846d94bd2/href</a></iframe><p>Get_sysml_state returns the SysML_state from “sa.”</p><p>State_changed takes a variable “sa” and “ex_m” and determines if the SysML_state will change if “my_step” is called. The &lt;&gt; symbol is not equal in Ocaml and IML.</p><p>So, we use these functions to create our verification goal. Verify that if Error_state, then state_changed is always true, meaning the SysML_state always changes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/92a46b59d9f4d01f44616a06233f935f/href">https://medium.com/media/92a46b59d9f4d01f44616a06233f935f/href</a></iframe><p>The verify function evaluates all parameters and attributes across all the SysML_states algebraically, <strong>NOT</strong> statistically, or through random sampling. If verify returns Proved, we know the Boolean function is true for all parameters and attributes.</p><p>Unfortunately, when we called this verify function, it returned Refuted with a counter-example. The counter-example pointed out that if the system is in the “off” SysML_state and with the power off, it will not change SysML_states. Of course, that makes perfect sense, so we modified our verify function to ensure we only consider when the power is on.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cf4b21debd3562a486d94a5612deecd6/href">https://medium.com/media/cf4b21debd3562a486d94a5612deecd6/href</a></iframe><p>So now we have two constraints: Power is on &amp; we are in an Error_state. When we ran this function, we again received Refuted. When we dig into the counter-example, we see that the SysML_state = “Yellow_on” AND the clock has stopped.</p><p>We can review the behavior of the “Yellow_on” SysML state. The state “Yellow_on” only happens after “Green_on” and the system stays in the “Yellow_on” state for 2 seconds before transitioning to “Red_on.” In the case where the clock has stopped, the system will never transition from “Yellow_on” because, without a clock, the controller will never register 2 seconds elapsing. We also notice that the system never checks the status of the system, including checking for errors. To correct this issue, we must add a “do action,” like most other states, to check for errors explicitly. Below you can see where we added the “do action” for the “Yellow_on” SysML_state along with a comment.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1388392e0e2e4defc453397682daa27f/href">https://medium.com/media/1388392e0e2e4defc453397682daa27f/href</a></iframe><p>By adding the “do action,” the “yellow_on_to_red_on” transition function will evaluate to true if an error is detected OR if 2 seconds elapse.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/44bbd9f38522e8c3f35a06743b4d286e/href">https://medium.com/media/44bbd9f38522e8c3f35a06743b4d286e/href</a></iframe><p>After identifying this issue with the design, and correcting it, we re-ran the verify function, and it returned <strong>Proved.</strong></p><p>This process helped us identify two significant issues with the design that could easily have existed in the production code and appeared intermittently, where some traffic lights have a perpetually “yellow_on” light. Not only would this be costly to maintain in the field, but it also would be a significant safety concern, as perpetual “yellow_on” is not compliant with US traffic laws. Motorists would handle this case in unpredictable ways.</p><h3>Conclusion</h3><p>I am excited about our progress, but we are just getting started. Today you can use the approach outlined in the blog post and verify your own SysML v2 models, but we are working to make that process much easier. We are building tooling to use the SysML v2 API to export SysML v2 models as JSON and automatically convert them to IML. We also plan to leverage our integration of the Large Language Models to enable a natural language interface for creating verification goals and synthesizing SysML v2 that is correct. To discuss this work, please get in touch with me at <a href="mailto:Jamie@imandra.ai">Jamie@imandra.ai</a>, or look for me at the next OMG members meeting.</p><h3>Appendix A: Traffic Light SysML v2</h3><pre>package Traffic_light {<br>    <br>    import ISQ::*;<br>    import ISQBase::*;<br>    import Time::*;<br>    import ScalarValues::*;<br>    import StringFunctions::*;<br>    import NumericalFunctions::*;<br>    import ScalarValues::*;<br>    import SI::*;<br>    <br>    port def Can_bus;<br>    port def Clock;<br>    port def Vehicle_sensor_port;<br>    port def Comm;<br>    <br>    enum def Light_state {<br>                lit;<br>                off;<br>                blinking;<br>                }<br>                      <br>    calc def Light_power { in light_state : Light_state;<br>           return : PowerValue = <br>                     if light_state == Light_state::off ? 0 [W]<br>                        else if light_state == Light_state::lit ? 12 [W]<br>                        else if light_state == Light_state::blinking ? 8 [W]<br>                        else 0 [W];<br>           }<br>           <br>    calc def Radio_power { in radio_on : Boolean;<br>           return : PowerValue = <br>                     if radio_on ? 4 [W]<br>                     else 0 [W];<br>           }<br>    calc light_power : Light_power;<br>    calc radio_power : Radio_power;<br>    <br>    <br>    constraint def MassConstraint {<br>        in partMasses : MassValue[0..*];<br>        in massLimit : MassValue;<br>            sum(partMasses) &lt;= massLimit<br>            }<br>    assert constraint massConstraint : MassConstraint {<br>            in partMasses = (light_housing.mass, traffic_light_controller.mass, traffic_light_counter.mass, traffic_light_radio.mass);<br>            in massLimit = 25.0 [kg];<br>        }  <br>            <br>    constraint def PowerConstraint {<br>        in partPowers : PowerValue[0..*];<br>        in powerLimit : PowerValue;<br>            sum(partPowers) &lt;= powerLimit<br>            }<br><br>    assert constraint powerConstraint : PowerConstraint {<br>            in partPower = (light_housing.light_inserts.green_light.power_usage,<br>                            light_housing.light_inserts.yellow_light.power_usage,<br>                            light_housing.light_inserts.red_light.power_usage,<br>                            light_housing.light_electronic_housing.power_usage,<br>                            traffic_light_controller.power_usage,<br>                            traffic_light_counter.power_usage,<br>                            traffic_light_radio.power_usage);                                       <br>            in powerLimit = 19.0 [W];<br>        }<br><br>    interface def vehicle_sensor_signal {<br>            end vehicle_sensor_out : Vehicle_sensor_port;<br>            end vehicile_sensor_in : ~Vehicle_sensor_port;<br>            }<br>    <br>    interface def clock_bus {<br>            end clock_out : Clock;<br>            end clock_in : ~Clock;<br>            }<br>    interface def comm_bus {<br>            end communications_out : Comm;<br>            end communications_in : ~Comm;<br>            }<br>    interface def light_control_bus {<br>            end light_control_out : Can_bus;<br>            end light_control_in : ~Can_bus;<br>            }  <br>            <br>    item def Power;<br>    item def state_message_item {<br>        attribute state_message : String;<br>    }<br>    port power_on;<br>    <br>    part def Light_housing {<br>        attribute mass : MassValue;<br>        part light_inserts [3]{<br>            attribute mass : MassValue;<br>        }<br>    }<br>    part def Light_electronics_housing {<br>        attribute mass : MassValue;<br>        attribute power_usage : PowerValue;<br>    }<br>    part def light {<br>        attribute color;<br>        attribute light_state : Light_state;<br>        attribute location : Integer;<br>        attribute mass : MassValue;<br>        attribute power_usage : PowerValue;<br>        port light_control_in : Can_bus;<br>        } <br>    part light_housing {   <br>        attribute mass :&gt;&gt; mass = 3.0 [kg];<br>        part light_electronic_housing : Light_electronics_housing {<br>            attribute mass :&gt;&gt; mass = 3.0 [kg];<br>            attribute power_usage :&gt;&gt; power_usage = 0.2 [W];<br>        }<br>        <br>    part def light_insert {<br>        attribute mass : MassValue;<br>        part red_light : light;<br>        part yellow_light : light;<br>        part green_light : light;<br>    }<br>        part light_inserts [3] : light_insert {<br>            attribute mass :&gt;&gt; mass = 3.0 [kg];<br>            part red_light :&gt;&gt; red_light {<br>                attribute red :&gt;&gt; color;<br>                attribute location :&gt;&gt; location =  0;<br>                attribute :&gt;&gt; light_state = Light_state::off;<br>                attribute mass :&gt;&gt; mass = 4.0 [kg];<br>                attribute power_usage :&gt;&gt; power_usage = light_power(light_housing.light_inserts.red_light.light_state);<br><br><br>            }<br>            part yellow_light :&gt;&gt; yellow_light {<br>                attribute yellow :&gt; color;<br>                attribute location :&gt;&gt; location =  1;<br>                attribute :&gt;&gt; light_state = Light_state::off;<br>                attribute mass :&gt;&gt; mass = 4.0 [kg];<br>                attribute power_usage :&gt;&gt; power_usage = light_power(light_housing.light_inserts.yellow_light.light_state);<br><br>            }<br>            part green_light :&gt;&gt; green_light {<br>                attribute green :&gt; color;<br>                attribute location :&gt;&gt; location =  2;<br>                attribute :&gt;&gt; light_state = Light_state::off;<br>                attribute mass :&gt;&gt; mass = 4.0 [kg];<br>                attribute power_usage :&gt;&gt; power_usage = light_power(light_housing.light_inserts.green_light.light_state);<br><br>            }<br><br>        }<br>    }<br>    part def Controller {<br>        attribute power : Boolean;<br>        attribute error : Boolean;<br>        attribute error_code : Integer;<br>        attribute message_queue : String;<br>        attribute counter : Integer;<br>        attribute current_time : TimeInstantValue;<br>        attribute state_duration : DurationValue;<br>        attribute wait_duration : Natural;<br>        attribute yellow_on_time : Natural;<br>        attribute green_on_time : Natural;<br>        attribute red_on_time : Natural;<br>        attribute yellow_light_state : Light_state;<br>        attribute red_light_state : Light_state;<br>        attribute green_light_state : Light_state;<br>        port light_control_out : Can_bus;<br>        port communications_in : Comm;<br>        attribute mass : MassValue;<br>        attribute power_usage : PowerValue;<br>        <br>    }<br>    part def Vehicle_sensor{<br>        attribute vehicle_at_light : Boolean;<br>    }<br>    part def Counter {<br>        attribute register : Integer;<br>        attribute mass : MassValue;<br>        attribute power_usage : PowerValue;<br>    }<br>    part def Radio {<br>        attribute register : Integer;<br>        port communications_out : Comm;<br>        attribute trans : Boolean;<br>        attribute mass : MassValue;<br>        attribute power_usage : PowerValue;<br>    }<br>    part vehicle_sensor : Vehicle_sensor {<br>        port vehicle_sensor_out : Vehicle_sensor_port;<br>    }<br>    part traffic_light_controller : Controller{<br>            attribute yellow_on_time :&gt;&gt; yellow_on_time =  2 [s];<br>            attribute green_on_time :&gt;&gt; green_on_time =  30 [s];<br>            attribute red_on_time :&gt;&gt; red_on_time =  90 [s];<br>            attribute mass :&gt;&gt; mass = 0.8 [kg];<br>            attribute power :&gt;&gt; power = 2.0 [W];<br><br>            action power_up_test;<br>            action reset_wait_clk{<br>                      assign wait_duration := 0 [s];<br>                      }<br>            <br>            <br>            action turn_lights_off {<br>                      assign traffic_light_controller.green_light_state := Light_state::off;<br>                      assign light_housing.light_inserts.green_light.light_state := Light_state::off;<br>                      assign traffic_light_controller.yellow_light_state := Light_state::off;<br>                      assign light_housing.light_inserts.yellow_light.light_state := Light_state::off;<br>                      assign traffic_light_controller.red_light_state := Light_state::off;<br>                      assign light_housing.light_inserts.red_light.light_state := Light_state::off;<br>                      assign traffic_light_radio.trans := false;<br>            }            <br><br>            action turn_yellow_on {<br>                      perform action turn_lights_off;<br>                      perform action reset_wait_clock;<br>                      assign traffic_light_controller.yellow_light_state := Light_state::lit;<br>                      assign light_housing.light_inserts.yellow_light.light_state := Light_state::lit;<br>                      }<br>            action turn_green_on {<br>                      perform action turn_lights_off;<br>                      perform action reset_wait_clock;<br>                      assign traffic_light_controller.green_light_state := Light_state::lit;<br>                      assign light_housing.light_inserts.green_light.light_state := Light_state::lit;<br>                      }<br>            action turn_red_on {<br>                      perform action turn_lights_off;<br>                      perform action reset_wait_clock;<br>                      assign traffic_light_controller.red_light_state := Light_state::lit;<br>                      assign light_housing.light_inserts.red_light.light_state := Light_state::lit;<br>                      }<br><br>            action reset_error;<br>            action reset_wait_clock;<br><br>            action run_diagnostic{<br>                action update_error_code;<br>                action update_error;<br>                action send_error_sms {<br>                        assign traffic_light_radio.trans := true;<br>                        }<br>                }<br>            action check_status{<br>                action check_error;<br>                action read_message_queue{<br>                    attribute message_from_queue : String;<br>                }<br>                }<br>            action blink_red_light {<br>                      perform action turn_lights_off;<br>                      assign traffic_light_controller.red_light_state := Light_state::blinking;<br>                      assign light_housing.light_inserts.red_light.light_state := Light_state::blinking;<br>                      }<br>            action blink_yellow_light {<br>                      assign traffic_light_controller.yellow_light_state := Light_state::blinking;<br>                      assign light_housing.light_inserts.yellow_light.light_state := Light_state::blinking;<br>                      }<br>        <br>            port clock_in : Clock;<br>            port vehicle_sensor_in :Vehicle_sensor_port;<br>            }<br>       part traffic_light_counter : Counter{<br>            port clock_out : Clock;<br>            attribute mass :&gt;&gt; mass = 0.4 [kg];<br>            attribute power_usage :&gt;&gt; power_usage = 0.4 [W];<br>         }<br>       part traffic_light_radio : Radio{<br>            attribute mass :&gt;&gt; mass = 0.6 [kg];<br>            attribute trans :&gt;&gt; trans = false;<br>            attribute power_usage :&gt;&gt; power_usage = radio_power(traffic_light_radio.trans); <br>         }<br>   <br>state traffic_light{<br>            entry;<br>            succession begin first start<br>                then off;    <br>                <br>            transition off_to_init<br>                first off <br>                accept Power via power_on<br>                then init; <br>                <br>            transition init_to_diagnostic<br>                first init<br>                accept when (traffic_light_controller.error == true)<br>                then diagnostic;<br>                <br>            transition diagnostic_to_red_blinking<br>                first diagnostic<br>                then red_blinking;<br><br>            transition operation_selection_to_init<br>                first operation_selection<br>                accept when (traffic_light_controller.error == true)<br>                then init;<br><br>            transition init_to_operation_selection<br>                first init<br>                accept when (traffic_light_controller.error == false)<br>                then operation_selection;<br>            <br>            transition operation_selection_to_stop_sign<br>                first operation_selection<br>                accept when ((traffic_light_controller.message_queue == &quot;operating_mode.stop_sign&quot;)<br>                        and (traffic_light_controller.error == false))<br>                then stop_sign;  <br><br>            transition operation_selection_to_caution<br>                first operation_selection<br>                accept when ((traffic_light_controller.message_queue == &quot;operating_mode.caution&quot;) <br>                        and (traffic_light_controller.error == false))<br>                then caution;<br>                <br>            transition operation_selection_to_traffic_flow<br>                first operation_selection<br>                accept when ((traffic_light_controller.message_queue == &quot;operating_mode.traffic_flow&quot;) <br>                        and (traffic_light_controller.error == false))<br>                then traffic_flow;<br>            <br>            transition caution_to_yellow_blinking<br>                first caution<br>                then yellow_blinking;<br>                <br>            transition stop_sign_to_red_blinking<br>                first stop_sign<br>                then red_blinking;<br>                <br>            transition traffic_flow_to_red_on<br>                first traffic_flow<br>                accept when (traffic_light_controller.error == false)<br>                then red_on;<br><br>            transition red_on_to_green_on<br>                first red_on<br>                accept when (traffic_light_controller.wait_duration &gt; traffic_light_controller.red_on_time) <br>                        and (vehicle_sensor.vehicle_at_light == true)<br>                        and (traffic_light_controller.message_queue == &quot;Safe_all_red&quot;)<br>                        and (traffic_light_controller.error == false)<br>                then green_on;<br>                <br>            transition red_on_to_traffic_flow<br>                first red_on<br>                accept when ((((traffic_light_controller.message_queue != &quot;&quot;)<br>                        and (traffic_light_controller.message_queue != &quot;Safe_all_red&quot;))<br>                        or (traffic_light_controller.error == true)))<br>                then traffic_flow; <br><br>            transition green_on_to_yellow_on<br>                first green_on<br>                accept when  (traffic_light_controller.wait_duration &gt; traffic_light_controller.green_on_time<br>                        or (traffic_light_controller.error == true))<br>                then yellow_on;<br><br>            transition yellow_on_to_red_on<br>                first yellow_on<br>                accept when (traffic_light_controller.wait_duration &gt; traffic_light_controller.yellow_on_time)<br>                then red_on;<br><br>            transition traffic_flow_to_operation_selection<br>                first traffic_flow<br>                accept when ((traffic_light_controller.message_queue != &quot;&quot;)<br>                        or (traffic_light_controller.error == true))<br>                then operation_selection;    <br>                <br>            transition red_blinking_to_operation_selection<br>                first red_blinking<br>                accept when ((traffic_light_controller.message_queue != &quot;&quot;)<br>                        or (traffic_light_controller.error == true))<br>                then operation_selection;<br>                <br>            transition yellow_blinking_to_operation_selection<br>                first yellow_blinking<br>                accept when ((traffic_light_controller.message_queue != &quot;&quot;)<br>                        or (traffic_light_controller.error == true))<br>                then operation_selection;<br><br>    }      <br>             state off;<br>                <br>             state init{<br>                    entry traffic_light_controller.power_up_test;<br>                    do traffic_light_controller.check_status.check_error;<br>                    exit;<br>                    }<br>                    <br>             state diagnostic {<br>                    entry traffic_light_controller.run_diagnostic;<br>                    exit traffic_light_controller.reset_error;<br>                    }<br>                <br>             state operation_selection{<br>                    entry traffic_light_controller.check_status.check_error;<br>                    do traffic_light_controller.check_status;<br>                    exit;<br>                    }<br>             state stop_sign;<br>             <br>             state red_blinking {<br>                    entry traffic_light_controller.blink_red_light;<br>                    do traffic_light_controller.check_status.check_error;<br>                    exit;<br>                    }<br>                <br>                <br>             state caution;<br>             <br>             state yellow_blinking {<br>                    entry traffic_light_controller.blink_yellow_light;<br>                    do traffic_light_controller.check_status; <br>                    exit;<br>                    }<br>                <br>             state traffic_flow {<br>                    entry traffic_light_controller.check_status;<br>                    exit;<br>                }<br>             <br>             state green_on {<br>                    entry traffic_light_controller.turn_green_on; <br>                    do traffic_light_controller.check_status;<br>                    exit;<br>                }<br>                <br>             state yellow_on {<br>                     entry traffic_light_controller.turn_yellow_on;<br>                     do traffic_light_controller.check_status.check_error;<br>                     exit;<br>                }<br>             <br>             state red_on {<br>                     entry traffic_light_controller.turn_red_on;<br>                     do traffic_light_controller.check_status;<br>                     exit;<br>                }<br>            <br>    interface : clock_bus connect<br>            traffic_light_counter.clock_out to<br>            traffic_light_controller.clock_in;<br>    interface : vehicle_sensor_signal connect<br>            vehicle_sensor.vehicle_sensor_out to<br>            traffic_light_controller.vehicle_sensor_in;     <br>    interface : light_control_bus connect<br>            traffic_light_controller.light_control_out to<br>            light_housing.light_inserts.red_light.light_control_in;<br>    interface : light_control_bus connect<br>            traffic_light_controller.light_control_out to<br>            light_housing.light_inserts.yellow_light.light_control_in;<br>    interface : light_control_bus connect<br>            traffic_light_controller.light_control_out to<br>            light_housing.light_inserts.green_light.light_control_in;<br><br>}<br><br></pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ad7e87addba8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/automated-reasoning-for-sysml-v2-ad7e87addba8">Automated Reasoning for SysML v2</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Analysing Machine Learning Models with Imandra]]></title>
            <link>https://medium.com/imandra/analysing-machine-learning-models-with-imandra-4510cf586927?source=rss----aa39f4f76a9f---4</link>
            <guid isPermaLink="false">https://medium.com/p/4510cf586927</guid>
            <category><![CDATA[random-forest]]></category>
            <category><![CDATA[formal-verification]]></category>
            <category><![CDATA[neural-networks]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[Lewis Hammond]]></dc:creator>
            <pubDate>Thu, 26 Sep 2019 21:44:23 GMT</pubDate>
            <atom:updated>2019-09-26T21:44:23.553Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6W4rD4IcRBOriezsxZISWw.jpeg" /></figure><p><em>The vast majority of work within formal methods (the area of computer science that reasons about hardware and software as mathematical objects in order to prove they have certain properties) has involved analysing models that are fully specified by the user. More and more, however, critical parts of algorithmic pipelines are constituted by models that are instead learnt from data using artificial intelligence (AI). The task of analysing these kinds of models presents fresh challenges for the formal methods community and has seen exciting progress in recent years. While scalability is still an important, open research problem — with state-of-the-art machine learning (ML) models often having millions of parameters —in this post we give an introduction to the paradigm by analysing two simple yet powerful learnt models using </em><a href="https://www.imandra.ai/"><em>Imandra</em></a><em>, a cloud-native automated reasoning engine bringing formal methods to the masses!</em></p><h3>Introduction</h3><p>Verifying properties of learnt models is a difficult task, but is becoming increasingly important in order to make sure that the AI systems using such models are safe, robust, and explainable. ML, and in particular deep learning, is a powerful technique that has seen unprecedented successes recently in a wide variety of tasks, often producing super-human performance. However, understanding the resulting models and forming guarantees of performance with respect to certain input or output conditions is often difficult. Imandra is a general-purpose automated reasoning engine offering a suite of tools that can be used to provide exactly these kinds of insights and guarantees for a wide range of algorithms.</p><p>Supervised learning is a subfield of ML that involves training a model using data made up of input-output pairs so that given a new, unseen input the model is able to produce an appropriate output prediction. In other words, the aim is to learn a model that approximates a <em>function</em> mapping inputs to outputs. To interact with Imandra we use the Imandra Modelling Language (IML), which is based on an augmented subset of the functional programming language OCaml, and can therefore reason about such functions in a particularly natural way.</p><p>To illustrate this approach we’ll be looking at examples from two of the most common tasks within supervised learning (and ML more generally): classification and regression. In particular, we’ll show how two of the most common kinds of model used to perform these tasks, random forests and neural networks, can be analysed using Imandra. For each task we’ll use a real-world benchmark dataset from the <a href="https://archive.ics.uci.edu/ml/index.php">UCI Machine Learning Repository</a> and create our models using Python with some standard ML libraries. You can find all of our code for both learning and analysing our models on <a href="https://github.com/AestheticIntegration/imandra-stats-experiments/tree/master/supervised_learning">GitHub</a>, and there’s also a corresponding cloud-based <a href="https://docs.imandra.ai/imandra-docs/notebooks/supervised-learning/">Imandra Jupyter Notebook</a> that you can try out for yourself.</p><h3>Classification</h3><p>In a classification task we want to learn to predict the label of a datapoint based on previous labelled data. In the classic <a href="https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+(Diagnostic)">Wisconsin Breast Cancer (Diagnostic) dataset</a> the aim is to decide whether a cancer is benign or malignant based on the features of a sample of cell nuclei. In the dataset we have the following variables:</p><pre>1. ID number<br>2. Diagnosis (malignant or benign)<br>3–32. Real values for the mean, standard error, and the ‘worst’ value for each cell nucleus’<br> a) Radius<br> b) Texture<br> c) Perimeter<br> d) Area<br> e) Smoothness<br> f) Compactness<br> g) Concavity<br> h) Concave points<br> i) Symmetry <br> j) Fractal dimension</pre><p>As is standard practice we pre-process the data before learning. First we standardise each variable to have zero mean and unit variance, then remove all but one from groups of variables that are highly correlated, along with those that have low mutual information with respect to the target variable. The data is split into training (80%) and test (20%) sets and we use Scikit-Learn to learn a random forest of 3 decision trees of maximum depth 3. As this is a relatively straightforward problem even this simple model achieves a fairly high accuracy. Using a short Python script each tree is then converted to IML and can be reasoned about using Imandra.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d56a04285e65b4151b813f4d81ee4545/href">https://medium.com/media/d56a04285e65b4151b813f4d81ee4545/href</a></iframe><p>We can also create a custom input type in Imandra for our model, so that we can keep track of the different features of our data. However, remember that we processed our data before learning. To make things easier, we’ll add in a function applying these transformations to each input variable using some multiplicative and additive scaling values extracted during our data pre-processing stage. After that we can define a full end-to-end model which combines these pre-processing steps and the random forest.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0f96c99dea1269a5c8c0282aa98463ef/href">https://medium.com/media/0f96c99dea1269a5c8c0282aa98463ef/href</a></iframe><p>As our IML model is fully executable we can query it, find counterexamples, prove properties, apply logical side-conditions, decompose its behaviour into regions, and more. As a quick sanity check to make sure everything is working, we can run a datum (which in this case happens to have the malignant label) from our dataset through the model as follows:</p><pre># rf_model {radius_mean = 17.99; <br>            compactness_mean = 0.2776;<br>            concavity_mean = 0.3001;<br>            radius_se = 1.095; <br>            compactness_worst = 0.6656; <br>            concavity_worst = 0.7119;<br>            concave_points_worst = 0.7119};;</pre><pre>- : string = &quot;malignant&quot;</pre><p>Looking good! We can also use Imandra to generate instances and counterexamples for us, potentially given logical side conditions that can be specified as functions outputting a Boolean value. If we do this naively then Imandra returns instances that, while correct, might be wildly different from any reasonable values we’d expect to see in real life. In general we often only care about the performance of our models when some reasonable bounds are placed on the input (for example, the mean radius can’t be negative, and if the values for this variable in our dataset range between 6.98 and 28.11 we wouldn’t really expect any value greater than, say, 35).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fGYKW4OYqpRc895JzFwgzw.jpeg" /></figure><p>Using the description of each variable in the dataset information above we can form a condition describing valid and reasonable inputs to our model. In ML as a whole, we are typically only interested in the performance and quality of a model over some particular distribution of data, about which we often have prior beliefs.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1310efaf6a93ab5155a3387d6786f6ef/href">https://medium.com/media/1310efaf6a93ab5155a3387d6786f6ef/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07b41cb18a4e52f0dd6a62dec3f2fb75/href">https://medium.com/media/07b41cb18a4e52f0dd6a62dec3f2fb75/href</a></iframe><p>This allows us to generate the following synthetic data point which our model would classify as benign. We can then access and compute with our instance x using the CX (counterexample) module and display the results in decimal (as opposed the standard fractional) notation using a custom pretty printer defined above:</p><pre># instance (fun x -&gt; rf_model x = &quot;benign&quot; &amp;&amp; is_valid_rf x);;</pre><pre>- : rf_input -&gt; bool = &lt;fun&gt;<br>Instance (after 0 steps, 0.021s):<br>  let (x : rf_input) =<br>    {radius_mean = (Real.mk_of_string<br>     &quot;28061993138323/5000000000000&quot;);<br>     compactness_mean = (Real.mk_of_string<br>     &quot;8487976201/5000000000000&quot;);<br>     concavity_mean = (Real.mk_of_string <br>     &quot;162320931909/2500000000000&quot;);<br>     radius_se = (Real.mk_of_string <br>     &quot;40971/20000&quot;);<br>     compactness_worst = (Real.mk_of_string <br>     &quot;321/5000&quot;);<br>     concavity_worst = (Real.mk_of_string<br>     &quot;415811319441/25000000000000&quot;);<br>     concave_points_worst = (Real.mk_of_string<br>     &quot;3877904791/781250000000&quot;)}<br><strong>[✔]</strong> Instance found.<br>module CX : sig val x : rf_input end</pre><pre># #install_printer pp_approx;;</pre><pre># CX.x;;</pre><pre>- : rf_input = {radius_mean = 5.61239862766; <br>                compactness_mean = 0.0016975952402;<br>                concavity_mean = 0.0649283727636; <br>                radius_se = 2.04855;<br>                compactness_worst = 0.0642; <br>                concavity_worst = 0.0166324527776;<br>                concave_points_worst = 0.00496371813248}</pre><p>We can also reason about our model in more interesting ways, such as checking the validity of certain constraints we want our model to satisfy. For example, if the surface of a cell nucleus has many, large concave sections, this is a particularly negative sign indicating that the cancer is likely to be malignant. We can use Imandra to easily verify that our model always classifies a (valid) sample of highly concave cells as malignant:</p><pre># verify (fun x -&gt;<br>          is_valid_rf x &amp;&amp;<br>          x.concavity_mean &gt;=. 0.4 &amp;&amp;<br>          x.concavity_worst &gt;=. 1.0 &amp;&amp;<br>          x.concave_points_worst &gt;=. 0.25 ==&gt;<br>          rf_model x = &quot;malignant&quot;);;</pre><pre>- : rf_input -&gt; bool = &lt;fun&gt;<br><strong>[✓]</strong> Theorem proved.</pre><p>Excellent, Imandra has proved for us that this property always holds, for all possible input values. Although our model is correct here, by tweaking some of the numbers in the statement we want to verify it isn’t hard to come up with ostensibly reasonable properties that our model fails to satisfy (we’ll see an example of this in the next section). Not only will Imandra tell us when a verification command fails, it also provides a particular instance of an input that violates the property. The instance is then automatically loaded into the state space and thus can be further inspected and reasoned about. This is one of the ways that Imandra can be used to help diagnose problems with models and to give insights into their behaviour.</p><p>The final feature we’ll look at in this section is region decomposition, a technique that Imandra uses to break up a potentially infinite input space into discrete regions over which the behaviour of the decomposed function is constant (for an introduction to this feature, take a look at <a href="https://medium.com/imandra/describing-algorithms-introduction-8d8224a0f920">one of our earlier posts</a>). The nested if ... then ... else statements in how the trees (which make up the random forest) are defined mean that they are a prime candidate for this functionality. As well as a logical description of the regions, Imandra also produces an interactive Voronoi diagram to help visualise an algorithm’s behaviour. Have a look on the notebook to see them in action and simply click on a region to see its description!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SpSFs25CDSKsAx2cX9Xbqg.png" /><figcaption>Voronoi diagrams for our decomposition of the random forest ensemble (left) and the first tree (right)</figcaption></figure><p>We can also use side conditions on the region decomposition of our model using the ~assuming: flag. One application here is in simulating partial observability. For example, perhaps we know most of the measurements for a particular set of cells and we’d like to see how the classification of the input depends on the remaining features. Let’s imagine that we only have the concavity measurements for a particular patient’s cell sample and we’d like to see how the output of our model depends on the values of the other features.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0e09ce353cb066647a89d8393cb2d1d3/href">https://medium.com/media/0e09ce353cb066647a89d8393cb2d1d3/href</a></iframe><p>By specifying the known values in a side condition we can get an interactive visual tool (embedded in our Imandra Jupyter Notebook, or available via an automatically generated HTML link) that provides us with a finite set of possible outcomes and a logical, human-readable description of each one:</p><pre>#install_printer Imandra_voronoi.Voronoi.print;;</pre><pre>Decompose.top <br>  ~ctx_asm_simp:true <br>  ~assuming:&quot;partial_observation&quot; <br>  &quot;rf_model&quot; [@@program];;</pre><pre>9 regions computed.<br>- : Imandra_interactive.Decompose.t list = <br>Open:<br>file:////var/folders/l9/d5spnx9177v53nkbpjl4f0yr0000gs/T/voronoi_ee639a.html</pre><h3>Regression</h3><p>In a regression task we want to learn to predict the value(s) of some variable(s) based on previous data. In the commonly used <a href="https://archive.ics.uci.edu/ml/datasets/forest+fires">Forest Fires dataset</a> the aim is to predict the area burnt by forest fires, in the northeast region of Portugal, by using meteorological and other data. This is a fairly difficult task and while the neural network below doesn’t achieve state-of-the-art performance, it’s enough to demonstrate how we can analyse relatively simple models of this kind in Imandra. In the dataset we have the following variables:</p><pre>1. X-axis spatial coordinate (within the Montesinho park map)<br>2. Y-axis spatial coordinate (within the Montesinho park map)<br>3. Month<br>4. Day<br>5. FFMC index (from the FWI system)<br>6. DMC index (from the FWI system)<br>7. DC index (from the FWI system)<br>8. ISI index (from the FWI system)<br>9. Temperature<br>10. Relative percentage humidity<br>11. Wind speed<br>12. Rainfall<br>13. Area of forest burnt</pre><p>We again pre-process the data before learning by first transforming the month and day variables into a numerical value and applying a sine transformation (so that similar times are close in value), as well as removing outliers and applying an approximate logarithmic transformation to the area variable (as recommended in the dataset description). Each variable is scaled to lie between 0 and 1, and those with high correlations and/or low mutual information with respect to the target variable are removed. We then split the data into training (80%) and test (20%) sets and use Keras to learn a simple feed-forward neural network with one (6 neuron) hidden layer, ReLU activation functions, and stochastic gradient descent to optimise the mean squared error. After saving our model as a .h5 file we use a short Python script to extract the network into an IML file and reason about it using Imandra.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bc97ca33aa4cd1a1f592549fb5cee33f/href">https://medium.com/media/bc97ca33aa4cd1a1f592549fb5cee33f/href</a></iframe><p>As in our first example we can also define some custom data types, functions that replicate our pre/post-processing of the data, and a condition that describes valid inputs to the model based on the dataset description (plus some reasonable assumptions about Portugal’s climate).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ab78050f45683ec88e0e83153e90462e/href">https://medium.com/media/ab78050f45683ec88e0e83153e90462e/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f24543d35fb6b6a553c24e94c4a67b26/href">https://medium.com/media/f24543d35fb6b6a553c24e94c4a67b26/href</a></iframe><p>By generating instances using side conditions, such as the one below (where we require the output to be more than 20 hectares, the temperature to be 20 degrees celsius and the month to be May) we can again both query and compute with our model, reasoning about the results directly using the CX module. Note that here our previously installed pretty printer is converting values to decimal notation for us again:</p><pre># instance (fun x -&gt; <br>            nn_model x &gt;. 20.0 &amp;&amp; <br>            x.temp = 20.0 &amp;&amp; <br>            x.month = May &amp;&amp; <br>            is_valid_nn x);;</pre><pre>- : nn_input -&gt; bool = &lt;fun&gt;<br>Instance (after 0 steps, 0.103s):<br>  let (x : nn_input) = <br>    {month = May; <br>     day = Mon; <br>     dmc = 500.; <br>     temp = 20.; <br>     rh = 100.;<br>     rain = (Real.mk_of_string  <br>     &quot;24634017759143723418947963885307992/<br>     1878541866480547748364634187934375&quot;)}<br><strong>[✔]</strong> Instance found.<br>module CX : sig val x : nn_input end</pre><pre># CX.x;;</pre><pre>- : nn_input = {month = May; <br>                day = Mon; <br>                dmc = 500.; <br>                temp = 20.; <br>                rh = 100.;<br>                rain = 13.1133717053}</pre><pre># nn_model CX.x;;</pre><pre>- : real = 20.0272890315</pre><p>The kinds of analysis we can perform here are not dissimilar to what we looked at above with our classification task, so without wanting to repeat ourselves too much, we’ll conclude this section by illustrating Imandra’s ability to prove a couple of useful properties about our network. We can start by verifying a condition that we’d hope any reasonable predictive model for this task would obey, namely that the area outputted by the model is never negative:</p><pre># verify (fun x -&gt; is_valid_nn x ==&gt; nn_model x &gt;=. 0.0);;</pre><pre>- : nn_input -&gt; bool = &lt;fun&gt;<br><strong>[✓]</strong> Theorem proved.</pre><p>Great, so we know that whatever happens, as long as our neural network receives a valid input it will <em>never</em> malfunction by outputting a negative number. Although this is a simple example, it’s exactly these kind of guarantees that are needed for the safe deployment of such models in the real world, where a network’s output could form part of, say, a control sequence for operating a driverless car.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dEdBoMEG6jMhZcuy0BM4Lg.jpeg" /></figure><p>Finally, we’ll try something slightly more ambitious and test a hypothesis. All other things remaining equal, we would expect that the higher the temperature, the larger the area of forest that would be burnt. Due to the imperfections in our model (because of limited data, stochasticity in training, the complicated patterns present in natural physical phenomena, and so on) this assertion is in fact easily falsifiable by Imandra, as can be seen here:</p><pre># verify (fun a b -&gt; <br>          is_valid_nn a &amp;&amp; <br>          is_valid_nn b &amp;&amp; <br>          a.month = b.month &amp;&amp; <br>          a.day = b.day &amp;&amp; <br>          a.dmc = b.dmc &amp;&amp; <br>          a.rh = b.rh &amp;&amp; <br>          a.rain = b.rain &amp;&amp; <br>          a.temp &gt;=. b.temp ==&gt; <br>          nn_model a &gt;=. nn_model b);;</pre><pre>- : nn_input -&gt; nn_input -&gt; bool = &lt;fun&gt;<br>Counterexample (after 0 steps, 0.116s):<br>  let (a : nn_input) =<br>    {month = Feb; <br>     day = Wed;<br>     dmc = (Real.mk_of_string <br>&quot;2001302860578649047203412202395247295896300726349164954426900657936422485111743/26290894775182656579389311741592195964262695657047583625044304683319346328960&quot;);<br>     temp = (Real.mk_of_string &quot;91160069288673997541144963139828470662342220288373496574482010785769071655/16907327829699457607324316232535174253545141901638317443758395294739129472&quot;);<br>     rh = (Real.mk_of_string &quot;310987560530841206048248341173125577069685813208286530077844293829277108589275/5258178955036531315877862348318439192852539131409516725008860936663869265792&quot;);<br>     rain = (Real.mk_of_string &quot;75815863864250199885180688130868702048660206232272093463832717880470859243/24896680658316909639573211876507761329794219372204151160079833980416047660&quot;)}<br>  let (b : nn_input) =<br>    {month = Feb; <br>     day = Wed;<br>     dmc = (Real.mk_of_string &quot;2001302860578649047203412202395247295896300726349164954426900657936422485111743/26290894775182656579389311741592195964262695657047583625044304683319346328960&quot;);<br>     temp = 0.;<br>     rh = (Real.mk_of_string &quot;310987560530841206048248341173125577069685813208286530077844293829277108589275/5258178955036531315877862348318439192852539131409516725008860936663869265792&quot;);<br>     rain = (Real.mk_of_string &quot;75815863864250199885180688130868702048660206232272093463832717880470859243/24896680658316909639573211876507761329794219372204151160079833980416047660&quot;)}<br><strong>[✗]</strong> Conjecture refuted.<br>module CX : sig val a : nn_input val b : nn_input end</pre><p>That’s quite a lot of information, but we can easily dive in and inspect the key parts of the counterexamples that Imandra has generated for us. In particular, we know that the inputs will be the same in all but the temperature feature, so let’s take a look at those values first and then run each counterexample through the model to show that it really does invalidate our hypothesis:</p><pre># CX.a.temp;;</pre><pre>- : real = 5.39174908104</pre><pre># CX.b.temp;;</pre><pre>- : real = 0.</pre><pre># nn_model CX.a;;</pre><pre>- : real = 1.47805400176</pre><pre># nn_model CX.b;;</pre><pre>- : real = 1.79452234896</pre><p>All is not lost, however! Although the network doesn’t satisfy our original verification statement we can restrict our setting in a sensible way in order to prove something slightly weaker:</p><ul><li>There is very little data from winter months, and so the model is unlikely to generalise well here, hence we’ll only consider non-winter months</li><li>We’ll increase the tolerance in temperature to 10 degrees celsius</li><li>We’ll increase the tolerance in area burnt to 25 hectares</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/947d19b6db05f715b3db2bf6be84f4e6/href">https://medium.com/media/947d19b6db05f715b3db2bf6be84f4e6/href</a></iframe><p>With these allowances in place, Imandra gives us back a final proof:</p><pre># verify (fun a b -&gt; <br>          is_valid_nn a &amp;&amp; <br>          is_valid_nn b &amp;&amp; <br>          a.month = b.month &amp;&amp; <br>          not (winter a.month) &amp;&amp;<br>          a.day = b.day &amp;&amp; <br>          a.dmc = b.dmc &amp;&amp; <br>          a.rh = b.rh &amp;&amp; <br>          a.rain = b.rain &amp;&amp; <br>          (a.temp -. 10.0) &gt;=. b.temp ==&gt; <br>          (nn_model a +. 25.0) &gt;=. nn_model b);;</pre><pre>- : nn_input -&gt; nn_input -&gt; bool = &lt;fun&gt;<br><strong>[✓]</strong> Theorem proved.</pre><h3>Conclusion</h3><p>Reasoning about models that have been learnt from data is just one of many ways that formal methods can be brought to bear upon ML. While this problem is certainly a hard one — most state-of-the-art approaches rely on highly specialised software, techniques, and assumptions that Imandra doesn’t (yet!) support — it’s one we’re interested in pursuing further, particularly when it comes to scalability. However there are also other exciting avenues in this area that we’re currently exploring, such as our <a href="https://medium.com/imandra/pyidf-diversity-of-experiences-in-reinforcement-learning-8d59f60f59ed">recent post</a> showing how region decomposition can be used to improve sample efficiency in deep reinforcement learning, for example.</p><p>If you’re interested in our work be sure to check our <a href="https://try.imandra.ai/">other notebooks</a>, find out more and get email updates on our <a href="https://www.imandra.ai/">website</a>, join the discussion on our <a href="https://discord.gg/byQApJ3">Discord channel</a>, and of course subscribe to our <a href="https://medium.com/imandra">Medium publication</a>!</p><h3>References</h3><ol><li>Dario Amodei, Chris Olah, Jacob Steinhardt, Paul Christiano, John Schulman, and Dan Mané. <a href="https://arxiv.org/pdf/1606.06565.pdf">Concrete Problems in AI Safety</a>. <em>arXiv preprint arXiv:1606.06565.</em> 2016.</li><li>Ian Goodfellow and Nicolas Papernot. <a href="http://www.cleverhans.io/security/privacy/ml/2017/06/14/verification.html">The Challenge of Verification and Testing of Machine Learning</a>. <em>CleverHans blog post (Accessed: 21.08.19)</em>. 2017.</li><li>Xiaowei Huang, Marta Kwiatkowska, Sen Wang, and Min Wu. <a href="http://qav.comlab.ox.ac.uk/papers/hkww17.pdf">Safety Verification of Deep Neural Networks</a>. In <em>International Conference on Computer Aided Verification</em>, pp. 3–29. 2017.</li><li>Pushmeet Kohli, Krishnamurthy Dvijotham, Jonathan Uesato, and Sven Gowal. <a href="https://deepmind.com/blog/article/robust-and-verified-ai">Identifying and Eliminating Bugs in Learned Predictive Models</a>. <em>DeepMind blog post (Accessed: 21.08.19)</em>. 2019.</li><li>Daniel Selsam, Percy Liang, and David L Dill. <a href="https://arxiv.org/pdf/1706.08605.pdf">Developing Bug-Free Machine Learning Systems with Formal Mathematics</a>. <em>arXiv preprint arXiv:1706.08605</em>. 2017.</li><li>Sanjit A Seshia, Dorsa Sadigh, and S Shankar Sastry. <a href="https://arxiv.org/pdf/1606.08514.pdf">Towards Verified Artificial Intelligence</a>. <em>arXiv preprint arXiv:1606.08514</em>. 2016.</li><li>John Törnblom and Simin Nadjm-Tehrani. <a href="https://www.ida.liu.se/labs/rtslab/publications/2018/John_FTSCS.pdf">Formal Verification of Random Forests in Safety-Critical Applications</a>. In <em>International Workshop on Formal Techniques for Safety-Critical Systems</em>, pp. 55–71. 2018.</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4510cf586927" width="1" height="1" alt=""><hr><p><a href="https://medium.com/imandra/analysing-machine-learning-models-with-imandra-4510cf586927">Analysing Machine Learning Models with Imandra</a> was originally published in <a href="https://medium.com/imandra">Imandra Inc.</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>