<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://sriramsami.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sriramsami.com/" rel="alternate" type="text/html" /><updated>2025-12-30T06:15:33+00:00</updated><id>https://sriramsami.com/feed.xml</id><title type="html">Sriram Sami</title><subtitle>Sriram Sami</subtitle><entry><title type="html">TurboAnimator</title><link href="https://sriramsami.com/turboanimator/" rel="alternate" type="text/html" title="TurboAnimator" /><published>2025-03-03T00:00:00+00:00</published><updated>2025-03-03T00:00:00+00:00</updated><id>https://sriramsami.com/turboanimator</id><content type="html" xml:base="https://sriramsami.com/turboanimator/"><![CDATA[<h1 id="overview">Overview</h1>

<p>TurboAnimator (at <a href="https://turboanimator.com" target="_blank">turboanimator.com</a>) is a browser extension I made to automate tedious animation tasks in Google Slides with a single keyboard shortcut. I made it because I use a lot of animations in my Google Slides for teaching, and it’s pretty tedious. There’s no default way to change the default animation style and delay, and you need many clicks to add, delete, and modify animations.</p>

<p>The extension simulates the clicks you would make to do these tasks, and does them for you. It’s saved me a lot of time, and I hope it can save you time too.</p>

<p>It’s free and available for Chrome and Firefox.</p>

<div class="d-flex flex-column flex-lg-row align-items-center">
    <a href="https://chrome.google.com/webstore/detail/turboanimator-for-google/mhdmaokphjlbobmlioagngakkofbchdo" target="_blank"><img class="app-badge" style="height: 10vh" src="/assets/images/chrome_store.png" alt="TurboAnimator on the Chrome Web Store" /></a>
    <a href="https://addons.mozilla.org/en-US/firefox/addon/slides-animator/" target="_blank"><img class="app-badge" style="height: 10vh" src="/assets/images/firefox.webp" alt="TurboAnimator at Firefox Browser Add-Ons" /></a>
</div>

<h1 id="interesting-things">Interesting things</h1>

<ul>
  <li>UI interaction on Google Slides isn’t entirely straightforward. Some elements need to be “clicked” multiple times via Javascript, but they require only one click in “real life”.</li>
  <li>A particularly interesting effect is that if we add animations too fast (specifically, if we add an animation and set the delay slider, then immediately create a new animation), the delay value is incorrectly for the previous animation. This is because the slider hasn’t finished updating yet in some internal method. A minimum delay of around 0.5s (not tested rigorously) is required to avoid this issue.</li>
  <li>I just like this idea in general because of how much time it genuinely saved - both across teaching and research, since well-animated slides are important for piecemeal content delivery.</li>
</ul>]]></content><author><name>sriram</name></author><category term="project" /><category term="extension" /><category term="google-slides" /><category term="animation" /><category term="productivity" /><summary type="html"><![CDATA[Browser extension to automate tedious animation tasks in Google Slides with a single keyboard shortcut.]]></summary></entry><entry><title type="html">NUS Timetable Optimizer</title><link href="https://sriramsami.com/nusoptimizer/" rel="alternate" type="text/html" title="NUS Timetable Optimizer" /><published>2025-01-02T00:00:00+00:00</published><updated>2025-01-02T00:00:00+00:00</updated><id>https://sriramsami.com/nusoptimizer</id><content type="html" xml:base="https://sriramsami.com/nusoptimizer/"><![CDATA[<h1 id="overview">Overview</h1>

<p>NUS Timetable Optimizer (at <a href="https://nus-optimizer.com/" target="_blank">nus-optimizer.com</a>) is a free and efficient tool to find the best timetable for your NUS modules. This project is the culmination of many years of work and failed attempts to try to realize the idea, and in many ways, I consider it to be one of my best achievements :)</p>

<p>While there’s more info at <a href="https://nus-optimizer.com/about" target="_blank">nus-optimizer.com/about</a> and on <a href="https://github.com/frizensami/nus-timetable-optimizer" target="_blank">GitHub</a>, the core technology is to use a “SMT Solver” e.g., <a href="https://github.com/Z3Prover/z3" target="_blank">Z3</a> to find the best timetable for you. This is a very powerful tool that can solve constraint problems as long as they are posed a certain way.</p>

<p>One of the critical things I’m proud of is that this runs <strong>entirely on the browser</strong>. Because of the CPU intensive nature of the problem, running a server to spawn threads to solve individua timetables at 100% CPU was a cost I did not want to pay (unlike other approaches to solve this). I had to use a WebAssembly version of Z3, which is a bit of a pain to set up, but ultimately let me host it for free.</p>

<h1 id="interesting-things">Interesting things</h1>

<ul>
  <li>These are the attempts I made before I finally got it right:
    <ul>
      <li><strong>Attempt 1 (August 2015, 6 years before final version)</strong>: “Week 0” of my first semester at NUS – I worked together with a few friends to build a brute-force heuristic solver that would try to find the best timetable for you. It was fun, but also very slow and not very accurate. That code is still up at <a href="https://github.com/frizensami/corsai" target="_blank">GitHub</a> but I can’t say the implementation is anything to be proud of.</li>
      <li><strong>Attempt 2 (March 2016, 5 years before final version)</strong>: NUS School of Computing flagship 24-hour hackthon “Hack-N-Roll” – four of us worked together to build a more efficient solver that would use a set of smarter heuristics. I don’t recall anything about the actual approach at this point, but I do recall it not really working that well when the judges came by to judge the hackathon results – again, too slow, too CPU intensive.</li>
      <li><strong>The drought</strong>: After consulting some of the faculty in NUS, I’d pretty much given up on the idea of building a solver that would work in a reasonable amount of time. Also, I had other things to do, like graduate.</li>
      <li><strong>Initial inspiration (2018, 3 years before the final version)</strong>: I heard that two students from NUS might have solved the problem by using a SAT solver (<a href="https://github.com/raynoldng/nusmods-planner" target="_blank">on GitHub</a>). I visited the website they had set up, and it looked good! So I figured the problem was solved, and decided to put it to rest.</li>
      <li><strong>Attempt 3 (2021, the final version)</strong>: I realized that the website I’d seen in 2018 was no longer up, and my understanding was that hosting costs were likely an issue. I decided to try and tackle the issue and also solve the problem of scalable hosting of this solver so that I wouldn’t have to fund the deployment out of my own pocket. After a few months of work, I finally got it working, and it’s been happily running with minimal interference since then :)</li>
    </ul>
  </li>
</ul>]]></content><author><name>sriram</name></author><category term="project" /><category term="timetable" /><category term="nus" /><category term="optimization" /><summary type="html"><![CDATA[A free and efficient tool to find the best timetable for your NUS modules.]]></summary></entry><entry><title type="html">Singapore Blood Stocks</title><link href="https://sriramsami.com/sgbloodstocks/" rel="alternate" type="text/html" title="Singapore Blood Stocks" /><published>2024-01-02T00:00:00+00:00</published><updated>2024-01-02T00:00:00+00:00</updated><id>https://sriramsami.com/sgbloodstocks</id><content type="html" xml:base="https://sriramsami.com/sgbloodstocks/"><![CDATA[]]></content><author><name>sriram</name></author><category term="project" /><category term="blood" /><category term="redcross" /><category term="singapore" /><category term="data" /><summary type="html"><![CDATA[A Telegram bot and cheeky scrapers to get the latest blood stock levels in Singapore.]]></summary></entry><entry><title type="html">ChatGPT Popup Notifier</title><link href="https://sriramsami.com/chatgpt-popup-notifier/" rel="alternate" type="text/html" title="ChatGPT Popup Notifier" /><published>2023-01-01T00:00:00+00:00</published><updated>2023-01-01T00:00:00+00:00</updated><id>https://sriramsami.com/chatgpt-popup-notifier</id><content type="html" xml:base="https://sriramsami.com/chatgpt-popup-notifier/"><![CDATA[<h1 id="overview">Overview</h1>

