<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Elevator Algorithm on ErrorZap</title><link>https://errorzap.com/tags/elevator-algorithm/</link><description>Recent content in Elevator Algorithm on ErrorZap</description><image><title>ErrorZap</title><url>https://errorzap.com/og.png</url><link>https://errorzap.com/og.png</link></image><generator>Hugo</generator><language>en-US</language><copyright>ErrorZap</copyright><lastBuildDate>Sat, 26 Sep 2026 17:04:00 -0600</lastBuildDate><atom:link href="https://errorzap.com/tags/elevator-algorithm/index.xml" rel="self" type="application/rss+xml"/><item><title>The Dispatch Game</title><link>https://errorzap.com/elevators/dispatch/</link><pubDate>Sat, 26 Sep 2026 17:04:00 -0600</pubDate><guid>https://errorzap.com/elevators/dispatch/</guid><description>Every elevator bank runs a scheduler. How collective control works, why destination dispatch took the buttons out of the car, what the big makers say their algorithms do, and a live simulator that races the two approaches.</description><content:encoded><![CDATA[<div class="ez-dossier-lede"><span>ELEVATORS · FLOOR 04 · DISPATCH</span><p>Pressing "up" doesn't call <em>an</em> elevator. It files a request with a scheduler that's juggling every car in the bank and guessing what everyone else is about to do.</p></div>
<h2 id="the-short-version">The short version</h2>
<ul>
<li>The classic rule is <strong>collective control</strong>: a car answers every call in its direction of travel, then turns around. If you&rsquo;ve studied disk scheduling, it&rsquo;s basically the SCAN algorithm.</li>
<li>Banks of cars add a <strong>group supervisor</strong> that assigns each hall call to the car with the best estimated arrival time, and switches strategy for morning rush, lunch, and evening.</li>
<li><strong>Destination dispatch</strong> asks for your floor <em>in the lobby</em>, then groups people going to the same floors into the same car. Schindler invented the first practical one, the Miconic 10, in 1990 and introduced it in 1992.</li>
<li>Modern makers advertise fuzzy logic, neural-network traffic prediction, genetic algorithms, and machine-learning tuning. Some of that is well documented, and some of it is brochure language.</li>
<li>Below, you can race the two approaches yourself.</li>
</ul>
<h2 id="classic-up-down-and-hope">Classic: up, down, and hope</h2>
<p>With plain up/down buttons, the system knows <em>that</em> you want to go up, but not
<em>where</em>. So a car stops for you, everyone piles in, and then everyone presses a
different floor. In a morning rush, a car leaving the lobby with 12 people can
easily end up stopping at 8 or 9 floors, and its round trip gets long. The next
person waiting in the lobby pays for every one of those stops.</p>
<h2 id="destination-dispatch-tell-it-where-first">Destination dispatch: tell it where first</h2>
<p>Destination dispatch moves the floor buttons into the lobby. You type 14 on a
kiosk (or badge in, or tap your phone), and the screen tells you which car to
take. The computer now knows every rider&rsquo;s destination <em>before</em> they board, so it
can send the three people going to 14 and the two going to 15 together, and put
the people going to 6 in a different car.</p>
<p>Each car makes fewer stops, so it gets back to the lobby sooner, and waits get
shorter. That&rsquo;s why many of these buildings have no floor buttons inside the
car at all. It&rsquo;s also why you shouldn&rsquo;t follow your friend into Car B if the
kiosk told you Car D. Car B doesn&rsquo;t know you exist.</p>
<figure class="el-fig el-sim" data-el-sim>
  <figcaption><b>FIG. 4 · LIVE</b>Two identical towers, same four cars, same people arriving at the same moments. The only difference is the software deciding who rides where.</figcaption>
  <div class="el-sim-controls">
    <label>Morning rush <input type="range" min="10" max="45" value="28" data-rate> <output data-rate-out>28</output> people/min</label>
    <button type="button" data-pause>Pause</button>
    <button type="button" data-reset>Reset</button>
    <span class="el-sim-clock" data-clock>07:58:00</span>
  </div>
  <div class="el-sim-towers">
    <div class="el-sim-tower" data-tower="0">
      <h4>Classic <small>up/down buttons, pick your floor inside</small></h4>
      <div class="el-sim-shafts"></div>
      <div class="el-sim-lobby"><span>Lobby line</span><b data-q>0</b></div>
      <div class="el-sim-stats">
        <div><span>Avg wait</span><b data-wait>–</b></div>
        <div><span>Lobby → desk</span><b data-trip>–</b></div>
        <div><span>Stops per trip</span><b data-stops>–</b></div>
        <div><span>Delivered</span><b data-done>0</b></div>
      </div>
    </div>
    <div class="el-sim-tower" data-tower="1">
      <h4>Destination dispatch <small>type your floor in the lobby, get a car</small></h4>
      <div class="el-sim-shafts"></div>
      <div class="el-sim-lobby"><span>Lobby line</span><b data-q>0</b></div>
      <div class="el-sim-stats">
        <div><span>Avg wait</span><b data-wait>–</b></div>
        <div><span>Lobby → desk</span><b data-trip>–</b></div>
        <div><span>Stops per trip</span><b data-stops>–</b></div>
        <div><span>Delivered</span><b data-done>0</b></div>
      </div>
    </div>
  </div>
  <p class="el-sim-note">A toy model, not any company's algorithm: 16 floors, 4 cars of 12 people, 2 s per floor, 8 s per stop, and everyone starting in the lobby. The destination tower groups riders so each car makes at most 4 stops. Real group controllers also juggle lunch rushes, people going between floors, energy use, and hundreds of tuning knobs. Above about 22 people a minute, the classic tower's line starts growing and doesn't stop. Drag the slider down to 10 and you'll see the flip side: in light traffic, destination dispatch can make you wait a little <em>longer</em>, because it holds cars to group riders.</p>
