<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.0.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-06-23T13:25:19+00:00</updated><id>/feed.xml</id><title type="html">博客，写作和技术笔记</title><subtitle>年轻的时候，我们容易把自己的创作冲动理解为创作才能</subtitle><entry><title type="html">反曲弓动作整理</title><link href="/2020/06/03/recurve-bow-skill.html" rel="alternate" type="text/html" title="反曲弓动作整理" /><published>2020-06-03T00:00:00+00:00</published><updated>2020-06-03T00:00:00+00:00</updated><id>/2020/06/03/recurve-bow-skill</id><content type="html" xml:base="/2020/06/03/recurve-bow-skill.html"><![CDATA[<p>主要以 <a href="https://www.bilibili.com/video/BV1KW411X7RZ">CCTV5 体育教学：射箭</a>整理。</p>

<h1 id="站立">站立</h1>

<ul>
  <li>平行式：双脚与靶呈一条直线。最自然的站姿，但是容易受到侧风影响。</li>
  <li>暴露式：左脚比平行式位置向后。适合持弓臂大臂短小臂长的人，有利于背部用力，但姿势更不顺畅。</li>
  <li>隐蔽式：右脚比平行式位置向后。适合持弓臂大臂长小臂短的人，重心更靠近持弓臂，但是容易弓弦打胳膊。</li>
</ul>

<p>为了防止身体整体在“脚尖-脚跟”方向晃动，可以略微有踮脚的意识，让重心靠近脚掌。</p>

<p>为了防止身体朝靶或远离靶晃动，要注意背部用力开弓，前后对称用力。</p>

<h1 id="勾弦">勾弦</h1>

<p>勾弦注意食指中指不要夹到箭。在拉开弦后，呈角度的弓弦会压拢手指，要注意此时也不要夹到箭。</p>

<h1 id="推弓">推弓</h1>

<ul>
  <li>高推法：用虎口推弓，需要较大的腕部力量，对弓的施力点靠近中心。</li>
  <li>中推法：用大鱼际推弓，较为轻松。</li>
  <li>低推法：用掌根推弓，用力更轻松，但是施力点不稳定，撒放时容易带来扰动。</li>
</ul>

<p>推弓时要注意不要向上下用力，弓不要位于手臂延长线的左右，因为偏离中心的用力在撒放会让弓身转动，带来干扰。</p>

<h1 id="举弓">举弓</h1>

<ul>
  <li>高位举弓：把弓略微举高，再准备拉开，手腕高度在眼睛附近。此时瞄准靶心偏上位置。用力最轻松。</li>
  <li>水平举弓：手腕高度在下巴附近。此时瞄准靶心。</li>
</ul>

<p>举弓要举稳后再把弓拉开。举弓后两手动作不要调整。</p>

<p>举弓时要沉肩。拉弓臂手肘要高过肩膀。</p>

<h1 id="开弓">开弓</h1>

<ul>
  <li>高位举弓的开弓：开弓过程中，瞄准器应随着弓的下降自然降落到靶心。</li>
  <li>水平举弓的开弓：开弓过程中保持瞄准靶心。</li>
</ul>

<p>开弓后不要进行二次瞄准，手肘高低也要一次到位。</p>

<p>用力时头不要靠近、远离弓，重心也不要向四周晃动。</p>

<p>开弓臂手腕不要弯曲，保证手背跟前臂呈一条直线。</p>

<h1 id="靠弦">靠弦</h1>

<p>可以通过鼻、口、下巴、胸与弓弦接触，食指靠在颌骨旁，来保证每次靠弦一致。胸部接触可以防止头前后移动。</p>

<ul>
  <li>侧向定位：弓弦靠住嘴右角。有利于后背用力。</li>
  <li>颌下定位：弓弦靠住鼻、嘴、下巴中央。</li>
</ul>

<p>要保证靠弦最短距离、一步到位。</p>

<p>嘴唇闭住，保证触弦位置一致。</p>

<p>手肘略微顺时针旋转，保证持弓臂的肩、手肘、手施力点在俯视上为一条直线。手与前肩持平或略高。</p>

<p>两臂肩关节尽量靠近箭杆。两肩保持沉肩。</p>

<p>拉弓臂手肘高于肩膀，后背用力，手指放松，调整手腕使手背手臂打平。保证勾弦点、搭箭点、靶中心在一条直线上。勾弦点位于身体重心正上方。</p>

<p>身体略微前倾但重心不要超过脚尖，能够保证不晃动，同时可以保证肩部容易沉肩、后背容易收紧。</p>

<h1 id="瞄准">瞄准</h1>

<ul>
  <li>弦内瞄准：视野中的弓弦右侧对准准星左侧。</li>
  <li>弦外瞄准：视野中的弓弦左侧对准准星右侧或弓身。</li>
</ul>

<p>使眼睛聚焦在瞄准器上，瞄实靶虚。</p>

<p>瞄准时间保持在 2 至 4 秒。</p>

<p>手不要用力顶住脸。</p>

<h1 id="继续用力">继续用力</h1>

<p>在有信号片的弓上，保持动作继续缓慢开弓直到信号片响。</p>

<h1 id="撒放">撒放</h1>

<p>后背继续用力带动拉弓臂手肘向后背移动，同时手指自然伸直。手指可以擦过脖子。撒放的同时，身体重心可以稍微向脚尖移动，但不要过大。推弓手不要握弓，避免撒放的反作用力传到弓上。撒放结束后，拉弓臂手肘应当越过中轴线，位于后背侧，手肘不要下垂。</p>

<p>手指放松，手指根部尽量与手背平直。撒放时不要主动用力伸直。勾弦手保持与手臂在一条直线上，不自然的角度会导致撒放时部分手指滑弦或过早离弦。</p>

<h1 id="动作暂留">动作暂留</h1>

<p>撒放完成后，停留 2 至 3 秒，感受并强化正确动作。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[true]]></summary></entry><entry><title type="html">Specifying Systems 第十四章笔记</title><link href="/2019/12/05/tla-plus-book-ch14.html" rel="alternate" type="text/html" title="Specifying Systems 第十四章笔记" /><published>2019-12-05T00:00:00+00:00</published><updated>2019-12-05T00:00:00+00:00</updated><id>/2019/12/05/tla-plus-book-ch14</id><content type="html" xml:base="/2019/12/05/tla-plus-book-ch14.html"><![CDATA[<h1 id="141-介绍-tlc">14.1 介绍 TLC</h1>

<p>TLC 是检查 TLA+ 错误的程序，它能够处理<code class="highlighter-rouge">Spec == Init /\ [][Next]_&lt;&lt;vars&gt;&gt; /\ Temporal</code>形式的大部分规范，这通过在配置文件中使用关键字<code class="highlighter-rouge">SPECIFICATION Spec</code>指定。如果规范不包含时序逻辑，也可以使用<code class="highlighter-rouge">INIT Init</code>和<code class="highlighter-rouge">NEXT Next</code>指定。TLC 中要检查的规范叫做模型。</p>

<p>TLC 不能处理<code class="highlighter-rouge">\EE</code>运算符，用户应当单独检查<code class="highlighter-rouge">\EE</code>涉及的子规范。这可以通过 5.8 节介绍的，为要使用<code class="highlighter-rouge">\EE</code>隐藏的变量在实例化时显式指定映射来绕过。</p>

<p>TLC 可以检查如下种类的情况：</p>

<ul>
  <li>
    <p>无意义的表达式（6.2 节）</p>
  </li>
  <li>
    <p>死锁，即<code class="highlighter-rouge">Next</code>步的前提不能被满足。但有的系统允许死锁表示系统按预期终止</p>
  </li>
  <li>
    <p>使用<code class="highlighter-rouge">PROPERTY</code>关键字检查某公式是否为真。TLC 实际上并不是检查<code class="highlighter-rouge">Spec =&gt; Prop</code>，而是对于<code class="highlighter-rouge">SPECIFICATION</code>和<code class="highlighter-rouge">PROPERTY</code>分拆安全性和活跃性部分，分别检查</p>

    <ul>
      <li>
        <p><code class="highlighter-rouge">SPECIFICATION</code>的安全性蕴含<code class="highlighter-rouge">PROPERTY</code>安全性</p>
      </li>
      <li>
        <p><code class="highlighter-rouge">SPECIFICATION</code>蕴含<code class="highlighter-rouge">PROPERTY</code>的活跃性</p>
      </li>
    </ul>

    <p>由于<code class="highlighter-rouge">PROPERTY</code>的安全性和活跃性分别检查，因此 TLC 不能处理两者相互影响的情况，也即不能处理非状态机闭包</p>
  </li>
  <li>
    <p>如果想要表达公式一直为真，可以使用<code class="highlighter-rouge">[]</code>或者<code class="highlighter-rouge">INVARIANT</code>关键字描述。如果使用<code class="highlighter-rouge">INVARIANT</code>关键字，要检查的必须是一个单状态公式</p>
  </li>
</ul>

<p>TLC 在开始检查前需要指定规范中常数参数的取值。</p>

<p>TLC 有两种工作模式，检查<strong>所有</strong>可到达的状态，即模型检查模式；或者随机生成状态作为系统行为，即模拟模式。本节我们先讨论前者。</p>

<p>对于状态过多、或者无穷状态的规范，我们可以指定约束限制一些不必要状态，这可以使用<code class="highlighter-rouge">CONSTRAINT</code>关键字指定约束公式。</p>

<p>TLC 对于不同类型公式的检查速度不同，建议按照类型不变量、检查安全性、检查活跃性的顺序进行检查。完成检查后，我们还可以编写一个“事后规范”，检查规范是否能蕴含这个“事后规范”。</p>

<h1 id="142-tlc-能处理什么">14.2 TLC 能处理什么</h1>

<p>TLA+ 的描述性太强，不存在能检查 TLA+ 所有规范的工具。TLC 能处理 TLA+ 的大多数情况。</p>

<h4 id="1421-tlc-值">14.2.1 TLC 值</h4>

<p>TLC 能表达的值比 TLA+ 更有限，TLC 值的四种基本类型是布尔型、整数、字符串、作为常量符号的模型值。TLC 值是由</p>

<ul>
  <li>
    <p>基本类型</p>
  </li>
  <li>
    <p><em>可比较</em>的 TLC 值构成的有限集</p>
  </li>
  <li>
    <p>定义域和值域全部是 TLC 值的函数</p>
  </li>
</ul>

<p>组成。两个值<em>可比较</em>的定义是 TLA+ 语义下两个值可以判断是否相等。字符串和数字在 TLA+ 中是不能判断相等的。包含不同个数元素的集合，即使元素分别是是字符串和数字，是可以判断不相等的。更精确的定义见 14.7.2</p>

<h4 id="1422-tlc-如何对表达式求值">14.2.2 TLC 如何对表达式求值</h4>

<p>检查规范的过程就是对表达式求值，查看结果是否为真的过程。</p>

<p>TLC 有短路特性，以此避免越界。</p>

<p>TLC 只能处理绑定变量<code class="highlighter-rouge">\E x \in S: p</code>的表达式，而且<code class="highlighter-rouge">S</code>必须是有限集。与 TLA+ 不同，TLC 不能处理未绑定变量的表达式<code class="highlighter-rouge">\E x: p</code>。</p>

<p>TLC 在<code class="highlighter-rouge">CHOOSE</code>无法取到合法的值时会报错。</p>

<p>TLC 仅在需要的时候求值，例如<code class="highlighter-rouge">[n \in Nat |-&gt; n * (n + 1)][3]</code>中<code class="highlighter-rouge">Nat</code>不是有限集，但可以求值。</p>

<p>TLC 对于合法的递归表达式有时不能求值，例如 6.3 节使用<code class="highlighter-rouge">record.f</code>和<code class="highlighter-rouge">record.g</code>解决依赖多个函数的问题。TLC 会完全对<code class="highlighter-rouge">record</code>求值，再取它的成员，因此有些情况下会造成自我依赖，进入死循环。</p>

<h4 id="1423-赋值和替换">14.2.3 赋值和替换</h4>

<p>TLC 的赋值指的是配置文件<code class="highlighter-rouge">CONSTANT</code>部分<code class="highlighter-rouge">c = v</code>形式的语句。<code class="highlighter-rouge">v</code>必须是一个 TLC 值，或者是 TLC 值的有限集。<code class="highlighter-rouge">v</code>可以包含模型中的某个变量，即模型值，作为一个常量符号。<code class="highlighter-rouge">c</code>可以是规范中定义的符号或者常数参数，TLC 会对其进行赋值或者覆盖。覆盖常常用于使用模型值表示 TLC 不能表达的 TLA+ 表达式，为了提高可读性，一般写作<code class="highlighter-rouge">symbol = symbol</code>。</p>

<p><code class="highlighter-rouge">CONSTANT</code>部分可以应用替换，表示成<code class="highlighter-rouge">c &lt;- d</code>的形式。替换与复制的区别是替换的<code class="highlighter-rouge">d</code>需要是有定义的符号，而<code class="highlighter-rouge">v</code>需要是 TLC 值。替换可以用于，在一个简单、快速写成的规范中用更高效的实现替换部分标识符。</p>

<h4 id="1424-时序逻辑公式求值">14.2.4 时序逻辑公式求值</h4>

<p>TLC 可以对满足如下条件的 TLA+ 时序逻辑公式求值：表达式良定义并且其“组成部分”都可以求值。良定义指的是表达式由如下四种公式合取而成：</p>

<ul>
  <li>
    <p>单状态公式</p>
  </li>
  <li>
    <p>不变量公式<code class="highlighter-rouge">[]P</code>，其中<code class="highlighter-rouge">P</code>是单状态公式</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">[][A]_v</code>，其中<code class="highlighter-rouge">A</code>是动作<code class="highlighter-rouge">v</code>是单状态公式</p>
  </li>
  <li>
    <p>由如下两种合取而成的公式</p>

    <ul>
      <li>
        <p>单状态时序逻辑公式，既不包含<code class="highlighter-rouge">'</code>变量</p>
      </li>
      <li>
        <p>满足公平性、<code class="highlighter-rouge">[]&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>、<code class="highlighter-rouge">&lt;&gt;[][A]_v</code>的形式，其中<code class="highlighter-rouge">A</code>是动作<code class="highlighter-rouge">v</code>是单状态公式。公平性的“组成部分”是<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>和<code class="highlighter-rouge">ENABLED &lt;&lt;A&gt;&gt;_v</code></p>
      </li>
    </ul>
  </li>
</ul>

<h4 id="1425-覆盖模块">14.2.5 覆盖模块</h4>

<p>TLC 支持使用 Java 实现覆盖模块，标准模块<code class="highlighter-rouge">Naturals</code>，<code class="highlighter-rouge">Integers</code>，<code class="highlighter-rouge">Sequences</code>，<code class="highlighter-rouge">FiniteSets</code>，<code class="highlighter-rouge">Bags</code>已经被覆盖。</p>

<h4 id="1426-tlc-如何计算状态">14.2.6 TLC 如何计算状态</h4>

<p>状态就是所有变量都具有对应的值，TLC 将不带<code class="highlighter-rouge">'</code>的变量计算对应的值，然后计算<code class="highlighter-rouge">Next</code>公式。<code class="highlighter-rouge">Next</code>公式的计算与 14.2.2 节提到的计算有两点不同：</p>

<ol>
  <li>
    <p>析取子式（包括<code class="highlighter-rouge">\/</code>，<code class="highlighter-rouge">\E</code>，<code class="highlighter-rouge">=&gt;</code>）隔离计算。合取子式仍然会依次计算并提前终止。</p>
  </li>
  <li>
    <p>对于第一次出现的<code class="highlighter-rouge">'</code>的变量<code class="highlighter-rouge">x' = e</code>进行赋值并对公式返回<code class="highlighter-rouge">TRUE</code>。除这种情况以外，TLC 会对<code class="highlighter-rouge">'</code>的变量的访问报错。</p>
  </li>
</ol>

<p>对于<code class="highlighter-rouge">ENABLED A</code>这种公式，TLC 把<code class="highlighter-rouge">A</code>作为<code class="highlighter-rouge">Next</code>公式进行计算，如果有至少一个可达状态，<code class="highlighter-rouge">ENABLED A</code>的值就为真。</p>

<h1 id="143-tlc-如何检查性质">14.3 TLC 如何检查性质</h1>

<p>14.1 节介绍 TLC 有两种工作状态，检查所有行为或者随机模拟一些行为。首先我们介绍前者。</p>

<p>将 TLC 配置文件中的各段进行整理，可以得到如下几类公式。定义某类公式为空表示为真：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">Init</code>，由<code class="highlighter-rouge">INIT</code>指定，或者是<code class="highlighter-rouge">SPECIFICATION</code>中所有单状态公式的合取</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">Next</code>，由<code class="highlighter-rouge">NEXT</code>指定，或者是<code class="highlighter-rouge">SPECIFICATION</code>中所有形如<code class="highlighter-rouge">[][N]_v</code>的<code class="highlighter-rouge">N</code>合取</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">Temporal</code>，<code class="highlighter-rouge">SPECIFICATION</code>中既不是单状态公式也不是<code class="highlighter-rouge">[][N]_v</code>的公式合取，通常表达活跃性</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">Invariant</code>，由<code class="highlighter-rouge">INVARIANT</code>指定，或者是<code class="highlighter-rouge">PROPERTY</code>中形如<code class="highlighter-rouge">[][I]</code>的<code class="highlighter-rouge">I</code>合取</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">ImpliedInit</code>，<code class="highlighter-rouge">PROPERTY</code>中单状态公式的合取</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">ImpliedAction</code>，<code class="highlighter-rouge">PROPERTY</code>中形如<code class="highlighter-rouge">[][A]_v</code>的<code class="highlighter-rouge">[A]_v</code>合取</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">ImpliedTemporal</code>，<code class="highlighter-rouge">PROPERTY</code>中除<code class="highlighter-rouge">Invariant</code>以外的时序逻辑公式</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">Constraint</code>，由<code class="highlighter-rouge">CONSTRAINT</code>指定</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">ActionConstraint</code>，由<code class="highlighter-rouge">ACTION-CONSTRAINT</code>指定。该类公式与<code class="highlighter-rouge">Constraint</code>类似，但是<code class="highlighter-rouge">Constraint</code>排除了一些状态，<code class="highlighter-rouge">ActionConstraint</code>排除了一些状态转移。<code class="highlighter-rouge">Constraint P</code>等价于<code class="highlighter-rouge">ActionConstraint P'</code></p>
  </li>
</ul>

<h4 id="1431-模型检查模式">14.3.1 模型检查模式</h4>

<p>该模式下 TLC 维护两个数据结构：有向图<code class="highlighter-rouge">G</code>的点集、边集分别存储可达的状态和转移，队列<code class="highlighter-rouge">U</code>存储<code class="highlighter-rouge">G</code>中尚未检查邻接点合法性的点。</p>

<p>TLC 维护的不变量包括：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">G</code>中的状态满足<code class="highlighter-rouge">Constraint</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">G</code>中的状态有自环边</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">G</code>中的状态是由满足<code class="highlighter-rouge">Init</code>的状态可达的</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">G</code>中的边满足<code class="highlighter-rouge">Next /\ ActionConstraint</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">U</code>中的点不重复，且都在<code class="highlighter-rouge">G</code>中</p>
  </li>
  <li>
    <p>对于<code class="highlighter-rouge">G</code>中不在<code class="highlighter-rouge">U</code>中的点<code class="highlighter-rouge">s</code>，如果<code class="highlighter-rouge">t</code>满足<code class="highlighter-rouge">Constraint</code>且边<code class="highlighter-rouge">(s, t)</code>满足<code class="highlighter-rouge">Next /\ ActionConstraint</code>，那么<code class="highlighter-rouge">t</code>和<code class="highlighter-rouge">(s, t)</code>也在<code class="highlighter-rouge">G</code>中</p>
  </li>
</ul>

<p>TLC 按照如下流程进行检查：</p>

<ul>
  <li>
    <p>检查 TLC 常数参数的指定是符合<code class="highlighter-rouge">ASSUME</code>的</p>
  </li>
  <li>
    <p>按照<code class="highlighter-rouge">Init</code>计算初始状态。对于每个初始状态<code class="highlighter-rouge">s</code>：</p>

    <ul>
      <li>
        <p>检查<code class="highlighter-rouge">Invariant</code>和<code class="highlighter-rouge">ImpliedInit</code></p>
      </li>
      <li>
        <p>如果<code class="highlighter-rouge">s</code>满足<code class="highlighter-rouge">Constraint</code>，将<code class="highlighter-rouge">s</code>添加到<code class="highlighter-rouge">G</code>和<code class="highlighter-rouge">U</code>中，将<code class="highlighter-rouge">(s, s)</code>添加到<code class="highlighter-rouge">G</code>中</p>
      </li>
    </ul>
  </li>
  <li>
    <p>当<code class="highlighter-rouge">U</code>不为空时：</p>

    <ul>
      <li>
        <p>队首出队，记为<code class="highlighter-rouge">s</code></p>
      </li>
      <li>
        <p>计算<code class="highlighter-rouge">s</code>所有可达的状态，记为集合<code class="highlighter-rouge">T</code></p>
      </li>
      <li>
        <p>如果<code class="highlighter-rouge">T</code>是空集而且 TLC 配置为不允许死锁，报告错误</p>
      </li>
      <li>
        <p>对于<code class="highlighter-rouge">T</code>中的每个状态<code class="highlighter-rouge">t</code>：</p>

        <ul>
          <li>
            <p>检查<code class="highlighter-rouge">t</code>满足<code class="highlighter-rouge">Invariant</code>，<code class="highlighter-rouge">(s, t)</code>满足<code class="highlighter-rouge">ImpliedAction</code></p>
          </li>
          <li>
            <p>如果<code class="highlighter-rouge">t</code>满足<code class="highlighter-rouge">Constraint</code>且<code class="highlighter-rouge">(s, t)</code>满足<code class="highlighter-rouge">ActionConstraint</code>：</p>

            <ul>
              <li>如果<code class="highlighter-rouge">t</code>不在<code class="highlighter-rouge">G</code>中，将其加到<code class="highlighter-rouge">G</code>中，并在<code class="highlighter-rouge">U</code>的队尾入队。<code class="highlighter-rouge">(s, t)</code>、<code class="highlighter-rouge">(t, t)</code>加到<code class="highlighter-rouge">G</code>中</li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>如果<code class="highlighter-rouge">ImpliedTemporal</code>不为空，在<code class="highlighter-rouge">G</code>每次加边时 TLC 都会为新增加的边求出<code class="highlighter-rouge">Temporal</code>和<code class="highlighter-rouge">ImpliedTemporal</code>的值。TLC 周期性检查<code class="highlighter-rouge">G</code>中由初始状态出发形成的无限长的路径是否满足<code class="highlighter-rouge">Temporal =&gt; ImpliedTemporal</code>。</p>

<h4 id="1432-模拟模式">14.3.2 模拟模式</h4>

<p>模拟模式不尝试检查所有的状态，因此可以看作<code class="highlighter-rouge">U</code>的最大长度限制为 1。在行为的状态数达到设定值的时候，TLC 进行时序逻辑相关性质的检查，并将<code class="highlighter-rouge">G</code>和<code class="highlighter-rouge">U</code>恢复到初始状态重新进行模拟。</p>

<h4 id="1433-视图和指征">14.3.3 <em>视图</em>和<em>指征</em></h4>

<p>在模型检查模式中，<code class="highlighter-rouge">G</code>中的节点实际上是<em>视图</em>而不是状态，这么做的原因是<em>视图</em>可以控制仅用部分变量区分<code class="highlighter-rouge">G</code>的点。<em>视图</em>在配置文件的<code class="highlighter-rouge">VIEW</code>进行指定。<em>视图</em>常见的应用是，用户为了调试规范增加了一些辅助变量，但是这样会增大搜索空间，所以用<em>视图</em>指定为原始的非调试变量。</p>

<p>由于<em>视图</em>限制了一些状态加入<code class="highlighter-rouge">G</code>，TLC 可能会因此将时序逻辑性质判断错误。</p>

<p>在实现中，TLC 是通过<em>视图</em>的哈希，即<em>指征</em>进行区分。这样又引入了哈希碰撞引发的错误概率。TLC 会在模型检查的结果中汇报这种概率。</p>

<p>在模拟模式中，没有这两个概念以及它们潜在的错误。</p>

<h4 id="1434-利用对称性">14.3.4 利用对称性</h4>

<p>对称性指轮换对称性，在配置文件中通过<code class="highlighter-rouge">SYMMETRY Perms</code>指定。<code class="highlighter-rouge">Perms</code>常常通过 TLC 模块的<code class="highlighter-rouge">Permutations(S)</code>运算符表示<code class="highlighter-rouge">S</code>的元素是满足轮换对称性的。在 TLC 增长<code class="highlighter-rouge">G</code>和<code class="highlighter-rouge">U</code>时检查轮换对称性引起的重复，从而避免添加不必要的状态。如果有多个集合<code class="highlighter-rouge">S1</code>，<code class="highlighter-rouge">S2</code>……，可以表达成<code class="highlighter-rouge">Permutations(S1) \union Permutations(S2) ...</code>。</p>

<p>TLC 同样会因为对称性跳过状态从而将时序逻辑性质判断错误。</p>

<p>在误用对称性时，TLC 会报告<code class="highlighter-rouge">Failed to recover the state from its fingerprint.</code></p>

<p>对称性也是仅在模型检查模式有效。</p>

<h4 id="1435-活跃性检查的局限">14.3.5 活跃性检查的局限</h4>

<p>在某些情况下，TLC 需要限制搜索空间的数目即<code class="highlighter-rouge">G</code>的大小，从而可能导致规范的活跃性不满足。例如对于</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EvenSpec == (x = 0) /\ [][x' = x + 2]_x /\ WF_x(x' = x + 2)
</code></pre></div></div>
<p>检查<code class="highlighter-rouge">&lt;&gt;(x = 1)</code>。TLC 不能处理无穷的状态，为了检查能终止，需要用<code class="highlighter-rouge">CONSTRAINT</code>限制<code class="highlighter-rouge">G</code>的大小（此处为<code class="highlighter-rouge">x</code>的取值）。而<code class="highlighter-rouge">x</code>的限制取值会导致<code class="highlighter-rouge">G</code>产生的无穷行为末尾出现<code class="highlighter-rouge">x</code>保持不变的无限个状态，因此弱公平性为假。按照蕴含的定义，<code class="highlighter-rouge">WF_x(x' = x + 2) =&gt; &lt;&gt;(x = 1)</code>为真，也就是 TLC 没有检查出错误。</p>

<p>为了避免这个情况，可以用一个必定为假的活跃性性质，检查 TLC 有没有正常工作。</p>

<h1 id="144-tlc-模块">14.4 TLC 模块</h1>

<p>TLC 模块提供了一些辅助功能。</p>

<p><code class="highlighter-rouge">Print(out, val)</code>运算符的值是<code class="highlighter-rouge">val</code>，但是 TLC 在对其进行求值时会打印<code class="highlighter-rouge">out</code>。</p>

<p><code class="highlighter-rouge">Assert(val, out)</code>起到了断言的作用，如果<code class="highlighter-rouge">val</code>为假，TLC 会打印<code class="highlighter-rouge">out</code>并终止。</p>

<p><code class="highlighter-rouge">JavaTime</code>会打印当前时间。</p>

<p><code class="highlighter-rouge">1 :&gt; "ab" @@ 2 :&gt; "cd"</code>表示定义域为<code class="highlighter-rouge">{1, 2}</code>取值分别为<code class="highlighter-rouge">{"ab", "cd"}</code>的函数。</p>

<p>前文中提到的<code class="highlighter-rouge">Permutations(S)</code>定义如下：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Permutations(S) == {f \in [S -&gt; S]: \A w \in S : \E v \in S : f[v] = w}
</code></pre></div></div>
<p>使用<code class="highlighter-rouge">Permutations(S)</code>和<code class="highlighter-rouge">:&gt;</code>、<code class="highlighter-rouge">@@</code>运算符，我们可以表达更复杂的对称性。例如对于两个处理器<code class="highlighter-rouge">p1</code>、<code class="highlighter-rouge">p2</code>分别有相关的地址<code class="highlighter-rouge">a11</code>、<code class="highlighter-rouge">a12</code>和<code class="highlighter-rouge">a21</code>、<code class="highlighter-rouge">a22</code>，我们可以定义对称性为</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Permutations({a11, a12}) \union {p1 :&gt; p2 @@ p2 :&gt; p1
                                  @@ a11 :&gt; a21 @@ a21 :&gt; a11
                                  @@ a12 :&gt; a22 @@ a22 :&gt; a12}
</code></pre></div></div>
<p>这个并集操作两边分别是交换第一个处理器相关的地址，以及交换两个处理器。可以验证所有的对称性可以通过这两个交换构造出来。</p>

<p><code class="highlighter-rouge">SortSeq(s, Op(_, _))</code>运算符对序列按照全序运算符<code class="highlighter-rouge">Op</code>进行排序。</p>

<h1 id="145-如何使用-tlc">14.5 如何使用 TLC</h1>

<h4 id="1451-运行-tlc">14.5.1 运行 TLC</h4>

<p>TLC 在运行时有诸多配置，包括：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">-deadlock</code>不检查死锁</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-simulate</code>模拟模式</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-depth num</code>模拟模式下行为的长度，默认为 100</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-seed num</code>模拟模式下随机数种子，取值在 -2^63 到 2^63 - 1 之间</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-aril num</code>模拟模式下影响初始化的随机数种子</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-coverage num</code>每隔<code class="highlighter-rouge">num</code>分钟打印赋值相关动作的执行覆盖情况</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-recover run_id</code>表示从<code class="highlighter-rouge">run_id</code>的存盘点继续</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-cleanup</code>清理临时文件，注意多个 TLC 实例运行时该选项会破坏运行中的实例</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-difftrace num</code>打印错误时使用在状态转移之间仅打印差异部分</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-terse</code>打印错误时打印精简的变量值</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-workers num</code>使用<code class="highlighter-rouge">num</code>个线程寻找可达状态，默认为 1</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-config config_file</code>表示使用<code class="highlighter-rouge">config_file</code>作为配置文件，默认与 TLA+ 文件同名</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">-nowarning</code>不显示警告</p>
  </li>
</ul>

<h4 id="1452-调试规范">14.5.2 调试规范</h4>

<p>TLC 在运行时会打印很多信息，这里仅介绍几个不直观的输出：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Implied-temporal checking--relative complexity = 8.
</code></pre></div></div>
<p>表示估计的时序逻辑公式相对于安全性公式的复杂度，这个值越高在检查活跃性时耗时越长。</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Progress(9): 2846 states generated, 984 distinct states found. 856 states left on queue.
</code></pre></div></div>
<p>括号中的<code class="highlighter-rouge">9</code>表示直径，即<code class="highlighter-rouge">G</code>中从初始状态延伸的路径长度的最大值。在多线程情况下，这个值可能并不准确。<code class="highlighter-rouge">856</code>即为<code class="highlighter-rouge">U</code>的长度，一般而言整个过程中该长度先上升后下降。</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- Checkpointing run states/99-05-20-15-47-55 completed
</code></pre></div></div>
<p><code class="highlighter-rouge">states/99-05-20-15-47-55</code>即为<code class="highlighter-rouge">run_id</code>存盘点。</p>

<h4 id="1453-高效使用-tlc-的提示">14.5.3 高效使用 TLC 的提示</h4>

<p>先为模型的常数参数赋较小的值，再逐渐增大。模拟模式在运气好的时候也能暴露错误。</p>

<p>对“成功”结果表示怀疑，因为有些规范中的错误会导致搜索状态意外减少。这可以通过<code class="highlighter-rouge">-coverage num</code>选项的输出或者添加额外的检查条件来排查。</p>

<p>更高效利用 TLC，例如分拆不变量，使用<code class="highlighter-rouge">Print</code>打印变量的变化，将出错的状态设置为初始状态、<code class="highlighter-rouge">Next</code>公式从而快速出错。</p>

<p>出错后不要急着从头开始。先使用上一条中提到的“快速出错”确保错误已经被修复。另外由于存盘点只保存<code class="highlighter-rouge">G</code>和<code class="highlighter-rouge">U</code>，如果修正错误后的规范没有改变变量名，可以复用之前的存盘点。</p>

<p>尽量多检查各种性质。</p>

<p>TLC 的目的是检查错误，因此为了绕过 TLC 的限制可以使用替换等方式调整规范。比如对于<code class="highlighter-rouge">Nat</code>等无限大小的集合使用<code class="highlighter-rouge">0..n</code>代替。</p>

<p>使用<code class="highlighter-rouge">ASSUME</code>快速计算 TLA+ 中简单公式的值。</p>

<h1 id="146-tlc-不能做什么">14.6 TLC 不能做什么</h1>

<p>TLC 的限制还包括：<code class="highlighter-rouge">Naturals</code>和<code class="highlighter-rouge">Integers</code>模块的 Java 实现实际上只能处理 -2^63 到 2^63 - 1 之间的值。</p>

<p>TLC 的部分语义也与 TLA+ 不同。包括：TLC 的<code class="highlighter-rouge">CHOOSE</code>运算符只保证对句法上相同的集合选出相同的元素，因此不保证<code class="highlighter-rouge">{1, 2}</code>与<code class="highlighter-rouge">{2, 1}</code>选出相同的元素；字符串是基本数据类型而不是函数，因此取下标会报错。</p>

<h1 id="147-更精确的表述">14.7 更精确的表述</h1>

<h4 id="1471-配置文件的语法">14.7.1 配置文件的语法</h4>

<p>配置文件的语法可以用 BNF 表示，详见原书。除此之外，<code class="highlighter-rouge">INIT</code>、<code class="highlighter-rouge">NEXT</code>定义只能有一次，<code class="highlighter-rouge">SPECIFICATION</code>定义只能出现一次且不能与<code class="highlighter-rouge">INIT</code>、<code class="highlighter-rouge">NEXT</code>同时出现。<code class="highlighter-rouge">VIEW</code>和<code class="highlighter-rouge">SYMMETRY</code>定义同样只能出现一次。除此以外的段都能定义多次。</p>

<h4 id="1472-可比较的-tlc-值">14.7.2 可比较的 TLC 值</h4>

<p>满足如下规则的值是可比较的：</p>

<ul>
  <li>
    <p>对于基本数据类型，如果类型相同就可比较</p>
  </li>
  <li>
    <p>模型值总是可比较的，因为它仅与自己相等</p>
  </li>
  <li>
    <p>集合的可比较定义为大小不同，或者大小相同且元素两两可比较</p>
  </li>
  <li>
    <p>函数的可比较仅当定义域可比较，或者定义域相同且相同的<code class="highlighter-rouge">x</code>对应的值可比较</p>
  </li>
</ul>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[14.1 介绍 TLC]]></summary></entry><entry><title type="html">Specifying Systems 第十三章笔记</title><link href="/2019/12/04/tla-plus-book-ch13.html" rel="alternate" type="text/html" title="Specifying Systems 第十三章笔记" /><published>2019-12-04T00:00:00+00:00</published><updated>2019-12-04T00:00:00+00:00</updated><id>/2019/12/04/tla-plus-book-ch13</id><content type="html" xml:base="/2019/12/04/tla-plus-book-ch13.html"><![CDATA[<h1 id="131-介绍">13.1 介绍</h1>

<p>TLATeX 使用 LaTeX 作为排版引擎由 tla 文件输出 PDF 等更易读的文件。TLATeX 提供了显示行号、传递 LaTeX 命令等功能，详见文档。</p>

<h1 id="132-注释阴影化">13.2 注释阴影化</h1>

<p>TLATeX 可以控制注释区域的阴影深度，或者结合其他工具调整阴影。</p>

<h1 id="133-对规范进行排版">13.3 对规范进行排版</h1>

<p>规范的排版不会破坏跨行对齐。符号之间不使用空格与使用一个空格的排版相同。排版会将模块前后的文字一同处理，这种特性也可以被屏蔽。排版会检查部分词法错误。</p>

<h1 id="134-对注释进行排版">13.4 对注释进行排版</h1>

<p>TLATeX 会区分单行注释与多行注释。多行注释有如下三种写法：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(**************)  \*************  (* This
(* This is    *)  \* This is         is a
(* a comment. *)  \* a comment.      comment. *)
(**************)  \************* 
</code></pre></div></div>
<p>其中前两种需要注意对齐。</p>

<p>多行注释的排版会自动检测段落、表格等多种情况进行折行，但不会进行跨行对齐。</p>

<p>注释的一些语法：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`这部分注释应用规范的样式（包括规范中的字符串样式）'
`^这部分注释应用注释的样式，交给 LaTeX 进行排版^'
`.这部分注释不进行排版，以 ASCII 形式输出.'
``本行首末是左右引号''
`~这部分注释不会显示~'
</code></pre></div></div>
<h1 id="135-调整输出格式">13.5 调整输出格式</h1>

<p>TLATeX 可以调整字号、文字高度宽度、偏移量。</p>

<h1 id="136-输出文件">13.6 输出文件</h1>

<p>TLATeX 可以配置输出文件的文件名、ASCII 版本的输出、LaTeX 样式。</p>

<h1 id="137-故障诊断">13.7 故障诊断</h1>

<p>TLATeX 会依次产生一些中间文件，并最终输出 PDF 等阅读文件。命令行输出和中间文件日志有助于定位问题。</p>

<h1 id="138-使用-latex-命令">13.8 使用 LaTeX 命令</h1>

<p>通过</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`^这部分注释应用注释的样式，交给 LaTeX 进行排版^'
</code></pre></div></div>
<p>这种方式使用 LaTeX 进行排版。由于 LaTeX 代码的可读性差，因此推荐使用如下方式同时维护一份 ASCII 可读的注释</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\*************************************
\* `^ \begin{describe}{gnat:}
\*     \item[gnat:] A tiny insect.
\*     \item[gnu:]  A short word.
\*    \end{describe} ^'
\* `~ gnat: A tiny insect.
\*
\*    gnu:  A short word. ~'
\*************************************
</code></pre></div></div>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[13.1 介绍]]></summary></entry><entry><title type="html">Specifying Systems 第十二章笔记</title><link href="/2019/12/03/tla-plus-book-ch12.html" rel="alternate" type="text/html" title="Specifying Systems 第十二章笔记" /><published>2019-12-03T00:00:00+00:00</published><updated>2019-12-03T00:00:00+00:00</updated><id>/2019/12/03/tla-plus-book-ch12</id><content type="html" xml:base="/2019/12/03/tla-plus-book-ch12.html"><![CDATA[<p>语法分析器能发现两种错误：语法错误与语义错误。更准确地说，语法错误是只违反了 BNF 语法或者优先级与对齐规则；语义错误将在十七章介绍，这两种错误意味着规范不能表达有意义的描述，而不是规范表达了错误的描述。</p>

<p>在发现错误时，语法分析器会打印当前的解析栈辅助定义问题。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[语法分析器能发现两种错误：语法错误与语义错误。更准确地说，语法错误是只违反了 BNF 语法或者优先级与对齐规则；语义错误将在十七章介绍，这两种错误意味着规范不能表达有意义的描述，而不是规范表达了错误的描述。]]></summary></entry><entry><title type="html">Specifying Systems 第十一章笔记</title><link href="/2019/11/29/tla-plus-book-ch11.html" rel="alternate" type="text/html" title="Specifying Systems 第十一章笔记" /><published>2019-11-29T00:00:00+00:00</published><updated>2019-11-29T00:00:00+00:00</updated><id>/2019/11/29/tla-plus-book-ch11</id><content type="html" xml:base="/2019/11/29/tla-plus-book-ch11.html"><![CDATA[<h1 id="111-数据结构的规范">11.1 数据结构的规范</h1>

<h4 id="1111-定义局部标识符">11.1.1 定义局部标识符</h4>

<p><code class="highlighter-rouge">LOCAL</code>关键词可以控制所修饰的<strong>定义</strong>标识符以及<strong>实例化</strong><code class="highlighter-rouge">INSTANCE</code>不导出到其他模块中，避免与其他模块的标识符冲突。但是<code class="highlighter-rouge">LOCAL</code>不能修饰变量、常量的<strong>声明</strong>，以及<code class="highlighter-rouge">EXTENDS</code>。</p>

<p>上一段说在<code class="highlighter-rouge">LOCAL</code>的语法上，<code class="highlighter-rouge">INSTANCE</code>和<code class="highlighter-rouge">EXTENDS</code>是不等同的。但是如果模块没有变量、常量的声明或者子模块，那么该模块的<code class="highlighter-rouge">INSTANCE</code>和<code class="highlighter-rouge">EXTENDS</code>效果一致（见 4.2.4 同名实例化），因此可以用<code class="highlighter-rouge">INSTANCE</code>导入并用<code class="highlighter-rouge">LOCAL</code>修饰。</p>

<h4 id="1112-图">11.1.2 图</h4>

<p>我们以图为例解释如何定义一个数据结构，具体而言，定义一个没有外部声明的有向图<code class="highlighter-rouge">Graphs</code>模块，以方便图与图之间的运算。<code class="highlighter-rouge">Graphs</code>模块通过<em>记录</em>访问两个域，对应点集<code class="highlighter-rouge">N</code>、边集<code class="highlighter-rouge">E</code>。该模块对外提供如下运算符：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">IsDirectedGraph(G)</code>：当且仅当<code class="highlighter-rouge">G</code>的<code class="highlighter-rouge">E</code>是<code class="highlighter-rouge">N \X N</code>的子集。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">DirectedSubgraph(G)</code>：返回有向图<code class="highlighter-rouge">G</code>的所有子图。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">IsUndirectedGraph(G)</code>、<code class="highlighter-rouge">UndirectedSubgraph(G)</code>：将上述的运算符扩展到无向图，在<code class="highlighter-rouge">Graphs</code>模块中，无向边通过两条形成环的边表示。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">Path(G)</code>：图的路径。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">AreConnectedIn(m, n, G)</code>：两个点<code class="highlighter-rouge">m</code>和<code class="highlighter-rouge">n</code>是否是连通的。</p>
  </li>
</ul>

<p>在此省略定义的其他的运算符。<code class="highlighter-rouge">Graphs</code>模块如下：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>------------------------------- MODULE Graphs ------------------------------- 
LOCAL INSTANCE Naturals
LOCAL INSTANCE Sequences

IsDirectedGraph(G) ==
   /\ G = [node |-&gt; G.node, edge |-&gt; G.edge]
   /\ G.edge \subseteq (G.node \X G.node)

DirectedSubgraph(G) ==    
  {H \in [node : SUBSET G.node, edge : SUBSET (G.node \X G.node)] :
     IsDirectedGraph(H) /\ H.edge \subseteq G.edge}
-----------------------------------------------------------------------------
IsUndirectedGraph(G) ==
   /\ IsDirectedGraph(G)
   /\ \A e \in G.edge : &lt;&lt;e[2], e[1]&gt;&gt; \in G.edge

UndirectedSubgraph(G) == {H \in DirectedSubgraph(G) : IsUndirectedGraph(H)}
-----------------------------------------------------------------------------
Path(G) == {p \in Seq(G.node) :
             /\ p /= &lt;&lt; &gt;&gt;
             /\ \A i \in 1..(Len(p)-1) : &lt;&lt;p[i], p[i+1]&gt;&gt; \in G.edge}

AreConnectedIn(m, n, G) == 
  \E p \in Path(G) : (p[1] = m) /\ (p[Len(p)] = n)
=============================================================================
</code></pre></div></div>

<h4 id="1113-解微分方程">11.1.3 解微分方程</h4>

<p>9.5 节说明了使用<code class="highlighter-rouge">Integrate</code>运算符描述混合系统，为了表达<code class="highlighter-rouge">Integrate</code>运算符，需要在 TLA+ 中表述微分等高级的数学运算。在此仅摘录极限的 TLA+ 表达。</p>

<p>邻域<code class="highlighter-rouge">Nbhd(r, e) == {s \in Real: (r - e &lt; s) /\ (s &lt; r + e)}</code>。</p>

<p>δ-ε 极限表示法求导数<code class="highlighter-rouge">df(r)</code></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\A e \in PosReal :
  \E d \in PosReal : 
    \A s \in Nbhd(r,d) \ {r} : (f[s] - f[r]) / (s - r) \in Nbhd(df[r], e)
</code></pre></div></div>

<p>完整的微分方程表示以及<code class="highlighter-rouge">Integrate</code>运算符定义见原书。</p>

<h4 id="1114-bnf-语法">11.1.4 BNF 语法</h4>

<p>TLA+ 的语法可以表示 BNF 语法，例如<code class="highlighter-rouge">L &amp; M == {s \o t : s \in L, t \in M}</code>，<code class="highlighter-rouge">L | M == L \cup M</code>。更复杂的表达 BNF 语法的方法见原书。</p>

<h1 id="112-其他内存规范">11.2 其他内存规范</h1>

<h4 id="1121-接口">11.2.1 接口</h4>

<p>为了尽量模拟真实世界中的内存，我们首先明确一个内存应当具有什么性质，并使用接口描述。本节描述一个多处理器、异步通信、每个处理器通过多个寄存器与内存交互的接口。</p>

<h4 id="1122-正确性条件">11.2.2 正确性条件</h4>

<p>思考如何使用正确性描述这个接口的行为。我们规定对于单个处理器，内存的行为如同串行按照发起请求的顺序执行一样。处理器只有“读”操作会从内存返回值，我们以“读”操作为例更具体讨论如何描述内存的行为。</p>

<p>如果两个处理器对相同地址写入各自的值，然后进行有限或无限次的读操作，始终读到各自的值。这样的行为需要我们回应：某些优先顺序需要在无限长的行为中保持，还是仅在有限长的行为中保持？如果允许“有限长的行为中保持”，可以解释为内存对某个处理器的请求始终优先于另一个处理，因此发生了上述有限长的行为。但无论如何也不能解释无限长的行为，因此我们决定仅在有限长的行为中保持某些优先顺序。</p>

<p>另一个问题是：内存没有规定多个处理器之间的顺序，那么某个处理器能否使用另一个处理器未来的值？这自然也是不现实的。不使用未来的值也意味着在某一时刻等价的串行化顺序不因为未来的请求改变。</p>

<h4 id="1123-串行内存">11.2.3 串行内存</h4>

<p>对于“与某种串行化等价”的性质，常常使用一个序列记录这种串行化的历史。我们使用<code class="highlighter-rouge">opQ[p][i]</code>表示处理器<code class="highlighter-rouge">p</code>的第<code class="highlighter-rouge">i</code>个操作，使用<code class="highlighter-rouge">p</code>、<code class="highlighter-rouge">i</code>表示<code class="highlighter-rouge">opId</code>，即</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>opId == {oiv \in [proc: Proc, idx: Nat]: 
            oiv.idx \in DOMAIN opQ[oiv.proc]}
</code></pre></div></div>

<p>系统的行为可以概括为<code class="highlighter-rouge">IssueRequest(proc, req, reg)</code>、<code class="highlighter-rouge">RespondToRequest(proc, reg)</code>、<code class="highlighter-rouge">Internal</code>三种，后两者应当用公平性对系统的特性进一步描述。其中<code class="highlighter-rouge">RespondToRequest</code>在产生结果后会对<code class="highlighter-rouge">opQ</code>进行就地修改，因此每个寄存器至多有一个对应的未完成的<code class="highlighter-rouge">opQ</code>中的操作。读操作会有一个<code class="highlighter-rouge">source</code>域记录读到数值的来源，这也由<code class="highlighter-rouge">RespondToRequest</code>进行修改。</p>

<p>在描述规范时，尽量把内部状态描述得可以通过单状态公式表达安全性。本节这个单状态公式就是<code class="highlighter-rouge">Serializable</code>。</p>

<p>为了固定多个处理器请求的相对串行顺序，使用<code class="highlighter-rouge">opOrder</code>集合进行记录。如果二元组属于该集合，则表示发生了二元组的两个元素具有相对顺序。<code class="highlighter-rouge">Serializable</code>使用<code class="highlighter-rouge">opOrder</code>描述系统性质：<code class="highlighter-rouge">opOrder</code>包含的元素个数只能增多；<code class="highlighter-rouge">opQ[p]</code>的顺序符合<code class="highlighter-rouge">opOrder</code>；读操作会读到相同地址的最后一次写入，或者初始值。最后一个性质表示为存在一个全序<code class="highlighter-rouge">R</code>满足：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\A oi \in opId:
    ("source" \in DOMAIN opQ[oi.proc][oi.idx]) =&gt;
        ~(\E oj \in goodSource(oi):                         /* not exsit such oj
            /\ &lt;&lt;oj, oi&gt;&gt; \in R
            /\ (opQ[oi.proc][oi.idx].source /= InitWr) =&gt;
                (&lt;&lt;opQ[oi.proc][oi.idx].source, oj&gt;&gt; \in R) /* that issued after the source of oi
        )
</code></pre></div></div>

<p><code class="highlighter-rouge">goodSource(oi)</code>表示某个<code class="highlighter-rouge">opId</code>可能作为<code class="highlighter-rouge">source</code>域的所有请求。读操作的<code class="highlighter-rouge">RespondToRequest</code>会断定<code class="highlighter-rouge">goodSource(oi)</code>中存在符合<code class="highlighter-rouge">opOrder'</code>的请求作为响应。</p>

<p>接下来描述系统的活跃性，比较难的一点是表示内存最终能得到确定、唯一的串行化顺序。一种尝试是</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\A oi, oj:
    (oi \in opId) /\ (oj in opId) =&gt;
        ((oi /= oj) =&gt;
            WF_&lt;&lt;...&gt;&gt;(/\ Internal
                       /\ (&lt;&lt;oi, oj&gt;&gt; \in opOrder') \/ (&lt;&lt;oj, oi&gt;&gt; \in opOrder'))
        )
</code></pre></div></div>
<p>但是要注意该公式是时序逻辑公式，<code class="highlighter-rouge">(oi \in opId) /\ (oj in opId)</code>这种单状态公式只是描述初始状态，而不是每个状态。因此需要将其放入公平性之中应用到每个状态。</p>

<h4 id="1124-串行一致性内存">11.2.4 串行一致性内存</h4>

<p>如果取消“某个处理器使用另一个处理器未来的值”的限制，也即某一时刻等价的串行化顺序可以因为未来的请求改变，就得到更简单的串行一致性内存。首先我们使<code class="highlighter-rouge">opQ</code>变为一个先入先出队列，“队列”的特性仍然保证了单个处理器需要满足的顺序关系，而新增的“可以出队”特性能在一定条件下获取未来的处理器值。</p>

<p>此外新增<code class="highlighter-rouge">mem</code>记录内存中的值。如果在读操作的<code class="highlighter-rouge">opQ</code>出队的同时，读操作返回的值是<code class="highlighter-rouge">mem</code>中地址对应的值，多个处理器就能通过<code class="highlighter-rouge">mem</code>通信，从而满足读操作结果的解释性。而为了表示获取未来的值这一特性，读操作返回的值不必是<strong>当前</strong><code class="highlighter-rouge">mem</code>的值，如果它返回一个非当前值，则需要等待未来另一个处理器写入这个值时<code class="highlighter-rouge">opQ</code>才能出队。我们使用活跃性限定这种对未来的等待不能是毫无根据的无限等待。</p>

<h4 id="1125-对内存规范的思考">11.2.5 对内存规范的思考</h4>

<p>上面提到的内存规范，有的能实现，有的需要过高的计算复杂度（例如在队列中搜索合法状态）才能实现，有的则不能实现。例如串行一致性内存在这种满足规范的场景下是无法实现的：多个处理器相互依赖未来值。</p>

<p>那些不能实现的规范往往是因为其不是状态机绑定的，也就是说某些实现中的公式会阻止一些<em>步</em>的发生。按照 8.9.2 节的解释，非状态机绑定是因为公平性描述的动作不能蕴含<code class="highlighter-rouge">Next</code>。但即便如此，有时为了简单的描述系统，我们还是会使用那些无法实现的规范。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[11.1 数据结构的规范]]></summary></entry><entry><title type="html">Specifying Systems 第十章笔记</title><link href="/2019/11/25/tla-plus-book-ch10.html" rel="alternate" type="text/html" title="Specifying Systems 第十章笔记" /><published>2019-11-25T00:00:00+00:00</published><updated>2019-11-25T00:00:00+00:00</updated><id>/2019/11/25/tla-plus-book-ch10</id><content type="html" xml:base="/2019/11/25/tla-plus-book-ch10.html"><![CDATA[<h1 id="101-组合两个规范">10.1 组合两个规范</h1>

<p>以时钟为例，研究两个时钟组合而成的系统。如果我们把两个时钟的规范通过合取连接，经过化简后，“两时钟”系统会多出一个 next <em>步</em>，表示两个时钟同时发生改变，这种规范叫做<em>非交错</em>规范。与其相对的<em>交错</em>规范指的是子组件不同时改变的规范。</p>

<p>如果我们想要“两时钟”系统是一个<em>交错</em>规范，可以通过下面两种方式之一解决：</p>

<ol>
  <li>
    <p>让每个时钟的 next 动作显式合取另一个时钟的变量保持不变</p>
  </li>
  <li>
    <p>依然合取原始的单个时钟的规范，但是再合取<code class="highlighter-rouge">[][(x' = x) \/ (y' = y)]_&lt;&lt;x, y&gt;&gt;</code>，其中<code class="highlighter-rouge">x</code>、<code class="highlighter-rouge">y</code>是两个时钟的小时值</p>
  </li>
</ol>

<h1 id="102-组合多个规范">10.2 组合多个规范</h1>

<p>在涉及组合多个规范的<em>交错</em>规范时，上一节的方式 1 就变得不合适了，因为让子组件去限制其他所有组件比较复杂。我们将方法 2 进行泛化：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[][\E k \in C: \A i \in C \ {k}: vi' = vi]_&lt;&lt;v&gt;&gt;
</code></pre></div></div>
<p>这个公式表示只有<code class="highlighter-rouge">vk</code>在改变，其他<code class="highlighter-rouge">vi</code>都不变。</p>

<p>回到时钟的例子。如果是多个时钟的组合而成的子系统，其中<code class="highlighter-rouge">hr[k]</code>表示第<code class="highlighter-rouge">k</code>个时钟显示的小时的数组。上面公式的<code class="highlighter-rouge">v</code>能否用<code class="highlighter-rouge">hr</code>替代呢？答案是不能。因为<code class="highlighter-rouge">hr</code>没有被限定定义域，因此可能在意料之外的定义域上有未预期的行为。解决这个问题有两种方式：</p>

<ol>
  <li>
    <p>用<code class="highlighter-rouge">hrfcn == [k \in Clock |-&gt; hr[k]]</code>代替<code class="highlighter-rouge">v</code></p>
  </li>
  <li>
    <p>合取一个运算符<code class="highlighter-rouge">[]IsFcnOn(hr, Clock)</code>，其中<code class="highlighter-rouge">IsFcnOn(f, S) == f = [x \in S |-&gt; f[x]]</code></p>
  </li>
</ol>

<p>另外，我们可以利用<code class="highlighter-rouge">EXCEPT</code>进一步简化组合规范<code class="highlighter-rouge">N(k) == hrfcn' = [hrfcn EXCEPT ![k] = (hr[k] + 1) % 12]</code></p>

<h1 id="103-fifo">10.3 FIFO</h1>

<p>FIFO 模块的自由变量是<code class="highlighter-rouge">in</code>、<code class="highlighter-rouge">out</code>两个<code class="highlighter-rouge">Channel</code>，以及<code class="highlighter-rouge">Message</code>消息集合。FIFO 由如下三个组件组合而成：Sender 影响<code class="highlighter-rouge">in.val</code>、<code class="highlighter-rouge">in.rdy</code>，Buffer 影响<code class="highlighter-rouge">in.ack</code>、<code class="highlighter-rouge">q</code>、<code class="highlighter-rouge">out.val</code>、<code class="highlighter-rouge">out.rdy</code>，Receiver 影响<code class="highlighter-rouge">out.ack</code>。我们可以注意到有的初始化公式是跨组件的：<code class="highlighter-rouge">(in.ack = in.rdy) /\ (out.ack = out.rdy)</code>。对于跨组件的公式有如下三种处理方法：</p>

<ol>
  <li>
    <p>在公式涉及的所有组件的初始化公式中，重复地声明该公式</p>
  </li>
  <li>
    <p>选择一个组件，在它的初始化公式中声明该公式</p>
  </li>
  <li>
    <p>在所有组件的外部独立声明该公式。</p>
  </li>
</ol>

<p>在描述<em>开系统</em>时，需要使用后两种方法。</p>

<h1 id="104-带有共享状态的组合">10.4 带有共享状态的组合</h1>

<h4 id="1041-显式状态变化">10.4.1 显式状态变化</h4>

<p>把上面的 FIFO 规范中的 Buffer 组件进行简化，用一个普通的<code class="highlighter-rouge">buf</code>序列代替，FIFO 就只剩下了 Sender 和 Receiver 两个组件，并且它们共同修改<code class="highlighter-rouge">buf</code>变量。这里的<code class="highlighter-rouge">buf</code>就是组件间的共享变量。我们尝试把整体的 FIFO 规范逆向分拆成两组件规范，Sender 和 Receiver 的<em>步</em>如下：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sndr == \/ /\ buf' = Append(buf, ...)
           /\ SCommunicate
           /\ UNCHANGED r
        \/ /\ SCompute
           /\ UNCHANGED &lt;&lt;buf, r&gt;&gt;

Rcvr == \/ /\ buf /= &lt;&lt;&gt;&gt;
           /\ buf' = Tail(buf)
           /\ RCommunicate
           /\ UNCHANGED s
        \/ /\ RCompute
           /\ UNCHANGED &lt;&lt;buf, s&gt;&gt;
</code></pre></div></div>

<p>两个组件的初始状态容易指定，但是<code class="highlighter-rouge">Next</code><em>步</em>比较难分拆。我们使用<code class="highlighter-rouge">NS</code>、<code class="highlighter-rouge">NR</code>表示 Sender 和 Receiver 的 <code class="highlighter-rouge">Next</code><em>步</em></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NS == Sndr \/ (sigma /\ (s' = s))
NR == Rcvr \/ (rho   /\ (r' = r))
</code></pre></div></div>
<p>其中<code class="highlighter-rouge">sigma</code>和<code class="highlighter-rouge">rho</code>是仅关于共享变量<code class="highlighter-rouge">buf</code>的动作，更准确地说，是<strong>不由自己引起</strong>的<code class="highlighter-rouge">buf</code>变化。</p>

<p>我们尝试从分离的<code class="highlighter-rouge">NS</code>和<code class="highlighter-rouge">NR</code>合取得到组合系统的规范，即</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[][NS]_&lt;&lt;buf, s&gt;&gt; /\ [][NR]_&lt;&lt;buf, r&gt;&gt; &lt;=&gt; [][Sndr \/ Rcvr]_&lt;&lt;buf, s, r&gt;&gt;
</code></pre></div></div>
<p>首先对上式左边进行化简：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[][
    /\ Sndr \/ ((sigma /\ (s' = s)) \/ UNCHANGED &lt;&lt;buf, s&gt;&gt;)
    /\ Rcvr \/ ((rho   /\ (r' = r)) \/ UNCHANGED &lt;&lt;buf, r&gt;&gt;)
]
==
[][
    \/ Sndr /\ Rcvr
    \/ Sndr /\ ((rho /\ (r' = r)) \/ UNCHANGED &lt;&lt;buf, r&gt;&gt;)
    \/ Rcvr /\ ((sigma /\ (s' = s)) \/ UNCHANGED &lt;&lt;buf, s&gt;&gt;)

    \/ s' = s /\ r' = r /\ buf' = buf                         \* if (sigma /\ rho) =&gt; (buf' = buf)
    \/ FALSE
    \/ FALSE
    \/ UNCHANGED &lt;&lt;buf, s, r&gt;&gt;
]
</code></pre></div></div>

<p>对于<code class="highlighter-rouge">Sndr /\ ((rho /\ (r' = r)) \/ UNCHANGED &lt;&lt;buf, r&gt;&gt;)</code>，可以看到<code class="highlighter-rouge">Sndr</code>的第二个子动作蕴含<code class="highlighter-rouge">UNCHANGED &lt;&lt;buf, r&gt;&gt;</code>，因此如果第一个子动作蕴含<code class="highlighter-rouge">rho</code>，该式即可化简为<code class="highlighter-rouge">Sndr</code>。对于<code class="highlighter-rouge">Rcvr /\ ((sigma /\ (s' = s)) \/ UNCHANGED &lt;&lt;buf, s&gt;&gt;)</code>也是同理。因此如果有下面三个假设：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">\A d: (buf' = Append(buf, d)) =&gt; rho</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">(buf /= &lt;&lt;&gt;&gt;) /\ (buf' = Tail(buf)) =&gt; sigma</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">(sigma /\ rho) =&gt; (buf' = buf)</code></p>
  </li>
</ul>

<p>整个左边就可以化简为<code class="highlighter-rouge">[][Sndr \/ Rcvr]_&lt;&lt;buf, s, r&gt;&gt;</code>。即完成了从分离的<code class="highlighter-rouge">NS</code>和<code class="highlighter-rouge">NR</code>合取得到组合系统的规范。</p>

<p>上面三个假设只需要取合理的<code class="highlighter-rouge">sigma</code>和<code class="highlighter-rouge">rho</code>即可。一种（很强的）取法是</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sigma == (buf /= &lt;&lt;&gt;&gt;) /\ (buf' = Tail(buf))
rho   == \E d: (buf' = Append(buf, d))
</code></pre></div></div>
<p>另一种（更弱的）取法是<code class="highlighter-rouge">sigma</code>不变，<code class="highlighter-rouge">rho == ~sigma</code>。</p>

<p>上面即为一个<em>交错</em>规范。</p>

<p>接下来考虑一种<em>非交错</em>规范，同时允许<code class="highlighter-rouge">SCompute</code>和<code class="highlighter-rouge">RCompute</code>并保持<code class="highlighter-rouge">buf</code>不变。我们定义<code class="highlighter-rouge">SSndr</code>和<code class="highlighter-rouge">RRcvr</code>为不描述对方组件不变的公式，即</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sndr &lt;=&gt; SSndr /\ (r' = r)
Rcvr &lt;=&gt; RRcvr /\ (s' = s)
</code></pre></div></div>
<p>因此组合而成的系统的 next <em>步</em>是<code class="highlighter-rouge">Sndr \/ Rcvr \/ (SSndr /\ RRcvr /\ (buf' = buf))</code>。我们又可以按照上面的方式定义<code class="highlighter-rouge">NS</code>、<code class="highlighter-rouge">NR</code>，并找到满足的<code class="highlighter-rouge">sigma</code>和<code class="highlighter-rouge">rho</code>。实际上<code class="highlighter-rouge">sigma</code>和<code class="highlighter-rouge">rho</code>与上面一致。</p>

<p>这种技巧可以推广到多个组件在共享变量的状态下进行组合：集合<code class="highlighter-rouge">C</code>由表示组件，共享变量是<code class="highlighter-rouge">w</code>，<code class="highlighter-rouge">N_k</code>第<code class="highlighter-rouge">k</code>个组件的<code class="highlighter-rouge">Next</code><em>步</em>，动作<code class="highlighter-rouge">mu_k</code>表示由第<code class="highlighter-rouge">k</code>个组件以外的组件对<code class="highlighter-rouge">w</code>产生的所有改变，<code class="highlighter-rouge">v_k</code>是第<code class="highlighter-rouge">k</code>个组件的私有状态。下面是<em>交错</em>规范的共享状态组合法则：</p>

<p>如下的 4 个条件</p>

<ol>
  <li>
    <p><code class="highlighter-rouge">(\A k \in C: v_k' = v_k) &lt;=&gt; (v' = v)</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">\A i, k \in C: N_k /\ (i /= k) =&gt; (v_i' = v_i)</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">\A i, k \in C: N_k /\ (w' = w) /\ (i /= k) =&gt; mu_i</code></p>
  </li>
  <li>
    <p><code class="highlighter-rouge">(\A \in C: mu_k) &lt;=&gt; (w' = w)</code>，表示在所有的组件以外，不存在改变<code class="highlighter-rouge">w</code>的组件，反之也成立</p>
  </li>
</ol>

<p>蕴含组件的合取等价于完整规范：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(\A k \in C: I_k /\ [][N_k \/ (mu_k /\ (v_k' = v_k))]_&lt;&lt;w, v_k&gt;&gt;)
&lt;=&gt;
(\A k \in C: I_k) /\ [][\E k \in C: N_k]_&lt;&lt;w, v&gt;&gt;
</code></pre></div></div>

<h4 id="1042-联合动作的组合">10.4.2 联合动作的组合</h4>

<p>对于某些组合，一些动作可能会不得不由多个组件共同完成，于是我们可以把动作拆分为前提、多组件共有的子动作、组件私有状态的变化等更细粒度，分配给多个组件进行描述。显然“多组件共有的子动作”需要出现在每个组件的动作中。</p>

<h1 id="105-一个简短的回顾">10.5 一个简短的回顾</h1>

<h4 id="1051-组合的分类">10.5.1 组合的分类</h4>

<ul>
  <li>
    <p><em>交错</em>规范与<em>非交错</em>规范</p>
  </li>
  <li>
    <p>分离式状态与共享式状态：分离式状态即系统的状态可以完整分拆为，若干个单独由某组件影响的子状态变量。</p>
  </li>
  <li>
    <p>联合动作与分离动作：联合动作规范是一种<em>非交错</em>规范，其中一些<em>步</em>会在多个组件上同时发生。不属于联合动作规范即为分离动作规范。</p>
  </li>
</ul>

<h4 id="1052-有关交错的思考">10.5.2 有关<em>交错</em>的思考</h4>

<p>在对真实系统的抽象上，<em>交错</em>与否并没有限制。但是<em>非交错</em>规范通常不能蕴含<em>交错</em>规范，因此为了实现一个抽象规范，通常需要写<em>交错</em>规范。通过合取限制<em>交错</em>的公式，<em>非交错</em>规范很容易转化为<em>交错</em>规范。</p>

<h4 id="1053-有关联合动作的思考">10.5.3 有关联合动作的思考</h4>

<p>“组合”就是为了把系统进行简化、分拆，但是联合动作阻止了这种分拆。一般而言，应当尽量减少联合动作，但是为了描述多组件的通信，为了把发送、接受两个动作抽象为一个动作，就需要联合动作以及增加辅助变量。</p>

<h1 id="106-活跃性和隐藏变量">10.6 活跃性和隐藏变量</h1>

<h4 id="1061-活跃性和状态机闭包">10.6.1 活跃性和状态机闭包</h4>

<p>本章的组合表示了由组件的安全性构造系统的安全性，那么活跃性也可以由组件的活跃性构造出吗？按照前文的建议，活跃性的描述应当使用弱公平性和强公平性，首先要注意公平性的下标，选择私有变量或者全部变量正确描述系统。</p>

<p>接下来考虑如果组件是状态机绑定的，整个系统是否也是状态机绑定的。这个问题也没有通用答案，依然要使用 8.9.2 节的经验，如果公平性描述的动作是<code class="highlighter-rouge">Next</code><em>步</em>的<em>子动作</em>，这个系统就是状态机绑定的。因此在<em>交错</em>规范中，如果组件的公平性描述的动作是整个系统<code class="highlighter-rouge">Next</code><em>步</em>的<em>子动作</em>，系统是状态机绑定的。<em>非交错</em>规范的组件会相互影响，往往不是状态机绑定的，例如第 9 章芝诺式规范可以看作是有联合动作的组件相互影响从而不满足状态机绑定。</p>

<h4 id="1062-隐藏变量">10.6.2 隐藏变量</h4>

<p>前文中我们介绍过使用<code class="highlighter-rouge">\EE</code>隐藏中间变量，对于组合而成的系统是否也可以这么做呢？按照定义，如果组件之间访问了共享变量，那么对各组件分别应用<code class="highlighter-rouge">\EE</code>，无法为共享变量建立跨组件的联系。如果（有限个）组件之间没有访问任何共享变量，对各组件隐藏变量就等价于对系统隐藏变量。</p>

<p>在<em>交错</em>规范中，如果组件 i 访问了其他组件 j 变量，但是这种访问仅是<code class="highlighter-rouge">UNCHANGED v_j</code>，那么也有上面的等价关系。</p>

<h1 id="107-开系统规范">10.7 <em>开系统</em>规范</h1>

<p>第 4 章介绍了<em>开系统</em>，本章我们可以从组合的角度进一步明确什么是<em>开系统</em>。前文中的规范是包含了系统与环境的<em>闭系统</em>，例如 FIFO 缓冲区规范中，缓冲区可以看作是系统，发送、接收方可以看作环境。<em>开系统</em>仅描述系统<code class="highlighter-rouge">M</code>本身，因此从组件的角度，似乎“环境<code class="highlighter-rouge">E</code>蕴含系统<code class="highlighter-rouge">M</code>”可以将<em>开系统</em>作为组件并与相应环境组合，形成一个<em>闭系统</em>规范。但这种描述太弱了，因为如果系统<code class="highlighter-rouge">M</code>主动破坏了规范导致<code class="highlighter-rouge">E</code>为假，<code class="highlighter-rouge">E =&gt; M</code>仍然成立。</p>

<p>为了限制上面的情况，引入新的运算符：<code class="highlighter-rouge">E -+-&gt; M</code>表示</p>

<ul>
  <li>
    <p>环境<code class="highlighter-rouge">E</code>蕴含系统<code class="highlighter-rouge">M</code></p>
  </li>
  <li>
    <p>对于任意自然数 n，如果行为的前 n 个状态满足<code class="highlighter-rouge">E</code>，那么前 n+1 个状态满足<code class="highlighter-rouge">M</code>。也即<code class="highlighter-rouge">M</code>不主动破坏了规范</p>
  </li>
</ul>

<p>对于一个<em>闭系统</em>规范，将其按照系统与环境拆分为组件，对同时与两者交互的公式分配给一方，以及提取不单独描述组件、仅描述组合而成的系统的公式（例如<code class="highlighter-rouge">IsFcnOn</code>），即可利用<code class="highlighter-rouge">-+-&gt;</code>转化为系统组件与环境组件。</p>

<h1 id="108-接口微调">10.8 <em>接口微调</em></h1>

<p><em>接口微调</em>指的是对高抽象层级的规范进行调整，得到低抽象层级的规范。</p>

<h4 id="1081-二进制时钟">10.8.1 二进制时钟</h4>

<p>本节给出一个<em>接口微调</em>的例子。以前文时钟为基础，描述一个二进制序列表示小时值的时钟。对于一个二进制序列转十进制的函数<code class="highlighter-rouge">BitArrayVal</code>，除了用它来作为实例化的<code class="highlighter-rouge">hr</code>映射之外，为了避免该函数无意间将未定义的输入转化为了合法的小时值，还要确定函数的定义域。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HourVal(b) == IF b \in [(0..3) -&gt; {0, 1}] THEN BitArrayVal(b)
                                          ELSE 99              \* any value doesn't satisfy HC!Init
B == INSTANCE HourClock WITH hr &lt;- HourVal(bits)
</code></pre></div></div>
<p>另一种不使用实例化的方式是，<code class="highlighter-rouge">\EE hr: [](hr = HourVal(bits)) /\ HC</code></p>

<h4 id="1082-微调一个信道">10.8.2 微调一个信道</h4>

<p>本节继续通过<em>接口微调</em>把一个信道转换为发送二进制值的信道。由于信道有发送-确认的机制，这里需要实例化两个信道，分别将可发送的数据指定为自然数和二进制，并通过一个中间缓冲区计算二进制到十进制的转化，以及在累积到若干二进制位后执行十进制信道的发送、确认<em>步</em>。</p>

<h4 id="1083-通用的接口微调">10.8.3 通用的<em>接口微调</em></h4>

<p>对于更通用场景而言，假如有一个高抽象层次的规范<code class="highlighter-rouge">HSpec</code>，想对它的接口进行调整实现一个更具体的规范<code class="highlighter-rouge">LSpec</code>，可以使用如下的公式：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LSpec == \EE h: IR /\ HSpec
</code></pre></div></div>
<p>其中<code class="highlighter-rouge">h</code>是<code class="highlighter-rouge">HSpec</code>的自由变量，<code class="highlighter-rouge">IR</code>模块将<code class="highlighter-rouge">LSpec</code>的<code class="highlighter-rouge">l</code>与<code class="highlighter-rouge">h</code>建立转化关系，<code class="highlighter-rouge">l</code>、<code class="highlighter-rouge">h</code>可能是长度不相等的元组。<code class="highlighter-rouge">IR</code>模块就起到了<em>接口微调</em>的作用。</p>

<p>对于<code class="highlighter-rouge">HSpec</code>与<code class="highlighter-rouge">LSpec</code>状态一一对应就能完成<code class="highlighter-rouge">h</code>与<code class="highlighter-rouge">l</code>的转换（例如 10.8.1），<code class="highlighter-rouge">IR</code>实际上只是<code class="highlighter-rouge">[]</code>表示的表达转换的单状态公式，这叫做<em>数据微调</em>。如果是多个状态才能决定<code class="highlighter-rouge">h</code>与<code class="highlighter-rouge">l</code>的转换（例如 10.8.2），<code class="highlighter-rouge">IR</code>就会表示得更加复杂。</p>

<h4 id="1084-开系统规范的接口微调">10.8.4 <em>开系统</em>规范的<em>接口微调</em></h4>

<p><em>开系统</em>在进行<em>接口微调</em>时需要注意系统与环境两部分，依然以<code class="highlighter-rouge">h</code>与<code class="highlighter-rouge">l</code>为例，<code class="highlighter-rouge">l</code>的变化需要正确地归纳到系统或者环境中。</p>

<p>当<em>开系统</em>有活跃性描述时，例如 10.8.2，当<code class="highlighter-rouge">l</code>因为环境的不正确而阻塞时，<code class="highlighter-rouge">h</code>因为无法累积而阻塞，就会导致<code class="highlighter-rouge">LSpec</code>下系统正确、环境不正确影响了<code class="highlighter-rouge">HSpec</code>的活跃性，从而影响了<code class="highlighter-rouge">HSpec</code>的正确。为了避免这种情况，<code class="highlighter-rouge">IR</code>中也要包含合取子式描述<code class="highlighter-rouge">LSpec</code>的活跃性。</p>

<h1 id="109-应当使用组合吗">10.9 应当使用组合吗？</h1>

<p>通常而言，写整体的规范或者组合而成的规范差别并不大。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[10.1 组合两个规范]]></summary></entry><entry><title type="html">Specifying Systems 第九章笔记</title><link href="/2019/11/17/tla-plus-book-ch9.html" rel="alternate" type="text/html" title="Specifying Systems 第九章笔记" /><published>2019-11-17T00:00:00+00:00</published><updated>2019-11-17T00:00:00+00:00</updated><id>/2019/11/17/tla-plus-book-ch9</id><content type="html" xml:base="/2019/11/17/tla-plus-book-ch9.html"><![CDATA[<h1 id="91-回顾时钟">9.1 回顾时钟</h1>

<p>上一章的活跃性可以描述未来某时刻会发生某特性，但是更具有现实意义的是<strong>实时性</strong>，即描述在未来的<strong>某段时间里</strong>会发生。以时钟为例，如果我们想要描述<code class="highlighter-rouge">hr</code>在某段时间内会改变，首先要引入一个<code class="highlighter-rouge">now</code>变量表示时间，并明确其特性，例如<code class="highlighter-rouge">hr</code>相对于<code class="highlighter-rouge">now</code>是瞬时改变的吗？<code class="highlighter-rouge">now</code>的改变是否有最小或者最大粒度……</p>

<p>我们使用<code class="highlighter-rouge">t</code>变量记录<code class="highlighter-rouge">hr</code>两次改变间<code class="highlighter-rouge">now</code>的变化。<em>动作</em>是公式，因此具有真假值的含义，<code class="highlighter-rouge">TNext == t' = IF HCnxt THEN 0 ELSE t + (now' - now)</code>中<code class="highlighter-rouge">HCnxt</code>表达<code class="highlighter-rouge">HCnxt</code><em>步</em>，因此本公式表示当<code class="highlighter-rouge">HCnxt</code>发生时<code class="highlighter-rouge">t</code>清零，当<code class="highlighter-rouge">HCnxt</code>不发生时<code class="highlighter-rouge">t</code>对经过的时间累加。</p>

<p>模块中可以继续定义子模块，在子模块上方的父模块部分对子模块可见。</p>

<p>很明显，<code class="highlighter-rouge">now</code>是一个单调增长的<code class="highlighter-rouge">Real</code>域变量，一个常常被忽略的问题时这种单调增长是否可以渐进于一个上界？本规范为了表示真实的时钟、排除有上界的<code class="highlighter-rouge">now</code>数列，可以使用<code class="highlighter-rouge">\A r \in Real : WF_now(NowNext /\ (now' &gt; r))</code>表示。即</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RTnow(v) == LET NowNext == /\ now' \in {r \in Real : r &gt; now} 
                           /\ UNCHANGED v
            IN  /\ now \in Real 
                /\ [][NowNext]_now
                /\ \A r \in Real : WF_now(NowNext /\ (now' &gt; r))
</code></pre></div></div>

<h1 id="92-更通用的实时性规范">9.2 更通用的实时性规范</h1>

<p>对于实时性的要求，一个更通用的描述是，<em>动作</em><code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>在连续（或累积）满足前提<code class="highlighter-rouge">D</code>秒到<code class="highlighter-rouge">E</code>秒的这段时间中一定发生。累计发生在实际中较少使用，因此我们只考虑连续<code class="highlighter-rouge">E</code>、<code class="highlighter-rouge">D</code>秒的情况。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RTBound(A, v, D, E) == 
  LET TNext(t)  ==  t' = IF &lt;&lt;A&gt;&gt;_v \/ ~(ENABLED &lt;&lt;A&gt;&gt;_v)'
                           THEN 0 
                           ELSE t + (now' - now)

      Timer(t) == (t = 0)  /\  [][TNext(t)]_&lt;&lt;t, v, now&gt;&gt;

      MaxTime(t) == [](t =&lt; E) 

      MinTime(t) == [][A =&gt; D =&lt; t]_v 
  IN \EE t : Timer(t) /\ MaxTime(t) /\ MinTime(t)
</code></pre></div></div>
<p>由定义可得，如果<code class="highlighter-rouge">E =&lt; Infinity</code>，<code class="highlighter-rouge">RTBound(A, v, D, E)</code>蕴含<code class="highlighter-rouge">WF_v(A)</code>。当<code class="highlighter-rouge">A</code>定义为<code class="highlighter-rouge">A1 \/ A2 \/ ...</code>而<code class="highlighter-rouge">Ai</code>的满足前提不并发以及前提的不满足只能通过<em>运行</em>该<em>动作</em>时，有重言式</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RTBound(A, v, D, E) = RTBound(A1, v, D, E) /\ RTBound(A2, v, D, E) /\ ...
</code></pre></div></div>

<h1 id="93-实时缓存">9.3 实时缓存</h1>

<p>前面两节定义的<code class="highlighter-rouge">RTnow</code>和<code class="highlighter-rouge">RTBound</code>可以作为通用的描述实时性的模块，例如内存可以将<code class="highlighter-rouge">RTBound(A, v, D, E)</code>的<code class="highlighter-rouge">A</code>定义为<code class="highlighter-rouge">(ctl[p] /= "rdy") /\ (ctl'[p] = "rdy")</code>，即描述处理器<code class="highlighter-rouge">p</code>恢复空闲的时间。</p>

<p>对于内存的实现——直写式缓存内存而言，如果它可以增加实时性的描述并蕴含上面的实时内存，就可以认为它是实时性内存的实现。然而目前的直写式缓存并不能蕴含实时内存，这是因为没有指明调度算法，有的处理器可能始终抢占<code class="highlighter-rouge">memQ</code>使得其他处理器资源始终受限。这可以对影响<code class="highlighter-rouge">memQ</code>的<em>动作</em>添加 round-robin 等调度算法解决。</p>

<p>具体而言，<code class="highlighter-rouge">RdMiss</code>和<code class="highlighter-rouge">DoWr</code>会添加消息到<code class="highlighter-rouge">memQ</code>上从而抢占其他处理器，所以这两个<em>动作</em>要添加 round-robin 作为前提，并更新<code class="highlighter-rouge">lastP</code>记录行动的上次处理器。round-robin 具体条件如下：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>position(p) == CHOOSE i \in 1..N : p = (lastP + i) % N
canGoNext(p) == \A q \in Proc : (position(q) &lt; position(p)) =&gt; ~ENABLED(RdMiss(q) \/ DoWr(q))
</code></pre></div></div>

<h1 id="94-芝诺式规范">9.4 芝诺式规范</h1>

<p>一个<code class="highlighter-rouge">now</code>无限增长但渐进上界的行为叫做“芝诺式的”。公式<code class="highlighter-rouge">\A r \in Real : WF_now(NowNext /\ (now' &gt; r))</code>阻止了芝诺式行为的发生，这个公式叫做<code class="highlighter-rouge">NZ</code>（Non-Zeno）。在仅允许芝诺式行为的系统中，<code class="highlighter-rouge">NZ</code>始终为<code class="highlighter-rouge">FALSE</code>。</p>

<p>仅允许芝诺式行为的规范叫做芝诺式规范，例如<code class="highlighter-rouge">RTBound(A, v, D, E)</code>中<code class="highlighter-rouge">D &gt; E</code>，意味着<code class="highlighter-rouge">now</code>不断趋近于<code class="highlighter-rouge">E</code>。芝诺式规范的形式化定义是：存在有限的行为满足规范的安全性部分，但是不存在无限的行为同时满足安全性和<code class="highlighter-rouge">NZ</code>。非芝诺式规范等价于状态机绑定的规范。芝诺式规范往往是因为实时性条件限制了一些系统行为的发生，这类似于非状态机闭包。</p>

<p>非芝诺式规范更有用，一般而言，由下面三类公式合取的规范是非芝诺式的：</p>

<ol>
  <li><code class="highlighter-rouge">Init /\ [][Next]_vars</code></li>
  <li><code class="highlighter-rouge">RTnow(vars)</code></li>
  <li>有限个<code class="highlighter-rouge">RTBound(Ai, vars, Di, Ei)</code>公式，其中
    <ul>
      <li><code class="highlighter-rouge">0 =&lt; Di =&lt; Ei =&lt; Infinity</code></li>
      <li><code class="highlighter-rouge">Ai</code>是<code class="highlighter-rouge">Next</code>的<em>子动作</em></li>
      <li>没有<em>步</em>同时满足不同的<code class="highlighter-rouge">Ai</code></li>
    </ul>
  </li>
</ol>

<p>描述更具体实现的规范容易满足上面的要求，描述更抽象的规范往往找不到合适的<code class="highlighter-rouge">Ai</code>满足<code class="highlighter-rouge">Ai</code>是<code class="highlighter-rouge">Next</code>的<em>子动作</em>。</p>

<h1 id="95-混合系统规范">9.5 混合系统规范</h1>

<p>TLA+ 将系统描述成了时间上离散的状态，但是真实世界中的系统往往有物理量需要连续时间，这叫做混合系统规范。混合系统规范可以通过在离散的时间点上应用积分运算符<code class="highlighter-rouge">Integrate</code>计算两个状态间的变化描述相关变量。具体而言，所有离散的变量形成元组<code class="highlighter-rouge">vd</code>，连续变量（除了<code class="highlighter-rouge">now</code>）形成元组<code class="highlighter-rouge">vc</code>，<code class="highlighter-rouge">RTnow</code>通过如下的公式替代</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/\ now' \in {r \in Real: r &gt; now}
/\ vc' = Integrate(D, now, now', vc)
/\ UNCHANGED vd
</code></pre></div></div>
<p>其中<code class="highlighter-rouge">D</code>是微分方程。这个公式的含义是，在公式<em>运行</em>的瞬间<code class="highlighter-rouge">vd</code>不变，通过积分计算<code class="highlighter-rouge">vc</code>的新状态。</p>

<h1 id="96-实时性的评论">9.6 实时性的评论</h1>

<p>实时性可以看作是比活跃性更强的描述，在简单系统中可以取代活跃性。通常而言，只使用<code class="highlighter-rouge">RTnow</code>和<code class="highlighter-rouge">RTBound</code>就能满足规范对实时性的描述和证明。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[9.1 回顾时钟]]></summary></entry><entry><title type="html">Specifying Systems 第八章笔记</title><link href="/2019/11/09/tla-plus-book-ch8.html" rel="alternate" type="text/html" title="Specifying Systems 第八章笔记" /><published>2019-11-09T00:00:00+00:00</published><updated>2019-11-09T00:00:00+00:00</updated><id>/2019/11/09/tla-plus-book-ch8</id><content type="html" xml:base="/2019/11/09/tla-plus-book-ch8.html"><![CDATA[<h1 id="81-时序逻辑">8.1 时序逻辑</h1>

<p>本章开始，我们将会更多地判断系统在“时间段”上的命题。之前的规范涉及了简单的时序逻辑，我们继续沿用那些符号并进行扩展。</p>

<p>在此引入一些术语简化表达：单状态公式表示只涉及一个状态的公式，<em>动作</em>表示只涉及一次状态转移的公式。</p>

<p>系统的行为定义为<strong>无限个</strong>状态通过状态转移形成的序列，时序逻辑公式<code class="highlighter-rouge">F</code>为系统的行为<code class="highlighter-rouge">sigma</code>判定真假值，这记为<code class="highlighter-rouge">sigma |= F</code>。从定义有<code class="highlighter-rouge">|=</code>对<code class="highlighter-rouge">/\</code>、<code class="highlighter-rouge">~</code>、<code class="highlighter-rouge">\E</code>、<code class="highlighter-rouge">\A</code>有分配律。接下来我们定义如何判断<code class="highlighter-rouge">|=</code>的真假值。</p>

<ul>
  <li>
    <p>对于单状态公式，系统行为的第一个状态的真假定义了<code class="highlighter-rouge">|=</code>的真假</p>
  </li>
  <li>
    <p>对于单状态公式<code class="highlighter-rouge">F</code>，<code class="highlighter-rouge">F</code>在行为的每个状态都为真，<code class="highlighter-rouge">sigma |= []F</code>才为真</p>
  </li>
  <li>
    <p>对于<em>动作</em><code class="highlighter-rouge">N</code>和状态公式<code class="highlighter-rouge">v</code>，行为的每一次状态转移都满足<code class="highlighter-rouge">[N]_v</code>，<code class="highlighter-rouge">sigma |= [][N]_v</code>才为真</p>
  </li>
</ul>

<p>上述三种公式在前面章节都已经出现，接下来我们对他们的含义进行扩展。</p>

<p>首先是将含义扩展到<em>动作</em>（但按照时序逻辑公式的定义，<em>动作</em>并不是时序逻辑公式，因为它不涉及无限个状态）。对于<em>动作</em><code class="highlighter-rouge">A</code>，<code class="highlighter-rouge">sigma |= A</code>为真表示系统的第一个状态转移满足<em>动作</em><code class="highlighter-rouge">A</code>，<code class="highlighter-rouge">sigma |= []A</code>表示系统的每个状态转移满足<em>动作</em><code class="highlighter-rouge">A</code>。依照这种方式，我们也可以将含义扩展到一般的时序逻辑公式。</p>

<p>接下来是定义<code class="highlighter-rouge">[]</code>运算符。将系统状态转移序列的任意一个状态视作“第一个状态”，也即截断前几个状态，如果任意截断后状态转移序列都满足时序逻辑公式<code class="highlighter-rouge">F</code>，则记为系统的行为满足<code class="highlighter-rouge">[]F</code>。因此<code class="highlighter-rouge">[]</code>运算符实际上表达了<strong>从某状态（默认为初始状态）以后始终满足</strong>的含义。</p>

<p>在 2.2 节中，为了描述过于频繁的观察，我们允许系统在相邻状态中所有变量都不改变，而这会使得有些时序逻辑公式为假。为了解决这个问题，TLA+ 仅允许表达“允许所有变量都不改变”的时序逻辑公式。通常而言，单状态公式、<code class="highlighter-rouge">[][N]_v</code>以及它们通过<code class="highlighter-rouge">[]</code>运算符和布尔运算符组合而成的公式都是 TLA+ 允许表达的。</p>

<p>接下来引入五种记号简化表达：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">&lt;&gt;F</code>表示<code class="highlighter-rouge">~[]~F</code>，即<code class="highlighter-rouge">F</code>不总是假的，即<code class="highlighter-rouge">&lt;&gt;</code>表达了<strong>从某状态以后至少一次满足</strong>的含义。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">F ~&gt; G</code>表示<code class="highlighter-rouge">[](F =&gt; &lt;&gt;G)</code>，即<code class="highlighter-rouge">F</code>为真之后，<code class="highlighter-rouge">G</code>会在某时为真。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>表示<code class="highlighter-rouge">~[][~A]_v</code>，即不是每一<em>步</em>都是<code class="highlighter-rouge">(~A) \/ (v' = v)</code><em>步</em>，即某一步会是<code class="highlighter-rouge">A /\ (v' /= v)</code><em>步</em>。我们也可以视作<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v == A /\ (v' /= v)</code>（但这不是时序逻辑公式，因为它只描述一次状态转移），并结合<code class="highlighter-rouge">&lt;&gt;</code>的含义记忆。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">[]&lt;&gt;F</code>即在任意时刻以后，<code class="highlighter-rouge">F</code>会为真。这也意味着<code class="highlighter-rouge">F</code>无穷次为真。</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">&lt;&gt;[]F</code>表示存在某个时刻，<code class="highlighter-rouge">F</code>在那以后一直为真。</p>
  </li>
</ul>

<p><code class="highlighter-rouge">[]</code>和<code class="highlighter-rouge">&lt;&gt;</code>的优先级比布尔运算符高，<code class="highlighter-rouge">~&gt;</code>的优先级比合取、析取低。</p>

<h1 id="82-时序逻辑的重言式">8.2 时序逻辑的重言式</h1>

<p>时序逻辑中的重言式更加复杂，有的重言式可以通过含义直接判断，有的则需要借助一些转化的技巧。</p>

<p><em>对偶</em>重言式是指，将时序逻辑公式的<code class="highlighter-rouge">[]</code>与<code class="highlighter-rouge">&lt;&gt;</code>、<code class="highlighter-rouge">/\</code>与<code class="highlighter-rouge">\/</code>相互替换，并反转蕴含的方向，就能得到一组<em>对偶</em>重言式。</p>

<p>需要注意<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>需要与<code class="highlighter-rouge">&lt;&gt;</code>组合才是时序逻辑公式，有些重言式会使得<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>变成独立的谓词，这是不合法的。例如</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[]&lt;&gt;(&lt;&lt;A&gt;&gt;_v \/ &lt;&lt;B&gt;&gt;_v) &lt;=&gt; ([]&lt;&gt;&lt;&lt;A&gt;&gt;_v) \/ ([]&lt;&gt;&lt;&lt;B&gt;&gt;_v)
</code></pre></div></div>
<p>其中<code class="highlighter-rouge">(&lt;&lt;A&gt;&gt;_v \/ &lt;&lt;B&gt;&gt;_v)</code>没有与<code class="highlighter-rouge">&lt;&gt;</code>组合，需要转化为<code class="highlighter-rouge">&lt;&lt;A \/ B&gt;&gt;_v</code>才是合法的公式。</p>

<h1 id="83-时序逻辑证明法则">8.3 时序逻辑证明法则</h1>

<p>时序逻辑的证明法则可以沿用命题逻辑的证明法则，除此之外</p>

<ul>
  <li>
    <p>时序逻辑有自己的证明法则：</p>

    <ul>
      <li>
        <p>泛化法则：对于时序逻辑命题<code class="highlighter-rouge">F</code>，如果所有的行为都有<code class="highlighter-rouge">F</code>，那么所有的行为都有<code class="highlighter-rouge">[]F</code>。这可以由 8.1 节对<code class="highlighter-rouge">[]</code>的定义和行为通过截断关系相互转化得到</p>
      </li>
      <li>
        <p>蕴含的泛化法则：对于时序逻辑命题<code class="highlighter-rouge">F</code>、<code class="highlighter-rouge">G</code>，如果所有的行为都有<code class="highlighter-rouge">F =&gt; G</code>，那么所有的行为都有<code class="highlighter-rouge">[]F =&gt; []G</code></p>
      </li>
    </ul>
  </li>
  <li>
    <p>命题逻辑的法则对应一个重言式，而时序逻辑不是。这是因为时序逻辑是对于系统的行为，即无限长的状态序列进行描述，重言式在语义上是命题逻辑，不能描述更高级的时序逻辑</p>

    <ul>
      <li>例如泛化法则并不是说<code class="highlighter-rouge">F =&gt; []F</code>是重言式。我们可以令<code class="highlighter-rouge">F</code>为状态公式，<code class="highlighter-rouge">sigma</code>的第一个状态使<code class="highlighter-rouge">F</code>为真，后续状态使<code class="highlighter-rouge">F</code>为假，容易验证<code class="highlighter-rouge">sigma |= (F =&gt; []F)</code>并不为真。</li>
    </ul>
  </li>
</ul>

<h1 id="84-弱公平性">8.4 弱公平性</h1>

<p>系统有安全性和活跃性两种特性，分别表示“某些特性总是满足”以及“某些特性在时间段上满足”。本书的第一部分说明 TLA+ 如何表达安全性，而在了解了<code class="highlighter-rouge">[]</code>和<code class="highlighter-rouge">&lt;&gt;</code>运算符之后，我们就可以表达活跃性了。对于第二章的时钟，一个永远运行的时钟可以表达为<code class="highlighter-rouge">[]&lt;&gt;&lt;&lt;HCnxt&gt;&gt;_hr</code>。</p>

<p>但是在有些情况下，活跃性还需要前提条件。第三章的异步接口</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rcv ==  /\ chan.rdy /= chan.ack
        /\ chan' = [chan EXCEPT !.ack = 1 - @]
</code></pre></div></div>
<p>为了表示发送的值总会被接受，前提是存在发送的值，也即<code class="highlighter-rouge">chan.rdy /= chan.ack</code>。TLA+ 通过<code class="highlighter-rouge">ENABLED</code>关键字描述某个<em>动作</em>的前提。因此“异步接口发送值总会被接受”可以描述为<code class="highlighter-rouge">[](ENABLED &lt;&lt;Rcv&gt;&gt;_chan =&gt; &lt;&gt;&lt;&lt;Rcv&gt;&gt;_chan)</code>或<code class="highlighter-rouge">[](ENABLED Rcv =&gt; &lt;&gt;&lt;&lt;Rcv&gt;&gt;_chan)</code>或<code class="highlighter-rouge">ENABLED Rcv ~&gt; &lt;&gt;&lt;&lt;Rcv&gt;&gt;_chan</code>。</p>

<p>弱公平性<code class="highlighter-rouge">WF_v(A)</code>定义为<code class="highlighter-rouge">[]([]ENABLED &lt;&lt;A&gt;&gt;_v =&gt; &lt;&gt;&lt;&lt;A&gt;&gt;_v)</code>，其含义是如果<em>动作</em><code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>在任意时刻 t 后都能进入永远满足前提的状态，那么<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>在 t 后必将发生。弱公平性与以下两种定义等价：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">[]&lt;&gt;(~ENABLED &lt;&lt;A&gt;&gt;_v) \/ []&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>，即<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>在任意时刻之后都有不满足前提条件的时候，或者在任意时刻后<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>总会发生</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">&lt;&gt;[](ENABLED &lt;&lt;A&gt;&gt;_v) =&gt; []&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>，即<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>在某时刻后会永远满足前提，则在任意时刻后<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>总会发生</p>
  </li>
</ul>

<p>定义了弱公平性后，我们发现“发送的值总会被接受”<code class="highlighter-rouge">[](ENABLED &lt;&lt;Rcv&gt;&gt;_chan =&gt; &lt;&gt;&lt;&lt;Rcv&gt;&gt;_chan)</code>与弱公平性<code class="highlighter-rouge">[]([]ENABLED &lt;&lt;Rcv&gt;&gt;_chan =&gt; &lt;&gt;&lt;&lt;Rcv&gt;&gt;_chan)</code>是不同的。如果我们能证明在异步接口的规范下两者恒等，就能用弱公平性简化表达。</p>

<p>这里由异步接口的规范可得：如果<code class="highlighter-rouge">&lt;&lt;Rcv&gt;&gt;_chan</code>的前提满足，即发送了一个值后，或者发生<code class="highlighter-rouge">&lt;&lt;Rcv&gt;&gt;_chan</code>从而接受这个值；或者这个值永不被接受，也就是<code class="highlighter-rouge">[](ENABLED &lt;&lt;Rcv&gt;&gt;_chan)</code>。这可以推出两者恒等。即下面是一个重言式，表示满足前件后“发送的值总会被接受”这种叙述与弱公平性等价</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[](E =&gt; []E \/ &lt;&gt;A) =&gt; ([](E =&gt; &lt;&gt;A) &lt;=&gt; []([]E =&gt; &lt;&gt;A))
</code></pre></div></div>
<p>公式中<code class="highlighter-rouge">E</code>表示<em>动作</em><code class="highlighter-rouge">A</code>的前提，<code class="highlighter-rouge">A</code>表示弱公平性描述的<em>动作</em>。</p>

<h1 id="85-内存的规范">8.5 内存的规范</h1>

<h4 id="851-对活跃性的需求">8.5.1 对活跃性的需求</h4>

<p>我们希望限定第五章的内存模型能够“对发出的请求最终会得到回复”。其中<code class="highlighter-rouge">MemoryInterface</code>模块只给出了<code class="highlighter-rouge">Reply</code>操作符接口的定义，没有具体公式的实现，因此<code class="highlighter-rouge">ENABLED</code>关键字不能判断何时满足前提，这需要我们增加一个假设</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ASSUME \A p, r, miOld : \E miNew : Reply(p, r, miOld, miNew)
</code></pre></div></div>

<p>请求的发送到回复需要经历两个逻辑上连续的<em>动作</em>，因此活跃性可以表达为</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Liveness == \A p \in Proc : WF_vars(Do(p)) /\ WF_vars(Rsp(p))
</code></pre></div></div>
<p>其中<code class="highlighter-rouge">vars</code>表示系统的所有变量。</p>

<h4 id="852-另一种表达">8.5.2 另一种表达</h4>

<p>上面的公式会让我们思考，是否<code class="highlighter-rouge">WF_v(A) /\ WF_v(B)</code>与<code class="highlighter-rouge">WF_v(A \/ B)</code>是等价的。一般而言它们不等价，但在内存的例子中，由于<code class="highlighter-rouge">Do(p)</code>和<code class="highlighter-rouge">Rsp(p)</code>的一者满足前提时，另一者前提不会被满足，直到前者被<em>运行</em>，它们就等价。这是一条通用的定理，具体证明见原书。</p>

<h4 id="853-泛化">8.5.3 泛化</h4>

<p>上一小节的定理引出了弱公平性合取法则：对于<em>动作</em><code class="highlighter-rouge">A1</code>，……，<code class="highlighter-rouge">An</code>，如果所有的<code class="highlighter-rouge">Ai</code>，<code class="highlighter-rouge">Aj</code>都满足一者满足前提时，另一者前提不会被满足，直到前者被<em>运行</em>。那么<code class="highlighter-rouge">WF_v(A1) /\ ... /\ WF_v(An)</code>等价于<code class="highlighter-rouge">WF_v(A1 \/ ... \/ An)</code>。</p>

<p>考虑到合取与析取与全称量词、存在量词对应，上面的弱公平性合取法则也可以表示为量词形式。</p>

<h1 id="86-强公平性">8.6 强公平性</h1>

<p>强公平性<code class="highlighter-rouge">SF_v(A)</code>有以下两种等价定义：</p>

<ul>
  <li>
    <p><code class="highlighter-rouge">&lt;&gt;[](~ENABLED &lt;&lt;A&gt;&gt;_v) \/ []&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>，即<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>在某时刻后保持不能满足前提，或者<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>发生无数次</p>
  </li>
  <li>
    <p><code class="highlighter-rouge">[]&lt;&gt;ENABLED &lt;&lt;A&gt;&gt;_v =&gt; []&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>，即如果<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>无数次满足前提，那么<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>发生无数次</p>
  </li>
</ul>

<p>其中第二种定义与弱公平性<code class="highlighter-rouge">&lt;&gt;[](ENABLED &lt;&lt;A&gt;&gt;_v) =&gt; []&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>类似。由于<code class="highlighter-rouge">&lt;&gt;[](ENABLED &lt;&lt;A&gt;&gt;_v)</code>蕴含<code class="highlighter-rouge">&lt;&gt;[](~ENABLED &lt;&lt;A&gt;&gt;_v)</code>，满足强公平性的系统一定满足弱公平性。在下面的条件下，强公平性与弱公平性等价：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[]&lt;&gt;(~ENABLED &lt;&lt;A&gt;&gt;_v) =&gt; &lt;&gt;[](~ENABLED&lt;&lt;A&gt;&gt;_v) \/ []&lt;&gt;&lt;&lt;A&gt;&gt;_v
</code></pre></div></div>
<p>一种常见的满足上式的条件是：<em>动作</em>的不满足前提<strong>仅</strong>因为<em>动作</em>的<em>运行</em>会取消自身的前提，例如第三章异步接口的<code class="highlighter-rouge">Rcv</code>后（如果没有其他<em>动作</em>）就不满足下次<code class="highlighter-rouge">Rcv</code>的前提。另一种满足上式的表述是：<em>动作</em>一旦满足前提，在它<em>运行</em>前会保持满足前提的状态，<em>运行</em>后则不满足前提。</p>

<p>强公平性的合取法则、量词形式的合取法则与弱公平性一致。</p>

<p>强公平性更难实现，也在规范中更少出现。在强、弱公平性等价的时候，最好表述弱公平性。此外应尽量使用强、弱公平性而不是原始的时序逻辑公式去描述系统。</p>

<h1 id="87-直写式缓存">8.7 直写式缓存</h1>

<p>直写式缓存与 8.4 节的异步接口类似，想描述“每个请求都将收到响应”，首先要尝试按照 8.4 节的等价关系转化为弱公平性。假设能满足转化，这意味着<code class="highlighter-rouge">Next</code>动作中与“请求-响应”相关的析取子式具有一定的公平性，而这些<em>动作</em>子式究竟应该使用弱公平性还是强公平性又需要我们仔细讨论。因此我们现在有两个问题：</p>

<ol>
  <li>
    <p>“每个请求都将收到响应”能否转化成弱公平性</p>
  </li>
  <li>
    <p>这种公平性是否需要加强为强公平性</p>
  </li>
</ol>

<p>首先考虑第一个问题。直写式缓存的读、写请求，这有多种方式满足“请求-响应”流程，例如<code class="highlighter-rouge">Req(p)</code>、<code class="highlighter-rouge">DoRd(p)</code>、<code class="highlighter-rouge">Rsp(p)</code>。这些<em>动作</em>可以通过验证 8.4 节的等价关系转化为弱公平性。但是有的<em>动作</em>并不是直接符合 8.4 节等价关系。例如<code class="highlighter-rouge">DoRd(p)</code>表示读请求在缓存命中时的响应，它的满足前提可能会被<code class="highlighter-rouge">Evict(p, a)</code>清理缓存所破坏，似乎不满足“<em>动作</em>的前提满足后，要么在未来<em>运行</em>该<em>动作</em>，要么该前提始终保持满足”的定义。</p>

<p>我们进一步考虑<code class="highlighter-rouge">Evict(p, a)</code>之后发生了什么。在<code class="highlighter-rouge">Evict(p, a)</code>破坏后，<code class="highlighter-rouge">RdMiss(p)</code>的前提被满足，如果<code class="highlighter-rouge">RdMiss(p)</code>以及后续<em>动作</em>符合 8.4 节的等价关系以及弱一致性，并在一连串<em>动作</em>后使得<code class="highlighter-rouge">DoRd(p)</code>的前提重新被满足，且不会被其他<em>动作</em>破坏，那么从更大的时间跨度上可以得到：<code class="highlighter-rouge">DoRd(p)</code>的前提满足后，要么该前提一直满足，要么<code class="highlighter-rouge">DoRd(p)</code>在未来会<em>运行</em>。从而能转换成弱公平性。</p>

<p>而<code class="highlighter-rouge">RdMiss(p)</code>与<code class="highlighter-rouge">DoWr(p)</code>的前提需要消息队列不满，因此它们对于处理器<code class="highlighter-rouge">p</code>的前提满足时，可能由于另一个处理器<code class="highlighter-rouge">q</code>的这两种<em>动作</em>使前提不满足。也就是说，它们的前提满足不能保持始终为真。为了描述“每个请求都将收到响应”的特性，这些<em>动作</em>需要强公平性。这也就是问题二。</p>

<p>因此直写式缓存的活跃性表示为</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/\ \A p \in Proc: /\ WF_vars(Rsp(p))) /\ WF_vars(DoRd(p))
                  /\ SF_vars(RdMiss(p)) /\ SF_vars(DoWr(p))
/\ WF_vars(MemQWr) /\ WF_vars(MemQRd)
</code></pre></div></div>
<p>考虑到<code class="highlighter-rouge">MemQWr</code>在全是写请求以及队列未满的时候无需<em>运行</em>，我们也可以通过合取这个限制条件进一步弱化描述。</p>

<h1 id="88-量词">8.8 量词</h1>

<p>在时序逻辑下，命题逻辑的量词表示所限定的变量在所有状态下保持不变。如果想要描述随着状态改变而改变的变量，应该使用时序逻辑量词<code class="highlighter-rouge">\EE</code>和<code class="highlighter-rouge">\AA</code>。</p>

<p><code class="highlighter-rouge">CHOOSE</code>运算符不能扩展到时序逻辑中。</p>

<h1 id="89-重温时序逻辑">8.9 重温时序逻辑</h1>

<h4 id="891-回顾">8.9.1 回顾</h4>

<p>时序逻辑公式既可以表达对系统的描述，也可以表达要证明的性质。一般而言，通过弱公平性和强公平性表示描述，通过基础的运算符表达要证明的性质。规范的完整形式如下：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Init /\ [][Next]_vars /\ Liveness
</code></pre></div></div>
<p>在表达不同抽象层级的规范时，常常使用<code class="highlighter-rouge">\E</code>、<code class="highlighter-rouge">\EE</code>隐藏一些中间变量。</p>

<h4 id="892-状态机闭包">8.9.2 状态机闭包</h4>

<p>描述系统的活跃性时应当尽量使用弱公平性和强公平性，而不是基本的时序逻辑公式，这是因为在构造时序逻辑公式时容易出错。这种出错常常会限制<code class="highlighter-rouge">Init</code>或<code class="highlighter-rouge">[][Next]_vars</code>中的一些状态或<em>动作</em>不能发生，因此使得规范意外地少描述了系统。一个不影响<code class="highlighter-rouge">Init</code>和<code class="highlighter-rouge">[][Next]_vars</code>的活跃性被称为状态机绑定的。</p>

<p>我们定义<code class="highlighter-rouge">A</code>是<code class="highlighter-rouge">Next</code>的<em>子动作</em>为：如果所有的<code class="highlighter-rouge">A</code><em>步</em>都是<code class="highlighter-rouge">Next</code>步，或者<code class="highlighter-rouge">A</code>蕴含<code class="highlighter-rouge">Next</code>。在大多数情况下，公平性描述的动作应该是<code class="highlighter-rouge">Next</code>的<em>子动作</em>，这样就不会破坏状态机绑定。在有些复杂情况下，确实存在一些系统不能通过弱一致性和强一致性描述活跃性。这将在 11.2 节描述。</p>

<h4 id="893-状态机闭包与概率">8.9.3 状态机闭包与概率</h4>

<p>可以把活跃性视作概率意义，例如对于一对状态机绑定的<code class="highlighter-rouge">Spec</code>和<code class="highlighter-rouge">[]&lt;&gt;&lt;&lt;A&gt;&gt;_v</code>，<code class="highlighter-rouge">Spec</code>所描述的系统总有可能发生无穷多次<code class="highlighter-rouge">&lt;&lt;A&gt;&gt;_v</code>。在使用 TLA+ 表示规范时，要注意真实系统的行为符不符合规范，以及规范的公平性足够由真实系统的行为推出。</p>

<h4 id="894-实例化映射和公平性">8.9.4 实例化映射和公平性</h4>

<p>5.8 节介绍了规范 A 实现规范 B 需要证明规范 A 蕴含了这种实例化的规范 B。在增加了活跃性后，实例化时的变量映射能否对弱、强公平性分配呢，也即：</p>

<p>由<code class="highlighter-rouge">&lt;&lt;vars0&gt;&gt;</code>实例化的<code class="highlighter-rouge">&lt;&lt;vars&gt;&gt;</code>，是否满足规范 B 的<code class="highlighter-rouge">WF_v(A)</code>等于规范 A 实例化的<code class="highlighter-rouge">WF_v0(A0)</code>呢？</p>

<p>答案是不等于，因为其他的运算符满足分配律，但<code class="highlighter-rouge">ENABLED</code>关键字不满足分配律。例如实例化为两个形式参数分配了同一个实际参数，接口中的前提就会产生干扰。</p>

<p>因此，在规范中出现实例化映射和公平性时，需要我们还原成时序逻辑公式并对<code class="highlighter-rouge">ENABLED</code>运算符按如下规则手动计算：</p>

<ol>
  <li>
    <p>对于任意<em>动作</em>，<code class="highlighter-rouge">ENABLED(A \/ B) &lt;=&gt; (ENABLED A) \/ (ENABLED B)</code></p>
  </li>
  <li>
    <p>对于单状态公式<code class="highlighter-rouge">P</code>和<em>动作</em><code class="highlighter-rouge">A</code>，<code class="highlighter-rouge">ENABLED(P /\ A) &lt;=&gt; P /\ (ENABLED A)</code></p>
  </li>
  <li>
    <p>对于<em>动作</em><code class="highlighter-rouge">A</code>和<code class="highlighter-rouge">B</code>，如果<code class="highlighter-rouge">A</code>和<code class="highlighter-rouge">B</code>不同时更改同一个变量，<code class="highlighter-rouge">ENABLED(A /\ B) &lt;=&gt; (ENABLED A) /\ (ENABLED B)</code></p>
  </li>
  <li>
    <p>对于任意变量<code class="highlighter-rouge">x</code>和单状态的表达式<code class="highlighter-rouge">exp</code>，<code class="highlighter-rouge">ENABLED(x' = exp) &lt;=&gt; TRUE</code>，<code class="highlighter-rouge">ENABLED(x \in exp) &lt;=&gt; (exp /= {})</code></p>
  </li>
</ol>

<h4 id="895-活跃性没那么重要">8.9.5 活跃性没那么重要</h4>

<p>实际应用中，安全性比活跃性更重要。</p>

<h4 id="896-时序逻辑常常引起困惑">8.9.6 时序逻辑常常引起困惑</h4>

<p>时序逻辑表达方法众多，可读性差，最好按照统一的方法写时序逻辑公式。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[8.1 时序逻辑]]></summary></entry><entry><title type="html">Specifying Systems 第六章笔记</title><link href="/2019/11/07/tla-plus-book-ch6.html" rel="alternate" type="text/html" title="Specifying Systems 第六章笔记" /><published>2019-11-07T00:00:00+00:00</published><updated>2019-11-07T00:00:00+00:00</updated><id>/2019/11/07/tla-plus-book-ch6</id><content type="html" xml:base="/2019/11/07/tla-plus-book-ch6.html"><![CDATA[<h1 id="61-集合">6.1 集合</h1>

<p>集合有两个更高级的运算符：</p>

<p>交集<code class="highlighter-rouge">UNION { {1, 2}, {2, 3} } = {1, 2, 3}</code></p>

<p>求子集（幂集）<code class="highlighter-rouge">SUBSET {1, 2} = { {}, {1}, {2}, {1, 2} }</code></p>

<p>描述某种类型且元素满足某种公式的集合有两种方法，以奇数为例：<code class="highlighter-rouge">{x \in Nat : x % 2 = 1}</code>和<code class="highlighter-rouge">{2 * n + 1 : n \in Nat}</code>。</p>

<p><code class="highlighter-rouge">FiniteSets</code>模块定义了两种新的运算符：</p>

<p><code class="highlighter-rouge">Cardinality(S)</code>在<code class="highlighter-rouge">S</code>是有限集时返回元素的个数。</p>

<p><code class="highlighter-rouge">IsFiniteSet(S)</code>判断<code class="highlighter-rouge">S</code>是有限集。</p>

<p>在 TLA+ 的集合定义下，如何避免罗素悖论？TLA+ 的方式是限制自身的表达能力，不能表达其元素与“所有集合”一一对应的集合。</p>

<h1 id="62-无意义的表达式">6.2 无意义的表达式</h1>

<p>TLA+ 没有类型系统，因此不能检查<code class="highlighter-rouge">3 / "abc"</code>等无意义的表达式。可以认为这些表达式具有一个由表达式唯一复现的、不确定的返回值。</p>

<h1 id="63-重温递归">6.3 重温递归</h1>

<p>TLA+ 要求递归函数有特殊的定义方法。下面的直观的递归定义阶乘函数是错误的：</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fact == [n \in Nat |-&gt; IF n = 0 THEN 1 ELSE n * fact[n - 1]]
</code></pre></div></div>
<p>原因是在解析右侧的表达式时，<code class="highlighter-rouge">fact</code>标识符还没有定义。</p>

<p>第五章中说明递归定义的语法是<code class="highlighter-rouge">f[x \in S] == e</code>，这实际上是<code class="highlighter-rouge">f == CHOOSE f: f = [x \in S |-&gt; e]</code>。TLA+ 通过对<code class="highlighter-rouge">CHOOSE</code>关键字的语法解析完成递归函数定义。同样，如果不存在合法的<code class="highlighter-rouge">CHOOSE</code>，例如无意义的表达式或者表达式与要定义的函数有逻辑冲突，递归函数将会定义为某个不确定的值。</p>

<p>有些递归函数表达为同时依赖多个函数，例如 f、g 的计算同时依赖 f、g。这可以通过定义一个<em>记录</em>并通过<code class="highlighter-rouge">record.f</code>和<code class="highlighter-rouge">record.g</code>访问解决。</p>

<p>虽然 TLA+ 支持递归定义，但是应当注意运用已提供的运算符，这样效率更高。例如不要使用递归重复定义<code class="highlighter-rouge">Head(s)</code>。</p>

<h1 id="64-函数与运算符">6.4 函数与运算符</h1>

<p>函数和运算符有如下区别：</p>

<ul>
  <li>
    <p>函数是一个语法完整的公式，运算符不是。具体而言，<code class="highlighter-rouge">function \in S</code>、<code class="highlighter-rouge">function[x] \in S</code>、<code class="highlighter-rouge">operator(x) \in S</code>都是合法的，但<code class="highlighter-rouge">operator \in S</code>有语法错误。</p>
  </li>
  <li>
    <p>函数有定义域，但运算符没有。有的运算符（例如<code class="highlighter-rouge">Tail(s)</code>）的定义域太大而不能在 TLA+ 下表达，参见 6.1 节。</p>
  </li>
  <li>运算符不能被递归定义。但在 TLA+ 中，可以通过能递归定义的函数辅助定义运算符。例如<code class="highlighter-rouge">Cardinality</code>运算符可以定义为：
    <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cardinality(S) ==
  LET CS[T \in SUBSET S] ==
      IF T = {} THEN 0
                ELSE 1 + CS[T \ {CHOOSE x: x \in T}]
  IN CS[S]
</code></pre></div>    </div>
  </li>
  <li>
    <p>运算符可以将另一个运算符作为参数。</p>
  </li>
  <li>
    <p>中缀关系只能由运算符定义。</p>
  </li>
  <li>错误的运算符写法会产生语法错误，但错误的函数写法不能被检查语法错误，一般只能产生语义错误。这与 TLC 等运行环境实现有关。</li>
</ul>

<h1 id="65-使用函数">6.5 使用函数</h1>

<p>按照前文定义的方式正确定义函数，奇怪的定义方法往往有潜在问题。</p>

<h1 id="66-choose-运算符">6.6 <code class="highlighter-rouge">CHOOSE</code> 运算符</h1>

<p><code class="highlighter-rouge">CHOOSE</code> 运算符在无法“选出”一个符合要求的元素时会返回不确定的值，但相同的<code class="highlighter-rouge">CHOOSE</code>公式是可复现的。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[6.1 集合]]></summary></entry><entry><title type="html">Specifying Systems 第七章笔记</title><link href="/2019/11/07/tla-plus-book-ch7.html" rel="alternate" type="text/html" title="Specifying Systems 第七章笔记" /><published>2019-11-07T00:00:00+00:00</published><updated>2019-11-07T00:00:00+00:00</updated><id>/2019/11/07/tla-plus-book-ch7</id><content type="html" xml:base="/2019/11/07/tla-plus-book-ch7.html"><![CDATA[<h1 id="71-为什么写规范">7.1 为什么写规范</h1>

<ul>
  <li>
    <p>在实现前、在设计阶段发现错误</p>
  </li>
  <li>
    <p>精确、清晰地描述系统</p>
  </li>
  <li>
    <p>应用工具自动化检查</p>
  </li>
</ul>

<h1 id="72-去规范什么">7.2 去规范什么</h1>

<p><strong>写规范的主要目的是暴露错误</strong>，TLA+ 擅长处理并发场景，因此应当主要用作暴露并发错误。</p>

<h1 id="73-原子性的粒度">7.3 原子性的粒度</h1>

<p>使用什么粒度描述系统通常需要经验的积累，这里有一个经验：</p>

<p>在某种粒度上，系统的状态转移如果是可交换的，并总能将<em>步</em>1与<em>步</em>2交换到相邻位置，那么就可以把<em>步</em>1和<em>步</em>2组合为一个<em>步</em>。</p>

<h1 id="74-数据结构">7.4 数据结构</h1>

<p>再次强调，<strong>写规范的主要目的是暴露错误</strong>，如果内存布局、数据结构等信息并不会出错，就无须在规范中指明。例如第五章使用<code class="highlighter-rouge">Send</code>运算符描述系统的状态转移，没有深入到系统如何实现<code class="highlighter-rouge">Send</code>之中。</p>

<h1 id="75-写一个规范">7.5 写一个规范</h1>

<p>在此总结一下写规范的步骤：</p>

<ol>
  <li>
    <p>选择要描述的系统组件和抽象层级</p>
  </li>
  <li>
    <p>选择变量表示系统状态，定义不变量、初始状态，此时可能需要定义额外的常量和假设</p>
  </li>
  <li>
    <p>描述状态转移，尽量做到紧凑、容易阅读</p>
  </li>
  <li>
    <p>描述时序逻辑特性，这会在第八章介绍</p>
  </li>
  <li>
    <p>描述系统的性质作为定理</p>
  </li>
</ol>

<h1 id="76-更多提示">7.6 更多提示</h1>

<h4 id="不要自作聪明">不要自作聪明</h4>

<p>指定变量的新值时，要注意最好使用<code class="highlighter-rouge">v1' = exp</code>或<code class="highlighter-rouge">v2' \in exp</code>的合取形式，而且<code class="highlighter-rouge">exp</code>中不能出现不确定的<code class="highlighter-rouge">'</code>变量。</p>

<h4 id="类型不变量不是假设"><em>类型不变量</em>不是假设</h4>

<p>要留意<em>类型不变量</em>中对变量<em>类型</em>做出的限制，其他公式是不知道的。例如</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>action1 == (n' &gt; 7) /\ ...
action2 == (n' &lt;= 6) /\ ...
Next == (n \in Nat) /\ (action1 \/ action2)
</code></pre></div></div>
<p>不要遗漏这里的<code class="highlighter-rouge">n \in Nat</code>。</p>

<h4 id="不要过于抽象">不要过于抽象</h4>

<p>以一个获取键盘输入的规范为例，要考虑到同时按下多个键的情况。规范过于抽象会忽视真实系统的一些状态。</p>

<h4 id="有的值不能比较">有的值不能比较</h4>

<p>TLA+ 不能判断两个不同<em>类型</em>值不相等。</p>

<h4 id="把量词提到外层">把量词提到外层</h4>

<p>量词提到子公式的外层能提升可读性。</p>

<h4 id="注意的对象">注意<code class="highlighter-rouge">'</code>的对象</h4>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>op(a) == x + a
</code></pre></div></div>
<p>对于上面运算符</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>op(y)' = (x + y)' = x' + y'
op(y') = x + y'
</code></pre></div></div>
<p>而<code class="highlighter-rouge">op'(y)</code>有语法错误，<code class="highlighter-rouge">'</code>只能应用在表达式上。</p>

<h4 id="注释就写成注释">注释就写成注释</h4>

<p>注释不要通过<code class="highlighter-rouge">/\ FALSE</code>等表达写成规范的一部分。</p>

<h1 id="77-何时写规范如何写规范">7.7 何时写规范，如何写规范</h1>

<p>最好在实现系统之前明确规范。规范可以先仅描述部分特性，即使这样也可以利用工具检查一些错误。</p>]]></content><author><name></name></author><category term="tech" /><summary type="html"><![CDATA[7.1 为什么写规范]]></summary></entry></feed>