<p>I made ChatGPT Popup Notifier since I always ask ChatGPT something that takes a while to process, switch to another tab, and sometimes forget about ChatGPT for hours. This extension notifies you when ChatGPT finishes generating your response, so you can get back to it immediately, including immediately notifying you if ChatGPT returned an error (which happens often!). For those of us using DALL-E, which takes ages to complete generating a picture, this also works.</p>

<p>The extension simulates the clicks you would make to do these tasks, and does them for you. It’s saved me a lot of time, and I hope it can save you time too.</p>

<p>It’s free and available for Chrome.</p>

<div class="d-flex flex-column flex-lg-row align-items-center">
    <a href="https://chromewebstore.google.com/detail/chatgpt-popup-notifier/ljhchoofdnmgnjgpclaofcedbfmbcmgp" target="_blank"><img class="app-badge" style="height: 10vh" src="/assets/images/chrome_store.png" alt="TurboAnimator on the Chrome Web Store" /></a>
</div>

<h2 id="interesting-things">Interesting things</h2>

<ul>
  <li>The extension includes <code class="language-plaintext highlighter-rouge">hacktimer.js</code>, which is a library that allows you to speed up ChatGPT responses even if you navigate away from the ChatGPT tab – this slowdown happens because browsers “throttle” background tabs. While I haven’t tested it comprehensively, it seemed faster with <code class="language-plaintext highlighter-rouge">hacktimer.js</code> than without it.</li>
</ul>]]></content><author><name>sriram</name></author><category term="project" /><category term="extension" /><category term="chatgpt" /><category term="productivity" /><summary type="html"><![CDATA[Browser extension that notifies you when ChatGPT finishes generating your response (and more).]]></summary></entry><entry><title type="html">Offline Plagiarism Detector</title><link href="https://sriramsami.com/plagiarism/" rel="alternate" type="text/html" title="Offline Plagiarism Detector" /><published>2023-01-01T00:00:00+00:00</published><updated>2023-01-01T00:00:00+00:00</updated><id>https://sriramsami.com/plagiarism</id><content type="html" xml:base="https://sriramsami.com/plagiarism/"><![CDATA[<h1 id="overview">Overview</h1>

<p>Online plagiarism detection tools usually come with a few constraints. It could be a paid-only service, the number of characters to check could be artificially limited, etc. This tool aims to fill a gap where:</p>

<ol>
  <li>Plagiarism cases are usually simple copy-paste jobs of a few text phrases with minor edits,</li>
  <li>Paying for an online tool is unpalatable,</li>
  <li>The source texts that might be copied from can be put together manually by the user into a few files (i.e. the Internet is not automatically searched by the tool), or the only concern is people copying from each other, and</li>
  <li>Running a command-line tool is simple enough for the user</li>
</ol>

<p>The tool is written in Rust, and it’s a quick-and-dirty implementation that uses either an equality check between sets of words or Levenshtein distance to detect plagiarism. It’s parallelized with Rayon to make Levenshtein distance calculations faster, specifically.</p>

<p>The Rust crates can be found here:</p>

<p><a href="https://crates.io/crates/plagiarism-basic"><img src="https://img.shields.io/crates/v/plagiarism-basic?label=PlagiarismBasic%20Executable" alt="Crates.io Version" /></a>
<a href="https://crates.io/crates/plagiarismbasic_lib"><img src="https://img.shields.io/crates/v/plagiarismbasic_lib?label=PlagiarismBasic%20Library" alt="Crates.io Version" /></a></p>

<p>and the <a href="https://github.com/frizensami/plagiarism-basic">GitHub repository here</a>.</p>

<h2 id="interesting-things">Interesting things</h2>

<ul>
  <li>We generate a HTML file using Handlebars and style it with Semantic UI. This is auto-opened by default using <code class="language-plaintext highlighter-rouge">xdg-open</code>.</li>
</ul>]]></content><author><name>sriram</name></author><category term="project" /><category term="rust" /><category term="plagiarism" /><category term="education" /><summary type="html"><![CDATA[Offline quick-and-dirty text plagiarism checker written in Rust]]></summary></entry><entry><title type="html">(Maybe) Tracking Singapore’s Blood Stock Fluctuations</title><link href="https://sriramsami.com/bloodstocks/" rel="alternate" type="text/html" title="(Maybe) Tracking Singapore’s Blood Stock Fluctuations" /><published>2022-05-26T12:26:00+00:00</published><updated>2022-05-26T12:26:00+00:00</updated><id>https://sriramsami.com/bloodstocks</id><content type="html" xml:base="https://sriramsami.com/bloodstocks/"><![CDATA[<p><span style="color:red"> The Red Cross website was redesigned so the technique I use has changed. However, this post is not yet updated with that information. </span></p>

<h2 id="tldr">TLDR</h2>

<ol>
  <li>I wrote a Telegram bot to track Singapore’s blood stock levels (<a href="https://t.me/sgbloodstocksbot" target="\_blank" rel="noopener">@sgbloodstocksbot</a>). Users can subscribe to the bot to be notified about changes in their blood type’s stock levels (or any changes at all).</li>
  <li>The stock data from 14th June 2021 onwards (constantly updated with new data) is available <a href="https://github.com/frizensami/red-cross-blood-stocks" target="\_blank" rel="noopener">in this repository</a> in the <a href="https://next.github.com/projects/flat-data" target="\_blank" rel="noopener">“Flat Data” format</a>.</li>
</ol>

<!-- ![Graph of Singapore Blood Stocks since June 2021](/assets/images/bloodstocks/bloodstocks.png){:height="auto"} -->

<!-- Dynamically generate a graph of blood stocks using Plotly. The data is in assets/blood.json -->
<!-- The data format is a list of dictionaries, each dict has structure e.g., {"date":"2024-03-06","bloodType":"A+","fillLevel":83} -->

<!-- Plot three subplots: (1) all the blood stocks (2) just the positive blood types (3) just the negative blood types -->
<!-- Reuse as much code as possible. Also, display any other graphs you think are useful.  -->

<h3 id="interactive-graphs">Interactive Graphs</h3>

<!-- Add a note in red that this section is best viewed on a desktop. -->

<p style="color:red"><em>These graphs are best viewed on a desktop.</em></p>

<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>

<details>
  <summary style="background-color: lightgrey; padding: 10px; margin-bottom: 10px;">
    <span style="display: inline-block; margin-right: 5px;" id="accordion-arrow">▶</span>
    Daily Blood Stock Data (Click to expand)
  </summary>
  <div id="blood-stocks-graph"></div>
</details>

<details open="">
  <summary style="background-color: lightgrey; padding: 10px; margin-bottom: 10px;">
    <span style="display: inline-block; margin-right: 5px;" id="accordion-arrow">▶</span>
    Weekly Blood Stock Data (Click to expand)
  </summary>
  <div id="blood-stocks-weekly-graph"></div>
</details>

<details>
  <summary style="background-color: lightgrey; padding: 10px; margin-bottom: 10px;">
    <span style="display: inline-block; margin-right: 5px;" id="accordion-arrow">▶</span>
    Monthly Blood Stock Data (Click to expand)
  </summary>
  <div id="blood-stocks-monthly-graph"></div>
</details>

<script>
  const accordions = document.querySelectorAll('details');
  accordions.forEach(accordion => {
    accordion.addEventListener('toggle', () => {
      const arrow = accordion.querySelector('#accordion-arrow');
      arrow.textContent = accordion.open ? '▼' : '▶';
    });
  });
</script>