</figure>
<script>
 
function elSimEngine(o) {
  const FLOORS = 16, CAP = 12, FLOOR_T = 2, STOP_T = 8, DWELL = 6, MAXSTOPS = 4;
  let rng, t, nextArr, towers;
  const mulberry = a => () => { a |= 0; a = a + 0x6D2B79F5 | 0; let x = Math.imul(a ^ a >>> 15, 1 | a); x = x + Math.imul(x ^ x >>> 7, 61 | x) ^ x; return ((x ^ x >>> 14) >>> 0) / 4294967296; };
  const mkCar = () => ({ pos: 0, state: 'lobby', timer: DWELL, riders: [], stops: [], next: [] });
  function reset() {
    rng = mulberry(o.seed || 42); t = 0; nextArr = 0;
    towers = [0, 1].map(i => ({ dd: i === 1, cars: Array.from({ length: o.cars || 4 }, mkCar), queue: [], waits: [], trips: [], stops: [], done: 0 }));
  }
  function etaLobby(c) {
    if (c.state === 'lobby') return 0;
    if (c.state === 'down') return c.pos * FLOOR_T;
    const top = c.stops.length ? c.stops[c.stops.length - 1] : c.pos;
    return (top - c.pos) * FLOOR_T + c.stops.length * STOP_T + top * FLOOR_T;
  }
  
  function assign(tw, p) {
    let best = null, bestCost = Infinity;
    for (const c of tw.cars) {
      if (c.next.length >= CAP) continue;
      const floors = new Set(c.next.map(g => g.dest));
      const has = floors.has(p.dest);
      if (!has && floors.size >= MAXSTOPS) continue;
      const cost = etaLobby(c) + (has ? 0 : STOP_T + floors.size * 3);
      if (cost < bestCost) { bestCost = cost; best = c; }
    }
    p.car = best;
    if (best) best.next.push(p);
  }
  function arrive() {
    const dest = 1 + Math.floor(rng() * (FLOORS - 1));
    towers.forEach(tw => { const p = { dest, t0: t }; tw.queue.push(p); if (tw.dd) assign(tw, p); });
  }
  function step(tw, dt) {
    if (tw.dd) tw.queue.forEach(p => { if (!p.car) assign(tw, p); });
    tw.cars.forEach((c, i) => {
      if (c.state === 'lobby') {
        const wasEmpty = !c.riders.length;
        if (tw.dd) {
          c.next.forEach(p => { if (!p.on) { p.on = true; c.riders.push(p); tw.waits.push(t - p.t0); tw.queue.splice(tw.queue.indexOf(p), 1); } });
        } else if (!tw.cars.some((o2, j) => j < i && o2.state === 'lobby' && o2.riders.length < CAP)) {
          while (tw.queue.length && c.riders.length < CAP) { const p = tw.queue.shift(); c.riders.push(p); tw.waits.push(t - p.t0); }
        }
        if (wasEmpty && c.riders.length) c.timer = DWELL;
        c.timer = Math.max(0, c.timer - dt);
        if (c.riders.length && (c.timer === 0 || c.riders.length >= CAP)) {
          c.stops = [...new Set(c.riders.map(r => r.dest))].sort((a, b) => a - b);
          c.next = []; tw.stops.push(c.stops.length); c.state = 'up';
        }
      } else if (c.state === 'up') {
        c.pos = Math.min(c.stops[0], c.pos + dt / FLOOR_T);
        if (c.pos === c.stops[0]) { c.state = 'doors'; c.timer = STOP_T; }
      } else if (c.state === 'doors') {
        c.timer -= dt;
        if (c.timer <= 0) {
          const f = c.stops.shift();
          c.riders = c.riders.filter(r => { if (r.dest !== f) return true; tw.trips.push(t - r.t0); tw.done++; return false; });
          c.state = c.stops.length ? 'up' : 'down';
        }
      } else {
        c.pos = Math.max(0, c.pos - dt / FLOOR_T);
        if (c.pos === 0) { c.state = 'lobby'; c.timer = DWELL; }
      }
    });
  }
  function advance(dt, perMin) {
    const rate = perMin / 60;
    while (dt > 0) {
      const h = Math.min(dt, 0.1); dt -= h; t += h;
      while (t >= nextArr) { arrive(); nextArr += -Math.log(1 - rng()) / rate; }
      towers.forEach(tw => step(tw, h));
    }
  }
  const avg = a => a.length ? a.slice(-200).reduce((x, y) => x + y, 0) / Math.min(a.length, 200) : null;
  reset();
  return { reset, advance, avg, FLOORS, CAP, get t() { return t; }, get towers() { return towers; } };
}
 
