
tag. We found the following text: "+s);var l=document.createElement("span");l.innerHTML=o.nodeValue,o.parentNode.insertBefore(l,o),o.parentNode.removeChild(o)}}}}catch(n){r.e(n)}finally{r.f()}}}catch(n){e.e(n)}finally{e.f()}})).observe(n,{childList:!0}),n}return d(t,n),m(t,null,[{key:"is",get:function(){return"d-article"}}])}(o(HTMLElement)),Z="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==e.g?e.g:"undefined"!=typeof self?self:{};function Q(n,t){return n(t={exports:{}},t.exports),t.exports}var J=Q((function(n,t){!function(n){function t(){this.months=["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"],this.notKey=[",","{","}"," ","="],this.pos=0,this.input="",this.entries=new Array,this.currentEntry="",this.setInput=function(n){this.input=n},this.getEntries=function(){return this.entries},this.isWhitespace=function(n){return" "==n||"\r"==n||"\t"==n||"\n"==n},this.match=function(n,t){if(null!=t&&null!=t||(t=!0),this.skipWhitespace(t),this.input.substring(this.pos,this.pos+n.length)!=n)throw"Token mismatch, expected "+n+", found "+this.input.substring(this.pos);this.pos+=n.length,this.skipWhitespace(t)},this.tryMatch=function(n,t){return null!=t&&null!=t||(t=!0),this.skipWhitespace(t),this.input.substring(this.pos,this.pos+n.length)==n},this.matchAt=function(){for(;this.input.length>this.pos&&"@"!=this.input[this.pos];)this.pos++;return"@"==this.input[this.pos]},this.skipWhitespace=function(n){for(;this.isWhitespace(this.input[this.pos]);)this.pos++;if("%"==this.input[this.pos]&&1==n){for(;"\n"!=this.input[this.pos];)this.pos++;this.skipWhitespace(n)}},this.value_braces=function(){var n=0;this.match("{",!1);for(var t=this.pos,e=!1;;){if(!e)if("}"==this.input[this.pos]){if(!(n>0)){var i=this.pos;return this.match("}",!1),this.input.substring(t,i)}n--}else if("{"==this.input[this.pos])n++;else if(this.pos>=this.input.length-1)throw"Unterminated value";e="\\"==this.input[this.pos]&&0==e,this.pos++}},this.value_comment=function(){for(var n="",t=0;!this.tryMatch("}",!1)||0!=t;){if(n+=this.input[this.pos],"{"==this.input[this.pos]&&t++,"}"==this.input[this.pos]&&t--,this.pos>=this.input.length-1)throw"Unterminated value:"+this.input.substring(start);this.pos++}return n},this.value_quotes=function(){this.match('"',!1);for(var n=this.pos,t=!1;;){if(!t){if('"'==this.input[this.pos]){var e=this.pos;return this.match('"',!1),this.input.substring(n,e)}if(this.pos>=this.input.length-1)throw"Unterminated value:"+this.input.substring(n)}t="\\"==this.input[this.pos]&&0==t,this.pos++}},this.single_value=function(){var n=this.pos;if(this.tryMatch("{"))return this.value_braces();if(this.tryMatch('"'))return this.value_quotes();var t=this.key();if(t.match("^[0-9]+$"))return t;if(this.months.indexOf(t.toLowerCase())>=0)return t.toLowerCase();throw"Value expected:"+this.input.substring(n)+" for key: "+t},this.value=function(){var n=[];for(n.push(this.single_value());this.tryMatch("#");)this.match("#"),n.push(this.single_value());return n.join("")},this.key=function(){for(var n=this.pos;;){if(this.pos>=this.input.length)throw"Runaway key";if(this.notKey.indexOf(this.input[this.pos])>=0)return this.input.substring(n,this.pos);this.pos++}},this.key_equals_value=function(){var n=this.key();if(this.tryMatch("="))return this.match("="),[n,this.value()];throw"... = value expected, equals sign missing:"+this.input.substring(this.pos)},this.key_value_list=function(){var n=this.key_equals_value();for(this.currentEntry.entryTags={},this.currentEntry.entryTags[n[0]]=n[1];this.tryMatch(",")&&(this.match(","),!this.tryMatch("}"));)n=this.key_equals_value(),this.currentEntry.entryTags[n[0]]=n[1]},this.entry_body=function(n){this.currentEntry={},this.currentEntry.citationKey=this.key(),this.currentEntry.entryType=n.substring(1),this.match(","),this.key_value_list(),this.entries.push(this.currentEntry)},this.directive=function(){return this.match("@"),"@"+this.key()},this.preamble=function(){this.currentEntry={},this.currentEntry.entryType="PREAMBLE",this.currentEntry.entry=this.value_comment(),this.entries.push(this.currentEntry)},this.comment=function(){this.currentEntry={},this.currentEntry.entryType="COMMENT",this.currentEntry.entry=this.value_comment(),this.entries.push(this.currentEntry)},this.entry=function(n){this.entry_body(n)},this.bibtex=function(){for(;this.matchAt();){var n=this.directive();this.match("{"),"@STRING"==n?this.string():"@PREAMBLE"==n?this.preamble():"@COMMENT"==n?this.comment():this.entry(n),this.match("}")}}}n.toJSON=function(n){var e=new t;return e.setInput(n),e.bibtex(),e.entries},n.toBibtex=function(n){var t="";for(var e in n){if(t+="@"+n[e].entryType,t+="{",n[e].citationKey&&(t+=n[e].citationKey+", "),n[e].entry&&(t+=n[e].entry),n[e].entryTags){var i="";for(var r in n[e].entryTags)0!=i.length&&(i+=", "),i+=r+"= {"+n[e].entryTags[r]+"}";t+=i}t+="}\n\n"}return t}}(t)}));function nn(n){return n.replace(/[\t\n ]+/g," ").replace(/{\\["^`.'acu~Hvs]( )?([a-zA-Z])}/g,(function(n,t,e){return e})).replace(/{\\([a-zA-Z])}/g,(function(n,t){return t}))}function tn(n){var t,e=new Map,i=a(J.toJSON(n));try{for(i.s();!(t=i.n()).done;){for(var r=t.value,o=0,s=Object.entries(r.entryTags);o ',n.githubCompareUpdatesUrl&&(t+='View all changes to this article since it was first published.')),t+='\n If you see mistakes or want to suggest changes, please create an issue on GitHub. Diagrams and text are licensed under Creative Commons Attribution CC-BY 4.0 with the source available on GitHub, unless noted otherwise. The figures that have been reused from other sources don’t fall under this license and can be recognized by a note in their caption: “Figure from …”. For attribution in academic contexts, please cite this work as BibTeX citation tag. We found the following text: ' + text);\n const wrapper = document.createElement('span');\n wrapper.innerHTML = addedNode.nodeValue;\n addedNode.parentNode.insertBefore(wrapper, addedNode);\n addedNode.parentNode.removeChild(addedNode);\n }\n } break;\n }\n }\n }\n }).observe(this, {childList: true});\n }\n\n }\n\n var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};\n\n function createCommonjsModule(fn, module) {\n \treturn module = { exports: {} }, fn(module, module.exports), module.exports;\n }\n\n var bibtexParse = createCommonjsModule(function (module, exports) {\n /* start bibtexParse 0.0.22 */\n\n //Original work by Henrik Muehe (c) 2010\n //\n //CommonJS port by Mikola Lysenko 2013\n //\n //Port to Browser lib by ORCID / RCPETERS\n //\n //Issues:\n //no comment handling within strings\n //no string concatenation\n //no variable values yet\n //Grammar implemented here:\n //bibtex -> (string | preamble | comment | entry)*;\n //string -> '@STRING' '{' key_equals_value '}';\n //preamble -> '@PREAMBLE' '{' value '}';\n //comment -> '@COMMENT' '{' value '}';\n //entry -> '@' key '{' key ',' key_value_list '}';\n //key_value_list -> key_equals_value (',' key_equals_value)*;\n //key_equals_value -> key '=' value;\n //value -> value_quotes | value_braces | key;\n //value_quotes -> '\"' .*? '\"'; // not quite\n //value_braces -> '{' .*? '\"'; // not quite\n (function(exports) {\n\n function BibtexParser() {\n\n this.months = [\"jan\", \"feb\", \"mar\", \"apr\", \"may\", \"jun\", \"jul\", \"aug\", \"sep\", \"oct\", \"nov\", \"dec\"];\n this.notKey = [',','{','}',' ','='];\n this.pos = 0;\n this.input = \"\";\n this.entries = new Array();\n\n this.currentEntry = \"\";\n\n this.setInput = function(t) {\n this.input = t;\n };\n\n this.getEntries = function() {\n return this.entries;\n };\n\n this.isWhitespace = function(s) {\n return (s == ' ' || s == '\\r' || s == '\\t' || s == '\\n');\n };\n\n this.match = function(s, canCommentOut) {\n if (canCommentOut == undefined || canCommentOut == null)\n canCommentOut = true;\n this.skipWhitespace(canCommentOut);\n if (this.input.substring(this.pos, this.pos + s.length) == s) {\n this.pos += s.length;\n } else {\n throw \"Token mismatch, expected \" + s + \", found \"\n + this.input.substring(this.pos);\n } this.skipWhitespace(canCommentOut);\n };\n\n this.tryMatch = function(s, canCommentOut) {\n if (canCommentOut == undefined || canCommentOut == null)\n canCommentOut = true;\n this.skipWhitespace(canCommentOut);\n if (this.input.substring(this.pos, this.pos + s.length) == s) {\n return true;\n } else {\n return false;\n } };\n\n /* when search for a match all text can be ignored, not just white space */\n this.matchAt = function() {\n while (this.input.length > this.pos && this.input[this.pos] != '@') {\n this.pos++;\n }\n if (this.input[this.pos] == '@') {\n return true;\n } return false;\n };\n\n this.skipWhitespace = function(canCommentOut) {\n while (this.isWhitespace(this.input[this.pos])) {\n this.pos++;\n } if (this.input[this.pos] == \"%\" && canCommentOut == true) {\n while (this.input[this.pos] != \"\\n\") {\n this.pos++;\n } this.skipWhitespace(canCommentOut);\n } };\n\n this.value_braces = function() {\n var bracecount = 0;\n this.match(\"{\", false);\n var start = this.pos;\n var escaped = false;\n while (true) {\n if (!escaped) {\n if (this.input[this.pos] == '}') {\n if (bracecount > 0) {\n bracecount--;\n } else {\n var end = this.pos;\n this.match(\"}\", false);\n return this.input.substring(start, end);\n } } else if (this.input[this.pos] == '{') {\n bracecount++;\n } else if (this.pos >= this.input.length - 1) {\n throw \"Unterminated value\";\n } } if (this.input[this.pos] == '\\\\' && escaped == false)\n escaped = true;\n else\n escaped = false;\n this.pos++;\n } };\n\n this.value_comment = function() {\n var str = '';\n var brcktCnt = 0;\n while (!(this.tryMatch(\"}\", false) && brcktCnt == 0)) {\n str = str + this.input[this.pos];\n if (this.input[this.pos] == '{')\n brcktCnt++;\n if (this.input[this.pos] == '}')\n brcktCnt--;\n if (this.pos >= this.input.length - 1) {\n throw \"Unterminated value:\" + this.input.substring(start);\n } this.pos++;\n } return str;\n };\n\n this.value_quotes = function() {\n this.match('\"', false);\n var start = this.pos;\n var escaped = false;\n while (true) {\n if (!escaped) {\n if (this.input[this.pos] == '\"') {\n var end = this.pos;\n this.match('\"', false);\n return this.input.substring(start, end);\n } else if (this.pos >= this.input.length - 1) {\n throw \"Unterminated value:\" + this.input.substring(start);\n } }\n if (this.input[this.pos] == '\\\\' && escaped == false)\n escaped = true;\n else\n escaped = false;\n this.pos++;\n } };\n\n this.single_value = function() {\n var start = this.pos;\n if (this.tryMatch(\"{\")) {\n return this.value_braces();\n } else if (this.tryMatch('\"')) {\n return this.value_quotes();\n } else {\n var k = this.key();\n if (k.match(\"^[0-9]+$\"))\n return k;\n else if (this.months.indexOf(k.toLowerCase()) >= 0)\n return k.toLowerCase();\n else\n throw \"Value expected:\" + this.input.substring(start) + ' for key: ' + k;\n\n } };\n\n this.value = function() {\n var values = [];\n values.push(this.single_value());\n while (this.tryMatch(\"#\")) {\n this.match(\"#\");\n values.push(this.single_value());\n } return values.join(\"\");\n };\n\n this.key = function() {\n var start = this.pos;\n while (true) {\n if (this.pos >= this.input.length) {\n throw \"Runaway key\";\n } // а-яА-Я is Cyrillic\n //console.log(this.input[this.pos]);\n if (this.notKey.indexOf(this.input[this.pos]) >= 0) {\n return this.input.substring(start, this.pos);\n } else {\n this.pos++;\n\n } } };\n\n this.key_equals_value = function() {\n var key = this.key();\n if (this.tryMatch(\"=\")) {\n this.match(\"=\");\n var val = this.value();\n return [ key, val ];\n } else {\n throw \"... = value expected, equals sign missing:\"\n + this.input.substring(this.pos);\n } };\n\n this.key_value_list = function() {\n var kv = this.key_equals_value();\n this.currentEntry['entryTags'] = {};\n this.currentEntry['entryTags'][kv[0]] = kv[1];\n while (this.tryMatch(\",\")) {\n this.match(\",\");\n // fixes problems with commas at the end of a list\n if (this.tryMatch(\"}\")) {\n break;\n }\n kv = this.key_equals_value();\n this.currentEntry['entryTags'][kv[0]] = kv[1];\n } };\n\n this.entry_body = function(d) {\n this.currentEntry = {};\n this.currentEntry['citationKey'] = this.key();\n this.currentEntry['entryType'] = d.substring(1);\n this.match(\",\");\n this.key_value_list();\n this.entries.push(this.currentEntry);\n };\n\n this.directive = function() {\n this.match(\"@\");\n return \"@\" + this.key();\n };\n\n this.preamble = function() {\n this.currentEntry = {};\n this.currentEntry['entryType'] = 'PREAMBLE';\n this.currentEntry['entry'] = this.value_comment();\n this.entries.push(this.currentEntry);\n };\n\n this.comment = function() {\n this.currentEntry = {};\n this.currentEntry['entryType'] = 'COMMENT';\n this.currentEntry['entry'] = this.value_comment();\n this.entries.push(this.currentEntry);\n };\n\n this.entry = function(d) {\n this.entry_body(d);\n };\n\n this.bibtex = function() {\n while (this.matchAt()) {\n var d = this.directive();\n this.match(\"{\");\n if (d == \"@STRING\") {\n this.string();\n } else if (d == \"@PREAMBLE\") {\n this.preamble();\n } else if (d == \"@COMMENT\") {\n this.comment();\n } else {\n this.entry(d);\n }\n this.match(\"}\");\n } };\n }\n exports.toJSON = function(bibtex) {\n var b = new BibtexParser();\n b.setInput(bibtex);\n b.bibtex();\n return b.entries;\n };\n\n /* added during hackathon don't hate on me */\n exports.toBibtex = function(json) {\n var out = '';\n for ( var i in json) {\n out += \"@\" + json[i].entryType;\n out += '{';\n if (json[i].citationKey)\n out += json[i].citationKey + ', ';\n if (json[i].entry)\n out += json[i].entry ;\n if (json[i].entryTags) {\n var tags = '';\n for (var jdx in json[i].entryTags) {\n if (tags.length != 0)\n tags += ', ';\n tags += jdx + '= {' + json[i].entryTags[jdx] + '}';\n }\n out += tags;\n }\n out += '}\\n\\n';\n }\n return out;\n\n };\n\n })( exports);\n\n /* end bibtexParse */\n });\n\n // Copyright 2018 The Distill Template Authors\n\n function normalizeTag(string) {\n return string\n .replace(/[\\t\\n ]+/g, ' ')\n .replace(/{\\\\[\"^`.'acu~Hvs]( )?([a-zA-Z])}/g, (full, x, char) => char)\n .replace(/{\\\\([a-zA-Z])}/g, (full, char) => char);\n }\n\n function parseBibtex(bibtex) {\n const bibliography = new Map();\n const parsedEntries = bibtexParse.toJSON(bibtex);\n for (const entry of parsedEntries) {\n // normalize tags; note entryTags is an object, not Map\n for (const [key, value] of Object.entries(entry.entryTags)) {\n entry.entryTags[key.toLowerCase()] = normalizeTag(value);\n }\n entry.entryTags.type = entry.entryType;\n // add to bibliography\n bibliography.set(entry.citationKey, entry.entryTags);\n }\n return bibliography;\n }\n\n function serializeFrontmatterToBibtex(frontMatter) {\n return `@article{${frontMatter.slug},\n author = {${frontMatter.bibtexAuthors}},\n title = {${frontMatter.title}},\n journal = {${frontMatter.journal.title}},\n year = {${frontMatter.publishedYear}},\n note = {${frontMatter.url}},\n doi = {${frontMatter.doi}}\n}`;\n }\n\n // Copyright 2018 The Distill Template Authors\n\n class Bibliography extends HTMLElement {\n\n static get is() { return 'd-bibliography'; }\n\n constructor() {\n super();\n\n // set up mutation observer\n const options = {childList: true, characterData: true, subtree: true};\n const observer = new MutationObserver( (entries) => {\n for (const entry of entries) {\n if (entry.target.nodeName === 'SCRIPT' || entry.type === 'characterData') {\n this.parseIfPossible();\n }\n }\n });\n observer.observe(this, options);\n }\n\n connectedCallback() {\n requestAnimationFrame(() => {\n this.parseIfPossible();\n });\n }\n\n parseIfPossible() {\n const scriptTag = this.querySelector('script');\n if (!scriptTag) return;\n if (scriptTag.type == 'text/bibtex') {\n const newBibtex = scriptTag.textContent;\n if (this.bibtex !== newBibtex) {\n this.bibtex = newBibtex;\n const bibliography = parseBibtex(this.bibtex);\n this.notify(bibliography);\n }\n } else if (scriptTag.type == 'text/json') {\n const bibliography = new Map(JSON.parse(scriptTag.textContent));\n this.notify(bibliography);\n } else {\n console.warn('Unsupported bibliography script tag type: ' + scriptTag.type);\n }\n }\n\n notify(bibliography) {\n const options = { detail: bibliography, bubbles: true };\n const event = new CustomEvent('onBibliographyChanged', options);\n this.dispatchEvent(event);\n }\n\n /* observe 'src' attribute */\n\n static get observedAttributes() {\n return ['src'];\n }\n\n receivedBibtex(event) {\n const bibliography = parseBibtex(event.target.response);\n this.notify(bibliography);\n }\n\n attributeChangedCallback(name, oldValue, newValue) {\n var oReq = new XMLHttpRequest();\n oReq.onload = (e) => this.receivedBibtex(e);\n oReq.onerror = () => console.warn(`Could not load Bibtex! (tried ${newValue})`);\n oReq.responseType = 'text';\n oReq.open('GET', newValue, true);\n oReq.send();\n }\n\n\n }\n\n // Copyright 2018 The Distill Template Authors\n //\n // Licensed under the Apache License, Version 2.0 (the \"License\");\n // you may not use this file except in compliance with the License.\n // You may obtain a copy of the License at\n //\n // http://www.apache.org/licenses/LICENSE-2.0\n //\n // Unless required by applicable law or agreed to in writing, software\n // distributed under the License is distributed on an \"AS IS\" BASIS,\n // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n // See the License for the specific language governing permissions and\n // limitations under the License.\n\n // import style from '../styles/d-byline.css';\n\n function bylineTemplate(frontMatter) {\n return `\n `;\n if (frontMatter.githubCompareUpdatesUrl) {\n html += `View all changes to this article since it was first published.`;\n }\n html += `\n If you see mistakes or want to suggest changes, please create an issue on GitHub. Diagrams and text are licensed under Creative Commons Attribution CC-BY 4.0 with the source available on GitHub, unless noted otherwise. The figures that have been reused from other sources don’t fall under this license and can be recognized by a note in their caption: “Figure from …”. For attribution in academic contexts, please cite this work as BibTeX citation The performance of a large language model (LLM) depends heavily on the quality and size of the LLMs.
+ However, the pretraining datasets for state-of-the-art open LLMs like Llama 3 Recently, we released 🍷 FineWeb, a new, large-scale
+ (15-trillion tokens, 44TB disk space) dataset for LLM pretraining. FineWeb is derived from 96 CommonCrawl snapshots and produces better-performing LLMs than other open pretraining datasets.
+
+
+
+ TLDR: This blog covers a discussion on processing and evaluating data quality at scale, the 🍷 FineWeb
+ recipe (listing and explaining all of our design choices), and the process followed to create its 📚 FineWeb-Edu subset. Now that we know the basics of distributed communication and computations it’s time to apply this to training LLMs at scale. Here’s the plan of action: we’ll go through increasingly complex distribution strategies, namely data, then tensor and finally pipeline parallelism, and show three things: For the experiments we scale across two dimensions: we make the models larger and larger and add more and more compute nodes and measure how throughput changes. So this is a good point to get ☕ #2 and we’ll have a look at the setup for the practical experiments. (1) FineWeb ablation models (2) Nemotron-340B architecture (without GQA) (3) Llama-400B, ffn dim = 1.2 hidden dim (without GQA) Efficiently training LLMs now requires amounts of compute which exceed in most case single GPUs or machine. Large distributed clusters are thus used to train these models and can range from hundreds to thousands of nodes each usually equipped with up to 8 GPUs. To make the best use of such an expensive hardware, a range of distributed training methods have been developed with the goal of ensuring that GPUs are highly utilized at all times and not waiting for data/synchronization/etc. Several methods can be used to distribute training and we’ll start with 4D parallelism followed-up by DeepSpeed stages. While we explain these strategies we’ll also run experiments to determine the trade-offs and understand the optimal settings. The name “4D parallelism” originates from the fact that it involves combining up to 4 distribution methods: data, tensor, pipeline, and sequence parallelism (each of these techniques can be used independently of the other). You may thus ask “So which one should I use?”. Unfortunately, there is no universal answer as the response will actually depend on the cluster setup as well as the model architecture. But do not despair for in this section we’ll develop strategies to figure out the best setting experimentally! In addition to 4D parallelism we’ll also take a look at “DeepSpeed”, a method developed by Microsoft which is generally complimentary to 4D parallelism and can be leveraged on top of it. Idea: show two things in every section Let’s quickly go over the basics before going into distributed training. When a model is trained on a single GPU, the training consists of 3 steps in the simplest case: As we’ll see in the future, these steps may be repeated or intertwined but for now we’ll start simple: In this figure the successive blue boxes on the top line can be seen as successive layers inside a model (same for the last line). The red boxes are the associated gradients for each of these layers. The batch size (bs) is one of the most important hyper-parameters in machine learning, affecting both model convergence and throughput. If the batch size is too small, gradients will tend to be noisy and the model may not be able to converge to optimal performances while a batch size too large can make the convergence of the model slower and waste compute. You can find a nice discussion of this topic in OpenAI’s paper on large batch training (https://arxiv.org/pdf/1812.06162). The batch size also affects the throughput: a small batch size will require more optimizer steps to train on a given amount of samples. Optimizer steps are costly (in compute time) and the throughput will thus be lower than when using a larger batch size. On the other hand, larger batches, while leading to higher throughput may suffer from slow convergence in the limits as we’ve just seen. There is generally an optimal batch size from a convergence/performance point of view (note that the batch size can usually still be changed around the optimal batch size without major impact to the performance of the model). Note that in the LLM community, batch sizes are commonly reported in terms of tokens instead of number of samples (BST - Batch Size Tokens) as each token has a label and thus a loss term and can thus be considered individual (although highly correlated) samples. A sweet spot for LLM training is usually on the order of 4-20 million tokens per batch (links GPT-3, DeepSeek, Llama). In the simplest case, training on a single machine, the BS and BST can be computed from the model input sequence length as follows: (note that from here on forward we’ll show the formulas for the batch size in number of samples but you can always get its token-unit counterpart by multiplying it with the sequence length) And we’re now hitting our first scaling problem: what if we can’t fit the model into GPU memory even with Good question, reader! Let’s start by understanding what led to our out-of-memory issue in the first place. To train a neural network model, one needs to store many elements in memory besides the weights themselves. Generally, the memory usage is made up from the following elements: Scaling up training is usually a question of playing with those constituents to keep memory low while not impacting performance too much. We’ll neglect the last two contributors as there’s usually not that much you can do about them unless you dive deep in the code. For the rest, they are usually different types of tensors that can have various sizes (usually multiples of one or several of batch size, sequence length, model hidden dimension and some potential sharding) and various precisions (with optimizer states and weights copy being often kept in full FP32 precision while activations can be of lower precision like BF16 or FP8). Let’s try to get some intuition for the memory requirement of these various elements. Let’s first look at the weights, gradients and optimizer states. They are all dependent on the number of parameters in a model. For a simple LLM the number of parameters is given by the following formula: In that equation, h corresponds to the hidden dimension, v to the vocabulary size, and L the number of layers in the model. Note that looking at the equation we can see that the term that will dominate at large model scales is the one with h^2 since it’s the only term growing quadratically as we scale the models. Let’s see how the number of parameters translates to memory usage. The memory requirements for the parameters and gradients are the number of parameters multiplied by the number of bytes per parameter. Mixed precision training with BF16 is the default nowadays which requires 2 bytes per parameter. In addition, there are a number of values necessary for the optimizer states: for ADAM it requires the momentum and the variance in FP32, each using 4 bytes, and an additional copy of the model weights in FP32, thus 12 bytes per parameter (ref: ZeRO): In old-fashioned full precision training both parameters and gradients would require 4 bytes each but the optimizer on the other hand wouldn’t need to store an extra full precision copy of the weights: So we can easily see that mixed precision itself doesn’t save memory as it just distributes the memory differently across the three components. So by multiplying the number of parameters by 16 (=2+2+12) you can quickly get a sense of how much GPU memory we need for a model: We can further decrease the memory usage if we choose FP8 training instead of BF16 but it is much less stable and a very active research topic (see here) thus we won’t go in details here. But we are not done yet, we’ll also need to store the forward pass activations which are used during the backward pass to compute the gradients. The total memory required for the activations in mixed precision (which contributes the leading factor of 2 below) is given by the following equation: You can follow this NVIDIA paper for a complete derivation, it essentially requires you to do some accounting of all the sizes of intermediate activations between each operation. What’s interesting here is that the memory is not static for a given model but depends critically on the sequence length. We can use the memory formulas and have a look how the memory usage changes for a model for various sequence lengths: This graph tells a striking story: for short sequences, activations are almost negligible, but starting at around 2-4k tokens they start to take up a significant amount of memory while parameter, gradient and optimizer state are roughly independent of the sequence length and batch size. For large batch/sequence, activations however become by far the largest memory burden. Is there a way to tame this “activation explosion”? Good question, reader! I see you’re following well and you’re lucky as the answer is “Yes”! Let’s talk about a technique called gradient checkpointing or more frequently activation recomputation which can help us cap activation memory footprint and is an essential tool in today’s large model training toolbox. The general idea behind gradient checkpointing is to discard some activations to save memory if we are willing to spend some extra compute to recompute them when needed. Typically we will save activations at some key points in memory and discard the rest and recompute them during the backward pass from the nearest activations: We can select these key activations according to several strategies and modern frameworks usually choose among the following three strategies: Let’s see how recomputation strategies can drastically reduce the memory footprint while selective recomputation strikes a nice balance between memory saving and recomputation cost: Note: Hardware vs Model flops. Most frameworks these days use FlashAttention (TODO: see later) which makes the attention computation less memory intensive through kernel fusion, thus most trainings use the We can save some GPU memory with activation recomputation but this only delays by a bit the next bottleneck: as hinted earlier for LLM training there is usually a sweet spot for the GBST and we need to work out the training configuration backward from there. However, you can’t choose MBS to be an arbitrary large number on your GPU; at some point you will run out of GPU memory again since you need to store at least some of the activations in memory. There is a useful trick to compensate for that: gradient accumulation (GradAcc). With gradient accumulation we will split our batch in micro-batch, do forward and backward passes repeatedly on each micro-batch, compute the gradients, and, as the name suggests, sum the gradients step by step before doing a final optimizer step. We call the What we now call With gradient accumulation the global batch size can be computed as follows: Gradient accumulation allows us to effectively increase our batch size up to infinity (!) while the memory footprint stays constant. Gradient accumulation is also compatible with activation recomputation for further memory reduction. One drawback however, is that gradient accumulation requires multiple consecutive forward/backward passes per optimization step thereby increasing the compute overhead and slowing down training. No free lunch! This is actually a bummer since the forward/backward passes for each micro-batch could actually totally be run in parallel. They are independent from each other and the only changing parameter are the input samples. Here comes data parallelism to solve exactly this problem! Let’s take a look, you say? Okay sure! The idea behind data parallelism (DP) is to parallelize forward and backward passes across GPUs, passing different batches of data per GPU (or groups of GPUs) to the same model instance. Just like for gradient accumulation, we need to average gradients across instances before we do the optimization step. The GBS equation can then be extended to: This means that we can reduce the number of gradient accumulation steps in favor of data parallel processes which speeds up training. In practice, people will tend to max out the number of data parallel nodes (the DP above) as much as possible as it’s inherently parallel versus the sequential Gradient Accumulation. Gradient accumulation is then added only to achieve a target batch size if DP alone is not sufficient. One exception to that is pipeline parallelism which we’ll discuss later. As you can see on the figure above, some gradients can already be gathered and summed (red boxes) even before gradients down the line (red boxes on the left of the current gradient) are still being computed. This significantly speeds up data parallelism. For instance, as soon as the backward pass of the last layer is done (last boxes on the right) those gradients can already be gathered/summed while the backward pass computations move to earlier layers, aka to the left. This lowers the communication/bandwidth pressure to sync gradients of the full model as it can be performed in part in parallel to the computation of said gradients. See this article for more information. A general recipe to determine an optimal data-parallel setup can be as follows: If the gradient accumulation ratio is lower than one, i.e. you have too many GPUs (!), you can either choose to not use all your GPUs or test if a lower MBS will speed up training. In these cases, you may want to prioritize throughput over the individual GPU utilization, you can then choose DP first and use a smaller MBS than possible in order to speed up training. Time to take a concrete example: We want to train a model with a GBS of 4M tokens and a sequence length of 4k. This means our batch size will be 1024 samples (we pick powers of two). We observe that a single of our GPU can fit MBS=2 in memory and we have 128 GPUs available for training. This means with 4 gradient accumulation steps we’ll achieve our goal of 1024 samples or 4M tokens per training step. Now what if we suddenly have 1024 GPUs available? We can achieve the same GBS and thus identical training by setting both MBS and gradient accumulation to 1 speeding up training significantly. [EXPERIMENTS WHERE WE INCREASE DP AND SHOW THROUGHPUT FOR SEVERAL MODELS] We’ve explored data parallelism, a simple strategy to scale training across more GPUs and gives consistent speed improvements. The keen reader might have noticed however that it rests on the assumption that we can fit at least one input sample forward pass (MBS=1) into our GPU memory. This is not always the case! In particular for larger models which often don’t fit into a single GPU anymore even with activation recomputations activated. In such case, we need to shard the model across devices! We’ll now study two complementary sharding methods, tensor and pipeline parallelism which are doing that. Let’s start by the simplest, tensor parallelism! So you’ve exhausted all the previous textbook tricks to try to fit your model on a single GPU but it still doesn’t fit? Let’s try to distribute this model across several GPUs. Unlike DP we will not simply duplicate the model but various parts of the model instance will be living on various GPUs. If we take a look at a typical matrix multiplication (the core of a neural network), we can get an idea about how we could split the model: Tensor parallelism is a technique in which a tensor is split into N shards along a particular dimension across N GPUs. Matrices can be split either on the column part or row part leading to row and column parallelism. Depending on which splitting strategy we choose will require different communications primitives. Column linear: This was for an example matrix multiplication. How do we apply this in practice to a real model? In the Transformer, there are 2 basic building blocks where tensor parallel can be applied: Feedforward layers comprise 2 successive MLPs with a non-linearity in-between. Here is the first part of it: Should we use row or column parallelization for the first MLP? Well it turns out parallelized GeLU only works in Column schema: In column schema: In row schema: If you rather like code, note that we can prove this with the following snippet as well: To avoid a synchronization step directly after the first MLP, we’ll thus start with Column Parallel and be able to directly perform parallel GELU. Now, what about the second MLP? Should it be column or row parallel? Let’s draft both options: We see that the “Column Parallel followed by Row Parallel” schema only involves two communications instead of four. It’s thus the most efficient schema in terms of communications. Let’s take a quick look at the backward pass: Now that we’ve found the most efficient schema for the Feedforward part of the transformer, let’s take a look at the multi-head attention block (MHA). We can generally follow a similar approach where the Q, K, V will be split in a Column Parallel fashion and the output projection will be split along the Row dimension. To dive in further particularities, a nice reference paper detailing TP is for instance Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism. Note: Sequence Parallel Tensor parallelism has been a great help to parallelize some of our computation on several GPU nodes with the limited cost of a few communication operations. It also had the additional benefit of reducing memory usage by splitting intermediate activations inside the feedforward elements across GPUs and thereby reducing the activations to store on each node. Could we push this approach further? Sequence parallelism applies this same idea to other parts of our model. We’ve applied tensor parallelism to two main parts in our models where combination of MLP allowed to naturally split the weights along major axis. The rest of the model mostly comprises layer norms, dropout and various summation of residuals, these contribute little to the computation but come with rather large forward activations to store. [Add some illustration of the forward activations to store for each part] Even though TP-SP mode helps reduce the memory used by activation values, it has two main drawbacks: An empirical estimation is that with TP=8, you can only train an 8B model with a 20K context length. However, LLaMA 3.1 has managed to scale the context length to 128K by using context parallelism. There are several ways to implement sequence parallelism. We used ring attention, which overlaps communication and computation. LLaMA3.1 uses all-gather along the sequence dimension because it is easier and more flexible to support different types of attention masks in all-gather based CP attention, such as the document mask. In the transformer model, tokens have no inherent information about their positional information. For these reasons, we need to use a positional encoding function. Assuming that in the multi-head attention layer, q_m is the “position-aware” query vector corresponding to a token at position m, k_n the “position-aware” key vector corresponding to the token at position n and f is our position embedding function, we would like our position vector to be a function of the input vectors and absolute positions like this: We may also want the positional encoding to model relative positional information between two input tokens. Relative positions help the model to operate across longer context spans and even context lengths not seen during training. The attention operation is generally a dot product operation between “position-aware” vectors q and k, so for a positional encoding that contains relative positional information, we’ll want to have: In other words, we want the result of ⟨ 𝑞_𝑚 , 𝑘_𝑛 ⟩ to depend on the values of q and k themselves, as well as their relative position m − n, but not m and n. This way, the model can focus on the relative difference between two tokens rather than their absolute positions. Let’s show that the RoPE positional embedding formulation satisfies the above formula. Rotation matrix RoPE are based on rotation matrices which have simple and interesting properties for us. In a 2D space, a rotation matrix has the following form: The rotation matrix has the following properties: RoPE in 2D space Assuming q and k are 2D column vectors, we can show that: Therefore, if we define our position embedding like this: f(x, m) = R(mθ)x where R is a 2D rotation matrix, we have q_m = R(mθ)q and k_n = R(nθ)k and then: We can see that a multiplication with a rotation matrix is exactly the positional encoding we were looking for. The result of ⟨ 𝑞_𝑚 , 𝑘_𝑛 ⟩ only depends on q, k and m-n. Implementation In our case, our internal vectors (the activations in our model) have much more than two elements. Let’s pair elements to get 2D vectors and apply the 2D rotation operation on these pairs. There are combinatorially many ways we can pair elements but generally two options are the most popular for implementing RoPE: we call them the interleaved and non-interleaved versions. (It’s still rather unfortunate to have two popular options) The angle of rotation, θi is defined as follows, where d is the dimension of the attention head: How does this look? When moving the same distance, vectors in some dimensions rotate faster than vectors in other dimensions. @Phuc Nguyen? @Haojun Zhao @Phuc Nguyen @Haojun Zhao @Haojun Zhao maybe? Through our open science efforts we hope to keep shining a light on the black box that is the training of high performance large language models as well as to give every model trainer the ability to create state-of-the-art LLMs. We are excited to continue iterating on FineWeb and to release increasingly better filtered subsets of web data, in a fully open and reproducible manner. In the short term, we are looking forward to applying the learnings from (English) FineWeb to other languages. While English currently dominates the LLM landscape, we believe that making high quality web data in other languages as accessible as possible would be incredibly impactful. In a nutshell: the future is bright and exciting for studying the science of creating datasets at scale and in the open 🤗. For attribution in academic contexts, please cite this work as BibTeX citationAuthors
\n Affiliation
\n \n Published
\n \n ".concat(n.map(U).map((function(n){return"
"))}}],[{key:"observedAttributes",get:function(){return["key","bibtex-key"]}}])}(x("d-cite",'\n\n\n\n\n'))(HTMLElement))),un=function(n){function t(){var n;f(this,t),n=s(this,t);return new MutationObserver(n.notify).observe(n,{childList:!0,characterData:!0,subtree:!0}),n}return d(t,n),m(t,[{key:"notify",value:function(){var n=new CustomEvent("onFootnoteChanged",{detail:this,bubbles:!0});document.dispatchEvent(n)}},{key:"connectedCallback",value:function(){var n=this;this.hoverBox=this.root.querySelector("d-hover-box"),window.customElements.whenDefined("d-hover-box").then((function(){n.hoverBox.listen(n)})),t.currentFootnoteId+=1;var e=t.currentFootnoteId.toString();this.root.host.id="d-footnote-"+e;var i="dt-fn-hover-box-"+e;this.hoverBox.id=i;var r=this.root.querySelector("#fn-");r.setAttribute("id","fn-"+e),r.setAttribute("data-hover-ref",i),r.textContent=e}}])}(x("d-footnote",'\n\n\n
Footnotes
\n\n",!1)(HTMLElement)),dn=function(n){function t(){return f(this,t),s(this,t)}return d(t,n),m(t,[{key:"connectedCallback",value:function(){}},{key:"listen",value:function(n){this.bindDivEvents(this),this.bindTriggerEvents(n)}},{key:"bindDivEvents",value:function(n){var t=this;n.addEventListener("mouseover",(function(){t.visible||t.showAtNode(n),t.stopTimeout()})),n.addEventListener("mouseout",(function(){t.extendTimeout(500)})),n.addEventListener("touchstart",(function(n){n.stopPropagation()}),{passive:!0}),document.body.addEventListener("touchstart",(function(){t.hide()}),{passive:!0})}},{key:"bindTriggerEvents",value:function(n){var t=this;n.addEventListener("mouseover",(function(){t.visible||t.showAtNode(n),t.stopTimeout()})),n.addEventListener("mouseout",(function(){t.extendTimeout(300)})),n.addEventListener("touchstart",(function(e){t.visible?t.hide():t.showAtNode(n),e.stopPropagation()}),{passive:!0})}},{key:"show",value:function(n){this.visible=!0,this.style.display="block",this.style.top=Math.round(n[1]+10)+"px"}},{key:"showAtNode",value:function(n){var t=n.getBoundingClientRect();this.show([n.offsetLeft+t.width,n.offsetTop+t.height])}},{key:"hide",value:function(){this.visible=!1,this.style.display="none",this.stopTimeout()}},{key:"stopTimeout",value:function(){this.timeout&&clearTimeout(this.timeout)}},{key:"extendTimeout",value:function(n){var t=this;this.stopTimeout(),this.timeout=setTimeout((function(){t.hide()}),n)}}])}(x("d-hover-box",'\n\n\n
Table of contents
\n ',r=a(t);try{for(r.s();!(e=r.n()).done;){var o=e.value,s="D-TITLE"==o.parentElement.tagName,l=o.getAttribute("no-toc");if(!s&&!l){var u=o.textContent,c='
",n.innerHTML=i}(n,t)})}}],[{key:"is",get:function(){return"d-toc"}}])}(o(HTMLElement));var gn=function(n){function t(){var n;return f(this,t),(n=s(this,t))._ready=!1,n._onscreen=!1,n._offscreen=!0,n}return d(t,n),m(t,[{key:"connectedCallback",value:function(){this.loadsWhileScrolling=this.hasAttribute("loadsWhileScrolling"),t.marginObserver.observe(this),t.directObserver.observe(this)}},{key:"disconnectedCallback",value:function(){t.marginObserver.unobserve(this),t.directObserver.unobserve(this)}},{key:"addEventListener",value:function(n,e){u(c(t.prototype),"addEventListener",this).call(this,n,e),"ready"===n&&-1!==t.readyQueue.indexOf(this)&&(this._ready=!1,t.runReadyQueue()),"onscreen"===n&&this.onscreen()}},{key:"ready",value:function(){this._ready=!0,t.marginObserver.unobserve(this);var n=new CustomEvent("ready");this.dispatchEvent(n)}},{key:"onscreen",value:function(){this._onscreen=!0,this._offscreen=!1;var n=new CustomEvent("onscreen");this.dispatchEvent(n)}},{key:"offscreen",value:function(){this._onscreen=!1,this._offscreen=!0;var n=new CustomEvent("offscreen");this.dispatchEvent(n)}}],[{key:"is",get:function(){return"d-figure"}},{key:"readyQueue",get:function(){return t._readyQueue||(t._readyQueue=[]),t._readyQueue}},{key:"addToReadyQueue",value:function(n){-1===t.readyQueue.indexOf(n)&&(t.readyQueue.push(n),t.runReadyQueue())}},{key:"runReadyQueue",value:function(){var n=t.readyQueue.sort((function(n,t){return n._seenOnScreen-t._seenOnScreen})).filter((function(n){return!n._ready})).pop();n&&(n.ready(),requestAnimationFrame(t.runReadyQueue))}},{key:"marginObserver",get:function(){if(!t._marginObserver){var n=window.innerHeight,e=Math.floor(2*n),i={rootMargin:e+"px 0px "+e+"px 0px",threshold:.01},r=t.didObserveMarginIntersection,o=new IntersectionObserver(r,i);t._marginObserver=o}return t._marginObserver}},{key:"didObserveMarginIntersection",value:function(n){var e,i=a(n);try{for(i.s();!(e=i.n()).done;){var r=e.value,o=r.target;r.isIntersecting&&!o._ready&&t.addToReadyQueue(o)}}catch(n){i.e(n)}finally{i.f()}}},{key:"directObserver",get:function(){return t._directObserver||(t._directObserver=new IntersectionObserver(t.didObserveDirectIntersection,{rootMargin:"0px",threshold:[0,1]})),t._directObserver}},{key:"didObserveDirectIntersection",value:function(n){var t,e=a(n);try{for(e.s();!(t=e.n()).done;){var i=t.value,r=i.target;i.isIntersecting?(r._seenOnScreen=new Date,r._offscreen&&r.onscreen()):r._onscreen&&r.offscreen()}}catch(n){e.e(n)}finally{e.f()}}}])}(o(HTMLElement));if("undefined"!=typeof window){var mn;gn.isScrolling=!1;window.addEventListener("scroll",(function(){gn.isScrolling=!0,clearTimeout(mn),mn=setTimeout((function(){gn.isScrolling=!1,gn.runReadyQueue()}),500)}),!0)}var bn=function(n){function t(){return f(this,t),s(this,t,arguments)}return d(t,n),m(t,[{key:"connectedCallback",value:function(){var n=this;this.shouldRemoveSelf()?this.parentElement.removeChild(this):this.root.querySelector("#interstitial-password-input").oninput=function(t){return n.passwordChanged(t)}}},{key:"passwordChanged",value:function(n){n.target.value===this.password&&(console.log("Correct password entered."),this.parentElement.removeChild(this),"undefined"!=typeof Storage&&(console.log("Saved that correct password was entered."),localStorage.setItem(this.localStorageIdentifier(),"true")))}},{key:"shouldRemoveSelf",value:function(){return window&&"distill.pub"===window.location.hostname?(console.warn("Interstitial found on production, hiding it."),!0):"undefined"!=typeof Storage&&"true"===localStorage.getItem(this.localStorageIdentifier())&&(console.log("Loaded that correct password was entered before; skipping interstitial."),!0)}},{key:"localStorageIdentifier",value:function(){return"distill-drafts"+(window?window.location.pathname:"-")+"interstitial-password-correct"}}])}(x("d-interstitial",'\n\n\n \n')(HTMLElement));function yn(n,t){return n"+c+"
":c+="
",i+=c}}}catch(n){r.e(n)}finally{r.f()}i+="=l)return-1;if(37===(r=t.charCodeAt(a++))){if(r=t.charAt(a++),!(o=x[r in $e?t.charAt(a++):r])||(i=o(n,e,i))<0)return-1}else if(r!=e.charCodeAt(i++))return-1}return i}return v.x=k(e,v),v.X=k(i,v),v.c=k(t,v),w.x=k(e,w),w.X=k(i,w),w.c=k(t,w),{format:function(n){var t=k(n+="",v);return t.toString=function(){return n},t},parse:function(n){var t=S(n+="",!1);return t.toString=function(){return n},t},utcFormat:function(n){var t=k(n+="",w);return t.toString=function(){return n},t},utcParse:function(n){var t=S(n+="",!0);return t.toString=function(){return n},t}}}(n)).format,He.parse,je=He.utcFormat,ze=He.utcParse}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Ji="%Y-%m-%dT%H:%M:%S.%LZ";Date.prototype.toISOString||je(Ji),+new Date("2000-01-01T00:00:00.000Z")||ze(Ji);var nr={value:function(){}};function tr(){for(var n,t=0,e=arguments.length,i={};t0)for(var e,i,r=new Array(e),o=0;oUpdates and Corrections
\n Reuse
\n Citation
\n '.concat(n.concatenatedAuthors,', "').concat(n.title,'", Distill, ').concat(n.publishedYear,'.
\n ').concat(function(n){return"@article{".concat(n.slug,",\n author = {").concat(n.bibtexAuthors,"},\n title = {").concat(n.title,"},\n journal = {").concat(n.journal.title,"},\n year = {").concat(n.publishedYear,"},\n note = {").concat(n.url,"},\n doi = {").concat(n.doi,"}\n}")}(n),"
\n ")),t}(n)}}],[{key:"is",get:function(){return"distill-appendix"}}])}(o(HTMLElement)),Ho=function(n){function t(){return f(this,t),s(this,t,arguments)}return d(t,n),m(t)}(x("distill-footer",'\n\n\n \n\n'))(HTMLElement));window.distill={runlevel:0,initialize:function(){if(window.distill.runlevel<1)throw new Error("Insufficient Runlevel for Distill Template!");if("distill"in window&&window.distill.templateIsLoading)throw new Error("Runlevel 1: Distill Template is getting loaded more than once, aborting!");window.distill.templateIsLoading=!0,console.debug("Runlevel 1: Distill Template has started loading."),function(n){var t="distill-prerendered-styles";if(!n.getElementById(t)){var e=n.createElement("style");e.id=t,e.type="text/css";var i=n.createTextNode(q);e.appendChild(i);var r=n.head.querySelector("script");n.head.insertBefore(e,r)}}(document),console.debug("Runlevel 1: Static Distill styles have been added."),console.debug("Runlevel 1->2."),window.distill.runlevel+=1;for(var n=0,t=Object.entries($.listeners);n
\" : \"\"} DOI: ${ent.doi}`;\n } else {\n return \"\";\n }\n }\n\n function title_string(ent) {\n return '' + ent.title + \" \";\n }\n\n function bibliography_cite(ent, fancy) {\n if (ent) {\n var cite = title_string(ent);\n cite += link_string(ent) + \"
\";\n if (ent.author) {\n cite += author_string(ent, \"${L}, ${I}\", \", \", \" and \");\n if (ent.year || ent.date) {\n cite += \", \";\n }\n }\n if (ent.year || ent.date) {\n cite += (ent.year || ent.date) + \". \";\n } else {\n cite += \". \";\n }\n cite += venue_string(ent);\n cite += doi_string(ent);\n return cite;\n /*var cite = author_string(ent, \"${L}, ${I}\", \", \", \" and \");\n if (ent.year || ent.date){\n cite += \", \" + (ent.year || ent.date) + \". \"\n } else {\n cite += \". \"\n }\n cite += \"\" + ent.title + \". \";\n cite += venue_string(ent);\n cite += doi_string(ent);\n cite += link_string(ent);\n return cite*/\n } else {\n return \"?\";\n }\n }\n\n function hover_cite(ent) {\n if (ent) {\n var cite = \"\";\n cite += \"\" + ent.title + \"\";\n cite += link_string(ent);\n cite += \"
\";\n\n var a_str = author_string(ent, \"${I} ${L}\", \", \") + \".\";\n var v_str =\n venue_string(ent).trim() + \" \" + ent.year + \". \" + doi_string(ent, true);\n\n if ((a_str + v_str).length < Math.min(40, ent.title.length)) {\n cite += a_str + \" \" + v_str;\n } else {\n cite += a_str + \"
\" + v_str;\n }\n return cite;\n } else {\n return \"?\";\n }\n }\n\n function domContentLoaded() {\n return ['interactive', 'complete'].indexOf(document.readyState) !== -1;\n }\n\n // Copyright 2018 The Distill Template Authors\n //\n // Licensed under the Apache License, Version 2.0 (the \"License\");\n // you may not use this file except in compliance with the License.\n // You may obtain a copy of the License at\n //\n // http://www.apache.org/licenses/LICENSE-2.0\n //\n // Unless required by applicable law or agreed to in writing, software\n // distributed under the License is distributed on an \"AS IS\" BASIS,\n // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n // See the License for the specific language governing permissions and\n // limitations under the License.\n\n function _moveLegacyAffiliationFormatIntoArray(frontMatter) {\n // authors used to have propoerties \"affiliation\" and \"affiliationURL\".\n // We now encourage using an array for affiliations containing objects with\n // properties \"name\" and \"url\".\n for (let author of frontMatter.authors) {\n const hasOldStyle = Boolean(author.affiliation);\n const hasNewStyle = Boolean(author.affiliations);\n if (!hasOldStyle) continue;\n if (hasNewStyle) {\n console.warn(`Author ${author.author} has both old-style (\"affiliation\" & \"affiliationURL\") and new style (\"affiliations\") affiliation information!`);\n } else {\n let newAffiliation = {\n \"name\": author.affiliation\n };\n if (author.affiliationURL) newAffiliation.url = author.affiliationURL;\n author.affiliations = [newAffiliation];\n }\n }\n return frontMatter\n }\n\n function parseFrontmatter(element) {\n const scriptTag = element.firstElementChild;\n if (scriptTag) {\n const type = scriptTag.getAttribute('type');\n if (type.split('/')[1] == 'json') {\n const content = scriptTag.textContent;\n const parsed = JSON.parse(content);\n return _moveLegacyAffiliationFormatIntoArray(parsed);\n } else {\n console.error('Distill only supports JSON frontmatter tags anymore; no more YAML.');\n }\n } else {\n console.error('You added a frontmatter tag but did not provide a script tag with front matter data in it. Please take a look at our templates.');\n }\n return {};\n }\n\n class FrontMatter$1 extends HTMLElement {\n\n static get is() { return 'd-front-matter'; }\n\n constructor() {\n super();\n\n const options = {childList: true, characterData: true, subtree: true};\n const observer = new MutationObserver( (entries) => {\n for (const entry of entries) {\n if (entry.target.nodeName === 'SCRIPT' || entry.type === 'characterData') {\n const data = parseFrontmatter(this);\n this.notify(data);\n }\n }\n });\n observer.observe(this, options);\n }\n\n notify(data) {\n const options = { detail: data, bubbles: true };\n const event = new CustomEvent('onFrontMatterChanged', options);\n document.dispatchEvent(event);\n }\n\n }\n\n // Copyright 2018 The Distill Template Authors\n //\n // Licensed under the Apache License, Version 2.0 (the \"License\");\n // you may not use this file except in compliance with the License.\n // You may obtain a copy of the License at\n //\n // http://www.apache.org/licenses/LICENSE-2.0\n //\n // Unless required by applicable law or agreed to in writing, software\n // distributed under the License is distributed on an \"AS IS\" BASIS,\n // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n // See the License for the specific language governing permissions and\n // limitations under the License.\n\n // no appendix -> add appendix\n // title in front, no h1 -> add it\n // no title in front, h1 -> read and put into frontMatter\n // footnote -> footnote list\n // break up bib\n // if citation, no bib-list -> add citation-list\n\n // if authors, no byline -> add byline\n\n function optionalComponents(dom, data) {\n const body = dom.body;\n const article = body.querySelector('d-article');\n\n // If we don't have an article tag, something weird is going on—giving up.\n if (!article) {\n console.warn('No d-article tag found; skipping adding optional components!');\n return;\n }\n\n let byline = dom.querySelector('d-byline');\n if (!byline) {\n if (data.authors) {\n byline = dom.createElement('d-byline');\n body.insertBefore(byline, article);\n } else {\n console.warn('No authors found in front matter; please add them before submission!');\n }\n }\n\n let title = dom.querySelector('d-title');\n if (!title) {\n title = dom.createElement('d-title');\n body.insertBefore(title, byline);\n }\n\n let h1 = title.querySelector('h1');\n if (!h1) {\n h1 = dom.createElement('h1');\n h1.textContent = data.title;\n title.insertBefore(h1, title.firstChild);\n }\n\n const hasPassword = typeof data.password !== 'undefined';\n let interstitial = body.querySelector('d-interstitial');\n if (hasPassword && !interstitial) {\n const inBrowser = typeof window !== 'undefined';\n const onLocalhost = inBrowser && window.location.hostname.includes('localhost');\n if (!inBrowser || !onLocalhost) {\n interstitial = dom.createElement('d-interstitial');\n interstitial.password = data.password;\n body.insertBefore(interstitial, body.firstChild);\n }\n } else if (!hasPassword && interstitial) {\n interstitial.parentElement.removeChild(this);\n }\n\n let appendix = dom.querySelector('d-appendix');\n if (!appendix) {\n appendix = dom.createElement('d-appendix');\n dom.body.appendChild(appendix);\n }\n\n let footnoteList = dom.querySelector('d-footnote-list');\n if (!footnoteList) {\n footnoteList = dom.createElement('d-footnote-list');\n appendix.appendChild(footnoteList);\n }\n\n let citationList = dom.querySelector('d-citation-list');\n if (!citationList) {\n citationList = dom.createElement('d-citation-list');\n appendix.appendChild(citationList);\n }\n\n }\n\n // Copyright 2018 The Distill Template Authors\n\n const frontMatter = new FrontMatter();\n\n const Controller = {\n frontMatter: frontMatter,\n waitingOn: {\n bibliography: [],\n citations: []\n },\n listeners: {\n onCiteKeyCreated(event) {\n const [citeTag, keys] = event.detail;\n\n // ensure we have citations\n if (!frontMatter.citationsCollected) {\n // console.debug('onCiteKeyCreated, but unresolved dependency (\"citations\"). Enqueing.');\n Controller.waitingOn.citations.push(() =>\n Controller.listeners.onCiteKeyCreated(event)\n );\n return;\n }\n\n // ensure we have a loaded bibliography\n if (!frontMatter.bibliographyParsed) {\n // console.debug('onCiteKeyCreated, but unresolved dependency (\"bibliography\"). Enqueing.');\n Controller.waitingOn.bibliography.push(() =>\n Controller.listeners.onCiteKeyCreated(event)\n );\n return;\n }\n\n const numbers = keys.map(key => frontMatter.citations.indexOf(key));\n citeTag.numbers = numbers;\n const entries = keys.map(key => frontMatter.bibliography.get(key));\n citeTag.entries = entries;\n },\n\n onCiteKeyChanged() {\n // const [citeTag, keys] = event.detail;\n\n // update citations\n frontMatter.citations = collect_citations();\n frontMatter.citationsCollected = true;\n for (const waitingCallback of Controller.waitingOn.citations.slice()) {\n waitingCallback();\n }\n\n // update bibliography\n const citationListTag = document.querySelector(\"d-citation-list\");\n const bibliographyEntries = new Map(\n frontMatter.citations.map(citationKey => {\n return [citationKey, frontMatter.bibliography.get(citationKey)];\n })\n );\n citationListTag.citations = bibliographyEntries;\n\n const citeTags = document.querySelectorAll(\"d-cite\");\n for (const citeTag of citeTags) {\n console.log(citeTag);\n const keys = citeTag.keys;\n const numbers = keys.map(key => frontMatter.citations.indexOf(key));\n citeTag.numbers = numbers;\n const entries = keys.map(key => frontMatter.bibliography.get(key));\n citeTag.entries = entries;\n }\n },\n\n onCiteKeyRemoved(event) {\n Controller.listeners.onCiteKeyChanged(event);\n },\n\n onBibliographyChanged(event) {\n const citationListTag = document.querySelector(\"d-citation-list\");\n\n const bibliography = event.detail;\n\n frontMatter.bibliography = bibliography;\n frontMatter.bibliographyParsed = true;\n for (const waitingCallback of Controller.waitingOn.bibliography.slice()) {\n waitingCallback();\n }\n\n // ensure we have citations\n if (!frontMatter.citationsCollected) {\n Controller.waitingOn.citations.push(function() {\n Controller.listeners.onBibliographyChanged({\n target: event.target,\n detail: event.detail\n });\n });\n return;\n }\n\n if (citationListTag.hasAttribute(\"distill-prerendered\")) {\n console.debug(\"Citation list was prerendered; not updating it.\");\n } else {\n const entries = new Map(\n frontMatter.citations.map(citationKey => {\n return [citationKey, frontMatter.bibliography.get(citationKey)];\n })\n );\n citationListTag.citations = entries;\n }\n },\n\n onFootnoteChanged() {\n // const footnote = event.detail;\n //TODO: optimize to only update current footnote\n const footnotesList = document.querySelector(\"d-footnote-list\");\n if (footnotesList) {\n const footnotes = document.querySelectorAll(\"d-footnote\");\n footnotesList.footnotes = footnotes;\n }\n },\n\n onFrontMatterChanged(event) {\n const data = event.detail;\n mergeFromYMLFrontmatter(frontMatter, data);\n\n const interstitial = document.querySelector(\"d-interstitial\");\n if (interstitial) {\n if (typeof frontMatter.password !== \"undefined\") {\n interstitial.password = frontMatter.password;\n } else {\n interstitial.parentElement.removeChild(interstitial);\n }\n }\n\n const prerendered = document.body.hasAttribute(\"distill-prerendered\");\n if (!prerendered && domContentLoaded()) {\n optionalComponents(document, frontMatter);\n\n const appendix = document.querySelector(\"distill-appendix\");\n if (appendix) {\n appendix.frontMatter = frontMatter;\n }\n\n const byline = document.querySelector(\"d-byline\");\n if (byline) {\n byline.frontMatter = frontMatter;\n }\n\n if (data.katex) {\n DMath.katexOptions = data.katex;\n }\n }\n },\n\n DOMContentLoaded() {\n if (Controller.loaded) {\n console.warn(\n \"Controller received DOMContentLoaded but was already loaded!\"\n );\n return;\n } else if (!domContentLoaded()) {\n console.warn(\n \"Controller received DOMContentLoaded at document.readyState: \" +\n document.readyState +\n \"!\"\n );\n return;\n } else {\n Controller.loaded = true;\n console.debug(\"Runlevel 4: Controller running DOMContentLoaded\");\n }\n\n const frontMatterTag = document.querySelector(\"d-front-matter\");\n if (frontMatterTag) {\n const data = parseFrontmatter(frontMatterTag);\n Controller.listeners.onFrontMatterChanged({ detail: data });\n }\n\n // Resolving \"citations\" dependency due to initial DOM load\n frontMatter.citations = collect_citations();\n frontMatter.citationsCollected = true;\n for (const waitingCallback of Controller.waitingOn.citations.slice()) {\n waitingCallback();\n }\n\n if (frontMatter.bibliographyParsed) {\n for (const waitingCallback of Controller.waitingOn.bibliography.slice()) {\n waitingCallback();\n }\n }\n\n const footnotesList = document.querySelector(\"d-footnote-list\");\n if (footnotesList) {\n const footnotes = document.querySelectorAll(\"d-footnote\");\n footnotesList.footnotes = footnotes;\n }\n }\n } // listeners\n }; // Controller\n\n var base = \"/*\\n * Copyright 2018 The Distill Template Authors\\n *\\n * Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n * you may not use this file except in compliance with the License.\\n * You may obtain a copy of the License at\\n *\\n * http://www.apache.org/licenses/LICENSE-2.0\\n *\\n * Unless required by applicable law or agreed to in writing, software\\n * distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n * See the License for the specific language governing permissions and\\n * limitations under the License.\\n */\\n\\nhtml {\\n font-size: 14px;\\n\\tline-height: 1.6em;\\n /* font-family: \\\"Libre Franklin\\\", \\\"Helvetica Neue\\\", sans-serif; */\\n font-family: -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, Oxygen, Ubuntu, Cantarell, \\\"Fira Sans\\\", \\\"Droid Sans\\\", \\\"Helvetica Neue\\\", Arial, sans-serif;\\n /*, \\\"Apple Color Emoji\\\", \\\"Segoe UI Emoji\\\", \\\"Segoe UI Symbol\\\";*/\\n text-size-adjust: 100%;\\n -ms-text-size-adjust: 100%;\\n -webkit-text-size-adjust: 100%;\\n}\\n\\n@media(min-width: 768px) {\\n html {\\n font-size: 16px;\\n }\\n}\\n\\nbody {\\n margin: 0;\\n}\\n\\na {\\n color: #004276;\\n}\\n\\nfigure {\\n margin: 0;\\n}\\n\\ntable {\\n\\tborder-collapse: collapse;\\n\\tborder-spacing: 0;\\n}\\n\\ntable th {\\n\\ttext-align: left;\\n}\\n\\ntable thead {\\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\\n}\\n\\ntable thead th {\\n padding-bottom: 0.5em;\\n}\\n\\ntable tbody :first-child td {\\n padding-top: 0.5em;\\n}\\n\\npre {\\n overflow: auto;\\n max-width: 100%;\\n}\\n\\np {\\n margin-top: 0;\\n margin-bottom: 1em;\\n}\\n\\nsup, sub {\\n vertical-align: baseline;\\n position: relative;\\n top: -0.4em;\\n line-height: 1em;\\n}\\n\\nsub {\\n top: 0.4em;\\n}\\n\\n.kicker,\\n.marker {\\n font-size: 15px;\\n font-weight: 600;\\n color: rgba(0, 0, 0, 0.5);\\n}\\n\\n\\n/* Headline */\\n\\n@media(min-width: 1024px) {\\n d-title h1 span {\\n display: block;\\n }\\n}\\n\\n/* Figure */\\n\\nfigure {\\n position: relative;\\n margin-bottom: 2.5em;\\n margin-top: 1.5em;\\n}\\n\\nfigcaption+figure {\\n\\n}\\n\\nfigure img {\\n width: 100%;\\n}\\n\\nfigure svg text,\\nfigure svg tspan {\\n}\\n\\nfigcaption,\\n.figcaption {\\n color: rgba(0, 0, 0, 0.6);\\n font-size: 12px;\\n line-height: 1.5em;\\n}\\n\\n@media(min-width: 1024px) {\\nfigcaption,\\n.figcaption {\\n font-size: 13px;\\n }\\n}\\n\\nfigure.external img {\\n background: white;\\n border: 1px solid rgba(0, 0, 0, 0.1);\\n box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1);\\n padding: 18px;\\n box-sizing: border-box;\\n}\\n\\nfigcaption a {\\n color: rgba(0, 0, 0, 0.6);\\n}\\n\\nfigcaption b,\\nfigcaption strong, {\\n font-weight: 600;\\n color: rgba(0, 0, 0, 1.0);\\n}\\n\";\n\n var layout = \"/*\\n * Copyright 2018 The Distill Template Authors\\n *\\n * Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n * you may not use this file except in compliance with the License.\\n * You may obtain a copy of the License at\\n *\\n * http://www.apache.org/licenses/LICENSE-2.0\\n *\\n * Unless required by applicable law or agreed to in writing, software\\n * distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n * See the License for the specific language governing permissions and\\n * limitations under the License.\\n */\\n\\n@supports not (display: grid) {\\n .base-grid,\\n distill-header,\\n d-title,\\n d-abstract,\\n d-article,\\n d-appendix,\\n distill-appendix,\\n d-byline,\\n d-footnote-list,\\n d-citation-list,\\n distill-footer {\\n display: block;\\n padding: 8px;\\n }\\n}\\n\\n.base-grid,\\ndistill-header,\\nd-title,\\nd-abstract,\\nd-article,\\nd-appendix,\\ndistill-appendix,\\nd-byline,\\nd-footnote-list,\\nd-citation-list,\\ndistill-footer {\\n display: grid;\\n justify-items: stretch;\\n grid-template-columns: [screen-start] 8px [page-start kicker-start text-start gutter-start middle-start] 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr [text-end page-end gutter-end kicker-end middle-end] 8px [screen-end];\\n grid-column-gap: 8px;\\n}\\n\\n.grid {\\n display: grid;\\n grid-column-gap: 8px;\\n}\\n\\n@media(min-width: 768px) {\\n .base-grid,\\n distill-header,\\n d-title,\\n d-abstract,\\n d-article,\\n d-appendix,\\n distill-appendix,\\n d-byline,\\n d-footnote-list,\\n d-citation-list,\\n distill-footer {\\n grid-template-columns: [screen-start] 1fr [page-start kicker-start middle-start text-start] 45px 45px 45px 45px 45px 45px 45px 45px [ kicker-end text-end gutter-start] 45px [middle-end] 45px [page-end gutter-end] 1fr [screen-end];\\n grid-column-gap: 16px;\\n }\\n\\n .grid {\\n grid-column-gap: 16px;\\n }\\n}\\n\\n@media(min-width: 1000px) {\\n .base-grid,\\n distill-header,\\n d-title,\\n d-abstract,\\n d-article,\\n d-appendix,\\n distill-appendix,\\n d-byline,\\n d-footnote-list,\\n d-citation-list,\\n distill-footer {\\n grid-template-columns: [screen-start] 1fr [page-start kicker-start] 50px [middle-start] 50px [text-start kicker-end] 50px 50px 50px 50px 50px 50px 50px 50px [text-end gutter-start] 50px [middle-end] 50px [page-end gutter-end] 1fr [screen-end];\\n grid-column-gap: 16px;\\n }\\n\\n .grid {\\n grid-column-gap: 16px;\\n }\\n}\\n\\n@media(min-width: 1180px) {\\n .base-grid,\\n distill-header,\\n d-title,\\n d-abstract,\\n d-article,\\n d-appendix,\\n distill-appendix,\\n d-byline,\\n d-footnote-list,\\n d-citation-list,\\n distill-footer {\\n grid-template-columns: [screen-start] 1fr [page-start kicker-start] 60px [middle-start] 60px [text-start kicker-end] 60px 60px 60px 60px 60px 60px 60px 60px [text-end gutter-start] 60px [middle-end] 60px [page-end gutter-end] 1fr [screen-end];\\n grid-column-gap: 32px;\\n }\\n\\n .grid {\\n grid-column-gap: 32px;\\n }\\n}\\n\\n\\n\\n\\n.base-grid {\\n grid-column: screen;\\n}\\n\\n/* .l-body,\\nd-article > * {\\n grid-column: text;\\n}\\n\\n.l-page,\\nd-title > *,\\nd-figure {\\n grid-column: page;\\n} */\\n\\n.l-gutter {\\n grid-column: gutter;\\n}\\n\\n.l-text,\\n.l-body {\\n grid-column: text;\\n}\\n\\n.l-page {\\n grid-column: page;\\n}\\n\\n.l-body-outset {\\n grid-column: middle;\\n}\\n\\n.l-page-outset {\\n grid-column: page;\\n}\\n\\n.l-screen {\\n grid-column: screen;\\n}\\n\\n.l-screen-inset {\\n grid-column: screen;\\n padding-left: 16px;\\n padding-left: 16px;\\n}\\n\\n\\n/* Aside */\\n\\nd-article aside {\\n grid-column: gutter;\\n font-size: 12px;\\n line-height: 1.6em;\\n color: rgba(0, 0, 0, 0.6)\\n}\\n\\n@media(min-width: 768px) {\\n aside {\\n grid-column: gutter;\\n }\\n\\n .side {\\n grid-column: gutter;\\n }\\n}\\n\";\n\n var print = \"/*\\n * Copyright 2018 The Distill Template Authors\\n *\\n * Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n * you may not use this file except in compliance with the License.\\n * You may obtain a copy of the License at\\n *\\n * http://www.apache.org/licenses/LICENSE-2.0\\n *\\n * Unless required by applicable law or agreed to in writing, software\\n * distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n * See the License for the specific language governing permissions and\\n * limitations under the License.\\n */\\n\\n@media print {\\n\\n @page {\\n size: 8in 11in;\\n @bottom-right {\\n content: counter(page) \\\" of \\\" counter(pages);\\n }\\n }\\n\\n html {\\n /* no general margins -- CSS Grid takes care of those */\\n }\\n\\n p, code {\\n page-break-inside: avoid;\\n }\\n\\n h2, h3 {\\n page-break-after: avoid;\\n }\\n\\n d-header {\\n visibility: hidden;\\n }\\n\\n d-footer {\\n display: none!important;\\n }\\n\\n}\\n\";\n\n var byline = \"/*\\n * Copyright 2018 The Distill Template Authors\\n *\\n * Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n * you may not use this file except in compliance with the License.\\n * You may obtain a copy of the License at\\n *\\n * http://www.apache.org/licenses/LICENSE-2.0\\n *\\n * Unless required by applicable law or agreed to in writing, software\\n * distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n * See the License for the specific language governing permissions and\\n * limitations under the License.\\n */\\n\\nd-byline {\\n contain: style;\\n overflow: hidden;\\n border-top: 1px solid rgba(0, 0, 0, 0.1);\\n font-size: 0.8rem;\\n line-height: 1.8em;\\n padding: 1.5rem 0;\\n min-height: 1.8em;\\n}\\n\\n\\nd-byline .byline {\\n grid-template-columns: 1fr 1fr;\\n grid-column: text;\\n}\\n\\n@media(min-width: 768px) {\\n d-byline .byline {\\n grid-template-columns: 1fr 1fr 1fr 1fr;\\n }\\n}\\n\\nd-byline .authors-affiliations {\\n grid-column-end: span 2;\\n grid-template-columns: 1fr 1fr;\\n margin-bottom: 1em;\\n}\\n\\n@media(min-width: 768px) {\\n d-byline .authors-affiliations {\\n margin-bottom: 0;\\n }\\n}\\n\\nd-byline h3 {\\n font-size: 0.6rem;\\n font-weight: 400;\\n color: rgba(0, 0, 0, 0.5);\\n margin: 0;\\n text-transform: uppercase;\\n}\\n\\nd-byline p {\\n margin: 0;\\n}\\n\\nd-byline a,\\nd-article d-byline a {\\n color: rgba(0, 0, 0, 0.8);\\n text-decoration: none;\\n border-bottom: none;\\n}\\n\\nd-article d-byline a:hover {\\n text-decoration: underline;\\n border-bottom: none;\\n}\\n\\nd-byline p.author {\\n font-weight: 500;\\n}\\n\\nd-byline .affiliations {\\n\\n}\\n\";\n\n var article = \"/*\\n * Copyright 2018 The Distill Template Authors\\n *\\n * Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n * you may not use this file except in compliance with the License.\\n * You may obtain a copy of the License at\\n *\\n * http://www.apache.org/licenses/LICENSE-2.0\\n *\\n * Unless required by applicable law or agreed to in writing, software\\n * distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n * See the License for the specific language governing permissions and\\n * limitations under the License.\\n */\\n\\nd-article {\\n contain: layout style;\\n overflow-x: hidden;\\n border-top: 1px solid rgba(0, 0, 0, 0.1);\\n padding-top: 2rem;\\n color: rgba(0, 0, 0, 0.8);\\n}\\n\\nd-article > * {\\n grid-column: text;\\n}\\n\\n@media(min-width: 768px) {\\n d-article {\\n font-size: 16px;\\n }\\n}\\n\\n@media(min-width: 1024px) {\\n d-article {\\n font-size: 1.06rem;\\n line-height: 1.7em;\\n }\\n}\\n\\n\\n/* H2 */\\n\\n\\nd-article .marker {\\n text-decoration: none;\\n border: none;\\n counter-reset: section;\\n grid-column: kicker;\\n line-height: 1.7em;\\n}\\n\\nd-article .marker:hover {\\n border: none;\\n}\\n\\nd-article .marker span {\\n padding: 0 3px 4px;\\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\\n position: relative;\\n top: 4px;\\n}\\n\\nd-article .marker:hover span {\\n color: rgba(0, 0, 0, 0.7);\\n border-bottom: 1px solid rgba(0, 0, 0, 0.7);\\n}\\n\\nd-article h2 {\\n font-weight: 600;\\n font-size: 24px;\\n line-height: 1.25em;\\n margin: 2rem 0 1.5rem 0;\\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\\n padding-bottom: 1rem;\\n}\\n\\n@media(min-width: 1024px) {\\n d-article h2 {\\n font-size: 36px;\\n }\\n}\\n\\n/* H3 */\\n\\nd-article h3 {\\n font-weight: 700;\\n font-size: 18px;\\n line-height: 1.4em;\\n margin-bottom: 1em;\\n margin-top: 2em;\\n}\\n\\n@media(min-width: 1024px) {\\n d-article h3 {\\n font-size: 20px;\\n }\\n}\\n\\n/* H4 */\\n\\nd-article h4 {\\n font-weight: 600;\\n text-transform: uppercase;\\n font-size: 14px;\\n line-height: 1.4em;\\n}\\n\\nd-article a {\\n color: inherit;\\n}\\n\\nd-article p,\\nd-article ul,\\nd-article ol,\\nd-article blockquote {\\n margin-top: 0;\\n margin-bottom: 1em;\\n margin-left: 0;\\n margin-right: 0;\\n}\\n\\nd-article blockquote {\\n border-left: 2px solid rgba(0, 0, 0, 0.2);\\n padding-left: 2em;\\n font-style: italic;\\n color: rgba(0, 0, 0, 0.6);\\n}\\n\\nd-article a {\\n border-bottom: 1px solid rgba(0, 0, 0, 0.4);\\n text-decoration: none;\\n}\\n\\nd-article a:hover {\\n border-bottom: 1px solid rgba(0, 0, 0, 0.8);\\n}\\n\\nd-article .link {\\n text-decoration: underline;\\n cursor: pointer;\\n}\\n\\nd-article ul,\\nd-article ol {\\n padding-left: 24px;\\n}\\n\\nd-article li {\\n margin-bottom: 1em;\\n margin-left: 0;\\n padding-left: 0;\\n}\\n\\nd-article li:last-child {\\n margin-bottom: 0;\\n}\\n\\nd-article pre {\\n font-size: 14px;\\n margin-bottom: 20px;\\n}\\n\\nd-article hr {\\n grid-column: screen;\\n width: 100%;\\n border: none;\\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\\n margin-top: 60px;\\n margin-bottom: 60px;\\n}\\n\\nd-article section {\\n margin-top: 60px;\\n margin-bottom: 60px;\\n}\\n\\nd-article span.equation-mimic {\\n font-family: georgia;\\n font-size: 115%;\\n font-style: italic;\\n}\\n\\nd-article > d-code,\\nd-article section > d-code {\\n display: block;\\n}\\n\\nd-article > d-math[block],\\nd-article section > d-math[block] {\\n display: block;\\n}\\n\\n@media (max-width: 768px) {\\n d-article > d-code,\\n d-article section > d-code,\\n d-article > d-math[block],\\n d-article section > d-math[block] {\\n overflow-x: scroll;\\n -ms-overflow-style: none; // IE 10+\\n overflow: -moz-scrollbars-none; // Firefox\\n }\\n\\n d-article > d-code::-webkit-scrollbar,\\n d-article section > d-code::-webkit-scrollbar,\\n d-article > d-math[block]::-webkit-scrollbar,\\n d-article section > d-math[block]::-webkit-scrollbar {\\n display: none; // Safari and Chrome\\n }\\n}\\n\\nd-article .citation {\\n color: #668;\\n cursor: pointer;\\n}\\n\\nd-include {\\n width: auto;\\n display: block;\\n}\\n\\nd-figure {\\n contain: layout style;\\n}\\n\\n/* KaTeX */\\n\\n.katex, .katex-prerendered {\\n contain: style;\\n display: inline-block;\\n}\\n\\n/* Tables */\\n\\nd-article table {\\n border-collapse: collapse;\\n margin-bottom: 1.5rem;\\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\\n}\\n\\nd-article table th {\\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\\n}\\n\\nd-article table td {\\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\\n}\\n\\nd-article table tr:last-of-type td {\\n border-bottom: none;\\n}\\n\\nd-article table th,\\nd-article table td {\\n font-size: 15px;\\n padding: 2px 8px;\\n}\\n\\nd-article table tbody :first-child td {\\n padding-top: 2px;\\n}\\n\";\n\n var title = \"/*\\n * Copyright 2018 The Distill Template Authors\\n *\\n * Licensed under the Apache License, Version 2.0 (the \\\"License\\\");\\n * you may not use this file except in compliance with the License.\\n * You may obtain a copy of the License at\\n *\\n * http://www.apache.org/licenses/LICENSE-2.0\\n *\\n * Unless required by applicable law or agreed to in writing, software\\n * distributed under the License is distributed on an \\\"AS IS\\\" BASIS,\\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\n * See the License for the specific language governing permissions and\\n * limitations under the License.\\n */\\n\\nd-title {\\n padding: 2rem 0 1.5rem;\\n contain: layout style;\\n overflow-x: hidden;\\n}\\n\\n@media(min-width: 768px) {\\n d-title {\\n padding: 4rem 0 1.5rem;\\n }\\n}\\n\\nd-title h1 {\\n grid-column: text;\\n font-size: 40px;\\n font-weight: 700;\\n line-height: 1.1em;\\n margin: 0 0 0.5rem;\\n}\\n\\n@media(min-width: 768px) {\\n d-title h1 {\\n font-size: 50px;\\n }\\n}\\n\\nd-title p {\\n font-weight: 300;\\n font-size: 1.2rem;\\n line-height: 1.55em;\\n grid-column: text;\\n}\\n\\nd-title .status {\\n margin-top: 0px;\\n font-size: 12px;\\n color: #009688;\\n opacity: 0.8;\\n grid-column: kicker;\\n}\\n\\nd-title .status span {\\n line-height: 1;\\n display: inline-block;\\n padding: 6px 0;\\n border-bottom: 1px solid #80cbc4;\\n font-size: 11px;\\n text-transform: uppercase;\\n}\\n\";\n\n // Copyright 2018 The Distill Template Authors\n\n const styles = base + layout + title + byline + article + math + print;\n\n function makeStyleTag(dom) {\n\n const styleTagId = 'distill-prerendered-styles';\n const prerenderedTag = dom.getElementById(styleTagId);\n if (!prerenderedTag) {\n const styleTag = dom.createElement('style');\n styleTag.id = styleTagId;\n styleTag.type = 'text/css';\n const cssTextTag = dom.createTextNode(styles);\n styleTag.appendChild(cssTextTag);\n const firstScriptTag = dom.head.querySelector('script');\n dom.head.insertBefore(styleTag, firstScriptTag);\n }\n\n }\n\n // Copyright 2018 The Distill Template Authors\n //\n // Licensed under the Apache License, Version 2.0 (the \"License\");\n // you may not use this file except in compliance with the License.\n // You may obtain a copy of the License at\n //\n // http://www.apache.org/licenses/LICENSE-2.0\n //\n // Unless required by applicable law or agreed to in writing, software\n // distributed under the License is distributed on an \"AS IS\" BASIS,\n // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n // See the License for the specific language governing permissions and\n // limitations under the License.\n\n function addPolyfill(polyfill, polyfillLoadedCallback) {\n console.debug('Runlevel 0: Polyfill required: ' + polyfill.name);\n const script = document.createElement('script');\n script.src = polyfill.url;\n script.async = false;\n if (polyfillLoadedCallback) {\n script.onload = function() { polyfillLoadedCallback(polyfill); };\n }\n script.onerror = function() {\n new Error('Runlevel 0: Polyfills failed to load script ' + polyfill.name);\n };\n document.head.appendChild(script);\n }\n\n const polyfills = [\n {\n name: 'WebComponents',\n support: function() {\n return 'customElements' in window &&\n 'attachShadow' in Element.prototype &&\n 'getRootNode' in Element.prototype &&\n 'content' in document.createElement('template') &&\n 'Promise' in window &&\n 'from' in Array;\n },\n url: 'https://distill.pub/third-party/polyfills/webcomponents-lite.js'\n }, {\n name: 'IntersectionObserver',\n support: function() {\n return 'IntersectionObserver' in window &&\n 'IntersectionObserverEntry' in window;\n },\n url: 'https://distill.pub/third-party/polyfills/intersection-observer.js'\n },\n ];\n\n class Polyfills {\n\n static browserSupportsAllFeatures() {\n return polyfills.every((poly) => poly.support());\n }\n\n static load(callback) {\n // Define an intermediate callback that checks if all is loaded.\n const polyfillLoaded = function(polyfill) {\n polyfill.loaded = true;\n console.debug('Runlevel 0: Polyfill has finished loading: ' + polyfill.name);\n // console.debug(window[polyfill.name]);\n if (Polyfills.neededPolyfills.every((poly) => poly.loaded)) {\n console.debug('Runlevel 0: All required polyfills have finished loading.');\n console.debug('Runlevel 0->1.');\n window.distillRunlevel = 1;\n callback();\n }\n };\n // Add polyfill script tags\n for (const polyfill of Polyfills.neededPolyfills) {\n addPolyfill(polyfill, polyfillLoaded);\n }\n }\n\n static get neededPolyfills() {\n if (!Polyfills._neededPolyfills) {\n Polyfills._neededPolyfills = polyfills.filter((poly) => !poly.support());\n }\n return Polyfills._neededPolyfills;\n }\n }\n\n // Copyright 2018 The Distill Template Authors\n //\n // Licensed under the Apache License, Version 2.0 (the \"License\");\n // you may not use this file except in compliance with the License.\n // You may obtain a copy of the License at\n //\n // http://www.apache.org/licenses/LICENSE-2.0\n //\n // Unless required by applicable law or agreed to in writing, software\n // distributed under the License is distributed on an \"AS IS\" BASIS,\n // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n // See the License for the specific language governing permissions and\n // limitations under the License.\n\n // const marginSmall = 16;\n // const marginLarge = 3 * marginSmall;\n // const margin = marginSmall + marginLarge;\n // const gutter = marginSmall;\n // const outsetAmount = margin / 2;\n // const numCols = 4;\n // const numGutters = numCols - 1;\n // const columnWidth = (768 - 2 * marginLarge - numGutters * gutter) / numCols;\n //\n // const screenwidth = 768;\n // const pageWidth = screenwidth - 2 * marginLarge;\n // const bodyWidth = pageWidth - columnWidth - gutter;\n\n function body(selector) {\n return `${selector} {\n grid-column: left / text;\n }\n `;\n }\n\n // Copyright 2018 The Distill Template Authors\n\n const T$1 = Template('d-abstract', `\n\n\n\n ${entries\n .map(hover_cite)\n .map(html => `
`;\n }\n }\n\n // Copyright 2018 The Distill Template Authors\n\n const styles$1 = `\nd-citation-list {\n contain: style;\n}\n\nd-citation-list .references {\n grid-column: text;\n}\n\nd-citation-list .references .title {\n font-weight: 500;\n}\n`;\n\n function renderCitationList(element, entries, dom=document) {\n if (entries.size > 0) {\n element.style.display = '';\n let list = element.querySelector('.references');\n if (list) {\n list.innerHTML = '';\n } else {\n const stylesTag = dom.createElement('style');\n stylesTag.innerHTML = styles$1;\n element.appendChild(stylesTag);\n\n const heading = dom.createElement('h3');\n heading.id = 'references';\n heading.textContent = 'References';\n element.appendChild(heading);\n\n list = dom.createElement('ol');\n list.id = 'references-list';\n list.className = 'references';\n element.appendChild(list);\n }\n\n for (const [key, entry] of entries) {\n const listItem = dom.createElement('li');\n listItem.id = key;\n listItem.innerHTML = bibliography_cite(entry);\n list.appendChild(listItem);\n }\n } else {\n element.style.display = 'none';\n }\n }\n\n class CitationList extends HTMLElement {\n\n static get is() { return 'd-citation-list'; }\n\n connectedCallback() {\n if (!this.hasAttribute('distill-prerendered')) {\n this.style.display = 'none';\n }\n }\n\n set citations(citations) {\n renderCitationList(this, citations);\n }\n\n }\n\n var prism = createCommonjsModule(function (module) {\n /* **********************************************\n Begin prism-core.js\n ********************************************** */\n\n var _self = (typeof window !== 'undefined')\n \t? window // if in browser\n \t: (\n \t\t(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)\n \t\t? self // if in worker\n \t\t: {} // if in node js\n \t);\n\n /**\n * Prism: Lightweight, robust, elegant syntax highlighting\n * MIT license http://www.opensource.org/licenses/mit-license.php/\n * @author Lea Verou http://lea.verou.me\n */\n\n var Prism = (function (_self){\n\n // Private helper vars\n var lang = /\\blang(?:uage)?-([\\w-]+)\\b/i;\n var uniqueId = 0;\n\n\n var _ = {\n \tmanual: _self.Prism && _self.Prism.manual,\n \tdisableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,\n \tutil: {\n \t\tencode: function encode(tokens) {\n \t\t\tif (tokens instanceof Token) {\n \t\t\t\treturn new Token(tokens.type, encode(tokens.content), tokens.alias);\n \t\t\t} else if (Array.isArray(tokens)) {\n \t\t\t\treturn tokens.map(encode);\n \t\t\t} else {\n \t\t\t\treturn tokens.replace(/&/g, '&').replace(/\n\n`);\n\n class Code extends Mutating(T$4(HTMLElement)) {\n\n renderContent() {\n\n // check if language can be highlighted\n this.languageName = this.getAttribute('language');\n if (!this.languageName) {\n console.warn('You need to provide a language attribute to your
Footnotes
\n\n`, false);\n\n class FootnoteList extends T$6(HTMLElement) {\n\n connectedCallback() {\n super.connectedCallback();\n\n this.list = this.root.querySelector('ol');\n // footnotes list is initially hidden\n this.root.style.display = 'none';\n // look through document and register existing footnotes\n // Store.subscribeTo('footnotes', (footnote) => {\n // this.renderFootnote(footnote);\n // });\n }\n\n // TODO: could optimize this to accept individual footnotes?\n set footnotes(footnotes) {\n this.list.innerHTML = '';\n if (footnotes.length) {\n // ensure footnote list is visible\n this.root.style.display = '';\n\n for (const footnote of footnotes) {\n // construct and append list item to show footnote\n const listItem = document.createElement('li');\n listItem.id = footnote.id + '-listing';\n listItem.innerHTML = footnote.innerHTML;\n\n const backlink = document.createElement('a');\n backlink.setAttribute('class', 'footnote-backlink');\n backlink.setAttribute('target', '_self');\n backlink.textContent = '[↩]';\n backlink.href = '#' + footnote.id;\n\n listItem.appendChild(backlink);\n this.list.appendChild(listItem);\n }\n } else {\n // ensure footnote list is invisible\n this.root.style.display = 'none';\n }\n }\n\n }\n\n // Copyright 2018 The Distill Template Authors\n\n const T$7 = Template('d-hover-box', `\n\n\n
Table of contents
\n `;\n\n for (const el of headings) {\n // should element be included in TOC?\n const isInTitle = el.parentElement.tagName == 'D-TITLE';\n const isException = el.getAttribute('no-toc');\n if (isInTitle || isException) continue;\n // create TOC entry\n const title = el.textContent;\n const link = '#' + el.getAttribute('id');\n\n let newLine = '
';\n element.innerHTML = ToC;\n }\n\n // Copyright 2018 The Distill Template Authors\n //\n // Licensed under the Apache License, Version 2.0 (the \"License\");\n // you may not use this file except in compliance with the License.\n // You may obtain a copy of the License at\n //\n // http://www.apache.org/licenses/LICENSE-2.0\n //\n // Unless required by applicable law or agreed to in writing, software\n // distributed under the License is distributed on an \"AS IS\" BASIS,\n // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n // See the License for the specific language governing permissions and\n // limitations under the License.\n\n // Figure\n //\n // d-figure provides a state-machine of visibility events:\n //\n // scroll out of view\n // +----------------+\n // *do work here* | |\n // +----------------+ +-+---------+ +-v---------+\n // | ready +----> onscreen | | offscreen |\n // +----------------+ +---------^-+ +---------+-+\n // | |\n // +----------------+\n // scroll into view\n //\n\n class Figure extends HTMLElement {\n\n static get is() { return 'd-figure'; }\n\n static get readyQueue() {\n if (!Figure._readyQueue) {\n Figure._readyQueue = [];\n }\n return Figure._readyQueue;\n }\n\n static addToReadyQueue(figure) {\n if (Figure.readyQueue.indexOf(figure) === -1) {\n Figure.readyQueue.push(figure);\n Figure.runReadyQueue();\n }\n }\n\n static runReadyQueue() {\n // console.log(\"Checking to run readyQueue, length: \" + Figure.readyQueue.length + \", scrolling: \" + Figure.isScrolling);\n // if (Figure.isScrolling) return;\n // console.log(\"Running ready Queue\");\n const figure = Figure.readyQueue\n .sort((a,b) => a._seenOnScreen - b._seenOnScreen )\n .filter((figure) => !figure._ready)\n .pop();\n if (figure) {\n figure.ready();\n requestAnimationFrame(Figure.runReadyQueue);\n }\n\n }\n\n constructor() {\n super();\n // debugger\n this._ready = false;\n this._onscreen = false;\n this._offscreen = true;\n }\n\n connectedCallback() {\n this.loadsWhileScrolling = this.hasAttribute('loadsWhileScrolling');\n Figure.marginObserver.observe(this);\n Figure.directObserver.observe(this);\n }\n\n disconnectedCallback() {\n Figure.marginObserver.unobserve(this);\n Figure.directObserver.unobserve(this);\n }\n\n // We use two separate observers:\n // One with an extra 1000px margin to warn if the viewpoint gets close,\n // And one for the actual on/off screen events\n\n static get marginObserver() {\n if (!Figure._marginObserver) {\n // if (!('IntersectionObserver' in window)) {\n // throw new Error('no interscetionobbserver!');\n // }\n const viewportHeight = window.innerHeight;\n const margin = Math.floor(2 * viewportHeight);\n const options = {rootMargin: margin + 'px 0px ' + margin + 'px 0px', threshold: 0.01};\n const callback = Figure.didObserveMarginIntersection;\n const observer = new IntersectionObserver(callback, options);\n Figure._marginObserver = observer;\n }\n return Figure._marginObserver;\n }\n\n static didObserveMarginIntersection(entries) {\n for (const entry of entries) {\n const figure = entry.target;\n if (entry.isIntersecting && !figure._ready) {\n Figure.addToReadyQueue(figure);\n }\n }\n }\n\n static get directObserver() {\n if (!Figure._directObserver) {\n Figure._directObserver = new IntersectionObserver(\n Figure.didObserveDirectIntersection, {\n rootMargin: '0px', threshold: [0, 1.0],\n }\n );\n }\n return Figure._directObserver;\n }\n\n static didObserveDirectIntersection(entries) {\n for (const entry of entries) {\n const figure = entry.target;\n if (entry.isIntersecting) {\n figure._seenOnScreen = new Date();\n // if (!figure._ready) { figure.ready(); }\n if (figure._offscreen) { figure.onscreen(); }\n } else {\n if (figure._onscreen) { figure.offscreen(); }\n }\n }\n }\n\n // Notify listeners that registered late, too:\n\n addEventListener(eventName, callback) {\n super.addEventListener(eventName, callback);\n // if we had already dispatched something while presumingly no one was listening, we do so again\n // debugger\n if (eventName === 'ready') {\n if (Figure.readyQueue.indexOf(this) !== -1) {\n this._ready = false;\n Figure.runReadyQueue();\n }\n }\n if (eventName === 'onscreen') {\n this.onscreen();\n }\n }\n\n // Custom Events\n\n ready() {\n // debugger\n this._ready = true;\n Figure.marginObserver.unobserve(this);\n const event = new CustomEvent('ready');\n this.dispatchEvent(event);\n }\n\n onscreen() {\n this._onscreen = true;\n this._offscreen = false;\n const event = new CustomEvent('onscreen');\n this.dispatchEvent(event);\n }\n\n offscreen() {\n this._onscreen = false;\n this._offscreen = true;\n const event = new CustomEvent('offscreen');\n this.dispatchEvent(event);\n }\n\n }\n\n if (typeof window !== 'undefined') {\n\n Figure.isScrolling = false;\n let timeout;\n const resetTimer = () => {\n Figure.isScrolling = true;\n clearTimeout(timeout);\n timeout = setTimeout(() => {\n Figure.isScrolling = false;\n Figure.runReadyQueue();\n }, 500);\n };\n window.addEventListener('scroll', resetTimer, true);\n\n }\n\n // Copyright 2018 The Distill Template Authors\n\n // This overlay is not secure.\n // It is only meant as a social deterrent.\n\n const productionHostname = 'distill.pub';\n const T$9 = Template('d-interstitial', `\n\n\n \n`);\n\n class Interstitial extends T$9(HTMLElement) {\n\n connectedCallback() {\n if (this.shouldRemoveSelf()) {\n this.parentElement.removeChild(this);\n } else {\n const passwordInput = this.root.querySelector('#interstitial-password-input');\n passwordInput.oninput = (event) => this.passwordChanged(event);\n }\n }\n\n passwordChanged(event) {\n const entered = event.target.value;\n if (entered === this.password) {\n console.log('Correct password entered.');\n this.parentElement.removeChild(this);\n if (typeof(Storage) !== 'undefined') {\n console.log('Saved that correct password was entered.');\n localStorage.setItem(this.localStorageIdentifier(), 'true');\n }\n }\n }\n\n shouldRemoveSelf() {\n // should never be visible in production\n if (window && window.location.hostname === productionHostname) {\n console.warn('Interstitial found on production, hiding it.');\n return true\n }\n // should only have to enter password once\n if (typeof(Storage) !== 'undefined') {\n if (localStorage.getItem(this.localStorageIdentifier()) === 'true') {\n console.log('Loaded that correct password was entered before; skipping interstitial.');\n return true;\n }\n }\n // otherwise, leave visible\n return false;\n }\n\n localStorageIdentifier() {\n const prefix = 'distill-drafts';\n const suffix = 'interstitial-password-correct';\n return prefix + (window ? window.location.pathname : '-') + suffix\n }\n\n }\n\n function ascending(a, b) {\n return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;\n }\n\n function bisector(compare) {\n if (compare.length === 1) compare = ascendingComparator(compare);\n return {\n left: function(a, x, lo, hi) {\n if (lo == null) lo = 0;\n if (hi == null) hi = a.length;\n while (lo < hi) {\n var mid = lo + hi >>> 1;\n if (compare(a[mid], x) < 0) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n },\n right: function(a, x, lo, hi) {\n if (lo == null) lo = 0;\n if (hi == null) hi = a.length;\n while (lo < hi) {\n var mid = lo + hi >>> 1;\n if (compare(a[mid], x) > 0) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n }\n };\n }\n\n function ascendingComparator(f) {\n return function(d, x) {\n return ascending(f(d), x);\n };\n }\n\n var ascendingBisect = bisector(ascending);\n var bisectRight = ascendingBisect.right;\n\n function range(start, stop, step) {\n start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;\n\n var i = -1,\n n = Math.max(0, Math.ceil((stop - start) / step)) | 0,\n range = new Array(n);\n\n while (++i < n) {\n range[i] = start + i * step;\n }\n\n return range;\n }\n\n var e10 = Math.sqrt(50),\n e5 = Math.sqrt(10),\n e2 = Math.sqrt(2);\n\n function ticks(start, stop, count) {\n var reverse,\n i = -1,\n n,\n ticks,\n step;\n\n stop = +stop, start = +start, count = +count;\n if (start === stop && count > 0) return [start];\n if (reverse = stop < start) n = start, start = stop, stop = n;\n if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step)) return [];\n\n if (step > 0) {\n start = Math.ceil(start / step);\n stop = Math.floor(stop / step);\n ticks = new Array(n = Math.ceil(stop - start + 1));\n while (++i < n) ticks[i] = (start + i) * step;\n } else {\n start = Math.floor(start * step);\n stop = Math.ceil(stop * step);\n ticks = new Array(n = Math.ceil(start - stop + 1));\n while (++i < n) ticks[i] = (start - i) / step;\n }\n\n if (reverse) ticks.reverse();\n\n return ticks;\n }\n\n function tickIncrement(start, stop, count) {\n var step = (stop - start) / Math.max(0, count),\n power = Math.floor(Math.log(step) / Math.LN10),\n error = step / Math.pow(10, power);\n return power >= 0\n ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * Math.pow(10, power)\n : -Math.pow(10, -power) / (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1);\n }\n\n function tickStep(start, stop, count) {\n var step0 = Math.abs(stop - start) / Math.max(0, count),\n step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),\n error = step0 / step1;\n if (error >= e10) step1 *= 10;\n else if (error >= e5) step1 *= 5;\n else if (error >= e2) step1 *= 2;\n return stop < start ? -step1 : step1;\n }\n\n function initRange(domain, range) {\n switch (arguments.length) {\n case 0: break;\n case 1: this.range(domain); break;\n default: this.range(range).domain(domain); break;\n }\n return this;\n }\n\n function define(constructor, factory, prototype) {\n constructor.prototype = factory.prototype = prototype;\n prototype.constructor = constructor;\n }\n\n function extend(parent, definition) {\n var prototype = Object.create(parent.prototype);\n for (var key in definition) prototype[key] = definition[key];\n return prototype;\n }\n\n function Color() {}\n\n var darker = 0.7;\n var brighter = 1 / darker;\n\n var reI = \"\\\\s*([+-]?\\\\d+)\\\\s*\",\n reN = \"\\\\s*([+-]?\\\\d*\\\\.?\\\\d+(?:[eE][+-]?\\\\d+)?)\\\\s*\",\n reP = \"\\\\s*([+-]?\\\\d*\\\\.?\\\\d+(?:[eE][+-]?\\\\d+)?)%\\\\s*\",\n reHex = /^#([0-9a-f]{3,8})$/,\n reRgbInteger = new RegExp(\"^rgb\\\\(\" + [reI, reI, reI] + \"\\\\)$\"),\n reRgbPercent = new RegExp(\"^rgb\\\\(\" + [reP, reP, reP] + \"\\\\)$\"),\n reRgbaInteger = new RegExp(\"^rgba\\\\(\" + [reI, reI, reI, reN] + \"\\\\)$\"),\n reRgbaPercent = new RegExp(\"^rgba\\\\(\" + [reP, reP, reP, reN] + \"\\\\)$\"),\n reHslPercent = new RegExp(\"^hsl\\\\(\" + [reN, reP, reP] + \"\\\\)$\"),\n reHslaPercent = new RegExp(\"^hsla\\\\(\" + [reN, reP, reP, reN] + \"\\\\)$\");\n\n var named = {\n aliceblue: 0xf0f8ff,\n antiquewhite: 0xfaebd7,\n aqua: 0x00ffff,\n aquamarine: 0x7fffd4,\n azure: 0xf0ffff,\n beige: 0xf5f5dc,\n bisque: 0xffe4c4,\n black: 0x000000,\n blanchedalmond: 0xffebcd,\n blue: 0x0000ff,\n blueviolet: 0x8a2be2,\n brown: 0xa52a2a,\n burlywood: 0xdeb887,\n cadetblue: 0x5f9ea0,\n chartreuse: 0x7fff00,\n chocolate: 0xd2691e,\n coral: 0xff7f50,\n cornflowerblue: 0x6495ed,\n cornsilk: 0xfff8dc,\n crimson: 0xdc143c,\n cyan: 0x00ffff,\n darkblue: 0x00008b,\n darkcyan: 0x008b8b,\n darkgoldenrod: 0xb8860b,\n darkgray: 0xa9a9a9,\n darkgreen: 0x006400,\n darkgrey: 0xa9a9a9,\n darkkhaki: 0xbdb76b,\n darkmagenta: 0x8b008b,\n darkolivegreen: 0x556b2f,\n darkorange: 0xff8c00,\n darkorchid: 0x9932cc,\n darkred: 0x8b0000,\n darksalmon: 0xe9967a,\n darkseagreen: 0x8fbc8f,\n darkslateblue: 0x483d8b,\n darkslategray: 0x2f4f4f,\n darkslategrey: 0x2f4f4f,\n darkturquoise: 0x00ced1,\n darkviolet: 0x9400d3,\n deeppink: 0xff1493,\n deepskyblue: 0x00bfff,\n dimgray: 0x696969,\n dimgrey: 0x696969,\n dodgerblue: 0x1e90ff,\n firebrick: 0xb22222,\n floralwhite: 0xfffaf0,\n forestgreen: 0x228b22,\n fuchsia: 0xff00ff,\n gainsboro: 0xdcdcdc,\n ghostwhite: 0xf8f8ff,\n gold: 0xffd700,\n goldenrod: 0xdaa520,\n gray: 0x808080,\n green: 0x008000,\n greenyellow: 0xadff2f,\n grey: 0x808080,\n honeydew: 0xf0fff0,\n hotpink: 0xff69b4,\n indianred: 0xcd5c5c,\n indigo: 0x4b0082,\n ivory: 0xfffff0,\n khaki: 0xf0e68c,\n lavender: 0xe6e6fa,\n lavenderblush: 0xfff0f5,\n lawngreen: 0x7cfc00,\n lemonchiffon: 0xfffacd,\n lightblue: 0xadd8e6,\n lightcoral: 0xf08080,\n lightcyan: 0xe0ffff,\n lightgoldenrodyellow: 0xfafad2,\n lightgray: 0xd3d3d3,\n lightgreen: 0x90ee90,\n lightgrey: 0xd3d3d3,\n lightpink: 0xffb6c1,\n lightsalmon: 0xffa07a,\n lightseagreen: 0x20b2aa,\n lightskyblue: 0x87cefa,\n lightslategray: 0x778899,\n lightslategrey: 0x778899,\n lightsteelblue: 0xb0c4de,\n lightyellow: 0xffffe0,\n lime: 0x00ff00,\n limegreen: 0x32cd32,\n linen: 0xfaf0e6,\n magenta: 0xff00ff,\n maroon: 0x800000,\n mediumaquamarine: 0x66cdaa,\n mediumblue: 0x0000cd,\n mediumorchid: 0xba55d3,\n mediumpurple: 0x9370db,\n mediumseagreen: 0x3cb371,\n mediumslateblue: 0x7b68ee,\n mediumspringgreen: 0x00fa9a,\n mediumturquoise: 0x48d1cc,\n mediumvioletred: 0xc71585,\n midnightblue: 0x191970,\n mintcream: 0xf5fffa,\n mistyrose: 0xffe4e1,\n moccasin: 0xffe4b5,\n navajowhite: 0xffdead,\n navy: 0x000080,\n oldlace: 0xfdf5e6,\n olive: 0x808000,\n olivedrab: 0x6b8e23,\n orange: 0xffa500,\n orangered: 0xff4500,\n orchid: 0xda70d6,\n palegoldenrod: 0xeee8aa,\n palegreen: 0x98fb98,\n paleturquoise: 0xafeeee,\n palevioletred: 0xdb7093,\n papayawhip: 0xffefd5,\n peachpuff: 0xffdab9,\n peru: 0xcd853f,\n pink: 0xffc0cb,\n plum: 0xdda0dd,\n powderblue: 0xb0e0e6,\n purple: 0x800080,\n rebeccapurple: 0x663399,\n red: 0xff0000,\n rosybrown: 0xbc8f8f,\n royalblue: 0x4169e1,\n saddlebrown: 0x8b4513,\n salmon: 0xfa8072,\n sandybrown: 0xf4a460,\n seagreen: 0x2e8b57,\n seashell: 0xfff5ee,\n sienna: 0xa0522d,\n silver: 0xc0c0c0,\n skyblue: 0x87ceeb,\n slateblue: 0x6a5acd,\n slategray: 0x708090,\n slategrey: 0x708090,\n snow: 0xfffafa,\n springgreen: 0x00ff7f,\n steelblue: 0x4682b4,\n tan: 0xd2b48c,\n teal: 0x008080,\n thistle: 0xd8bfd8,\n tomato: 0xff6347,\n turquoise: 0x40e0d0,\n violet: 0xee82ee,\n wheat: 0xf5deb3,\n white: 0xffffff,\n whitesmoke: 0xf5f5f5,\n yellow: 0xffff00,\n yellowgreen: 0x9acd32\n };\n\n define(Color, color, {\n copy: function(channels) {\n return Object.assign(new this.constructor, this, channels);\n },\n displayable: function() {\n return this.rgb().displayable();\n },\n hex: color_formatHex, // Deprecated! Use color.formatHex.\n formatHex: color_formatHex,\n formatHsl: color_formatHsl,\n formatRgb: color_formatRgb,\n toString: color_formatRgb\n });\n\n function color_formatHex() {\n return this.rgb().formatHex();\n }\n\n function color_formatHsl() {\n return hslConvert(this).formatHsl();\n }\n\n function color_formatRgb() {\n return this.rgb().formatRgb();\n }\n\n function color(format) {\n var m, l;\n format = (format + \"\").trim().toLowerCase();\n return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000\n : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00\n : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000\n : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000\n : null) // invalid hex\n : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)\n : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%)\n : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)\n : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1)\n : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)\n : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)\n : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins\n : format === \"transparent\" ? new Rgb(NaN, NaN, NaN, 0)\n : null;\n }\n\n function rgbn(n) {\n return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1);\n }\n\n function rgba(r, g, b, a) {\n if (a <= 0) r = g = b = NaN;\n return new Rgb(r, g, b, a);\n }\n\n function rgbConvert(o) {\n if (!(o instanceof Color)) o = color(o);\n if (!o) return new Rgb;\n o = o.rgb();\n return new Rgb(o.r, o.g, o.b, o.opacity);\n }\n\n function rgb(r, g, b, opacity) {\n return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity);\n }\n\n function Rgb(r, g, b, opacity) {\n this.r = +r;\n this.g = +g;\n this.b = +b;\n this.opacity = +opacity;\n }\n\n define(Rgb, rgb, extend(Color, {\n brighter: function(k) {\n k = k == null ? brighter : Math.pow(brighter, k);\n return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);\n },\n darker: function(k) {\n k = k == null ? darker : Math.pow(darker, k);\n return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);\n },\n rgb: function() {\n return this;\n },\n displayable: function() {\n return (-0.5 <= this.r && this.r < 255.5)\n && (-0.5 <= this.g && this.g < 255.5)\n && (-0.5 <= this.b && this.b < 255.5)\n && (0 <= this.opacity && this.opacity <= 1);\n },\n hex: rgb_formatHex, // Deprecated! Use color.formatHex.\n formatHex: rgb_formatHex,\n formatRgb: rgb_formatRgb,\n toString: rgb_formatRgb\n }));\n\n function rgb_formatHex() {\n return \"#\" + hex(this.r) + hex(this.g) + hex(this.b);\n }\n\n function rgb_formatRgb() {\n var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));\n return (a === 1 ? \"rgb(\" : \"rgba(\")\n + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + \", \"\n + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + \", \"\n + Math.max(0, Math.min(255, Math.round(this.b) || 0))\n + (a === 1 ? \")\" : \", \" + a + \")\");\n }\n\n function hex(value) {\n value = Math.max(0, Math.min(255, Math.round(value) || 0));\n return (value < 16 ? \"0\" : \"\") + value.toString(16);\n }\n\n function hsla(h, s, l, a) {\n if (a <= 0) h = s = l = NaN;\n else if (l <= 0 || l >= 1) h = s = NaN;\n else if (s <= 0) h = NaN;\n return new Hsl(h, s, l, a);\n }\n\n function hslConvert(o) {\n if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);\n if (!(o instanceof Color)) o = color(o);\n if (!o) return new Hsl;\n if (o instanceof Hsl) return o;\n o = o.rgb();\n var r = o.r / 255,\n g = o.g / 255,\n b = o.b / 255,\n min = Math.min(r, g, b),\n max = Math.max(r, g, b),\n h = NaN,\n s = max - min,\n l = (max + min) / 2;\n if (s) {\n if (r === max) h = (g - b) / s + (g < b) * 6;\n else if (g === max) h = (b - r) / s + 2;\n else h = (r - g) / s + 4;\n s /= l < 0.5 ? max + min : 2 - max - min;\n h *= 60;\n } else {\n s = l > 0 && l < 1 ? 0 : h;\n }\n return new Hsl(h, s, l, o.opacity);\n }\n\n function hsl(h, s, l, opacity) {\n return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity);\n }\n\n function Hsl(h, s, l, opacity) {\n this.h = +h;\n this.s = +s;\n this.l = +l;\n this.opacity = +opacity;\n }\n\n define(Hsl, hsl, extend(Color, {\n brighter: function(k) {\n k = k == null ? brighter : Math.pow(brighter, k);\n return new Hsl(this.h, this.s, this.l * k, this.opacity);\n },\n darker: function(k) {\n k = k == null ? darker : Math.pow(darker, k);\n return new Hsl(this.h, this.s, this.l * k, this.opacity);\n },\n rgb: function() {\n var h = this.h % 360 + (this.h < 0) * 360,\n s = isNaN(h) || isNaN(this.s) ? 0 : this.s,\n l = this.l,\n m2 = l + (l < 0.5 ? l : 1 - l) * s,\n m1 = 2 * l - m2;\n return new Rgb(\n hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),\n hsl2rgb(h, m1, m2),\n hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),\n this.opacity\n );\n },\n displayable: function() {\n return (0 <= this.s && this.s <= 1 || isNaN(this.s))\n && (0 <= this.l && this.l <= 1)\n && (0 <= this.opacity && this.opacity <= 1);\n },\n formatHsl: function() {\n var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));\n return (a === 1 ? \"hsl(\" : \"hsla(\")\n + (this.h || 0) + \", \"\n + (this.s || 0) * 100 + \"%, \"\n + (this.l || 0) * 100 + \"%\"\n + (a === 1 ? \")\" : \", \" + a + \")\");\n }\n }));\n\n /* From FvD 13.37, CSS Color Module Level 3 */\n function hsl2rgb(h, m1, m2) {\n return (h < 60 ? m1 + (m2 - m1) * h / 60\n : h < 180 ? m2\n : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60\n : m1) * 255;\n }\n\n var deg2rad = Math.PI / 180;\n var rad2deg = 180 / Math.PI;\n\n // https://observablehq.com/@mbostock/lab-and-rgb\n var K = 18,\n Xn = 0.96422,\n Yn = 1,\n Zn = 0.82521,\n t0 = 4 / 29,\n t1 = 6 / 29,\n t2 = 3 * t1 * t1,\n t3 = t1 * t1 * t1;\n\n function labConvert(o) {\n if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity);\n if (o instanceof Hcl) return hcl2lab(o);\n if (!(o instanceof Rgb)) o = rgbConvert(o);\n var r = rgb2lrgb(o.r),\n g = rgb2lrgb(o.g),\n b = rgb2lrgb(o.b),\n y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn), x, z;\n if (r === g && g === b) x = z = y; else {\n x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);\n z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);\n }\n return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity);\n }\n\n function lab(l, a, b, opacity) {\n return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity);\n }\n\n function Lab(l, a, b, opacity) {\n this.l = +l;\n this.a = +a;\n this.b = +b;\n this.opacity = +opacity;\n }\n\n define(Lab, lab, extend(Color, {\n brighter: function(k) {\n return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity);\n },\n darker: function(k) {\n return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity);\n },\n rgb: function() {\n var y = (this.l + 16) / 116,\n x = isNaN(this.a) ? y : y + this.a / 500,\n z = isNaN(this.b) ? y : y - this.b / 200;\n x = Xn * lab2xyz(x);\n y = Yn * lab2xyz(y);\n z = Zn * lab2xyz(z);\n return new Rgb(\n lrgb2rgb( 3.1338561 * x - 1.6168667 * y - 0.4906146 * z),\n lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z),\n lrgb2rgb( 0.0719453 * x - 0.2289914 * y + 1.4052427 * z),\n this.opacity\n );\n }\n }));\n\n function xyz2lab(t) {\n return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0;\n }\n\n function lab2xyz(t) {\n return t > t1 ? t * t * t : t2 * (t - t0);\n }\n\n function lrgb2rgb(x) {\n return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055);\n }\n\n function rgb2lrgb(x) {\n return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);\n }\n\n function hclConvert(o) {\n if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity);\n if (!(o instanceof Lab)) o = labConvert(o);\n if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l && o.l < 100 ? 0 : NaN, o.l, o.opacity);\n var h = Math.atan2(o.b, o.a) * rad2deg;\n return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity);\n }\n\n function hcl(h, c, l, opacity) {\n return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity);\n }\n\n function Hcl(h, c, l, opacity) {\n this.h = +h;\n this.c = +c;\n this.l = +l;\n this.opacity = +opacity;\n }\n\n function hcl2lab(o) {\n if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity);\n var h = o.h * deg2rad;\n return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity);\n }\n\n define(Hcl, hcl, extend(Color, {\n brighter: function(k) {\n return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity);\n },\n darker: function(k) {\n return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity);\n },\n rgb: function() {\n return hcl2lab(this).rgb();\n }\n }));\n\n var A = -0.14861,\n B = +1.78277,\n C = -0.29227,\n D = -0.90649,\n E = +1.97294,\n ED = E * D,\n EB = E * B,\n BC_DA = B * C - D * A;\n\n function cubehelixConvert(o) {\n if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity);\n if (!(o instanceof Rgb)) o = rgbConvert(o);\n var r = o.r / 255,\n g = o.g / 255,\n b = o.b / 255,\n l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB),\n bl = b - l,\n k = (E * (g - l) - C * bl) / D,\n s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), // NaN if l=0 or l=1\n h = s ? Math.atan2(k, bl) * rad2deg - 120 : NaN;\n return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity);\n }\n\n function cubehelix(h, s, l, opacity) {\n return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity);\n }\n\n function Cubehelix(h, s, l, opacity) {\n this.h = +h;\n this.s = +s;\n this.l = +l;\n this.opacity = +opacity;\n }\n\n define(Cubehelix, cubehelix, extend(Color, {\n brighter: function(k) {\n k = k == null ? brighter : Math.pow(brighter, k);\n return new Cubehelix(this.h, this.s, this.l * k, this.opacity);\n },\n darker: function(k) {\n k = k == null ? darker : Math.pow(darker, k);\n return new Cubehelix(this.h, this.s, this.l * k, this.opacity);\n },\n rgb: function() {\n var h = isNaN(this.h) ? 0 : (this.h + 120) * deg2rad,\n l = +this.l,\n a = isNaN(this.s) ? 0 : this.s * l * (1 - l),\n cosh = Math.cos(h),\n sinh = Math.sin(h);\n return new Rgb(\n 255 * (l + a * (A * cosh + B * sinh)),\n 255 * (l + a * (C * cosh + D * sinh)),\n 255 * (l + a * (E * cosh)),\n this.opacity\n );\n }\n }));\n\n function constant(x) {\n return function() {\n return x;\n };\n }\n\n function linear(a, d) {\n return function(t) {\n return a + t * d;\n };\n }\n\n function exponential(a, b, y) {\n return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) {\n return Math.pow(a + t * b, y);\n };\n }\n\n function gamma(y) {\n return (y = +y) === 1 ? nogamma : function(a, b) {\n return b - a ? exponential(a, b, y) : constant(isNaN(a) ? b : a);\n };\n }\n\n function nogamma(a, b) {\n var d = b - a;\n return d ? linear(a, d) : constant(isNaN(a) ? b : a);\n }\n\n var rgb$1 = (function rgbGamma(y) {\n var color = gamma(y);\n\n function rgb$1(start, end) {\n var r = color((start = rgb(start)).r, (end = rgb(end)).r),\n g = color(start.g, end.g),\n b = color(start.b, end.b),\n opacity = nogamma(start.opacity, end.opacity);\n return function(t) {\n start.r = r(t);\n start.g = g(t);\n start.b = b(t);\n start.opacity = opacity(t);\n return start + \"\";\n };\n }\n\n rgb$1.gamma = rgbGamma;\n\n return rgb$1;\n })(1);\n\n function numberArray(a, b) {\n if (!b) b = [];\n var n = a ? Math.min(b.length, a.length) : 0,\n c = b.slice(),\n i;\n return function(t) {\n for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t;\n return c;\n };\n }\n\n function isNumberArray(x) {\n return ArrayBuffer.isView(x) && !(x instanceof DataView);\n }\n\n function genericArray(a, b) {\n var nb = b ? b.length : 0,\n na = a ? Math.min(nb, a.length) : 0,\n x = new Array(na),\n c = new Array(nb),\n i;\n\n for (i = 0; i < na; ++i) x[i] = interpolate(a[i], b[i]);\n for (; i < nb; ++i) c[i] = b[i];\n\n return function(t) {\n for (i = 0; i < na; ++i) c[i] = x[i](t);\n return c;\n };\n }\n\n function date(a, b) {\n var d = new Date;\n return a = +a, b = +b, function(t) {\n return d.setTime(a * (1 - t) + b * t), d;\n };\n }\n\n function interpolateNumber(a, b) {\n return a = +a, b = +b, function(t) {\n return a * (1 - t) + b * t;\n };\n }\n\n function object(a, b) {\n var i = {},\n c = {},\n k;\n\n if (a === null || typeof a !== \"object\") a = {};\n if (b === null || typeof b !== \"object\") b = {};\n\n for (k in b) {\n if (k in a) {\n i[k] = interpolate(a[k], b[k]);\n } else {\n c[k] = b[k];\n }\n }\n\n return function(t) {\n for (k in i) c[k] = i[k](t);\n return c;\n };\n }\n\n var reA = /[-+]?(?:\\d+\\.?\\d*|\\.?\\d+)(?:[eE][-+]?\\d+)?/g,\n reB = new RegExp(reA.source, \"g\");\n\n function zero(b) {\n return function() {\n return b;\n };\n }\n\n function one(b) {\n return function(t) {\n return b(t) + \"\";\n };\n }\n\n function string(a, b) {\n var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b\n am, // current match in a\n bm, // current match in b\n bs, // string preceding current number in b, if any\n i = -1, // index in s\n s = [], // string constants and placeholders\n q = []; // number interpolators\n\n // Coerce inputs to strings.\n a = a + \"\", b = b + \"\";\n\n // Interpolate pairs of numbers in a & b.\n while ((am = reA.exec(a))\n && (bm = reB.exec(b))) {\n if ((bs = bm.index) > bi) { // a string precedes the next number in b\n bs = b.slice(bi, bs);\n if (s[i]) s[i] += bs; // coalesce with previous string\n else s[++i] = bs;\n }\n if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match\n if (s[i]) s[i] += bm; // coalesce with previous string\n else s[++i] = bm;\n } else { // interpolate non-matching numbers\n s[++i] = null;\n q.push({i: i, x: interpolateNumber(am, bm)});\n }\n bi = reB.lastIndex;\n }\n\n // Add remains of b.\n if (bi < b.length) {\n bs = b.slice(bi);\n if (s[i]) s[i] += bs; // coalesce with previous string\n else s[++i] = bs;\n }\n\n // Special optimization for only a single match.\n // Otherwise, interpolate each of the numbers and rejoin the string.\n return s.length < 2 ? (q[0]\n ? one(q[0].x)\n : zero(b))\n : (b = q.length, function(t) {\n for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);\n return s.join(\"\");\n });\n }\n\n function interpolate(a, b) {\n var t = typeof b, c;\n return b == null || t === \"boolean\" ? constant(b)\n : (t === \"number\" ? interpolateNumber\n : t === \"string\" ? ((c = color(b)) ? (b = c, rgb$1) : string)\n : b instanceof color ? rgb$1\n : b instanceof Date ? date\n : isNumberArray(b) ? numberArray\n : Array.isArray(b) ? genericArray\n : typeof b.valueOf !== \"function\" && typeof b.toString !== \"function\" || isNaN(b) ? object\n : interpolateNumber)(a, b);\n }\n\n function interpolateRound(a, b) {\n return a = +a, b = +b, function(t) {\n return Math.round(a * (1 - t) + b * t);\n };\n }\n\n function constant$1(x) {\n return function() {\n return x;\n };\n }\n\n function number(x) {\n return +x;\n }\n\n var unit = [0, 1];\n\n function identity(x) {\n return x;\n }\n\n function normalize(a, b) {\n return (b -= (a = +a))\n ? function(x) { return (x - a) / b; }\n : constant$1(isNaN(b) ? NaN : 0.5);\n }\n\n function clamper(a, b) {\n var t;\n if (a > b) t = a, a = b, b = t;\n return function(x) { return Math.max(a, Math.min(b, x)); };\n }\n\n // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1].\n // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b].\n function bimap(domain, range, interpolate) {\n var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1];\n if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0);\n else d0 = normalize(d0, d1), r0 = interpolate(r0, r1);\n return function(x) { return r0(d0(x)); };\n }\n\n function polymap(domain, range, interpolate) {\n var j = Math.min(domain.length, range.length) - 1,\n d = new Array(j),\n r = new Array(j),\n i = -1;\n\n // Reverse descending domains.\n if (domain[j] < domain[0]) {\n domain = domain.slice().reverse();\n range = range.slice().reverse();\n }\n\n while (++i < j) {\n d[i] = normalize(domain[i], domain[i + 1]);\n r[i] = interpolate(range[i], range[i + 1]);\n }\n\n return function(x) {\n var i = bisectRight(domain, x, 1, j) - 1;\n return r[i](d[i](x));\n };\n }\n\n function copy(source, target) {\n return target\n .domain(source.domain())\n .range(source.range())\n .interpolate(source.interpolate())\n .clamp(source.clamp())\n .unknown(source.unknown());\n }\n\n function transformer() {\n var domain = unit,\n range = unit,\n interpolate$1 = interpolate,\n transform,\n untransform,\n unknown,\n clamp = identity,\n piecewise,\n output,\n input;\n\n function rescale() {\n var n = Math.min(domain.length, range.length);\n if (clamp !== identity) clamp = clamper(domain[0], domain[n - 1]);\n piecewise = n > 2 ? polymap : bimap;\n output = input = null;\n return scale;\n }\n\n function scale(x) {\n return isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate$1)))(transform(clamp(x)));\n }\n\n scale.invert = function(y) {\n return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y)));\n };\n\n scale.domain = function(_) {\n return arguments.length ? (domain = Array.from(_, number), rescale()) : domain.slice();\n };\n\n scale.range = function(_) {\n return arguments.length ? (range = Array.from(_), rescale()) : range.slice();\n };\n\n scale.rangeRound = function(_) {\n return range = Array.from(_), interpolate$1 = interpolateRound, rescale();\n };\n\n scale.clamp = function(_) {\n return arguments.length ? (clamp = _ ? true : identity, rescale()) : clamp !== identity;\n };\n\n scale.interpolate = function(_) {\n return arguments.length ? (interpolate$1 = _, rescale()) : interpolate$1;\n };\n\n scale.unknown = function(_) {\n return arguments.length ? (unknown = _, scale) : unknown;\n };\n\n return function(t, u) {\n transform = t, untransform = u;\n return rescale();\n };\n }\n\n function continuous() {\n return transformer()(identity, identity);\n }\n\n // Computes the decimal coefficient and exponent of the specified number x with\n // significant digits p, where x is positive and p is in [1, 21] or undefined.\n // For example, formatDecimal(1.23) returns [\"123\", 0].\n function formatDecimal(x, p) {\n if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf(\"e\")) < 0) return null; // NaN, ±Infinity\n var i, coefficient = x.slice(0, i);\n\n // The string returned by toExponential either has the form \\d\\.\\d+e[-+]\\d+\n // (e.g., 1.2e+3) or the form \\de[-+]\\d+ (e.g., 1e+3).\n return [\n coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient,\n +x.slice(i + 1)\n ];\n }\n\n function exponent(x) {\n return x = formatDecimal(Math.abs(x)), x ? x[1] : NaN;\n }\n\n function formatGroup(grouping, thousands) {\n return function(value, width) {\n var i = value.length,\n t = [],\n j = 0,\n g = grouping[0],\n length = 0;\n\n while (i > 0 && g > 0) {\n if (length + g + 1 > width) g = Math.max(1, width - length);\n t.push(value.substring(i -= g, i + g));\n if ((length += g + 1) > width) break;\n g = grouping[j = (j + 1) % grouping.length];\n }\n\n return t.reverse().join(thousands);\n };\n }\n\n function formatNumerals(numerals) {\n return function(value) {\n return value.replace(/[0-9]/g, function(i) {\n return numerals[+i];\n });\n };\n }\n\n // [[fill]align][sign][symbol][0][width][,][.precision][~][type]\n var re = /^(?:(.)?([<>=^]))?([+\\-( ])?([$#])?(0)?(\\d+)?(,)?(\\.\\d+)?(~)?([a-z%])?$/i;\n\n function formatSpecifier(specifier) {\n if (!(match = re.exec(specifier))) throw new Error(\"invalid format: \" + specifier);\n var match;\n return new FormatSpecifier({\n fill: match[1],\n align: match[2],\n sign: match[3],\n symbol: match[4],\n zero: match[5],\n width: match[6],\n comma: match[7],\n precision: match[8] && match[8].slice(1),\n trim: match[9],\n type: match[10]\n });\n }\n\n formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof\n\n function FormatSpecifier(specifier) {\n this.fill = specifier.fill === undefined ? \" \" : specifier.fill + \"\";\n this.align = specifier.align === undefined ? \">\" : specifier.align + \"\";\n this.sign = specifier.sign === undefined ? \"-\" : specifier.sign + \"\";\n this.symbol = specifier.symbol === undefined ? \"\" : specifier.symbol + \"\";\n this.zero = !!specifier.zero;\n this.width = specifier.width === undefined ? undefined : +specifier.width;\n this.comma = !!specifier.comma;\n this.precision = specifier.precision === undefined ? undefined : +specifier.precision;\n this.trim = !!specifier.trim;\n this.type = specifier.type === undefined ? \"\" : specifier.type + \"\";\n }\n\n FormatSpecifier.prototype.toString = function() {\n return this.fill\n + this.align\n + this.sign\n + this.symbol\n + (this.zero ? \"0\" : \"\")\n + (this.width === undefined ? \"\" : Math.max(1, this.width | 0))\n + (this.comma ? \",\" : \"\")\n + (this.precision === undefined ? \"\" : \".\" + Math.max(0, this.precision | 0))\n + (this.trim ? \"~\" : \"\")\n + this.type;\n };\n\n // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k.\n function formatTrim(s) {\n out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {\n switch (s[i]) {\n case \".\": i0 = i1 = i; break;\n case \"0\": if (i0 === 0) i0 = i; i1 = i; break;\n default: if (!+s[i]) break out; if (i0 > 0) i0 = 0; break;\n }\n }\n return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;\n }\n\n var prefixExponent;\n\n function formatPrefixAuto(x, p) {\n var d = formatDecimal(x, p);\n if (!d) return x + \"\";\n var coefficient = d[0],\n exponent = d[1],\n i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1,\n n = coefficient.length;\n return i === n ? coefficient\n : i > n ? coefficient + new Array(i - n + 1).join(\"0\")\n : i > 0 ? coefficient.slice(0, i) + \".\" + coefficient.slice(i)\n : \"0.\" + new Array(1 - i).join(\"0\") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than 1y!\n }\n\n function formatRounded(x, p) {\n var d = formatDecimal(x, p);\n if (!d) return x + \"\";\n var coefficient = d[0],\n exponent = d[1];\n return exponent < 0 ? \"0.\" + new Array(-exponent).join(\"0\") + coefficient\n : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + \".\" + coefficient.slice(exponent + 1)\n : coefficient + new Array(exponent - coefficient.length + 2).join(\"0\");\n }\n\n var formatTypes = {\n \"%\": function(x, p) { return (x * 100).toFixed(p); },\n \"b\": function(x) { return Math.round(x).toString(2); },\n \"c\": function(x) { return x + \"\"; },\n \"d\": function(x) { return Math.round(x).toString(10); },\n \"e\": function(x, p) { return x.toExponential(p); },\n \"f\": function(x, p) { return x.toFixed(p); },\n \"g\": function(x, p) { return x.toPrecision(p); },\n \"o\": function(x) { return Math.round(x).toString(8); },\n \"p\": function(x, p) { return formatRounded(x * 100, p); },\n \"r\": formatRounded,\n \"s\": formatPrefixAuto,\n \"X\": function(x) { return Math.round(x).toString(16).toUpperCase(); },\n \"x\": function(x) { return Math.round(x).toString(16); }\n };\n\n function identity$1(x) {\n return x;\n }\n\n var map = Array.prototype.map,\n prefixes = [\"y\",\"z\",\"a\",\"f\",\"p\",\"n\",\"µ\",\"m\",\"\",\"k\",\"M\",\"G\",\"T\",\"P\",\"E\",\"Z\",\"Y\"];\n\n function formatLocale(locale) {\n var group = locale.grouping === undefined || locale.thousands === undefined ? identity$1 : formatGroup(map.call(locale.grouping, Number), locale.thousands + \"\"),\n currencyPrefix = locale.currency === undefined ? \"\" : locale.currency[0] + \"\",\n currencySuffix = locale.currency === undefined ? \"\" : locale.currency[1] + \"\",\n decimal = locale.decimal === undefined ? \".\" : locale.decimal + \"\",\n numerals = locale.numerals === undefined ? identity$1 : formatNumerals(map.call(locale.numerals, String)),\n percent = locale.percent === undefined ? \"%\" : locale.percent + \"\",\n minus = locale.minus === undefined ? \"-\" : locale.minus + \"\",\n nan = locale.nan === undefined ? \"NaN\" : locale.nan + \"\";\n\n function newFormat(specifier) {\n specifier = formatSpecifier(specifier);\n\n var fill = specifier.fill,\n align = specifier.align,\n sign = specifier.sign,\n symbol = specifier.symbol,\n zero = specifier.zero,\n width = specifier.width,\n comma = specifier.comma,\n precision = specifier.precision,\n trim = specifier.trim,\n type = specifier.type;\n\n // The \"n\" type is an alias for \",g\".\n if (type === \"n\") comma = true, type = \"g\";\n\n // The \"\" type, and any invalid type, is an alias for \".12~g\".\n else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = \"g\";\n\n // If zero fill is specified, padding goes after sign and before digits.\n if (zero || (fill === \"0\" && align === \"=\")) zero = true, fill = \"0\", align = \"=\";\n\n // Compute the prefix and suffix.\n // For SI-prefix, the suffix is lazily computed.\n var prefix = symbol === \"$\" ? currencyPrefix : symbol === \"#\" && /[boxX]/.test(type) ? \"0\" + type.toLowerCase() : \"\",\n suffix = symbol === \"$\" ? currencySuffix : /[%p]/.test(type) ? percent : \"\";\n\n // What format function should we use?\n // Is this an integer type?\n // Can this type generate exponential notation?\n var formatType = formatTypes[type],\n maybeSuffix = /[defgprs%]/.test(type);\n\n // Set the default precision if not specified,\n // or clamp the specified precision to the supported range.\n // For significant precision, it must be in [1, 21].\n // For fixed precision, it must be in [0, 20].\n precision = precision === undefined ? 6\n : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision))\n : Math.max(0, Math.min(20, precision));\n\n function format(value) {\n var valuePrefix = prefix,\n valueSuffix = suffix,\n i, n, c;\n\n if (type === \"c\") {\n valueSuffix = formatType(value) + valueSuffix;\n value = \"\";\n } else {\n value = +value;\n\n // Determine the sign. -0 is not less than 0, but 1 / -0 is!\n var valueNegative = value < 0 || 1 / value < 0;\n\n // Perform the initial formatting.\n value = isNaN(value) ? nan : formatType(Math.abs(value), precision);\n\n // Trim insignificant zeros.\n if (trim) value = formatTrim(value);\n\n // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.\n if (valueNegative && +value === 0 && sign !== \"+\") valueNegative = false;\n\n // Compute the prefix and suffix.\n valuePrefix = (valueNegative ? (sign === \"(\" ? sign : minus) : sign === \"-\" || sign === \"(\" ? \"\" : sign) + valuePrefix;\n valueSuffix = (type === \"s\" ? prefixes[8 + prefixExponent / 3] : \"\") + valueSuffix + (valueNegative && sign === \"(\" ? \")\" : \"\");\n\n // Break the formatted value into the integer “value” part that can be\n // grouped, and fractional or exponential “suffix” part that is not.\n if (maybeSuffix) {\n i = -1, n = value.length;\n while (++i < n) {\n if (c = value.charCodeAt(i), 48 > c || c > 57) {\n valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix;\n value = value.slice(0, i);\n break;\n }\n }\n }\n }\n\n // If the fill character is not \"0\", grouping is applied before padding.\n if (comma && !zero) value = group(value, Infinity);\n\n // Compute the padding.\n var length = valuePrefix.length + value.length + valueSuffix.length,\n padding = length < width ? new Array(width - length + 1).join(fill) : \"\";\n\n // If the fill character is \"0\", grouping is applied after padding.\n if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = \"\";\n\n // Reconstruct the final output based on the desired alignment.\n switch (align) {\n case \"<\": value = valuePrefix + value + valueSuffix + padding; break;\n case \"=\": value = valuePrefix + padding + value + valueSuffix; break;\n case \"^\": value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); break;\n default: value = padding + valuePrefix + value + valueSuffix; break;\n }\n\n return numerals(value);\n }\n\n format.toString = function() {\n return specifier + \"\";\n };\n\n return format;\n }\n\n function formatPrefix(specifier, value) {\n var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = \"f\", specifier)),\n e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3,\n k = Math.pow(10, -e),\n prefix = prefixes[8 + e / 3];\n return function(value) {\n return f(k * value) + prefix;\n };\n }\n\n return {\n format: newFormat,\n formatPrefix: formatPrefix\n };\n }\n\n var locale;\n var format;\n var formatPrefix;\n\n defaultLocale({\n decimal: \".\",\n thousands: \",\",\n grouping: [3],\n currency: [\"$\", \"\"],\n minus: \"-\"\n });\n\n function defaultLocale(definition) {\n locale = formatLocale(definition);\n format = locale.format;\n formatPrefix = locale.formatPrefix;\n return locale;\n }\n\n function precisionFixed(step) {\n return Math.max(0, -exponent(Math.abs(step)));\n }\n\n function precisionPrefix(step, value) {\n return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step)));\n }\n\n function precisionRound(step, max) {\n step = Math.abs(step), max = Math.abs(max) - step;\n return Math.max(0, exponent(max) - exponent(step)) + 1;\n }\n\n function tickFormat(start, stop, count, specifier) {\n var step = tickStep(start, stop, count),\n precision;\n specifier = formatSpecifier(specifier == null ? \",f\" : specifier);\n switch (specifier.type) {\n case \"s\": {\n var value = Math.max(Math.abs(start), Math.abs(stop));\n if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision;\n return formatPrefix(specifier, value);\n }\n case \"\":\n case \"e\":\n case \"g\":\n case \"p\":\n case \"r\": {\n if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === \"e\");\n break;\n }\n case \"f\":\n case \"%\": {\n if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === \"%\") * 2;\n break;\n }\n }\n return format(specifier);\n }\n\n function linearish(scale) {\n var domain = scale.domain;\n\n scale.ticks = function(count) {\n var d = domain();\n return ticks(d[0], d[d.length - 1], count == null ? 10 : count);\n };\n\n scale.tickFormat = function(count, specifier) {\n var d = domain();\n return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier);\n };\n\n scale.nice = function(count) {\n if (count == null) count = 10;\n\n var d = domain(),\n i0 = 0,\n i1 = d.length - 1,\n start = d[i0],\n stop = d[i1],\n step;\n\n if (stop < start) {\n step = start, start = stop, stop = step;\n step = i0, i0 = i1, i1 = step;\n }\n\n step = tickIncrement(start, stop, count);\n\n if (step > 0) {\n start = Math.floor(start / step) * step;\n stop = Math.ceil(stop / step) * step;\n step = tickIncrement(start, stop, count);\n } else if (step < 0) {\n start = Math.ceil(start * step) / step;\n stop = Math.floor(stop * step) / step;\n step = tickIncrement(start, stop, count);\n }\n\n if (step > 0) {\n d[i0] = Math.floor(start / step) * step;\n d[i1] = Math.ceil(stop / step) * step;\n domain(d);\n } else if (step < 0) {\n d[i0] = Math.ceil(start * step) / step;\n d[i1] = Math.floor(stop * step) / step;\n domain(d);\n }\n\n return scale;\n };\n\n return scale;\n }\n\n function linear$1() {\n var scale = continuous();\n\n scale.copy = function() {\n return copy(scale, linear$1());\n };\n\n initRange.apply(scale, arguments);\n\n return linearish(scale);\n }\n\n var t0$1 = new Date,\n t1$1 = new Date;\n\n function newInterval(floori, offseti, count, field) {\n\n function interval(date) {\n return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date;\n }\n\n interval.floor = function(date) {\n return floori(date = new Date(+date)), date;\n };\n\n interval.ceil = function(date) {\n return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date;\n };\n\n interval.round = function(date) {\n var d0 = interval(date),\n d1 = interval.ceil(date);\n return date - d0 < d1 - date ? d0 : d1;\n };\n\n interval.offset = function(date, step) {\n return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date;\n };\n\n interval.range = function(start, stop, step) {\n var range = [], previous;\n start = interval.ceil(start);\n step = step == null ? 1 : Math.floor(step);\n if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date\n do range.push(previous = new Date(+start)), offseti(start, step), floori(start);\n while (previous < start && start < stop);\n return range;\n };\n\n interval.filter = function(test) {\n return newInterval(function(date) {\n if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1);\n }, function(date, step) {\n if (date >= date) {\n if (step < 0) while (++step <= 0) {\n while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty\n } else while (--step >= 0) {\n while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty\n }\n }\n });\n };\n\n if (count) {\n interval.count = function(start, end) {\n t0$1.setTime(+start), t1$1.setTime(+end);\n floori(t0$1), floori(t1$1);\n return Math.floor(count(t0$1, t1$1));\n };\n\n interval.every = function(step) {\n step = Math.floor(step);\n return !isFinite(step) || !(step > 0) ? null\n : !(step > 1) ? interval\n : interval.filter(field\n ? function(d) { return field(d) % step === 0; }\n : function(d) { return interval.count(0, d) % step === 0; });\n };\n }\n\n return interval;\n }\n\n var millisecond = newInterval(function() {\n // noop\n }, function(date, step) {\n date.setTime(+date + step);\n }, function(start, end) {\n return end - start;\n });\n\n // An optimized implementation for this simple case.\n millisecond.every = function(k) {\n k = Math.floor(k);\n if (!isFinite(k) || !(k > 0)) return null;\n if (!(k > 1)) return millisecond;\n return newInterval(function(date) {\n date.setTime(Math.floor(date / k) * k);\n }, function(date, step) {\n date.setTime(+date + step * k);\n }, function(start, end) {\n return (end - start) / k;\n });\n };\n\n var durationSecond = 1e3;\n var durationMinute = 6e4;\n var durationHour = 36e5;\n var durationDay = 864e5;\n var durationWeek = 6048e5;\n\n var second = newInterval(function(date) {\n date.setTime(date - date.getMilliseconds());\n }, function(date, step) {\n date.setTime(+date + step * durationSecond);\n }, function(start, end) {\n return (end - start) / durationSecond;\n }, function(date) {\n return date.getUTCSeconds();\n });\n\n var minute = newInterval(function(date) {\n date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond);\n }, function(date, step) {\n date.setTime(+date + step * durationMinute);\n }, function(start, end) {\n return (end - start) / durationMinute;\n }, function(date) {\n return date.getMinutes();\n });\n\n var hour = newInterval(function(date) {\n date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute);\n }, function(date, step) {\n date.setTime(+date + step * durationHour);\n }, function(start, end) {\n return (end - start) / durationHour;\n }, function(date) {\n return date.getHours();\n });\n\n var day = newInterval(function(date) {\n date.setHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setDate(date.getDate() + step);\n }, function(start, end) {\n return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay;\n }, function(date) {\n return date.getDate() - 1;\n });\n\n function weekday(i) {\n return newInterval(function(date) {\n date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7);\n date.setHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setDate(date.getDate() + step * 7);\n }, function(start, end) {\n return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek;\n });\n }\n\n var sunday = weekday(0);\n var monday = weekday(1);\n var tuesday = weekday(2);\n var wednesday = weekday(3);\n var thursday = weekday(4);\n var friday = weekday(5);\n var saturday = weekday(6);\n\n var month = newInterval(function(date) {\n date.setDate(1);\n date.setHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setMonth(date.getMonth() + step);\n }, function(start, end) {\n return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12;\n }, function(date) {\n return date.getMonth();\n });\n\n var year = newInterval(function(date) {\n date.setMonth(0, 1);\n date.setHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setFullYear(date.getFullYear() + step);\n }, function(start, end) {\n return end.getFullYear() - start.getFullYear();\n }, function(date) {\n return date.getFullYear();\n });\n\n // An optimized implementation for this simple case.\n year.every = function(k) {\n return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function(date) {\n date.setFullYear(Math.floor(date.getFullYear() / k) * k);\n date.setMonth(0, 1);\n date.setHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setFullYear(date.getFullYear() + step * k);\n });\n };\n\n var utcMinute = newInterval(function(date) {\n date.setUTCSeconds(0, 0);\n }, function(date, step) {\n date.setTime(+date + step * durationMinute);\n }, function(start, end) {\n return (end - start) / durationMinute;\n }, function(date) {\n return date.getUTCMinutes();\n });\n\n var utcHour = newInterval(function(date) {\n date.setUTCMinutes(0, 0, 0);\n }, function(date, step) {\n date.setTime(+date + step * durationHour);\n }, function(start, end) {\n return (end - start) / durationHour;\n }, function(date) {\n return date.getUTCHours();\n });\n\n var utcDay = newInterval(function(date) {\n date.setUTCHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setUTCDate(date.getUTCDate() + step);\n }, function(start, end) {\n return (end - start) / durationDay;\n }, function(date) {\n return date.getUTCDate() - 1;\n });\n\n function utcWeekday(i) {\n return newInterval(function(date) {\n date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7);\n date.setUTCHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setUTCDate(date.getUTCDate() + step * 7);\n }, function(start, end) {\n return (end - start) / durationWeek;\n });\n }\n\n var utcSunday = utcWeekday(0);\n var utcMonday = utcWeekday(1);\n var utcTuesday = utcWeekday(2);\n var utcWednesday = utcWeekday(3);\n var utcThursday = utcWeekday(4);\n var utcFriday = utcWeekday(5);\n var utcSaturday = utcWeekday(6);\n\n var utcMonth = newInterval(function(date) {\n date.setUTCDate(1);\n date.setUTCHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setUTCMonth(date.getUTCMonth() + step);\n }, function(start, end) {\n return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12;\n }, function(date) {\n return date.getUTCMonth();\n });\n\n var utcYear = newInterval(function(date) {\n date.setUTCMonth(0, 1);\n date.setUTCHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setUTCFullYear(date.getUTCFullYear() + step);\n }, function(start, end) {\n return end.getUTCFullYear() - start.getUTCFullYear();\n }, function(date) {\n return date.getUTCFullYear();\n });\n\n // An optimized implementation for this simple case.\n utcYear.every = function(k) {\n return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function(date) {\n date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k);\n date.setUTCMonth(0, 1);\n date.setUTCHours(0, 0, 0, 0);\n }, function(date, step) {\n date.setUTCFullYear(date.getUTCFullYear() + step * k);\n });\n };\n\n function localDate(d) {\n if (0 <= d.y && d.y < 100) {\n var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L);\n date.setFullYear(d.y);\n return date;\n }\n return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L);\n }\n\n function utcDate(d) {\n if (0 <= d.y && d.y < 100) {\n var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L));\n date.setUTCFullYear(d.y);\n return date;\n }\n return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L));\n }\n\n function newDate(y, m, d) {\n return {y: y, m: m, d: d, H: 0, M: 0, S: 0, L: 0};\n }\n\n function formatLocale$1(locale) {\n var locale_dateTime = locale.dateTime,\n locale_date = locale.date,\n locale_time = locale.time,\n locale_periods = locale.periods,\n locale_weekdays = locale.days,\n locale_shortWeekdays = locale.shortDays,\n locale_months = locale.months,\n locale_shortMonths = locale.shortMonths;\n\n var periodRe = formatRe(locale_periods),\n periodLookup = formatLookup(locale_periods),\n weekdayRe = formatRe(locale_weekdays),\n weekdayLookup = formatLookup(locale_weekdays),\n shortWeekdayRe = formatRe(locale_shortWeekdays),\n shortWeekdayLookup = formatLookup(locale_shortWeekdays),\n monthRe = formatRe(locale_months),\n monthLookup = formatLookup(locale_months),\n shortMonthRe = formatRe(locale_shortMonths),\n shortMonthLookup = formatLookup(locale_shortMonths);\n\n var formats = {\n \"a\": formatShortWeekday,\n \"A\": formatWeekday,\n \"b\": formatShortMonth,\n \"B\": formatMonth,\n \"c\": null,\n \"d\": formatDayOfMonth,\n \"e\": formatDayOfMonth,\n \"f\": formatMicroseconds,\n \"H\": formatHour24,\n \"I\": formatHour12,\n \"j\": formatDayOfYear,\n \"L\": formatMilliseconds,\n \"m\": formatMonthNumber,\n \"M\": formatMinutes,\n \"p\": formatPeriod,\n \"q\": formatQuarter,\n \"Q\": formatUnixTimestamp,\n \"s\": formatUnixTimestampSeconds,\n \"S\": formatSeconds,\n \"u\": formatWeekdayNumberMonday,\n \"U\": formatWeekNumberSunday,\n \"V\": formatWeekNumberISO,\n \"w\": formatWeekdayNumberSunday,\n \"W\": formatWeekNumberMonday,\n \"x\": null,\n \"X\": null,\n \"y\": formatYear,\n \"Y\": formatFullYear,\n \"Z\": formatZone,\n \"%\": formatLiteralPercent\n };\n\n var utcFormats = {\n \"a\": formatUTCShortWeekday,\n \"A\": formatUTCWeekday,\n \"b\": formatUTCShortMonth,\n \"B\": formatUTCMonth,\n \"c\": null,\n \"d\": formatUTCDayOfMonth,\n \"e\": formatUTCDayOfMonth,\n \"f\": formatUTCMicroseconds,\n \"H\": formatUTCHour24,\n \"I\": formatUTCHour12,\n \"j\": formatUTCDayOfYear,\n \"L\": formatUTCMilliseconds,\n \"m\": formatUTCMonthNumber,\n \"M\": formatUTCMinutes,\n \"p\": formatUTCPeriod,\n \"q\": formatUTCQuarter,\n \"Q\": formatUnixTimestamp,\n \"s\": formatUnixTimestampSeconds,\n \"S\": formatUTCSeconds,\n \"u\": formatUTCWeekdayNumberMonday,\n \"U\": formatUTCWeekNumberSunday,\n \"V\": formatUTCWeekNumberISO,\n \"w\": formatUTCWeekdayNumberSunday,\n \"W\": formatUTCWeekNumberMonday,\n \"x\": null,\n \"X\": null,\n \"y\": formatUTCYear,\n \"Y\": formatUTCFullYear,\n \"Z\": formatUTCZone,\n \"%\": formatLiteralPercent\n };\n\n var parses = {\n \"a\": parseShortWeekday,\n \"A\": parseWeekday,\n \"b\": parseShortMonth,\n \"B\": parseMonth,\n \"c\": parseLocaleDateTime,\n \"d\": parseDayOfMonth,\n \"e\": parseDayOfMonth,\n \"f\": parseMicroseconds,\n \"H\": parseHour24,\n \"I\": parseHour24,\n \"j\": parseDayOfYear,\n \"L\": parseMilliseconds,\n \"m\": parseMonthNumber,\n \"M\": parseMinutes,\n \"p\": parsePeriod,\n \"q\": parseQuarter,\n \"Q\": parseUnixTimestamp,\n \"s\": parseUnixTimestampSeconds,\n \"S\": parseSeconds,\n \"u\": parseWeekdayNumberMonday,\n \"U\": parseWeekNumberSunday,\n \"V\": parseWeekNumberISO,\n \"w\": parseWeekdayNumberSunday,\n \"W\": parseWeekNumberMonday,\n \"x\": parseLocaleDate,\n \"X\": parseLocaleTime,\n \"y\": parseYear,\n \"Y\": parseFullYear,\n \"Z\": parseZone,\n \"%\": parseLiteralPercent\n };\n\n // These recursive directive definitions must be deferred.\n formats.x = newFormat(locale_date, formats);\n formats.X = newFormat(locale_time, formats);\n formats.c = newFormat(locale_dateTime, formats);\n utcFormats.x = newFormat(locale_date, utcFormats);\n utcFormats.X = newFormat(locale_time, utcFormats);\n utcFormats.c = newFormat(locale_dateTime, utcFormats);\n\n function newFormat(specifier, formats) {\n return function(date) {\n var string = [],\n i = -1,\n j = 0,\n n = specifier.length,\n c,\n pad,\n format;\n\n if (!(date instanceof Date)) date = new Date(+date);\n\n while (++i < n) {\n if (specifier.charCodeAt(i) === 37) {\n string.push(specifier.slice(j, i));\n if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);\n else pad = c === \"e\" ? \" \" : \"0\";\n if (format = formats[c]) c = format(date, pad);\n string.push(c);\n j = i + 1;\n }\n }\n\n string.push(specifier.slice(j, i));\n return string.join(\"\");\n };\n }\n\n function newParse(specifier, Z) {\n return function(string) {\n var d = newDate(1900, undefined, 1),\n i = parseSpecifier(d, specifier, string += \"\", 0),\n week, day$1;\n if (i != string.length) return null;\n\n // If a UNIX timestamp is specified, return it.\n if (\"Q\" in d) return new Date(d.Q);\n if (\"s\" in d) return new Date(d.s * 1000 + (\"L\" in d ? d.L : 0));\n\n // If this is utcParse, never use the local timezone.\n if (Z && !(\"Z\" in d)) d.Z = 0;\n\n // The am-pm flag is 0 for AM, and 1 for PM.\n if (\"p\" in d) d.H = d.H % 12 + d.p * 12;\n\n // If the month was not specified, inherit from the quarter.\n if (d.m === undefined) d.m = \"q\" in d ? d.q : 0;\n\n // Convert day-of-week and week-of-year to day-of-year.\n if (\"V\" in d) {\n if (d.V < 1 || d.V > 53) return null;\n if (!(\"w\" in d)) d.w = 1;\n if (\"Z\" in d) {\n week = utcDate(newDate(d.y, 0, 1)), day$1 = week.getUTCDay();\n week = day$1 > 4 || day$1 === 0 ? utcMonday.ceil(week) : utcMonday(week);\n week = utcDay.offset(week, (d.V - 1) * 7);\n d.y = week.getUTCFullYear();\n d.m = week.getUTCMonth();\n d.d = week.getUTCDate() + (d.w + 6) % 7;\n } else {\n week = localDate(newDate(d.y, 0, 1)), day$1 = week.getDay();\n week = day$1 > 4 || day$1 === 0 ? monday.ceil(week) : monday(week);\n week = day.offset(week, (d.V - 1) * 7);\n d.y = week.getFullYear();\n d.m = week.getMonth();\n d.d = week.getDate() + (d.w + 6) % 7;\n }\n } else if (\"W\" in d || \"U\" in d) {\n if (!(\"w\" in d)) d.w = \"u\" in d ? d.u % 7 : \"W\" in d ? 1 : 0;\n day$1 = \"Z\" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay();\n d.m = 0;\n d.d = \"W\" in d ? (d.w + 6) % 7 + d.W * 7 - (day$1 + 5) % 7 : d.w + d.U * 7 - (day$1 + 6) % 7;\n }\n\n // If a time zone is specified, all fields are interpreted as UTC and then\n // offset according to the specified time zone.\n if (\"Z\" in d) {\n d.H += d.Z / 100 | 0;\n d.M += d.Z % 100;\n return utcDate(d);\n }\n\n // Otherwise, all fields are in local time.\n return localDate(d);\n };\n }\n\n function parseSpecifier(d, specifier, string, j) {\n var i = 0,\n n = specifier.length,\n m = string.length,\n c,\n parse;\n\n while (i < n) {\n if (j >= m) return -1;\n c = specifier.charCodeAt(i++);\n if (c === 37) {\n c = specifier.charAt(i++);\n parse = parses[c in pads ? specifier.charAt(i++) : c];\n if (!parse || ((j = parse(d, string, j)) < 0)) return -1;\n } else if (c != string.charCodeAt(j++)) {\n return -1;\n }\n }\n\n return j;\n }\n\n function parsePeriod(d, string, i) {\n var n = periodRe.exec(string.slice(i));\n return n ? (d.p = periodLookup[n[0].toLowerCase()], i + n[0].length) : -1;\n }\n\n function parseShortWeekday(d, string, i) {\n var n = shortWeekdayRe.exec(string.slice(i));\n return n ? (d.w = shortWeekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1;\n }\n\n function parseWeekday(d, string, i) {\n var n = weekdayRe.exec(string.slice(i));\n return n ? (d.w = weekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1;\n }\n\n function parseShortMonth(d, string, i) {\n var n = shortMonthRe.exec(string.slice(i));\n return n ? (d.m = shortMonthLookup[n[0].toLowerCase()], i + n[0].length) : -1;\n }\n\n function parseMonth(d, string, i) {\n var n = monthRe.exec(string.slice(i));\n return n ? (d.m = monthLookup[n[0].toLowerCase()], i + n[0].length) : -1;\n }\n\n function parseLocaleDateTime(d, string, i) {\n return parseSpecifier(d, locale_dateTime, string, i);\n }\n\n function parseLocaleDate(d, string, i) {\n return parseSpecifier(d, locale_date, string, i);\n }\n\n function parseLocaleTime(d, string, i) {\n return parseSpecifier(d, locale_time, string, i);\n }\n\n function formatShortWeekday(d) {\n return locale_shortWeekdays[d.getDay()];\n }\n\n function formatWeekday(d) {\n return locale_weekdays[d.getDay()];\n }\n\n function formatShortMonth(d) {\n return locale_shortMonths[d.getMonth()];\n }\n\n function formatMonth(d) {\n return locale_months[d.getMonth()];\n }\n\n function formatPeriod(d) {\n return locale_periods[+(d.getHours() >= 12)];\n }\n\n function formatQuarter(d) {\n return 1 + ~~(d.getMonth() / 3);\n }\n\n function formatUTCShortWeekday(d) {\n return locale_shortWeekdays[d.getUTCDay()];\n }\n\n function formatUTCWeekday(d) {\n return locale_weekdays[d.getUTCDay()];\n }\n\n function formatUTCShortMonth(d) {\n return locale_shortMonths[d.getUTCMonth()];\n }\n\n function formatUTCMonth(d) {\n return locale_months[d.getUTCMonth()];\n }\n\n function formatUTCPeriod(d) {\n return locale_periods[+(d.getUTCHours() >= 12)];\n }\n\n function formatUTCQuarter(d) {\n return 1 + ~~(d.getUTCMonth() / 3);\n }\n\n return {\n format: function(specifier) {\n var f = newFormat(specifier += \"\", formats);\n f.toString = function() { return specifier; };\n return f;\n },\n parse: function(specifier) {\n var p = newParse(specifier += \"\", false);\n p.toString = function() { return specifier; };\n return p;\n },\n utcFormat: function(specifier) {\n var f = newFormat(specifier += \"\", utcFormats);\n f.toString = function() { return specifier; };\n return f;\n },\n utcParse: function(specifier) {\n var p = newParse(specifier += \"\", true);\n p.toString = function() { return specifier; };\n return p;\n }\n };\n }\n\n var pads = {\"-\": \"\", \"_\": \" \", \"0\": \"0\"},\n numberRe = /^\\s*\\d+/, // note: ignores next directive\n percentRe = /^%/,\n requoteRe = /[\\\\^$*+?|[\\]().{}]/g;\n\n function pad(value, fill, width) {\n var sign = value < 0 ? \"-\" : \"\",\n string = (sign ? -value : value) + \"\",\n length = string.length;\n return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);\n }\n\n function requote(s) {\n return s.replace(requoteRe, \"\\\\$&\");\n }\n\n function formatRe(names) {\n return new RegExp(\"^(?:\" + names.map(requote).join(\"|\") + \")\", \"i\");\n }\n\n function formatLookup(names) {\n var map = {}, i = -1, n = names.length;\n while (++i < n) map[names[i].toLowerCase()] = i;\n return map;\n }\n\n function parseWeekdayNumberSunday(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 1));\n return n ? (d.w = +n[0], i + n[0].length) : -1;\n }\n\n function parseWeekdayNumberMonday(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 1));\n return n ? (d.u = +n[0], i + n[0].length) : -1;\n }\n\n function parseWeekNumberSunday(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.U = +n[0], i + n[0].length) : -1;\n }\n\n function parseWeekNumberISO(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.V = +n[0], i + n[0].length) : -1;\n }\n\n function parseWeekNumberMonday(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.W = +n[0], i + n[0].length) : -1;\n }\n\n function parseFullYear(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 4));\n return n ? (d.y = +n[0], i + n[0].length) : -1;\n }\n\n function parseYear(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1;\n }\n\n function parseZone(d, string, i) {\n var n = /^(Z)|([+-]\\d\\d)(?::?(\\d\\d))?/.exec(string.slice(i, i + 6));\n return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || \"00\")), i + n[0].length) : -1;\n }\n\n function parseQuarter(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 1));\n return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1;\n }\n\n function parseMonthNumber(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.m = n[0] - 1, i + n[0].length) : -1;\n }\n\n function parseDayOfMonth(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.d = +n[0], i + n[0].length) : -1;\n }\n\n function parseDayOfYear(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 3));\n return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1;\n }\n\n function parseHour24(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.H = +n[0], i + n[0].length) : -1;\n }\n\n function parseMinutes(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.M = +n[0], i + n[0].length) : -1;\n }\n\n function parseSeconds(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 2));\n return n ? (d.S = +n[0], i + n[0].length) : -1;\n }\n\n function parseMilliseconds(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 3));\n return n ? (d.L = +n[0], i + n[0].length) : -1;\n }\n\n function parseMicroseconds(d, string, i) {\n var n = numberRe.exec(string.slice(i, i + 6));\n return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1;\n }\n\n function parseLiteralPercent(d, string, i) {\n var n = percentRe.exec(string.slice(i, i + 1));\n return n ? i + n[0].length : -1;\n }\n\n function parseUnixTimestamp(d, string, i) {\n var n = numberRe.exec(string.slice(i));\n return n ? (d.Q = +n[0], i + n[0].length) : -1;\n }\n\n function parseUnixTimestampSeconds(d, string, i) {\n var n = numberRe.exec(string.slice(i));\n return n ? (d.s = +n[0], i + n[0].length) : -1;\n }\n\n function formatDayOfMonth(d, p) {\n return pad(d.getDate(), p, 2);\n }\n\n function formatHour24(d, p) {\n return pad(d.getHours(), p, 2);\n }\n\n function formatHour12(d, p) {\n return pad(d.getHours() % 12 || 12, p, 2);\n }\n\n function formatDayOfYear(d, p) {\n return pad(1 + day.count(year(d), d), p, 3);\n }\n\n function formatMilliseconds(d, p) {\n return pad(d.getMilliseconds(), p, 3);\n }\n\n function formatMicroseconds(d, p) {\n return formatMilliseconds(d, p) + \"000\";\n }\n\n function formatMonthNumber(d, p) {\n return pad(d.getMonth() + 1, p, 2);\n }\n\n function formatMinutes(d, p) {\n return pad(d.getMinutes(), p, 2);\n }\n\n function formatSeconds(d, p) {\n return pad(d.getSeconds(), p, 2);\n }\n\n function formatWeekdayNumberMonday(d) {\n var day = d.getDay();\n return day === 0 ? 7 : day;\n }\n\n function formatWeekNumberSunday(d, p) {\n return pad(sunday.count(year(d) - 1, d), p, 2);\n }\n\n function formatWeekNumberISO(d, p) {\n var day = d.getDay();\n d = (day >= 4 || day === 0) ? thursday(d) : thursday.ceil(d);\n return pad(thursday.count(year(d), d) + (year(d).getDay() === 4), p, 2);\n }\n\n function formatWeekdayNumberSunday(d) {\n return d.getDay();\n }\n\n function formatWeekNumberMonday(d, p) {\n return pad(monday.count(year(d) - 1, d), p, 2);\n }\n\n function formatYear(d, p) {\n return pad(d.getFullYear() % 100, p, 2);\n }\n\n function formatFullYear(d, p) {\n return pad(d.getFullYear() % 10000, p, 4);\n }\n\n function formatZone(d) {\n var z = d.getTimezoneOffset();\n return (z > 0 ? \"-\" : (z *= -1, \"+\"))\n + pad(z / 60 | 0, \"0\", 2)\n + pad(z % 60, \"0\", 2);\n }\n\n function formatUTCDayOfMonth(d, p) {\n return pad(d.getUTCDate(), p, 2);\n }\n\n function formatUTCHour24(d, p) {\n return pad(d.getUTCHours(), p, 2);\n }\n\n function formatUTCHour12(d, p) {\n return pad(d.getUTCHours() % 12 || 12, p, 2);\n }\n\n function formatUTCDayOfYear(d, p) {\n return pad(1 + utcDay.count(utcYear(d), d), p, 3);\n }\n\n function formatUTCMilliseconds(d, p) {\n return pad(d.getUTCMilliseconds(), p, 3);\n }\n\n function formatUTCMicroseconds(d, p) {\n return formatUTCMilliseconds(d, p) + \"000\";\n }\n\n function formatUTCMonthNumber(d, p) {\n return pad(d.getUTCMonth() + 1, p, 2);\n }\n\n function formatUTCMinutes(d, p) {\n return pad(d.getUTCMinutes(), p, 2);\n }\n\n function formatUTCSeconds(d, p) {\n return pad(d.getUTCSeconds(), p, 2);\n }\n\n function formatUTCWeekdayNumberMonday(d) {\n var dow = d.getUTCDay();\n return dow === 0 ? 7 : dow;\n }\n\n function formatUTCWeekNumberSunday(d, p) {\n return pad(utcSunday.count(utcYear(d) - 1, d), p, 2);\n }\n\n function formatUTCWeekNumberISO(d, p) {\n var day = d.getUTCDay();\n d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d);\n return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2);\n }\n\n function formatUTCWeekdayNumberSunday(d) {\n return d.getUTCDay();\n }\n\n function formatUTCWeekNumberMonday(d, p) {\n return pad(utcMonday.count(utcYear(d) - 1, d), p, 2);\n }\n\n function formatUTCYear(d, p) {\n return pad(d.getUTCFullYear() % 100, p, 2);\n }\n\n function formatUTCFullYear(d, p) {\n return pad(d.getUTCFullYear() % 10000, p, 4);\n }\n\n function formatUTCZone() {\n return \"+0000\";\n }\n\n function formatLiteralPercent() {\n return \"%\";\n }\n\n function formatUnixTimestamp(d) {\n return +d;\n }\n\n function formatUnixTimestampSeconds(d) {\n return Math.floor(+d / 1000);\n }\n\n var locale$1;\n var timeFormat;\n var timeParse;\n var utcFormat;\n var utcParse;\n\n defaultLocale$1({\n dateTime: \"%x, %X\",\n date: \"%-m/%-d/%Y\",\n time: \"%-I:%M:%S %p\",\n periods: [\"AM\", \"PM\"],\n days: [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"],\n shortDays: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n months: [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"],\n shortMonths: [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\n });\n\n function defaultLocale$1(definition) {\n locale$1 = formatLocale$1(definition);\n timeFormat = locale$1.format;\n timeParse = locale$1.parse;\n utcFormat = locale$1.utcFormat;\n utcParse = locale$1.utcParse;\n return locale$1;\n }\n\n var isoSpecifier = \"%Y-%m-%dT%H:%M:%S.%LZ\";\n\n function formatIsoNative(date) {\n return date.toISOString();\n }\n\n var formatIso = Date.prototype.toISOString\n ? formatIsoNative\n : utcFormat(isoSpecifier);\n\n function parseIsoNative(string) {\n var date = new Date(string);\n return isNaN(date) ? null : date;\n }\n\n var parseIso = +new Date(\"2000-01-01T00:00:00.000Z\")\n ? parseIsoNative\n : utcParse(isoSpecifier);\n\n var noop = {value: function() {}};\n\n function dispatch() {\n for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {\n if (!(t = arguments[i] + \"\") || (t in _) || /[\\s.]/.test(t)) throw new Error(\"illegal type: \" + t);\n _[t] = [];\n }\n return new Dispatch(_);\n }\n\n function Dispatch(_) {\n this._ = _;\n }\n\n function parseTypenames(typenames, types) {\n return typenames.trim().split(/^|\\s+/).map(function(t) {\n var name = \"\", i = t.indexOf(\".\");\n if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);\n if (t && !types.hasOwnProperty(t)) throw new Error(\"unknown type: \" + t);\n return {type: t, name: name};\n });\n }\n\n Dispatch.prototype = dispatch.prototype = {\n constructor: Dispatch,\n on: function(typename, callback) {\n var _ = this._,\n T = parseTypenames(typename + \"\", _),\n t,\n i = -1,\n n = T.length;\n\n // If no callback was specified, return the callback of the given type and name.\n if (arguments.length < 2) {\n while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;\n return;\n }\n\n // If a type was specified, set the callback for the given type and name.\n // Otherwise, if a null callback was specified, remove callbacks of the given name.\n if (callback != null && typeof callback !== \"function\") throw new Error(\"invalid callback: \" + callback);\n while (++i < n) {\n if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);\n else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);\n }\n\n return this;\n },\n copy: function() {\n var copy = {}, _ = this._;\n for (var t in _) copy[t] = _[t].slice();\n return new Dispatch(copy);\n },\n call: function(type, that) {\n if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];\n if (!this._.hasOwnProperty(type)) throw new Error(\"unknown type: \" + type);\n for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);\n },\n apply: function(type, that, args) {\n if (!this._.hasOwnProperty(type)) throw new Error(\"unknown type: \" + type);\n for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);\n }\n };\n\n function get(type, name) {\n for (var i = 0, n = type.length, c; i < n; ++i) {\n if ((c = type[i]).name === name) {\n return c.value;\n }\n }\n }\n\n function set(type, name, callback) {\n for (var i = 0, n = type.length; i < n; ++i) {\n if (type[i].name === name) {\n type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));\n break;\n }\n }\n if (callback != null) type.push({name: name, value: callback});\n return type;\n }\n\n var xhtml = \"http://www.w3.org/1999/xhtml\";\n\n var namespaces = {\n svg: \"http://www.w3.org/2000/svg\",\n xhtml: xhtml,\n xlink: \"http://www.w3.org/1999/xlink\",\n xml: \"http://www.w3.org/XML/1998/namespace\",\n xmlns: \"http://www.w3.org/2000/xmlns/\"\n };\n\n function namespace(name) {\n var prefix = name += \"\", i = prefix.indexOf(\":\");\n if (i >= 0 && (prefix = name.slice(0, i)) !== \"xmlns\") name = name.slice(i + 1);\n return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name;\n }\n\n function creatorInherit(name) {\n return function() {\n var document = this.ownerDocument,\n uri = this.namespaceURI;\n return uri === xhtml && document.documentElement.namespaceURI === xhtml\n ? document.createElement(name)\n : document.createElementNS(uri, name);\n };\n }\n\n function creatorFixed(fullname) {\n return function() {\n return this.ownerDocument.createElementNS(fullname.space, fullname.local);\n };\n }\n\n function creator(name) {\n var fullname = namespace(name);\n return (fullname.local\n ? creatorFixed\n : creatorInherit)(fullname);\n }\n\n function none() {}\n\n function selector(selector) {\n return selector == null ? none : function() {\n return this.querySelector(selector);\n };\n }\n\n function selection_select(select) {\n if (typeof select !== \"function\") select = selector(select);\n\n for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {\n for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {\n if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {\n if (\"__data__\" in node) subnode.__data__ = node.__data__;\n subgroup[i] = subnode;\n }\n }\n }\n\n return new Selection(subgroups, this._parents);\n }\n\n function empty() {\n return [];\n }\n\n function selectorAll(selector) {\n return selector == null ? empty : function() {\n return this.querySelectorAll(selector);\n };\n }\n\n function selection_selectAll(select) {\n if (typeof select !== \"function\") select = selectorAll(select);\n\n for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {\n for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {\n if (node = group[i]) {\n subgroups.push(select.call(node, node.__data__, i, group));\n parents.push(node);\n }\n }\n }\n\n return new Selection(subgroups, parents);\n }\n\n function matcher(selector) {\n return function() {\n return this.matches(selector);\n };\n }\n\n function selection_filter(match) {\n if (typeof match !== \"function\") match = matcher(match);\n\n for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {\n for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {\n if ((node = group[i]) && match.call(node, node.__data__, i, group)) {\n subgroup.push(node);\n }\n }\n }\n\n return new Selection(subgroups, this._parents);\n }\n\n function sparse(update) {\n return new Array(update.length);\n }\n\n function selection_enter() {\n return new Selection(this._enter || this._groups.map(sparse), this._parents);\n }\n\n function EnterNode(parent, datum) {\n this.ownerDocument = parent.ownerDocument;\n this.namespaceURI = parent.namespaceURI;\n this._next = null;\n this._parent = parent;\n this.__data__ = datum;\n }\n\n EnterNode.prototype = {\n constructor: EnterNode,\n appendChild: function(child) { return this._parent.insertBefore(child, this._next); },\n insertBefore: function(child, next) { return this._parent.insertBefore(child, next); },\n querySelector: function(selector) { return this._parent.querySelector(selector); },\n querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); }\n };\n\n function constant$2(x) {\n return function() {\n return x;\n };\n }\n\n var keyPrefix = \"$\"; // Protect against keys like “__proto__”.\n\n function bindIndex(parent, group, enter, update, exit, data) {\n var i = 0,\n node,\n groupLength = group.length,\n dataLength = data.length;\n\n // Put any non-null nodes that fit into update.\n // Put any null nodes into enter.\n // Put any remaining data into enter.\n for (; i < dataLength; ++i) {\n if (node = group[i]) {\n node.__data__ = data[i];\n update[i] = node;\n } else {\n enter[i] = new EnterNode(parent, data[i]);\n }\n }\n\n // Put any non-null nodes that don’t fit into exit.\n for (; i < groupLength; ++i) {\n if (node = group[i]) {\n exit[i] = node;\n }\n }\n }\n\n function bindKey(parent, group, enter, update, exit, data, key) {\n var i,\n node,\n nodeByKeyValue = {},\n groupLength = group.length,\n dataLength = data.length,\n keyValues = new Array(groupLength),\n keyValue;\n\n // Compute the key for each node.\n // If multiple nodes have the same key, the duplicates are added to exit.\n for (i = 0; i < groupLength; ++i) {\n if (node = group[i]) {\n keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group);\n if (keyValue in nodeByKeyValue) {\n exit[i] = node;\n } else {\n nodeByKeyValue[keyValue] = node;\n }\n }\n }\n\n // Compute the key for each datum.\n // If there a node associated with this key, join and add it to update.\n // If there is not (or the key is a duplicate), add it to enter.\n for (i = 0; i < dataLength; ++i) {\n keyValue = keyPrefix + key.call(parent, data[i], i, data);\n if (node = nodeByKeyValue[keyValue]) {\n update[i] = node;\n node.__data__ = data[i];\n nodeByKeyValue[keyValue] = null;\n } else {\n enter[i] = new EnterNode(parent, data[i]);\n }\n }\n\n // Add any remaining nodes that were not bound to data to exit.\n for (i = 0; i < groupLength; ++i) {\n if ((node = group[i]) && (nodeByKeyValue[keyValues[i]] === node)) {\n exit[i] = node;\n }\n }\n }\n\n function selection_data(value, key) {\n if (!value) {\n data = new Array(this.size()), j = -1;\n this.each(function(d) { data[++j] = d; });\n return data;\n }\n\n var bind = key ? bindKey : bindIndex,\n parents = this._parents,\n groups = this._groups;\n\n if (typeof value !== \"function\") value = constant$2(value);\n\n for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {\n var parent = parents[j],\n group = groups[j],\n groupLength = group.length,\n data = value.call(parent, parent && parent.__data__, j, parents),\n dataLength = data.length,\n enterGroup = enter[j] = new Array(dataLength),\n updateGroup = update[j] = new Array(dataLength),\n exitGroup = exit[j] = new Array(groupLength);\n\n bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);\n\n // Now connect the enter nodes to their following update node, such that\n // appendChild can insert the materialized enter node before this node,\n // rather than at the end of the parent node.\n for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {\n if (previous = enterGroup[i0]) {\n if (i0 >= i1) i1 = i0 + 1;\n while (!(next = updateGroup[i1]) && ++i1 < dataLength);\n previous._next = next || null;\n }\n }\n }\n\n update = new Selection(update, parents);\n update._enter = enter;\n update._exit = exit;\n return update;\n }\n\n function selection_exit() {\n return new Selection(this._exit || this._groups.map(sparse), this._parents);\n }\n\n function selection_join(onenter, onupdate, onexit) {\n var enter = this.enter(), update = this, exit = this.exit();\n enter = typeof onenter === \"function\" ? onenter(enter) : enter.append(onenter + \"\");\n if (onupdate != null) update = onupdate(update);\n if (onexit == null) exit.remove(); else onexit(exit);\n return enter && update ? enter.merge(update).order() : update;\n }\n\n function selection_merge(selection) {\n\n for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {\n for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {\n if (node = group0[i] || group1[i]) {\n merge[i] = node;\n }\n }\n }\n\n for (; j < m0; ++j) {\n merges[j] = groups0[j];\n }\n\n return new Selection(merges, this._parents);\n }\n\n function selection_order() {\n\n for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {\n for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {\n if (node = group[i]) {\n if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);\n next = node;\n }\n }\n }\n\n return this;\n }\n\n function selection_sort(compare) {\n if (!compare) compare = ascending$1;\n\n function compareNode(a, b) {\n return a && b ? compare(a.__data__, b.__data__) : !a - !b;\n }\n\n for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {\n for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {\n if (node = group[i]) {\n sortgroup[i] = node;\n }\n }\n sortgroup.sort(compareNode);\n }\n\n return new Selection(sortgroups, this._parents).order();\n }\n\n function ascending$1(a, b) {\n return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;\n }\n\n function selection_call() {\n var callback = arguments[0];\n arguments[0] = this;\n callback.apply(null, arguments);\n return this;\n }\n\n function selection_nodes() {\n var nodes = new Array(this.size()), i = -1;\n this.each(function() { nodes[++i] = this; });\n return nodes;\n }\n\n function selection_node() {\n\n for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {\n for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {\n var node = group[i];\n if (node) return node;\n }\n }\n\n return null;\n }\n\n function selection_size() {\n var size = 0;\n this.each(function() { ++size; });\n return size;\n }\n\n function selection_empty() {\n return !this.node();\n }\n\n function selection_each(callback) {\n\n for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {\n for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {\n if (node = group[i]) callback.call(node, node.__data__, i, group);\n }\n }\n\n return this;\n }\n\n function attrRemove(name) {\n return function() {\n this.removeAttribute(name);\n };\n }\n\n function attrRemoveNS(fullname) {\n return function() {\n this.removeAttributeNS(fullname.space, fullname.local);\n };\n }\n\n function attrConstant(name, value) {\n return function() {\n this.setAttribute(name, value);\n };\n }\n\n function attrConstantNS(fullname, value) {\n return function() {\n this.setAttributeNS(fullname.space, fullname.local, value);\n };\n }\n\n function attrFunction(name, value) {\n return function() {\n var v = value.apply(this, arguments);\n if (v == null) this.removeAttribute(name);\n else this.setAttribute(name, v);\n };\n }\n\n function attrFunctionNS(fullname, value) {\n return function() {\n var v = value.apply(this, arguments);\n if (v == null) this.removeAttributeNS(fullname.space, fullname.local);\n else this.setAttributeNS(fullname.space, fullname.local, v);\n };\n }\n\n function selection_attr(name, value) {\n var fullname = namespace(name);\n\n if (arguments.length < 2) {\n var node = this.node();\n return fullname.local\n ? node.getAttributeNS(fullname.space, fullname.local)\n : node.getAttribute(fullname);\n }\n\n return this.each((value == null\n ? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === \"function\"\n ? (fullname.local ? attrFunctionNS : attrFunction)\n : (fullname.local ? attrConstantNS : attrConstant)))(fullname, value));\n }\n\n function defaultView(node) {\n return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node\n || (node.document && node) // node is a Window\n || node.defaultView; // node is a Document\n }\n\n function styleRemove(name) {\n return function() {\n this.style.removeProperty(name);\n };\n }\n\n function styleConstant(name, value, priority) {\n return function() {\n this.style.setProperty(name, value, priority);\n };\n }\n\n function styleFunction(name, value, priority) {\n return function() {\n var v = value.apply(this, arguments);\n if (v == null) this.style.removeProperty(name);\n else this.style.setProperty(name, v, priority);\n };\n }\n\n function selection_style(name, value, priority) {\n return arguments.length > 1\n ? this.each((value == null\n ? styleRemove : typeof value === \"function\"\n ? styleFunction\n : styleConstant)(name, value, priority == null ? \"\" : priority))\n : styleValue(this.node(), name);\n }\n\n function styleValue(node, name) {\n return node.style.getPropertyValue(name)\n || defaultView(node).getComputedStyle(node, null).getPropertyValue(name);\n }\n\n function propertyRemove(name) {\n return function() {\n delete this[name];\n };\n }\n\n function propertyConstant(name, value) {\n return function() {\n this[name] = value;\n };\n }\n\n function propertyFunction(name, value) {\n return function() {\n var v = value.apply(this, arguments);\n if (v == null) delete this[name];\n else this[name] = v;\n };\n }\n\n function selection_property(name, value) {\n return arguments.length > 1\n ? this.each((value == null\n ? propertyRemove : typeof value === \"function\"\n ? propertyFunction\n : propertyConstant)(name, value))\n : this.node()[name];\n }\n\n function classArray(string) {\n return string.trim().split(/^|\\s+/);\n }\n\n function classList(node) {\n return node.classList || new ClassList(node);\n }\n\n function ClassList(node) {\n this._node = node;\n this._names = classArray(node.getAttribute(\"class\") || \"\");\n }\n\n ClassList.prototype = {\n add: function(name) {\n var i = this._names.indexOf(name);\n if (i < 0) {\n this._names.push(name);\n this._node.setAttribute(\"class\", this._names.join(\" \"));\n }\n },\n remove: function(name) {\n var i = this._names.indexOf(name);\n if (i >= 0) {\n this._names.splice(i, 1);\n this._node.setAttribute(\"class\", this._names.join(\" \"));\n }\n },\n contains: function(name) {\n return this._names.indexOf(name) >= 0;\n }\n };\n\n function classedAdd(node, names) {\n var list = classList(node), i = -1, n = names.length;\n while (++i < n) list.add(names[i]);\n }\n\n function classedRemove(node, names) {\n var list = classList(node), i = -1, n = names.length;\n while (++i < n) list.remove(names[i]);\n }\n\n function classedTrue(names) {\n return function() {\n classedAdd(this, names);\n };\n }\n\n function classedFalse(names) {\n return function() {\n classedRemove(this, names);\n };\n }\n\n function classedFunction(names, value) {\n return function() {\n (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names);\n };\n }\n\n function selection_classed(name, value) {\n var names = classArray(name + \"\");\n\n if (arguments.length < 2) {\n var list = classList(this.node()), i = -1, n = names.length;\n while (++i < n) if (!list.contains(names[i])) return false;\n return true;\n }\n\n return this.each((typeof value === \"function\"\n ? classedFunction : value\n ? classedTrue\n : classedFalse)(names, value));\n }\n\n function textRemove() {\n this.textContent = \"\";\n }\n\n function textConstant(value) {\n return function() {\n this.textContent = value;\n };\n }\n\n function textFunction(value) {\n return function() {\n var v = value.apply(this, arguments);\n this.textContent = v == null ? \"\" : v;\n };\n }\n\n function selection_text(value) {\n return arguments.length\n ? this.each(value == null\n ? textRemove : (typeof value === \"function\"\n ? textFunction\n : textConstant)(value))\n : this.node().textContent;\n }\n\n function htmlRemove() {\n this.innerHTML = \"\";\n }\n\n function htmlConstant(value) {\n return function() {\n this.innerHTML = value;\n };\n }\n\n function htmlFunction(value) {\n return function() {\n var v = value.apply(this, arguments);\n this.innerHTML = v == null ? \"\" : v;\n };\n }\n\n function selection_html(value) {\n return arguments.length\n ? this.each(value == null\n ? htmlRemove : (typeof value === \"function\"\n ? htmlFunction\n : htmlConstant)(value))\n : this.node().innerHTML;\n }\n\n function raise() {\n if (this.nextSibling) this.parentNode.appendChild(this);\n }\n\n function selection_raise() {\n return this.each(raise);\n }\n\n function lower() {\n if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);\n }\n\n function selection_lower() {\n return this.each(lower);\n }\n\n function selection_append(name) {\n var create = typeof name === \"function\" ? name : creator(name);\n return this.select(function() {\n return this.appendChild(create.apply(this, arguments));\n });\n }\n\n function constantNull() {\n return null;\n }\n\n function selection_insert(name, before) {\n var create = typeof name === \"function\" ? name : creator(name),\n select = before == null ? constantNull : typeof before === \"function\" ? before : selector(before);\n return this.select(function() {\n return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);\n });\n }\n\n function remove() {\n var parent = this.parentNode;\n if (parent) parent.removeChild(this);\n }\n\n function selection_remove() {\n return this.each(remove);\n }\n\n function selection_cloneShallow() {\n var clone = this.cloneNode(false), parent = this.parentNode;\n return parent ? parent.insertBefore(clone, this.nextSibling) : clone;\n }\n\n function selection_cloneDeep() {\n var clone = this.cloneNode(true), parent = this.parentNode;\n return parent ? parent.insertBefore(clone, this.nextSibling) : clone;\n }\n\n function selection_clone(deep) {\n return this.select(deep ? selection_cloneDeep : selection_cloneShallow);\n }\n\n function selection_datum(value) {\n return arguments.length\n ? this.property(\"__data__\", value)\n : this.node().__data__;\n }\n\n var filterEvents = {};\n\n var event = null;\n\n if (typeof document !== \"undefined\") {\n var element = document.documentElement;\n if (!(\"onmouseenter\" in element)) {\n filterEvents = {mouseenter: \"mouseover\", mouseleave: \"mouseout\"};\n }\n }\n\n function filterContextListener(listener, index, group) {\n listener = contextListener(listener, index, group);\n return function(event) {\n var related = event.relatedTarget;\n if (!related || (related !== this && !(related.compareDocumentPosition(this) & 8))) {\n listener.call(this, event);\n }\n };\n }\n\n function contextListener(listener, index, group) {\n return function(event1) {\n var event0 = event; // Events can be reentrant (e.g., focus).\n event = event1;\n try {\n listener.call(this, this.__data__, index, group);\n } finally {\n event = event0;\n }\n };\n }\n\n function parseTypenames$1(typenames) {\n return typenames.trim().split(/^|\\s+/).map(function(t) {\n var name = \"\", i = t.indexOf(\".\");\n if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);\n return {type: t, name: name};\n });\n }\n\n function onRemove(typename) {\n return function() {\n var on = this.__on;\n if (!on) return;\n for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {\n if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {\n this.removeEventListener(o.type, o.listener, o.capture);\n } else {\n on[++i] = o;\n }\n }\n if (++i) on.length = i;\n else delete this.__on;\n };\n }\n\n function onAdd(typename, value, capture) {\n var wrap = filterEvents.hasOwnProperty(typename.type) ? filterContextListener : contextListener;\n return function(d, i, group) {\n var on = this.__on, o, listener = wrap(value, i, group);\n if (on) for (var j = 0, m = on.length; j < m; ++j) {\n if ((o = on[j]).type === typename.type && o.name === typename.name) {\n this.removeEventListener(o.type, o.listener, o.capture);\n this.addEventListener(o.type, o.listener = listener, o.capture = capture);\n o.value = value;\n return;\n }\n }\n this.addEventListener(typename.type, listener, capture);\n o = {type: typename.type, name: typename.name, value: value, listener: listener, capture: capture};\n if (!on) this.__on = [o];\n else on.push(o);\n };\n }\n\n function selection_on(typename, value, capture) {\n var typenames = parseTypenames$1(typename + \"\"), i, n = typenames.length, t;\n\n if (arguments.length < 2) {\n var on = this.node().__on;\n if (on) for (var j = 0, m = on.length, o; j < m; ++j) {\n for (i = 0, o = on[j]; i < n; ++i) {\n if ((t = typenames[i]).type === o.type && t.name === o.name) {\n return o.value;\n }\n }\n }\n return;\n }\n\n on = value ? onAdd : onRemove;\n if (capture == null) capture = false;\n for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture));\n return this;\n }\n\n function customEvent(event1, listener, that, args) {\n var event0 = event;\n event1.sourceEvent = event;\n event = event1;\n try {\n return listener.apply(that, args);\n } finally {\n event = event0;\n }\n }\n\n function dispatchEvent(node, type, params) {\n var window = defaultView(node),\n event = window.CustomEvent;\n\n if (typeof event === \"function\") {\n event = new event(type, params);\n } else {\n event = window.document.createEvent(\"Event\");\n if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;\n else event.initEvent(type, false, false);\n }\n\n node.dispatchEvent(event);\n }\n\n function dispatchConstant(type, params) {\n return function() {\n return dispatchEvent(this, type, params);\n };\n }\n\n function dispatchFunction(type, params) {\n return function() {\n return dispatchEvent(this, type, params.apply(this, arguments));\n };\n }\n\n function selection_dispatch(type, params) {\n return this.each((typeof params === \"function\"\n ? dispatchFunction\n : dispatchConstant)(type, params));\n }\n\n var root = [null];\n\n function Selection(groups, parents) {\n this._groups = groups;\n this._parents = parents;\n }\n\n function selection() {\n return new Selection([[document.documentElement]], root);\n }\n\n Selection.prototype = selection.prototype = {\n constructor: Selection,\n select: selection_select,\n selectAll: selection_selectAll,\n filter: selection_filter,\n data: selection_data,\n enter: selection_enter,\n exit: selection_exit,\n join: selection_join,\n merge: selection_merge,\n order: selection_order,\n sort: selection_sort,\n call: selection_call,\n nodes: selection_nodes,\n node: selection_node,\n size: selection_size,\n empty: selection_empty,\n each: selection_each,\n attr: selection_attr,\n style: selection_style,\n property: selection_property,\n classed: selection_classed,\n text: selection_text,\n html: selection_html,\n raise: selection_raise,\n lower: selection_lower,\n append: selection_append,\n insert: selection_insert,\n remove: selection_remove,\n clone: selection_clone,\n datum: selection_datum,\n on: selection_on,\n dispatch: selection_dispatch\n };\n\n function select(selector) {\n return typeof selector === \"string\"\n ? new Selection([[document.querySelector(selector)]], [document.documentElement])\n : new Selection([[selector]], root);\n }\n\n function sourceEvent() {\n var current = event, source;\n while (source = current.sourceEvent) current = source;\n return current;\n }\n\n function point(node, event) {\n var svg = node.ownerSVGElement || node;\n\n if (svg.createSVGPoint) {\n var point = svg.createSVGPoint();\n point.x = event.clientX, point.y = event.clientY;\n point = point.matrixTransform(node.getScreenCTM().inverse());\n return [point.x, point.y];\n }\n\n var rect = node.getBoundingClientRect();\n return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];\n }\n\n function mouse(node) {\n var event = sourceEvent();\n if (event.changedTouches) event = event.changedTouches[0];\n return point(node, event);\n }\n\n function touch(node, touches, identifier) {\n if (arguments.length < 3) identifier = touches, touches = sourceEvent().changedTouches;\n\n for (var i = 0, n = touches ? touches.length : 0, touch; i < n; ++i) {\n if ((touch = touches[i]).identifier === identifier) {\n return point(node, touch);\n }\n }\n\n return null;\n }\n\n function nopropagation() {\n event.stopImmediatePropagation();\n }\n\n function noevent() {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n\n function nodrag(view) {\n var root = view.document.documentElement,\n selection = select(view).on(\"dragstart.drag\", noevent, true);\n if (\"onselectstart\" in root) {\n selection.on(\"selectstart.drag\", noevent, true);\n } else {\n root.__noselect = root.style.MozUserSelect;\n root.style.MozUserSelect = \"none\";\n }\n }\n\n function yesdrag(view, noclick) {\n var root = view.document.documentElement,\n selection = select(view).on(\"dragstart.drag\", null);\n if (noclick) {\n selection.on(\"click.drag\", noevent, true);\n setTimeout(function() { selection.on(\"click.drag\", null); }, 0);\n }\n if (\"onselectstart\" in root) {\n selection.on(\"selectstart.drag\", null);\n } else {\n root.style.MozUserSelect = root.__noselect;\n delete root.__noselect;\n }\n }\n\n function constant$3(x) {\n return function() {\n return x;\n };\n }\n\n function DragEvent(target, type, subject, id, active, x, y, dx, dy, dispatch) {\n this.target = target;\n this.type = type;\n this.subject = subject;\n this.identifier = id;\n this.active = active;\n this.x = x;\n this.y = y;\n this.dx = dx;\n this.dy = dy;\n this._ = dispatch;\n }\n\n DragEvent.prototype.on = function() {\n var value = this._.on.apply(this._, arguments);\n return value === this._ ? this : value;\n };\n\n // Ignore right-click, since that should open the context menu.\n function defaultFilter() {\n return !event.ctrlKey && !event.button;\n }\n\n function defaultContainer() {\n return this.parentNode;\n }\n\n function defaultSubject(d) {\n return d == null ? {x: event.x, y: event.y} : d;\n }\n\n function defaultTouchable() {\n return navigator.maxTouchPoints || (\"ontouchstart\" in this);\n }\n\n function drag() {\n var filter = defaultFilter,\n container = defaultContainer,\n subject = defaultSubject,\n touchable = defaultTouchable,\n gestures = {},\n listeners = dispatch(\"start\", \"drag\", \"end\"),\n active = 0,\n mousedownx,\n mousedowny,\n mousemoving,\n touchending,\n clickDistance2 = 0;\n\n function drag(selection) {\n selection\n .on(\"mousedown.drag\", mousedowned)\n .filter(touchable)\n .on(\"touchstart.drag\", touchstarted)\n .on(\"touchmove.drag\", touchmoved)\n .on(\"touchend.drag touchcancel.drag\", touchended)\n .style(\"touch-action\", \"none\")\n .style(\"-webkit-tap-highlight-color\", \"rgba(0,0,0,0)\");\n }\n\n function mousedowned() {\n if (touchending || !filter.apply(this, arguments)) return;\n var gesture = beforestart(\"mouse\", container.apply(this, arguments), mouse, this, arguments);\n if (!gesture) return;\n select(event.view).on(\"mousemove.drag\", mousemoved, true).on(\"mouseup.drag\", mouseupped, true);\n nodrag(event.view);\n nopropagation();\n mousemoving = false;\n mousedownx = event.clientX;\n mousedowny = event.clientY;\n gesture(\"start\");\n }\n\n function mousemoved() {\n noevent();\n if (!mousemoving) {\n var dx = event.clientX - mousedownx, dy = event.clientY - mousedowny;\n mousemoving = dx * dx + dy * dy > clickDistance2;\n }\n gestures.mouse(\"drag\");\n }\n\n function mouseupped() {\n select(event.view).on(\"mousemove.drag mouseup.drag\", null);\n yesdrag(event.view, mousemoving);\n noevent();\n gestures.mouse(\"end\");\n }\n\n function touchstarted() {\n if (!filter.apply(this, arguments)) return;\n var touches = event.changedTouches,\n c = container.apply(this, arguments),\n n = touches.length, i, gesture;\n\n for (i = 0; i < n; ++i) {\n if (gesture = beforestart(touches[i].identifier, c, touch, this, arguments)) {\n nopropagation();\n gesture(\"start\");\n }\n }\n }\n\n function touchmoved() {\n var touches = event.changedTouches,\n n = touches.length, i, gesture;\n\n for (i = 0; i < n; ++i) {\n if (gesture = gestures[touches[i].identifier]) {\n noevent();\n gesture(\"drag\");\n }\n }\n }\n\n function touchended() {\n var touches = event.changedTouches,\n n = touches.length, i, gesture;\n\n if (touchending) clearTimeout(touchending);\n touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed!\n for (i = 0; i < n; ++i) {\n if (gesture = gestures[touches[i].identifier]) {\n nopropagation();\n gesture(\"end\");\n }\n }\n }\n\n function beforestart(id, container, point, that, args) {\n var p = point(container, id), s, dx, dy,\n sublisteners = listeners.copy();\n\n if (!customEvent(new DragEvent(drag, \"beforestart\", s, id, active, p[0], p[1], 0, 0, sublisteners), function() {\n if ((event.subject = s = subject.apply(that, args)) == null) return false;\n dx = s.x - p[0] || 0;\n dy = s.y - p[1] || 0;\n return true;\n })) return;\n\n return function gesture(type) {\n var p0 = p, n;\n switch (type) {\n case \"start\": gestures[id] = gesture, n = active++; break;\n case \"end\": delete gestures[id], --active; // nobreak\n case \"drag\": p = point(container, id), n = active; break;\n }\n customEvent(new DragEvent(drag, type, s, id, n, p[0] + dx, p[1] + dy, p[0] - p0[0], p[1] - p0[1], sublisteners), sublisteners.apply, sublisteners, [type, that, args]);\n };\n }\n\n drag.filter = function(_) {\n return arguments.length ? (filter = typeof _ === \"function\" ? _ : constant$3(!!_), drag) : filter;\n };\n\n drag.container = function(_) {\n return arguments.length ? (container = typeof _ === \"function\" ? _ : constant$3(_), drag) : container;\n };\n\n drag.subject = function(_) {\n return arguments.length ? (subject = typeof _ === \"function\" ? _ : constant$3(_), drag) : subject;\n };\n\n drag.touchable = function(_) {\n return arguments.length ? (touchable = typeof _ === \"function\" ? _ : constant$3(!!_), drag) : touchable;\n };\n\n drag.on = function() {\n var value = listeners.on.apply(listeners, arguments);\n return value === listeners ? drag : value;\n };\n\n drag.clickDistance = function(_) {\n return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2);\n };\n\n return drag;\n }\n\n // Copyright 2018 The Distill Template Authors\n\n const T$a = Template('d-slider', `\n\n\n ' + newLine + '
';\n } else {\n newLine += '
';\n }\n ToC += newLine;\n\n }\n\n ToC += 'Updates and Corrections
\n Reuse
\n Citation
\n ${frontMatter.concatenatedAuthors}, \"${frontMatter.title}\", Distill, ${frontMatter.publishedYear}.
\n ${serializeFrontmatterToBibtex(frontMatter)}
\n `;\n }\n\n return html;\n }\n\n class DistillAppendix extends HTMLElement {\n\n static get is() { return 'distill-appendix'; }\n\n set frontMatter(frontMatter) {\n this.innerHTML = appendixTemplate(frontMatter);\n }\n\n }\n\n const footerTemplate = `\n\n\n \n\n`;\n\n // Copyright 2018 The Distill Template Authors\n\n const T$c = Template('distill-footer', footerTemplate);\n\n class DistillFooter extends T$c(HTMLElement) {\n\n }\n\n // Copyright 2018 The Distill Template Authors\n\n let templateIsLoading = false;\n let runlevel = 0;\n const initialize = function() {\n if (window.distill.runlevel < 1) {\n throw new Error(\"Insufficient Runlevel for Distill Template!\");\n }\n\n /* 1. Flag that we're being loaded */\n if (\"distill\" in window && window.distill.templateIsLoading) {\n throw new Error(\n \"Runlevel 1: Distill Template is getting loaded more than once, aborting!\"\n );\n } else {\n window.distill.templateIsLoading = true;\n console.debug(\"Runlevel 1: Distill Template has started loading.\");\n }\n\n /* 2. Add styles if they weren't added during prerendering */\n makeStyleTag(document);\n console.debug(\"Runlevel 1: Static Distill styles have been added.\");\n console.debug(\"Runlevel 1->2.\");\n window.distill.runlevel += 1;\n\n /* 3. Register Controller listener functions */\n /* Needs to happen before components to their connected callbacks have a controller to talk to. */\n for (const [functionName, callback] of Object.entries(Controller.listeners)) {\n if (typeof callback === \"function\") {\n document.addEventListener(functionName, callback);\n } else {\n console.error(\"Runlevel 2: Controller listeners need to be functions!\");\n }\n }\n console.debug(\"Runlevel 2: We can now listen to controller events.\");\n console.debug(\"Runlevel 2->3.\");\n window.distill.runlevel += 1;\n\n /* 4. Register components */\n const components = [\n Abstract, Appendix, Article, Bibliography, Byline, Cite, CitationList, Code,\n Footnote, FootnoteList, FrontMatter$1, HoverBox, Title, DMath, References, TOC, Figure,\n Slider, Interstitial\n ];\n\n const distillComponents = [DistillHeader, DistillAppendix, DistillFooter];\n\n if (window.distill.runlevel < 2) {\n throw new Error(\"Insufficient Runlevel for adding custom elements!\");\n }\n const allComponents = components.concat(distillComponents);\n for (const component of allComponents) {\n console.debug(\"Runlevel 2: Registering custom element: \" + component.is);\n customElements.define(component.is, component);\n }\n\n console.debug(\n \"Runlevel 3: Distill Template finished registering custom elements.\"\n );\n console.debug(\"Runlevel 3->4.\");\n window.distill.runlevel += 1;\n\n // If template was added after DOMContentLoaded we may have missed that event.\n // Controller will check for that case, so trigger the event explicitly:\n if (domContentLoaded()) {\n Controller.listeners.DOMContentLoaded();\n }\n\n console.debug(\"Runlevel 4: Distill Template initialisation complete.\");\n window.distill.templateIsLoading = false;\n window.distill.templateHasLoaded = true;\n };\n\n window.distill = { runlevel, initialize, templateIsLoading };\n\n /* 0. Check browser feature support; synchronously polyfill if needed */\n if (Polyfills.browserSupportsAllFeatures()) {\n console.debug(\"Runlevel 0: No need for polyfills.\");\n console.debug(\"Runlevel 0->1.\");\n window.distill.runlevel += 1;\n window.distill.initialize();\n } else {\n console.debug(\"Runlevel 0: Distill Template is loading polyfills.\");\n Polyfills.load(window.distill.initialize);\n }\n\n})));\n//# sourceMappingURL=template.v2.js.map\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","// startup\n// Load entry module and return exports\n// This entry module used 'module' so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(2792);\n"],"names":["define","days","months","zeroPad","n","RFC","date","day","getDay","substring","paddedDate","getDate","month","getMonth","year","getFullYear","toString","hours","getUTCHours","minutes","getUTCMinutes","seconds","getUTCSeconds","concat","Author","_createClass","object","_classCallCheck","this","name","author","personalURL","authorURL","affiliation","affiliationURL","affiliations","key","get","names","split","slice","length","join","FrontMatter","title","description","authors","bibliography","Map","bibliographyParsed","citations","citationsCollected","journal","katex","doi","undefined","publishedDate","_url","distillPath","url","set","value","githubPath","_previewURL","updatedDate","toISOString","volume","publishedYear","Error","lastName","map","firstName","slug","toLowerCase","_this","citationKey","_bibliography","_typeof","property","hasOwnProperty","mapFromObject","target","Object","assign","Array","from","reduce","_ref","_ref2","_slicedToArray","objectFromMap","bibliographyEntries","githubUrl","previewURL","issue","publishedDateRFC","publishedMonth","publishedDay","publishedMonthPadded","publishedDayPadded","updatedDateRFC","concatenatedAuthors","bibtexAuthors","source","frontMatter","Mutating","superclass","_superclass","_class","_this2","_callSuper","options","childList","characterData","subtree","observer","MutationObserver","disconnect","renderIfPossible","observe","_inherits","_get","_getPrototypeOf","prototype","call","textContent","root","renderContent","console","error","constructor","Template","templateString","useShadow","arguments","template","document","createElement","innerHTML","window","ShadyCSS","prepareTemplate","_superclass2","_class2","_this3","clone","importNode","content","attachShadow","mode","shadowRoot","appendChild","hasAttribute","styleElement","insertBefore","firstChild","query","querySelector","querySelectorAll","math","findEndOfMath","delimiter","text","startIndex","index","braceLevel","delimLength","character","splitAtDelimiters","startData","leftDelim","rightDelim","display","finalData","i","type","data","lookingForLeft","currIndex","nextIndex","indexOf","push","rawData","renderMathInText","optionsCopy","delimiters","left","right","splitWithDelimiters","fragment","createDocumentFragment","createTextNode","tag","displayMode","setAttribute","e","ParseError","errorCallback","renderElem","elem","childNodes","childNode","nodeType","mightHaveMath","frag","replaceChild","ignoredTags","nodeName","defaultAutoRenderOptions","msg","err","renderMathInElement","delimiterStrings","flatMap","d","some","katexCSSTag","DMath","_Mutating","localOptions","katexOptions","katexAdded","addKatex","container","render","_katexOptions","katexLoadedCallback","_step","_iterator","_createForOfIteratorHelper","s","done","f","body","head","insertAdjacentHTML","scriptTag","src","async","onload","crossorigin","T","HTMLElement","collect_citations","_step2","dom","Set","_iterator2","_step3","_iterator3","getAttribute","k","trim","add","author_string","ent","sep","finalSep","name_strings","last","firsts","initials","replace","venue_string","cite","booktitle","number","pages","publisher","link_string","arxiv_match","exec","label","doi_string","new_line","bibliography_cite","fancy","title_string","hover_cite","a_str","v_str","Math","min","domContentLoaded","readyState","parseFrontmatter","element","firstElementChild","_step4","_iterator4","hasOldStyle","Boolean","hasNewStyle","warn","newAffiliation","_moveLegacyAffiliationFormatIntoArray","JSON","parse","inlineMathRendered","FrontMatter$1","_HTMLElement","_this4","entries","_step5","_iterator5","entry","notify","event","CustomEvent","detail","bubbles","dispatchEvent","_wrapNativeSuper","Controller","waitingOn","listeners","onCiteKeyCreated","_event$detail","citeTag","keys","numbers","onCiteKeyChanged","_step6","_iterator6","waitingCallback","citationListTag","_step7","_iterator7","log","onCiteKeyRemoved","onBibliographyChanged","_step8","_iterator8","debug","onFootnoteChanged","footnotesList","footnotes","onFrontMatterChanged","published","Date","String","authorObject","password","interstitial","parentElement","removeChild","article","byline","h1","hasPassword","inBrowser","onLocalhost","location","hostname","includes","appendix","footnoteList","citationList","optionalComponents","DOMContentLoaded","loaded","frontMatterTag","_step9","_iterator9","_step10","_iterator10","styles","base","addPolyfill","polyfill","polyfillLoadedCallback","script","onerror","polyfills","support","Element","Polyfills","every","poly","callback","_step11","polyfillLoaded","neededPolyfills","distillRunlevel","_iterator11","_neededPolyfills","filter","Abstract","_T$","selector","T$1","Appendix","_T$2","T$2","isOnlyWhitespace","Article","_HTMLElement2","_this5","mutations","_step12","_iterator12","_step13","_iterator13","addedNodes","addedNode","nodeValue","test","wrapper","parentNode","commonjsGlobal","globalThis","global","self","createCommonjsModule","fn","module","exports","bibtexParse","BibtexParser","notKey","pos","input","currentEntry","setInput","t","getEntries","isWhitespace","match","canCommentOut","skipWhitespace","tryMatch","matchAt","value_braces","bracecount","start","escaped","end","value_comment","str","brcktCnt","value_quotes","single_value","values","key_equals_value","key_value_list","kv","entry_body","directive","preamble","comment","bibtex","string","toJSON","b","toBibtex","json","out","entryType","entryTags","tags","jdx","normalizeTag","full","x","char","parseBibtex","_step14","_iterator14","_i","_Object$entries","_Object$entries$_i","Bibliography","_HTMLElement3","_this6","_step15","_iterator15","parseIfPossible","_this7","requestAnimationFrame","newBibtex","response","oldValue","newValue","_this8","oReq","XMLHttpRequest","receivedBibtex","responseType","open","send","Byline","_HTMLElement4","bylineTemplate","Cite","_T$3","_this9","_numbers","_entries","_this10","outerSpan","innerSpan","hoverBox","customElements","whenDefined","then","listen","displayNumbers","displayEntries","eventName","result","html","T$3","CitationList","_HTMLElement5","style","size","list","stylesTag","heading","id","className","_step16","_iterator16","_step16$value","listItem","renderCitationList","prism","Prism","_self","lang","uniqueId","_","manual","disableWorkerMessageHandler","util","encode","tokens","Token","alias","isArray","o","objId","obj","defineProperty","deepClone","visited","forEach","v","getLanguage","currentScript","stack","scripts","getElementsByTagName","languages","extend","redef","inside","before","insert","grammar","ret","token","newToken","old","DFS","propertyType","plugins","highlightAll","highlightAllUnder","env","hooks","run","elements","apply","highlightElement","language","parent","code","insertHighlightedCode","highlightedCode","Worker","worker","filename","onmessage","evt","postMessage","stringify","immediateClose","highlight","tokenize","rest","tokenList","LinkedList","addAfter","matchGrammar","array","node","next","tail","toArray","all","callbacks","matchedStr","greedy","startNode","startPos","oneshot","patterns","j","pattern","lookbehind","lookbehindLength","flags","RegExp","currentNode","removeCount","prev","lastIndex","to","p","after","removeFrom","removeRange","newNode","count","classes","attributes","aliases","addEventListener","message","close","highlightAutomaticallyCallback","defer","setTimeout","WorkerGlobalScope","markup","tagName","includedCdataInside","def","xml","mathml","svg","css","addInlined","clike","javascript","js","fileHighlight","Extensions","pre","extension","xhr","onreadystatechange","status","responseText","statusText","python","py","lua","envVars","insideString","bash","variable","toBeCopied","shell","go","inner","createInline","starAlternative","tableCell","tableRow","tableLine","markdown","punctuation","walkTokens","l","codeLang","codeBlock","cls","autoloader","valueOf","floor","random","loadLanguages","ele","getElementById","md","julia","Code","_Mutating2","languageName","codeTag","tabs","ShadowRoot","preTag","T$4","Footnote","_T$4","_this11","_this12","currentFootnoteId","IdString","host","span","T$5","FootnoteList","_T$5","_step17","_iterator17","footnote","backlink","href","T$6","HoverBox","_T$6","bindDivEvents","bindTriggerEvents","_this13","visible","showAtNode","stopTimeout","extendTimeout","stopPropagation","passive","hide","_this14","position","top","round","bbox","getBoundingClientRect","show","offsetLeft","width","offsetTop","height","timeout","clearTimeout","time","_this15","T$7","Title","_HTMLElement6","References","_T$7","T$8","TOC","_HTMLElement7","_this16","headings","_step18","ToC","_iterator18","el","isInTitle","isException","newLine","renderTOC","Figure","_HTMLElement8","_this17","_ready","_onscreen","_offscreen","loadsWhileScrolling","marginObserver","directObserver","unobserve","readyQueue","runReadyQueue","onscreen","_readyQueue","figure","sort","a","_seenOnScreen","pop","ready","_marginObserver","viewportHeight","innerHeight","margin","rootMargin","threshold","didObserveMarginIntersection","IntersectionObserver","_step19","_iterator19","isIntersecting","addToReadyQueue","_directObserver","didObserveDirectIntersection","_step20","_iterator20","offscreen","isScrolling","Interstitial","_T$8","_this18","shouldRemoveSelf","oninput","passwordChanged","Storage","localStorage","setItem","localStorageIdentifier","getItem","pathname","T$9","ascending","NaN","compare","bisectRight","lo","hi","mid","e10","sqrt","e5","e2","tickIncrement","stop","step","max","power","LN10","pow","initRange","domain","range","factory","definition","create","Color","darker","brighter","reI","reN","reP","reHex","reRgbInteger","reRgbPercent","reRgbaInteger","reRgbaPercent","reHslPercent","reHslaPercent","named","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","green","greenyellow","grey","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen","color_formatHex","rgb","formatHex","color_formatRgb","formatRgb","color","format","m","parseInt","rgbn","Rgb","rgba","hsla","r","g","rgbConvert","opacity","rgb_formatHex","hex","rgb_formatRgb","isNaN","h","Hsl","hslConvert","hsl2rgb","m1","m2","copy","channels","displayable","formatHsl","deg2rad","PI","rad2deg","Xn","Yn","Zn","t0","t1","t2","t3","labConvert","Lab","Hcl","hcl2lab","z","rgb2lrgb","y","xyz2lab","lab2xyz","lrgb2rgb","c","cos","sin","atan2","hclConvert","A","B","C","D","E","ED","EB","BC_DA","Cubehelix","constant","gamma","nogamma","exponential","linear","bl","cubehelixConvert","cosh","sinh","rgb$1","rgbGamma","numberArray","genericArray","nb","na","interpolate","setTime","interpolateNumber","reA","reB","am","bm","bs","bi","q","one","zero","ArrayBuffer","isView","DataView","interpolateRound","unit","identity","normalize","bimap","d0","d1","r0","r1","polymap","reverse","transformer","transform","untransform","unknown","piecewise","output","interpolate$1","clamp","rescale","scale","invert","rangeRound","u","formatDecimal","toExponential","coefficient","exponent","abs","prefixExponent","re","formatSpecifier","specifier","FormatSpecifier","fill","align","sign","symbol","comma","precision","formatRounded","formatTypes","toFixed","toPrecision","toUpperCase","identity$1","locale","formatPrefix","prefixes","formatLocale","grouping","thousands","group","Number","currencyPrefix","currency","currencySuffix","decimal","numerals","formatNumerals","percent","minus","nan","newFormat","prefix","suffix","formatType","maybeSuffix","valuePrefix","valueSuffix","valueNegative","i1","i0","formatTrim","charCodeAt","Infinity","padding","tickFormat","step0","step1","tickStep","precisionPrefix","precisionRound","precisionFixed","linearish","ticks","isFinite","ceil","nice","linear$1","t0$1","t1$1","newInterval","floori","offseti","field","interval","offset","previous","millisecond","durationSecond","durationMinute","durationHour","durationDay","durationWeek","getMilliseconds","getSeconds","getMinutes","getHours","setHours","setDate","getTimezoneOffset","weekday","sunday","monday","thursday","setMonth","setFullYear","setUTCSeconds","setUTCMinutes","utcDay","setUTCHours","setUTCDate","getUTCDate","utcWeekday","getUTCDay","utcSunday","utcMonday","utcThursday","utcYear","setUTCMonth","getUTCMonth","getUTCFullYear","setUTCFullYear","localDate","H","M","S","L","utcDate","UTC","newDate","locale$1","utcFormat","utcParse","pads","numberRe","percentRe","requoteRe","pad","requote","formatRe","formatLookup","parseWeekdayNumberSunday","w","parseWeekdayNumberMonday","parseWeekNumberSunday","U","parseWeekNumberISO","V","parseWeekNumberMonday","W","parseFullYear","parseYear","parseZone","Z","parseQuarter","parseMonthNumber","parseDayOfMonth","parseDayOfYear","parseHour24","parseMinutes","parseSeconds","parseMilliseconds","parseMicroseconds","parseLiteralPercent","parseUnixTimestamp","Q","parseUnixTimestampSeconds","formatDayOfMonth","formatHour24","formatHour12","formatDayOfYear","formatMilliseconds","formatMicroseconds","formatMonthNumber","formatMinutes","formatSeconds","formatWeekdayNumberMonday","formatWeekNumberSunday","formatWeekNumberISO","formatWeekdayNumberSunday","formatWeekNumberMonday","formatYear","formatFullYear","formatZone","formatUTCDayOfMonth","formatUTCHour24","formatUTCHour12","formatUTCDayOfYear","formatUTCMilliseconds","getUTCMilliseconds","formatUTCMicroseconds","formatUTCMonthNumber","formatUTCMinutes","formatUTCSeconds","formatUTCWeekdayNumberMonday","dow","formatUTCWeekNumberSunday","formatUTCWeekNumberISO","formatUTCWeekdayNumberSunday","formatUTCWeekNumberMonday","formatUTCYear","formatUTCFullYear","formatUTCZone","formatLiteralPercent","formatUnixTimestamp","formatUnixTimestampSeconds","locale_dateTime","dateTime","locale_date","locale_time","locale_periods","periods","locale_weekdays","locale_shortWeekdays","shortDays","locale_months","locale_shortMonths","shortMonths","periodRe","periodLookup","weekdayRe","weekdayLookup","shortWeekdayRe","shortWeekdayLookup","monthRe","monthLookup","shortMonthRe","shortMonthLookup","formats","utcFormats","parses","parseSpecifier","charAt","newParse","week","day$1","X","formatLocale$1","defaultLocale$1","isoSpecifier","noop","dispatch","Dispatch","on","typename","types","that","args","xhtml","namespaces","xlink","xmlns","namespace","space","local","creatorInherit","ownerDocument","uri","namespaceURI","documentElement","createElementNS","creatorFixed","fullname","creator","none","empty","sparse","update","EnterNode","datum","_next","_parent","__data__","child","keyPrefix","bindIndex","enter","exit","groupLength","dataLength","bindKey","keyValue","nodeByKeyValue","keyValues","ascending$1","attrRemove","removeAttribute","attrRemoveNS","removeAttributeNS","attrConstant","attrConstantNS","setAttributeNS","attrFunction","attrFunctionNS","defaultView","styleRemove","removeProperty","styleConstant","priority","setProperty","styleFunction","propertyRemove","propertyConstant","propertyFunction","classArray","classList","ClassList","_node","_names","classedAdd","classedRemove","remove","classedTrue","classedFalse","classedFunction","textRemove","textConstant","textFunction","htmlRemove","htmlConstant","htmlFunction","raise","nextSibling","lower","previousSibling","constantNull","selection_cloneShallow","cloneNode","selection_cloneDeep","splice","contains","filterEvents","filterContextListener","listener","contextListener","related","relatedTarget","compareDocumentPosition","event1","event0","onRemove","__on","removeEventListener","capture","onAdd","wrap","customEvent","sourceEvent","params","createEvent","initEvent","cancelable","dispatchConstant","dispatchFunction","mouseenter","mouseleave","Selection","groups","parents","_groups","_parents","select","current","point","ownerSVGElement","createSVGPoint","clientX","clientY","matrixTransform","getScreenCTM","inverse","rect","clientLeft","clientTop","mouse","changedTouches","touch","touches","identifier","nopropagation","stopImmediatePropagation","noevent","preventDefault","constant$3","DragEvent","subject","active","dx","dy","defaultFilter","ctrlKey","button","defaultContainer","defaultSubject","defaultTouchable","navigator","maxTouchPoints","drag","mousedownx","mousedowny","mousemoving","touchending","touchable","gestures","clickDistance2","selection","mousedowned","touchstarted","touchmoved","touchended","gesture","beforestart","view","mousemoved","mouseupped","__noselect","MozUserSelect","nodrag","noclick","yesdrag","sublisteners","p0","clickDistance","subgroups","subnode","subgroup","selectAll","selectorAll","matches","matcher","each","bind","enterGroup","updateGroup","_enter","_exit","onenter","onupdate","onexit","append","merge","order","groups0","groups1","m0","merges","group0","group1","compareNode","sortgroups","sortgroup","nodes","attr","getAttributeNS","getPropertyValue","getComputedStyle","styleValue","classed","deep","typenames","parseTypenames$1","T$a","keyCodes","Slider","_T$a","_this19","connected","mouseEvent","knob","background","trackFill","track","origin","renderTicks","changeValue","dragUpdate","dispatchChange","onKeyDown","keyCode","quantizeValue","validateValueRange","dispatchInput","Event","_this20","ticksContainer","tick","logo","DistillHeader","_T$b","T$b","DistillAppendix","_HTMLElement9","githubCompareUpdatesUrl","serializeFrontmatterToBibtex","appendixTemplate","DistillFooter","_T$c","T$c","distill","runlevel","initialize","templateIsLoading","styleTagId","styleTag","cssTextTag","firstScriptTag","makeStyleTag","_i2","_Object$entries2","_Object$entries2$_i","functionName","components","distillComponents","_step21","_iterator21","component","is","templateHasLoaded","browserSupportsAllFeatures","load","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","Function"],"sourceRoot":""}
\ No newline at end of file
diff --git a/dist/e1951519f6f0596f7356.woff2 b/dist/e1951519f6f0596f7356.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..249a28662218a7a17ad8bd1fe072169ecb666a49
Binary files /dev/null and b/dist/e1951519f6f0596f7356.woff2 differ
diff --git a/dist/e418bf257af1052628d8.woff2 b/dist/e418bf257af1052628d8.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..680c13085076a2f6c5a7e695935ec3f21cddb65f
Binary files /dev/null and b/dist/e418bf257af1052628d8.woff2 differ
diff --git a/dist/ed305b5434865e06ffde.ttf b/dist/ed305b5434865e06ffde.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..b8461b275fae76efd0d21fd0f1aaa696a5b10f9a
Binary files /dev/null and b/dist/ed305b5434865e06ffde.ttf differ
diff --git a/dist/f650f111a3b890d116f1.woff2 b/dist/f650f111a3b890d116f1.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..eb24a7ba282b03d830fa6c63ee897d92a5188736
Binary files /dev/null and b/dist/f650f111a3b890d116f1.woff2 differ
diff --git a/dist/fa675e5e4bec9eb250b6.ttf b/dist/fa675e5e4bec9eb250b6.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..0e9b0f354ad460202bba554359f5adcc8da666b7
Binary files /dev/null and b/dist/fa675e5e4bec9eb250b6.ttf differ
diff --git a/dist/fc9ba5249878cd8f8d88.ttf b/dist/fc9ba5249878cd8f8d88.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..fd679bf374af72f2a183b97b40c9c7e9e51fbe5e
Binary files /dev/null and b/dist/fc9ba5249878cd8f8d88.ttf differ
diff --git a/dist/fd947498bc16392e76c2.woff b/dist/fd947498bc16392e76c2.woff
new file mode 100644
index 0000000000000000000000000000000000000000..6f43b594b6c1d863a0e3f93b001f8dd503316464
Binary files /dev/null and b/dist/fd947498bc16392e76c2.woff differ
diff --git a/dist/fe2176f79edaa716e621.woff2 b/dist/fe2176f79edaa716e621.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..b50920e138807f385d0b0359f4f0f09891f18406
Binary files /dev/null and b/dist/fe2176f79edaa716e621.woff2 differ
diff --git a/dist/index.html b/dist/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..15806df26ddd3ef0e390b23f7d2ab936c84a22bd
--- /dev/null
+++ b/dist/index.html
@@ -0,0 +1,917 @@
+
+
+
+
+
+
+
+ 🔭 Ultra-Guide to Scaling LLM training
+
+
Scaling Models and Hardware
+
+
+
+
+ Experiment setup
+
+
+
+
+
+
+
+
+
+
+ 1B (1)
+ 7B
+ 70B
+ 340B (2)
+ 400B (3)
+
+
+ N Layers
+ 24
+ 32
+ 80
+ 96
+ 126
+
+
+ N Heads
+ 32
+ 32
+ 64
+ 96
+ 128
+
+
+
+ Dimension
+ 2048
+ 4096
+ 8192
+ 18432
+ 16384
+ Distribution Methods
+
+
+
+
+No Parallelism
+
+
+
+
+
+
+
+
+
+BS=1
?A brief overview of memory usage in Transformers
+
+
+
+
+import torch; torch.ones((1, 1)).to("cuda")
and then checking the GPU memory with nvidia-smi
+
+
+
+
+
+
+
+ Model parameters
+ Memory requirements
+
+
+ 1B
+ 16 GB
+
+
+ 7B
+ 112 GB
+
+
+ 70B
+ 1120 GB
+
+
+
+405B
+ 6480 GB
+
+
+
+
Activation recomputation
+
+
+
+
+
+
+full
strategy since it requires a forward pass through each layer essentially adding a full forward pass during the backward pass. This strategy saves the most memory but is the most expensive one in terms of compute. This increases the compute cost by up to 30-40% which is very noticeable.
+
+
full
settings.micro batch size
(MBS) the batch size for each forward pass on a single node (the number of samples flowing through the model in one forward pass). We’ll refer to the overall batch size between each optimizer step as the global batch size
(GBS). If we do one optimizer step each 8 forward/backward pass, the global batch size
will be 8 times the micro batch size
.global batch size
thus corresponds to what we’ve called up to now just batch size
for simplicity (we now make the terms more precise to avoid ambiguity).
+
+
Data Parallelism
+
+
+
+
+
+
+Tensor Parallelism
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def example_gelu():
+ from torch.nn.functional import gelu
+
+ X = torch.randn(4, 2, device="cuda", dtype=torch.float32)
+ W = torch.randn(2, 2, device="cuda", dtype=torch.float32)
+
+ W_0, W_1 = W.chunk(2, dim=1)
+
+ # Column linear
+ y_col_1 = torch.cat([gelu(X @ W_0), gelu(X @ W_1)], dim=1)
+ y_col_2 = gelu(torch.cat([X @ W_0, X @ W_1], dim=1))
+
+ # All match
+ torch.testing.assert_close(y_col_1, y_col_2, rtol=1e-5, atol=1e-5)
+
+ # Row linear
+ X_0, X_1 = X.chunk(2, dim=1)
+ W_0, W_1 = W.chunk(2, dim=0)
+ y_row_1 = gelu(X_0 @ W_0) + gelu(X_1 @ W_1)
+ y_row_2 = gelu(X_0 @ W_0 + X_1 @ W_1)
+
+ # Mismatch
+ torch.testing.assert_close(y_row_1, y_row_2, rtol=1e-5, atol=1e-5)
+
+
+
+
+
+
+
+
+
+
+
+def column_linear_forward(X, local_W, group):
+ Y_local = X @ local_W.t()
+ return Y_local
+
+def column_linear_backward(local_grad_Y, X, local_W, group):
+ local_grad_X = local_grad_Y @ local_W
+ grad_W = local_grad_Y.t() @ X
+ return local_grad_X, grad_W
+
+def row_linear_forward(local_X, local_W, group):
+ Y_local = local_X @ local_W.t()
+ dist.all_reduce(Y_local, group=group)
+ Y = Y_local
+ return Y
+
+def row_linear_backward(grad_Y, X, local_W, group):
+ local_grad_X = grad_Y @ local_W
+ grad_W = grad_Y.t() @ X
+ return local_grad_X, grad_W
+
+def example_column_row_linear():
+ # torchrun --nproc_per_node=2 tp_all_reduce.py
+ group = dist.distributed_c10d._get_default_group()
+
+ X_ref = torch.arange(4 * 2, device="cuda", dtype=torch.float32, requires_grad=True).reshape(4, 2)
+ W_ref_layer1 = torch.arange(1, 5, device="cuda", dtype=torch.float32, requires_grad=True).reshape(2, 2) * 10
+ W_ref_layer2 = torch.arange(1, 5, device="cuda", dtype=torch.float32, requires_grad=True).reshape(2, 2)
+
+ X_ref.retain_grad()
+ W_ref_layer1.retain_grad()
+ W_ref_layer2.retain_grad()
+
+ dist.broadcast(X_ref, src=0, group=group)
+ dist.broadcast(W_ref_layer1, src=0, group=group)
+ dist.broadcast(W_ref_layer2, src=0, group=group)
+
+ X = X_ref.clone()
+ W_layer1 = W_ref_layer1.clone()
+ W_layer2 = W_ref_layer2.clone()
+
+ # Forward
+ Y_ref_linear1 = X_ref @ W_ref_layer1.t()
+ Y_ref_linear1.retain_grad()
+
+ # We will transpose for matrix multiplication. As a result, we need to split row-wise
+ Y_local_linear1 = column_linear_forward(X, split_tensor(W_layer1, dim=0), group)
+
+ torch.testing.assert_close(Y_local_linear1, split_tensor(Y_ref_linear1, dim=1), rtol=1e-5, atol=1e-5)
+
+ Y_local_linear2 = row_linear_forward(Y_local_linear1, split_tensor(W_ref_layer2, dim=1), group)
+ Y_ref_linear2 = Y_ref_linear1 @ W_ref_layer2.t()
+ torch.testing.assert_close(Y_local_linear2, Y_ref_linear2, rtol=1e-5, atol=1e-5)
+
+ # Backward
+ Y_ref_linear2.sum().backward()
+
+ grad_Y = torch.ones_like(Y_ref_linear2)
+ grad_X_linear2, grad_W_linear2 = row_linear_backward(grad_Y, Y_local_linear1, split_tensor(W_layer2, dim=1), group)
+
+ torch.testing.assert_close(grad_X_linear2, split_tensor(Y_ref_linear1.grad, dim=1), rtol=1e-5, atol=1e-5)
+ torch.testing.assert_close(grad_W_linear2, split_tensor(W_ref_layer2.grad, dim=1), rtol=1e-5, atol=1e-5)
+
+ grad_X, grad_W = column_linear_backward(grad_X_linear2, X, split_tensor(W_layer1, dim=0), group)
+
+ torch.testing.assert_close(grad_X, X_ref.grad, rtol=1e-5, atol=1e-5)
+ torch.testing.assert_close(grad_W, split_tensor(W_ref_layer1.grad, dim=0), rtol=1e-5, atol=1e-5)
+
+if __name__ == "__main__":
+ dist.init_process_group("nccl", rank=int(os.environ["RANK"]), world_size=int(os.environ["WORLD_SIZE"]))
+ torch.cuda.set_device(int(os.environ["LOCAL_RANK"]))
+
+ example_column_row_linear()
+
+
+
+
+
Sequence Parallelism
+
+Context Parallelism
+
+
+
+
+Pipeline Parallelism
+
+Overlapping computation and communication
+
+ZeRO
+
+II – Architecture
+
+Transformers
+
+Choosing the right dimensions
+
+Positional Embeddings (Learned, RoPE, ALiBi)
+
+RoPE
+
+
+
+
+
+
+
+
+
+transformers
library:
+
Attention (MHA, MQA, GQA)
+
+Optimized Operations
+
+Flash Attention 1&2&3
+
+Fused Kernels
+
+III – Training Recipe
+
+Batch Size
+
+Initialization + rescaling activations inside the model
+
+Numerical Precision
+
+FP16/BF16/FP8
+
+Long Context Training
+
+Evaluation
+
+Infini-Attention
+
+Ring Attention
+
+RoPE scaling / Yarn
+
+References
+
+
+
+ Conclusion and looking forward
+ Citation
+ Penedo, et al., "The FineWeb Datasets: Decanting the Web for the Finest Text Data at Scale", 2024.
+ @misc{penedo2024finewebdatasetsdecantingweb,
+ title={The FineWeb Datasets: Decanting the Web for the Finest Text Data at Scale},
+ author={Guilherme Penedo and Hynek Kydlíček and Loubna Ben allal and Anton Lozhkov and Margaret Mitchell and Colin Raffel and Leandro Von Werra and Thomas Wolf},
+ year={2024},
+ eprint={2406.17557},
+ archivePrefix={arXiv},
+ primaryClass={cs.CL}
+ url={https://arxiv.org/abs/2406.17557},
+}
+-1&&e%1==0&&e
").length;a=N.t+te*i+10-K-A*ve*s}_e((o?"h":"v")+se._id+"title",{avoid:{selection:n.select(r).selectAll("g."+se._id+"tick"),side:q,offsetTop:o?0:N.t,offsetLeft:o?N.l:0,maxShift:o?R.width:R.height},attributes:{x:e,y:a,"text-anchor":"middle"},transform:{rotate:o?-90:0,offset:0}})}},i.previousPromises,function(){var n,l=K+M/2;-1===ge.indexOf("inside")&&(n=h.bBox(ye.node()),l+=o?n.width:n.height),pe=me.select("text");var u=0,f=o&&"top"===q,m=!o&&"right"===q,g=0;if(pe.node()&&!pe.classed(T.jsPlaceholder)){var y,x=me.select(".h"+se._id+"title-math-group").node();x&&(o&&ue||!o&&!ue)?(u=(n=h.bBox(x)).width,y=n.height):(u=(n=h.bBox(me.node())).right-N.l-(o?re:le),y=n.bottom-N.t-(o?le:re),o||"top"!==q||(l+=n.height,g=n.height)),m&&(pe.attr("transform",c(u/2+ve/2,0)),u*=2),l=Math.max(l,o?u:y)}var b=2*(o?O:D)+l+z+M/2,A=0;!o&&j.text&&"bottom"===L&&E<=0&&(b+=A=b/2,g+=A),R._hColorbarMoveTitle=A,R._hColorbarMoveCBTitle=g;var F=z+M,H=(o?re:le)-F/2-(o?O:0),U=(o?le:re)-(o?$:D+g-A);e.select("."+T.cbbg).attr("x",H).attr("y",U).attr(o?"width":"height",Math.max(b-A,2)).attr(o?"height":"width",Math.max($+F,2)).call(p.fill,C).call(p.stroke,t.bordercolor).style("stroke-width",z);var V=m?Math.max(u-10,0):0;e.selectAll("."+T.cboutline).attr("x",(o?re:le+O)+V).attr("y",(o?le+D-$:re)+(f?xe:0)).attr(o?"width":"height",Math.max(K,2)).attr(o?"height":"width",Math.max($-(o?2*D+xe:2*O+V),2)).call(p.stroke,t.outlinecolor).style({fill:"none","stroke-width":M});var Y=o?ne*b:0,X=o?0:(1-ae)*b-g;if(Y=P?N.l-Y:-Y,X=I?N.t-X:-X,e.attr("transform",c(Y,X)),!o&&(z||a(C).getAlpha()&&!a.equals(R.paper_bgcolor,C))){var G=ye.selectAll("text"),W=G[0].length,Z=e.select("."+T.cbbg).node(),J=h.bBox(Z),Q=h.getTranslate(e);G.each((function(e,t){var r=W-1;if(0===t||t===r){var n,a=h.bBox(this),i=h.getTranslate(this);if(t===r){var o=a.right+i.x;(n=J.right+Q.x+le-z-2+B-o)>0&&(n=0)}else if(0===t){var l=a.left+i.x;(n=J.left+Q.x+le+z+2-l)<0&&(n=0)}n&&(W<3?this.setAttribute("transform","translate("+n+",0) "+this.getAttribute("transform")):this.setAttribute("visibility","hidden"))}}))}var ee={},te=w[S],ie=k[S],oe=w[L],ce=k[L],fe=b-K;o?("pixels"===d?(ee.y=E,ee.t=$*oe,ee.b=$*ce):(ee.t=ee.b=0,ee.yt=E+s*oe,ee.yb=E-s*ce),"pixels"===_?(ee.x=B,ee.l=b*te,ee.r=b*ie):(ee.l=fe*te,ee.r=fe*ie,ee.xl=B-v*te,ee.xr=B+v*ie)):("pixels"===d?(ee.x=B,ee.l=$*te,ee.r=$*ie):(ee.l=ee.r=0,ee.xl=B+s*te,ee.xr=B-s*ie),"pixels"===_?(ee.y=1-E,ee.t=b*oe,ee.b=b*ce):(ee.t=fe*oe,ee.b=fe*ce,ee.yt=E-v*oe,ee.yb=E+v*ce));var de=t.y<.5?"b":"t",he=t.x<.5?"l":"r";r._fullLayout._reservedMargin[t._id]={};var be={r:R.width-H-Y,l:H+ee.r,b:R.height-U-X,t:U+ee.b};P&&I?i.autoMargin(r,t._id,ee):P?r._fullLayout._reservedMargin[t._id][de]=be[de]:I||o?r._fullLayout._reservedMargin[t._id][he]=be[he]:r._fullLayout._reservedMargin[t._id][de]=be[de]}],r)}(r,t,e);v&&v.then&&(e._promises||[]).push(v),e._context.edits.colorbarPosition&&function(e,t,r){var n,a,i,l="v"===t.orientation,u=r._fullLayout._size;s.init({element:e.node(),gd:r,prepFn:function(){n=e.attr("transform"),d(e)},moveFn:function(r,o){e.attr("transform",n+c(r,o)),a=s.align((l?t._uFrac:t._vFrac)+r/u.w,l?t._thickFrac:t._lenFrac,0,1,t.xanchor),i=s.align((l?t._vFrac:1-t._uFrac)-o/u.h,l?t._lenFrac:t._thickFrac,0,1,t.yanchor);var f=s.getCursor(a,i,t.xanchor,t.yanchor);d(e,f)},doneFn:function(){if(d(e),void 0!==a&&void 0!==i){var n={};n[t._propPrefix+"x"]=a,n[t._propPrefix+"y"]=i,void 0!==t._traceIndex?o.call("_guiRestyle",r,n,t._traceIndex):o.call("_guiRelayout",r,n)}}})}(r,t,e)})),t.exit().each((function(t){i.autoMargin(e,t._id)})).remove(),t.order()}}},553:function(e,t,r){"use strict";var n=r(3400);e.exports=function(e){return n.isPlainObject(e.colorbar)}},5080:function(e,t,r){"use strict";e.exports={moduleType:"component",name:"colorbar",attributes:r(616),supplyDefaults:r(4013),draw:r(7848).draw,hasColorbar:r(553)}},9084:function(e,t,r){"use strict";var n=r(616),a=r(3756).counter,i=r(2996),o=r(8304).scales;function l(e){return"`"+e+"`"}i(o),e.exports=function(e,t){e=e||"";var r,i=(t=t||{}).cLetter||"c",s=("onlyIfNumerical"in t?t.onlyIfNumerical:Boolean(e),"noScale"in t?t.noScale:"marker.line"===e),u="showScaleDflt"in t?t.showScaleDflt:"z"===i,c="string"==typeof t.colorscaleDflt?o[t.colorscaleDflt]:null,f=t.editTypeOverride||"",d=e?e+".":"";"colorAttr"in t?(r=t.colorAttr,t.colorAttr):l(d+(r={z:"z",c:"color"}[i]));var h=i+"auto",p=i+"min",m=i+"max",g=i+"mid",v=(l(d+h),l(d+p),l(d+m),{});v[p]=v[m]=void 0;var y={};y[h]=!1;var x={};return"color"===r&&(x.color={valType:"color",arrayOk:!0,editType:f||"style"},t.anim&&(x.color.anim=!0)),x[h]={valType:"boolean",dflt:!0,editType:"calc",impliedEdits:v},x[p]={valType:"number",dflt:null,editType:f||"plot",impliedEdits:y},x[m]={valType:"number",dflt:null,editType:f||"plot",impliedEdits:y},x[g]={valType:"number",dflt:null,editType:"calc",impliedEdits:v},x.colorscale={valType:"colorscale",editType:"calc",dflt:c,impliedEdits:{autocolorscale:!1}},x.autocolorscale={valType:"boolean",dflt:!1!==t.autoColorDflt,editType:"calc",impliedEdits:{colorscale:void 0}},x.reversescale={valType:"boolean",dflt:!1,editType:"plot"},s||(x.showscale={valType:"boolean",dflt:u,editType:"calc"},x.colorbar=n),t.noColorAxis||(x.coloraxis={valType:"subplotid",regex:a("coloraxis"),dflt:null,editType:"calc"}),x}},7128:function(e,t,r){"use strict";var n=r(8248),a=r(3400),i=r(4288).extractOpts;e.exports=function(e,t,r){var o,l=e._fullLayout,s=r.vals,u=r.containerStr,c=u?a.nestedProperty(t,u).get():t,f=i(c),d=!1!==f.auto,h=f.min,p=f.max,m=f.mid,g=function(){return a.aggNums(Math.min,null,s)},v=function(){return a.aggNums(Math.max,null,s)};void 0===h?h=g():d&&(h=c._colorAx&&n(h)?Math.min(h,g()):g()),void 0===p?p=v():d&&(p=c._colorAx&&n(p)?Math.max(p,v()):v()),d&&void 0!==m&&(p-m>m-h?h=m-(p-m):p-m4/3-l?o:l}},7416:function(e,t,r){"use strict";var n=r(3400),a=[["sw-resize","s-resize","se-resize"],["w-resize","move","e-resize"],["nw-resize","n-resize","ne-resize"]];e.exports=function(e,t,r,i){return e="left"===r?0:"center"===r?1:"right"===r?2:n.constrain(Math.floor(3*e),0,2),t="bottom"===i?0:"middle"===i?1:"top"===i?2:n.constrain(Math.floor(3*t),0,2),a[t][e]}},2760:function(e,t){"use strict";t.selectMode=function(e){return"lasso"===e||"select"===e},t.drawMode=function(e){return"drawclosedpath"===e||"drawopenpath"===e||"drawline"===e||"drawrect"===e||"drawcircle"===e},t.openMode=function(e){return"drawline"===e||"drawopenpath"===e},t.rectMode=function(e){return"select"===e||"drawline"===e||"drawrect"===e||"drawcircle"===e},t.freeMode=function(e){return"lasso"===e||"drawclosedpath"===e||"drawopenpath"===e},t.selectingOrDrawing=function(e){return t.freeMode(e)||t.rectMode(e)}},6476:function(e,t,r){"use strict";var n=r(9128),a=r(2264),i=r(9184),o=r(3400).removeElement,l=r(3816),s=e.exports={};s.align=r(8316),s.getCursor=r(7416);var u=r(2616);function c(){var e=document.createElement("div");e.className="dragcover";var t=e.style;return t.position="fixed",t.left=0,t.right=0,t.top=0,t.bottom=0,t.zIndex=999999999,t.background="none",document.body.appendChild(e),e}function f(e){return n(e.changedTouches?e.changedTouches[0]:e,document.body)}s.unhover=u.wrapped,s.unhoverRaw=u.raw,s.init=function(e){var t,r,n,u,d,h,p,m,g=e.gd,v=1,y=g._context.doubleClickDelay,x=e.element;g._mouseDownTime||(g._mouseDownTime=0),x.style.pointerEvents="all",x.onmousedown=_,i?(x._ontouchstart&&x.removeEventListener("touchstart",x._ontouchstart),x._ontouchstart=_,x.addEventListener("touchstart",_,{passive:!1})):x.ontouchstart=_;var b=e.clampFn||function(e,t,r){return Math.abs(e)([\s\S]*)<\/extra>/;function E(e,t){var r=t.gd,a=r._fullLayout,i=t.hovermode,l=t.rotateLabels,c=t.bgColor,d=t.container,m=t.outerContainer,g=t.commonLabelOpts||{};if(0===e.length)return[[]];var w=t.fontFamily||x.HOVERFONT,k=t.fontSize||x.HOVERFONTSIZE,T=t.fontWeight||a.font.weight,M=t.fontStyle||a.font.style,S=t.fontVariant||a.font.variant,L=t.fontTextcase||a.font.textcase,O=t.fontLineposition||a.font.lineposition,B=t.fontShadow||a.font.shadow,E=e[0],P=E.xa,R=E.ya,N=i.charAt(0),F=N+"Label",H=E[F];if(void 0===H&&"multicategory"===P.type)for(var j=0;j
"),void 0!==e.yLabel&&(s+="y: "+e.yLabel+"
"),"choropleth"!==e.trace.type&&"choroplethmapbox"!==e.trace.type&&(s+=(s?"z: ":"")+e.zLabel)):t&&e[u+"Label"]===a?s=e[c+"Label"]||"":void 0===e.xLabel?void 0!==e.yLabel&&"scattercarpet"!==e.trace.type&&(s=e.yLabel):s=void 0===e.yLabel?e.xLabel:"("+e.xLabel+", "+e.yLabel+")",!e.text&&0!==e.text||Array.isArray(e.text)||(s+=(s?"
":"")+e.text),void 0!==e.extraText&&(s+=(s?"
":"")+e.extraText),i&&""===s&&!e.hovertemplate&&(""===l&&i.remove(),s=l);var f=e.hovertemplate||!1;if(f){var d=e.hovertemplateLabels||e;e[u+"Label"]!==a&&(d[u+"other"]=d[u+"Val"],d[u+"otherLabel"]=d[u+"Label"]),s=(s=o.hovertemplateString(f,d,n._d3locale,e.eventData[0]||{},e.trace._meta)).replace(B,(function(t,r){return l=j(r,e.nameLength),""}))}return[s,l]}function P(e,t){var r=0,n=e.offset;return t&&(n*=-M,r=e.offset*T),{x:r,y:n}}function R(e,t,r,a){var i=function(e){return e*r},o=function(e){return e*a};e.each((function(e){var r=n.select(this);if(e.del)return r.remove();var a,l,s,u,c=r.select("text.nums"),d=e.anchor,p="end"===d?-1:1,m=(u=(s=(l={start:1,end:-1,middle:0}[(a=e).anchor])*(z+C))+l*(a.txwidth+C),"middle"===a.anchor&&(s-=a.tx2width/2,u+=a.txwidth/2+C),{alignShift:l,textShiftX:s,text2ShiftX:u}),g=P(e,t),v=g.x,y=g.y,x="middle"===d;r.select("path").attr("d",x?"M-"+i(e.bx/2+e.tx2width/2)+","+o(y-e.by/2)+"h"+i(e.bx)+"v"+o(e.by)+"h-"+i(e.bx)+"Z":"M0,0L"+i(p*z+v)+","+o(z+y)+"v"+o(e.by/2-z)+"h"+i(p*e.bx)+"v-"+o(e.by)+"H"+i(p*z+v)+"V"+o(y-z)+"Z");var b=v+m.textShiftX,_=y+e.ty0-e.by/2+C,A=e.textAlign||"auto";"auto"!==A&&("left"===A&&"start"!==d?(c.attr("text-anchor","start"),b=x?-e.bx/2-e.tx2width/2+C:-e.bx-C):"right"===A&&"end"!==d&&(c.attr("text-anchor","end"),b=x?e.bx/2-e.tx2width/2-C:e.bx+C)),c.call(f.positionText,i(b),o(_)),e.tx2width&&(r.select("text.name").call(f.positionText,i(m.text2ShiftX+m.alignShift*C+v),o(y+e.ty0-e.by/2+C)),r.select("rect").call(h.setRect,i(m.text2ShiftX+(m.alignShift-1)*e.tx2width/2+v),o(y-e.by/2-1),i(e.tx2width),o(e.by+2)))}))}function N(e,t){var r=e.index,n=e.trace||{},i=e.cd[0],l=e.cd[r]||{};function s(e){return e||a(e)&&0===e}var u=Array.isArray(r)?function(e,t){var a=o.castOption(i,r,e);return s(a)?a:o.extractOption({},n,"",t)}:function(e,t){return o.extractOption(l,n,e,t)};function c(t,r,n){var a=u(r,n);s(a)&&(e[t]=a)}if(c("hoverinfo","hi","hoverinfo"),c("bgcolor","hbg","hoverlabel.bgcolor"),c("borderColor","hbc","hoverlabel.bordercolor"),c("fontFamily","htf","hoverlabel.font.family"),c("fontSize","hts","hoverlabel.font.size"),c("fontColor","htc","hoverlabel.font.color"),c("fontWeight","htw","hoverlabel.font.weight"),c("fontStyle","hty","hoverlabel.font.style"),c("fontVariant","htv","hoverlabel.font.variant"),c("nameLength","hnl","hoverlabel.namelength"),c("textAlign","hta","hoverlabel.align"),e.posref="y"===t||"closest"===t&&"h"===n.orientation?e.xa._offset+(e.x0+e.x1)/2:e.ya._offset+(e.y0+e.y1)/2,e.x0=o.constrain(e.x0,0,e.xa._length),e.x1=o.constrain(e.x1,0,e.xa._length),e.y0=o.constrain(e.y0,0,e.ya._length),e.y1=o.constrain(e.y1,0,e.ya._length),void 0!==e.xLabelVal&&(e.xLabel="xLabel"in e?e.xLabel:g.hoverLabelText(e.xa,e.xLabelVal,n.xhoverformat),e.xVal=e.xa.c2d(e.xLabelVal)),void 0!==e.yLabelVal&&(e.yLabel="yLabel"in e?e.yLabel:g.hoverLabelText(e.ya,e.yLabelVal,n.yhoverformat),e.yVal=e.ya.c2d(e.yLabelVal)),void 0!==e.zLabelVal&&void 0===e.zLabel&&(e.zLabel=String(e.zLabelVal)),!(isNaN(e.xerr)||"log"===e.xa.type&&e.xerr<=0)){var f=g.tickText(e.xa,e.xa.c2l(e.xerr),"hover").text;void 0!==e.xerrneg?e.xLabel+=" +"+f+" / -"+g.tickText(e.xa,e.xa.c2l(e.xerrneg),"hover").text:e.xLabel+=" ± "+f,"x"===t&&(e.distance+=1)}if(!(isNaN(e.yerr)||"log"===e.ya.type&&e.yerr<=0)){var d=g.tickText(e.ya,e.ya.c2l(e.yerr),"hover").text;void 0!==e.yerrneg?e.yLabel+=" +"+d+" / -"+g.tickText(e.ya,e.ya.c2l(e.yerrneg),"hover").text:e.yLabel+=" ± "+d,"y"===t&&(e.distance+=1)}var h=e.hoverinfo||e.trace.hoverinfo;return h&&"all"!==h&&(-1===(h=Array.isArray(h)?h:h.split("+")).indexOf("x")&&(e.xLabel=void 0),-1===h.indexOf("y")&&(e.yLabel=void 0),-1===h.indexOf("z")&&(e.zLabel=void 0),-1===h.indexOf("text")&&(e.text=void 0),-1===h.indexOf("name")&&(e.name=void 0)),e}function F(e,t,r){var n,a,o=r.container,l=r.fullLayout,s=l._size,u=r.event,c=!!t.hLinePoint,f=!!t.vLinePoint;if(o.selectAll(".spikeline").remove(),f||c){var d=p.combine(l.plot_bgcolor,l.paper_bgcolor);if(c){var m,v,y=t.hLinePoint;n=y&&y.xa,"cursor"===(a=y&&y.ya).spikesnap?(m=u.pointerX,v=u.pointerY):(m=n._offset+y.x,v=a._offset+y.y);var x,b,_=i.readability(y.color,d)<1.5?p.contrast(d):y.color,A=a.spikemode,w=a.spikethickness,k=a.spikecolor||_,T=g.getPxPosition(e,a);if(-1!==A.indexOf("toaxis")||-1!==A.indexOf("across")){if(-1!==A.indexOf("toaxis")&&(x=T,b=m),-1!==A.indexOf("across")){var M=a._counterDomainMin,z=a._counterDomainMax;"free"===a.anchor&&(M=Math.min(M,a.position),z=Math.max(z,a.position)),x=s.l+M*s.w,b=s.l+z*s.w}o.insert("line",":first-child").attr({x1:x,x2:b,y1:v,y2:v,"stroke-width":w,stroke:k,"stroke-dasharray":h.dashStyle(a.spikedash,w)}).classed("spikeline",!0).classed("crisp",!0),o.insert("line",":first-child").attr({x1:x,x2:b,y1:v,y2:v,"stroke-width":w+2,stroke:d}).classed("spikeline",!0).classed("crisp",!0)}-1!==A.indexOf("marker")&&o.insert("circle",":first-child").attr({cx:T+("right"!==a.side?w:-w),cy:v,r:w,fill:k}).classed("spikeline",!0)}if(f){var C,S,L=t.vLinePoint;n=L&&L.xa,a=L&&L.ya,"cursor"===n.spikesnap?(C=u.pointerX,S=u.pointerY):(C=n._offset+L.x,S=a._offset+L.y);var O,D,B=i.readability(L.color,d)<1.5?p.contrast(d):L.color,E=n.spikemode,I=n.spikethickness,P=n.spikecolor||B,R=g.getPxPosition(e,n);if(-1!==E.indexOf("toaxis")||-1!==E.indexOf("across")){if(-1!==E.indexOf("toaxis")&&(O=R,D=S),-1!==E.indexOf("across")){var N=n._counterDomainMin,F=n._counterDomainMax;"free"===n.anchor&&(N=Math.min(N,n.position),F=Math.max(F,n.position)),O=s.t+(1-F)*s.h,D=s.t+(1-N)*s.h}o.insert("line",":first-child").attr({x1:C,x2:C,y1:O,y2:D,"stroke-width":I,stroke:P,"stroke-dasharray":h.dashStyle(n.spikedash,I)}).classed("spikeline",!0).classed("crisp",!0),o.insert("line",":first-child").attr({x1:C,x2:C,y1:O,y2:D,"stroke-width":I+2,stroke:d}).classed("spikeline",!0).classed("crisp",!0)}-1!==E.indexOf("marker")&&o.insert("circle",":first-child").attr({cx:C,cy:R-("top"!==n.side?I:-I),r:I,fill:P}).classed("spikeline",!0)}}}function H(e,t){return!t||t.vLinePoint!==e._spikepoints.vLinePoint||t.hLinePoint!==e._spikepoints.hLinePoint}function j(e,t){return f.plainText(e||"",{len:t,allowedTags:["br","sub","sup","b","i","em"]})}function q(e,t,r){var n=t[e+"a"],a=t[e+"Val"],i=t.cd[0];if("category"===n.type||"multicategory"===n.type)a=n._categoriesMap[a];else if("date"===n.type){var o=t.trace[e+"periodalignment"];if(o){var l=t.cd[t.index],s=l[e+"Start"];void 0===s&&(s=l[e]);var u=l[e+"End"];void 0===u&&(u=l[e]);var c=u-s;"end"===o?a+=c:"middle"===o&&(a+=c/2)}a=n.d2c(a)}return i&&i.t&&i.t.posLetter===n._id&&("group"!==r.boxmode&&"group"!==r.violinmode||(a+=i.t.dPos)),a}function U(e){return e.offsetTop+e.clientTop}function V(e){return e.offsetLeft+e.clientLeft}function Y(e,t){var r=e._fullLayout,n=t.getBoundingClientRect(),a=n.left,i=n.top,l=a+n.width,s=i+n.height,u=o.apply3DTransform(r._invTransform)(a,i),c=o.apply3DTransform(r._invTransform)(l,s),f=u[0],d=u[1],h=c[0],p=c[1];return{x:f,y:d,width:h-f,height:p-d,top:Math.min(d,p),left:Math.min(f,h),right:Math.max(f,h),bottom:Math.max(d,p)}}},6132:function(e,t,r){"use strict";var n=r(3400),a=r(6308),i=r(624).isUnifiedHover;e.exports=function(e,t,r,o){o=o||{};var l=t.legend;function s(e){o.font[e]||(o.font[e]=l?t.legend.font[e]:t.font[e])}t&&i(t.hovermode)&&(o.font||(o.font={}),s("size"),s("family"),s("color"),s("weight"),s("style"),s("variant"),l?(o.bgcolor||(o.bgcolor=a.combine(t.legend.bgcolor,t.paper_bgcolor)),o.bordercolor||(o.bordercolor=t.legend.bordercolor)):o.bgcolor||(o.bgcolor=t.paper_bgcolor)),r("hoverlabel.bgcolor",o.bgcolor),r("hoverlabel.bordercolor",o.bordercolor),r("hoverlabel.namelength",o.namelength),n.coerceFont(r,"hoverlabel.font",o.font),r("hoverlabel.align",o.align)}},1008:function(e,t,r){"use strict";var n=r(3400),a=r(5460);e.exports=function(e,t){function r(r,i){return void 0!==t[r]?t[r]:n.coerce(e,t,a,r,i)}return r("clickmode"),r("hoversubplots"),r("hovermode")}},3024:function(e,t,r){"use strict";var n=r(3428),a=r(3400),i=r(6476),o=r(624),l=r(5460),s=r(3292);e.exports={moduleType:"component",name:"fx",constants:r(2456),schema:{layout:l},attributes:r(5756),layoutAttributes:l,supplyLayoutGlobalDefaults:r(1976),supplyDefaults:r(5448),supplyLayoutDefaults:r(8336),calc:r(5056),getDistanceFunction:o.getDistanceFunction,getClosest:o.getClosest,inbox:o.inbox,quadrature:o.quadrature,appendArrayPointValue:o.appendArrayPointValue,castHoverOption:function(e,t,r){return a.castOption(e,t,"hoverlabel."+r)},castHoverinfo:function(e,t,r){return a.castOption(e,r,"hoverinfo",(function(r){return a.coerceHoverinfo({hoverinfo:r},{_module:e._module},t)}))},hover:s.hover,unhover:i.unhover,loneHover:s.loneHover,loneUnhover:function(e){var t=a.isD3Selection(e)?e:n.select(e);t.selectAll("g.hovertext").remove(),t.selectAll(".spikeline").remove()},click:r(2376)}},5460:function(e,t,r){"use strict";var n=r(2456),a=r(5376),i=a({editType:"none"});i.family.dflt=n.HOVERFONT,i.size.dflt=n.HOVERFONTSIZE,e.exports={clickmode:{valType:"flaglist",flags:["event","select"],dflt:"event",editType:"plot",extras:["none"]},dragmode:{valType:"enumerated",values:["zoom","pan","select","lasso","drawclosedpath","drawopenpath","drawline","drawrect","drawcircle","orbit","turntable",!1],dflt:"zoom",editType:"modebar"},hovermode:{valType:"enumerated",values:["x","y","closest",!1,"x unified","y unified"],dflt:"closest",editType:"modebar"},hoversubplots:{valType:"enumerated",values:["single","overlaying","axis"],dflt:"overlaying",editType:"none"},hoverdistance:{valType:"integer",min:-1,dflt:20,editType:"none"},spikedistance:{valType:"integer",min:-1,dflt:-1,editType:"none"},hoverlabel:{bgcolor:{valType:"color",editType:"none"},bordercolor:{valType:"color",editType:"none"},font:i,grouptitlefont:a({editType:"none"}),align:{valType:"enumerated",values:["left","right","auto"],dflt:"auto",editType:"none"},namelength:{valType:"integer",min:-1,dflt:15,editType:"none"},editType:"none"},selectdirection:{valType:"enumerated",values:["h","v","d","any"],dflt:"any",editType:"none"}}},8336:function(e,t,r){"use strict";var n=r(3400),a=r(5460),i=r(1008),o=r(6132);e.exports=function(e,t){function r(r,i){return n.coerce(e,t,a,r,i)}i(e,t)&&(r("hoverdistance"),r("spikedistance")),"select"===r("dragmode")&&r("selectdirection");var l=t._has("mapbox"),s=t._has("geo"),u=t._basePlotModules.length;"zoom"===t.dragmode&&((l||s)&&1===u||l&&s&&2===u)&&(t.dragmode="pan"),o(e,t,r),n.coerceFont(r,"hoverlabel.grouptitlefont",t.hoverlabel.font)}},1976:function(e,t,r){"use strict";var n=r(3400),a=r(6132),i=r(5460);e.exports=function(e,t){a(e,t,(function(r,a){return n.coerce(e,t,i,r,a)}))}},2704:function(e,t,r){"use strict";var n=r(3400),a=r(3756).counter,i=r(6968).u,o=r(3816).idRegex,l=r(1780),s={rows:{valType:"integer",min:1,editType:"plot"},roworder:{valType:"enumerated",values:["top to bottom","bottom to top"],dflt:"top to bottom",editType:"plot"},columns:{valType:"integer",min:1,editType:"plot"},subplots:{valType:"info_array",freeLength:!0,dimensions:2,items:{valType:"enumerated",values:[a("xy").toString(),""],editType:"plot"},editType:"plot"},xaxes:{valType:"info_array",freeLength:!0,items:{valType:"enumerated",values:[o.x.toString(),""],editType:"plot"},editType:"plot"},yaxes:{valType:"info_array",freeLength:!0,items:{valType:"enumerated",values:[o.y.toString(),""],editType:"plot"},editType:"plot"},pattern:{valType:"enumerated",values:["independent","coupled"],dflt:"coupled",editType:"plot"},xgap:{valType:"number",min:0,max:1,editType:"plot"},ygap:{valType:"number",min:0,max:1,editType:"plot"},domain:i({name:"grid",editType:"plot",noGridCell:!0},{}),xside:{valType:"enumerated",values:["bottom","bottom plot","top plot","top"],dflt:"bottom plot",editType:"plot"},yside:{valType:"enumerated",values:["left","left plot","right plot","right"],dflt:"left plot",editType:"plot"},editType:"plot"};function u(e,t,r){var n=t[r+"axes"],a=Object.keys((e._splomAxes||{})[r]||{});return Array.isArray(n)?n:a.length?a:void 0}function c(e,t,r,n,a,i){var o=t(e+"gap",r),l=t("domain."+e);t(e+"side",n);for(var s=new Array(a),u=l[0],c=(l[1]-u)/(a-o),f=c*(1-o),d=0;d1){d||h||p||"independent"===k("pattern")&&(d=!0),g._hasSubplotGrid=d;var x,b,_="top to bottom"===k("roworder"),A=d?.2:.1,w=d?.3:.1;m&&t._splomGridDflt&&(x=t._splomGridDflt.xside,b=t._splomGridDflt.yside),g._domains={x:c("x",k,A,x,y),y:c("y",k,w,b,v,_)}}else delete t.grid}function k(e,t){return n.coerce(r,g,s,e,t)}},contentDefaults:function(e,t){var r=t.grid;if(r&&r._domains){var n,a,i,o,l,s,c,d=e.grid||{},h=t._subplots,p=r._hasSubplotGrid,m=r.rows,g=r.columns,v="independent"===r.pattern,y=r._axisMap={};if(p){var x=d.subplots||[];s=r.subplots=new Array(m);var b=1;for(n=0;n=l&&(a-=l*Math.floor(a/l)),a<0?a=-1-a:a>=o&&(a=l-1-a),i+=e[a]*u[n];c[r]=i}return c},f.syncOrAsync=function(e,t,r){var n;function a(){return f.syncOrAsync(e,t,r)}for(;e.length;)if((n=(0,e.splice(0,1)[0])(t))&&n.then)return n.then(a);return r&&r(t)},f.stripTrailingSlash=function(e){return"/"===e.substr(-1)?e.substr(0,e.length-1):e},f.noneOrAll=function(e,t,r){if(e){var n,a=!1,i=!0;for(n=0;n
/i;t.BR_TAG_ALL=/
/gi;var _=/(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i,A=/(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i,w=/(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i,k=/(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i;function T(e,t){if(!e)return null;var r=e.match(t),n=r&&(r[3]||r[4]);return n&&S(n)}var M=/(^|;)\s*color:/;t.plainText=function(e,t){for(var r=void 0!==(t=t||{}).len&&-1!==t.len?t.len:1/0,n=void 0!==t.allowedTags?t.allowedTags:["br"],a=e.split(y),i=[],o="",l=0,s=0;s