<script>
  async function plotDailyGraph() {
    const response = await fetch('/assets/blood.json');
    const data = await response.json();
    const bloodTypes = [...new Set(data.map(d => d.bloodType))];
    const positiveBloodTypes = bloodTypes.filter(bloodType => bloodType.includes('+'));
    const negativeBloodTypes = bloodTypes.filter(bloodType => bloodType.includes('-'));
    const positiveTraces = positiveBloodTypes.map(bloodType => ({
      x: data.filter(d => d.bloodType === bloodType).map(d => d.date),
      y: data.filter(d => d.bloodType === bloodType).map(d => d.fillLevel),
      mode: 'lines',
      name: bloodType
    }));
    const negativeTraces = negativeBloodTypes.map(bloodType => ({
      x: data.filter(d => d.bloodType === bloodType).map(d => d.date),
      y: data.filter(d => d.bloodType === bloodType).map(d => d.fillLevel),
      mode: 'lines',
      name: bloodType
    }));
    const layout = {
      title: 'Daily Singapore Blood Stocks since June 2021 (Data not collected from Mid-August to Mid-Oct 2023)',
      xaxis: {
        title: 'Date',
        tickformat: '%b %Y',
        type: 'date',
        rangemode: 'tozero'
      },
      yaxis: {
        title: 'Fill Level (%)'
      },
      subplot_titles: ['Positive Blood Stocks', 'Negative Blood Stocks']
    };
    Plotly.newPlot('blood-stocks-graph', [...positiveTraces, ...negativeTraces], { ...layout, showlegend: true });
  }
  plotDailyGraph();
</script>

<!-- Plot a weekly-averaged graph of the data. Do not use reduce functions. Sort by the date. Only display the month and year on x axis -->
<script>
  async function plotWeeklyGraph() {
    const response = await fetch('/assets/blood.json');
    const data = await response.json();
    const bloodTypes = [...new Set(data.map(d => d.bloodType))];
    const traces = bloodTypes.map(bloodType => {
      const bloodTypeData = data.filter(d => d.bloodType === bloodType);
      const weeklyAverages = [];
      let weeklyTotal = 0;
      let weeklyCount = 0;
      for (let i = 0; i < bloodTypeData.length; i++) {
        weeklyTotal += bloodTypeData[i].fillLevel;
        weeklyCount++;
        if (i % 7 === 6 || i === bloodTypeData.length - 1) {
          weeklyAverages.push(weeklyTotal / weeklyCount);
          weeklyTotal = 0;
          weeklyCount = 0;
        }
      }
      return {
        x: weeklyAverages.map((_, i) => bloodTypeData[i * 7].date),
        y: weeklyAverages,
        mode: 'lines',
        name: bloodType
      };
    });
    const layout = {
      title: 'Weekly Averaged Singapore Blood Stocks since June 2021 (Data not collected from Mid-August to Mid-Oct 2023)',
      xaxis: {
        title: 'Date',
        tickformat: '%b %Y',
        type: 'date',
        rangemode: 'tozero'
      },
      yaxis: {
        title: 'Fill Level (%)'
      }
    };
    Plotly.newPlot('blood-stocks-weekly-graph', traces, layout);
  }
  plotWeeklyGraph();
</script>

<!-- Plot now a monthly averaged version -->
<script>
  async function plotMonthlyGraph() {
    const response = await fetch('/assets/blood.json');
    const data = await response.json();
    const bloodTypes = [...new Set(data.map(d => d.bloodType))];
    const traces = bloodTypes.map(bloodType => {
      const bloodTypeData = data.filter(d => d.bloodType === bloodType);
      const monthlyAverages = [];
      let monthlyTotal = 0;
      let monthlyCount = 0;
      for (let i = 0; i < bloodTypeData.length; i++) {
        monthlyTotal += bloodTypeData[i].fillLevel;
        monthlyCount++;
        if (i % 30 === 29 || i === bloodTypeData.length - 1) {
          monthlyAverages.push(monthlyTotal / monthlyCount);
          monthlyTotal = 0;
          monthlyCount = 0;
        }
      }
      return {
        x: monthlyAverages.map((_, i) => bloodTypeData[i * 30].date),
        y: monthlyAverages,
        mode: 'lines',
        name: bloodType
      };
    });
    const layout = {
      title: 'Monthly Averaged Singapore Blood Stocks since June 2021 (Data not collected from Mid-August to Mid-Oct 2023)',
      xaxis: {
        title: 'Date',
        tickformat: '%b %Y',
        type: 'date',
        rangemode: 'tozero'
      },
      yaxis: {
        title: 'Fill Level (%)'
      }
    };
    Plotly.newPlot('blood-stocks-monthly-graph', traces, layout);
  }
  plotMonthlyGraph();
</script>

<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->

<p><strong>Table of Contents</strong></p>

<ul>
  <li><a href="#tldr">TLDR</a>
    <ul>
      <li><a href="#interactive-graphs">Interactive Graphs</a></li>
    </ul>
  </li>
  <li><a href="#disclaimers">Disclaimers</a></li>
  <li><a href="#why-did-i-do-this">Why did I do this?</a></li>
  <li><a href="#where-do-i-get-exact-blood-stock-levels">Where do I get exact blood stock levels?</a>
    <ul>
      <li><a href="#is-this-okay">Is this okay?</a></li>
    </ul>
  </li>
  <li><a href="#overview-of-the-data">Overview of the data</a>
    <ul>
      <li><a href="#downloading-and-plotting">Downloading and plotting</a></li>
      <li><a href="#graph-format-and-data-frequency">Graph format and data frequency</a></li>
      <li><a href="#some-questions">Some questions</a></li>
    </ul>
  </li>
  <li><a href="#wrapping-up">Wrapping up</a></li>
  <li><a href="#resources">Resources</a></li>
</ul>

<!-- markdown-toc end -->

<h2 id="disclaimers">Disclaimers</h2>

<ul>
  <li>This is all for personal curiosity and a side-effect of making a (hopefully) useful tool. I am not an expert in any related field.</li>
  <li>I don’t know if it’s a good idea to donate blood only when blood stocks dip below 100%. None of this is advice.</li>
  <li>While it’s a strong hypothesis, there is no strict guarantee that the blood stock levels are accurate. Read <a href="#where-do-you-get-exact-blood-stock-levels">Where do you get exact blood stock levels?</a> for more.</li>
</ul>

<h2 id="why-did-i-do-this">Why did I do this?</h2>

<p>I was looking to donate my blood sometime in April 2021. Each time I checked the <a href="https://redcross.sg/" target="\_blank" rel="noopener">Singapore Red Cross website</a> (scroll down there to see blood stocks), my blood type was fully stocked. It didn’t seem like the most useful time to donate, so I decided to wait.</p>

<p>The (happy) problem was, every time I checked for a while, my stock level was full. <strong>When was the best time to donate?</strong>.</p>

<p>I decided to make a Telegram bot <a href="https://t.me/sgbloodstocksbot" target="\_blank" rel="noopener">(try @sgbloodstocksbot here!)</a> that scrapes data from the Red Cross website and pings me when my blood type’s stocks change.</p>

<p>I then heard about <a href="https://github.com/datascapesg/" target="\_blank" rel="noopener">Datascape Singapore</a>, a project to save Singapore-related data into <a href="https://next.github.com/projects/flat-data" target="\_blank" rel="noopener">“Flat Data” files on GitHub</a>. I used the same code from the Telegram bot to scrape and <strong>save the blood stock data daily</strong>, from 14th June 2021 onward, into <a href="https://github.com/frizensami/red-cross-blood-stocks" target="\_blank" rel="noopener">this repository</a> (see <code class="language-plaintext highlighter-rouge">blood-stocks.json</code> for the latest scraped data).</p>

<h2 id="where-do-i-get-exact-blood-stock-levels">Where do I get exact blood stock levels?</h2>

<p><span style="color:red"> The Red Cross website was redesigned so the technique I use has changed. Howver, this post is not yet updated with that information. This section is outdated, but hopefully still interesting. </span></p>

<p>This is where the (maybe) comes from. There is no official API or data source that I am aware of, so scraping is the only option. The Red Cross reports blood stock levels like this on their website:</p>

<p><img src="/assets/images/bloodstocks/redcross_stock.png" alt="Red Cross blood stock reporting" height="auto" /></p>