(() => {
  const SPEEDUP = 6;
  document.querySelectorAll('[data-el-sim]').forEach(root => {
    const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
    const rateIn = root.querySelector('[data-rate]'), rateOut = root.querySelector('[data-rate-out]');
    const sim = elSimEngine({ seed: 42 });
    const els = [...root.querySelectorAll('[data-tower]')];
    let carEls = [], paused = reduce, last = null;
    const fmt = s => s == null ? '–' : (s < 60 ? Math.round(s) + ' s' : Math.floor(s / 60) + 'm ' + String(Math.round(s % 60)).padStart(2, '0') + 's');
    function build() {
      carEls = els.map((el, ti) => {
        const shafts = el.querySelector('.el-sim-shafts'); shafts.innerHTML = '';
        return sim.towers[ti].cars.map((c, i) => {
          const s = document.createElement('div'); s.className = 'el-sim-shaft';
          const d = document.createElement('div'); d.className = 'el-sim-car';
          d.innerHTML = '<b>' + 'ABCD'[i] + '</b><i></i>';
          s.appendChild(d); shafts.appendChild(s); return d;
        });
      });
    }
    function draw() {
      sim.towers.forEach((tw, ti) => {
        tw.cars.forEach((c, i) => {
          const d = carEls[ti][i];
          d.style.bottom = (c.pos / (sim.FLOORS - 1) * 100) + '%';
          d.querySelector('i').textContent = c.riders.length || '';
          d.classList.toggle('open', c.state === 'lobby' || c.state === 'doors');
          d.classList.toggle('full', c.riders.length >= sim.CAP);
        });
        const q = els[ti];
        q.querySelector('[data-q]').textContent = tw.queue.length;
        q.querySelector('[data-wait]').textContent = fmt(sim.avg(tw.waits));
        q.querySelector('[data-trip]').textContent = fmt(sim.avg(tw.trips));
        const sp = sim.avg(tw.stops); q.querySelector('[data-stops]').textContent = sp == null ? '–' : sp.toFixed(1);
        q.querySelector('[data-done]').textContent = tw.done;
      });
      const clock = 7 * 3600 + 58 * 60 + Math.floor(sim.t);
      root.querySelector('[data-clock]').textContent = [clock / 3600, clock / 60 % 60, clock % 60].map(v => String(Math.floor(v)).padStart(2, '0')).join(':');
    }
    function tick(now) {
      if (last == null) last = now;
      const dt = Math.min(0.25, (now - last) / 1000) * SPEEDUP; last = now;
      if (!paused) { sim.advance(dt, +rateIn.value); draw(); }
      requestAnimationFrame(tick);
    }
    rateIn.addEventListener('input', () => { rateOut.textContent = rateIn.value; });
    root.querySelector('[data-pause]').textContent = paused ? 'Play' : 'Pause';
    root.querySelector('[data-pause]').addEventListener('click', e => { paused = !paused; e.target.textContent = paused ? 'Play' : 'Pause'; });
    root.querySelector('[data-reset]').addEventListener('click', () => { sim.reset(); build(); draw(); });
    build(); draw();
    new IntersectionObserver((es, io) => es.forEach(e => { if (e.isIntersecting) { io.disconnect(); requestAnimationFrame(tick); } })).observe(root);
  });
})();
</script>

