averylaird.com
http://averylaird.com/
Pico Now Public<p>In my <a href="http://www.averylaird.com/programming/piece-table/2018/05/10/insertions-piece-table/">post</a> last week, I mentioned having a test “toy” version. It is dubbed “pico,” and is now hosted on a <a href="https://github.com/avery-laird/pico">public github repo</a> for anyone to look at, tinker with, or improve.</p>
<p>Pico is in the “barely works” phase, and only supports insertions for now. I will be working on this off and on over the next few weeks/months. Check it out if you’d like to contribute, or just take a look at the progress.</p>
Sat, 26 May 2018 00:00:00 +0000
http://averylaird.com/programming/piece-table/pico/2018/05/26/publish-pico/
http://averylaird.com/programming/piece-table/pico/2018/05/26/publish-pico/Piece Tables, Splay Trees, and "Trables" (Oh My!)<p>This was a lot harder that I thought it would be (and if you’re not sure what “it” is, check out my <a href="http://www.averylaird.com/programming/the%20text%20editor/2017/09/30/the-piece-table/">last post</a>). First, I tried using a red-black tree to store the pieces, similar to the <a href="http://e98cuenc.free.fr/wordprocessor/piecetable.html">method proposed</a> by Joaquin Cuenca Abela. He has already written a very impressive C++ implementation. The caveat with red-black trees is that they have a lot of cases to deal with, and (in most situations) perform only slightly better than some alternatives. And, as it was once wisely said:</p>
<blockquote>
<p>
“...premature optimization is the root of all evil.”
</p>
<footer><cite title="Donald Knuth">Donald Knuth</cite></footer>
</blockquote>
<p>When thinking about balanced binary trees, there are always three main structures to consider: AVL trees, splay trees, and normal (unbalanced) BST trees. It may seem like a contradiction to include unbalanced BSTs in a list of balanced trees; however, UBSTs can remained balanced as long as the keys being inserted are sufficiently unsorted. A text editor doesn’t usually meet this requirement.</p>
<p>AVL Trees usually perform very well, almost as well as red-black trees. If you’re unsure about the nature of the data being stored in the tree, the AVL tree works nicely. We can work a bit smarter though – there’s a simpler and faster solution to consider: Splay trees. This structure redistributes the nodes of a tree similar to AVL trees. The main difference is that we do not aim to balance the tree at all – we keep shifting around (“splaying”) nodes until the most recently inserted node is the root of the tree. And as I’ll explain below, we can “encode” the positions of pieces in the structure of the tree, eliminating the need to keep track of indices.</p>
<p>At first glance, it might seem that splay trees should not perform well at all, perhaps even worse than the UBST, because it is not particularly balanced. Surprisingly, the amortized time complexity of operations on a splay tree is <script type="math/tex">O(logN)</script>. This is because splay trees rely on the assumption that recently accessed data is the most likely to be required soon in the future. This is generally true, but especially relevant in the case of text editors. Most of the time, we are working on only a small section of the document.</p>
<p>Parts of Atom were <a href="http://blog.atom.io/2017/10/12/atoms-new-buffer-implementation.html">recently rewritten</a> to use a splay tree, with apparently pretty good results. I know that splay trees will perform very well with the common use case – I’m unsure how they will fare with large buffers and multiple cursors/concurrent editors. There’s only one way to find out.</p>
<blockquote>
<p>
“One good test is worth a thousand expert opinions.”
</p>
<footer><cite title="Wernher Von Braun">Wernher Von Braun</cite></footer>
</blockquote>
<h2 id="operations">Operations</h2>
<p>When designing the buffer interface, two basic functions must be considered:</p>
<script type="math/tex; mode=display">\begin{align}
\mathrm{insert}(index, string) \tag{1}\label{1} \\
\mathrm{delete}(span) \tag{2}\label{2}
\end{align}</script>
<p>A <script type="math/tex">span</script> is an ordered set of two indices, <script type="math/tex">(i_s, i_e)</script> which represents all characters within that range of indices (inclusive). In this post, I will handle only the insertion operation, and address deletion in a later post.</p>
<p>The <script type="math/tex">index</script> is known to us through the cursor position, and the <script type="math/tex">string</script> is given by user input along with <script type="math/tex">span</script>. All other values must be handled internally. We are storing the information about edits in a piece table, abstractly; but actually, since we keep each piece in a tree, it’s more of a “piece tree” (or a “trable?”). There are some conditions that we must place on this tree, mainly that <em>an inorder traversal starting from the root of the tree results in a proper evaluation of the buffer</em>. Because a node is equivalent to a piece, and a tree is equivalent to a table, I will use these terms interchangeably (depending on what is most appropriate in the context).</p>
<p>Although we can properly evaluate the entire table by starting at the root, and any <em>section</em> of the buffer by traversing a child node and its subtrees, we do not know where the section belongs in the buffer. As I’ll explain, there’s no way to know a node’s position in the buffer without any information about the rest of the tree. This is because we will use a very clever suggestion from Joaquin Abela, which allows us to store nodes based on their <strong>relative index</strong> — their index relative to other pieces in the table.</p>
<p>To implement this method, there are two changes we need to make to the typical splay tree: how we insert nodes, and how we store data. The procedure for inserting nodes is affected by the key we choose to sort the data. Intuitively, this should be something related to the position of a piece within the document. At first, we might consider using the desired insertion index, <script type="math/tex">i_d</script>.</p>
<p>The issue with using <script type="math/tex">i_d</script> is that it must change for any insertion at index <script type="math/tex">i \leq i_d</script>. After such an insertion, we would have to update every node after <script type="math/tex">i</script>. In the case of several insertions at the beginning of an existing buffer, this cases a <script type="math/tex">O(n)</script> time complexity for each insertion — no better than an array.</p>
<p>Joaquin Abela suggests a <a href="http://e98cuenc.free.fr/wordprocessor/piecetable.html">solution to this problem</a>: store the offset information in the nodes themselves. He does this by storing subtree sizes, rather than indices:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Explicit Indexing Relative Indexing
n1 n1
index: 14 size_right: 1
length: 10 size_left: 14
/ \ => length: 10
n2 n3 / \
index: 0 index: 23 n2 n3
length: 14 length: 1 size_right: 0 size_right: 0
/ \ / \ size_left: 0 size_left: 0
A B C D length: 14 length: 1
/ \ / \
A B C D
</code></pre></div></div>
<p>It’s worth spending a bit of time talking about the example above, because there are two <strong>very important</strong> differences to consider. Firstly, note that <code class="highlighter-rouge">n2</code> has a index of <code class="highlighter-rouge">0</code> and a length of <code class="highlighter-rouge">14</code>, while <code class="highlighter-rouge">n1</code> picks up at index <code class="highlighter-rouge">14</code>. This is because size is 1-indexed (I do not consider strings of length <code class="highlighter-rouge">0</code> to have meaning) whereas index is 0-indexed. So, <code class="highlighter-rouge">n2</code> actually <em>spans</em> indices <code class="highlighter-rouge">0 -> 13</code>. I will also define an insertion at index <script type="math/tex">i</script> to have the following behaviour:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0123456789ABCDEFGH
This is a sentence
^ insert 'i' @ index D
This is a senitence
</code></pre></div></div>
<p>Such that the index <script type="math/tex">i</script> refers to the desired final position.</p>
<p>Secondly, and <strong>most importantly</strong>, note how the structure of the tree does not change in either case. This illustrates how the structure of the tree itself stores the correct order of the pieces relative to each other, and implies that any resources spent storing and updating the index is a waste. Unlike indices, this order is also constant even after splaying — once the node is inserted properly, everything works fine.</p>
<p>Finally, we need to consider how an inserted piece may require the “split” of an existing piece. This involves devising a test to detect when a split should occur, and performing the split such that all properties of a piece table and a splay tree are preserved (I will not give a particularly in-depth explanation of splay trees here, but <a href="https://en.wikipedia.org/wiki/Splay_tree">the Wikipedia page</a> is a good place to start).</p>
<h3 id="insertions">Insertions</h3>
<p>To make an insertion, we will still need to consider the desired index, but we can forget it afterwards. I will go through an example below. We will use the same example as above, assuming the existing buffer was inserted as a single piece.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(1) Original Tree (2) Insert 'i' @ index 13
size_right: 0 size_right: 0 Because:
size_left: 0 size_left: 0 offset <index 13 < offset+length
length: 18 --> length: 18 we must split!
text: 0x0 text: 0x0
(2.1) View of subtree during split
/
size_right: 0
size_left: 13
length: 1
text: 0x1
/
size_right: 0
size_left: 0
length: 13
text: 0x2
(2.2) Join split subtree with parent node
size_right: 0
size_left: 0 <-- must update size_left
length: 18 <-- must update length
text: 0x0 <-- must update text pointer
/
size_right: 0
size_left: 13
length: 1
text: 0x1
/
size_right: 0
size_left: 0
length: 13
text: 0x2
(2.3) Update parent node
size_right: 0
size_left: 19 <-- 13+1+5
length: 5 <-- 18-13
text: 0x3 <-- explained below
/
size_right: 0
size_left: 13
length: 1
text: 0x1
/
size_right: 0
size_left: 0
length: 13
text: 0x2
</code></pre></div></div>
<p>Of course, there are actually (up to) 3 different ways to perform such a split. I choose to form a linked list on whatever side the node is supposed to be inserted, because we know there aren’t any children there. The other thing to consider is how memory is managed throughout. If we examine just the memory addresses mentioned above, we might see something like this:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(1) 0x0: This is a sentence
(2.1) 0x0: This is a sentence
0x1: i
(2.2) 0x0: This is a sentence
0x1: i
0x2: This is a sen
(2.3) 0x0: This is a sentence <-- here we can free(0x0)
0x1: i
0x2: This is a sen
0x3: tence
</code></pre></div></div>
<p>This method can be done without copying if we also store a <code class="highlighter-rouge">start</code> value in the node. We still need to allocate memory for the newly inserted character, but we would never have to change a pointer to memory once it is allocated. We can just increment the <code class="highlighter-rouge">start</code> value to be whatever the length of the first node in the split is. For example, the following memory contents and pointer/<code class="highlighter-rouge">start</code> pairs:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x0 This is a sentence
0x1 i
0x0, start = 0 , length = 13
0x1, start = 1 , length = 1
0x0, start = 13, length = 5
</code></pre></div></div>
<p>The <code class="highlighter-rouge">start</code> value would be relative to the text pointer. We can get the section of text we want by reading from <code class="highlighter-rouge">text+start</code> by <code class="highlighter-rouge">sizeof(text_type)*length</code> bytes (the data we’re storing may or may not be a <code class="highlighter-rouge">char</code>).</p>
<p>Finally, if we do an inorder traversal of the example tree, we get:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This is a senitence
|-----------|||---|
piece1 | |
piece2----| |
piece3-------|
</code></pre></div></div>
<p>An insertion without a split would form a subtree of a single node, and perform the same join operation. However, only the <code class="highlighter-rouge">size_left</code> of the parent node (in this case) would be changed — the <code class="highlighter-rouge">length</code> and <code class="highlighter-rouge">start</code> values would not change. Actually, if we follow the process described above, a split will always join of the left side of a parent, because the desired insertion index is less than the span of that piece.</p>
<h2 id="code">Code</h2>
<p>First we will need a piece, which is just a collection of three values:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">Piece</span> <span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">start</span><span class="p">,</span> <span class="n">length</span><span class="p">;</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">text</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>
<p>I’m assuming we’re storing <code class="highlighter-rouge">char</code>s here, but we can replace this with a different type later if we have to. We will be storing these pieces in a tree structure:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">Tree</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Piece</span> <span class="o">*</span><span class="n">piece</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">Tree</span> <span class="o">*</span><span class="n">left</span><span class="p">,</span> <span class="o">*</span><span class="n">right</span><span class="p">,</span> <span class="o">*</span><span class="n">parent</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">size_left</span><span class="p">,</span> <span class="n">size_right</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>
<p>In terms of types, that’s about it. I won’t post a bunch of code here, it’s all available <a href="https://github.com/avery-laird/pico">on github</a>. However, I will talk about the functions involved in insertions, and how I handle that process.</p>
<p>First, we follow the typical BST insertion algorithm. However, before performing the insertion, we perform a split test. If a split must be performed, we do it, otherwise, just insert the node normally. We record the address of the newly inserted node, and pass it to the splay function.</p>
<p>The splay function takes the address of the new node, and splays it up the tree until it is the root of the tree. That’s it! Sounded pretty easy to me the first time I tried to write it. And, maybe if I had better coding chops, it would’ve been. However, after a while, I did manage to get a testable base implementation.</p>
<h2 id="testing">Testing</h2>
<p>It’s hard to do useful, comprehensive tests in this case, since there are so many situations which may occur. However, I did some <strong>very</strong> rudimentary testing and benchmarking of the <code class="highlighter-rouge">insert</code> operation, with interesting results.</p>
<p>First, I inserted 1,000,000 characters into a piece table, which is equivalent to a dense document with 30,000 pages,<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup> giving the following graph:</p>
<p><img src="/static/implement-piece-table/instantaneous-insert-time-1000000.png" alt="" /></p>
<p>This is certainly a strange looking graph indeed, and that is mostly related to the nature of a splay tree. My guess is that the straight lines represent operations close to each other, and therefore very fast. The much slower insertion times represent operations very far from previous ones, which then become fast due to the splay operation. This is not bad for a worst case, but not particularly impressive either. Things get interesting if we take a look at a different quantity.</p>
<p>Inserting characters randomly doesn’t give a fair representation of the common use case for most editors. To get a more balanced perspective, I also recorded the average time for each insertion into a table of a certain size. I kept a running total of insertion time, and after each insertion, I divided by the current table size. This reflects the average time for each insertion up to that point. I got the following result:</p>
<p><img src="/static/implement-piece-table/average-1000000.png" alt="" /></p>
<p>This suggests that, no matter the table size, it should always take about the same time to perform an insertion, and that is a result I’m happy with. As some simple, preliminary tests, there is not much that can be inferred — perhaps a comparison between other implementations (array, linked list, etc) would be interesting. My inner skeptic also feels that running times this quick do seem <em>a bit</em> too good to be true, and there’s always the possibility of a bug that I haven’t noticed. I plan to design some better tests to rule out this possibility.</p>
<p>You can check out all the code <a href="https://github.com/avery-laird/pico">on github</a>. My next rough steps are supporting deletions, undo, and then working on an API. After that, multiple cursors, users — and beyond!</p>
<blockquote class="aside aside-sm">
<p>This article is also <a href="http://getcolorings.com/ru-piece-table">available in Russian</a> thanks to <a href="http://getcolorings.com/">Stanislav</a></p>
</blockquote>
<div class="footnotes">
<ol>
<li id="fn:1">
<p>I’m using Joaqin’s estimate here <a href="#fnref:1" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>
Thu, 10 May 2018 00:00:00 +0000
http://averylaird.com/programming/piece-table/2018/05/10/insertions-piece-table/
http://averylaird.com/programming/piece-table/2018/05/10/insertions-piece-table/Text Editor: Data Structures<p>The first step in building my text editor is to implement the core API. If you’re wondering why I want to do this, the original article is <a href="/programming/2017/09/22/the-text-editor/">here</a>.</p>
<p>I researched several data types, and I tried to be <em>language agnostic</em>. I wanted my decision to not be influenced by any particular language, and first see if there was a “best way” out there, solely based on operations. Of course, a “best way” rarely exists. However, in the case of text manipulation and storage, there are some clear “worst ways” and “better ways.”</p>
<h2 id="the-worst-way">The Worst Way</h2>
<p>The worst way to store and manipulate text is to use an array. Firstly, the entire file must be loaded into the array first, which raises issues with time and memory. Even worse still, every insertion and deletion requires each element in the array to be moved. There are more downsides, but already this method is clearly not practical. The array can be dismissed as an option rather quickly.</p>
<blockquote>
<p>By the way: this isn’t a challenge. Please don’t try to find worse ways to manipulate text.</p>
</blockquote>
<h2 id="a-good-way">A Good Way</h2>
<p>Another option is a binary tree structure called a <a href="https://en.wikipedia.org/wiki/Rope_(data_structure)">rope</a>. Skip to the <a href="#a-rope">next section</a> if binary trees aren’t your thing.</p>
<blockquote>
<p>If you are unfamiliar with binary trees, check out <a href="https://en.wikipedia.org/wiki/Binary_tree">this</a> as a starting point.</p>
</blockquote>
<p>Basically, the string is split into sections, and stored in the leaves. The weight of each leaf is the length of the string segment. The weight of each non-leaf node is the total length of the strings on its left subtree. For example, in the diagram<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup> below, node <script type="math/tex">E</script> is a leaf with a string segment <script type="math/tex">6</script> characters long. Therefore, it has a weight of <script type="math/tex">6</script>, as well as its parent node. However, node <script type="math/tex">B</script> has a weight of <script type="math/tex">9</script>, because nodes <script type="math/tex">E</script> and <script type="math/tex">F</script> together have a length of <script type="math/tex">9</script>.</p>
<p><img src="/static/rope-data-structure.png" alt="Source: Wikipedia, https://en.wikipedia.org/wiki/Rope_(data_structure)" style="display: block; margin-left: auto; margin-right: auto" /></p>
<p>This is a lot more efficient than an array. A rope has two main operations, <code class="highlighter-rouge">Split</code> and <code class="highlighter-rouge">Concat</code> (concatenation). <code class="highlighter-rouge">Split</code> splits one string into two strings at a given index, and <code class="highlighter-rouge">Concat</code> concatenates two strings into one. You can preform either an insert or delete with either of these two basic operations. To insert characters, you can split the string once (where you want to insert the content) and concatenate it twice (on either side of the inserted content). Deletions work similarly, by splitting the string twice, and concatenating them again without including the deleted content.</p>
<p>There’s a big downside. Using a rope is quite confusing and complicated. It’s difficult to explain even in an abstract manner. Working out the kinks in real life, while still making the code maintainable and readable, seems like a nightmare. What’s more, it still uses a lot of space. It didn’t seem like the best option yet, so I kept looking.</p>
<h2 id="a-better-way">A Better Way</h2>
<p>The <a href="https://en.wikipedia.org/wiki/Gap_buffer">Gap Buffer</a> is much simpler than the rope. The idea is this: operations on text are often localized. Most of the time, we’re not jumping all over the document. So, we create a “gap” between characters stored in an array. We keep track of how large the gap is by using pointers or array indices. Let’s examine two cases (using pointers):</p>
<dl>
<dt><strong>Insertion</strong></dt>
<dd>The gap is a certain size to begin with. We copy over the inserted content, and if it exceeds the size of the gap, we expand the gap.</dd>
</dl>
<dl>
<dt><strong>Deletion</strong></dt>
<dd>We shift the pointers of the gap to include the deleted content.</dd>
</dl>
<p>This is makes a lot of sense. We are plagued somewhat by the same issues as an array; under certain circumstances, if we move too far from the gap, every element in the array will have to be moved. However, it’s most likely that this is a rare occurrence for the average user. It is quite possible that the speed gained with most operations will outweigh the inefficiency of certain edge cases. In fact, the editor I’m writing this in – <a href="https://www.gnu.org/software/emacs/">Emacs</a> – uses a gap buffer, and it’s probably the fastest editor I’ve ever used. That fact alone is a pretty convincing argument to use a gap buffer. But if I’m starting from scratch, I want every aspect of the software to be the best option there is. And maybe there’s a better(est) way.</p>
<h2 id="the-betterest-way">The Better(est) Way</h2>
<p>A couple months ago, <a href="https://www.rosslaird.com/">my Dad</a> asked me for help with a problem. He was converting one of <a href="https://www.rosslaird.com/stones-throw/">his books</a> to markdown, and there was an issue with the footnotes. In markdown, footnotes do not automatically number themselves<sup id="fnref:2"><a href="#fn:2" class="footnote">2</a></sup>; they need to be labelled with either a number or some text, like this: <code class="highlighter-rouge">[^1]</code> or <code class="highlighter-rouge">[^footnote]</code>. The definition is the same, with a colon at the end.</p>
<p>He had used pandoc to mostly convert the document, but every footnote had the format <code class="highlighter-rouge">[^#]</code>. It was my job to make a script to replace every <code class="highlighter-rouge">#</code> with a number, starting from <script type="math/tex">1</script>.</p>
<p>Easy, right?</p>
<p>Well, that’s what I thought. I whipped up a regex, scanned through the document, and replaced all occurrences of the pattern with an increasing integer. And, it spat out garbage.</p>
<p>Why? Because I had made a really, really obvious mistake. The counter doesn’t always take up the same amount of space. The script kept overwriting content, and the offset grew larger the more footnotes were replaced. There’s a simple fix: keep track of how much more space you take up, and add that to your current position in the document. I made that one simple change, and everything worked perfectly. Without knowing it at the time, I had used a <a href="https://en.wikipedia.org/wiki/Piece_table">Piece Table</a>.</p>
<p>The Wikipedia page for the Piece Table is only 8 lines long (Yikes!) Even more concerning, it mentions Microsoft Word among the examples of editors that use piece tables. However, the piece table is a very promising structure. What’s more, at its conception Word was lightning fast with infinite redo/undo, as explained in <a href="https://web.archive.org/web/20160308183811/http://1017.songtrellisopml.com/whatsbeenwroughtusingpiecetables">this interesting article</a> by a Microsoft developer. If you have the time, it’s a cool read.</p>
<p>In 1998, Charles Crowley wrote <a href="https://www.cs.unm.edu/~crowley/papers/sds.pdf">a paper</a> investigating the pros and cons of various data structures used in text editors. His paper includes the structures we covered, like gap buffers, arrays, and ropes. He concluded that – from a basis of speed, simplicity, and structure – the piece table was the leading method. From my point of view, the piece table is also the most elegant solution.</p>
<p>We need two buffers: the original file (read-only), and a new file that will contain all of our added text (append-only). Lastly, we have a table that has three columns: file, start, length. This is which file to use (original or new), where the text segment starts in each file (pre-edit), and the length of the segment. Here’s an example:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Original File: A_large_span_of_text (underscores denote spaces)
New File: English_
File Start Length
-----------------------------------
Original 0 2
Original 8 8
New 0 8
Original 16 4
Sequence: A_span_of_English_text
</code></pre></div></div>
<p>Keep in mind that the <code class="highlighter-rouge">Start</code> index is <em>not relative</em> to previous edits. This is something that gets handled at runtime by adding the length of each previous edit (like what I did to fix my footnote script). Since the length is already included in the table, this is a trivial step.</p>
<p>I like this solution the most because:</p>
<ol>
<li>It’s <strong>elegant</strong>. Only the minimal amount of information is recorded.</li>
<li>It’s <strong>simple</strong>. We run through the table, and do the necessary operations. We don’t need to re-balance or traverse a binary tree.</li>
<li>It’s <strong>fast</strong>. This solution has the <em>potential</em> to be lightning fast. There are some additional optimizations to take into account, which are explained in detail <a href="http://www.catch22.net/tuts/piece-chains">here</a></li>
</ol>
<p>The piece table method certainly has its complications, and there are different variations in implementation. It is certainly a daunting task. I’m going to see how far I get. Another article will accompany my attempts to implement the piece table method.</p>
<h3 id="notes">Notes</h3>
<p><strong>P.S.</strong> If you’d like to read this article in Russian, <a href="http://howtorecover.me/">Vlad Brown</a> has a translated copy on <a href="http://howtorecover.me/text-editor-ru">his website.</a> Thanks Vlad!</p>
<div class="footnotes">
<ol>
<li id="fn:1">
<p>From <a href="https://en.wikipedia.org/wiki/Rope_(data_structure)">Wikipedia</a> <a href="#fnref:1" class="reversefootnote">↩</a></p>
</li>
<li id="fn:2">
<p>They should! Why aren’t they!?!?! Somebody needs to make that a markdown extension. Every time you want to insert an indexed footnote, you type <code class="highlighter-rouge">[^#]</code>. Then, it takes every footnote definition with that format and matches them up. If there’s a mismatch (like a named reference), you wonder why some of your footnotes are missing and fix it. I had to change all of my footnotes just to insert this footnote. It’s crazy. <a href="#fnref:2" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>
Sat, 30 Sep 2017 00:00:00 +0000
http://averylaird.com/programming/the%20text%20editor/2017/09/30/the-piece-table/
http://averylaird.com/programming/the%20text%20editor/2017/09/30/the-piece-table/A Text Editor<p>Lately I’ve been playing around with the idea of creating a text editor. Here’s a very quick and basic overview of what I think it should contain. The text editor has extensions, managers, projects, and a core API.</p>
<h2 id="the-editor-has-extensions">The Editor has Extensions</h2>
<p>For example, a user wants the editor (TE) to integrate with pandoc in some way. Or, they may want a spell checker. The user could write an extension, which behaves as additional behaviour of the API. It expands the types of calls that can be made to the API.</p>
<h2 id="the-editor-has-managers">The Editor has Managers</h2>
<p>The core API makes no assumptions about how the user would like to display their content. A manager must be written for every display method (the web, desktop, etc).</p>
<h2 id="the-editor-has-projects">The Editor has Projects</h2>
<p>Projects are introduced as an abstract type: a project may be anything. For example, in the case of a Jekyll project, there is a root directory (the website), some configuration information, certain commands that must be run to generate and manage the site, and so on. These can be defined in a Project, eg:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"> <span class="k">class</span> <span class="nc">Project</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="o">...</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">class</span> <span class="nc">Jekyll</span><span class="p">(</span><span class="n">Project</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">root_dir</span><span class="p">,</span> <span class="n">ruby_dir</span><span class="p">,</span> <span class="o">...</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">create_post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">...</span><span class="p">):</span>
<span class="n">title</span> <span class="o">=</span> <span class="s">"</span><span class="si">%</span><span class="s">s-</span><span class="si">%</span><span class="s">s-</span><span class="si">%</span><span class="s">s-</span><span class="si">%</span><span class="s">s.</span><span class="si">%</span><span class="s">s"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">year</span><span class="p">,</span> <span class="n">month</span><span class="p">,</span> <span class="n">day</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">file_type</span><span class="p">)</span>
<span class="o">...</span>
</code></pre></figure>
<p>A project can be less complicated. They can be an analogue to major modes in emacs; there may be a markdown project. In this case, the markdown extension might be loaded, and the pandoc extension could be available to convert from markdown to PDF.</p>
<h2 id="the-editor-has-an-api">The Editor has an API</h2>
<p>The API must supply certain basic functionality. A buffer can be created, edited, and saved. Furthermore, the buffer is available to extensions. Managers and Projects have no direct access to the buffer, they must query the API.</p>
<h2 id="how-is-the-editor-different-from-emacs">How is The Editor different from Emacs?</h2>
<p>I feel The Editor fills a role Emacs does not. Extensible with Python rather than Elisp, project management (multiple directories) with different major modes, more web-friendly API. Emacs of course has all of these things in theory, but not without a lot of customization that I wish to mitigate.</p>
<h2 id="how-is-the-editor-different-from-atom">How is The Editor different from Atom?</h2>
<p>I haven’t used atom in some time. However, I ran into similar problems of extensibility and nodejs, and project management.</p>
<h2 id="youre-wrong-my-favourite-editor-is-better">You’re Wrong; My Favourite Editor is Better</h2>
<p>There is nothing wrong with Atom or Sublime, and I love Emacs. All of those editors are hugely customizable, expertly built, and loaded with features. My main motivation is not to disrupt the editor habitat. I wanted to work on a project, and this seemed like a cool thing to try out. We’ll see how it goes. Wish me luck!</p>
Fri, 22 Sep 2017 00:00:00 +0000
http://averylaird.com/programming/2017/09/22/the-text-editor/
http://averylaird.com/programming/2017/09/22/the-text-editor/Describing Elliptical Orbits Programmatically<blockquote>
<p>This is a migrated post from my old website. If you see any odd formatting or other inconsistencies, please <a href="mailto:laird.avery@gmail.com">let me know</a>.</p>
</blockquote>
<p>Like most space nerds, I play <a href="https://kerbalspaceprogram.com/en/">Kerbal Space Program</a>. I also read <em><a href="https://en.wikipedia.org/wiki/The_Martian_%28Weir_novel%29">The Martian</a></em> a couple months ago; it was a terrific book, and I highly recommend it. One of my favourite aspects of the book, which is also its claim to fame, is its very impressive intention to be as scientifically accurate as possible. I’ve always been interested in how KSP simulates orbits, but <em>The Martian</em> also got me thinking about how actual orbital maneuvers are planned, and the math involved. That’s why I decided to see if I could use math, and Python, to describe the orbits of Earth and Mars.</p>
<h2 id="the-math">The Math</h2>
<p><a href="https://en.wikipedia.org/wiki/Kepler%27s_laws_of_planetary_motion#Position_as_a_function_of_time">This wikipedia article</a> very helpfully breaks down the math into four distinct steps:</p>
<ol>
<li>
<p>Compute the <strong>mean anomaly</strong>: <script type="math/tex">M = nt, nP = 2\pi</script></p>
<script type="math/tex; mode=display">M = \frac{2\pi t}{P}</script>
<p>Where <script type="math/tex">n</script> is the <em>mean motion</em>, <script type="math/tex">M</script> is the <em>mean anomaly</em>, and <script type="math/tex">t</script> is the time since <a href="https://en.wikipedia.org/wiki/Perihelion_and_aphelion">perihelion</a>. It’s interesting to note that in the consolidated formula, we get the relationship <script type="math/tex">\frac{t}{P}</script>. Therefore, since <script type="math/tex">2\pi</script> is constant, the term which defines <script type="math/tex">M</script> is simply the ratio between time since perihelion and the orbital period. This means that <strong>any</strong> unit of time can be used, as long as it is used for <strong>both parameters</strong>.</p>
</li>
<li>
<p>Compute the <strong>eccentric anomaly</strong> <script type="math/tex">E</script> by solving <a href="https://en.wikipedia.org/wiki/Kepler%27s_equation">Kepler’s equation</a>:</p>
<script type="math/tex; mode=display">M = E - \varepsilon\sin{E}</script>
<p>Where <script type="math/tex">\varepsilon</script> is the <a href="https://en.wikipedia.org/wiki/Orbital_eccentricity">eccentricity</a> of the orbit.</p>
</li>
<li>
<p>Compute the <strong>true anomaly</strong> <script type="math/tex">\theta</script> by the equation:</p>
<script type="math/tex; mode=display">(1 - \varepsilon)\tan^2{\frac{\theta}{2}} = (1 + \varepsilon)\tan^2{\frac{E}{2}}</script>
</li>
<li>
<p>Compute the <strong>heliocentric distance</strong>:</p>
<script type="math/tex; mode=display">r = a(1 - \varepsilon \cos{E})</script>
<p>Where <script type="math/tex">a</script> is the <a href="https://en.wikipedia.org/wiki/Semi-major_axis">semi-major axis</a>.</p>
</li>
</ol>
<p>Next, we’ll translate each step in to code.</p>
<blockquote>
<p>You can find all the code in one place at <a href="#code">the end of this article</a></p>
</blockquote>
<h3 id="step-one">Step One</h3>
<p>First, you’ll need to import the <code class="highlighter-rouge">math</code> module, and install <code class="highlighter-rouge">matplotlib</code>. I recommend using a package manager to install <code class="highlighter-rouge">matplotlib</code>, eg <code class="highlighter-rouge">sudo apt-get install python-matplotlib</code>. As the first thing in your file, you should end up with:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">math</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">pyplot</span> <span class="k">as</span> <span class="n">plt</span> </code></pre></figure>
<p>For step one, we need to <strong>compute the mean anomaly</strong>. We need time since perihelion and the orbital period, so we’ll make those parameters:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">step_one</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">p</span><span class="p">):</span>
<span class="s">"""
M = mean anomaly
M = 2pi * t
-------
P
"""</span>
<span class="k">return</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">math</span><span class="o">.</span><span class="n">pi</span> <span class="o">*</span> <span class="n">t</span><span class="p">)</span> <span class="o">/</span> <span class="n">p</span></code></pre></figure>
<h3 id="step-two">Step Two</h3>
<p>In step two, we’re solving for the <strong>eccentric anomaly</strong>, and we need the <em>mean anomaly</em> and <em>eccentricity</em> to do it.</p>
<p>Since Kepler’s equation is <a href="https://en.wikipedia.org/wiki/Transcendental_equation">transcendental</a>, and cannot be solved algebraically, the solution has to be found numerically.</p>
<p>We’ve got two lines, one horizontal, one slanted with slope = 1, and the point where they intersect is our solution. Or, you could move the <script type="math/tex">M</script> over to get <script type="math/tex">M - E + \varepsilon\sin{E} = 0</script> and say the root is your solution. I choose the former. It looks something like this:</p>
<p><img src="http://averylaird.com/static/elliptical/kepler_equation_graph.png" alt="Kepler's Equation" width="60%" style="display:block;margin:0 auto" /></p>
<p>We know that <script type="math/tex">M</script>, a constant value, will always be greater than <script type="math/tex">E - \varepsilon\sin{E}</script> when <script type="math/tex">E = 0</script>. In fact, the right side of the equation is basically a slanted <script type="math/tex">\sin{x}</script> graph. You could also think of it as a <script type="math/tex">y = x</script> graph being sinusoidally translated up and down, where the amplitude of translation is the eccentricity of the orbit.</p>
<p>Knowing that the right side of the equation will always start out as being less than the left, to find the intersection point we can just increase values of <script type="math/tex">E</script> (starting with <script type="math/tex">E = 0</script>) until the right side is equal to the left. <strong>However</strong>: we’ll be computing thousands of positions, and we want to be able to find the solution very quickly, but also with lots of precision. That’s why our algorithm should be as follows:</p>
<p><strong>Initial Conditions:</strong> <script type="math/tex">E = 0</script></p>
<ul>
<li>Is right side <script type="math/tex">% <![CDATA[
< %]]></script> left side?
<ul>
<li>Yes: increment <script type="math/tex">E</script> by <script type="math/tex">1</script></li>
<li>No: Is right side > left side?
<ul>
<li>Yes: decrement <script type="math/tex">E</script> by <script type="math/tex">0.00001</script></li>
<li>No: <strong>stop</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>This method is fast, but can also be arbitrarily precise by adding as many decimal points to the decrement step as need.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">step_two</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">e</span><span class="p">):</span>
<span class="s">"""
M = mean anomaly
E = eccentric anomaly
e = eccentricity
M = E - esinE
"""</span>
<span class="k">def</span> <span class="nf">M</span><span class="p">(</span><span class="n">E</span><span class="p">):</span> <span class="k">return</span> <span class="n">E</span> <span class="o">-</span> <span class="p">(</span><span class="n">e</span> <span class="o">*</span> <span class="n">math</span><span class="o">.</span><span class="n">sin</span><span class="p">(</span><span class="n">E</span><span class="p">))</span>
<span class="n">E</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">m</span> <span class="o">></span> <span class="n">M</span><span class="p">(</span><span class="n">E</span><span class="p">):</span>
<span class="n">E</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">M</span><span class="p">(</span><span class="n">E</span><span class="p">)</span> <span class="o">></span> <span class="n">m</span><span class="p">:</span>
<span class="n">E</span> <span class="o">-=</span> <span class="mf">0.00001</span>
<span class="k">return</span> <span class="n">E</span></code></pre></figure>
<h3 id="step-three">Step Three</h3>
<p>This is, by far, the hardest step to implement. It’s easy to get tripped up because it involves a <script type="math/tex">\tan</script> equation which has <strong>two solutions</strong> in a given cycle. This step will require a bit of high school trigonometry.</p>
<p>First of all, the right side of the equation, <script type="math/tex">(1 + \varepsilon)\tan^2{\frac{E}{2}}</script>, is a number – all of the variables are known – so let’s forget about it for now.</p>
<p>Normally, <script type="math/tex">\tan</script> has a period of <script type="math/tex">\pi</script>. When you square it, the period doesn’t change, but when you divide the variable <script type="math/tex">\theta</script> by 2, <script type="math/tex">\tan^2{\frac{\theta}{2}}</script>, then the period becomes <script type="math/tex">2\pi</script>:</p>
<p><img src="http://averylaird.com/static/elliptical/tan.png" alt="" style="display:block;margin:0 auto" /></p>
<p>Now the right side of our equation, which is a number (not a variable), will be a horizontal line which intersects with the <script type="math/tex">\tan^2{\frac{\theta}{2}}</script> <strong>twice</strong>, like this:</p>
<p><img src="http://averylaird.com/static/elliptical/tan_and_linear.png" alt="" style="display:block;margin:0 auto" /></p>
<p>Now the tricky part is that we need <strong>both</strong> of those solutions. One of the solutions is for when <script type="math/tex">0 \le t \le \frac{P}{2}</script>, and the other is for when <script type="math/tex">t \gt \frac{P}{2}</script>. In other words, without the second solution, you won’t be able to calculate true anomalies for times greater than half the orbital period. My solution is this:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">step_three</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">):</span>
<span class="s">"""
(1 - e)tan^2(theta/2) = (1 + e)tan^2(E/2)
e = eccentricity
theta = true anomaly
E = eccentric anomaly
"""</span>
<span class="k">def</span> <span class="nf">l</span><span class="p">(</span><span class="n">theta</span><span class="p">):</span> <span class="k">return</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">e</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">tan</span><span class="p">(</span><span class="n">theta</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span><span class="o">**</span><span class="mi">2</span>
<span class="n">r</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">+</span><span class="n">e</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">tan</span><span class="p">(</span><span class="n">E</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span><span class="o">**</span><span class="mi">2</span>
<span class="n">theta</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">l</span><span class="p">(</span><span class="n">theta</span><span class="p">)</span> <span class="o"><</span> <span class="n">r</span><span class="p">:</span>
<span class="n">theta</span> <span class="o">+=</span> <span class="mf">0.1</span>
<span class="k">while</span> <span class="n">r</span> <span class="o"><</span> <span class="n">l</span><span class="p">(</span><span class="n">theta</span><span class="p">):</span>
<span class="n">theta</span> <span class="o">-=</span> <span class="mf">0.00001</span>
<span class="k">return</span> <span class="p">[</span><span class="n">theta</span><span class="p">,</span> <span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">pi</span> <span class="o">-</span> <span class="n">theta</span><span class="p">)</span> <span class="o">+</span> <span class="n">theta</span><span class="p">]</span></code></pre></figure>
<p>As you can see, this step is very similar in principle to step two, with some key differences. First, I increment by 0.1 and not 1. This is because the angles are very small to begin with, and so incrementing by 0.1 many times is much faster than decrementing by 0.00001 many times. Second, this step returns a list and not a number. The list contains both solutions. In order to get the second solution, a bit of reasoning is need.</p>
<p>First, the <script type="math/tex">\tan^2{\frac{\theta}{2}}</script> graph is symmetrical in its cycle, meaning the values after <script type="math/tex">\pi</script> can be described as a reflection of the previous values (<script type="math/tex">0 \lt x \lt \pi</script>) over a line <script type="math/tex">x = \pi</script>. This means that the distance from the first solution <script type="math/tex">\theta_1</script> to <script type="math/tex">\pi</script>, which is <script type="math/tex">\pi - \theta_1</script>, is equal to the distance from the second solution <script type="math/tex">\theta_2</script> to <script type="math/tex">\pi</script>. Therefore, the distance <strong>between</strong> solutions is <script type="math/tex">2(\pi - \theta_1)</script>. The value of <script type="math/tex">\theta_2</script> can then be found by the equation:</p>
<script type="math/tex; mode=display">\theta_2 = 2(\pi - \theta_1) + \theta_1</script>
<p>The solution to use will be determined later, since <script type="math/tex">t</script> and <script type="math/tex">P</script> are required.</p>
<p>Okay, we made it through the toughest part! It’s all smooth sailing from here.</p>
<h3 id="step-four">Step Four</h3>
<p>This is where we calculate the heliocentric distance, or the planet’s distance from the sun. It is given by the equation <script type="math/tex">r = a(1 - \varepsilon\cos{E})</script>. We will need the semi-major axis, eccentricity, and eccentric anomaly as parameters:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">step_four</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">):</span>
<span class="s">"""
a = semi-major axis
e = eccentricity
E = eccentric anomaly
r = a(1 - ecosE)
"""</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="p">(</span><span class="n">e</span> <span class="o">*</span> <span class="n">math</span><span class="o">.</span><span class="n">cos</span><span class="p">(</span><span class="n">E</span><span class="p">)))</span></code></pre></figure>
<h3 id="tying-it-all-together">Tying it all together</h3>
<p>At this point, you have all the tools you need to predict the position of a planetary body as a function of time. However, I would recommend creating one last function that handles the order of calculations and determines which true anomaly solution to use. I just made a simple, barebones one:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">calculate</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">a</span><span class="p">):</span>
<span class="n">M</span> <span class="o">=</span> <span class="n">step_one</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>
<span class="n">E</span> <span class="o">=</span> <span class="n">step_two</span><span class="p">(</span><span class="n">M</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">list</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">modf</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="o">/</span> <span class="n">p</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> <span class="o">></span> <span class="mf">0.5</span><span class="p">:</span>
<span class="n">theta</span> <span class="o">=</span> <span class="n">step_three</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="nb">list</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">modf</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="o">/</span> <span class="n">p</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">:</span>
<span class="n">theta</span> <span class="o">=</span> <span class="n">step_three</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">step_four</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">theta</span><span class="p">,</span> <span class="n">r</span><span class="p">]</span> </code></pre></figure>
<p>Lastly, I like to plot things, and I want to plot orbits. At the beginning of this article I said I wanted to predict the orbits of Earth <em>and</em> Mars, so here’s some example code to accomplish this:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">e_theta</span><span class="p">,</span> <span class="n">e_r</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
<span class="n">m_theta</span><span class="p">,</span> <span class="n">m_r</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">365</span><span class="p">):</span>
<span class="n">e_coords</span> <span class="o">=</span> <span class="n">calculate</span><span class="p">(</span><span class="mf">0.0167</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="mi">365</span><span class="p">,</span> <span class="mf">1.496E8</span><span class="p">)</span>
<span class="n">e_theta</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e_coords</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">e_r</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e_coords</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">m_coords</span> <span class="o">=</span> <span class="n">calculate</span><span class="p">(</span><span class="mf">0.0935</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="mi">687</span><span class="p">,</span> <span class="mf">2.2792E8</span><span class="p">)</span>
<span class="n">m_theta</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">m_coords</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">m_r</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">m_coords</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">plt</span><span class="o">.</span><span class="n">polar</span><span class="p">(</span><span class="n">e_theta</span><span class="p">,</span> <span class="n">e_r</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">polar</span><span class="p">(</span><span class="n">m_theta</span><span class="p">,</span> <span class="n">m_r</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span></code></pre></figure>
<p>This gives me a pretty graph that looks like this:</p>
<p><img src="http://averylaird.com/static/elliptical/figure_1.png" alt="" style="display:block;margin:0 auto" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>With this code, I see many interesting avenues of research. I can plot more planets, graph their velocity, relative distances, relative angles, etc. Eventually I plan to refine this code for actual calendar dates, and use it to determine launch dates and windows.</p>
<h3 id="improvements">Improvements</h3>
<p>Some possible improvements could be made on the true anomaly function. Since orbits are quite often plotted one way, and sequentially, it could be optimized for this purpose by accepting the previously calculated angle as a starting point for calculating the next, instead of starting from 0 for all angles. This would be easily done with a parameter which defaults to 0.</p>
<p>The orbits could also be more precise by taking the gravitational influence of other bodies in to account. Additionally, when calculating relative distances more precision could be gained by taking into account the third dimension. This would require additional orbital elements, namely the orbital inclination.</p>
<h3 id="the-whole--">The whole <code class="highlighter-rouge">!#</code> <a name="code"></a></h3>
<p>Here’s all the code, for those who are lazy:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">math</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="k">def</span> <span class="nf">step_one</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">p</span><span class="p">):</span>
<span class="s">"""
M = mean anomaly
M = 2pi * t
-------
P
"""</span>
<span class="k">return</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">math</span><span class="o">.</span><span class="n">pi</span> <span class="o">*</span> <span class="n">t</span><span class="p">)</span> <span class="o">/</span> <span class="n">p</span>
<span class="k">def</span> <span class="nf">step_two</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">e</span><span class="p">):</span>
<span class="s">"""
M = mean anomaly
E = eccentric anomaly
e = eccentricity
M = E - esinE
"""</span>
<span class="k">def</span> <span class="nf">M</span><span class="p">(</span><span class="n">E</span><span class="p">):</span> <span class="k">return</span> <span class="n">E</span> <span class="o">-</span> <span class="p">(</span><span class="n">e</span> <span class="o">*</span> <span class="n">math</span><span class="o">.</span><span class="n">sin</span><span class="p">(</span><span class="n">E</span><span class="p">))</span>
<span class="n">E</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">m</span> <span class="o">></span> <span class="n">M</span><span class="p">(</span><span class="n">E</span><span class="p">):</span>
<span class="n">E</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">M</span><span class="p">(</span><span class="n">E</span><span class="p">)</span> <span class="o">></span> <span class="n">m</span><span class="p">:</span>
<span class="n">E</span> <span class="o">-=</span> <span class="mf">0.00001</span>
<span class="k">return</span> <span class="n">E</span>
<span class="k">def</span> <span class="nf">step_three</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">):</span>
<span class="s">"""
(1 - e)tan^2(theta/2) = (1 + e)tan^2(E/2)
e = eccentricity
theta = true anomaly
E = eccentric anomaly
"""</span>
<span class="k">def</span> <span class="nf">l</span><span class="p">(</span><span class="n">theta</span><span class="p">):</span> <span class="k">return</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">e</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">tan</span><span class="p">(</span><span class="n">theta</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span><span class="o">**</span><span class="mi">2</span>
<span class="n">r</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">+</span><span class="n">e</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">tan</span><span class="p">(</span><span class="n">E</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span><span class="o">**</span><span class="mi">2</span>
<span class="n">theta</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">l</span><span class="p">(</span><span class="n">theta</span><span class="p">)</span> <span class="o"><</span> <span class="n">r</span><span class="p">:</span>
<span class="n">theta</span> <span class="o">+=</span> <span class="mf">0.1</span>
<span class="k">while</span> <span class="n">r</span> <span class="o"><</span> <span class="n">l</span><span class="p">(</span><span class="n">theta</span><span class="p">):</span>
<span class="n">theta</span> <span class="o">-=</span> <span class="mf">0.00001</span>
<span class="k">return</span> <span class="p">[</span><span class="n">theta</span><span class="p">,</span> <span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">pi</span> <span class="o">-</span> <span class="n">theta</span><span class="p">)</span> <span class="o">+</span> <span class="n">theta</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">step_four</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">):</span>
<span class="s">"""
a = semi-major axis
e = eccentricity
E = eccentric anomaly
r = a(1 - ecosE)
"""</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="p">(</span><span class="n">e</span> <span class="o">*</span> <span class="n">math</span><span class="o">.</span><span class="n">cos</span><span class="p">(</span><span class="n">E</span><span class="p">)))</span>
<span class="k">def</span> <span class="nf">calculate</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">a</span><span class="p">):</span>
<span class="n">M</span> <span class="o">=</span> <span class="n">step_one</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>
<span class="n">E</span> <span class="o">=</span> <span class="n">step_two</span><span class="p">(</span><span class="n">M</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">list</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">modf</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="o">/</span> <span class="n">p</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> <span class="o">></span> <span class="mf">0.5</span><span class="p">:</span>
<span class="n">theta</span> <span class="o">=</span> <span class="n">step_three</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="nb">list</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">modf</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="o">/</span> <span class="n">p</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">:</span>
<span class="n">theta</span> <span class="o">=</span> <span class="n">step_three</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">step_four</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">E</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">theta</span><span class="p">,</span> <span class="n">r</span><span class="p">]</span>
<span class="n">e_theta</span><span class="p">,</span> <span class="n">e_r</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
<span class="n">m_theta</span><span class="p">,</span> <span class="n">m_r</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">365</span><span class="p">):</span>
<span class="n">e_coords</span> <span class="o">=</span> <span class="n">calculate</span><span class="p">(</span><span class="mf">0.0167</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="mi">365</span><span class="p">,</span> <span class="mf">1.496E8</span><span class="p">)</span>
<span class="n">e_theta</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e_coords</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">e_r</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e_coords</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">m_coords</span> <span class="o">=</span> <span class="n">calculate</span><span class="p">(</span><span class="mf">0.0935</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="mi">687</span><span class="p">,</span> <span class="mf">2.2792E8</span><span class="p">)</span>
<span class="n">m_theta</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">m_coords</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">m_r</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">m_coords</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">plt</span><span class="o">.</span><span class="n">polar</span><span class="p">(</span><span class="n">e_theta</span><span class="p">,</span> <span class="n">e_r</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">polar</span><span class="p">(</span><span class="n">m_theta</span><span class="p">,</span> <span class="n">m_r</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span></code></pre></figure>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>If you made it all the way to the end, I’m very impressed. Thanks for reading!</p>
<p>I try to be as accurate as possible, but if you see any mistakes or have any questions, comment below or feel free to <a href="mailto:laird.avery@gmail.com">email me</a>.</p>
Fri, 19 Jun 2015 19:37:03 +0000
http://averylaird.com/2015/06/19/describing-elliptical-orbits-programmatically/
http://averylaird.com/2015/06/19/describing-elliptical-orbits-programmatically/