<p>It seems like there are only four preset levels for each blood type: <strong>Healthy/Moderate/Low/Critical</strong>. That seemed fine at first. I could just report the broad category of blood stock level. However, if you look closer, you’ll notice something curious.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">A- Blood Type</th>
      <th style="text-align: center">O- Blood Type</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><img src="/assets/images/bloodstocks/bloodstock_aminus.png" alt="Bloodstock picture for A- type" height="auto" /></td>
      <td style="text-align: center"><img src="/assets/images/bloodstocks/bloodstock_ominus.png" alt="Bloodstock picture for O- type" height="auto" /></td>
    </tr>
  </tbody>
</table>

<p>Two blood types, both reported as “Low”, have different “fill levels” in the image. Specifically, the HTML representing each image was (note the <code class="language-plaintext highlighter-rouge">height</code> parameter):</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- A- blood type --&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"fill_humam"</span> <span class="na">style=</span><span class="s">"height: 45%; background: #ffc000;"</span><span class="nt">&gt;&lt;/div&gt;</span>

<span class="c">&lt;!-- 0- blood type --&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"fill_humam"</span> <span class="na">style=</span><span class="s">"height: 35%; background: #ffc000;"</span><span class="nt">&gt;&lt;/div&gt;</span>
</code></pre></div></div>

<p>So maybe there are more presets than expected (increments of 5% each time?). But if we look at the <code class="language-plaintext highlighter-rouge">height</code> parameters of some of the other blood level images:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- A+ blood type --&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"fill_humam"</span> <span class="na">style=</span><span class="s">"height: 59%; background: #ffff00;"</span><span class="nt">&gt;&lt;/div&gt;</span>

<span class="c">&lt;!-- AB- blood type --&gt;</span>
<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"fill_humam"</span> <span class="na">style=</span><span class="s">"height: 18%; background: #ff0000;"</span><span class="nt">&gt;&lt;/div&gt;</span>
</code></pre></div></div>

<p>These blood stock levels seem to be <strong>updated to a precision of 1%</strong>. One last piece of evidence:</p>

<p><img src="/assets/images/bloodstocks/telegram_update.png" alt="Bloodstock update on Telegram" height="auto" /></p>

<p><a href="https://t.me/sgbloodstocksbot" target="\_blank" rel="noopener">@sgbloodstocksbot</a> pings all subscribers when blood stock levels change (you can watch for a specific blood type or any of them). Notice that the O+ blood type changes only by 1%. Visually, no website user will see a 1% change in the image fill level, so this must be some semi-automated and precise process.</p>

<p><strong>TLDR: I hypothesize that the image fill levels are accurate and precise blood stock levels</strong>.</p>

<h3 id="is-this-okay">Is this okay?</h3>

<p>I get asked sometimes if the blood stock levels are sensitive information and whether I should be publicly broadcasting them. In my view, if the stock levels are accurate, and are already publicly updated by the Red Cross to within 1% (even if it’s encoded within an image), then I’m just using public data. My only intention is to create a useful service to know when to donate blood.</p>

<h2 id="overview-of-the-data">Overview of the data</h2>

<h3 id="downloading-and-plotting">Downloading and plotting</h3>

<p>The blood stock data is stored in <a href="https://github.com/frizensami/red-cross-blood-stocks" target="\_blank" rel="noopener">this repository</a>, but each update is a separate <a href="https://www.atlassian.com/git/tutorials/saving-changes/git-commit" target="\_blank" rel="noopener">git commit</a>. We first have to download all versions of <code class="language-plaintext highlighter-rouge">blood-stocks.json</code> across all commits. The code to download and analyze the most up-to-date data is <a href="https://github.com/frizensami/bloodstock_analysis" target="\_blank" rel="noopener">here</a>, feel free to give it a go!</p>

<p style="text-align: center;"><img src="/assets/images/bloodstocks/bloodstocks.png" alt="Sample Graph of Singapore Blood Stocks" height="auto" />
If you’re on mobile, you can press and hold on this image and open it in a new tab.</p>

<h3 id="graph-format-and-data-frequency">Graph format and data frequency</h3>

<p>This is a subset of the full data, just as an example of the data format and some insights.</p>

<ul>
  <li>
    <p>The <strong>x-axis</strong> represents <strong>time</strong>.</p>
  </li>
  <li>
    <p>The <strong>y-axis</strong> represents the <strong>reported blood stock level</strong>, from 0% to 100%. In this observation period, the stocks never hit 0%, but a few blood types spent a significant amount of time at 100%.</p>

    <ul>
      <li>One thing we don’t know if 100% represents the same number of units of blood for every blood type. Since the demand for blood varies across blood types, maybe the target 100% amount is different for each?</li>
    </ul>
  </li>
  <li>
    <p><strong>Each dot</strong> on each line represents <strong>one observation</strong>.</p>
    <ul>
      <li>Notice that the dots are not evenly spaced. The blood stocks only <strong>update on weekdays</strong>! They also don’t seem to update on public holidays. For instance, 4th November 2021, Thursday, the day of Deepavali, is missing. This probably points to a human-in-the-loop system.</li>
    </ul>
  </li>
</ul>

<h3 id="some-questions">Some questions</h3>

<p>Disclaimer again that I’m not an expert. The Red Cross has <a href="https://www.hsa.gov.sg/blood-donation/blood-facts-and-figures" target="\_blank" rel="noopener">facts and figures here</a> and an infographic of 2021 blood usage <a href="https://www-hsa-gov-sg-admin.cwp.sg/docs/default-source/bsg/big-blood-picture-2021.pdf" target="\_blank" rel="noopener">here</a>.</p>

<p>These are just some layman questions:</p>

<ul>
  <li>Is blood demand / supply seasonal? How best can we understand these trends?</li>
  <li>What changed in Mid-Oct 2021 to lower positive-type blood supply so much?</li>
  <li>What happened to B- stock in Nov 2021?</li>
  <li>How much do blood drives affect stock levels?</li>
  <li>and many others</li>
</ul>

<p>Please feel free to download and analyze this data as well.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>This data is all a side effect of <a href="https://t.me/sgbloodstocksbot" target="\_blank" rel="noopener">@sgbloodstocksbot</a>, so hopefully both the data and bot turn out to be useful. Please feel free to drop me a message if you’re interested in anything related to this :)</p>

<h2 id="resources">Resources</h2>

<ul>
  <li><a href="https://t.me/sgbloodstocksbot">Singapore Blood Stocks Bot on Telegram</a>, code is <a href="https://github.com/frizensami/sg-blood-stocks-bot">here</a></li>
  <li><a href="https://github.com/frizensami/scrapers/blob/develop/netlify/functions/redcross-bloodstocks.js">Scraper for blood stocks</a></li>
  <li><a href="https://github.com/frizensami/red-cross-blood-stocks">All blood stock data (Github Flat Data format)</a></li>
  <li><a href="https://github.com/frizensami/bloodstock_analysis">Bloodstock analysis scripts used for this post</a></li>