<h2 id="what-the-big-makers-say-theyre-doing">What the big makers say they&rsquo;re doing</h2>
<p>Group control algorithms are proprietary, so what we have is what the
manufacturers choose to publish. Treat the fancier claims as marketing unless
there&rsquo;s detail behind them.</p>
<ul>
<li><strong>Mitsubishi Electric ΣAI-2200C.</strong> The best-documented. The brochure
describes fuzzy logic and expert-system rules, neural-network traffic
prediction, a &ldquo;Dynamic Rule-set Optimizer&rdquo; that simulates competing strategies
every few minutes and picks the winner, &ldquo;psychological&rdquo; waiting-time scoring,
and an energy-saving mode that prefers the cheaper move (like a heavy car
going <em>down</em>) when several cars could arrive at about the same time.</li>
<li><strong>Otis Compass 360 / Compass Infinity.</strong> Destination management with
&ldquo;SmartGrouping.&rdquo; Otis says Compass Infinity uses machine learning to tune the
hundreds of dispatch parameters that would otherwise be set by hand and go
stale as a building&rsquo;s traffic changes.</li>
<li><strong>KONE Destination.</strong> KONE&rsquo;s brochure language lists traffic forecasting,
fuzzy logic, and genetic algorithms. That&rsquo;s a vendor claim. Nobody outside
KONE has published the internals.</li>
<li><strong>Schindler PORT.</strong> The descendant of the 1992 Miconic 10. It adds identity:
your badge can carry your default floor, access rights, and accessibility
needs, like extra door time.</li>
<li><strong>TK Elevator TWIN.</strong> Two cars in one shaft, so the scheduler also has to
keep them from ever meeting. More on Floor 8.</li>
</ul>

<figure class="el-yt">
  <button type="button" class="el-yt-play" data-yt="66X4nQPE4bQ" aria-label="Play video: Introducing Compass 360">
    <img src="https://i.ytimg.com/vi/66X4nQPE4bQ/hqdefault.jpg" alt="" loading="lazy" width="480" height="360">
    <span class="el-yt-btn" aria-hidden="true">▶</span>
  </button>
  <figcaption><b>Introducing Compass 360</b><span>Otis Elevator Co.</span><small>Otis&rsquo;s pitch for its destination dispatch system, with a look at the touchscreen side.</small>
    <a href="https://www.youtube.com/watch?v=66X4nQPE4bQ" rel="noopener" target="_blank">Watch on YouTube ↗</a></figcaption>
</figure>
<script>
document.addEventListener('click', e => {
  const b = e.target.closest('.el-yt-play'); if (!b) return;
  const f = document.createElement('iframe');
  f.src = 'https://www.youtube-nocookie.com/embed/' + b.dataset.yt + '?autoplay=1&rel=0';
  f.title = b.getAttribute('aria-label'); f.allow = 'autoplay; encrypted-media; picture-in-picture'; f.allowFullscreen = true;
  f.className = 'el-yt-frame'; b.replaceWith(f);
});
</script>

<h2 id="the-access-control-twist">The access-control twist</h2>
<p>Once the elevator knows who you are, it becomes part of building security.
Access control integrator AMAG describes two patterns. In one, the elevator asks
the badge system, in real time, &ldquo;may this person go to floor 12?&rdquo; In the other,
the cardholder database is copied into the elevator system, which decides
locally. Either way, your floor choice is now tied to your badge. That&rsquo;s handy
for tenants, and it&rsquo;s one more system for the building&rsquo;s IT people to think
about.</p>
<div class="el-callout teal"><b>Play it yourself</b><p><a href="https://play.elevatorsaga.com/">Elevator Saga</a> is a free browser game where you write the JavaScript that dispatches the elevators. Its API uses the same ideas real group controllers worry about: destination queues, load factor, and what to do with an idle car. It's harder than it looks.</p></div>
<h2 id="sources">Sources</h2>
<ol class="ez-sources">
<li><a href="https://en.wikipedia.org/wiki/Destination_dispatch">Wikipedia: Destination dispatch</a></li>
<li><a href="https://www.schindler.sa/en/elevators/destination-control/port-elevator.html">Schindler: Miconic 10 (1992) and PORT</a></li>
<li><a href="https://mitsubishielevator.com/uploads/files/pdf/elevators/2200c_Brochure.pdf">Mitsubishi Electric: ΣAI-2200C group control brochure (PDF)</a></li>
<li><a href="https://www.otis.com/en/us/products-services/products/compass-360">Otis: Compass 360</a></li>
<li><a href="https://www.kone.me/en/Images/brochure-kone-destination_tcm103-18639.pdf">KONE: Destination brochure (PDF)</a></li>
<li><a href="https://www.schindler.com/en/elevators/destination-control/port-elevator.html">Schindler: PORT destination control</a></li>
<li><a href="https://www.amag.com/wp-content/uploads/2023/12/AMAG-1520_WhitePapers_ElevatorDispatch_v1.pdf">AMAG: destination dispatch and access control white paper (PDF)</a></li>
<li><a href="https://play.elevatorsaga.com/documentation.html">Elevator Saga: documentation</a></li>
</ol>
]]></content:encoded></item></channel></rss>