</ul>]]></content><author><name>sriram</name></author><category term="blog" /><category term="redcross" /><category term="bloodbank" /><category term="data-analysis" /><category term="scraping" /><summary type="html"><![CDATA[The Red Cross website was redesigned so the technique I use has changed. However, this post is not yet updated with that information.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sriramsami.com/assets/images/markdown.jpg" /><media:content medium="image" url="https://sriramsami.com/assets/images/markdown.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">LAPD: Laser-Assisted Photography Detection</title><link href="https://sriramsami.com/lapd/" rel="alternate" type="text/html" title="LAPD: Laser-Assisted Photography Detection" /><published>2022-01-02T00:00:00+00:00</published><updated>2022-01-02T00:00:00+00:00</updated><id>https://sriramsami.com/lapd</id><content type="html" xml:base="https://sriramsami.com/lapd/"><![CDATA[<h1 id="overview">Overview</h1>

<p>LAPD (at <a href="https://github.com/frizensami/lapd" target="_blank">github.com/frizensami/lapd/</a>) is my research project to detect hidden cameras with just a smartphone. We created a smartphone app on Android that emits laser signals from the time-of-flight (ToF) sensor on certain smartphones, and uses computer vision and machine learning techniques to locate the unique reflections from hidden cameras.</p>

<p>Most of the information about LAPD can be found on my <a href="/research">research page</a>. However, I thought it might be interesting to describe some of the interesting quirks of the project.</p>

<h1 id="interesting-things">Interesting things</h1>

<ul>
  <li>Perhaps surprisingly, our biggest issue was Android’s augmented reality framework (ARCore). We added this to the project since we needed to know the 3D positions of objects around the smartphone, so that we could calculate angles, distances, etc. However, we couldn’t access the raw data from the ToF sensor while ARCore was running, so we ended up with a nerfed version of ARCore that we had to implement many, many bandaids on.
    <ul>
      <li>Just as an indication of how powerless we are with ARCore, here’s <a href="https://github.com/google-ar/arcore-android-sdk/issues/153">an issue</a> on the ARCore GitHub repo that’s been open for many years about a simple issue, with many people chiming in, yet with no response.</li>
    </ul>
  </li>
  <li>We also had to manually align the ToF sensor with the camera sensor, since the ToF sensor is not aligned with the camera sensor on most smartphones. The funny part is that the “standard computer vision approach” is to use a checkerboard pattern, but that has two issues: (a) the checkboard looks different from the PoV of the ToF sensor vs the normal camera, and (b) we somehow could not get this as accurate as just asking the user to calibrate it by inputting two numbers to align the scaling and offset of the two images.</li>
</ul>]]></content><author><name>sriram</name></author><category term="project" /><category term="research" /><category term="time-of-flight" /><category term="smartphone" /><category term="hidden-camera" /><summary type="html"><![CDATA[Part of my doctoral research: a novel hidden camera detection and localization system that leverages the time-of-flight (ToF) sensor on commodity smartphones.]]></summary></entry><entry><title type="html">Round Trip, MD</title><link href="https://sriramsami.com/roundtripmd/" rel="alternate" type="text/html" title="Round Trip, MD" /><published>2022-01-02T00:00:00+00:00</published><updated>2022-01-02T00:00:00+00:00</updated><id>https://sriramsami.com/roundtripmd</id><content type="html" xml:base="https://sriramsami.com/roundtripmd/"><![CDATA[<h1 id="overview">Overview</h1>

<p>Round Trip MD (at <a href="https://roundtrip.sriramsami.com/" target="_blank">roundtrip.sriramsami.com</a>) attempts to ease the pressure of junior doctors having to lead senior doctors on rounds around unfamiliar / complex environments. Given a list of wards to visit in the hospital, it returns the most efficient route (by time). I made this in collaboration with a medical student friend who helped to map out <a href="https://www.sgh.com.sg/" target="_blank">Singapore General Hospital</a> (SGH) and its wards.</p>

<h1 id="interesting-things">Interesting things</h1>

<ul>
  <li>The solver includes both an <em>exact</em> solver (for a bruteforce search when the number of stops in the rounds are low) and <em>genetic</em> solver (for a heuristic search if there are many stops).</li>
  <li>It includes details about lift lobbies and staircases and their expected “costs” in terms of time when taking these routes</li>
  <li>However, it doesn’t account for “effort”, i.e., that stairs are harder to climb than lifts. Another issue is that taking one set of stairs is fine, but taking many in succession is not, but it’s really hard to think of a way to quantify and represent this.</li>
  <li>The brute force solver just generates all possible permutations of node orders, and computes the cost for the whole route. A nice optimization here is that the cost of the route is the sum of the cost of individual shortest paths, so we can just cache the shortest paths we find along the way between combinations of two nodes.</li>
  <li>The route is <em>directed</em> – some areas in the hospital are one-way only! This complicates things for the sake of a few edges, but it’s necessary since these situations apparently happen very often.</li>
</ul>]]></content><author><name>sriram</name></author><category term="project" /><category term="website" /><category term="pathfinding" /><category term="graphs" /><category term="medical" /><summary type="html"><![CDATA[A route planner for doctors' rounds within Singapore General Hospital]]></summary></entry><entry><title type="html">A beginner’s experience with TLAPS (The TLA+ Proof System)</title><link href="https://sriramsami.com/tlaps/" rel="alternate" type="text/html" title="A beginner’s experience with TLAPS (The TLA+ Proof System)" /><published>2019-04-24T10:00:00+00:00</published><updated>2019-04-24T10:00:00+00:00</updated><id>https://sriramsami.com/tlaps</id><content type="html" xml:base="https://sriramsami.com/tlaps/"><![CDATA[<!-- TOC depthFrom:2 -->

<ul>
  <li><a href="#1-pre-requisite-knowlege-and-resources">1. Pre-requisite Knowlege and Resources</a></li>
  <li><a href="#2-motivation">2. Motivation</a></li>
  <li><a href="#3-getting-started">3. Getting Started</a>
    <ul>
      <li><a href="#31-pre-requisites">3.1. Pre-Requisites</a></li>
      <li><a href="#32-writing-proofs-in-the-tla-toolbox">3.2. Writing proofs in the TLA+ Toolbox</a></li>
    </ul>
  </li>
  <li><a href="#4-the-hello-world-of-tlaps">4. The “Hello World” of TLAPS</a>
    <ul>
      <li><a href="#41-running-these-proofs">4.1. Running these proofs</a></li>
    </ul>
  </li>
  <li><a href="#5-proving-transitive-more-than-relationships">5. Proving Transitive More-Than relationships</a></li>
  <li><a href="#6-proving-some-square-root-stuff-using-operators-and-structured-proofs">6. Proving Some Square Root Stuff: Using Operators and Structured Proofs</a></li>
  <li><a href="#7-dividing-linear-combinations">7. Dividing Linear Combinations</a>
    <ul>
      <li><a href="#71-not-following-my-own-advice">7.1. Not following my own advice</a></li>
    </ul>
  </li>
  <li><a href="#8-conclusions">8. Conclusions</a></li>
</ul>

<!-- /TOC -->

<h2 id="1-pre-requisite-knowlege-and-resources">1. Pre-requisite Knowlege and Resources</h2>
<ul>
  <li><a href="https://sriramsami.com/tlaplus/">My initial post on Learning TLA+</a> - to understand the difference between TLA+/PlusCal/TLC (finite state checking) and TLAPS (general proofs)</li>
  <li>A basic understanding of TLA+ syntax. Even without this knowledge, perhaps the syntax in this post will be self-explanatory.</li>
</ul>

<h2 id="2-motivation">2. Motivation</h2>
<p>The resources in my first TLA+ post were extremely useful to me in understanding the TLA+/TLAPS syntax and semantics, but I found each source individually to be either too focused on specific examples or too general. This post will focus on interleaving examples of increasing complexity with the syntactical constructs in the language.</p>

<h2 id="3-getting-started">3. Getting Started</h2>
<h3 id="31-pre-requisites">3.1. Pre-Requisites</h3>
<ul>
  <li>TLA+ Toolbox is installed</li>
  <li>TLAPS is installed from <a href="https://tla.msr-inria.inria.fr/tlaps/content/Download/Binaries.html">binaries</a> or source - does not come with the Toolbox.</li>
</ul>

<h3 id="32-writing-proofs-in-the-tla-toolbox">3.2. Writing proofs in the TLA+ Toolbox</h3>
<ol>
  <li>Open a new specification or .tla file in the TLA+ Toolbox</li>
  <li>To prevent complaints from the PlusCal translator, keep an empty algorithm block in the file
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(* --algorithm proof
begin     
 skip;
end algorithm; *)
</code></pre></div>    </div>
  </li>
  <li>Alternatively, you can just add the theorems and proofs to an existing .tla file with standard TLA+/PlusCal code, these two systems co-exist and actually benefit from each other</li>
</ol>

<h2 id="4-the-hello-world-of-tlaps">4. The “Hello World” of TLAPS</h2>
<p>We’re going to force TLAPS to prove <code class="language-plaintext highlighter-rouge">1 + 1 = 2</code>, a real test of its abilities.</p>

<p>I find there are two <strong>classes</strong> of constructs when using TLAPS:</p>
<ol>
  <li>Theorems (Describing what needs to be proved)</li>
  <li>Proofs (Actually proving the active theorem)</li>
</ol>

<p>For TLAPS, the <strong>theorem</strong> would be written as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM OnePlusOne == 1 + 1 = 2
</code></pre></div></div>

<p>Here, the <strong>proof</strong> is <strong>obvious</strong>, since it follows from standard mathematical knowledge. So, the proof is written as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROOF OBVIOUS
</code></pre></div></div>

<p>Putting both theorem and proof together, we get:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM OnePlusOne == 1 + 1 = 2
PROOF 
  OBVIOUS
</code></pre></div></div>

<p>What caused some confusion during the process of learning TLAPS was that:</p>
<ul>
  <li>The word “PROOF” is optional</li>
  <li>Newlines are usually ignored</li>
</ul>

<p>So the proof above is equivalent to</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM OnePlusOne == 1 + 1 = 2  OBVIOUS
</code></pre></div></div>

<p>and</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM OnePlusOne == 
  1 + 1 = 2  
PROOF OBVIOUS
</code></pre></div></div>
<p>etc.</p>

<h3 id="41-running-these-proofs">4.1. Running these proofs</h3>
<p>In the TLA+ Toolbox: Use Ctrl+G Ctrl+G to prove a particular step of a theorem. The theorem and its associated steps turn green if the proof backends prove the theorem with the proof that’s been given.</p>

<p><img src="/assets/images/tlaps/oneplusone.png" alt="One Plus One Proof" /></p>

<h2 id="5-proving-transitive-more-than-relationships">5. Proving Transitive More-Than relationships</h2>
<p>We want to prove that if <code class="language-plaintext highlighter-rouge">X &gt; Y</code> and <code class="language-plaintext highlighter-rouge">Y &gt; Z</code> then <code class="language-plaintext highlighter-rouge">X &gt; Z + 1</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM Transitive == 
    ASSUME 
        NEW X \in Nat,
        NEW Y \in Nat,
        NEW Z \in Nat,
        X &gt; Y,
        Y &gt; Z
        PROVE X &gt; Z + 1
</code></pre></div></div>

<p>Here, we declare 3 new identifiers (X, Y, and Z) and declare their domain to be the Natural numbers (this requires an <code class="language-plaintext highlighter-rouge">EXTENDS Naturals</code> at the top of the .tla file)</p>

<p>Is this an obvious proof? In general, I try to use PROOF OBVIOUS every time just in case TLAPS can figure out the proof without any additional information. Here, the set of facts available to prove <code class="language-plaintext highlighter-rouge">X &gt; Z + 1</code> is <code class="language-plaintext highlighter-rouge">X \in Nat</code>, <code class="language-plaintext highlighter-rouge">Y \in Nat</code>, <code class="language-plaintext highlighter-rouge">Z \in Nat</code>, <code class="language-plaintext highlighter-rouge">X &gt; Y</code>, <code class="language-plaintext highlighter-rouge">Y &gt; Z</code>. So is TLAPS able to figure this out without any more information?</p>

<p><img src="/assets/images/tlaps/transitive.png" alt="Transitive Proof" /></p>

<p>Looks like it. Adding <code class="language-plaintext highlighter-rouge">PROOF OBVIOUS</code> to complete the proof is sufficient for TLAPS in this case. Let’s move on to a slightly more complex example.</p>

<h2 id="6-proving-some-square-root-stuff-using-operators-and-structured-proofs">6. Proving Some Square Root Stuff: Using Operators and Structured Proofs</h2>
<p>We want to prove that 4 and 9 both have a square root. Pretty easy, but we also want to define a generic operator to check if some variable has a square root. Our implementation will be:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HasSqrt(Y) == \E k \in 1..Y : k * k = Y
</code></pre></div></div>
<p>Or “Y has a square root if there exists a value k in the range 1 to Y such that k * k is Y”.</p>

<p>Let’s state our theorem:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM TheseHaveSqrt ==
    ASSUME
        NEW X \in {4, 9}
    PROVE HasSqrt(X)
</code></pre></div></div>
<p>So our proof here could be stated in one step, but it’s a good time to introduce the idea of <strong>structured proofs</strong>. These are multi-line proofs that can have step numbers and refer to each other. Each step is its own <strong>proof obligation</strong>, i.e. something that has to be proven as well, and proofs can be nested inside other proofs. This is too abstract to understand from a description, so here is our example proof for <code class="language-plaintext highlighter-rouge">THEOREM TheseHaveSqrt</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM TheseHaveSqrt ==
    ASSUME
        NEW X \in {4, 9}
    PROVE HasSqrt(X)
PROOF 
    &lt;1&gt;1 HasSqrt(4) /\ HasSqrt(9) BY DEF HasSqrt
    &lt;1&gt;2 QED BY &lt;1&gt;1
</code></pre></div></div>

<p>So here, there is one level of proof (level &lt;1&gt;) and 2 steps within the level. Each step in the proof is either <strong>a step that requires its own proof</strong>, or a <strong>step that requires no proof</strong>. Here, <strong>both steps require their own proof</strong>.</p>

<p>Focusing in on step &lt;1&gt;1:</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;1 HasSqrt(4) /\ HasSqrt(9) BY DEF HasSqrt</code></p>

<p>We are asserting that two facts are true: <code class="language-plaintext highlighter-rouge">HasSqrt(4)</code> AND (/\) <code class="language-plaintext highlighter-rouge">HasSqrt(9)</code>. However, if we were to just say:</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;1 HasSqrt(4) /\ HasSqrt(9) OBVIOUS</code></p>

<p>TLAPS would highlight the step in red.</p>

<p><img src="/assets/images/tlaps/hassqrt-wrong.png" alt="HasSqrt Wrong" /></p>

<p>TLAPS does not <strong>expand definitions by default</strong>. In this case, the back-end provers will see the opaque symbol HasSqrt and have no idea what to do with it. If we check the <strong>Interesting Obligations</strong> window in the TLA+ Toolbox after trying and failing to prove this step, we see this information:</p>

<p><img src="/assets/images/tlaps/io.png" alt="HasSqrt Wrong" /></p>

<p>What this means is that our three backend provers (zenon, Isabelle, and smt) failed to prove our highlighted obligation (<code class="language-plaintext highlighter-rouge">&lt;1&gt;1 HasSqrt(4) /\ HasSqrt(9) OBVIOUS</code>). The information sent to the provers was:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(* SMT failed with status = unknown *)
ASSUME NEW CONSTANT X \in {4, 9}
PROVE  HasSqrt(4) /\ HasSqrt(9)
</code></pre></div></div>

<p>This is an extremely important tool. We can now tell that the backend provers see the operator <code class="language-plaintext highlighter-rouge">HasSqrt</code> but not the definition behind it. If we were to correct our &lt;1&gt;1 step to:</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;1 HasSqrt(4) /\ HasSqrt(9) BY DEF HasSqrt</code></p>

<p>We would be asking TLAPS to use the expanded definition of HasSqrt, and the information sent to our provers would essentially be:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ASSUME NEW CONSTANT X \in {4, 9}
PROVE (\E k \in 1..4 : k * k = 4) /\ (\E k_1 \in 1..9 : k * k = 9)
</code></pre></div></div>

<p>We wouldn’t see this: the <strong>Interesting Obligations</strong> window doesn’t show up when something has been successfully proven. This does illustrate the mechanism used by TLAPS to keep the number of facts low: only those that are specifically said to be needed are used for a step. Let’s look at our theorem and proof again:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM TheseHaveSqrt ==
    ASSUME
        NEW X \in {4, 9}
    PROVE HasSqrt(X)
PROOF 
    &lt;1&gt;1 HasSqrt(4) /\ HasSqrt(9) BY DEF HasSqrt
    &lt;1&gt;2 QED BY &lt;1&gt;1
</code></pre></div></div>

<p>Every structured proof ends with a QED step, which basically brings together all the proof steps in the rest of the structured proof with the goal of proving the current theorem. In our case, saying</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;1&gt;2 QED BY &lt;1&gt;1
</code></pre></div></div>
<p>Is saying that “We can prove the goal HasSqrt(X) for X in {4, 9} by adding the fact &lt;1&gt;1” into the proof. The QED step itself requires proof (therefore <code class="language-plaintext highlighter-rouge">QED OBVIOUS</code> is a possible step), in this case, we’re saying “Assuming &lt;1&gt;1 is true, we have proved the theorem”. Given the wrong facts to QED will cause this step to fail, for e.g. <code class="language-plaintext highlighter-rouge">QED BY X = 5</code> in this case. Once we correct everything, we get:</p>

<p><img src="/assets/images/tlaps/sqrt.png" alt="HasSqrt" /></p>

<h2 id="7-dividing-linear-combinations">7. Dividing Linear Combinations</h2>
<p>We want to prove an interesting theorem:</p>

<p>If:</p>
<ul>
  <li>A divides B without remainder, and</li>
  <li>A divides C without remainder</li>
</ul>

<p>Then:</p>
<ul>
  <li>A divides Bx + Cy (the “linear combination” of B and C) without remainder, where x and y are any two integers.</li>
</ul>

<p>First we need to define what it means for some value to divide another. We will define it as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Divides(n, d) == d &gt; 0 /\ \E k \in Nat : n = d * k
</code></pre></div></div>
<p>So for <code class="language-plaintext highlighter-rouge">d</code> to divide some <code class="language-plaintext highlighter-rouge">n</code>, <code class="language-plaintext highlighter-rouge">d</code> must be more than 0, and there must be a number <code class="language-plaintext highlighter-rouge">k</code> such that <code class="language-plaintext highlighter-rouge">d * k = n</code>. We could also define it as <code class="language-plaintext highlighter-rouge">n % d = 0</code>, but this seems harder to work with: we’d have to prove of <code class="language-plaintext highlighter-rouge">d &gt; 0</code> and so on.</p>

<p>Let’s state the theorem as specified:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM LinearCombinationDivides == 
    ASSUME
        NEW A \in Nat,
        NEW B \in Nat,
        NEW C \in Nat,
        Divides(B, A),
        Divides(C, A)
    PROVE
        \A x, y \in Nat : Divides((B * x) + (C * y), A)
</code></pre></div></div>

<p>This will require a longer structured proof. Simply put: <code class="language-plaintext highlighter-rouge">PROOF OBVIOUS</code> fails here, citing insufficient facts. One possible proof is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM LinearCombinationDivides == 
    ASSUME
        NEW A \in Nat,
        NEW B \in Nat,
        NEW C \in Nat,
        Divides(B, A),
        Divides(C, A)
    PROVE
        \A x, y \in Nat : Divides((B * x) + (C * y), A)
PROOF
    &lt;1&gt;1  PICK k \in Nat : B = A * k BY DEF Divides
    &lt;1&gt;2  PICK m \in Nat : C = A * m BY DEF Divides
    &lt;1&gt;3  TAKE x \in Nat 
    &lt;1&gt;4  (B * x) = (A * k) * x BY &lt;1&gt;1
    &lt;1&gt;5  TAKE y \in Nat
    &lt;1&gt;6  (C * y) = (A * m) * y BY &lt;1&gt;2
    &lt;1&gt;7  (B * x) + (C * y) = ((A * k) * x) + ((A * m) * y) BY &lt;1&gt;1, &lt;1&gt;2
    &lt;1&gt;8  ((A * k) * x) + ((A * m) * y) = A * (k * x + m * y) BY &lt;1&gt;7
    &lt;1&gt;9  (B * x) + (C * y) = A * (k * x + m * y) BY &lt;1&gt;7, &lt;1&gt;8
    &lt;1&gt;10 QED BY &lt;1&gt;9 DEF Divides
</code></pre></div></div>

<p>There are a lot of new concepts, and we will explore them step-wise.</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;1  PICK k \in Nat : B = A * k BY DEF Divides</code></p>

<p>Step &lt;1&gt;1 states that for the rest of the proof, we are going to <code class="language-plaintext highlighter-rouge">PICK</code> a value k such that B = A * k, and we state that the reason we can do this is due to the definition of Divides. This makes sense: we said Divides(B, A) which implies B = A * k. Why didn’t we say <code class="language-plaintext highlighter-rouge">&lt;1&gt;1  \E k \in Nat : B = A * k BY DEF Divides</code> instead (<code class="language-plaintext highlighter-rouge">\E</code> vs <code class="language-plaintext highlighter-rouge">PICK</code>)? <code class="language-plaintext highlighter-rouge">\E k...</code> makes the definition of <code class="language-plaintext highlighter-rouge">k</code> local to only that step, <code class="language-plaintext highlighter-rouge">PICK</code> makes <code class="language-plaintext highlighter-rouge">k</code> accessible to the rest of the proof when we include the step elsewhere. Maybe it can be thought of as a private variable (<code class="language-plaintext highlighter-rouge">\E k</code>) vs a public variable (<code class="language-plaintext highlighter-rouge">PICK k</code>).</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;2  PICK m \in Nat : C = A * m BY DEF Divides</code></p>

<p>Step &lt;1&gt;2 is pretty much identical to step 1, just that we are doing the same thing for C.</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;3  TAKE x \in Nat</code></p>

<p>Step &lt;1&gt;3 is the first <strong>proof-less structured proof step</strong> that we have encountered. This can be thought of as equivalent to “for all y in Nat…”. Note the difference here: <code class="language-plaintext highlighter-rouge">TAKE</code> is like for-all and doesn’t require proof, <code class="language-plaintext highlighter-rouge">PICK</code> is like there-exists and does require proof.</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;4  (B * x) = (A * k) * x BY &lt;1&gt;1</code></p>

<p>In Step &lt;1&gt;4, we say that by adding the step &lt;1&gt;1 (<code class="language-plaintext highlighter-rouge">PICK k \in Nat : B = A * k BY DEF Divides</code>) to our list of facts for this step, we can prove that <code class="language-plaintext highlighter-rouge">(B * x) = (A * k) * x</code>, which makes sense since we are just expanding the definition of B. We do this for steps &lt;1&gt;5 and &lt;1&gt;6 for C as well.</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;7  (B * x) + (C * y) = ((A * k) * x) + ((A * m) * y) BY &lt;1&gt;1, &lt;1&gt;2</code></p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;8  ((A * k) * x) + ((A * m) * y) = A * (k * x + m * y) BY &lt;1&gt;7</code></p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;9  (B * x) + (C * y) = A * (k * x + m * y) BY &lt;1&gt;7, &lt;1&gt;8</code></p>

<p>The next three steps are basic arithmetic to achieve the form (B * x) + (C * y) = A * (some natural number). After this, only one last step is necessary to complete the proof:</p>

<p><code class="language-plaintext highlighter-rouge">&lt;1&gt;10 QED BY &lt;1&gt;9 DEF Divides</code></p>

<p>So using fact &lt;1&gt;9, we are able to finally prove our theorem.</p>

<p><img src="/assets/images/tlaps/linearcomb.png" alt="LinearComb" /></p>

<h3 id="71-not-following-my-own-advice">7.1. Not following my own advice</h3>
<p>While this example shows the methodical steps behind structured proofs and how to refer to previous steps to prove the current step, TLAPS was actually much smarter than I gave it credit for. I noticed that some steps of my proof were redundant when commenting them out, and then after I removed all of them…</p>

<p><img src="/assets/images/tlaps/linearcomb-obv.png" alt="LinearComb-Obvious" /></p>

<p>So actually, supplying the definition of Divides to TLAPS was sufficient for the backend provers to prove this theorem. My original definition of Divides had an extra <code class="language-plaintext highlighter-rouge">d &lt;= n</code> constraint that I removed while doing some other proofs, since it was an unnecessary constraint, and that was one of the things that made this proof obvious to TLAPS. The lesson to learn here: <strong>always check the <code class="language-plaintext highlighter-rouge">OBVIOUS</code> and <code class="language-plaintext highlighter-rouge">BY DEF &lt;..relevant definitions..&gt;</code> proofs first before trying to prove anything else</strong>.</p>

<h2 id="8-conclusions">8. Conclusions</h2>

<p>TLAPS is a really useful proof system that forces users to write rigorous mathematical proofs. After a while, it mostly feels like working together with the proof system to achieve some goal, as opposed to struggling to teach it some obvious fact. The main window into what the proof system is “thinking” is the <strong>Interesting Obligations</strong> window when it can’t prove something. I intend to combine my models in standard TLA+/PlusCal/TLC with TLAPS, since we are just scratching the surface of the proof system in this post. It’s also capable of checking temporal logic to a degree, and can handle induction over the natural numbers, among many other capabilities.</p>

<p>To continue learning TLAPS, the resources on TLAPS from <a href="https://sriramsami.com/tlaplus/">my initial post on Learning TLA+</a> were my main references. I’m not reproducing those references here, in order to limit any updates to the list to just that post. I’d recommend reading through those resources multiple times in different orders until more of TLAPS begins to make sense.</p>]]></content><author><name>sriram</name></author><category term="blog" /><summary type="html"><![CDATA[A beginner's experience with TLAPS (The TLA+ Proof System)]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sriramsami.com/assets/images/markdown.jpg" /><media:content medium="image" url="https://sriramsami.com/assets/images/markdown.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Learning TLA+ to write formal specifications</title><link href="https://sriramsami.com/tlaplus/" rel="alternate" type="text/html" title="Learning TLA+ to write formal specifications" /><published>2019-04-18T10:00:00+00:00</published><updated>2019-04-18T10:00:00+00:00</updated><id>https://sriramsami.com/tlaplus</id><content type="html" xml:base="https://sriramsami.com/tlaplus/"><![CDATA[<h2 id="resources">Resources</h2>
<p>These are the resources I used to learn about TLA+ and the system surrounding it. Examples from these are used throughout this post.</p>

<p><strong>TLA+</strong></p>
<ul>
  <li><a href="https://learntla.com/introduction/">Learn TLA+</a>.</li>
  <li><a href="https://lamport.azurewebsites.net/tla/book-02-08-08.pdf">Specifying Systems: Leslie Lamport</a></li>
</ul>

<p><strong>PlusCal</strong></p>
<ul>
  <li><a href="https://lamport.azurewebsites.net/tla/p-manual.pdf">PlusCal User Manual</a></li>
  <li><a href="https://lamport.azurewebsites.net/pubs/pluscal.pdf">Pluscal Language (Examples)</a></li>
</ul>

<p><strong>TLAPS</strong></p>
<ul>
  <li><a href="http://tla2014.loria.fr/slides/kriener.pdf">A Tutorial Introduction to TLAPS</a></li>
  <li><a href="http://lamport.azurewebsites.net/tla/tla2-guide.pdf">TLA+ and TLAPS syntax</a></li>
  <li><a href="https://lamport.azurewebsites.net/pubs/proof.pdf">How to Write a 21st Century Proof</a></li>
  <li><a href="http://lamport.azurewebsites.net/tla/hyperbook.html">TLA+ Hyperbook, check the Proof track</a></li>
</ul>

<h2 id="tla-pluscal-tlc-tla-toolbox-and-tlaps">TLA+, PlusCal, TLC, TLA+ Toolbox, and TLAPS,</h2>
<p><strong>TLA+</strong> is a high-level language used to specify and model systems. It resembles writing a description of something in more precise mathematics, along with temporal logic operators to specify theorems that change with time (eventually, always…), and many other language constructs to make writing specifications more precise and straightforward.</p>

<p>Specification of a 1-bit clock (The canonical example):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VARIABLE clock
Init == clock \in {0, 1}
Tick == IF clock = 0 THEN clock' = 1 ELSE clock' = 0

Spec == Init /\ [][Tick]_&lt;&lt;clock&gt;&gt;
</code></pre></div></div>

<p>To be read as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Declare a variable called clock
In the initial state, clock is set to 0 or 1
During the tick, the clock value goes 0 -&gt; 1 or 1 -&gt; 0

The overall specification: The initial state must be satisfied, 
                           and (/\) always ([]), we must have a Tick 
                           or a step where nothing changes.
</code></pre></div></div>

<p><strong>PlusCal</strong> is a more familiar language that <strong>transpiles to TLA+</strong>. It is a convenience that’s used for writing algorithms in TLA+ in a low-effort manner. For the same example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- fair algorithm OneBitClock {
  variable clock \in {0, 1};
  {
    while (TRUE) {
      if (clock = 0)
        clock := 1
      else 
        clock := 0    
    }
  }
}
</code></pre></div></div>

<p>The translation of this to TLA+ is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VARIABLE clock

vars == &lt;&lt; clock &gt;&gt;

Init == (* Global variables *)
        /\ clock \in {0, 1}

Next == IF clock = 0
           THEN /\ clock' = 1
           ELSE /\ clock' = 0

Spec == /\ Init /\ [][Next]_vars
        /\ WF_vars(Next)
</code></pre></div></div>

<p><strong>TLC</strong> is the actual model checker used to check TLA+ formulae (like Spec). It checks all possible states using a breadth-first search (to generate the shortest possible error trace) to confirm if the stated invariants hold. It can also be parallelized and even distributed across many nodes.</p>

<p>The <strong>TLA+ Toolbox</strong> is the standard development environment for working with PlusCal / TLA+. I am not aware of any standard setup that works better than the Toolbox, but that would be a good step forward.</p>

<p><strong>TLAPS (TLA+ Proof System)</strong> is a system for constructing and checking proofs. This is different from the TLA+/PlusCal/TLC system, because those involve searching a finite set of states one by one. Here, we can prove in the general case from claims and assertions that something is true, similar to a precise mathematical proof of a theorem.</p>

<p>This needs to be instal1led separated from the TLA+ Toolbox. Try <a href="https://tla.msr-inria.inria.fr/tlaps/content/Download/Binaries.html">https://tla.msr-inria.inria.fr/tlaps/content/Download/Binaries.html</a></p>

<p>I could not find an example of an extremely simple proof, so here is the simplest one I could write:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM OnePlusOne == 1 + 1 = 2
    OBVIOUS
</code></pre></div></div>

<p>This step is obvious, and TLAPS agrees.</p>

<p>Below is a proof that for three sequences A, B and C:</p>

<p><code class="language-plaintext highlighter-rouge">C ◦ A = C ◦ B =&gt;  A = B</code></p>

<p>where <code class="language-plaintext highlighter-rouge">◦</code> is the concatenation operator.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>THEOREM ConcatLeftCancel :=
    ASSUME
        NEW S,
        NEW A ∈ Seq(S),
        NEW B ∈ Seq(S),
        NEW C ∈ Seq(S),
        C ◦ A = C ◦ B
    PROVE
    A = B
PROOF
1.1: Len(A) = Len(B) OBVIOUS C ◦ A = C ◦ B
1.2: A ∈ [1 . . Len(A) → S] OBVIOUS A ∈ Seq(S)
1.3: B ∈ [1 . . Len(A) → S] BY 1.1
1.4: ASSUME NEW i ∈ 1 . . Len(A) PROVE A[i] = B[i]
    2.1: A[i] = (C ◦ A)[i + Len(C)] OBVIOUS (defn of C ◦ A)
    2.2: B[i] = (C ◦ B)[i + Len(C)] BY 1.1 (defn of C ◦ B)
    2: QED BY 2.1, 2.2
1: QED BY 1.2, 1.3, 1.4
</code></pre></div></div>]]></content><author><name>sriram</name></author><category term="blog" /><summary type="html"><![CDATA[Learning TLA+ to write formal specifications]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sriramsami.com/assets/images/markdown.jpg" /><media:content medium="image" url="https://sriramsami.com/assets/images/markdown.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>